From 7b371269c9bd829336c31a65e39a5fa783be9a3f Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 22 Aug 2026 07:05:51 -0700 Subject: [PATCH 1/4] Add tests for sealed-run terminal detection and this-week totals CL-6595: a run whose event log arrives pre-combined in a single push (no per-event blobs) is invisible to the current newly-terminal scan. CL-6667: Mission Control's "this week" total must include today. --- apps/web/test/mission-control-page.test.tsx | 77 +++++++ .../src/workflow-run-kind.test.ts | 204 ++++++++++++++++++ 2 files changed, 281 insertions(+) create mode 100644 vendor/intx/hub-sessions/src/workflow-run-kind.test.ts diff --git a/apps/web/test/mission-control-page.test.tsx b/apps/web/test/mission-control-page.test.tsx index 4b9b663e..34162776 100644 --- a/apps/web/test/mission-control-page.test.tsx +++ b/apps/web/test/mission-control-page.test.tsx @@ -153,6 +153,49 @@ function stubEmptyBenchFetch(): void { }) as typeof fetch; } +function stubBenchFetchWithActivity( + days: { + day: string; + turns: number; + tokens: number; + byModel: { model: string; tokens: number; costUsd: number | null }[]; + }[], +): void { + globalThis.fetch = ((input: RequestInfo | URL) => { + const url = typeof input === "string" ? input : input.toString(); + if (url.includes("/approvals/needs-you")) { + return Promise.resolve( + new Response(JSON.stringify({ items: [] }), { + status: 200, + headers: { "content-type": "application/json" }, + }), + ); + } + if (url.includes("/insights/activity")) { + return Promise.resolve( + new Response(JSON.stringify({ days }), { + status: 200, + headers: { "content-type": "application/json" }, + }), + ); + } + if (url.includes("/agent-definitions/visible")) { + return Promise.resolve( + new Response(JSON.stringify({ definitions: [] }), { + status: 200, + headers: { "content-type": "application/json" }, + }), + ); + } + return Promise.resolve( + new Response(JSON.stringify({ items: [], data: [], nextCursor: null }), { + status: 200, + headers: { "content-type": "application/json" }, + }), + ); + }) as typeof fetch; +} + describe("MissionControlRoute", () => { let container: HTMLDivElement | null = null; let root: Root | null = null; @@ -192,4 +235,38 @@ describe("MissionControlRoute", () => { expect(container.textContent).toContain("Nothing running right now"); expect(container.textContent).toContain("Nothing recent yet."); }); + + test("This week's run count includes today, so it is never less than runs today (CL-6667)", async () => { + const todayKey = new Date().toISOString().slice(0, 10); + const yesterdayKey = new Date(Date.now() - 24 * 60 * 60 * 1000) + .toISOString() + .slice(0, 10); + stubBenchFetchWithActivity([ + { day: yesterdayKey, turns: 4, tokens: 100, byModel: [] }, + { day: todayKey, turns: 10, tokens: 200, byModel: [] }, + ]); + container = document.createElement("div"); + document.body.appendChild(container); + root = createRoot(container); + await act(async () => { + root?.render( + + undefined}> + + undefined} /> + + + , + ); + }); + for (let count = 0; count < 5; count += 1) { + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + } + // Today (10) + yesterday (4) = 14. A "This week" that silently excludes + // today would show 4, which is less than "Runs today" (10) -- the + // logical impossibility CL-6667 reported. + expect(container.textContent).toContain("14 runs"); + }); }); diff --git a/vendor/intx/hub-sessions/src/workflow-run-kind.test.ts b/vendor/intx/hub-sessions/src/workflow-run-kind.test.ts new file mode 100644 index 00000000..ae8c3c9e --- /dev/null +++ b/vendor/intx/hub-sessions/src/workflow-run-kind.test.ts @@ -0,0 +1,204 @@ +import { describe, expect, test } from "bun:test"; + +import { + WORKFLOW_RUN_RUNS_PREFIX, + readCommittedWorkflowRunTerminalStatus, + workflowRunKindHandler, +} from "./workflow-run-kind"; +import { WORKFLOW_RUN_EVENTS_FILE } from "./workflow-run-event-log"; +import type { CommittedReads, CommittedTreeEntry } from "./repo-store"; + +const encoder = new TextEncoder(); + +/** A `CommittedReads` fake backed by an in-memory path -> content map. */ +function makeCommittedReads(files: Record): CommittedReads { + const oidFor = (path: string) => `oid:${path}`; + return { + listDir: async (relPath: string): Promise => { + const prefix = relPath === "" ? "" : `${relPath}/`; + const children = new Map(); + for (const p of Object.keys(files)) { + if (!p.startsWith(prefix)) continue; + const rest = p.slice(prefix.length); + const [name, ...more] = rest.split("/"); + if (name === undefined) continue; + children.set(name, { + name, + oid: more.length === 0 ? oidFor(p) : oidFor(`${prefix}${name}`), + type: more.length === 0 ? "blob" : "tree", + }); + } + return [...children.values()]; + }, + readBlobByOid: async (oid: string): Promise => { + for (const [p, content] of Object.entries(files)) { + if (oidFor(p) === oid) return encoder.encode(content); + } + throw new Error(`no such blob oid: ${oid}`); + }, + treeOid: async () => null, + }; +} + +/** + * A minimal in-memory tree: paths are POSIX, root-relative, no leading + * slash. `listDir` returns the direct child names of a directory path + * (files and subdirectories alike, matching the substrate's contract); + * `readBlob`/`priorReadBlob` resolve a file's own bytes. + */ +function makeTree(files: Record) { + const paths = Object.keys(files); + const listDir = async (dir: string): Promise => { + const prefix = dir === "" ? "" : `${dir}/`; + const children = new Set(); + for (const p of paths) { + if (!p.startsWith(prefix)) continue; + const rest = p.slice(prefix.length); + const name = rest.split("/")[0]; + if (name !== undefined) children.add(name); + } + return [...children]; + }; + const readBlob = async (path: string): Promise => { + const content = files[path]; + if (content === undefined) throw new Error(`no such blob: ${path}`); + return encoder.encode(content); + }; + return { listDir, readBlob }; +} + +function emptyPrior() { + return { + priorListDir: async () => [], + priorReadBlob: async () => null, + }; +} + +const hubPrincipal = { kind: "hub" as const }; + +describe("workflowRunKindHandler.validatePush — CL-6595 combined-run terminal detection", () => { + test("a run sealed from birth (events.jsonl only, never per-event files) is reported newly terminal", async () => { + // This is the exact shape of the runs behind CL-6595: the run's own + // trace page already says "This run finished before we started + // recording steps" because its entire event log arrived pre-combined + // in a single push, with no per-event `.json` blobs ever landing. + const combined = [ + JSON.stringify({ type: "RunStarted", seq: 0 }), + JSON.stringify({ type: "RunCompleted", seq: 1 }), + ].join("\n"); + const { listDir, readBlob } = makeTree({ + [`${WORKFLOW_RUN_RUNS_PREFIX}/run_1/${WORKFLOW_RUN_EVENTS_FILE}`]: combined, + }); + + const result = await workflowRunKindHandler.validatePush({ + repoId: { kind: "workflow-run", id: "run_1" }, + ref: "refs/heads/main", + principal: hubPrincipal, + topLevelTreePaths: [WORKFLOW_RUN_RUNS_PREFIX], + readBlob, + listDir, + ...emptyPrior(), + }); + + expect(result.ok).toBe(true); + if (!result.ok) return; + expect(result.newlyTerminalRuns).toEqual([ + { runId: "run_1", status: "completed", terminalEventJson: expect.any(String) }, + ]); + }); + + test("a run already sealed in the prior tree is not reported newly terminal again", async () => { + const combined = [ + JSON.stringify({ type: "RunStarted", seq: 0 }), + JSON.stringify({ type: "RunFailed", seq: 1 }), + ].join("\n"); + const files = { + [`${WORKFLOW_RUN_RUNS_PREFIX}/run_1/${WORKFLOW_RUN_EVENTS_FILE}`]: combined, + }; + const { listDir, readBlob } = makeTree(files); + const prior = makeTree(files); + + const result = await workflowRunKindHandler.validatePush({ + repoId: { kind: "workflow-run", id: "run_1" }, + ref: "refs/heads/main", + principal: hubPrincipal, + topLevelTreePaths: [WORKFLOW_RUN_RUNS_PREFIX], + readBlob, + listDir, + priorListDir: prior.listDir, + priorReadBlob: async (path) => { + try { + return await prior.readBlob(path); + } catch { + return null; + } + }, + }); + + expect(result.ok).toBe(true); + if (!result.ok) return; + expect(result.newlyTerminalRuns ?? []).toEqual([]); + }); + + test("a live per-event run with no terminal event yet is not reported terminal", async () => { + const { listDir, readBlob } = makeTree({ + [`${WORKFLOW_RUN_RUNS_PREFIX}/run_1/events/0.json`]: JSON.stringify({ + type: "RunStarted", + seq: 0, + }), + }); + + const result = await workflowRunKindHandler.validatePush({ + repoId: { kind: "workflow-run", id: "run_1" }, + ref: "refs/heads/main", + principal: hubPrincipal, + topLevelTreePaths: [WORKFLOW_RUN_RUNS_PREFIX], + readBlob, + listDir, + ...emptyPrior(), + }); + + expect(result.ok).toBe(true); + if (!result.ok) return; + expect(result.newlyTerminalRuns ?? []).toEqual([]); + }); +}); + +describe("readCommittedWorkflowRunTerminalStatus", () => { + test("maps a sealed run's terminal event to its workflow_run.status", async () => { + const reads = makeCommittedReads({ + [`${WORKFLOW_RUN_RUNS_PREFIX}/run_1/${WORKFLOW_RUN_EVENTS_FILE}`]: [ + JSON.stringify({ type: "RunStarted", seq: 0 }), + JSON.stringify({ type: "RunCancelled", seq: 1 }), + ].join("\n"), + }); + expect( + await readCommittedWorkflowRunTerminalStatus(reads, "run_1"), + ).toBe("cancelled"); + }); + + test("returns null for a live per-event run", async () => { + const reads = makeCommittedReads({ + [`${WORKFLOW_RUN_RUNS_PREFIX}/run_1/events/0.json`]: JSON.stringify({ + type: "RunStarted", + seq: 0, + }), + }); + expect( + await readCommittedWorkflowRunTerminalStatus(reads, "run_1"), + ).toBeNull(); + }); + + test("returns null for an absent run", async () => { + const reads = makeCommittedReads({}); + expect( + await readCommittedWorkflowRunTerminalStatus(reads, "run_1"), + ).toBeNull(); + }); + + test("returns null when reads is null (no ref/repo yet)", async () => { + expect( + await readCommittedWorkflowRunTerminalStatus(null, "run_1"), + ).toBeNull(); + }); +}); From 1b46e920016b5bdd48ada5c3af0eac1c31a974bd Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 22 Aug 2026 07:06:00 -0700 Subject: [PATCH 2/4] workflow-run-kind: detect terminal status on sealed combined event logs (CL-6595) enumerateEventBlobs skips a run whose events already live in one combined events.jsonl instead of per-event .json blobs, so a run sealed from birth (its whole log, terminal event included, arriving in a single push) never surfaced to the newly-terminal scan and markTerminal never fired -- workflow_run.status stayed "running" forever despite the run having genuinely finished and delivered. validatePush now also walks the combined-run set and reports a newly-sealed run as terminal by reading its log's last event. hub-session-lookups.ts adds a same-push backfill via the new readCommittedWorkflowRunTerminalStatus export, so a run that still somehow slips past the primary detection self-heals on its next pack instead of every future reader hitting the same stale column. --- .../hub-sessions/src/hub-session-lookups.ts | 40 +++++- vendor/intx/hub-sessions/src/index.ts | 1 + .../hub-sessions/src/workflow-run-kind.ts | 117 ++++++++++++++++++ 3 files changed, 157 insertions(+), 1 deletion(-) diff --git a/vendor/intx/hub-sessions/src/hub-session-lookups.ts b/vendor/intx/hub-sessions/src/hub-session-lookups.ts index 58539262..2bcad1e6 100644 --- a/vendor/intx/hub-sessions/src/hub-session-lookups.ts +++ b/vendor/intx/hub-sessions/src/hub-session-lookups.ts @@ -34,7 +34,10 @@ import { listAcceptedWorkflowDispatches, listConsumedWorkflowDispatches, } from "./workflow-dispatch-settlement"; -import { readCommittedWorkflowRunLifecycle } from "./workflow-run-kind"; +import { + readCommittedWorkflowRunLifecycle, + readCommittedWorkflowRunTerminalStatus, +} from "./workflow-run-kind"; const logger = getLogger(["hub", "lookups"]); @@ -730,6 +733,41 @@ export function createHubSessionLookups( } catch (error) { logger.error`Failed to close unsettled workflow dispatches for terminal run ${anchorAddress}: ${error instanceof Error ? error.message : String(error)}`; } + + // Defense in depth for CL-6595: the committed Git log just proved + // this run terminal, independent of whether the newly-terminal + // detection above caught it on this pack (or any earlier one). If + // `workflow_run.status` is still live, self-heal it here rather than + // leaving every future reader to hit the same stale column. + try { + const reads = await agentRepoStore.repoStore.openCommittedReads( + { kind: "hub" }, + repoId, + ref, + ); + const status = await readCommittedWorkflowRunTerminalStatus( + reads, + anchor.id, + ); + if (status !== null) { + const won = await db.transaction((tx) => + workflowRunStore.markTerminal(anchor.id, status, now, tx), + ); + if (won !== null && won.principalId !== null) { + await db + .update(principal) + .set({ status: "deactivated", updatedAt: now }) + .where( + and( + eq(principal.id, won.principalId), + eq(principal.refId, anchor.id), + ), + ); + } + } + } catch (error) { + logger.error`Terminal-status backfill failed for run ${anchor.id}; workflow_run.status may still read live: ${error instanceof Error ? error.message : String(error)}`; + } } return { accepted: true }; diff --git a/vendor/intx/hub-sessions/src/index.ts b/vendor/intx/hub-sessions/src/index.ts index 5054518f..8c017364 100644 --- a/vendor/intx/hub-sessions/src/index.ts +++ b/vendor/intx/hub-sessions/src/index.ts @@ -161,6 +161,7 @@ export { markConsumed, readOwnedMessageIds, readCommittedWorkflowRunLifecycle, + readCommittedWorkflowRunTerminalStatus, readWorkflowRunLifecycle, replayProcessingToInbox, WORKFLOW_RUN_GITIGNORE_PATH, diff --git a/vendor/intx/hub-sessions/src/workflow-run-kind.ts b/vendor/intx/hub-sessions/src/workflow-run-kind.ts index 09ad7e01..5548a1d3 100644 --- a/vendor/intx/hub-sessions/src/workflow-run-kind.ts +++ b/vendor/intx/hub-sessions/src/workflow-run-kind.ts @@ -2259,6 +2259,44 @@ export const workflowRunKindHandler: KindHandler = { return { ok: false, reason: combinedRuns.reason }; } + // A sealed run (`hasCombined`, above) is skipped by the per-event + // terminal scan entirely, since its events never appear as individual + // `.json` blobs -- `checkCombinedStructure` already proved every + // combined `events.jsonl` ends on a terminal event, so every combined + // run IS terminal. Surface it here as newly terminal unless the prior + // tree already carried the same sealed file (already reported on + // whichever push first sealed it, or a run compacted before its first + // push ever landed -- CL-6595's "finished before we started recording + // steps" runs are exactly this case: sealed from birth, so the + // per-event loop above never had a blob to see them by). + for (const runId of combinedRuns.combinedRunIds) { + const runDirPath = `${WORKFLOW_RUN_RUNS_PREFIX}/${runId}`; + const priorChildren = await priorListDir(runDirPath); + if (priorChildren.includes(WORKFLOW_RUN_EVENTS_FILE)) continue; + const combinedPath = `${runDirPath}/${WORKFLOW_RUN_EVENTS_FILE}`; + const content = new TextDecoder().decode(await readBlob(combinedPath)); + const lines = splitCombinedEventLog(content); + const lastLine = lines[lines.length - 1]; + if (lastLine === undefined) { + throw new Error( + `combined event log ${combinedPath} sealed with no lines after passing structural validation`, + ); + } + const body: unknown = JSON.parse(lastLine); + const type = + typeof body === "object" && body !== null && "type" in body + ? body.type + : undefined; + const status = + typeof type === "string" ? TERMINAL_EVENT_STATUS.get(type) : undefined; + if (status === undefined) { + throw new Error( + `combined event log ${combinedPath} for run ${runId} sealed without a recognized terminal event type`, + ); + } + newlyTerminalRuns.push({ runId, status, terminalEventJson: lastLine }); + } + const blobsEnumerated = await enumerateRunBlobs(listDir, scopeRunIds); if (!blobsEnumerated.ok) { logger.debug`workflow-run validatePush rejected ${repoId.kind}/${repoId.id} on ${ref}: ${blobsEnumerated.reason}`; @@ -3375,6 +3413,85 @@ export async function readCommittedWorkflowRunLifecycle( return eventEntries.length === 0 ? "absent" : "live"; } +/** + * Read one run's terminal `workflow_run.status` value from a committed + * workflow-run tree, or `null` if the run has not reached a terminal event. + * Companion to `readCommittedWorkflowRunLifecycle` for a caller that needs + * the actual status to write (e.g. a `markTerminal` backfill), not just the + * live/terminal/absent classification. + */ +export async function readCommittedWorkflowRunTerminalStatus( + reads: CommittedReads | null, + runId: string, +): Promise<"completed" | "failed" | "cancelled" | null> { + if (reads === null) return null; + const runPath = `${WORKFLOW_RUN_RUNS_PREFIX}/${runId}`; + const runChildren = await reads.listDir(runPath); + const sealed = runChildren.find( + (entry) => entry.type === "blob" && entry.name === WORKFLOW_RUN_EVENTS_FILE, + ); + if (sealed !== undefined) { + const content = new TextDecoder().decode( + await reads.readBlobByOid(sealed.oid), + ); + const lines = splitCombinedEventLog(content); + const lastLine = lines[lines.length - 1]; + if (lastLine === undefined) { + throw new Error( + `combined event log ${runPath}/${WORKFLOW_RUN_EVENTS_FILE} is sealed but has no lines`, + ); + } + const body: unknown = JSON.parse(lastLine); + const type = + typeof body === "object" && body !== null && "type" in body + ? body.type + : undefined; + const status = + typeof type === "string" ? TERMINAL_EVENT_STATUS.get(type) : undefined; + if (status === undefined) { + throw new Error( + `sealed run ${runId} has no recognized terminal event type`, + ); + } + return status; + } + + const eventsPath = `${runPath}/${WORKFLOW_RUN_EVENTS_DIR}`; + const eventEntries = (await reads.listDir(eventsPath)).filter( + (entry) => entry.type === "blob" && parseEventSeq(entry.name) !== null, + ); + const latest = eventEntries.reduce<(typeof eventEntries)[number] | undefined>( + (candidate, entry) => { + if (candidate === undefined) return entry; + const candidateSeq = parseEventSeq(candidate.name); + const entrySeq = parseEventSeq(entry.name); + return entrySeq !== null && + candidateSeq !== null && + entrySeq > candidateSeq + ? entry + : candidate; + }, + undefined, + ); + if (latest === undefined) return null; + const eventPath = `${eventsPath}/${latest.name}`; + let parsed: unknown; + try { + parsed = JSON.parse( + new TextDecoder().decode(await reads.readBlobByOid(latest.oid)), + ); + } catch (cause) { + throw new Error(`workflow_run_event_unreadable: ${eventPath}`, { cause }); + } + const type = + typeof parsed === "object" && parsed !== null && "type" in parsed + ? parsed.type + : undefined; + return typeof type === "string" + ? (TERMINAL_EVENT_STATUS.get(type) ?? null) + : null; +} + /** * Read the durable lifecycle of one run from the workflow-run working tree. * `grants.json` alone is still an absent run: grants are staged before the From b19757f5703037e745bee81db39247b4ba7b7c1e Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 22 Aug 2026 07:06:01 -0700 Subject: [PATCH 3/4] mission-control: include today in the this-week total (CL-6667) "This week" summed over priorDays, which explicitly excludes today -- so it could read lower than "Runs today" on the same page, which is never legitimate since a week necessarily includes today. --- apps/web/src/pages/mission-control-page.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/web/src/pages/mission-control-page.tsx b/apps/web/src/pages/mission-control-page.tsx index bdb94770..a1a0b60f 100644 --- a/apps/web/src/pages/mission-control-page.tsx +++ b/apps/web/src/pages/mission-control-page.tsx @@ -508,16 +508,16 @@ export function MissionControlRoute({ ) : null} {insightsActivity.kind === "ready" ? (

