mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-26 11:12:53 +00:00
Fix finish notification steering dispatch
This commit is contained in:
@@ -10,6 +10,9 @@ import {
|
||||
} from "./agent-prompt.js";
|
||||
import type { AgentManagerEvent, ManagedAgent } from "./agent-manager.js";
|
||||
|
||||
const CHILD_AGENT_ID = "11111111-1111-4111-8111-111111111111";
|
||||
const CALLER_AGENT_ID = "22222222-2222-4222-8222-222222222222";
|
||||
|
||||
test("isSystemInjectedEnvelope matches the envelope formatSystemNotificationPrompt produces", () => {
|
||||
expect(isSystemInjectedEnvelope(formatSystemNotificationPrompt("child finished"))).toBe(true);
|
||||
expect(isSystemInjectedEnvelope("hello world")).toBe(false);
|
||||
@@ -19,12 +22,12 @@ it("does not notify archived callers", async () => {
|
||||
let subscriber: ((event: AgentManagerEvent) => void) | null = null;
|
||||
|
||||
const childAgent: ManagedAgent = Object.create(null);
|
||||
Reflect.set(childAgent, "id", "child-agent");
|
||||
Reflect.set(childAgent, "id", CHILD_AGENT_ID);
|
||||
Reflect.set(childAgent, "lifecycle", "idle");
|
||||
Reflect.set(childAgent, "config", { title: "Child Agent" });
|
||||
|
||||
const callerAgent: ManagedAgent = Object.create(null);
|
||||
Reflect.set(callerAgent, "id", "caller-agent");
|
||||
Reflect.set(callerAgent, "id", CALLER_AGENT_ID);
|
||||
Reflect.set(callerAgent, "lifecycle", "idle");
|
||||
Reflect.set(callerAgent, "config", { title: "Caller Agent" });
|
||||
|
||||
@@ -33,10 +36,10 @@ it("does not notify archived callers", async () => {
|
||||
agentManager,
|
||||
"getAgent",
|
||||
vi.fn((agentId: string) => {
|
||||
if (agentId === "child-agent") {
|
||||
if (agentId === CHILD_AGENT_ID) {
|
||||
return childAgent;
|
||||
}
|
||||
if (agentId === "caller-agent") {
|
||||
if (agentId === CALLER_AGENT_ID) {
|
||||
return callerAgent;
|
||||
}
|
||||
return null;
|
||||
@@ -59,7 +62,7 @@ it("does not notify archived callers", async () => {
|
||||
Reflect.set(agentManager, "startAgentRun", startAgentRunSpy);
|
||||
|
||||
const agentStorageGetSpy = vi.fn(async (agentId: string) =>
|
||||
agentId === "caller-agent" ? { archivedAt: "2024-01-01" } : null,
|
||||
agentId === CALLER_AGENT_ID ? { archivedAt: "2024-01-01" } : null,
|
||||
);
|
||||
const agentStorage: AgentStorage = Object.create(AgentStorage.prototype);
|
||||
Reflect.set(agentStorage, "get", agentStorageGetSpy);
|
||||
@@ -67,8 +70,8 @@ it("does not notify archived callers", async () => {
|
||||
setupFinishNotification({
|
||||
agentManager,
|
||||
agentStorage,
|
||||
childAgentId: "child-agent",
|
||||
callerAgentId: "caller-agent",
|
||||
childAgentId: CHILD_AGENT_ID,
|
||||
callerAgentId: CALLER_AGENT_ID,
|
||||
logger: createTestLogger(),
|
||||
});
|
||||
|
||||
@@ -87,7 +90,7 @@ it("does not notify archived callers", async () => {
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(agentStorageGetSpy).toHaveBeenCalledWith("caller-agent");
|
||||
expect(agentStorageGetSpy).toHaveBeenCalledWith(CALLER_AGENT_ID);
|
||||
});
|
||||
|
||||
expect(startAgentRunSpy).not.toHaveBeenCalled();
|
||||
@@ -97,12 +100,12 @@ it("uses AgentManager startAgentRun for finish notifications", async () => {
|
||||
let subscriber: ((event: AgentManagerEvent) => void) | null = null;
|
||||
|
||||
const childAgent: ManagedAgent = Object.create(null);
|
||||
Reflect.set(childAgent, "id", "child-agent");
|
||||
Reflect.set(childAgent, "id", CHILD_AGENT_ID);
|
||||
Reflect.set(childAgent, "lifecycle", "idle");
|
||||
Reflect.set(childAgent, "config", { title: "Child Agent" });
|
||||
|
||||
const callerAgent: ManagedAgent = Object.create(null);
|
||||
Reflect.set(callerAgent, "id", "caller-agent");
|
||||
Reflect.set(callerAgent, "id", CALLER_AGENT_ID);
|
||||
Reflect.set(callerAgent, "lifecycle", "idle");
|
||||
Reflect.set(callerAgent, "config", { title: "Caller Agent" });
|
||||
|
||||
@@ -116,10 +119,10 @@ it("uses AgentManager startAgentRun for finish notifications", async () => {
|
||||
agentManager,
|
||||
"getAgent",
|
||||
vi.fn((agentId: string) => {
|
||||
if (agentId === "child-agent") {
|
||||
if (agentId === CHILD_AGENT_ID) {
|
||||
return childAgent;
|
||||
}
|
||||
if (agentId === "caller-agent") {
|
||||
if (agentId === CALLER_AGENT_ID) {
|
||||
return callerAgent;
|
||||
}
|
||||
return null;
|
||||
@@ -141,14 +144,16 @@ it("uses AgentManager startAgentRun for finish notifications", async () => {
|
||||
Reflect.set(
|
||||
agentStorage,
|
||||
"get",
|
||||
vi.fn(async () => null),
|
||||
vi.fn(async (agentId: string) =>
|
||||
agentId === CHILD_AGENT_ID ? { title: "Child Agent" } : null,
|
||||
),
|
||||
);
|
||||
|
||||
setupFinishNotification({
|
||||
agentManager,
|
||||
agentStorage,
|
||||
childAgentId: "child-agent",
|
||||
callerAgentId: "caller-agent",
|
||||
childAgentId: CHILD_AGENT_ID,
|
||||
callerAgentId: CALLER_AGENT_ID,
|
||||
logger: createTestLogger(),
|
||||
});
|
||||
|
||||
@@ -168,8 +173,8 @@ it("uses AgentManager startAgentRun for finish notifications", async () => {
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(startAgentRunSpy).toHaveBeenCalledWith(
|
||||
"caller-agent",
|
||||
"<paseo-system>\nAgent child-agent (Child Agent) finished.\n</paseo-system>",
|
||||
CALLER_AGENT_ID,
|
||||
`<paseo-system>\nAgent ${CHILD_AGENT_ID} (Child Agent) finished.\n</paseo-system>`,
|
||||
{ replaceRunning: true },
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
import type { Logger } from "pino";
|
||||
|
||||
import type { AgentPromptInput, AgentRunOptions } from "./agent-sdk-types.js";
|
||||
import type { AgentManager, ManagedAgent } from "./agent-manager.js";
|
||||
import type { AgentManager, ManagedAgent, StartAgentRunOptions } from "./agent-manager.js";
|
||||
import type { AgentStorage } from "./agent-storage.js";
|
||||
import { ensureAgentLoaded } from "./agent-loading.js";
|
||||
|
||||
export type AgentRunController = Pick<
|
||||
AgentManager,
|
||||
"getAgent" | "tryRunOutOfBand" | "hasInFlightRun" | "replaceAgentRun" | "streamAgent"
|
||||
>;
|
||||
|
||||
export interface StartAgentRunOptions {
|
||||
replaceRunning?: boolean;
|
||||
runOptions?: AgentRunOptions;
|
||||
}
|
||||
export type AgentRunController = Pick<AgentManager, "getAgent" | "startAgentRun">;
|
||||
|
||||
export function startAgentRun(
|
||||
agentManager: AgentRunController,
|
||||
@@ -35,23 +27,17 @@ export function startAgentRun(
|
||||
},
|
||||
"agent.session.start_stream.request",
|
||||
);
|
||||
// Out-of-band commands (e.g. /goal pause) must run WITHOUT canceling an
|
||||
// in-flight turn — replaceAgentRun would interrupt the running turn. The
|
||||
// intercept lives at this layer so it covers every prompt entrypoint.
|
||||
if (agentManager.tryRunOutOfBand(agentId, prompt)) {
|
||||
|
||||
const result = agentManager.startAgentRun(agentId, prompt, options);
|
||||
if (result.outOfBand) {
|
||||
return { outOfBand: true };
|
||||
}
|
||||
const shouldReplace = Boolean(options?.replaceRunning && agentManager.hasInFlightRun(agentId));
|
||||
const runOptions = options?.runOptions;
|
||||
const iterator = shouldReplace
|
||||
? agentManager.replaceAgentRun(agentId, prompt, runOptions)
|
||||
: agentManager.streamAgent(agentId, prompt, runOptions);
|
||||
const iterator = result.events;
|
||||
logger.trace(
|
||||
{
|
||||
agentId,
|
||||
provider: snapshot?.provider,
|
||||
providerSessionId: snapshot?.persistence?.sessionId ?? undefined,
|
||||
shouldReplace,
|
||||
},
|
||||
"agent.session.start_stream.iterator_returned",
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user