fix(coding-agent): use session stop extension event
This commit is contained in:
+1
-1
@@ -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`
|
||||
|
||||
|
||||
@@ -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`);
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@@ -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],
|
||||
},
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user