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
@@ -4,10 +4,32 @@ import type { WorkspaceLockMode } from "./types";
|
||||
/**
|
||||
* Resolve the workspace and lock semantics for a given run context.
|
||||
*
|
||||
* Previously this logic lived both in run-dispatch.ts (normal dispatch path)
|
||||
* and worker-crash-recovery.ts (re-enqueue after crash); the recovery
|
||||
* copy silently dropped the exclusive/shared distinction. Sharing the
|
||||
* helper guarantees both paths agree on which runs hold exclusive locks.
|
||||
* Lock policy (v0.4.168+):
|
||||
*
|
||||
* task.workspaceId set → exclusive
|
||||
* The user (or an upstream caller) explicitly bound a task to a
|
||||
* workspace, which is the opt-in signal for "this run needs the
|
||||
* workspace to itself". Rare, and reserved for workflows like a
|
||||
* sprint merge or release build where concurrent writes would
|
||||
* corrupt state.
|
||||
*
|
||||
* agent.workspaceId set → shared
|
||||
* project.workspaceId set → shared
|
||||
* company fallback → shared
|
||||
* Multiple agents sharing the *same* workspace is the normal case
|
||||
* — it represents "this team works in this repo", not "this agent
|
||||
* owns this repo". The previous exclusive default forced four
|
||||
* co-located teammates to run one at a time, which deadlocked
|
||||
* heartbeats (dispatch bailed → task stuck in_progress → retry
|
||||
* lost the checkout race forever). Git itself serializes the
|
||||
* dangerous operations (`git index.lock`); anything coarser here
|
||||
* just blocks readers.
|
||||
*
|
||||
* Previously this logic lived both in run-dispatch.ts (normal dispatch
|
||||
* path) and worker-crash-recovery.ts (re-enqueue after crash); the
|
||||
* recovery copy silently dropped the exclusive/shared distinction.
|
||||
* Sharing the helper guarantees both paths agree on which runs hold
|
||||
* exclusive locks.
|
||||
*/
|
||||
export function resolveWorkspaceWithLockMode(
|
||||
db: AppDatabase,
|
||||
@@ -28,12 +50,12 @@ export function resolveWorkspaceWithLockMode(
|
||||
|
||||
const agentWorkspace = db.getWorkspace(agent.workspaceId);
|
||||
if (agentWorkspace) {
|
||||
return { workspace: agentWorkspace, lockMode: "exclusive", source: "agent" };
|
||||
return { workspace: agentWorkspace, lockMode: "shared", source: "agent" };
|
||||
}
|
||||
|
||||
const projectWorkspace = db.getProjectPrimaryWorkspace(task?.projectId ?? null, companyId);
|
||||
if (projectWorkspace) {
|
||||
return { workspace: projectWorkspace, lockMode: "exclusive", source: "project" };
|
||||
return { workspace: projectWorkspace, lockMode: "shared", source: "project" };
|
||||
}
|
||||
|
||||
const fallbackWorkspace = db.getAnyCompanyWorkspace(companyId);
|
||||
|
||||
Reference in new issue
Block a user