refactor(session): agent-scope rotation + accumulation; auto-register file artifacts
Sessions are already agent-scoped post-v34, but the code kept routing
rotation / accumulation / handoff through the task indirection. Heartbeat
runs (no task) silently skipped all three, letting taskless transcripts
grow past the model's context window — the 7M-token explosion.
- DB adds getSessionById(sessionId) — direct lookup bypassing
tasks.active_session_id
- session-management-service: generateHandoff(session) is now agent-scoped,
walking recent runs across tasks + heartbeats; getHandoffForAgent(agentId)
is the canonical lookup, getHandoffContext(taskId) is a thin shim for
callers that still hold a task id
- turn-context.ts::resolveSession drops the `if (!task)` early return:
rotation evaluation runs on the agent session unconditionally
- run-finished-helpers.ts::accumulateAndRotateSession drops the `!task`
gate: tokens accumulate and rotation fires for every run, keyed on the
agent session, not the task
- Rotation evaluator contract: evaluatePreRunSessionRotation(agentId,
connectorId) → { rotated, handoff, sessionId }; old (taskId, connectorId)
signature removed
IPC contract tightening: ipc-support::emitEvent becomes a best-effort
sink. "Render frame was disposed" during renderer reload is a lifecycle
signal, not a logic error; silently drop it instead of letting it abort
downstream event emissions.
Artifact projection: pi's write/edit tool results now upsert a
`work_products` row via a new agent-runtime/artifact-projector.ts. No
more agents writing 54 files into the workspace that the UI never sees.
Migration v40 relaxes work_products.task_id to nullable so heartbeat
artifacts can register too.
File size: split agent-event-projection.ts out of runtime-bridge.ts to
keep the bridge under the 400-line cap after adding the projector hook.
Tests: 604 unit + integration green; generateHandoff callsites updated
for the single-argument signature; artifact-projector has pure-function
coverage via the projector module's own shape.
This commit is contained in:
1 parent
61fb4b947f
commit
ff5e22092a
45 files changed
+3409
-300
No files matched your search
@@ -185,7 +185,7 @@ export function buildDocumentMethods(
|
||||
// ── Work Products ──
|
||||
|
||||
createWorkProduct(input: {
|
||||
companyId: string; taskId: string; projectId?: string | null;
|
||||
companyId: string; taskId: string | null; projectId?: string | null;
|
||||
type: string; provider: string; externalId?: string | null;
|
||||
title: string; url?: string | null; status?: string; reviewState?: string;
|
||||
isPrimary?: boolean; summary?: string | null; metadataJson?: string | null;
|
||||
@@ -193,7 +193,7 @@ export function buildDocumentMethods(
|
||||
}): string {
|
||||
const id = randomUUID();
|
||||
const ts = nowIso();
|
||||
if (input.isPrimary) {
|
||||
if (input.isPrimary && input.taskId) {
|
||||
getDb().prepare(
|
||||
"UPDATE work_products SET is_primary = 0, updated_at = ? WHERE company_id = ? AND task_id = ? AND type = ?",
|
||||
).run(ts, input.companyId, input.taskId, input.type);
|
||||
@@ -202,7 +202,7 @@ export function buildDocumentMethods(
|
||||
`INSERT INTO work_products (id, company_id, project_id, task_id, type, provider, external_id, title, url, status, review_state, is_primary, summary, metadata_json, created_by_run_id, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
).run(
|
||||
id, input.companyId, input.projectId ?? null, input.taskId,
|
||||
id, input.companyId, input.projectId ?? null, input.taskId ?? null,
|
||||
input.type, input.provider, input.externalId ?? null,
|
||||
input.title, input.url ?? null, input.status ?? "active",
|
||||
input.reviewState ?? "none", input.isPrimary ? 1 : 0,
|
||||
@@ -212,6 +212,29 @@ export function buildDocumentMethods(
|
||||
return id;
|
||||
},
|
||||
|
||||
/**
|
||||
* Look up a work product by its natural identity — the tuple
|
||||
* (companyId, provider, externalId) uniquely names a registered
|
||||
* artifact. Returns null when not found. Used by the artifact
|
||||
* projector to make tool-output registration idempotent: the first
|
||||
* write inserts, subsequent writes (same path, same provider) go
|
||||
* through `updateWorkProduct` instead of spamming duplicates.
|
||||
*/
|
||||
findWorkProductByExternalId(
|
||||
companyId: string,
|
||||
provider: string,
|
||||
externalId: string,
|
||||
): { id: string; taskId: string | null } | null {
|
||||
const row = getDb()
|
||||
.prepare(
|
||||
"SELECT id, task_id as taskId FROM work_products WHERE company_id = ? AND provider = ? AND external_id = ? LIMIT 1",
|
||||
)
|
||||
.get(companyId, provider, externalId) as
|
||||
| { id: string; taskId: string | null }
|
||||
| undefined;
|
||||
return row ?? null;
|
||||
},
|
||||
|
||||
updateWorkProduct(
|
||||
id: string,
|
||||
companyId: string,
|
||||
|
||||
Reference in new issue
Block a user