Promote artifact registration to a primary run outcome
Per-tool `afterToolCall` projection stays the primary write path but is now backed by an end-of-run reconciliation sweep on `RuntimeBridge`. `reconcileWorkspaceArtifacts` re-scans the workspace via `git status` before the run's resources are released, idempotently upserting any file the per-tool hook skipped — the exact gap that was leaving the DocumentCenter empty while the disk filled up. Projection failures stop being swallowed silently: the hook now logs a tool-level summary at INFO and throws are routed to ERROR so they are visible in the desktop log tail. The reconciliation sweep surfaces its own failures the same way, so an operator can tell the difference between "no files produced" and "files produced but registration broken".
This commit is contained in:
1 parent
db9375fdfe
commit
23a19a8180
3 files changed
+91
-3
No files matched your search
@@ -11,6 +11,7 @@ import type { CredentialResolver } from "@main/credentials";
|
||||
import { getConnectorDefinition } from "@main/connectors";
|
||||
import { projectInboxMessageToPi } from "./inbox-projection";
|
||||
import { projectAgentEventToLogChunk, extractTextBody } from "./agent-event-projection";
|
||||
import { reconcileWorkspaceArtifacts } from "./artifact-projector";
|
||||
import {
|
||||
handleBeforeToolCall,
|
||||
handleAfterToolCall,
|
||||
@@ -90,14 +91,44 @@ export class RuntimeBridge {
|
||||
* and the inbound-correlation log. Callers must invoke after a
|
||||
* `run-finished` worker message so the maps stay bounded by concurrent-
|
||||
* run count rather than process lifetime.
|
||||
*
|
||||
* Before eviction, sweeps the workspace to register any filesystem
|
||||
* artifacts the per-tool `afterToolCall` hook missed. This is the
|
||||
* backstop that keeps the DocumentCenter consistent with the disk even
|
||||
* when individual projections skipped (bash against a non-git repo,
|
||||
* path-ignore false positives, transient `git status` failures).
|
||||
*/
|
||||
releaseRunResources(runId: string): void {
|
||||
const ctx = this.activeRuns.get(runId);
|
||||
if (ctx) this.reconcileArtifactsForRun(ctx);
|
||||
this.activeRuns.delete(runId);
|
||||
this.inboundCorrelations.delete(runId);
|
||||
if (ctx) this.sessionStores.delete(ctx.taskSessionId);
|
||||
}
|
||||
|
||||
private reconcileArtifactsForRun(ctx: ActiveRunContext): void {
|
||||
if (!ctx.workspacePath) return;
|
||||
try {
|
||||
const outcomes = reconcileWorkspaceArtifacts(this.deps.db, {
|
||||
runId: ctx.runId,
|
||||
agentId: ctx.agentId,
|
||||
companyId: ctx.companyId,
|
||||
taskId: ctx.taskId,
|
||||
workspacePath: ctx.workspacePath,
|
||||
});
|
||||
const inserted = outcomes.filter((o) => o.kind === "inserted").length;
|
||||
if (inserted > 0) {
|
||||
this.deps.logger.info(
|
||||
`[runtime-bridge] reconciliation registered ${inserted} new artifact(s) for run ${ctx.runId}`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
this.deps.logger.error(
|
||||
`[runtime-bridge] artifact reconciliation failed for run ${ctx.runId}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an inbound message that is about to enter (or has already
|
||||
* entered) the recipient's transcript as a user-role turn. Called from
|
||||
|
||||
Reference in new issue
Block a user