fix: preserve investigation execution history by round

This commit is contained in:
2026-10-30 17:07:53 +08:00
parent 1ee21d1c7f
commit 185bf31446
16 changed files with 830 additions and 32 deletions
+70
View File
@@ -138,6 +138,76 @@ describe("Agent API client sessions", () => {
await expect(createAgentApiClient("http://agent.local").loadSession("missing")).resolves.toBeNull();
});
it("loads a task execution history through its exact task and run binding", async () => {
const fetchMock = vi.fn(async (..._args: Parameters<typeof fetch>) => Response.json({
id: "execution-session",
session_id: "execution-session",
title: "南桥管道爆管调查 · 执行记录",
purpose: "task_execution",
task_id: "50000000-0000-4000-8000-000000000001",
created_at: "2026-10-30T01:00:00Z",
updated_at: "2026-10-30T01:05:00Z",
messages: [{ id: "assistant", role: "assistant", parts: [{ type: "text", text: "调查完成" }] }],
is_streaming: false,
run_status: "completed"
}));
vi.stubGlobal("fetch", fetchMock);
const client = createAgentApiClient("http://agent.local", {
getAccessToken: async () => "token",
getProjectId: async () => "project"
});
const loaded = await client.loadTaskExecutionSession(
"50000000-0000-4000-8000-000000000001",
"70000000-0000-4000-8000-000000000001",
"execution-session"
);
expect(loaded).toMatchObject({
sessionId: "execution-session",
title: "南桥管道爆管调查 · 执行记录",
purpose: "task_execution",
taskId: "50000000-0000-4000-8000-000000000001",
runStatus: "completed"
});
const url = new URL(String(fetchMock.mock.calls[0]?.[0]));
expect(url.pathname).toBe("/api/v1/task/50000000-0000-4000-8000-000000000001/execution-session");
expect(url.searchParams.get("run_id")).toBe("70000000-0000-4000-8000-000000000001");
expect(url.searchParams.get("session_id")).toBe("execution-session");
const headers = new Headers(fetchMock.mock.calls[0]?.[1]?.headers);
expect(headers.get("Authorization")).toBe("Bearer token");
expect(headers.get("X-Project-Id")).toBe("project");
});
it("creates a debug branch bound to an exact task execution history", async () => {
const fetchMock = vi.fn(async (..._args: Parameters<typeof fetch>) => Response.json({
session_id: "debug-session",
title: "南桥管道爆管调查 · 调试",
purpose: "task_collaboration",
task_id: "50000000-0000-4000-8000-000000000001",
parent_session_id: "execution-session",
created: true
}, { status: 201 }));
vi.stubGlobal("fetch", fetchMock);
const client = createAgentApiClient("http://agent.local", {
getAccessToken: async () => "token",
getProjectId: async () => "project"
});
await expect(client.createTaskExecutionDebugSession(
"50000000-0000-4000-8000-000000000001",
"70000000-0000-4000-8000-000000000001",
"execution-session"
)).resolves.toMatchObject({ session_id: "debug-session", purpose: "task_collaboration" });
const [url, init] = fetchMock.mock.calls[0] ?? [];
expect(new URL(String(url)).pathname).toBe("/api/v1/task/50000000-0000-4000-8000-000000000001/execution-session/debug");
expect(JSON.parse(String(init?.body))).toEqual({
run_id: "70000000-0000-4000-8000-000000000001",
session_id: "execution-session"
});
});
it("falls back when a candidate is not the agent service", async () => {
const fetchMock = vi
.fn()
+26
View File
@@ -174,6 +174,8 @@ export type AgentApiClient = InteractionRequestApi & {
moveSessionToTopic: (sessionId: string, topicId: string | null) => Promise<void>;
loadOlderMessages: (sessionId: string, before: string, signal?: AbortSignal) => Promise<AgentHistoryMessagePage>;
createTaskCollaborationSession: (taskId: string) => Promise<AgentChatSession>;
createTaskExecutionDebugSession: (taskId: string, runId: string, sessionId: string) => Promise<AgentChatSession>;
loadTaskExecutionSession: (taskId: string, runId: string, sessionId: string, signal?: AbortSignal) => Promise<AgentLoadedChatSession | null>;
loadSession: (sessionId: string, options?: { messageLimit?: number; signal?: AbortSignal }) => Promise<AgentLoadedChatSession | null>;
streamSession: (
sessionId: string,
@@ -365,6 +367,30 @@ export function createAgentApiClient(
);
},
async createTaskExecutionDebugSession(taskId, runId, sessionId) {
return requestJson<AgentChatSession>(
`/task/${encodeURIComponent(taskId)}/execution-session/debug`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ run_id: runId, session_id: sessionId })
}
);
},
async loadTaskExecutionSession(taskId, runId, sessionId, signal) {
const query = new URLSearchParams({ run_id: runId, session_id: sessionId });
const response = await request(
`/task/${encodeURIComponent(taskId)}/execution-session?${query}`,
{ signal }
);
const text = await response.text();
const data = text ? JSON.parse(text) : null;
if (response.status === 404) return null;
if (!response.ok) throw new Error(getResponseErrorMessage(data, response.status));
return toLoadedSession(data);
},
async getFrontendActionRegistry() {
return requestJson<unknown>("/frontend-action-registry");
},