fix(coding-agent): use session stop extension event

This commit is contained in:
ben
2026-06-17 14:51:22 +08:00
committed by can1357
parent 8e45ed9016
commit b7ac4b4c43
9 changed files with 28 additions and 28 deletions
+1 -1
View File
@@ -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`
+3 -3
View File
@@ -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`);
});
```
+1 -1
View File
@@ -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
@@ -322,8 +322,8 @@ export class ExtensionRunner {
await this.emit({ type: "credential_disabled", ...event });
}
async emitSubagentStop(messages: AgentMessage[]): Promise<void> {
await this.emit({ type: "subagent_stop", messages });
async emitSessionStop(messages: AgentMessage[]): Promise<void> {
await this.emit({ type: "session_stop", messages });
}
getUIContext(): ExtensionUIContext {
@@ -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<BeforeAgentStartEvent, BeforeAgentStartEventResult>): void;
on(event: "agent_start", handler: ExtensionHandler<AgentStartEvent>): void;
on(event: "agent_end", handler: ExtensionHandler<AgentEndEvent>): void;
on(event: "subagent_stop", handler: ExtensionHandler<SubagentStopEvent>): void;
on(event: "session_stop", handler: ExtensionHandler<SessionStopEvent>): void;
on(event: "turn_start", handler: ExtensionHandler<TurnStartEvent>): void;
on(event: "turn_end", handler: ExtensionHandler<TurnEndEvent>): void;
on(event: "message_start", handler: ExtensionHandler<MessageStartEvent>): void;
@@ -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[];
}
@@ -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 });
}
@@ -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);
@@ -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],
},
]);