forked from bai/curriculum-project-hub
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:
@@ -0,0 +1,45 @@
|
||||
-- AgentMessage: per-message record of the agent loop (queryable projection
|
||||
-- alongside the transcript JSONL file, which remains the agent's read-back memory).
|
||||
CREATE TABLE "AgentMessage" (
|
||||
"id" TEXT NOT NULL,
|
||||
"sessionId" TEXT NOT NULL,
|
||||
"runId" TEXT,
|
||||
"role" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"attachments" JSONB NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "AgentMessage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "AgentMessage_sessionId_createdAt_idx" ON "AgentMessage"("sessionId", "createdAt");
|
||||
CREATE INDEX "AgentMessage_runId_idx" ON "AgentMessage"("runId");
|
||||
|
||||
ALTER TABLE "AgentMessage" ADD CONSTRAINT "AgentMessage_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "AgentSession"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "AgentMessage" ADD CONSTRAINT "AgentMessage_runId_fkey" FOREIGN KEY ("runId") REFERENCES "AgentRun"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AgentFileChange: file change captured during a run (write/rename/delete).
|
||||
CREATE TABLE "AgentFileChange" (
|
||||
"id" TEXT NOT NULL,
|
||||
"runId" TEXT NOT NULL,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"path" TEXT NOT NULL,
|
||||
"operation" TEXT NOT NULL,
|
||||
"beforeHash" TEXT,
|
||||
"afterHash" TEXT,
|
||||
"diff" TEXT,
|
||||
"metadata" JSONB NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "AgentFileChange_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX "AgentFileChange_runId_idx" ON "AgentFileChange"("runId");
|
||||
CREATE INDEX "AgentFileChange_projectId_idx" ON "AgentFileChange"("projectId");
|
||||
CREATE INDEX "AgentFileChange_path_idx" ON "AgentFileChange"("path");
|
||||
|
||||
ALTER TABLE "AgentFileChange" ADD CONSTRAINT "AgentFileChange_runId_fkey" FOREIGN KEY ("runId") REFERENCES "AgentRun"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "AgentFileChange" ADD CONSTRAINT "AgentFileChange_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- ProjectAgentLock: add heartbeatAt for liveness (A-5).
|
||||
ALTER TABLE "ProjectAgentLock" ADD COLUMN "heartbeatAt" TIMESTAMP(3);
|
||||
@@ -84,6 +84,7 @@ model Project {
|
||||
permissionSettings PermissionSettings[] @relation("projectSettings")
|
||||
roleTriggerGrants RoleTriggerGrant[] @relation("projectRoleGrants")
|
||||
auditEntries AuditEntry[] @relation("projectAudit")
|
||||
fileChanges AgentFileChange[] @relation("projectFileChanges")
|
||||
|
||||
@@index([archivedAt])
|
||||
}
|
||||
@@ -125,6 +126,7 @@ model AgentSession {
|
||||
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
runs AgentRun[]
|
||||
messages AgentMessage[] @relation("sessionMessages")
|
||||
|
||||
@@index([projectId, archivedAt])
|
||||
@@index([provider, model])
|
||||
@@ -172,6 +174,8 @@ model AgentRun {
|
||||
session AgentSession? @relation(fields: [sessionId], references: [id], onDelete: SetNull)
|
||||
requestedBy User? @relation("runRequester", fields: [requestedByUserId], references: [id], onDelete: SetNull)
|
||||
projectLock ProjectAgentLock?
|
||||
messages AgentMessage[] @relation("runMessages")
|
||||
fileChanges AgentFileChange[] @relation("runFileChanges")
|
||||
|
||||
@@index([projectId, status])
|
||||
@@index([sessionId])
|
||||
@@ -184,11 +188,12 @@ model AgentRun {
|
||||
/// a run holds at most one lock. WellFormed (holder is non-terminal) is an
|
||||
/// app-level invariant checked on read/write, not a DB constraint.
|
||||
model ProjectAgentLock {
|
||||
projectId String @id
|
||||
runId String @unique
|
||||
projectId String @id
|
||||
runId String @unique
|
||||
holderUserId String?
|
||||
acquiredAt DateTime @default(now())
|
||||
expiresAt DateTime?
|
||||
acquiredAt DateTime @default(now())
|
||||
heartbeatAt DateTime?
|
||||
expiresAt DateTime?
|
||||
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
run AgentRun @relation(fields: [runId], references: [id], onDelete: Cascade)
|
||||
@@ -331,3 +336,49 @@ model FeishuEventReceipt {
|
||||
@@index([messageId])
|
||||
@@index([receivedAt])
|
||||
}
|
||||
|
||||
// --- Structured run history (ADR-0003 Memory) ----------------------------
|
||||
|
||||
/// Per-message record of the agent loop. The transcript JSONL file remains the
|
||||
/// agent's own read-back memory (runner loads it to seed the next run); these
|
||||
/// rows are the queryable/auditable projection for admin APIs. ADR-0003:
|
||||
/// session messages ≠ Feishu group chat history.
|
||||
model AgentMessage {
|
||||
id String @id @default(cuid())
|
||||
sessionId String
|
||||
runId String?
|
||||
role String
|
||||
content String
|
||||
attachments Json
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
session AgentSession @relation("sessionMessages", fields: [sessionId], references: [id], onDelete: Cascade)
|
||||
run AgentRun? @relation("runMessages", fields: [runId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([sessionId, createdAt])
|
||||
@@index([runId])
|
||||
}
|
||||
|
||||
/// File change captured during a run (writeFile/rename/delete in workspace).
|
||||
/// before/after hash + diff for audit and admin review. Linked to the run
|
||||
/// that produced it; the tool's execute closure records via a sink the runner
|
||||
/// injects into ToolContext (A-3 file-change capture).
|
||||
model AgentFileChange {
|
||||
id String @id @default(cuid())
|
||||
runId String
|
||||
projectId String
|
||||
path String
|
||||
operation String
|
||||
beforeHash String?
|
||||
afterHash String?
|
||||
diff String?
|
||||
metadata Json
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
run AgentRun @relation("runFileChanges", fields: [runId], references: [id], onDelete: Cascade)
|
||||
project Project @relation("projectFileChanges", fields: [projectId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([runId])
|
||||
@@index([projectId])
|
||||
@@index([path])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user