diff --git a/packages/coding-agent/src/core/custom-tools/loader.ts b/packages/coding-agent/src/core/custom-tools/loader.ts index 095f41c8..3772e0d7 100644 --- a/packages/coding-agent/src/core/custom-tools/loader.ts +++ b/packages/coding-agent/src/core/custom-tools/loader.ts @@ -298,7 +298,8 @@ export async function loadCustomTools( // Shared API object - all tools get the same instance const sharedApi: ToolAPI = { cwd, - exec: (command: string, args: string[], options?: ExecOptions) => execCommand(command, args, cwd, options), + exec: (command: string, args: string[], options?: ExecOptions) => + execCommand(command, args, options?.cwd ?? cwd, options), ui: createNoOpUIContext(), hasUI: false, }; diff --git a/packages/coding-agent/src/core/custom-tools/types.ts b/packages/coding-agent/src/core/custom-tools/types.ts index b5ccc591..d9e3297d 100644 --- a/packages/coding-agent/src/core/custom-tools/types.ts +++ b/packages/coding-agent/src/core/custom-tools/types.ts @@ -31,6 +31,8 @@ export interface ExecOptions { signal?: AbortSignal; /** Timeout in milliseconds */ timeout?: number; + /** Working directory */ + cwd?: string; } /** API passed to custom tool factory (stable across session changes) */ diff --git a/packages/coding-agent/test/compaction-hooks-example.test.ts b/packages/coding-agent/test/compaction-hooks-example.test.ts index 0d200702..126cb1a6 100644 --- a/packages/coding-agent/test/compaction-hooks-example.test.ts +++ b/packages/coding-agent/test/compaction-hooks-example.test.ts @@ -9,11 +9,13 @@ describe("Documentation example", () => { it("custom compaction example should type-check correctly", () => { // This is the example from hooks.md - verify it compiles const exampleHook = (pi: HookAPI) => { - pi.on("session", async (event, _ctx) => { + pi.on("session", async (event, ctx) => { if (event.reason !== "before_compact") return; // After narrowing, these should all be accessible - const { preparation, previousCompactions, sessionManager, modelRegistry, model } = event; + // sessionManager and modelRegistry come from ctx, not event + const { preparation, previousCompactions, model } = event; + const { sessionManager, modelRegistry } = ctx; const { messagesToSummarize, messagesToKeep, tokensBefore, firstKeptEntryId, cutPoint } = preparation; // Get previous summary from most recent compaction diff --git a/packages/coding-agent/test/compaction-hooks.test.ts b/packages/coding-agent/test/compaction-hooks.test.ts index 6e9a071d..66d21b3d 100644 --- a/packages/coding-agent/test/compaction-hooks.test.ts +++ b/packages/coding-agent/test/compaction-hooks.test.ts @@ -91,7 +91,7 @@ describe.skipIf(!API_KEY)("Compaction hooks", () => { const authStorage = new AuthStorage(join(tempDir, "auth.json")); const modelRegistry = new ModelRegistry(authStorage); - hookRunner = new HookRunner(hooks, tempDir); + hookRunner = new HookRunner(hooks, tempDir, sessionManager, modelRegistry); hookRunner.setUIContext( { select: async () => null, @@ -101,7 +101,6 @@ describe.skipIf(!API_KEY)("Compaction hooks", () => { }, false, ); - hookRunner.setSessionFile(sessionManager.getSessionFile() ?? null); session = new AgentSession({ agent, @@ -140,8 +139,7 @@ describe.skipIf(!API_KEY)("Compaction hooks", () => { expect(beforeEvent.preparation.messagesToKeep).toBeDefined(); expect(beforeEvent.preparation.tokensBefore).toBeGreaterThanOrEqual(0); expect(beforeEvent.model).toBeDefined(); - expect(beforeEvent.sessionManager).toBeDefined(); - expect(beforeEvent.modelRegistry).toBeDefined(); + // sessionManager and modelRegistry are now on ctx, not event } const afterEvent = compactEvents[0]; @@ -217,8 +215,9 @@ describe.skipIf(!API_KEY)("Compaction hooks", () => { const afterEvent = compactEvents[0]; if (afterEvent.reason === "compact") { - const entries = afterEvent.sessionManager.getEntries(); - const hasCompactionEntry = entries.some((e) => e.type === "compaction"); + // sessionManager is now on ctx, use session.sessionManager directly + const entries = session.sessionManager.getEntries(); + const hasCompactionEntry = entries.some((e: { type: string }) => e.type === "compaction"); expect(hasCompactionEntry).toBe(true); } }, 120000); @@ -361,9 +360,12 @@ describe.skipIf(!API_KEY)("Compaction hooks", () => { expect(event.model).toHaveProperty("provider"); expect(event.model).toHaveProperty("id"); - expect(typeof event.modelRegistry.getApiKey).toBe("function"); + // sessionManager and modelRegistry are now on ctx, not event + // Verify they're accessible via session + expect(typeof session.sessionManager.getEntries).toBe("function"); + expect(typeof session.modelRegistry.getApiKey).toBe("function"); - const entries = event.sessionManager.getEntries(); + const entries = session.sessionManager.getEntries(); expect(Array.isArray(entries)).toBe(true); expect(entries.length).toBeGreaterThan(0); }, 120000);