- {priorDays + {days .reduce((sum, day) => sum + day.turns, 0) .toLocaleString()}{" "} runs ·{" "} {formatUsd( - priorDays.some((day) => + days.some((day) => day.byModel.some((model) => model.costUsd === null), ) ? null - : priorDays.reduce( + : days.reduce( (sum, day) => sum + day.byModel.reduce( From 2e3d6f37c862bc5d51d813cafc7ca0c942e6bad6 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sat, 22 Aug 2026 07:06:24 -0700 Subject: [PATCH 4/4] Update docs: record CL-6595 sealed-run terminal detection delta --- VENDORED.md | 11 +++++++++++ scripts/checks/kill-dates.txt | 2 +- vendor/intx/hub-sessions/VENDORED-FROM | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/VENDORED.md b/VENDORED.md index 26c3acc9..da39c797 100644 --- a/VENDORED.md +++ b/VENDORED.md @@ -226,6 +226,17 @@ can re-invert is now persisted as-is; anything else collapses to a stable bad tool-call name fails that turn cleanly instead of wedging the room. `@intx/inference` is added to `vendor/intx/hub-sessions`'s own `package.json` dependencies for this. +`vendor/intx/hub-sessions` (CL-6595) fixes `workflow-run-kind.ts`'s +newly-terminal detection, which skipped a run's `events.jsonl` subtree +entirely (`enumerateEventBlobs` only walks per-event `.json` files), +so a run sealed from birth — its whole event log arriving pre-combined in +one push, with no per-event blobs ever landing — never fired `markTerminal` +and stayed "running" in `workflow_run.status` forever despite the run +having genuinely finished; `validatePush` now also scans a newly-sealed +run's combined log for its terminal event, and `hub-session-lookups.ts` +gained a same-push defense-in-depth backfill via the new +`readCommittedWorkflowRunTerminalStatus` export, in case a future pack still +slips past the primary detection. Each package's `VENDORED-FROM` file restates its own delta. `apps/sidecar` records `b5580a02` (v0.3.0): the fork tracks the diff --git a/scripts/checks/kill-dates.txt b/scripts/checks/kill-dates.txt index 8c73d886..afbf3686 100644 --- a/scripts/checks/kill-dates.txt +++ b/scripts/checks/kill-dates.txt @@ -16,7 +16,7 @@ apps/sidecar | sawyer | 2026-09-19 vendor/intx/db | sawyer | 2026-09-19 | 642b29735a7e36a1d3decdf8af26c33fa91e0989720323addb4c2f786ae88a18 vendor/intx/hub-api | sawyer | 2026-09-19 | 8449bf150e0253ca5d5eb26450c3ce2e5ff81116a02cdf2ba049b26453cfa201 -vendor/intx/hub-sessions | sawyer | 2026-09-19 | ac63efef43d612a610af470a632298a3358fff6f9400cf4ce4b4fa431ef10015 +vendor/intx/hub-sessions | sawyer | 2026-09-19 | df5f1d275e197668851c53f58c51182d8bc455f3585e2ed65784d3687a856001 vendor/intx/workflow | sawyer | 2026-09-19 | 34628e7bbd0587f131a07e3a206141983881106963a20ab607e68aeed1135593 vendor/intx/workflow-deploy | sawyer | 2026-09-19 | 95711adf282180852b0daec1cac39d00a4dc24aff15f9a515e07eb3d2ca749f9 vendor/intx/workflow-host | sawyer | 2026-09-19 | 6e6717e784cc55035a595320b2b8e6ea01b49ac42d4c77b444a0dc59e354b8d0 diff --git a/vendor/intx/hub-sessions/VENDORED-FROM b/vendor/intx/hub-sessions/VENDORED-FROM index 0460d897..b882821e 100644 --- a/vendor/intx/hub-sessions/VENDORED-FROM +++ b/vendor/intx/hub-sessions/VENDORED-FROM @@ -5,3 +5,4 @@ Local modifications: exports map repointed from the upstream intx-src condition CL-6388: `deployCodeSourcedWorkflow` now INSERTs its anchor `workflow_run` row BEFORE emitting the source-ref deploy frame (publicKey null until the ack stamps it; a failed emit deletes the row). Upstream's frame-then-insert ordering let the spawned child's first refs/heads/events pack push race the deploy ack, and receiveWorkflowRunPack fails closed (path_violation) on the missing anchor row, so every fresh deployment's first events pack was rejected and the durable event log never bootstrapped. CL-6395: CL-6388's "a failed emit deletes the row" was too broad — any rejection from `emitSourceRefDeployFrame`, including an ack-timeout or socket-drop that fires strictly AFTER the `agent.deploy` frame already reached the sidecar, deleted the anchor row and permanently orphaned an already-spawned child on the missing-anchor `path_violation` path. `ws/sidecar-handler.ts` now exports `DeployFrameNotSentError`, thrown only by a guard clause that runs before `conn.send()` or by `conn.send()` itself throwing synchronously — the sole cases that provably never reached the wire; every other deploy rejection (timeout, disconnect, reconnect takeover, ack-processing failure) is raised through the pending-deploy's `reject()`, which by construction only fires after the send. `deployCodeSourcedWorkflow` deletes the pre-inserted row only on `DeployFrameNotSentError`; any other failure keeps the row and logs one reconciliation line. Also corrects an overclaiming comment in hub-session-lookups.ts: `markTerminal`'s null return means no row in a LIVE status (`deployed` or `running`) matched, not specifically "running". CL-6478: `event-collector.ts`'s `tool_call` handling in `handleInferenceDone` now runs `block.name` through a new `sanitize-tool-name.ts` module before persisting it. `@intx/inference`'s `decodeToolName` is deliberately total — a hallucinated or provider-mangled function name is returned verbatim rather than throwing — but `encodeToolName` throws when that same name is later put back on the wire to build the next turn's outbound request, so persisting a decoded name unchecked wedged the room forever once the bad name was durable. `sanitizeToolNameForPersistence` round-trips the name through `encodeToolName` before it is written; a name that cannot be re-encoded collapses to a stable `malformed_tool_call` placeholder instead. `@intx/inference` is added to this package's own `package.json` dependencies for the check. +CL-6595: `workflow-run-kind.ts`'s newly-terminal detection in `validatePush` only ever scanned a run's per-event `runs//events/.json` blobs; `enumerateEventBlobs` explicitly skips a run whose events already live in a combined `events.jsonl` (`hasCombined` -> `continue`), so a run sealed from birth — its entire event log, including the terminal event, arriving pre-combined in a single push with no per-event blobs ever landing — was never surfaced as newly terminal and `markTerminal` never fired, leaving `workflow_run.status` stuck live forever despite the run having genuinely finished. `validatePush` now also walks `validateCombinedEventRuns`' `combinedRunIds` and reports a newly-sealed run (absent from the prior tree's combined form) as terminal by reading its combined log's last (terminal, by `checkCombinedStructure`'s own invariant) event. A new `readCommittedWorkflowRunTerminalStatus` export mirrors `readCommittedWorkflowRunLifecycle` but returns the mapped `workflow_run.status` value instead of just live/terminal/absent; `hub-session-lookups.ts`'s pack-receive path now calls it as a same-push defense-in-depth backfill (calling `markTerminal` directly) whenever the committed log proves a run terminal, independent of whether the primary per-push detection caught it.