diff --git a/docs/extensions.md b/docs/extensions.md index 4356cff67..1b410cd47 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -217,7 +217,7 @@ Cancelable pre-events: - `after_provider_response` - `context` - `agent_start` / `agent_end` — agent loop lifecycle; `agent_end` is the main-agent stop-style hook and can queue hidden continuation context with `pi.sendMessage(..., { deliverAs: "nextTurn", triggerTurn: true })` -- `subagent_stop` — task/subagent completion lifecycle; use this instead of `agent_end` for subagent-only cleanup or mission status tracking +- `session_stop` — task/subagent session completion lifecycle; use this instead of `agent_end` for subagent-only cleanup or mission status tracking - `turn_start` / `turn_end` - `message_start` / `message_update` / `message_end` diff --git a/docs/skills/authoring-extensions.md b/docs/skills/authoring-extensions.md index abf4448cd..7e43162b0 100644 --- a/docs/skills/authoring-extensions.md +++ b/docs/skills/authoring-extensions.md @@ -202,9 +202,9 @@ pi.on("turn_end", async (_event, ctx) => { ctx.ui.setStatus("tokens", `~${ctx.getContextUsage()?.tokens ?? "?"} tokens`); }); -pi.on("subagent_stop", async (event, ctx) => { - // Fires for task/subagent completion; main-agent stop-style continuation stays on agent_end. - ctx.ui.setStatus("subagent", `${event.messages.length} completion messages`); +pi.on("session_stop", async (event, ctx) => { + // Fires for task/subagent session completion; main-agent stop-style continuation stays on agent_end. + ctx.ui.setStatus("session", `${event.messages.length} completion messages`); }); ``` diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index c5b4a63e4..83cefa391 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -36,7 +36,7 @@ - Fixed hashline visible-line validation for ACP editor reads so `INS.POST` anchors displayed by bridge-backed range and multi-range `read` output are merged into the session snapshot before `edit` validates them ([#2773](https://github.com/can1357/oh-my-pi/issues/2773)). ### Added -- Added a `subagent_stop` extension event for task/subagent completion, leaving `agent_end` scoped to main-agent stop-style continuation ([#2834](https://github.com/can1357/oh-my-pi/issues/2834)). +- Added a `session_stop` extension event for task/subagent completion, leaving `agent_end` scoped to main-agent stop-style continuation ([#2834](https://github.com/can1357/oh-my-pi/issues/2834)). ### Fixed diff --git a/packages/coding-agent/src/extensibility/extensions/runner.ts b/packages/coding-agent/src/extensibility/extensions/runner.ts index b63eb2adb..c2436c1ca 100644 --- a/packages/coding-agent/src/extensibility/extensions/runner.ts +++ b/packages/coding-agent/src/extensibility/extensions/runner.ts @@ -322,8 +322,8 @@ export class ExtensionRunner { await this.emit({ type: "credential_disabled", ...event }); } - async emitSubagentStop(messages: AgentMessage[]): Promise { - await this.emit({ type: "subagent_stop", messages }); + async emitSessionStop(messages: AgentMessage[]): Promise { + await this.emit({ type: "session_stop", messages }); } getUIContext(): ExtensionUIContext { diff --git a/packages/coding-agent/src/extensibility/extensions/types.ts b/packages/coding-agent/src/extensibility/extensions/types.ts index 93d4edabf..7ac8b8bc6 100644 --- a/packages/coding-agent/src/extensibility/extensions/types.ts +++ b/packages/coding-agent/src/extensibility/extensions/types.ts @@ -82,9 +82,9 @@ import type { SessionEvent, SessionShutdownEvent, SessionStartEvent, + SessionStopEvent, SessionSwitchEvent, SessionTreeEvent, - SubagentStopEvent, TodoReminderEvent, ToolCallEventResult, ToolResultEventResult, @@ -526,7 +526,7 @@ export interface BeforeAgentStartEvent { systemPrompt: string[]; } -export type { AgentEndEvent, AgentStartEvent, SubagentStopEvent, TurnEndEvent, TurnStartEvent } from "../shared-events"; +export type { AgentEndEvent, AgentStartEvent, SessionStopEvent, TurnEndEvent, TurnStartEvent } from "../shared-events"; /** Fired when a message starts (user, assistant, or toolResult) */ export interface MessageStartEvent { @@ -803,7 +803,7 @@ export type ExtensionEvent = | BeforeAgentStartEvent | AgentStartEvent | AgentEndEvent - | SubagentStopEvent + | SessionStopEvent | TurnStartEvent | TurnEndEvent | MessageStartEvent @@ -980,7 +980,7 @@ export interface ExtensionAPI { on(event: "before_agent_start", handler: ExtensionHandler): void; on(event: "agent_start", handler: ExtensionHandler): void; on(event: "agent_end", handler: ExtensionHandler): void; - on(event: "subagent_stop", handler: ExtensionHandler): void; + on(event: "session_stop", handler: ExtensionHandler): void; on(event: "turn_start", handler: ExtensionHandler): void; on(event: "turn_end", handler: ExtensionHandler): void; on(event: "message_start", handler: ExtensionHandler): void; diff --git a/packages/coding-agent/src/extensibility/shared-events.ts b/packages/coding-agent/src/extensibility/shared-events.ts index 65528aec2..234226752 100644 --- a/packages/coding-agent/src/extensibility/shared-events.ts +++ b/packages/coding-agent/src/extensibility/shared-events.ts @@ -181,9 +181,9 @@ export interface AgentEndEvent { messages: AgentMessage[]; } -/** Fired when a subagent loop ends */ -export interface SubagentStopEvent { - type: "subagent_stop"; +/** Fired when a task/subagent session loop ends */ +export interface SessionStopEvent { + type: "session_stop"; messages: AgentMessage[]; } diff --git a/packages/coding-agent/src/session/agent-session.ts b/packages/coding-agent/src/session/agent-session.ts index 1350a0128..abc0c7417 100644 --- a/packages/coding-agent/src/session/agent-session.ts +++ b/packages/coding-agent/src/session/agent-session.ts @@ -3534,7 +3534,7 @@ export class AgentSession { await this.#extensionRunner.emit({ type: "agent_start" }); } else if (event.type === "agent_end") { if (this.#agentKind === "sub") { - await this.#extensionRunner.emitSubagentStop(event.messages); + await this.#extensionRunner.emitSessionStop(event.messages); } else { await this.#extensionRunner.emit({ type: "agent_end", messages: event.messages }); } diff --git a/packages/coding-agent/test/agent-session-eager-task.test.ts b/packages/coding-agent/test/agent-session-eager-task.test.ts index 8467d306a..c5f2e5e59 100644 --- a/packages/coding-agent/test/agent-session-eager-task.test.ts +++ b/packages/coding-agent/test/agent-session-eager-task.test.ts @@ -283,7 +283,7 @@ describe("AgentSession eager task prelude", () => { expect(observedCalls[0]?.messageTexts[0]).toContain("delegation is enabled"); }); - it("routes subagent completion to subagent_stop while main sessions keep agent_end", async () => { + it("routes subagent completion to session_stop while main sessions keep agent_end", async () => { const mainAgentEndEvents: AgentMessage[][] = []; const mainEmit = vi.fn((event: { type: string; messages?: AgentMessage[] }) => { if (event.type === "agent_end" && event.messages) { @@ -291,27 +291,27 @@ describe("AgentSession eager task prelude", () => { } return Promise.resolve(undefined); }); - const mainEmitSubagentStop = vi.fn((_messages: AgentMessage[]) => Promise.resolve()); + const mainEmitSessionStop = vi.fn((_messages: AgentMessage[]) => Promise.resolve()); const mainEmitBeforeAgentStart = vi.fn((_prompt: string, _images: unknown, _systemPrompt: string[]) => Promise.resolve(undefined), ); const mainExtensionRunner = { emit: mainEmit, emitBeforeAgentStart: mainEmitBeforeAgentStart, - emitSubagentStop: mainEmitSubagentStop, + emitSessionStop: mainEmitSessionStop, } as unknown as ExtensionRunner; const { session: mainSession } = await createHarness({}, undefined, undefined, undefined, mainExtensionRunner); await mainSession.prompt("finish the main turn"); await mainSession.waitForIdle(); - expect(mainEmitSubagentStop).not.toHaveBeenCalled(); + expect(mainEmitSessionStop).not.toHaveBeenCalled(); expect(mainAgentEndEvents).toHaveLength(1); expect(mainAgentEndEvents[0]?.some(message => message.role === "assistant")).toBe(true); const subagentStopEvents: AgentMessage[][] = []; const subEmit = vi.fn((_event: { type: string; messages?: AgentMessage[] }) => Promise.resolve(undefined)); - const subEmitSubagentStop = vi.fn((messages: AgentMessage[]) => { + const subEmitSessionStop = vi.fn((messages: AgentMessage[]) => { subagentStopEvents.push(messages); return Promise.resolve(); }); @@ -321,7 +321,7 @@ describe("AgentSession eager task prelude", () => { const subExtensionRunner = { emit: subEmit, emitBeforeAgentStart: subEmitBeforeAgentStart, - emitSubagentStop: subEmitSubagentStop, + emitSessionStop: subEmitSessionStop, } as unknown as ExtensionRunner; const { session: subSession } = await createHarness({}, "SubAgent", undefined, "sub", subExtensionRunner); diff --git a/packages/coding-agent/test/extensions-runner.test.ts b/packages/coding-agent/test/extensions-runner.test.ts index 12da898fd..b3cfb9767 100644 --- a/packages/coding-agent/test/extensions-runner.test.ts +++ b/packages/coding-agent/test/extensions-runner.test.ts @@ -604,14 +604,14 @@ describe("ExtensionRunner", () => { }); }); - describe("subagent_stop", () => { - it("invokes handlers with completed subagent messages", async () => { - const eventsPath = path.join(tempDir.path(), "subagent-stop-events.jsonl"); + describe("session_stop", () => { + it("invokes handlers with completed stopped session messages", async () => { + const eventsPath = path.join(tempDir.path(), "session-stop-events.jsonl"); const extCode = ` import * as fs from "node:fs"; export default function(pi) { - pi.on("subagent_stop", async (event) => { + pi.on("session_stop", async (event) => { fs.appendFileSync( ${JSON.stringify(eventsPath)}, JSON.stringify({ @@ -622,7 +622,7 @@ describe("ExtensionRunner", () => { }); } `; - fs.writeFileSync(path.join(extensionsDir, "subagent-stop.ts"), extCode); + fs.writeFileSync(path.join(extensionsDir, "session-stop.ts"), extCode); const result = await loadTestExtensions(); const runner = new ExtensionRunner( @@ -650,7 +650,7 @@ describe("ExtensionRunner", () => { timestamp: 123, }; - await runner.emitSubagentStop([completedMessage]); + await runner.emitSessionStop([completedMessage]); const events = fs .readFileSync(eventsPath, "utf8") @@ -659,7 +659,7 @@ describe("ExtensionRunner", () => { .map(line => JSON.parse(line)); expect(events).toEqual([ { - type: "subagent_stop", + type: "session_stop", messages: [completedMessage], }, ]);