feat(hub): 结构化运行历史(A-3) + lock 心跳(A-5)

A-3 结构化历史:
- 新增 AgentMessage 表(每条消息的 queryable 投影,transcript 文件仍是 agent 读回记忆)
- 新增 AgentFileChange 表(run 期间文件变更 before/after hash + operation)
- runner.ts: generateText 后 persistAgentMessages 落库 result.responseMessages
- workspace.ts writeFileTool: 写前算 beforeHash(SHA-256),写后算 afterHash,通过 ctx.onFileChange sink 记录
- runner.ts flushFileChanges: 收集 sink 的变更批量写入 AgentFileChange
- ToolContext 加 onFileChange sink 字段
- trigger.ts 接线 runId/sessionId/prisma 到 RunRequest

A-5 lock 心跳:
- ProjectAgentLock 加 heartbeatAt 列
- runner.ts onStepFinish 回调:每步 model step 后更新 heartbeatAt(吞错,lock 可能被 force-release)

60 tests 全过(tsc 干净)。
This commit is contained in:
2026-07-07 22:20:30 +08:00
parent f8c3e16a52
commit e7924f78d5
8 changed files with 280 additions and 7 deletions
+16
View File
@@ -288,6 +288,22 @@ describe("trigger full lifecycle (integration)", () => {
expect(audit.some((a) => a.action === "run.created")).toBe(true);
expect(audit.some((a) => a.action === "run.finished")).toBe(true);
});
it("persists AgentMessage rows for the run (A-3 structured history)", async () => {
await seedProject("proj-15", "chat-15");
const trigger = makeTriggerHandler({ prisma, modelFactory, tools, models, logger: silentLogger });
await trigger(makeEvent("chat-15", "@_user_1 写教案"), rt);
await vi.waitFor(async () => {
const msgs = await prisma.agentMessage.findMany();
expect(msgs.length).toBeGreaterThanOrEqual(1);
});
const msgs = await prisma.agentMessage.findMany({ orderBy: { createdAt: "asc" } });
expect(msgs.length).toBeGreaterThanOrEqual(1);
const run = await prisma.agentRun.findFirst();
expect(msgs.every((m) => m.runId === run?.id)).toBe(true);
expect(msgs.some((m) => m.role === "assistant")).toBe(true);
});
});
afterAll(async () => {