From f1831912b04bc5c0851c9765a3f4831799968086 Mon Sep 17 00:00:00 2001 From: HiranoMasaaki Date: Tue, 17 Feb 2026 03:11:15 +0000 Subject: [PATCH 1/2] refactor: remove dead lazyInit field from codebase The lazyInit field existed in schemas and types but no code read it to control behavior. All hardcoded assignments set it to false, and SkillManager.fromExpert() connects all skills synchronously regardless. Remove the field, its E2E test (which tested batch event timing, not lazy initialization), and all references across 25 files. Supersedes #547. Co-Authored-By: Claude Opus 4.6 --- .changeset/remove-lazy-init.md | 8 +++ .github/workflows/e2e.yml | 1 - apps/create-expert/perstack.toml | 2 - benchmarks/mcp-startup/perstack.toml | 1 - benchmarks/production-perf/perstack.toml | 1 - docs/references/perstack-toml.md | 1 - e2e/experts/lazy-init.toml | 54 ------------------- e2e/experts/mixed-tools.toml | 1 - e2e/experts/skills.toml | 1 - e2e/experts/special-tools.toml | 1 - packages/core/src/schemas/expert.ts | 1 - packages/core/src/schemas/perstack-toml.ts | 2 - packages/core/src/schemas/skill.test.ts | 2 - packages/core/src/schemas/skill.ts | 3 -- packages/installer/src/expert-resolver.ts | 2 - .../src/helpers/resolve-expert.test.ts | 1 - .../runtime/src/helpers/resolve-expert.ts | 2 - packages/runtime/src/messages/message.test.ts | 3 +- .../adapters/in-memory-base-adapter.test.ts | 1 - .../src/adapters/lockfile-adapter.test.ts | 2 +- .../src/adapters/lockfile-adapter.ts | 1 - .../src/adapters/mcp-adapter.test.ts | 2 +- .../skill-manager/src/skill-manager.test.ts | 9 +--- packages/skill-manager/src/skill-manager.ts | 2 - .../src/utils/base-skill-helpers.test.ts | 2 +- 25 files changed, 13 insertions(+), 93 deletions(-) create mode 100644 .changeset/remove-lazy-init.md delete mode 100644 e2e/experts/lazy-init.toml diff --git a/.changeset/remove-lazy-init.md b/.changeset/remove-lazy-init.md new file mode 100644 index 00000000..5baddf70 --- /dev/null +++ b/.changeset/remove-lazy-init.md @@ -0,0 +1,8 @@ +--- +"@perstack/core": patch +"@perstack/skill-manager": patch +"@perstack/runtime": patch +"@perstack/installer": patch +--- + +Remove dead `lazyInit` field from schemas, types, and all usages diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 9df7630e..86736217 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -34,7 +34,6 @@ jobs: e2e/perstack-cli/providers.test.ts e2e/perstack-cli/error-handling.test.ts e2e/perstack-cli/interactive.test.ts - e2e/perstack-cli/lazy-init.test.ts e2e/perstack-cli/bundled-base.test.ts e2e/perstack-cli/versioned-base.test.ts e2e/perstack-cli/runtime-version.test.ts diff --git a/apps/create-expert/perstack.toml b/apps/create-expert/perstack.toml index 58560edd..dbf8e205 100644 --- a/apps/create-expert/perstack.toml +++ b/apps/create-expert/perstack.toml @@ -59,7 +59,6 @@ pick = ["readTextFile", "writeTextFile", "listDirectory", "think", "attemptCompl # args = ["-y", "some-mcp-server"] # requiredEnv = ["API_KEY"] # rule = "Instructions for using this skill" -# lazyInit = false ``` ## Your Workflow @@ -115,5 +114,4 @@ description = "Test-run expert definitions to verify they work correctly" command = "npx" packageName = "@perstack/create-expert-skill" requiredEnv = ["PROVIDER_API_KEY"] -lazyInit = true rule = "After creating or modifying an expert in perstack.toml, use runExpert to test it with a simple query. Review the activities to verify correctness." diff --git a/benchmarks/mcp-startup/perstack.toml b/benchmarks/mcp-startup/perstack.toml index 4df0364e..22bd6035 100644 --- a/benchmarks/mcp-startup/perstack.toml +++ b/benchmarks/mcp-startup/perstack.toml @@ -20,4 +20,3 @@ description = "Browser automation capabilities using Firecrawl" command = "npx" packageName = "firecrawl-mcp" requiredEnv = ["FIRECRAWL_API_KEY"] -lazyInit = false diff --git a/benchmarks/production-perf/perstack.toml b/benchmarks/production-perf/perstack.toml index 75057604..374cf135 100644 --- a/benchmarks/production-perf/perstack.toml +++ b/benchmarks/production-perf/perstack.toml @@ -29,4 +29,3 @@ pick = ["attemptCompletion"] type = "mcpStdioSkill" command = "node" args = ["../../e2e/fixtures/minimal-mcp-server.mjs"] -lazyInit = false diff --git a/docs/references/perstack-toml.md b/docs/references/perstack-toml.md index 1b00b3fc..cd5ea529 100644 --- a/docs/references/perstack-toml.md +++ b/docs/references/perstack-toml.md @@ -252,7 +252,6 @@ requiredEnv = ["API_KEY"] | `omit` | string[] | No | Tools to exclude (blacklist) | | `requiredEnv` | string[] | No | Required environment variables | | `allowedDomains` | string[] | No | Allowed domain patterns for network access | -| `lazyInit` | boolean | No | Delay initialization until first use (default: `false`) | ### MCP SSE Skill diff --git a/e2e/experts/lazy-init.toml b/e2e/experts/lazy-init.toml deleted file mode 100644 index 0bb317a6..00000000 --- a/e2e/experts/lazy-init.toml +++ /dev/null @@ -1,54 +0,0 @@ -model = "claude-haiku-4-5" - -[provider] -providerName = "anthropic" - -envPath = [".env", ".env.local"] - -# Expert with all skills lazyInit=false -# All skills should be fully connected BEFORE startRun -[experts."e2e-lazy-init-all-false"] -version = "1.0.0" -description = "E2E test expert with all lazyInit = false" -instruction = """ -Call attemptCompletion with result "OK" -""" - -[experts."e2e-lazy-init-all-false".skills."@perstack/base"] -type = "mcpStdioSkill" -command = "npx" -packageName = "@perstack/base" -pick = ["attemptCompletion"] -lazyInit = false - -[experts."e2e-lazy-init-all-false".skills."attacker"] -type = "mcpStdioSkill" -description = "Minimal MCP server (no lazy init)" -command = "node" -args = ["e2e/fixtures/minimal-mcp-server.mjs"] -lazyInit = false - -# Expert with multiple skills: one lazyInit=false (required), one lazyInit=true -# Tests that: -# - @perstack/base (always lazyInit=false) blocks startRun -# - exa (lazyInit=true) may connect AFTER startRun -[experts."e2e-lazy-init-mixed"] -version = "1.0.0" -description = "E2E test expert with mixed lazyInit settings" -instruction = """ -Call attemptCompletion with result "OK" -""" - -[experts."e2e-lazy-init-mixed".skills."@perstack/base"] -type = "mcpStdioSkill" -command = "npx" -packageName = "@perstack/base" -pick = ["attemptCompletion"] -lazyInit = false - -[experts."e2e-lazy-init-mixed".skills."attacker"] -type = "mcpStdioSkill" -description = "Minimal MCP server (lazy init)" -command = "node" -args = ["e2e/fixtures/minimal-mcp-server.mjs"] -lazyInit = true diff --git a/e2e/experts/mixed-tools.toml b/e2e/experts/mixed-tools.toml index d8207b07..066b2939 100644 --- a/e2e/experts/mixed-tools.toml +++ b/e2e/experts/mixed-tools.toml @@ -22,7 +22,6 @@ description = "Web search" command = "npx" args = ["-y", "exa-mcp-server"] requiredEnv = ["EXA_API_KEY"] -lazyInit = false [experts."e2e-mixed-tools".skills."user-input"] type = "interactiveSkill" diff --git a/e2e/experts/skills.toml b/e2e/experts/skills.toml index e5f692ae..bccc59d3 100644 --- a/e2e/experts/skills.toml +++ b/e2e/experts/skills.toml @@ -49,7 +49,6 @@ description = "Web search" command = "npx" args = ["-y", "exa-mcp-server"] requiredEnv = ["EXA_API_KEY"] -lazyInit = false [experts."e2e-multi-skill".skills."@perstack/base"] type = "mcpStdioSkill" diff --git a/e2e/experts/special-tools.toml b/e2e/experts/special-tools.toml index 5dd91402..64b00aad 100644 --- a/e2e/experts/special-tools.toml +++ b/e2e/experts/special-tools.toml @@ -23,7 +23,6 @@ description = "Web search" command = "npx" args = ["-y", "exa-mcp-server"] requiredEnv = ["EXA_API_KEY"] -lazyInit = false [experts."e2e-special-tools".skills."@perstack/base"] type = "mcpStdioSkill" diff --git a/packages/core/src/schemas/expert.ts b/packages/core/src/schemas/expert.ts index 88f6809a..1b96cd2a 100644 --- a/packages/core/src/schemas/expert.ts +++ b/packages/core/src/schemas/expert.ts @@ -80,7 +80,6 @@ export const expertSchema = z.object({ pick: [], omit: [], requiredEnv: [], - lazyInit: false, } satisfies SkillWithoutName, }) .transform((skills) => { diff --git a/packages/core/src/schemas/perstack-toml.ts b/packages/core/src/schemas/perstack-toml.ts index 6b8ba2cd..0d3ff692 100644 --- a/packages/core/src/schemas/perstack-toml.ts +++ b/packages/core/src/schemas/perstack-toml.ts @@ -158,7 +158,6 @@ export type PerstackConfigSkill = args?: string[] requiredEnv?: string[] allowedDomains?: string[] - lazyInit?: boolean } | { type: "mcpSseSkill" @@ -264,7 +263,6 @@ export const perstackConfigSchema = z.object({ args: z.array(z.string()).optional(), requiredEnv: z.array(z.string()).optional(), allowedDomains: z.array(domainPatternSchema).optional(), - lazyInit: z.boolean().optional().default(false), }), z.object({ type: z.literal("mcpSseSkill"), diff --git a/packages/core/src/schemas/skill.test.ts b/packages/core/src/schemas/skill.test.ts index c291171d..3bc3abe9 100644 --- a/packages/core/src/schemas/skill.test.ts +++ b/packages/core/src/schemas/skill.test.ts @@ -13,7 +13,6 @@ describe("@perstack/core: mcpStdioSkillSchema", () => { expect(result.name).toBe("test-skill") expect(result.pick).toEqual([]) expect(result.omit).toEqual([]) - expect(result.lazyInit).toBe(false) }) it("applies default values", () => { @@ -26,7 +25,6 @@ describe("@perstack/core: mcpStdioSkillSchema", () => { expect(result.omit).toEqual([]) expect(result.args).toEqual([]) expect(result.requiredEnv).toEqual([]) - expect(result.lazyInit).toBe(false) }) }) diff --git a/packages/core/src/schemas/skill.ts b/packages/core/src/schemas/skill.ts index 371a5c8b..1a9ff3d8 100644 --- a/packages/core/src/schemas/skill.ts +++ b/packages/core/src/schemas/skill.ts @@ -70,8 +70,6 @@ export interface McpStdioSkill { args: string[] /** Environment variables required by this skill */ requiredEnv: string[] - /** Whether to delay initialization until first use */ - lazyInit: boolean } export const mcpStdioSkillSchema = z.object({ @@ -85,7 +83,6 @@ export const mcpStdioSkillSchema = z.object({ packageName: z.string().optional(), args: z.array(z.string()).optional().default([]), requiredEnv: z.array(z.string()).optional().default([]), - lazyInit: z.boolean().optional().default(false), }) mcpStdioSkillSchema satisfies z.ZodType diff --git a/packages/installer/src/expert-resolver.ts b/packages/installer/src/expert-resolver.ts index ea2c47c2..b51539d1 100644 --- a/packages/installer/src/expert-resolver.ts +++ b/packages/installer/src/expert-resolver.ts @@ -66,7 +66,6 @@ export function toRuntimeExpert(key: string, expert: PublishedExpertData): Exper command: skill.command, packageName: skill.packageName, requiredEnv: skill.requiredEnv ?? [], - lazyInit: false, }, ] case "mcpSseSkill": @@ -80,7 +79,6 @@ export function toRuntimeExpert(key: string, expert: PublishedExpertData): Exper pick: skill.pick ?? [], omit: skill.omit ?? [], endpoint: skill.endpoint, - lazyInit: false, }, ] case "interactiveSkill": diff --git a/packages/runtime/src/helpers/resolve-expert.test.ts b/packages/runtime/src/helpers/resolve-expert.test.ts index 7c1619c9..68551e46 100644 --- a/packages/runtime/src/helpers/resolve-expert.test.ts +++ b/packages/runtime/src/helpers/resolve-expert.test.ts @@ -65,7 +65,6 @@ function createTestExpert(overrides: Partial = {}): Expert { requiredEnv: [], pick: [], omit: [], - lazyInit: false, }, }, delegates: [], diff --git a/packages/runtime/src/helpers/resolve-expert.ts b/packages/runtime/src/helpers/resolve-expert.ts index b9560f82..e8f0eff6 100644 --- a/packages/runtime/src/helpers/resolve-expert.ts +++ b/packages/runtime/src/helpers/resolve-expert.ts @@ -90,7 +90,6 @@ function toRuntimeExpert( command: skill.command, packageName: skill.packageName, requiredEnv: skill.requiredEnv ?? [], - lazyInit: false, }, ] case "mcpSseSkill": @@ -104,7 +103,6 @@ function toRuntimeExpert( pick: skill.pick ?? [], omit: skill.omit ?? [], endpoint: skill.endpoint, - lazyInit: false, }, ] case "interactiveSkill": diff --git a/packages/runtime/src/messages/message.test.ts b/packages/runtime/src/messages/message.test.ts index 97042f9d..b0efdb55 100644 --- a/packages/runtime/src/messages/message.test.ts +++ b/packages/runtime/src/messages/message.test.ts @@ -536,7 +536,7 @@ describe("@perstack/messages: instruction-message", () => { requiredEnv: [], pick: [], omit: [], - lazyInit: false, + rule: "Always use this skill carefully.", }, }, @@ -565,7 +565,6 @@ describe("@perstack/messages: instruction-message", () => { requiredEnv: [], pick: [], omit: [], - lazyInit: false, }, }, delegates: [], diff --git a/packages/skill-manager/src/adapters/in-memory-base-adapter.test.ts b/packages/skill-manager/src/adapters/in-memory-base-adapter.test.ts index c5fcbcfd..4c5076e7 100644 --- a/packages/skill-manager/src/adapters/in-memory-base-adapter.test.ts +++ b/packages/skill-manager/src/adapters/in-memory-base-adapter.test.ts @@ -45,7 +45,6 @@ function createBaseSkill() { requiredEnv: [], pick: [], omit: [], - lazyInit: false, } } diff --git a/packages/skill-manager/src/adapters/lockfile-adapter.test.ts b/packages/skill-manager/src/adapters/lockfile-adapter.test.ts index e77434fc..ab0c74dc 100644 --- a/packages/skill-manager/src/adapters/lockfile-adapter.test.ts +++ b/packages/skill-manager/src/adapters/lockfile-adapter.test.ts @@ -15,7 +15,7 @@ function createMockSkill(overrides: Partial = {}): McpStdioSkill requiredEnv: [], pick: [], omit: [], - lazyInit: false, + ...overrides, } as McpStdioSkill } diff --git a/packages/skill-manager/src/adapters/lockfile-adapter.ts b/packages/skill-manager/src/adapters/lockfile-adapter.ts index c3d25de8..a016efbd 100644 --- a/packages/skill-manager/src/adapters/lockfile-adapter.ts +++ b/packages/skill-manager/src/adapters/lockfile-adapter.ts @@ -127,7 +127,6 @@ export class LockfileSkillAdapter extends SkillAdapter { command: overrideCommand, packageName: undefined, args: overrideArgs, - lazyInit: false, } as McpStdioSkill } return skill diff --git a/packages/skill-manager/src/adapters/mcp-adapter.test.ts b/packages/skill-manager/src/adapters/mcp-adapter.test.ts index ff1b6d65..68a7b556 100644 --- a/packages/skill-manager/src/adapters/mcp-adapter.test.ts +++ b/packages/skill-manager/src/adapters/mcp-adapter.test.ts @@ -35,7 +35,7 @@ function createStdioSkill(overrides: Partial = {}): McpStdioSkill requiredEnv: [], pick: [], omit: [], - lazyInit: false, + ...overrides, } as McpStdioSkill } diff --git a/packages/skill-manager/src/skill-manager.test.ts b/packages/skill-manager/src/skill-manager.test.ts index c5a55063..25c28fb6 100644 --- a/packages/skill-manager/src/skill-manager.test.ts +++ b/packages/skill-manager/src/skill-manager.test.ts @@ -119,7 +119,7 @@ function createBaseSkill(overrides: Partial = {}): McpStdioSkill requiredEnv: [], pick: [], omit: [], - lazyInit: false, + ...overrides, } as McpStdioSkill } @@ -197,7 +197,6 @@ describe("@perstack/skill-manager: SkillManager", () => { requiredEnv: [], pick: [], omit: [], - lazyInit: false, } as McpStdioSkill, }, }) @@ -292,7 +291,6 @@ describe("@perstack/skill-manager: SkillManager", () => { requiredEnv: [], pick: [], omit: [], - lazyInit: false, } as McpStdioSkill, }, }) @@ -412,7 +410,6 @@ describe("@perstack/skill-manager: SkillManager", () => { requiredEnv: [], pick: [], omit: [], - lazyInit: false, } as McpStdioSkill, }, }) @@ -517,7 +514,6 @@ describe("@perstack/skill-manager: SkillManager", () => { requiredEnv: [], pick: [], omit: [], - lazyInit: false, } as McpStdioSkill await manager.addSkill(skill) expect(manager.getAdapters().has("new-skill")).toBe(true) @@ -540,7 +536,6 @@ describe("@perstack/skill-manager: SkillManager", () => { requiredEnv: [], pick: [], omit: [], - lazyInit: false, } as McpStdioSkill await manager.addSkill(skill) expect(factory.createMcp).toHaveBeenCalledWith( @@ -683,7 +678,6 @@ describe("buildSkillFromInput", () => { packageName: "@my/pkg", args: [], requiredEnv: [], - lazyInit: false, }) }) @@ -711,7 +705,6 @@ describe("buildSkillFromInput", () => { packageName: "@my/pkg", args: ["--flag"], requiredEnv: ["API_KEY"], - lazyInit: false, }) }) diff --git a/packages/skill-manager/src/skill-manager.ts b/packages/skill-manager/src/skill-manager.ts index fdd5fc99..48c45245 100644 --- a/packages/skill-manager/src/skill-manager.ts +++ b/packages/skill-manager/src/skill-manager.ts @@ -57,7 +57,6 @@ function applyBaseSkillCommandOverride( command: overrideCommand, packageName: undefined, args: overrideArgs, - lazyInit: false, } as McpStdioSkill } return skill @@ -113,7 +112,6 @@ export function buildSkillFromInput(input: { packageName: input.packageName, args: input.args ?? [], requiredEnv: input.requiredEnv ?? [], - lazyInit: false, } as McpStdioSkill } diff --git a/packages/skill-manager/src/utils/base-skill-helpers.test.ts b/packages/skill-manager/src/utils/base-skill-helpers.test.ts index 78caf3c0..5c5552b5 100644 --- a/packages/skill-manager/src/utils/base-skill-helpers.test.ts +++ b/packages/skill-manager/src/utils/base-skill-helpers.test.ts @@ -11,7 +11,7 @@ const createMcpStdioSkill = (overrides: Partial = {}): McpStdioSk pick: [], omit: [], requiredEnv: [], - lazyInit: false, + ...overrides, }) From 263f58e5bac6f9531a9e3024d90a74f297133541 Mon Sep 17 00:00:00 2001 From: HiranoMasaaki Date: Tue, 17 Feb 2026 03:13:16 +0000 Subject: [PATCH 2/2] refactor: delete lazy-init E2E test file Missed in previous commit when switching branches. Co-Authored-By: Claude Opus 4.6 --- e2e/perstack-cli/lazy-init.test.ts | 125 ----------------------------- 1 file changed, 125 deletions(-) delete mode 100644 e2e/perstack-cli/lazy-init.test.ts diff --git a/e2e/perstack-cli/lazy-init.test.ts b/e2e/perstack-cli/lazy-init.test.ts deleted file mode 100644 index 7f32ff93..00000000 --- a/e2e/perstack-cli/lazy-init.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Lazy Init E2E Tests - * - * Tests skill initialization behavior based on lazyInit setting: - * - lazyInit = false (default): All skills must be fully connected BEFORE startRun - * - lazyInit = true: startRun can occur BEFORE skill is fully connected - * - * TOML: e2e/experts/lazy-init.toml - */ -import { describe, expect, it } from "vitest" -import { runCli, withEventParsing } from "../lib/runner.js" - -const LAZY_INIT_CONFIG = "./e2e/experts/lazy-init.toml" -// LLM API calls require extended timeout -const LLM_TIMEOUT = 180000 - -type SkillConnectedEvent = { - type: "skillConnected" - skillName: string - timestamp: number -} - -type StartRunEvent = { - type: "startRun" - timestamp: number -} - -type AnyEvent = { type: string; timestamp?: number; skillName?: string } - -function getSkillConnectedEvents(events: AnyEvent[]): SkillConnectedEvent[] { - return events.filter((e) => e.type === "skillConnected") as SkillConnectedEvent[] -} - -function getStartRunEvent(events: AnyEvent[]): StartRunEvent | undefined { - return events.find((e) => e.type === "startRun") as StartRunEvent | undefined -} - -describe.concurrent("Lazy Init", () => { - /** - * Tests all skills with lazyInit=false: - * All MCP servers must be fully connected BEFORE startRun begins. - */ - it( - "all lazyInit=false: all skills should be connected before startRun", - async () => { - const cmdResult = await runCli( - ["run", "--config", LAZY_INIT_CONFIG, "e2e-lazy-init-all-false", "Complete the task"], - { timeout: LLM_TIMEOUT }, - ) - const result = withEventParsing(cmdResult) - expect(result.exitCode).toBe(0) - - const events = result.events as AnyEvent[] - const skillConnectedEvents = getSkillConnectedEvents(events) - const startRunEvent = getStartRunEvent(events) - - expect(startRunEvent).toBeDefined() - - // Find skill events - const baseSkillEvent = skillConnectedEvents.find((e) => e.skillName === "@perstack/base") - const attackerSkillEvent = skillConnectedEvents.find((e) => e.skillName === "attacker") - - expect(baseSkillEvent).toBeDefined() - expect(attackerSkillEvent).toBeDefined() - - // Both skills (lazyInit=false) should be connected BEFORE startRun - expect(baseSkillEvent!.timestamp).toBeLessThanOrEqual(startRunEvent!.timestamp) - expect(attackerSkillEvent!.timestamp).toBeLessThanOrEqual(startRunEvent!.timestamp) - - // Run should complete successfully - const completeRunEvent = events.find((e) => e.type === "completeRun") - expect(completeRunEvent).toBeDefined() - }, - LLM_TIMEOUT, - ) - - /** - * Tests mixed lazyInit settings: - * - @perstack/base is always lazyInit=false (enforced by runtime), so it blocks startRun - * - attacker (minimal-mcp-server) has lazyInit=true, runtime does NOT wait for it - * - * This verifies that: - * 1. lazyInit=false skills are fully connected before startRun - * 2. lazyInit=true skills do NOT block startRun (startRun occurs immediately after base connects) - * 3. The run still completes successfully - */ - it( - "mixed: lazyInit=false blocks startRun, lazyInit=true does not block", - async () => { - const cmdResult = await runCli( - ["run", "--config", LAZY_INIT_CONFIG, "e2e-lazy-init-mixed", "Complete the task"], - { timeout: LLM_TIMEOUT }, - ) - const result = withEventParsing(cmdResult) - expect(result.exitCode).toBe(0) - - const events = result.events as AnyEvent[] - const skillConnectedEvents = getSkillConnectedEvents(events) - const startRunEvent = getStartRunEvent(events) - - expect(startRunEvent).toBeDefined() - - // Find skill events - const baseSkillEvent = skillConnectedEvents.find((e) => e.skillName === "@perstack/base") - const attackerSkillEvent = skillConnectedEvents.find((e) => e.skillName === "attacker") - - expect(baseSkillEvent).toBeDefined() - expect(attackerSkillEvent).toBeDefined() - - // @perstack/base (lazyInit=false) should be connected BEFORE startRun - expect(baseSkillEvent!.timestamp).toBeLessThanOrEqual(startRunEvent!.timestamp) - - // Verify that startRun happens immediately after base connects (not waiting for attacker) - // The gap between base connected and startRun should be small (< 50ms) - // Using 50ms to account for event loop, GC pauses, and CI runner variability - const gapMs = startRunEvent!.timestamp - baseSkillEvent!.timestamp - expect(gapMs).toBeLessThan(50) - - // Run should complete successfully - const completeRunEvent = events.find((e) => e.type === "completeRun") - expect(completeRunEvent).toBeDefined() - }, - LLM_TIMEOUT, - ) -})