orkestrator 2.22.3 → 2.24.0-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2377,6 +2377,7 @@ var DEBUG_EVENTS = new Set([
2377
2377
  "[session-manager] Prompt completed",
2378
2378
  "[session-manager] Query completed successfully",
2379
2379
  "[session-manager] Query result",
2380
+ "[session-manager] Query stopped by the user",
2380
2381
  "[session-manager] Question not found for requestId:",
2381
2382
  "[session-manager] Re-prompting after approved-plan ExitPlanMode failure",
2382
2383
  "[session-manager] Re-prompting with plan rejection feedback",
@@ -2533,8 +2534,24 @@ function claudeSessionPreferencesDir() {
2533
2534
  function claudeCommandChangesDir() {
2534
2535
  return join(claudeHome(), ".claude", "orkestrator", "command-changes");
2535
2536
  }
2537
+ function claudeChangeEvidenceDir() {
2538
+ return join(claudeHome(), ".claude", "orkestrator", "change-evidence");
2539
+ }
2536
2540
 
2537
2541
  // ../../bridges/claude-bridge/src/services/session-preferences.ts
2542
+ function parseTurnInFlightMark(value) {
2543
+ if (!value || typeof value !== "object" || Array.isArray(value))
2544
+ return;
2545
+ const record = value;
2546
+ if (typeof record.bridgeInstanceId !== "string" || record.bridgeInstanceId.length === 0 || record.bridgeInstanceId.length > 64 || typeof record.pid !== "number" || !Number.isSafeInteger(record.pid) || record.pid <= 0 || typeof record.startedAt !== "number" || !Number.isFinite(record.startedAt)) {
2547
+ return;
2548
+ }
2549
+ return {
2550
+ bridgeInstanceId: record.bridgeInstanceId,
2551
+ pid: record.pid,
2552
+ startedAt: record.startedAt
2553
+ };
2554
+ }
2538
2555
  var MAX_SHELL_TASK_DETAILS = 256;
2539
2556
  function parseShellTaskDetails(value) {
2540
2557
  if (!value || typeof value !== "object")
@@ -2782,6 +2799,9 @@ function parsePreferences(raw) {
2782
2799
  if (Array.isArray(record.shellTaskDetails)) {
2783
2800
  preferences.shellTaskDetails = record.shellTaskDetails.slice(-MAX_SHELL_TASK_DETAILS).map(parseShellTaskDetails).filter((entry) => entry !== undefined);
2784
2801
  }
2802
+ const turnInFlight = parseTurnInFlightMark(record.turnInFlight);
2803
+ if (turnInFlight)
2804
+ preferences.turnInFlight = turnInFlight;
2785
2805
  return preferences;
2786
2806
  }
2787
2807
  function parseSteerJournalEntry(value) {
@@ -5685,6 +5705,11 @@ function parseMcpToolName(toolName, knownServerNames) {
5685
5705
  if (!toolName.startsWith("mcp_")) {
5686
5706
  return { isMcpTool: false };
5687
5707
  }
5708
+ if (toolName.startsWith("mcp__")) {
5709
+ const separator = toolName.indexOf("__", 5);
5710
+ const server = separator > 5 ? toolName.slice(5, separator) : "";
5711
+ return server ? { isMcpTool: true, mcpServerName: server } : { isMcpTool: true };
5712
+ }
5688
5713
  const remainder = toolName.slice(4);
5689
5714
  if (knownServerNames && knownServerNames.size > 0) {
5690
5715
  let matchedServer;
@@ -6620,6 +6645,64 @@ function normalizePersistedSessionMessages(persisted) {
6620
6645
  return { messages, taskRegistry, backgroundTasks };
6621
6646
  }
6622
6647
 
6648
+ // ../../bridges/claude-bridge/src/services/session-manager-turn-interruption.ts
6649
+ import { randomUUID } from "node:crypto";
6650
+ var CLAUDE_RESTARTED_MID_TURN_MESSAGE = "Claude restarted before this turn finished, so its work stopped partway. Send a message to continue.";
6651
+ var BRIDGE_INSTANCE_ID = randomUUID();
6652
+ function claudeProcessKilledMessage(signal) {
6653
+ return `Claude Code was stopped (${signal}) before this turn finished, so its work stopped partway. Send a message to continue.`;
6654
+ }
6655
+ function killedClaudeProcessSignal(error) {
6656
+ if (!error || typeof error !== "object")
6657
+ return;
6658
+ const record = error;
6659
+ if (record.errorClass !== "process_killed_by_signal")
6660
+ return;
6661
+ return typeof record.signal === "string" && /^SIG[A-Z0-9]{1,8}$/.test(record.signal) ? record.signal : "a signal";
6662
+ }
6663
+ function turnInFlight(session) {
6664
+ return session.status === "running" || (session.retainedContinuationRequestIds?.size ?? 0) > 0 || session.status === "error" && session.interruption === "restart";
6665
+ }
6666
+ function syncTurnInFlightMark(session, options = {}) {
6667
+ if (session.deleting)
6668
+ return;
6669
+ writeTurnInFlightMark(session, options.stopped !== true && turnInFlight(session), options.started);
6670
+ }
6671
+ function clearTurnInFlightMarkForClose(session) {
6672
+ writeTurnInFlightMark(session, false);
6673
+ }
6674
+ function writeTurnInFlightMark(session, wanted, claim = false) {
6675
+ if (wanted === (session.turnInFlightMarked === true) && !(wanted && claim && !session.turnInFlightMarkOwned))
6676
+ return;
6677
+ const sdkSessionId = session.sdkSessionId ?? sdkSessionIdFromBridgeId(session.id);
6678
+ if (!sdkSessionId)
6679
+ return;
6680
+ session.turnInFlightMarked = wanted;
6681
+ session.turnInFlightMarkOwned = wanted;
6682
+ const update = {
6683
+ turnInFlight: wanted ? { bridgeInstanceId: BRIDGE_INSTANCE_ID, pid: process.pid, startedAt: Date.now() } : undefined
6684
+ };
6685
+ updateSessionPreferences(sdkSessionId, update).catch((error) => {
6686
+ debugLog("[session-manager] Failed to record the turn-in-flight mark:", error instanceof Error ? error.message : String(error));
6687
+ });
6688
+ }
6689
+ function restoredTurnInterruption(preferences) {
6690
+ const mark = preferences?.turnInFlight;
6691
+ if (!mark)
6692
+ return { status: "idle" };
6693
+ if (mark.bridgeInstanceId === BRIDGE_INSTANCE_ID) {
6694
+ return { status: "idle", turnInFlightMarked: true, turnInFlightMarkOwned: true };
6695
+ }
6696
+ if (mark.pid !== process.pid && processIsAlive(mark.pid))
6697
+ return { status: "idle" };
6698
+ return {
6699
+ status: "error",
6700
+ error: CLAUDE_RESTARTED_MID_TURN_MESSAGE,
6701
+ interruption: "restart",
6702
+ turnInFlightMarked: true
6703
+ };
6704
+ }
6705
+
6623
6706
  // ../../bridges/claude-bridge/src/services/session-manager-lifecycle.ts
6624
6707
  async function getSessionActivity(sessionId) {
6625
6708
  const session = sessions.get(sessionId);
@@ -6879,6 +6962,7 @@ function abortSession(sessionId) {
6879
6962
  session.activity = undefined;
6880
6963
  session.thinkingTokens = undefined;
6881
6964
  releaseQueryControl(session);
6965
+ syncTurnInFlightMark(session, { stopped: true });
6882
6966
  cleanupPendingInteractions(sessionId);
6883
6967
  appendInterruptedNotice(session, sessionId);
6884
6968
  eventEmitter.emit({
@@ -7386,7 +7470,7 @@ function noMessages() {
7386
7470
  // ../../bridges/claude-bridge/src/services/session-manager-persistence.ts
7387
7471
  import { constants } from "node:fs";
7388
7472
  import { lstat as lstat2, open as open2, realpath } from "node:fs/promises";
7389
- import { isAbsolute as isAbsolute3, join as join6, relative as relative2, resolve as resolve3, sep } from "node:path";
7473
+ import { isAbsolute as isAbsolute3, join as join8, relative as relative2, resolve as resolve3, sep } from "node:path";
7390
7474
 
7391
7475
  // ../protocol/src/node/command-change-journal.ts
7392
7476
  import { appendFile, mkdir as mkdir2, readFile as readFile4, rename as rename2, rm as rm2, stat as stat3, writeFile as writeFile2 } from "node:fs/promises";
@@ -7539,7 +7623,7 @@ async function readJournal(path, maxEntries) {
7539
7623
 
7540
7624
  // ../protocol/src/node/workspace-change-probe.ts
7541
7625
  import { execFile as execFile3, spawn as spawn2 } from "node:child_process";
7542
- import { randomUUID } from "node:crypto";
7626
+ import { randomUUID as randomUUID2 } from "node:crypto";
7543
7627
  import { rmSync } from "node:fs";
7544
7628
  import { copyFile, lstat, mkdtemp, rm as rm3 } from "node:fs/promises";
7545
7629
  import { tmpdir } from "node:os";
@@ -7662,11 +7746,31 @@ class WorkspaceChangeProbe {
7662
7746
  await this.open(cwd, callId, "note");
7663
7747
  }
7664
7748
  async end(callId) {
7749
+ const observation = await this.endObservation(callId);
7750
+ const change = observation?.change;
7751
+ if (!change)
7752
+ return;
7753
+ if (observation.overlap === "known-overlap" || observation.boundary.before === "late-start" || observation.omittedReasons.length > 0) {
7754
+ change.approximate = true;
7755
+ }
7756
+ return change;
7757
+ }
7758
+ async endObservation(callId) {
7665
7759
  await this.opening.get(callId);
7666
7760
  const call = this.calls.get(callId);
7667
7761
  if (!call)
7668
7762
  return;
7669
7763
  this.calls.delete(callId);
7764
+ const boundary = {
7765
+ before: call.mode === "fresh" ? "fresh-before" : call.late ? "late-start" : "prior-observation",
7766
+ after: "after-terminal"
7767
+ };
7768
+ const unavailable = (reason) => ({
7769
+ boundary: { before: boundary.before, after: "unknown" },
7770
+ overlap: call.overlapped ? "known-overlap" : "none-observed",
7771
+ omittedReasons: [],
7772
+ unavailableReason: reason
7773
+ });
7670
7774
  try {
7671
7775
  if (!call.measure || !call.before) {
7672
7776
  if (call.repo.keepsBaseline && !call.repo.disabled) {
@@ -7675,20 +7779,26 @@ class WorkspaceChangeProbe {
7675
7779
  return;
7676
7780
  }
7677
7781
  const before = await call.before;
7678
- if (!before || call.repo.disabled)
7679
- return;
7782
+ if (!before)
7783
+ return unavailable("before-unavailable");
7784
+ if (call.repo.disabled)
7785
+ return unavailable("probe-disabled");
7680
7786
  const after = await this.snapshotShared(call.repo, "fresh");
7681
- if (!after || call.unavailable)
7682
- return;
7787
+ if (!after)
7788
+ return unavailable("after-unavailable");
7789
+ if (call.unavailable)
7790
+ return unavailable("baseline-priming");
7683
7791
  const change = await this.diff(call.repo, before, after).catch(() => {
7684
7792
  return;
7685
7793
  });
7686
7794
  if (!change)
7687
- return;
7688
- if (call.overlapped || call.late || skippedFilesChanged(before.skipped, after.skipped)) {
7689
- change.approximate = true;
7690
- }
7691
- return change;
7795
+ return unavailable("diff-failed");
7796
+ return {
7797
+ change,
7798
+ boundary,
7799
+ overlap: call.overlapped ? "known-overlap" : "none-observed",
7800
+ omittedReasons: skippedFilesChanged(before.skipped, after.skipped) ? ["untracked-file-too-large"] : []
7801
+ };
7692
7802
  } finally {
7693
7803
  call.repo.open.delete(call);
7694
7804
  await this.releaseUnusedObjects(call.repo);
@@ -7732,6 +7842,7 @@ class WorkspaceChangeProbe {
7732
7842
  const call = {
7733
7843
  id: callId,
7734
7844
  repo,
7845
+ mode,
7735
7846
  measure,
7736
7847
  openedAt: this.now(),
7737
7848
  overlapped: false,
@@ -7842,7 +7953,7 @@ class WorkspaceChangeProbe {
7842
7953
  return;
7843
7954
  const { root, indexPath } = repo.location;
7844
7955
  const started = this.now();
7845
- const tempIndex = join4(this.tempDir, `orkestrator-probe-${randomUUID()}.index`);
7956
+ const tempIndex = join4(this.tempDir, `orkestrator-probe-${randomUUID2()}.index`);
7846
7957
  const objectDirectory = await mkdtemp(join4(this.tempDir, "orkestrator-probe-objects-"));
7847
7958
  temporaryObjectDirectories.add(objectDirectory);
7848
7959
  let retained = false;
@@ -7943,70 +8054,1486 @@ class WorkspaceChangeProbe {
7943
8054
  return change;
7944
8055
  }
7945
8056
  }
7946
- function parseNameStatusZ(output) {
7947
- const tokens = output.split("\x00");
7948
- const statuses = new Map;
7949
- for (let index = 0;index < tokens.length - 1; ) {
7950
- const status = tokens[index++]?.[0];
7951
- if (status === "R" || status === "C") {
7952
- index += 1;
7953
- const path = tokens[index++];
7954
- if (path)
7955
- statuses.set(path, "R");
7956
- } else {
7957
- const path = tokens[index++];
7958
- if (path && (status === "A" || status === "D" || status === "M"))
7959
- statuses.set(path, status);
8057
+ function parseNameStatusZ(output) {
8058
+ const tokens = output.split("\x00");
8059
+ const statuses = new Map;
8060
+ for (let index = 0;index < tokens.length - 1; ) {
8061
+ const status = tokens[index++]?.[0];
8062
+ if (status === "R" || status === "C") {
8063
+ index += 1;
8064
+ const path = tokens[index++];
8065
+ if (path)
8066
+ statuses.set(path, "R");
8067
+ } else {
8068
+ const path = tokens[index++];
8069
+ if (path && (status === "A" || status === "D" || status === "M"))
8070
+ statuses.set(path, status);
8071
+ }
8072
+ }
8073
+ return statuses;
8074
+ }
8075
+ function splitNul(value) {
8076
+ return value.split("\x00").filter((entry) => entry.length > 0);
8077
+ }
8078
+ function skippedFilesChanged(before, after) {
8079
+ if (before.size !== after.size)
8080
+ return true;
8081
+ for (const [path, stamp] of before) {
8082
+ if (after.get(path) !== stamp)
8083
+ return true;
8084
+ }
8085
+ return false;
8086
+ }
8087
+ function parseNumstatZ(output) {
8088
+ const tokens = output.split("\x00");
8089
+ const files = [];
8090
+ for (let index = 0;index < tokens.length; index += 1) {
8091
+ const header = tokens[index];
8092
+ if (!header)
8093
+ continue;
8094
+ const match = /^(-|\d+)\t(-|\d+)\t(.*)$/s.exec(header);
8095
+ if (!match)
8096
+ continue;
8097
+ const [, rawAdditions, rawDeletions, inlinePath] = match;
8098
+ let path = inlinePath ?? "";
8099
+ let previousPath;
8100
+ if (path === "") {
8101
+ previousPath = tokens[index + 1];
8102
+ path = tokens[index + 2] ?? "";
8103
+ index += 2;
8104
+ }
8105
+ if (!path)
8106
+ continue;
8107
+ const binary = rawAdditions === "-" || rawDeletions === "-";
8108
+ files.push({
8109
+ path,
8110
+ additions: binary ? 0 : Number(rawAdditions),
8111
+ deletions: binary ? 0 : Number(rawDeletions),
8112
+ ...binary ? { binary: true } : {},
8113
+ ...previousPath ? { previousPath } : {}
8114
+ });
8115
+ }
8116
+ return files;
8117
+ }
8118
+
8119
+ // ../../bridges/claude-bridge/src/services/command-changes.ts
8120
+ import { join as join7 } from "node:path";
8121
+
8122
+ // ../../bridges/claude-bridge/src/services/change-evidence.ts
8123
+ import { createHash as createHash7 } from "node:crypto";
8124
+ import {
8125
+ closeSync,
8126
+ existsSync,
8127
+ mkdirSync,
8128
+ openSync,
8129
+ readFileSync as readFileSync2,
8130
+ rmSync as rmSync2,
8131
+ writeSync
8132
+ } from "node:fs";
8133
+ import { join as join6 } from "node:path";
8134
+
8135
+ // ../protocol/src/field-validation.ts
8136
+ function isPlainRecord(value) {
8137
+ if (typeof value !== "object" || value === null || Array.isArray(value))
8138
+ return false;
8139
+ const proto = Object.getPrototypeOf(value);
8140
+ return proto === Object.prototype || proto === null;
8141
+ }
8142
+
8143
+ // ../protocol/src/change-evidence.ts
8144
+ var PROVIDER_EVIDENCE_SCHEMA_VERSION = 1;
8145
+ var TOOL_KINDS2 = [
8146
+ "shell",
8147
+ "file-edit",
8148
+ "file-write",
8149
+ "file-delete",
8150
+ "file-move",
8151
+ "notebook-edit",
8152
+ "read",
8153
+ "other"
8154
+ ];
8155
+ var TOOL_PHASES = [
8156
+ "proposed",
8157
+ "awaiting-approval",
8158
+ "running",
8159
+ "succeeded",
8160
+ "failed",
8161
+ "denied",
8162
+ "cancelled",
8163
+ "unknown"
8164
+ ];
8165
+ var TERMINAL_TOOL_PHASES = new Set([
8166
+ "succeeded",
8167
+ "failed",
8168
+ "denied",
8169
+ "cancelled"
8170
+ ]);
8171
+ var EXECUTION_OCCURRENCE_VALUES = [
8172
+ "not-started",
8173
+ "started",
8174
+ "did-not-run",
8175
+ "unknown"
8176
+ ];
8177
+ var CANONICAL_FRAME_METADATA_MAX_BYTES = 256 * 1024;
8178
+ var MAX_EVIDENCE_UPDATES_PER_FRAME = 256;
8179
+ var MAX_EVIDENCE_ID_LENGTH = 512;
8180
+ var MAX_EVIDENCE_PATH_LENGTH = 4096;
8181
+ var MAX_GAP_FRAME_IDS = 1024;
8182
+ function oneOf(values, value) {
8183
+ return typeof value === "string" && values.includes(value);
8184
+ }
8185
+ function isId(value) {
8186
+ return typeof value === "string" && value.length > 0 && value.length <= MAX_EVIDENCE_ID_LENGTH;
8187
+ }
8188
+ function isPathValue(value) {
8189
+ return typeof value === "string" && value.length > 0 && value.length <= MAX_EVIDENCE_PATH_LENGTH && !value.includes("\x00");
8190
+ }
8191
+ function isCount2(value) {
8192
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
8193
+ }
8194
+ function isUnsignedDecimal(value) {
8195
+ return typeof value === "string" && /^(0|[1-9][0-9]{0,39})$/.test(value);
8196
+ }
8197
+ function compareUnsignedDecimal(left, right) {
8198
+ if (left.length !== right.length)
8199
+ return left.length < right.length ? -1 : 1;
8200
+ return left < right ? -1 : left > right ? 1 : 0;
8201
+ }
8202
+ function bodyRefIssues(value, path) {
8203
+ if (!isPlainRecord(value))
8204
+ return [{ path, message: "expected a body reference" }];
8205
+ const issues = [];
8206
+ if (!isId(value.id))
8207
+ issues.push({ path: `${path}.id`, message: "expected an opaque id" });
8208
+ if (!oneOf(["pending", "available", "unavailable"], value.state)) {
8209
+ issues.push({ path: `${path}.state`, message: "unknown body state" });
8210
+ }
8211
+ if (!isCount2(value.retainedBytes)) {
8212
+ issues.push({ path: `${path}.retainedBytes`, message: "expected a byte count" });
8213
+ }
8214
+ if (value.originalBytes !== undefined && !isCount2(value.originalBytes)) {
8215
+ issues.push({ path: `${path}.originalBytes`, message: "expected a byte count" });
8216
+ }
8217
+ if (typeof value.complete !== "boolean") {
8218
+ issues.push({ path: `${path}.complete`, message: "expected a boolean" });
8219
+ }
8220
+ if (value.state === "unavailable" && typeof value.unavailableReason !== "string") {
8221
+ issues.push({ path: `${path}.unavailableReason`, message: "unavailable needs a reason" });
8222
+ }
8223
+ return issues;
8224
+ }
8225
+ function sideIssues(value, path) {
8226
+ if (!isPlainRecord(value))
8227
+ return [{ path, message: "expected a file side" }];
8228
+ if (value.state === "absent")
8229
+ return [];
8230
+ if (value.state === "unknown") {
8231
+ return typeof value.reason === "string" && value.reason.length > 0 ? [] : [{ path: `${path}.reason`, message: "unknown sides need a reason" }];
8232
+ }
8233
+ if (value.state === "present")
8234
+ return bodyRefIssues(value.body, `${path}.body`);
8235
+ return [{ path: `${path}.state`, message: "unknown side state" }];
8236
+ }
8237
+ var CLAIMS = ["proposal", "provider-reported", "unknown"];
8238
+ function evidenceIssues(value, path) {
8239
+ if (!isPlainRecord(value))
8240
+ return [{ path, message: "expected evidence" }];
8241
+ const issues = [];
8242
+ if (!oneOf(CLAIMS, value.effectClaim)) {
8243
+ issues.push({ path: `${path}.effectClaim`, message: "unknown effect claim" });
8244
+ }
8245
+ const optionalPath = (key) => {
8246
+ if (value[key] !== undefined && !isPathValue(value[key])) {
8247
+ issues.push({ path: `${path}.${key}`, message: "invalid path" });
8248
+ }
8249
+ };
8250
+ switch (value.kind) {
8251
+ case "patch":
8252
+ optionalPath("path");
8253
+ optionalPath("previousPath");
8254
+ issues.push(...bodyRefIssues(value.body, `${path}.body`));
8255
+ if (!oneOf(["git_unified", "opaque"], value.format)) {
8256
+ issues.push({ path: `${path}.format`, message: "unknown patch format" });
8257
+ }
8258
+ break;
8259
+ case "replacement":
8260
+ if (!isPathValue(value.path))
8261
+ issues.push({ path: `${path}.path`, message: "invalid path" });
8262
+ issues.push(...sideIssues(value.before, `${path}.before`));
8263
+ issues.push(...sideIssues(value.after, `${path}.after`));
8264
+ if (!oneOf(["fragment", "whole-file", "notebook-cell", "unknown"], value.extent)) {
8265
+ issues.push({ path: `${path}.extent`, message: "unknown extent" });
8266
+ }
8267
+ if (!oneOf(["input", "result"], value.origin)) {
8268
+ issues.push({ path: `${path}.origin`, message: "unknown origin" });
8269
+ }
8270
+ break;
8271
+ case "reported-counts":
8272
+ optionalPath("path");
8273
+ for (const key of ["additions", "deletions"]) {
8274
+ if (value[key] !== null && !isCount2(value[key])) {
8275
+ issues.push({ path: `${path}.${key}`, message: "expected a count or null" });
8276
+ }
8277
+ }
8278
+ if (value.contentLines !== undefined && value.contentLines !== null && !isCount2(value.contentLines)) {
8279
+ issues.push({ path: `${path}.contentLines`, message: "expected a count or null" });
8280
+ }
8281
+ if (!oneOf(["file", "tool", "turn"], value.scope)) {
8282
+ issues.push({ path: `${path}.scope`, message: "unknown count scope" });
8283
+ }
8284
+ break;
8285
+ case "path-operation":
8286
+ if (!isPathValue(value.path))
8287
+ issues.push({ path: `${path}.path`, message: "invalid path" });
8288
+ optionalPath("previousPath");
8289
+ if (!oneOf(["add", "modify", "delete", "rename", "unknown"], value.operation)) {
8290
+ issues.push({ path: `${path}.operation`, message: "unknown operation" });
8291
+ }
8292
+ break;
8293
+ default:
8294
+ issues.push({ path: `${path}.kind`, message: "unknown evidence kind" });
8295
+ }
8296
+ return issues;
8297
+ }
8298
+ function updateIssues(value, path) {
8299
+ if (!isPlainRecord(value))
8300
+ return [{ path, message: "expected an evidence update" }];
8301
+ const issues = [];
8302
+ if (!isId(value.evidenceKey))
8303
+ issues.push({ path: `${path}.evidenceKey`, message: "required" });
8304
+ if (!isId(value.revisionKey))
8305
+ issues.push({ path: `${path}.revisionKey`, message: "required" });
8306
+ if (value.kind === "replace") {
8307
+ if (value.replacesRevisionKey !== undefined && !isId(value.replacesRevisionKey)) {
8308
+ issues.push({ path: `${path}.replacesRevisionKey`, message: "invalid" });
8309
+ }
8310
+ issues.push(...evidenceIssues(value.evidence, `${path}.evidence`));
8311
+ } else if (value.kind === "append") {
8312
+ if (!isId(value.targetBodyId))
8313
+ issues.push({ path: `${path}.targetBodyId`, message: "id" });
8314
+ if (!isId(value.chunkId))
8315
+ issues.push({ path: `${path}.chunkId`, message: "id" });
8316
+ if (!isCount2(value.chunkIndex))
8317
+ issues.push({ path: `${path}.chunkIndex`, message: "count" });
8318
+ if (!isCount2(value.offsetBytes))
8319
+ issues.push({ path: `${path}.offsetBytes`, message: "count" });
8320
+ if (typeof value.final !== "boolean")
8321
+ issues.push({ path: `${path}.final`, message: "bool" });
8322
+ issues.push(...bodyRefIssues(value.chunk, `${path}.chunk`));
8323
+ } else {
8324
+ issues.push({ path: `${path}.kind`, message: "expected replace or append" });
8325
+ }
8326
+ return issues;
8327
+ }
8328
+ function updatesIssues(value, path) {
8329
+ if (!Array.isArray(value))
8330
+ return [{ path, message: "expected an array" }];
8331
+ if (value.length > MAX_EVIDENCE_UPDATES_PER_FRAME) {
8332
+ return [{ path, message: `at most ${MAX_EVIDENCE_UPDATES_PER_FRAME} updates per frame` }];
8333
+ }
8334
+ return value.flatMap((update, index) => updateIssues(update, `${path}[${index}]`));
8335
+ }
8336
+ function gapCoverageIssues(value, path) {
8337
+ if (!isPlainRecord(value))
8338
+ return [{ path, message: "expected gap coverage" }];
8339
+ switch (value.kind) {
8340
+ case "frames":
8341
+ return Array.isArray(value.frameIds) && value.frameIds.length <= MAX_GAP_FRAME_IDS && value.frameIds.every(isId) ? [] : [{ path: `${path}.frameIds`, message: "expected bounded frame ids" }];
8342
+ case "source-sequence":
8343
+ return isId(value.scopeId) && isId(value.epoch) && isUnsignedDecimal(value.fromInclusive) && isUnsignedDecimal(value.toInclusive) && compareUnsignedDecimal(value.fromInclusive, value.toInclusive) <= 0 ? [] : [{ path, message: "invalid sequence range" }];
8344
+ case "unknown-range":
8345
+ return isId(value.bindingId) && isId(value.runtimeEpoch) ? [] : [{ path, message: "invalid unknown range" }];
8346
+ default:
8347
+ return [{ path: `${path}.kind`, message: "unknown coverage kind" }];
8348
+ }
8349
+ }
8350
+ function eventIssues(value, path) {
8351
+ if (!isPlainRecord(value))
8352
+ return [{ path, message: "expected an event" }];
8353
+ const issues = [];
8354
+ switch (value.kind) {
8355
+ case "tool":
8356
+ if (!oneOf(TOOL_KINDS2, value.toolKind))
8357
+ issues.push({ path, message: "unknown tool kind" });
8358
+ if (!oneOf(TOOL_PHASES, value.phase))
8359
+ issues.push({ path, message: "unknown phase" });
8360
+ if (!oneOf(EXECUTION_OCCURRENCE_VALUES, value.executionOccurrence)) {
8361
+ issues.push({ path, message: "unknown execution occurrence" });
8362
+ }
8363
+ if (value.backgroundTaskId !== undefined && !isId(value.backgroundTaskId)) {
8364
+ issues.push({ path: `${path}.backgroundTaskId`, message: "invalid id" });
8365
+ }
8366
+ issues.push(...updatesIssues(value.evidence, `${path}.evidence`));
8367
+ break;
8368
+ case "turn":
8369
+ if (!oneOf(["proposed", "running", "succeeded", "failed", "cancelled", "unknown"], value.phase)) {
8370
+ issues.push({ path, message: "unknown turn phase" });
8371
+ }
8372
+ if (!oneOf(["may-write", "quiescent", "unknown"], value.writerLiveness)) {
8373
+ issues.push({ path, message: "unknown writer liveness" });
8374
+ }
8375
+ break;
8376
+ case "background-task":
8377
+ if (!isId(value.taskId))
8378
+ issues.push({ path: `${path}.taskId`, message: "required" });
8379
+ if (!oneOf(["running", "succeeded", "failed", "cancelled", "unknown"], value.phase)) {
8380
+ issues.push({ path, message: "unknown task phase" });
8381
+ }
8382
+ if (!oneOf(EXECUTION_OCCURRENCE_VALUES, value.executionOccurrence)) {
8383
+ issues.push({ path, message: "unknown execution occurrence" });
8384
+ }
8385
+ if (!oneOf(["may-write", "quiescent", "unknown"], value.writerLiveness)) {
8386
+ issues.push({ path, message: "unknown writer liveness" });
8387
+ }
8388
+ issues.push(...updatesIssues(value.evidence, `${path}.evidence`));
8389
+ break;
8390
+ case "turn-diff":
8391
+ if (!oneOf(["provider-reported", "unknown"], value.endpointMeaning)) {
8392
+ issues.push({ path, message: "unknown endpoint meaning" });
8393
+ }
8394
+ issues.push(...updatesIssues(value.evidence, `${path}.evidence`));
8395
+ break;
8396
+ case "stream-gap":
8397
+ if (!isId(value.gapId))
8398
+ issues.push({ path: `${path}.gapId`, message: "required" });
8399
+ if (typeof value.reason !== "string" || value.reason.length === 0) {
8400
+ issues.push({ path: `${path}.reason`, message: "required" });
8401
+ }
8402
+ issues.push(...gapCoverageIssues(value.coverage, `${path}.coverage`));
8403
+ break;
8404
+ default:
8405
+ issues.push({ path: `${path}.kind`, message: "unknown event kind" });
8406
+ }
8407
+ return issues;
8408
+ }
8409
+ function providerEvidenceFrameIssues(value) {
8410
+ if (!isPlainRecord(value))
8411
+ return [{ path: "$", message: "expected an object" }];
8412
+ const issues = [];
8413
+ if (value.schemaVersion !== PROVIDER_EVIDENCE_SCHEMA_VERSION) {
8414
+ issues.push({ path: "schemaVersion", message: "unsupported" });
8415
+ }
8416
+ for (const key of ["bindingId", "runtimeEpoch", "capabilityRevision", "frameId"]) {
8417
+ if (!isId(value[key]))
8418
+ issues.push({ path: key, message: "required id" });
8419
+ }
8420
+ if (value.sourceSequence !== undefined) {
8421
+ const seq = value.sourceSequence;
8422
+ if (!isPlainRecord(seq) || !isId(seq.scopeId) || !isId(seq.epoch) || !isUnsignedDecimal(seq.value)) {
8423
+ issues.push({ path: "sourceSequence", message: "invalid source sequence" });
8424
+ }
8425
+ }
8426
+ if (value.sourceCursor !== undefined) {
8427
+ const cursor = value.sourceCursor;
8428
+ if (!isPlainRecord(cursor) || !isId(cursor.scopeId) || !isId(cursor.value)) {
8429
+ issues.push({ path: "sourceCursor", message: "invalid source cursor" });
8430
+ }
8431
+ }
8432
+ const source = value.source;
8433
+ if (!isPlainRecord(source))
8434
+ issues.push({ path: "source", message: "required" });
8435
+ else {
8436
+ if (!isId(source.conversationId)) {
8437
+ issues.push({ path: "source.conversationId", message: "required" });
8438
+ }
8439
+ for (const key of [
8440
+ "turnId",
8441
+ "callId",
8442
+ "parentCallId",
8443
+ "parentConversationId",
8444
+ "subagentId",
8445
+ "itemId",
8446
+ "attemptId",
8447
+ "nativeRevision"
8448
+ ]) {
8449
+ if (source[key] !== undefined && !isId(source[key])) {
8450
+ issues.push({ path: `source.${key}`, message: "invalid id" });
8451
+ }
8452
+ }
8453
+ if (!oneOf(["live", "history", "reconciliation"], source.mode)) {
8454
+ issues.push({ path: "source.mode", message: "unknown mode" });
8455
+ }
8456
+ }
8457
+ issues.push(...eventIssues(value.event, "event"));
8458
+ if (value.raw !== undefined)
8459
+ issues.push(...bodyRefIssues(value.raw, "raw"));
8460
+ if (issues.length > 0)
8461
+ return issues;
8462
+ const event = value.event;
8463
+ const src = source;
8464
+ if (event.kind === "tool" && !src.callId && !src.itemId) {
8465
+ issues.push({ path: "source.callId", message: "tool frames need a native call identity" });
8466
+ }
8467
+ if ((event.kind === "turn" || event.kind === "turn-diff") && !src.turnId) {
8468
+ issues.push({ path: "source.turnId", message: "turn frames need a turn identity" });
8469
+ }
8470
+ if (JSON.stringify(value).length > CANONICAL_FRAME_METADATA_MAX_BYTES) {
8471
+ issues.push({ path: "$", message: "frame metadata exceeds its byte budget" });
8472
+ }
8473
+ return issues;
8474
+ }
8475
+ function isProviderEvidenceFrame(value) {
8476
+ return providerEvidenceFrameIssues(value).length === 0;
8477
+ }
8478
+ var MAX_OBSERVATION_FILES = 200;
8479
+ function workspaceObservationReportIssues(value) {
8480
+ if (!isPlainRecord(value))
8481
+ return [{ path: "$", message: "expected an object" }];
8482
+ const issues = [];
8483
+ if (value.schemaVersion !== PROVIDER_EVIDENCE_SCHEMA_VERSION) {
8484
+ issues.push({ path: "schemaVersion", message: "unsupported" });
8485
+ }
8486
+ for (const key of ["reportId", "bindingId", "runtimeEpoch", "capabilityRevision"]) {
8487
+ if (!isId(value[key]))
8488
+ issues.push({ path: key, message: "required id" });
8489
+ }
8490
+ const source = value.source;
8491
+ if (!isPlainRecord(source) || !isId(source.conversationId)) {
8492
+ issues.push({ path: "source", message: "conversation identity required" });
8493
+ }
8494
+ if (!oneOf(["host-worker", "container-worker"], value.executor)) {
8495
+ issues.push({ path: "executor", message: "unknown executor" });
8496
+ }
8497
+ const boundary = value.boundary;
8498
+ if (!isPlainRecord(boundary) || !oneOf(["fresh-before", "prior-observation", "late-start", "unknown"], boundary.before) || !oneOf(["after-terminal", "running-observation", "later-recovery", "unknown"], boundary.after)) {
8499
+ issues.push({ path: "boundary", message: "invalid boundary" });
8500
+ }
8501
+ if (!oneOf(["known-overlap", "none-observed", "unknown"], value.overlap)) {
8502
+ issues.push({ path: "overlap", message: "unknown overlap" });
8503
+ }
8504
+ if (!oneOf(["stable-observation", "changed-during-capture", "unknown"], value.consistency)) {
8505
+ issues.push({ path: "consistency", message: "unknown consistency" });
8506
+ }
8507
+ const outcome = value.outcome;
8508
+ if (!isPlainRecord(outcome))
8509
+ issues.push({ path: "outcome", message: "required" });
8510
+ else if (outcome.kind === "unavailable") {
8511
+ if (typeof outcome.reason !== "string" || !outcome.reason) {
8512
+ issues.push({ path: "outcome.reason", message: "required" });
8513
+ }
8514
+ } else if (outcome.kind === "measured") {
8515
+ if (!isCount2(outcome.additions) || !isCount2(outcome.deletions) || !isCount2(outcome.changedFiles)) {
8516
+ issues.push({ path: "outcome", message: "invalid totals" });
8517
+ }
8518
+ if (!Array.isArray(outcome.files) || outcome.files.length > MAX_OBSERVATION_FILES || !outcome.files.every((file) => isPlainRecord(file) && isPathValue(file.path) && (file.previousPath === undefined || isPathValue(file.previousPath)) && oneOf(["A", "D", "M", "R", "T", "unknown"], file.status) && (file.additions === null || isCount2(file.additions)) && (file.deletions === null || isCount2(file.deletions)) && typeof file.binary === "boolean")) {
8519
+ issues.push({ path: "outcome.files", message: "invalid files" });
8520
+ }
8521
+ if (typeof outcome.filesTruncated !== "boolean") {
8522
+ issues.push({ path: "outcome.filesTruncated", message: "required" });
8523
+ }
8524
+ if (!Array.isArray(outcome.omittedReasons) || !outcome.omittedReasons.every((reason) => typeof reason === "string")) {
8525
+ issues.push({ path: "outcome.omittedReasons", message: "required" });
8526
+ }
8527
+ } else {
8528
+ issues.push({ path: "outcome.kind", message: "unknown outcome" });
8529
+ }
8530
+ if (typeof value.observedAt !== "string")
8531
+ issues.push({ path: "observedAt", message: "required" });
8532
+ return issues;
8533
+ }
8534
+
8535
+ // ../protocol/src/node/change-tracking/evidence-spool.ts
8536
+ import { createHash as createHash6, randomUUID as randomUUID3 } from "node:crypto";
8537
+ import { readdirSync, readFileSync } from "node:fs";
8538
+ import { appendFile as appendFile2, mkdir as mkdir3, rename as rename3, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
8539
+ import { join as join5 } from "node:path";
8540
+ var DEFAULT_EVIDENCE_SPOOL_LIMITS = {
8541
+ maxEntries: 2048,
8542
+ maxEntryBytes: 256 * 1024,
8543
+ maxQueuedBytes: 16 * 1024 * 1024,
8544
+ maxBodyBytes: 64 * 1024 * 1024,
8545
+ maxSingleBodyBytes: 8 * 1024 * 1024,
8546
+ maxReadEntries: 256,
8547
+ maxReadBytes: 4 * 1024 * 1024
8548
+ };
8549
+ var ENTRIES_FILE = "entries.jsonl";
8550
+ var BODY_DIRECTORY = "bodies";
8551
+ function evidenceBodyId(bytes) {
8552
+ return `sha256:${createHash6("sha256").update(bytes).digest("hex")}`;
8553
+ }
8554
+ function stableEvidenceId(...parts) {
8555
+ const hash = createHash6("sha256");
8556
+ for (const part of parts)
8557
+ hash.update(`${part ?? ""}\x00`);
8558
+ return hash.digest("hex").slice(0, 40);
8559
+ }
8560
+
8561
+ class ChangeEvidenceSpool {
8562
+ runtimeEpoch;
8563
+ limits;
8564
+ directory;
8565
+ entries = [];
8566
+ bodies = new Map;
8567
+ capabilities = new Map;
8568
+ seenFrameIds = new Set;
8569
+ nextSeq = 1;
8570
+ queuedBytes = 0;
8571
+ bodyBytes = 0;
8572
+ pendingBodies = new Set;
8573
+ droppedFrameIds = [];
8574
+ droppedUnknown = false;
8575
+ gapBindingId = "";
8576
+ io = Promise.resolve();
8577
+ pendingLines = [];
8578
+ flushScheduled = false;
8579
+ restoring = false;
8580
+ constructor(options = {}) {
8581
+ this.limits = { ...DEFAULT_EVIDENCE_SPOOL_LIMITS, ...options.limits };
8582
+ this.runtimeEpoch = options.runtimeEpoch ?? randomUUID3();
8583
+ this.directory = options.directory;
8584
+ if (this.directory) {
8585
+ this.restoring = true;
8586
+ try {
8587
+ this.restore(this.directory);
8588
+ } finally {
8589
+ this.restoring = false;
8590
+ }
8591
+ this.compactPersisted();
8592
+ }
8593
+ }
8594
+ declareCapabilities(capabilities) {
8595
+ this.capabilities.set(capabilities.capabilityRevision, capabilities);
8596
+ }
8597
+ retainBody(content) {
8598
+ const original = typeof content === "string" ? new TextEncoder().encode(content) : content;
8599
+ const complete = original.length <= this.limits.maxSingleBodyBytes;
8600
+ const bytes = complete ? original : original.subarray(0, this.limits.maxSingleBodyBytes);
8601
+ const id = evidenceBodyId(bytes);
8602
+ if (!this.bodies.has(id) && this.bodyBytes + bytes.length > this.limits.maxBodyBytes) {
8603
+ return {
8604
+ id: `unretained:${stableEvidenceId(evidenceBodyId(original))}`,
8605
+ state: "unavailable",
8606
+ retainedBytes: 0,
8607
+ originalBytes: original.length,
8608
+ complete: false,
8609
+ unavailableReason: "bridge-spool-body-capacity"
8610
+ };
8611
+ }
8612
+ if (!this.bodies.has(id)) {
8613
+ this.bodies.set(id, { bytes, refs: 0 });
8614
+ this.pendingBodies.add(id);
8615
+ this.bodyBytes += bytes.length;
8616
+ this.persistBody(id, bytes);
8617
+ }
8618
+ return {
8619
+ id,
8620
+ state: "available",
8621
+ retainedBytes: bytes.length,
8622
+ ...complete ? {} : { originalBytes: original.length },
8623
+ complete
8624
+ };
8625
+ }
8626
+ appendFrame(frame) {
8627
+ if (this.seenFrameIds.has(frame.frameId)) {
8628
+ this.releasePendingBodies();
8629
+ return true;
8630
+ }
8631
+ this.gapBindingId ||= frame.bindingId;
8632
+ const frameId = frame.frameId;
8633
+ if (!isProviderEvidenceFrame(frame)) {
8634
+ this.recordDrop(frameId);
8635
+ this.releasePendingBodies();
8636
+ return false;
8637
+ }
8638
+ this.seenFrameIds.add(frame.frameId);
8639
+ if (this.seenFrameIds.size > this.limits.maxEntries * 4) {
8640
+ const oldest = this.seenFrameIds.values().next().value;
8641
+ if (oldest !== undefined)
8642
+ this.seenFrameIds.delete(oldest);
8643
+ }
8644
+ this.gapBindingId ||= frame.bindingId;
8645
+ const accepted = this.push({ seq: 0, type: "frame", frame }, collectBodyIds(frame), frame.frameId);
8646
+ this.releasePendingBodies();
8647
+ return accepted;
8648
+ }
8649
+ appendObservation(report) {
8650
+ if (workspaceObservationReportIssues(report).length > 0)
8651
+ return false;
8652
+ this.gapBindingId ||= report.bindingId;
8653
+ return this.push({ seq: 0, type: "observation", report }, [], report.reportId);
8654
+ }
8655
+ push(entry, bodyIds, identity) {
8656
+ this.flushGap();
8657
+ const serialized = JSON.stringify(entry);
8658
+ const bytes = serialized.length;
8659
+ if (bytes > this.limits.maxEntryBytes || this.entries.length >= this.limits.maxEntries - 1 || this.queuedBytes + bytes > this.limits.maxQueuedBytes) {
8660
+ this.recordDrop(identity);
8661
+ return false;
8662
+ }
8663
+ entry.seq = this.nextSeq++;
8664
+ for (const id of bodyIds) {
8665
+ const body = this.bodies.get(id);
8666
+ if (body)
8667
+ body.refs++;
8668
+ }
8669
+ this.entries.push({ entry, bytes, bodyIds });
8670
+ this.queuedBytes += bytes;
8671
+ this.persistEntry(entry);
8672
+ return true;
8673
+ }
8674
+ recordDrop(identity) {
8675
+ if (this.droppedFrameIds.length < 1000)
8676
+ this.droppedFrameIds.push(identity);
8677
+ else
8678
+ this.droppedUnknown = true;
8679
+ }
8680
+ flushGap() {
8681
+ if (this.droppedFrameIds.length === 0 && !this.droppedUnknown)
8682
+ return;
8683
+ if (this.entries.length >= this.limits.maxEntries - 1)
8684
+ return;
8685
+ const gapId = randomUUID3();
8686
+ const coverage = this.droppedUnknown ? {
8687
+ kind: "unknown-range",
8688
+ bindingId: this.gapBindingId || "unknown",
8689
+ runtimeEpoch: this.runtimeEpoch
8690
+ } : { kind: "frames", frameIds: this.droppedFrameIds };
8691
+ const gap = {
8692
+ schemaVersion: PROVIDER_EVIDENCE_SCHEMA_VERSION,
8693
+ bindingId: this.gapBindingId || "unknown",
8694
+ runtimeEpoch: this.runtimeEpoch,
8695
+ capabilityRevision: "spool",
8696
+ frameId: `gap:${gapId}`,
8697
+ source: { conversationId: "spool", mode: "live" },
8698
+ event: { kind: "stream-gap", gapId, coverage, reason: "bridge-spool-capacity" }
8699
+ };
8700
+ this.droppedFrameIds = [];
8701
+ this.droppedUnknown = false;
8702
+ const entry = { seq: this.nextSeq++, type: "frame", frame: gap };
8703
+ const bytes = JSON.stringify(entry).length;
8704
+ this.entries.push({ entry, bytes, bodyIds: [] });
8705
+ this.queuedBytes += bytes;
8706
+ this.persistEntry(entry);
8707
+ }
8708
+ read(cursor, maxEntries = this.limits.maxReadEntries) {
8709
+ this.flushGap();
8710
+ const limit = Math.max(1, Math.min(maxEntries, this.limits.maxReadEntries));
8711
+ const entries = [];
8712
+ let bytes = 0;
8713
+ let nextCursor = cursor;
8714
+ for (const stored of this.entries) {
8715
+ if (stored.entry.seq <= cursor)
8716
+ continue;
8717
+ if (entries.length >= limit)
8718
+ break;
8719
+ if (entries.length > 0 && bytes + stored.bytes > this.limits.maxReadBytes)
8720
+ break;
8721
+ entries.push(stored.entry);
8722
+ bytes += stored.bytes;
8723
+ nextCursor = stored.entry.seq;
8724
+ }
8725
+ return {
8726
+ schemaVersion: PROVIDER_EVIDENCE_SCHEMA_VERSION,
8727
+ runtimeEpoch: this.runtimeEpoch,
8728
+ entries,
8729
+ nextCursor,
8730
+ pendingGap: this.droppedFrameIds.length > 0 || this.droppedUnknown,
8731
+ capabilities: [...this.capabilities.values()]
8732
+ };
8733
+ }
8734
+ readBody(id) {
8735
+ return this.bodies.get(id)?.bytes ?? null;
8736
+ }
8737
+ acknowledge(request) {
8738
+ if (request.runtimeEpoch !== this.runtimeEpoch)
8739
+ return { released: 0 };
8740
+ let released = 0;
8741
+ while (this.entries.length > 0 && this.entries[0].entry.seq <= request.upTo) {
8742
+ const stored = this.entries.shift();
8743
+ this.queuedBytes -= stored.bytes;
8744
+ released++;
8745
+ for (const id of stored.bodyIds) {
8746
+ const body = this.bodies.get(id);
8747
+ if (body)
8748
+ body.refs--;
8749
+ }
8750
+ }
8751
+ this.releaseUnreferencedBodies([...request.retainedBodyIds, ...request.unavailableBodyIds]);
8752
+ if (released > 0)
8753
+ this.compactPersisted();
8754
+ return { released };
8755
+ }
8756
+ releasePendingBodies() {
8757
+ this.releaseUnreferencedBodies(this.pendingBodies);
8758
+ this.pendingBodies.clear();
8759
+ }
8760
+ releaseUnreferencedBodies(ids) {
8761
+ for (const id of Array.from(ids)) {
8762
+ const body = this.bodies.get(id);
8763
+ if (!body || body.refs > 0)
8764
+ continue;
8765
+ this.bodies.delete(id);
8766
+ this.bodyBytes -= body.bytes.length;
8767
+ this.removeBody(id);
8768
+ }
8769
+ }
8770
+ stats() {
8771
+ return {
8772
+ entries: this.entries.length,
8773
+ queuedBytes: this.queuedBytes,
8774
+ bodies: this.bodies.size,
8775
+ bodyBytes: this.bodyBytes
8776
+ };
8777
+ }
8778
+ enqueueIo(operation) {
8779
+ this.io = this.io.then(operation).catch(() => {});
8780
+ }
8781
+ persistEntry(entry) {
8782
+ if (!this.directory || this.restoring)
8783
+ return;
8784
+ this.pendingLines.push(`${JSON.stringify({ epoch: this.runtimeEpoch, entry })}
8785
+ `);
8786
+ if (this.flushScheduled)
8787
+ return;
8788
+ this.flushScheduled = true;
8789
+ const timer = setTimeout(() => {
8790
+ this.flushScheduled = false;
8791
+ this.flushLines();
8792
+ }, 0);
8793
+ timer.unref?.();
8794
+ }
8795
+ flushLines() {
8796
+ const directory = this.directory;
8797
+ if (!directory || this.pendingLines.length === 0)
8798
+ return;
8799
+ const lines = this.pendingLines.splice(0).join("");
8800
+ this.enqueueIo(async () => {
8801
+ await mkdir3(directory, { recursive: true, mode: 448 });
8802
+ await appendFile2(join5(directory, ENTRIES_FILE), lines, { mode: 384 });
8803
+ });
8804
+ }
8805
+ persistBody(id, bytes) {
8806
+ const directory = this.directory;
8807
+ if (!directory || this.restoring)
8808
+ return;
8809
+ this.enqueueIo(async () => {
8810
+ const bodies = join5(directory, BODY_DIRECTORY);
8811
+ await mkdir3(bodies, { recursive: true, mode: 448 });
8812
+ await writeFile3(join5(bodies, bodyFileName(id)), bytes, { mode: 384, flag: "wx" }).catch((error) => {
8813
+ if (error.code !== "EEXIST")
8814
+ throw error;
8815
+ });
8816
+ });
8817
+ }
8818
+ removeBody(id) {
8819
+ const directory = this.directory;
8820
+ if (!directory)
8821
+ return;
8822
+ this.enqueueIo(() => rm4(join5(directory, BODY_DIRECTORY, bodyFileName(id)), { force: true }));
8823
+ }
8824
+ compactPersisted() {
8825
+ const directory = this.directory;
8826
+ if (!directory)
8827
+ return;
8828
+ this.pendingLines.length = 0;
8829
+ const body = this.entries.map((stored) => JSON.stringify({ epoch: this.runtimeEpoch, entry: stored.entry })).join(`
8830
+ `);
8831
+ this.enqueueIo(async () => {
8832
+ await mkdir3(directory, { recursive: true, mode: 448 });
8833
+ const file = join5(directory, ENTRIES_FILE);
8834
+ const temp = `${file}.${process.pid}.tmp`;
8835
+ await writeFile3(temp, body ? `${body}
8836
+ ` : "", { mode: 384 });
8837
+ await rename3(temp, file);
8838
+ });
8839
+ }
8840
+ async flush() {
8841
+ this.flushLines();
8842
+ await this.io;
8843
+ }
8844
+ restore(directory) {
8845
+ const bodyDirectory = join5(directory, BODY_DIRECTORY);
8846
+ try {
8847
+ for (const name of readdirSync(bodyDirectory)) {
8848
+ const bytes = new Uint8Array(readFileSync(join5(bodyDirectory, name)));
8849
+ const id = evidenceBodyId(bytes);
8850
+ if (bodyFileName(id) !== name)
8851
+ continue;
8852
+ if (this.bodyBytes + bytes.length > this.limits.maxBodyBytes)
8853
+ break;
8854
+ this.bodies.set(id, { bytes, refs: 0 });
8855
+ this.bodyBytes += bytes.length;
8856
+ }
8857
+ } catch {}
8858
+ let text = "";
8859
+ try {
8860
+ text = readFileSync(join5(directory, ENTRIES_FILE), "utf8");
8861
+ } catch {
8862
+ return;
8863
+ }
8864
+ for (const line of text.split(`
8865
+ `)) {
8866
+ if (!line)
8867
+ continue;
8868
+ let parsed;
8869
+ try {
8870
+ parsed = JSON.parse(line);
8871
+ } catch {
8872
+ continue;
8873
+ }
8874
+ const entry = parsed.entry;
8875
+ if (!entry)
8876
+ continue;
8877
+ if (entry.type === "frame" && isProviderEvidenceFrame(entry.frame)) {
8878
+ if (this.seenFrameIds.has(entry.frame.frameId))
8879
+ continue;
8880
+ this.seenFrameIds.add(entry.frame.frameId);
8881
+ this.push({ ...entry, seq: 0 }, collectBodyIds(entry.frame), entry.frame.frameId);
8882
+ } else if (entry.type === "observation" && workspaceObservationReportIssues(entry.report).length === 0) {
8883
+ this.push({ ...entry, seq: 0 }, [], entry.report.reportId);
8884
+ }
8885
+ }
8886
+ for (const [id, body] of Array.from(this.bodies)) {
8887
+ if (body.refs > 0)
8888
+ continue;
8889
+ this.bodies.delete(id);
8890
+ this.bodyBytes -= body.bytes.length;
8891
+ this.removeBody(id);
8892
+ }
8893
+ }
8894
+ }
8895
+ function bodyFileName(id) {
8896
+ return id.replace(/[^A-Za-z0-9]/g, "_");
8897
+ }
8898
+ function collectBodyIds(frame) {
8899
+ const ids = new Set;
8900
+ const add = (ref) => {
8901
+ if (ref && ref.state === "available")
8902
+ ids.add(ref.id);
8903
+ };
8904
+ add(frame.raw);
8905
+ const event = frame.event;
8906
+ const updates = event.kind === "tool" || event.kind === "background-task" || event.kind === "turn-diff" ? event.evidence : [];
8907
+ for (const update of updates) {
8908
+ if (update.kind === "append") {
8909
+ add(update.chunk);
8910
+ continue;
8911
+ }
8912
+ const evidence = update.evidence;
8913
+ if (evidence.kind === "patch")
8914
+ add(evidence.body);
8915
+ if (evidence.kind === "replacement") {
8916
+ if (evidence.before.state === "present")
8917
+ add(evidence.before.body);
8918
+ if (evidence.after.state === "present")
8919
+ add(evidence.after.body);
8920
+ }
8921
+ }
8922
+ return [...ids];
8923
+ }
8924
+
8925
+ // ../protocol/src/node/change-tracking/evidence-builders.ts
8926
+ var MAX_KEY = 200;
8927
+ function clampId(value) {
8928
+ return value.length <= MAX_KEY ? value : `${value.slice(0, 120)}~${stableEvidenceId(value)}`;
8929
+ }
8930
+ var MAX_TRACKED_REVISIONS = 4096;
8931
+
8932
+ class ChangeEvidenceRecorder {
8933
+ capabilityRevision;
8934
+ lastRevisions = new Map;
8935
+ capabilities;
8936
+ spool;
8937
+ provider;
8938
+ executor;
8939
+ constructor(options) {
8940
+ this.spool = options.spool;
8941
+ this.provider = options.provider;
8942
+ this.executor = options.executor ?? "host-worker";
8943
+ this.capabilityRevision = `cap:${stableEvidenceId(options.provider, JSON.stringify(options.capabilities))}`;
8944
+ this.capabilities = { ...options.capabilities, capabilityRevision: this.capabilityRevision };
8945
+ this.spool.declareCapabilities(this.capabilities);
8946
+ }
8947
+ bindingFor(conversationId) {
8948
+ return clampId(`${this.provider}:${conversationId}`);
8949
+ }
8950
+ source(input) {
8951
+ const source = {
8952
+ conversationId: clampId(input.conversationId),
8953
+ mode: input.mode ?? "live"
8954
+ };
8955
+ for (const key of [
8956
+ "turnId",
8957
+ "callId",
8958
+ "parentCallId",
8959
+ "parentConversationId",
8960
+ "subagentId",
8961
+ "itemId",
8962
+ "attemptId",
8963
+ "nativeRevision"
8964
+ ]) {
8965
+ const value = input[key];
8966
+ if (value)
8967
+ source[key] = clampId(value);
8968
+ }
8969
+ return source;
8970
+ }
8971
+ side(value, reason) {
8972
+ if (value === null)
8973
+ return { state: "absent" };
8974
+ if (value === undefined)
8975
+ return { state: "unknown", reason };
8976
+ return { state: "present", body: this.spool.retainBody(value) };
8977
+ }
8978
+ updates(inputs) {
8979
+ const counters = new Map;
8980
+ return inputs.map((input) => {
8981
+ const ordinal = counters.get(input.kind) ?? 0;
8982
+ counters.set(input.kind, ordinal + 1);
8983
+ const evidenceKey = clampId(input.key ?? `${input.kind}:${ordinal}`);
8984
+ const evidence = this.evidence(input);
8985
+ return {
8986
+ kind: "replace",
8987
+ evidenceKey,
8988
+ revisionKey: `rev:${stableEvidenceId(evidenceKey, canonicalEvidence(evidence))}`,
8989
+ evidence
8990
+ };
8991
+ });
8992
+ }
8993
+ evidence(input) {
8994
+ switch (input.kind) {
8995
+ case "patch":
8996
+ return {
8997
+ kind: "patch",
8998
+ ...input.path ? { path: input.path } : {},
8999
+ ...input.previousPath ? { previousPath: input.previousPath } : {},
9000
+ body: this.spool.retainBody(input.text),
9001
+ format: input.format ?? "git_unified",
9002
+ effectClaim: input.claim
9003
+ };
9004
+ case "replacement":
9005
+ return {
9006
+ kind: "replacement",
9007
+ path: input.path,
9008
+ before: this.side(input.before, input.unknownReason ?? "before-not-supplied"),
9009
+ after: this.side(input.after, input.unknownReason ?? "after-not-supplied"),
9010
+ extent: input.extent,
9011
+ origin: input.origin,
9012
+ effectClaim: input.claim
9013
+ };
9014
+ case "reported-counts":
9015
+ return {
9016
+ kind: "reported-counts",
9017
+ ...input.path ? { path: input.path } : {},
9018
+ additions: input.additions,
9019
+ deletions: input.deletions,
9020
+ ...input.contentLines !== undefined ? { contentLines: input.contentLines } : {},
9021
+ scope: input.scope,
9022
+ effectClaim: input.claim
9023
+ };
9024
+ case "path-operation":
9025
+ return {
9026
+ kind: "path-operation",
9027
+ path: input.path,
9028
+ ...input.previousPath ? { previousPath: input.previousPath } : {},
9029
+ operation: input.operation,
9030
+ effectClaim: input.claim
9031
+ };
9032
+ }
9033
+ }
9034
+ frame(source, event, identity) {
9035
+ const bindingId = this.bindingFor(source.conversationId);
9036
+ return {
9037
+ schemaVersion: PROVIDER_EVIDENCE_SCHEMA_VERSION,
9038
+ bindingId,
9039
+ runtimeEpoch: this.spool.runtimeEpoch,
9040
+ capabilityRevision: this.capabilityRevision,
9041
+ frameId: `frame:${stableEvidenceId(bindingId, identity)}`,
9042
+ source: this.source(source),
9043
+ event
9044
+ };
9045
+ }
9046
+ tool(source, event) {
9047
+ const updates = this.supersede(`${this.bindingFor(source.conversationId)}\x00${source.callId}\x00${source.attemptId ?? ""}`, this.updates(event.evidence ?? []));
9048
+ const identity = [
9049
+ "tool",
9050
+ source.callId,
9051
+ source.attemptId,
9052
+ event.toolKind,
9053
+ event.phase,
9054
+ event.executionOccurrence,
9055
+ event.backgroundTaskId,
9056
+ ...updates.map((u) => u.revisionKey)
9057
+ ].join("\x00");
9058
+ return this.spool.appendFrame(this.frame(source, {
9059
+ kind: "tool",
9060
+ toolKind: event.toolKind,
9061
+ phase: event.phase,
9062
+ executionOccurrence: event.executionOccurrence,
9063
+ evidence: updates,
9064
+ ...event.backgroundTaskId ? { backgroundTaskId: clampId(event.backgroundTaskId) } : {}
9065
+ }, identity));
9066
+ }
9067
+ supersede(scope, updates) {
9068
+ return updates.map((update) => {
9069
+ if (update.kind !== "replace")
9070
+ return update;
9071
+ const key = `${scope}\x00${update.evidenceKey}`;
9072
+ const previous = this.lastRevisions.get(key);
9073
+ const content = update.revisionKey;
9074
+ const revisionKey = previous?.content === content ? previous.revision : previous ? `rev:${stableEvidenceId(scope, content, previous.revision)}` : content;
9075
+ this.lastRevisions.delete(key);
9076
+ this.lastRevisions.set(key, { content, revision: revisionKey });
9077
+ if (this.lastRevisions.size > MAX_TRACKED_REVISIONS) {
9078
+ const oldest = this.lastRevisions.keys().next().value;
9079
+ if (oldest !== undefined)
9080
+ this.lastRevisions.delete(oldest);
9081
+ }
9082
+ return {
9083
+ ...update,
9084
+ revisionKey,
9085
+ ...previous && previous.revision !== revisionKey ? { replacesRevisionKey: previous.revision } : {}
9086
+ };
9087
+ });
9088
+ }
9089
+ turn(source, phase, writerLiveness = "unknown") {
9090
+ return this.spool.appendFrame(this.frame(source, { kind: "turn", phase, writerLiveness }, ["turn", source.turnId, phase, writerLiveness].join("\x00")));
9091
+ }
9092
+ turnDiff(source, patch) {
9093
+ const updates = this.supersede(`${this.bindingFor(source.conversationId)}\x00turn\x00${source.turnId}`, this.updates([{ kind: "patch", key: "turn-diff", text: patch, claim: "provider-reported" }]));
9094
+ return this.spool.appendFrame(this.frame(source, { kind: "turn-diff", evidence: updates, endpointMeaning: "provider-reported" }, ["turn-diff", source.turnId, updates[0].revisionKey].join("\x00")));
9095
+ }
9096
+ backgroundTask(source, task) {
9097
+ return this.spool.appendFrame(this.frame(source, {
9098
+ kind: "background-task",
9099
+ taskId: clampId(task.taskId),
9100
+ ...task.parentCallId ? { parentCallId: clampId(task.parentCallId) } : {},
9101
+ phase: task.phase,
9102
+ executionOccurrence: task.executionOccurrence,
9103
+ writerLiveness: task.writerLiveness,
9104
+ evidence: []
9105
+ }, ["task", task.taskId, task.phase, task.writerLiveness].join("\x00")));
9106
+ }
9107
+ observation(source, observation) {
9108
+ const bindingId = this.bindingFor(source.conversationId);
9109
+ const report = {
9110
+ schemaVersion: PROVIDER_EVIDENCE_SCHEMA_VERSION,
9111
+ reportId: `obs:${stableEvidenceId(bindingId, source.callId, source.attemptId, observation.windowId)}`,
9112
+ bindingId,
9113
+ runtimeEpoch: this.spool.runtimeEpoch,
9114
+ capabilityRevision: this.capabilityRevision,
9115
+ source: {
9116
+ conversationId: clampId(source.conversationId),
9117
+ ...source.turnId ? { turnId: clampId(source.turnId) } : {},
9118
+ callId: clampId(source.callId),
9119
+ ...source.attemptId ? { attemptId: clampId(source.attemptId) } : {}
9120
+ },
9121
+ executor: this.executor,
9122
+ boundary: observation.boundary,
9123
+ overlap: observation.overlap,
9124
+ consistency: observation.consistency ?? "unknown",
9125
+ outcome: observation.change ? measuredOutcome(observation.change, observation.omittedReasons ?? []) : { kind: "unavailable", reason: observation.unavailableReason ?? "capture-unavailable" },
9126
+ observedAt: new Date().toISOString()
9127
+ };
9128
+ return this.spool.appendObservation(report);
9129
+ }
9130
+ }
9131
+ function measuredOutcome(change, omittedReasons) {
9132
+ const files = change.files.slice(0, 200).map((file) => ({
9133
+ path: file.path,
9134
+ ...file.previousPath ? { previousPath: file.previousPath } : {},
9135
+ status: file.status ?? "unknown",
9136
+ additions: file.binary ? null : file.additions,
9137
+ deletions: file.binary ? null : file.deletions,
9138
+ binary: file.binary === true
9139
+ }));
9140
+ return {
9141
+ kind: "measured",
9142
+ files,
9143
+ additions: change.additions,
9144
+ deletions: change.deletions,
9145
+ changedFiles: change.filesTruncated ? Math.max(files.length, change.files.length) : files.length,
9146
+ filesTruncated: change.filesTruncated === true,
9147
+ omittedReasons
9148
+ };
9149
+ }
9150
+ function bodyKey(ref) {
9151
+ return `${ref.id}:${ref.state}:${ref.complete}`;
9152
+ }
9153
+ function canonicalEvidence(evidence) {
9154
+ switch (evidence.kind) {
9155
+ case "patch":
9156
+ return [
9157
+ "patch",
9158
+ evidence.path,
9159
+ evidence.previousPath,
9160
+ evidence.format,
9161
+ evidence.effectClaim,
9162
+ bodyKey(evidence.body)
9163
+ ].join("\x00");
9164
+ case "replacement": {
9165
+ const side = (value) => value.state === "present" ? `p:${bodyKey(value.body)}` : value.state === "absent" ? "a" : `u:${value.reason}`;
9166
+ return [
9167
+ "replacement",
9168
+ evidence.path,
9169
+ side(evidence.before),
9170
+ side(evidence.after),
9171
+ evidence.extent,
9172
+ evidence.origin,
9173
+ evidence.effectClaim
9174
+ ].join("\x00");
9175
+ }
9176
+ case "reported-counts":
9177
+ return [
9178
+ "counts",
9179
+ evidence.path,
9180
+ evidence.additions,
9181
+ evidence.deletions,
9182
+ evidence.contentLines,
9183
+ evidence.scope,
9184
+ evidence.effectClaim
9185
+ ].join("\x00");
9186
+ case "path-operation":
9187
+ return [
9188
+ "path",
9189
+ evidence.path,
9190
+ evidence.previousPath,
9191
+ evidence.operation,
9192
+ evidence.effectClaim
9193
+ ].join("\x00");
9194
+ }
9195
+ }
9196
+
9197
+ // ../../bridges/claude-bridge/src/services/change-evidence.ts
9198
+ function detectCaptureExecutor(exists = existsSync) {
9199
+ return exists("/.dockerenv") || exists("/run/.containerenv") ? "container-worker" : "host-worker";
9200
+ }
9201
+ function claudeChangeCapabilities(executor) {
9202
+ return {
9203
+ resolution: "resolved",
9204
+ beforeTurn: "controlled-dispatch",
9205
+ afterTurn: "terminal-observed",
9206
+ beforeTool: "awaited",
9207
+ afterTool: "awaited",
9208
+ historicalCallIdentity: "stable",
9209
+ fileEvidence: { resolution: "confirmed", kinds: ["replacement"] },
9210
+ turnDiff: "absent",
9211
+ captureExecutor: executor,
9212
+ captureAvailability: "ready",
9213
+ backgroundCompletion: "launch-only"
9214
+ };
9215
+ }
9216
+ function claudeToolKind(toolName) {
9217
+ switch (toolName) {
9218
+ case "Bash":
9219
+ return "shell";
9220
+ case "Edit":
9221
+ case "MultiEdit":
9222
+ return "file-edit";
9223
+ case "Write":
9224
+ return "file-write";
9225
+ case "NotebookEdit":
9226
+ return "notebook-edit";
9227
+ case "Read":
9228
+ case "Grep":
9229
+ case "Glob":
9230
+ return "read";
9231
+ default:
9232
+ return "other";
9233
+ }
9234
+ }
9235
+ function record(value) {
9236
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
9237
+ }
9238
+ function text(value) {
9239
+ return typeof value === "string" ? value : undefined;
9240
+ }
9241
+ function path(value) {
9242
+ return typeof value === "string" && value.length > 0 ? value : undefined;
9243
+ }
9244
+ function claudeProposalEvidence(toolName, input) {
9245
+ const fields = record(input);
9246
+ switch (toolName) {
9247
+ case "Edit": {
9248
+ const filePath = path(fields.file_path);
9249
+ if (!filePath)
9250
+ return [];
9251
+ return [
9252
+ {
9253
+ kind: "replacement",
9254
+ key: "edit:0",
9255
+ path: filePath,
9256
+ before: text(fields.old_string),
9257
+ after: text(fields.new_string),
9258
+ unknownReason: "not-in-input",
9259
+ extent: "fragment",
9260
+ origin: "input",
9261
+ claim: "proposal"
9262
+ }
9263
+ ];
9264
+ }
9265
+ case "MultiEdit": {
9266
+ const filePath = path(fields.file_path);
9267
+ if (!filePath || !Array.isArray(fields.edits))
9268
+ return [];
9269
+ return fields.edits.map((edit, index) => {
9270
+ const entry = record(edit);
9271
+ return {
9272
+ kind: "replacement",
9273
+ key: `edit:${index}`,
9274
+ path: filePath,
9275
+ before: text(entry.old_string),
9276
+ after: text(entry.new_string),
9277
+ unknownReason: "not-in-input",
9278
+ extent: "fragment",
9279
+ origin: "input",
9280
+ claim: "proposal"
9281
+ };
9282
+ });
9283
+ }
9284
+ case "Write": {
9285
+ const filePath = path(fields.file_path);
9286
+ if (!filePath)
9287
+ return [];
9288
+ return [
9289
+ {
9290
+ kind: "replacement",
9291
+ key: "write",
9292
+ path: filePath,
9293
+ before: undefined,
9294
+ after: text(fields.content),
9295
+ unknownReason: "before-not-captured",
9296
+ extent: "whole-file",
9297
+ origin: "input",
9298
+ claim: "proposal"
9299
+ }
9300
+ ];
9301
+ }
9302
+ case "NotebookEdit": {
9303
+ const filePath = path(fields.notebook_path);
9304
+ if (!filePath)
9305
+ return [];
9306
+ const deleting = fields.edit_mode === "delete";
9307
+ return [
9308
+ {
9309
+ kind: "replacement",
9310
+ key: "cell",
9311
+ path: filePath,
9312
+ before: undefined,
9313
+ after: deleting ? null : text(fields.new_source),
9314
+ unknownReason: "before-not-captured",
9315
+ extent: "notebook-cell",
9316
+ origin: "input",
9317
+ claim: "proposal"
9318
+ }
9319
+ ];
9320
+ }
9321
+ default:
9322
+ return [];
9323
+ }
9324
+ }
9325
+ function claudeToolResultOutcomes(message) {
9326
+ const content = record(record(message).message).content;
9327
+ if (!Array.isArray(content))
9328
+ return [];
9329
+ return content.flatMap((block) => {
9330
+ const entry = record(block);
9331
+ return entry.type === "tool_result" && typeof entry.tool_use_id === "string" && entry.tool_use_id ? [{ callId: entry.tool_use_id, isError: entry.is_error === true }] : [];
9332
+ });
9333
+ }
9334
+ function claudeToolUses(message) {
9335
+ const content = record(record(message).message).content;
9336
+ if (!Array.isArray(content))
9337
+ return [];
9338
+ return content.flatMap((block) => {
9339
+ const entry = record(block);
9340
+ return entry.type === "tool_use" && typeof entry.id === "string" && entry.id && typeof entry.name === "string" ? [{ callId: entry.id, toolName: entry.name, input: entry.input }] : [];
9341
+ });
9342
+ }
9343
+ function legacyCommandChange(observation) {
9344
+ const change = observation?.change;
9345
+ if (!change)
9346
+ return;
9347
+ if (observation.overlap === "known-overlap" || observation.boundary.before === "late-start" || observation.omittedReasons.length > 0) {
9348
+ change.approximate = true;
9349
+ }
9350
+ return change;
9351
+ }
9352
+ var MAX_TRACKED_CALLS = 4096;
9353
+ function createChangeEvidence(options) {
9354
+ const executor = options.executor ?? detectCaptureExecutor();
9355
+ const spool = new ChangeEvidenceSpool(options.directory ? { directory: options.directory } : {});
9356
+ const recorder = new ChangeEvidenceRecorder({
9357
+ spool,
9358
+ provider: "claude",
9359
+ capabilities: claudeChangeCapabilities(executor),
9360
+ executor
9361
+ });
9362
+ return {
9363
+ spool,
9364
+ recorder,
9365
+ executor,
9366
+ calls: new Map,
9367
+ executed: new Map,
9368
+ activeTurns: new Map
9369
+ };
9370
+ }
9371
+ function claimSpoolDirectory() {
9372
+ const scope = process.env.CWD || process.cwd();
9373
+ const directory = join6(claudeChangeEvidenceDir(), createHash7("sha256").update(scope).digest("hex").slice(0, 32));
9374
+ const owner = join6(directory, "owner.pid");
9375
+ try {
9376
+ mkdirSync(directory, { recursive: true, mode: 448 });
9377
+ for (let attempt = 0;attempt < 2; attempt++) {
9378
+ try {
9379
+ const fd = openSync(owner, "wx", 384);
9380
+ writeSync(fd, String(process.pid));
9381
+ closeSync(fd);
9382
+ return directory;
9383
+ } catch (error) {
9384
+ if (error.code !== "EEXIST")
9385
+ return;
9386
+ const holder = Number.parseInt(readFileSync2(owner, "utf8"), 10);
9387
+ if (holder === process.pid)
9388
+ return directory;
9389
+ if (Number.isSafeInteger(holder) && holder > 0 && processIsAlive2(holder))
9390
+ return;
9391
+ rmSync2(owner, { force: true });
9392
+ }
7960
9393
  }
9394
+ } catch {}
9395
+ return;
9396
+ }
9397
+ function processIsAlive2(pid) {
9398
+ try {
9399
+ process.kill(pid, 0);
9400
+ return true;
9401
+ } catch (error) {
9402
+ return error.code === "EPERM";
7961
9403
  }
7962
- return statuses;
7963
9404
  }
7964
- function splitNul(value) {
7965
- return value.split("\x00").filter((entry) => entry.length > 0);
9405
+ var current;
9406
+ function claudeChangeEvidence() {
9407
+ current ??= createChangeEvidence({
9408
+ directory: claimSpoolDirectory()
9409
+ });
9410
+ return current;
7966
9411
  }
7967
- function skippedFilesChanged(before, after) {
7968
- if (before.size !== after.size)
7969
- return true;
7970
- for (const [path, stamp] of before) {
7971
- if (after.get(path) !== stamp)
7972
- return true;
9412
+ function remember(map, key, value) {
9413
+ map.delete(key);
9414
+ map.set(key, value);
9415
+ while (map.size > MAX_TRACKED_CALLS) {
9416
+ const oldest = map.keys().next().value;
9417
+ if (oldest === undefined)
9418
+ break;
9419
+ map.delete(oldest);
7973
9420
  }
7974
- return false;
7975
9421
  }
7976
- function parseNumstatZ(output) {
7977
- const tokens = output.split("\x00");
7978
- const files = [];
7979
- for (let index = 0;index < tokens.length; index += 1) {
7980
- const header = tokens[index];
7981
- if (!header)
7982
- continue;
7983
- const match = /^(-|\d+)\t(-|\d+)\t(.*)$/s.exec(header);
7984
- if (!match)
7985
- continue;
7986
- const [, rawAdditions, rawDeletions, inlinePath] = match;
7987
- let path = inlinePath ?? "";
7988
- let previousPath;
7989
- if (path === "") {
7990
- previousPath = tokens[index + 1];
7991
- path = tokens[index + 2] ?? "";
7992
- index += 2;
9422
+ function emit(state, callId, call, phase, executionOccurrence) {
9423
+ call.phase = phase;
9424
+ const backgroundTaskId = state.executed.get(callId)?.backgroundTaskId;
9425
+ state.recorder.tool({
9426
+ conversationId: call.conversationId,
9427
+ callId,
9428
+ ...call.turnId ? { turnId: call.turnId } : {},
9429
+ ...call.parentCallId ? { parentCallId: call.parentCallId } : {}
9430
+ }, {
9431
+ toolKind: call.toolKind,
9432
+ phase,
9433
+ executionOccurrence,
9434
+ evidence: call.evidence,
9435
+ ...backgroundTaskId && phase !== "running" ? { backgroundTaskId } : {}
9436
+ });
9437
+ }
9438
+ function ensureCall(state, conversationId, callId, native) {
9439
+ const existing = state.calls.get(callId);
9440
+ if (existing) {
9441
+ if (existing.evidence.length === 0 && native.toolName && native.input !== undefined) {
9442
+ existing.evidence = claudeProposalEvidence(native.toolName, native.input);
7993
9443
  }
7994
- if (!path)
7995
- continue;
7996
- const binary = rawAdditions === "-" || rawDeletions === "-";
7997
- files.push({
7998
- path,
7999
- additions: binary ? 0 : Number(rawAdditions),
8000
- deletions: binary ? 0 : Number(rawDeletions),
8001
- ...binary ? { binary: true } : {},
8002
- ...previousPath ? { previousPath } : {}
8003
- });
9444
+ if (existing.toolKind === "other" && native.toolName) {
9445
+ existing.toolKind = claudeToolKind(native.toolName);
9446
+ }
9447
+ existing.turnId ??= native.turnId;
9448
+ existing.parentCallId ??= native.parentCallId;
9449
+ return existing;
8004
9450
  }
8005
- return files;
9451
+ const turnId = native.turnId ?? state.activeTurns.get(conversationId);
9452
+ const call = {
9453
+ conversationId,
9454
+ toolKind: claudeToolKind(native.toolName),
9455
+ evidence: native.toolName && native.input !== undefined ? claudeProposalEvidence(native.toolName, native.input) : [],
9456
+ phase: "unknown",
9457
+ ...turnId ? { turnId } : {},
9458
+ ...native.parentCallId ? { parentCallId: native.parentCallId } : {}
9459
+ };
9460
+ remember(state.calls, callId, call);
9461
+ return call;
9462
+ }
9463
+ function recordClaudeTurn(conversationId, turnId, phase, writerLiveness = "unknown") {
9464
+ const state = claudeChangeEvidence();
9465
+ if (phase === "running")
9466
+ remember(state.activeTurns, conversationId, turnId);
9467
+ state.recorder.turn({ conversationId, turnId }, phase, writerLiveness);
9468
+ }
9469
+ function recordClaudeToolUse(conversationId, use) {
9470
+ const state = claudeChangeEvidence();
9471
+ const call = ensureCall(state, conversationId, use.callId, use);
9472
+ if (call.phase === "denied")
9473
+ emit(state, use.callId, call, "denied", "did-not-run");
9474
+ else if (call.phase === "unknown")
9475
+ emit(state, use.callId, call, "running", "unknown");
9476
+ }
9477
+ function recordClaudeToolAwaitingApproval(conversationId, callId, toolName, input) {
9478
+ if (!callId)
9479
+ return;
9480
+ const state = claudeChangeEvidence();
9481
+ const call = ensureCall(state, conversationId, callId, { toolName, input });
9482
+ if (TERMINAL_TOOL_PHASES.has(call.phase))
9483
+ return;
9484
+ emit(state, callId, call, "awaiting-approval", "not-started");
9485
+ }
9486
+ function recordClaudePermissionDecision(conversationId, callId, toolName, input, behavior) {
9487
+ if (!callId)
9488
+ return;
9489
+ const state = claudeChangeEvidence();
9490
+ if (behavior === "deny") {
9491
+ recordClaudeToolDenied(conversationId, callId, { toolName, input });
9492
+ return;
9493
+ }
9494
+ const call = state.calls.get(callId);
9495
+ if (call?.phase === "awaiting-approval")
9496
+ emit(state, callId, call, "running", "unknown");
9497
+ }
9498
+ function recordClaudeToolDenied(conversationId, callId, native = {}) {
9499
+ const state = claudeChangeEvidence();
9500
+ const call = ensureCall(state, conversationId, callId, native);
9501
+ if (TERMINAL_TOOL_PHASES.has(call.phase) && call.phase !== "denied")
9502
+ return;
9503
+ emit(state, callId, call, "denied", "did-not-run");
9504
+ }
9505
+ function recordClaudeToolExecuted(callId, backgroundTaskId) {
9506
+ remember(claudeChangeEvidence().executed, callId, backgroundTaskId ? { backgroundTaskId } : {});
9507
+ }
9508
+ function recordClaudeToolResult(callId, isError) {
9509
+ const state = claudeChangeEvidence();
9510
+ const call = state.calls.get(callId);
9511
+ if (!call || call.phase === "denied")
9512
+ return;
9513
+ const ran = state.executed.has(callId);
9514
+ emit(state, callId, call, isError ? "failed" : "succeeded", !isError || ran ? "started" : "unknown");
9515
+ }
9516
+ function recordClaudeObservation(conversationId, callId, observation) {
9517
+ const state = claudeChangeEvidence();
9518
+ const call = state.calls.get(callId);
9519
+ const turnId = call?.turnId ?? state.activeTurns.get(conversationId);
9520
+ state.recorder.observation({
9521
+ conversationId: call?.conversationId ?? conversationId,
9522
+ callId,
9523
+ ...turnId ? { turnId } : {}
9524
+ }, {
9525
+ boundary: observation.boundary,
9526
+ overlap: observation.overlap,
9527
+ ...observation.change ? { change: observation.change } : {},
9528
+ omittedReasons: observation.omittedReasons,
9529
+ ...observation.unavailableReason ? { unavailableReason: observation.unavailableReason } : {}
9530
+ });
9531
+ }
9532
+ function claudeEvidenceTurnId(turn) {
9533
+ return turn.dispatchRequestId || turn.promptUuid;
8006
9534
  }
8007
9535
 
8008
9536
  // ../../bridges/claude-bridge/src/services/command-changes.ts
8009
- import { join as join5 } from "node:path";
8010
9537
  var commandChangeProbe = new WorkspaceChangeProbe;
8011
9538
  var COMMAND_CHANGE_HOOK_MATCHER = "^(Bash|Edit|Write|MultiEdit|NotebookEdit)$";
8012
9539
  var COMMAND_CHANGE_HOOK_TIMEOUT_SECONDS = 60;
@@ -8020,7 +9547,7 @@ function commandChangeJournal(sdkSessionId) {
8020
9547
  if (journal) {
8021
9548
  journals.delete(sdkSessionId);
8022
9549
  } else {
8023
- journal = new CommandChangeJournal(join5(claudeCommandChangesDir(), `${sdkSessionId}.jsonl`));
9550
+ journal = new CommandChangeJournal(join7(claudeCommandChangesDir(), `${sdkSessionId}.jsonl`));
8024
9551
  }
8025
9552
  journals.set(sdkSessionId, journal);
8026
9553
  while (journals.size > MAX_CACHED_JOURNALS) {
@@ -8042,7 +9569,11 @@ function isMeasuredCommand(toolName, toolInput) {
8042
9569
  const input = toolInput && typeof toolInput === "object" ? toolInput : {};
8043
9570
  return input.run_in_background !== true;
8044
9571
  }
8045
- function commandChangeHooks(onMeasured) {
9572
+ function backgroundTaskIdOf(response) {
9573
+ const id = response?.backgroundTaskId;
9574
+ return typeof id === "string" && id.length > 0 ? id : undefined;
9575
+ }
9576
+ function commandChangeHooks(onMeasured, conversationId) {
8046
9577
  const before = async (input) => {
8047
9578
  if (input.hook_event_name !== "PreToolUse" || !input.tool_use_id)
8048
9579
  return {};
@@ -8059,11 +9590,16 @@ function commandChangeHooks(onMeasured) {
8059
9590
  }
8060
9591
  if (!input.tool_use_id)
8061
9592
  return {};
8062
- const change = await commandChangeProbe.end(input.tool_use_id);
9593
+ recordClaudeToolExecuted(input.tool_use_id, input.hook_event_name === "PostToolUse" ? backgroundTaskIdOf(input.tool_response) : undefined);
9594
+ const observation = await commandChangeProbe.endObservation(input.tool_use_id);
9595
+ const change = legacyCommandChange(observation);
8063
9596
  if (change && hasMeasuredChanges(change)) {
8064
9597
  commandChangeJournal(input.session_id)?.append(input.tool_use_id, change);
8065
9598
  onMeasured(input.tool_use_id, change);
8066
9599
  }
9600
+ if (observation && conversationId) {
9601
+ recordClaudeObservation(conversationId, input.tool_use_id, observation);
9602
+ }
8067
9603
  return {};
8068
9604
  };
8069
9605
  const matcher = (hook) => ({
@@ -8185,7 +9721,7 @@ async function reconcilePersistedSessions() {
8185
9721
  id,
8186
9722
  title: info.customTitle || info.summary || `Session ${info.sessionId.slice(-6)}`,
8187
9723
  messages: [],
8188
- status: "idle",
9724
+ ...restoredTurnInterruption(storedPreferences),
8189
9725
  createdAt: new Date(info.createdAt ?? info.lastModified),
8190
9726
  lastActivity: new Date(info.lastModified),
8191
9727
  sdkSessionId: info.sessionId,
@@ -8227,7 +9763,7 @@ async function materializePersistedSessionState(sessionId, sdkId) {
8227
9763
  id: sessionId,
8228
9764
  title: info.customTitle || info.summary || `Session ${sdkId.slice(-6)}`,
8229
9765
  messages: [],
8230
- status: "idle",
9766
+ ...restoredTurnInterruption(preferences),
8231
9767
  createdAt: new Date(info.createdAt ?? info.lastModified),
8232
9768
  lastActivity: new Date(info.lastModified),
8233
9769
  sdkSessionId: sdkId,
@@ -8761,7 +10297,7 @@ async function assertNoSymlinkComponents(lexicalRoot, targetPath) {
8761
10297
  const childPath = relative2(lexicalRoot, targetPath);
8762
10298
  let currentPath = lexicalRoot;
8763
10299
  for (const segment of childPath.split(sep).filter(Boolean)) {
8764
- currentPath = join6(currentPath, segment);
10300
+ currentPath = join8(currentPath, segment);
8765
10301
  const stats = await lstat2(currentPath).catch((error) => {
8766
10302
  throw attachmentErrorForFsFailure(error);
8767
10303
  });
@@ -9876,6 +11412,7 @@ function releaseCompletedTurnToBackgroundTasks(turn, backgroundTasksRunning) {
9876
11412
  if (turn.dispatchRequestId) {
9877
11413
  recordPromptDispatch(sessionId, turn.dispatchRequestId, "already-processed");
9878
11414
  }
11415
+ syncTurnInFlightMark(session);
9879
11416
  eventEmitter.emit({
9880
11417
  type: "session.idle",
9881
11418
  sessionId,
@@ -9896,6 +11433,7 @@ function stopWaitingForContinuation(turn) {
9896
11433
  session.retainedContinuationRequestIds = undefined;
9897
11434
  }
9898
11435
  }
11436
+ syncTurnInFlightMark(session);
9899
11437
  }
9900
11438
  function waitForContinuationAfterNotification(turn) {
9901
11439
  const { session } = turn;
@@ -9905,6 +11443,7 @@ function waitForContinuationAfterNotification(turn) {
9905
11443
  if (turn.dispatchRequestId) {
9906
11444
  (session.retainedContinuationRequestIds ??= new Set).add(turn.dispatchRequestId);
9907
11445
  }
11446
+ syncTurnInFlightMark(session);
9908
11447
  if (turn.runtime?.idleTimer)
9909
11448
  armClaudeRuntimeIdleDetach(turn.runtime);
9910
11449
  }
@@ -9996,6 +11535,7 @@ function captureTurnStartup(session) {
9996
11535
  turnStartedAt: session.turnStartedAt,
9997
11536
  abortController: session.abortController,
9998
11537
  error: session.error,
11538
+ interruption: session.interruption,
9999
11539
  lastActivity: session.lastActivity,
10000
11540
  persistedMessagesLoaded: session.persistedMessagesLoaded,
10001
11541
  structuredOutput: session.structuredOutput,
@@ -10164,6 +11704,7 @@ async function applyPromptPlanMode(admission, planMode) {
10164
11704
  session.status = beforeStartup.status;
10165
11705
  session.turnStartedAt = beforeStartup.turnStartedAt;
10166
11706
  session.error = beforeStartup.error;
11707
+ session.interruption = beforeStartup.interruption;
10167
11708
  session.lastActivity = beforeStartup.lastActivity;
10168
11709
  session.persistedMessagesLoaded = beforeStartup.persistedMessagesLoaded;
10169
11710
  if (needsTranscriptHydration)
@@ -10183,6 +11724,7 @@ async function applyPromptPlanMode(admission, planMode) {
10183
11724
  function beginPromptActivity(admission) {
10184
11725
  const { session, sessionId } = admission;
10185
11726
  session.error = undefined;
11727
+ session.interruption = undefined;
10186
11728
  session.lastActivity = new Date;
10187
11729
  if (session.promptSuggestion !== undefined) {
10188
11730
  session.promptSuggestion = undefined;
@@ -10284,11 +11826,84 @@ function createClaudeTurn(admission, input) {
10284
11826
  import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
10285
11827
 
10286
11828
  // ../../bridges/claude-bridge/src/services/mcp-config.ts
10287
- import { createHash as createHash6 } from "node:crypto";
10288
- import { join as join7 } from "node:path";
11829
+ import { createHash as createHash8 } from "node:crypto";
11830
+ import { join as join9 } from "node:path";
11831
+
11832
+ // ../protocol/src/agent-mail.ts
11833
+ var AGENT_MAIL_MAX_BODY_BYTES = 32 * 1024;
11834
+ var AGENT_MAIL_MAX_STORE_BYTES = 32 * 1024 * 1024;
11835
+ var AGENT_MAIL_DEFAULT_RETENTION_DAYS = 14;
11836
+ var AGENT_MAIL_DEFAULT_AUTONOMOUS_WAKE_BUDGET = 16;
11837
+ var AGENT_MAIL_DEFAULT_HOURLY_WAKE_LIMIT = 40;
11838
+ var AGENT_MAIL_TOOL_NAMES = Object.freeze([
11839
+ "list_mailboxes",
11840
+ "send_message",
11841
+ "check_inbox",
11842
+ "read_message",
11843
+ "ack_message",
11844
+ "reply_message",
11845
+ "get_message_status"
11846
+ ]);
11847
+ var DEFAULT_AGENT_MESSAGING_SETTINGS = Object.freeze({
11848
+ enabled: true,
11849
+ allowCrossProject: false,
11850
+ defaultInjectPolicy: "off",
11851
+ wakePeers: false,
11852
+ autonomousWakeBudget: AGENT_MAIL_DEFAULT_AUTONOMOUS_WAKE_BUDGET,
11853
+ hourlyWakeLimit: AGENT_MAIL_DEFAULT_HOURLY_WAKE_LIMIT,
11854
+ coordinatorStatusWakes: "settled",
11855
+ retentionDays: AGENT_MAIL_DEFAULT_RETENTION_DAYS,
11856
+ paused: false
11857
+ });
11858
+ function agentMailCapabilities(tabType, agent, locked = true) {
11859
+ if (tabType === "agent-native") {
11860
+ if (!locked || !agent)
11861
+ return { canPull: false, canSend: false, canInject: false };
11862
+ if (agent === "claude" || agent === "codex" || agent === "opencode" || agent === "pi" || agent === "cursor" || agent === "grok") {
11863
+ return { canPull: true, canSend: true, canInject: true };
11864
+ }
11865
+ return { canPull: false, canSend: false, canInject: false };
11866
+ }
11867
+ if (tabType === "claude-tmux")
11868
+ return { canPull: true, canSend: true, canInject: true };
11869
+ if (["claude", "codex", "opencode"].includes(tabType)) {
11870
+ return { canPull: true, canSend: true, canInject: false };
11871
+ }
11872
+ if (["cursor", "grok", "pi"].includes(tabType)) {
11873
+ return { canPull: false, canSend: false, canInject: false };
11874
+ }
11875
+ return { canPull: false, canSend: false, canInject: false };
11876
+ }
11877
+ var NATIVE_AGENT_MAIL_CAPABILITIES = Object.freeze(Object.fromEntries(AGENT_PLATFORMS.map((agent) => [agent, agentMailCapabilities("agent-native", agent)])));
11878
+
11879
+ // ../protocol/src/transcript-artifacts.ts
11880
+ var ARTIFACT_TOOL_NAMES = Object.freeze([
11881
+ "create_artifact",
11882
+ "update_artifact",
11883
+ "preview_artifact",
11884
+ "publish_artifact",
11885
+ "get_artifact",
11886
+ "get_artifact_operation",
11887
+ "cancel_artifact_operation"
11888
+ ]);
11889
+ var ORKESTRATOR_ARTIFACT_MCP_SERVER_NAME = "orkestrator-artifacts";
11890
+ var ORKESTRATOR_ARTIFACT_MCP_PATH = "/artifact-mcp";
11891
+
11892
+ // ../../bridges/claude-bridge/src/services/mcp-config.ts
10289
11893
  var AGENT_MCP_SERVER_NAME = "orkestrator";
10290
11894
  var AGENT_MCP_URL_ENV = "ORKESTRATOR_AGENT_MCP_URL";
10291
11895
  var AGENT_MCP_TOKEN_ENV = "ORKESTRATOR_AGENT_MCP_TOKEN";
11896
+ function getOrkestratorArtifactMcpServer(connection) {
11897
+ const token = connection?.artifacts?.token?.trim();
11898
+ const agentServer = getOrkestratorAgentMcpServerFromConnection(connection);
11899
+ if (!token || Buffer.byteLength(token, "utf8") > 1024 || !agentServer)
11900
+ return null;
11901
+ return {
11902
+ ...agentServer,
11903
+ url: new URL(ORKESTRATOR_ARTIFACT_MCP_PATH, agentServer.url).toString(),
11904
+ headers: { Authorization: `Bearer ${token}` }
11905
+ };
11906
+ }
10292
11907
  function getOrkestratorAgentMcpServerFromConnection(connection) {
10293
11908
  if (!connection?.url?.trim() || !connection.token?.trim())
10294
11909
  return null;
@@ -10321,7 +11936,7 @@ async function loadGlobalMcpServers() {
10321
11936
  return servers ?? {};
10322
11937
  }
10323
11938
  async function loadProjectMcpServers(cwd) {
10324
- const servers = await readJsonSliceCached(join7(cwd, ".mcp.json"), "mcpServers", (config) => config?.mcpServers);
11939
+ const servers = await readJsonSliceCached(join9(cwd, ".mcp.json"), "mcpServers", (config) => config?.mcpServers);
10325
11940
  return servers ?? {};
10326
11941
  }
10327
11942
  async function loadProjectOverridesFromGlobal2(cwd) {
@@ -10425,7 +12040,7 @@ async function loadScopedMcpServers(cwd, scope) {
10425
12040
  const [global2, projectGlobal, projectLocal] = await Promise.all([
10426
12041
  readJsonSliceCachedWithDigest(path, "mcpServers", (config) => config?.mcpServers),
10427
12042
  scope === "all" ? readJsonSliceCachedWithDigest(path, `projects:${cwd}:mcpServers`, (config) => config?.projects?.[cwd]?.mcpServers) : null,
10428
- scope === "all" ? readJsonSliceCachedWithDigest(join7(cwd, ".mcp.json"), "mcpServers", (config) => config?.mcpServers) : null
12043
+ scope === "all" ? readJsonSliceCachedWithDigest(join9(cwd, ".mcp.json"), "mcpServers", (config) => config?.mcpServers) : null
10429
12044
  ]);
10430
12045
  const configs = {
10431
12046
  ...global2.value,
@@ -10439,7 +12054,7 @@ async function loadScopedMcpServers(cwd, scope) {
10439
12054
  return { configs, revision: { fingerprint: revisionFingerprint(sources), sources, scope } };
10440
12055
  }
10441
12056
  function revisionFingerprint(sources) {
10442
- return createHash6("sha256").update(`user=${sources.user}\x00project=${sources.project}`).digest("base64url");
12057
+ return createHash8("sha256").update(`user=${sources.user}\x00project=${sources.project}`).digest("base64url");
10443
12058
  }
10444
12059
  async function getMcpRuntimeConfig(cwd, env = process.env, connection, sources = "all") {
10445
12060
  const { configs, revision } = await loadScopedMcpServers(cwd, normalizeScope(sources));
@@ -10453,14 +12068,20 @@ async function getMcpRuntimeConfig(cwd, env = process.env, connection, sources =
10453
12068
  url: new URL("/design-mcp", agentServer.url).toString()
10454
12069
  };
10455
12070
  }
12071
+ const artifactServer = getOrkestratorArtifactMcpServer(connection);
12072
+ if (artifactServer)
12073
+ servers[ORKESTRATOR_ARTIFACT_MCP_SERVER_NAME] = artifactServer;
12074
+ else
12075
+ delete servers[ORKESTRATOR_ARTIFACT_MCP_SERVER_NAME];
10456
12076
  const research = normalizeScope(sources) === "none" ? coordinatorResearchMcpServersFromEnv(env) : [];
10457
12077
  for (const server of research)
10458
12078
  servers[server.name] = { type: "http", url: server.url };
10459
12079
  return {
10460
12080
  servers,
10461
12081
  names: new Set([
10462
- ...Object.keys(configs),
12082
+ ...Object.keys(configs).filter((name) => name !== ORKESTRATOR_ARTIFACT_MCP_SERVER_NAME),
10463
12083
  ...agentServer ? [AGENT_MCP_SERVER_NAME, ...connection?.design ? ["orkestrator-design"] : []] : [],
12084
+ ...artifactServer ? [ORKESTRATOR_ARTIFACT_MCP_SERVER_NAME] : [],
10464
12085
  ...research.map((server) => server.name)
10465
12086
  ]),
10466
12087
  revision
@@ -10468,7 +12089,7 @@ async function getMcpRuntimeConfig(cwd, env = process.env, connection, sources =
10468
12089
  }
10469
12090
 
10470
12091
  // ../../bridges/claude-bridge/src/services/session-manager-commands.ts
10471
- import { randomUUID as randomUUID2 } from "node:crypto";
12092
+ import { randomUUID as randomUUID4 } from "node:crypto";
10472
12093
 
10473
12094
  // ../protocol/src/agent-command-catalogue.ts
10474
12095
  var NATIVE_AGENT_COMMAND_CATALOGUE_VERSION = 1;
@@ -10772,8 +12393,8 @@ function idleSteerPromptReply(prompt, agentLabel) {
10772
12393
  }
10773
12394
 
10774
12395
  // ../../bridges/claude-bridge/src/services/claude-terminal-commands.ts
10775
- import { mkdir as mkdir3, readFile as readFile5, rename as rename3, writeFile as writeFile3 } from "node:fs/promises";
10776
- import { join as join8 } from "node:path";
12396
+ import { mkdir as mkdir4, readFile as readFile5, rename as rename4, writeFile as writeFile4 } from "node:fs/promises";
12397
+ import { join as join10 } from "node:path";
10777
12398
  var FILE_NAME = "terminal-slash-commands.json";
10778
12399
  var MAX_VERSIONS = 4;
10779
12400
  var MAX_NAMES = 512;
@@ -10784,7 +12405,7 @@ var dirty = false;
10784
12405
  var entries = [];
10785
12406
  var writes = Promise.resolve();
10786
12407
  function filePath() {
10787
- return join8(claudeOrkestratorDir(), FILE_NAME);
12408
+ return join10(claudeOrkestratorDir(), FILE_NAME);
10788
12409
  }
10789
12410
  function validVersion(value) {
10790
12411
  return typeof value === "string" && value.length > 0 && value.length <= MAX_VERSION_LENGTH;
@@ -10839,22 +12460,22 @@ function recordTerminalSlashCommands(version, commands) {
10839
12460
  writes = writes.then(async () => {
10840
12461
  await load();
10841
12462
  const directory = claudeOrkestratorDir();
10842
- await mkdir3(directory, { recursive: true });
12463
+ await mkdir4(directory, { recursive: true });
10843
12464
  const target = filePath();
10844
12465
  const temporary = `${target}.${process.pid}.${Date.now()}.tmp`;
10845
12466
  const snapshot = entries;
10846
- await writeFile3(temporary, `${JSON.stringify({ versions: snapshot })}
12467
+ await writeFile4(temporary, `${JSON.stringify({ versions: snapshot })}
10847
12468
  `, {
10848
12469
  mode: 384
10849
12470
  });
10850
- await rename3(temporary, target);
12471
+ await rename4(temporary, target);
10851
12472
  if (entries === snapshot)
10852
12473
  dirty = false;
10853
12474
  }).catch(() => {});
10854
12475
  }
10855
12476
 
10856
12477
  // ../../bridges/claude-bridge/src/services/session-manager-catalog.ts
10857
- import { createHash as createHash7 } from "node:crypto";
12478
+ import { createHash as createHash9 } from "node:crypto";
10858
12479
 
10859
12480
  // ../../bridges/claude-bridge/src/services/claude-sign-in.ts
10860
12481
  var AUTH_CACHE_TTL_MS = 30000;
@@ -11221,7 +12842,7 @@ async function steerClaudeSession(sessionId, text, requestId, expectedRunId) {
11221
12842
  requestId
11222
12843
  };
11223
12844
  const session = sessions.get(sessionId);
11224
- const inputDigest = createHash7("sha256").update(text).digest("hex");
12845
+ const inputDigest = createHash9("sha256").update(text).digest("hex");
11225
12846
  const previous = session?.steerJournal?.get(requestId);
11226
12847
  if (previous) {
11227
12848
  if (previous.inputDigest !== inputDigest || previous.expectedRunId !== expectedRunId) {
@@ -11355,9 +12976,11 @@ async function configureClaudeSession(session, input) {
11355
12976
  }
11356
12977
  }
11357
12978
  async function gracefulInterruptClaudeSession(sessionId) {
11358
- const control = sessions.get(sessionId)?.queryControl;
11359
- if (!control?.interrupt)
12979
+ const session = sessions.get(sessionId);
12980
+ const control = session?.queryControl;
12981
+ if (!session || !control?.interrupt)
11360
12982
  return { interrupted: false, stillQueued: [] };
12983
+ session.interruptRequestedGeneration = session.latestTurnGeneration;
11361
12984
  try {
11362
12985
  const receipt = await control.interrupt();
11363
12986
  return { interrupted: true, stillQueued: receipt?.still_queued ?? [] };
@@ -11368,7 +12991,7 @@ async function gracefulInterruptClaudeSession(sessionId) {
11368
12991
  }
11369
12992
 
11370
12993
  // ../../bridges/claude-bridge/src/services/session-manager-commands.ts
11371
- var CATALOGUE_GENERATION = randomUUID2();
12994
+ var CATALOGUE_GENERATION = randomUUID4();
11372
12995
  var COMMAND_PROBE_TIMEOUT_MS = 30000;
11373
12996
  var MAX_LOCAL_COMMAND_OUTPUT_BYTES = 64 * 1024;
11374
12997
  var MAX_MESSAGE_BYTES = 512;
@@ -11980,61 +13603,14 @@ function createStaleMcpServerHook(mcp) {
11980
13603
  hookSpecificOutput: {
11981
13604
  hookEventName: "PreToolUse",
11982
13605
  permissionDecision: "deny",
11983
- permissionDecisionReason: `The MCP server "${server}" was removed from this session's configuration, so ${input.tool_name} is not available. Claude is still running background work in this session; the server unloads when that work finishes or the session is stopped.`
13606
+ permissionDecisionReason: `The MCP server "${server}" was removed from this session's configuration, so ${input.tool_name} is not available. This Claude process loaded it at launch and cannot unload it; it is gone once the process closes after an idle period, or when the session is stopped.`
11984
13607
  }
11985
13608
  };
11986
13609
  };
11987
13610
  }
11988
13611
 
11989
- // ../protocol/src/agent-mail.ts
11990
- var AGENT_MAIL_MAX_BODY_BYTES = 32 * 1024;
11991
- var AGENT_MAIL_MAX_STORE_BYTES = 32 * 1024 * 1024;
11992
- var AGENT_MAIL_DEFAULT_RETENTION_DAYS = 14;
11993
- var AGENT_MAIL_DEFAULT_AUTONOMOUS_WAKE_BUDGET = 16;
11994
- var AGENT_MAIL_DEFAULT_HOURLY_WAKE_LIMIT = 40;
11995
- var AGENT_MAIL_TOOL_NAMES = Object.freeze([
11996
- "list_mailboxes",
11997
- "send_message",
11998
- "check_inbox",
11999
- "read_message",
12000
- "ack_message",
12001
- "reply_message",
12002
- "get_message_status"
12003
- ]);
12004
- var DEFAULT_AGENT_MESSAGING_SETTINGS = Object.freeze({
12005
- enabled: true,
12006
- allowCrossProject: false,
12007
- defaultInjectPolicy: "off",
12008
- wakePeers: false,
12009
- autonomousWakeBudget: AGENT_MAIL_DEFAULT_AUTONOMOUS_WAKE_BUDGET,
12010
- hourlyWakeLimit: AGENT_MAIL_DEFAULT_HOURLY_WAKE_LIMIT,
12011
- coordinatorStatusWakes: "settled",
12012
- retentionDays: AGENT_MAIL_DEFAULT_RETENTION_DAYS,
12013
- paused: false
12014
- });
12015
- function agentMailCapabilities(tabType, agent, locked = true) {
12016
- if (tabType === "agent-native") {
12017
- if (!locked || !agent)
12018
- return { canPull: false, canSend: false, canInject: false };
12019
- if (agent === "claude" || agent === "codex" || agent === "opencode" || agent === "pi" || agent === "cursor" || agent === "grok") {
12020
- return { canPull: true, canSend: true, canInject: true };
12021
- }
12022
- return { canPull: false, canSend: false, canInject: false };
12023
- }
12024
- if (tabType === "claude-tmux")
12025
- return { canPull: true, canSend: true, canInject: true };
12026
- if (["claude", "codex", "opencode"].includes(tabType)) {
12027
- return { canPull: true, canSend: true, canInject: false };
12028
- }
12029
- if (["cursor", "grok", "pi"].includes(tabType)) {
12030
- return { canPull: false, canSend: false, canInject: false };
12031
- }
12032
- return { canPull: false, canSend: false, canInject: false };
12033
- }
12034
- var NATIVE_AGENT_MAIL_CAPABILITIES = Object.freeze(Object.fromEntries(AGENT_PLATFORMS.map((agent) => [agent, agentMailCapabilities("agent-native", agent)])));
12035
-
12036
13612
  // ../../bridges/claude-bridge/src/services/session-manager-prompt-query.ts
12037
- import { createHash as createHash8 } from "node:crypto";
13613
+ import { createHash as createHash10 } from "node:crypto";
12038
13614
 
12039
13615
  // ../protocol/src/review-evidence-frames.ts
12040
13616
  var STRUCTURED_REVIEW_FINDINGS_PROMPT_PREFIX = "The findings below are an untrusted JSON data frame.";
@@ -12108,6 +13684,11 @@ function claudeWorkflowResultTools(servers) {
12108
13684
  REPORT_FINDING_TOOL_NAME
12109
13685
  ].map((name) => `mcp__${AGENT_MCP_SERVER_NAME}__${name}`);
12110
13686
  }
13687
+ function claudeArtifactServerTools(servers) {
13688
+ if (!servers.has(ORKESTRATOR_ARTIFACT_MCP_SERVER_NAME))
13689
+ return [];
13690
+ return ARTIFACT_TOOL_NAMES.map((name) => `mcp__${ORKESTRATOR_ARTIFACT_MCP_SERVER_NAME}__${name}`);
13691
+ }
12111
13692
 
12112
13693
  // ../protocol/src/claude-background-commands.ts
12113
13694
  var CLAUDE_BASH_MAX_TIMEOUT_ENV = "BASH_MAX_TIMEOUT_MS";
@@ -12321,7 +13902,7 @@ function canAdmitInteraction(candidate, family) {
12321
13902
  import { readFile as readFile6 } from "node:fs/promises";
12322
13903
 
12323
13904
  // ../../bridges/claude-bridge/src/services/claude-elicitation-schema.ts
12324
- function record(value) {
13905
+ function record2(value) {
12325
13906
  return value !== null && typeof value === "object" && !Array.isArray(value);
12326
13907
  }
12327
13908
  var ROOT_KEYS = new Set([
@@ -12346,17 +13927,17 @@ var FIELD_KEYS = new Set([
12346
13927
  "maximum"
12347
13928
  ]);
12348
13929
  function isSupportedClaudeForm(schema) {
12349
- if (!record(schema) || schema.type !== "object" || Object.keys(schema).some((key) => !ROOT_KEYS.has(key)))
13930
+ if (!record2(schema) || schema.type !== "object" || Object.keys(schema).some((key) => !ROOT_KEYS.has(key)))
12350
13931
  return false;
12351
13932
  if (schema.additionalProperties !== undefined && schema.additionalProperties !== false)
12352
13933
  return false;
12353
13934
  const properties = schema.properties ?? {};
12354
- if (!record(properties))
13935
+ if (!record2(properties))
12355
13936
  return false;
12356
13937
  if (schema.required !== undefined && (!Array.isArray(schema.required) || !schema.required.every((key) => typeof key === "string" && Object.hasOwn(properties, key))))
12357
13938
  return false;
12358
13939
  return Object.values(properties).every((field) => {
12359
- if (!record(field) || Object.keys(field).some((key) => !FIELD_KEYS.has(key)))
13940
+ if (!record2(field) || Object.keys(field).some((key) => !FIELD_KEYS.has(key)))
12360
13941
  return false;
12361
13942
  if (!["string", "number", "integer", "boolean"].includes(String(field.type)))
12362
13943
  return false;
@@ -12376,7 +13957,7 @@ function isSupportedClaudeForm(schema) {
12376
13957
  });
12377
13958
  }
12378
13959
  function isValidClaudeFormContent(schema, content) {
12379
- if (!isSupportedClaudeForm(schema) || !record(content))
13960
+ if (!isSupportedClaudeForm(schema) || !record2(content))
12380
13961
  return false;
12381
13962
  const properties = schema.properties ?? {};
12382
13963
  if (schema.required?.some((key) => !Object.hasOwn(content, key)))
@@ -12903,8 +14484,21 @@ function shellTaskHooks(session) {
12903
14484
  const matcher = { matcher: "^Bash$", hooks: [hook] };
12904
14485
  return { PreToolUse: matcher, PostToolUse: matcher, PostToolUseFailure: matcher };
12905
14486
  }
12906
- function commandChangeHookFor(route) {
12907
- return commandChangeHooks((toolUseId, change) => route(toolUseId).applyCommandChanges(toolUseId, change));
14487
+ function commandChangeHookFor(route, conversationId) {
14488
+ return commandChangeHooks((toolUseId, change) => route(toolUseId).applyCommandChanges(toolUseId, change), conversationId);
14489
+ }
14490
+ function recordingHookDenials(hook, conversationId) {
14491
+ return async (input, toolUseId, options) => {
14492
+ const output = await hook(input, toolUseId, options);
14493
+ const decision = output.hookSpecificOutput?.permissionDecision;
14494
+ if (input.hook_event_name === "PreToolUse" && input.tool_use_id && decision === "deny") {
14495
+ recordClaudeToolDenied(conversationId, input.tool_use_id, {
14496
+ toolName: input.tool_name,
14497
+ input: input.tool_input
14498
+ });
14499
+ }
14500
+ return output;
14501
+ };
12908
14502
  }
12909
14503
  function turnHook(turn, hook) {
12910
14504
  return (hookInput) => hook(turn, hookInput);
@@ -12951,7 +14545,7 @@ async function resolveQueryPlan(turn, sdkTextPrompt, testHooks) {
12951
14545
  "Agent"
12952
14546
  ];
12953
14547
  const coordinatorReadOnly = isCoordinatorReadOnlyPolicy(policy);
12954
- const agentMailTools = mcpServerNames.has(AGENT_MCP_SERVER_NAME) ? AGENT_MAIL_TOOL_NAMES.map((name) => `mcp__${AGENT_MCP_SERVER_NAME}__${name}`) : [];
14548
+ const agentMailTools = mcpServerNames.has(AGENT_MCP_SERVER_NAME) ? [...AGENT_MAIL_TOOL_NAMES, ...ARTIFACT_TOOL_NAMES].map((name) => `mcp__${AGENT_MCP_SERVER_NAME}__${name}`) : [];
12955
14549
  const allowedTools = coordinatorReadOnly ? claudeReadOnlyAllowedTools({
12956
14550
  agentMcpServerNames: mcpServerNames.has(AGENT_MCP_SERVER_NAME) ? [AGENT_MCP_SERVER_NAME] : [],
12957
14551
  policy,
@@ -12959,7 +14553,8 @@ async function resolveQueryPlan(turn, sdkTextPrompt, testHooks) {
12959
14553
  }) : [
12960
14554
  ...policy?.toolPolicy?.allow ?? defaultAllowedTools,
12961
14555
  ...agentMailTools,
12962
- ...claudeWorkflowResultTools(mcpServerNames)
14556
+ ...claudeWorkflowResultTools(mcpServerNames),
14557
+ ...claudeArtifactServerTools(mcpServerNames)
12963
14558
  ];
12964
14559
  const disallowedTools = coordinatorReadOnly ? claudeDeniedTools(policy) : claudeDisallowedTools(policy);
12965
14560
  const fastMode = options?.fastMode === true;
@@ -12991,7 +14586,7 @@ async function resolveQueryPlan(turn, sdkTextPrompt, testHooks) {
12991
14586
  parameterValues: options?.parameterValues,
12992
14587
  fastMode
12993
14588
  });
12994
- const mcpServersKey = createHash8("sha256").update(JSON.stringify(mcpServers)).digest("hex");
14589
+ const mcpServersKey = createHash10("sha256").update(JSON.stringify(mcpServers)).digest("hex");
12995
14590
  return {
12996
14591
  cwd,
12997
14592
  policy,
@@ -13048,7 +14643,7 @@ function claudeQueryOptions(turn, plan, turnFor, mcp) {
13048
14643
  } = plan;
13049
14644
  const latestTurnHook = (pick) => (...args) => pick(turnFor())(...args);
13050
14645
  const backgroundTaskHook = latestTurnHook((target) => target.recordBackgroundTaskHook);
13051
- const commandChangeHook = commandChangeHookFor(turnFor);
14646
+ const commandChangeHook = commandChangeHookFor(turnFor, session.id);
13052
14647
  const shellTaskHook = shellTaskHooks(session);
13053
14648
  return {
13054
14649
  cwd,
@@ -13112,8 +14707,8 @@ function claudeQueryOptions(turn, plan, turnFor, mcp) {
13112
14707
  plugins: pluginCount > 0 ? plugins : undefined,
13113
14708
  hooks: {
13114
14709
  PreToolUse: [
13115
- ...coordinatorReadOnly && policy ? [{ hooks: [createCoordinatorReadOnlyHook(policy)] }] : [],
13116
- { hooks: [createStaleMcpServerHook(mcp)] },
14710
+ ...coordinatorReadOnly && policy ? [{ hooks: [recordingHookDenials(createCoordinatorReadOnlyHook(policy), session.id)] }] : [],
14711
+ { hooks: [recordingHookDenials(createStaleMcpServerHook(mcp), session.id)] },
13117
14712
  commandChangeHook.PreToolUse,
13118
14713
  shellTaskHook.PreToolUse
13119
14714
  ],
@@ -13134,19 +14729,19 @@ function claudeQueryOptions(turn, plan, turnFor, mcp) {
13134
14729
  }
13135
14730
 
13136
14731
  // ../../bridges/claude-bridge/src/services/diagnostics.ts
13137
- function record2(value) {
14732
+ function record3(value) {
13138
14733
  return value !== null && typeof value === "object" && !Array.isArray(value);
13139
14734
  }
13140
14735
  function observeClaudeMessage(diagnostics, message) {
13141
14736
  diagnostics.activity("message");
13142
14737
  diagnostics.streamEvent();
13143
- if (!record2(message))
14738
+ if (!record3(message))
13144
14739
  return;
13145
- const body = record2(message.message) ? message.message : undefined;
14740
+ const body = record3(message.message) ? message.message : undefined;
13146
14741
  if (!Array.isArray(body?.content))
13147
14742
  return;
13148
14743
  for (const block of body.content.slice(0, 512)) {
13149
- if (!record2(block))
14744
+ if (!record3(block))
13150
14745
  continue;
13151
14746
  if (block.type === "tool_use") {
13152
14747
  const name = typeof block.name === "string" && block.name.length <= 64 ? block.name.toLowerCase() : "other";
@@ -13243,6 +14838,14 @@ function handleAssistantMessage(turn, message) {
13243
14838
  stream.pendingPlanRejectionFeedback = null;
13244
14839
  }
13245
14840
  const { orderedParts, newTaskIds, contentBlockCount } = parseMessageContent(message, toolTracker, turn.mcpServerNames, activeTaskIds, taskRegistry, receivedAt, recordUnknownContentBlock);
14841
+ const evidenceParent = message.parent_tool_use_id;
14842
+ for (const use of claudeToolUses(message)) {
14843
+ recordClaudeToolUse(sessionId, {
14844
+ ...use,
14845
+ turnId: claudeEvidenceTurnId(turn),
14846
+ ...typeof evidenceParent === "string" && evidenceParent ? { parentCallId: evidenceParent } : {}
14847
+ });
14848
+ }
13246
14849
  if (turn.runtime && turn.thisTurn) {
13247
14850
  for (const toolUseId of toolUseIdsFromAssistantMessage(message)) {
13248
14851
  recordClaudeToolOwner(turn.runtime, toolUseId, turn.thisTurn);
@@ -13279,10 +14882,10 @@ function handleAssistantMessage(turn, message) {
13279
14882
  var MAX_INIT_PLUGINS = 128;
13280
14883
  var MAX_INIT_PLUGIN_ERRORS = 64;
13281
14884
  var MAX_PLUGIN_ERROR_LENGTH = 1000;
13282
- function text(value, limit) {
14885
+ function text2(value, limit) {
13283
14886
  return typeof value === "string" && value.length > 0 ? value.slice(0, limit) : undefined;
13284
14887
  }
13285
- function record3(value) {
14888
+ function record4(value) {
13286
14889
  return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
13287
14890
  }
13288
14891
  function errorNamesRow(pluginId, errorPath, row) {
@@ -13305,11 +14908,11 @@ function pluginStatusesFromInit(init) {
13305
14908
  const statuses = [];
13306
14909
  const mcpServers = Array.isArray(init.mcp_servers) ? init.mcp_servers : [];
13307
14910
  for (const candidate of mcpServers) {
13308
- const server = record3(candidate);
13309
- const name = text(server?.name, 256);
14911
+ const server = record4(candidate);
14912
+ const name = text2(server?.name, 256);
13310
14913
  if (!name?.startsWith("plugin:"))
13311
14914
  continue;
13312
- const error = text(server?.error, MAX_PLUGIN_ERROR_LENGTH);
14915
+ const error = text2(server?.error, MAX_PLUGIN_ERROR_LENGTH);
13313
14916
  statuses.push({
13314
14917
  name,
13315
14918
  status: server?.status === "connected" ? "loaded" : "failed",
@@ -13319,24 +14922,24 @@ function pluginStatusesFromInit(init) {
13319
14922
  const plugins = Array.isArray(init.plugins) ? init.plugins : [];
13320
14923
  const loadedRows = [];
13321
14924
  for (const candidate of plugins) {
13322
- const plugin = record3(candidate);
13323
- const name = text(plugin?.name, 256);
14925
+ const plugin = record4(candidate);
14926
+ const name = text2(plugin?.name, 256);
13324
14927
  if (!name)
13325
14928
  continue;
13326
- const path = text(plugin?.path, 4096);
14929
+ const path = text2(plugin?.path, 4096);
13327
14930
  loadedRows.push({ name, ...path ? { path } : {}, status: "loaded" });
13328
14931
  }
13329
14932
  statuses.push(...loadedRows);
13330
14933
  const errors = [];
13331
14934
  const rawErrors = Array.isArray(init.plugin_errors) ? init.plugin_errors : [];
13332
14935
  for (const candidate of rawErrors.slice(0, MAX_INIT_PLUGIN_ERRORS)) {
13333
- const entry = record3(candidate);
13334
- const pluginId = text(entry?.plugin, 256);
14936
+ const entry = record4(candidate);
14937
+ const pluginId = text2(entry?.plugin, 256);
13335
14938
  if (!pluginId)
13336
14939
  continue;
13337
- const message = text(entry?.message, MAX_PLUGIN_ERROR_LENGTH) ?? "Plugin failed to load";
13338
- const type = text(entry?.type, 128) ?? "generic-error";
13339
- const path = text(entry?.path, 4096);
14940
+ const message = text2(entry?.message, MAX_PLUGIN_ERROR_LENGTH) ?? "Plugin failed to load";
14941
+ const type = text2(entry?.type, 128) ?? "generic-error";
14942
+ const path = text2(entry?.path, 4096);
13340
14943
  const row = loadedRows.find((candidateRow) => errorNamesRow(pluginId, path, candidateRow));
13341
14944
  if (row) {
13342
14945
  row.error ??= message;
@@ -13600,6 +15203,10 @@ function claudeStartupFailureMessage(reason) {
13600
15203
  return "Anthropic refused your organization's required Claude settings for this sign-in. Sign in to Claude again; if that does not help, ask an administrator to allow Claude Code for this account.";
13601
15204
  }
13602
15205
  }
15206
+ var STOPPED_TERMINAL_REASONS = new Set([
15207
+ "aborted_streaming",
15208
+ "aborted_tools"
15209
+ ]);
13603
15210
  async function handleResultMessage(turn, message) {
13604
15211
  const { session, sessionId, stream } = turn;
13605
15212
  const resultMsg = message;
@@ -13642,6 +15249,9 @@ async function handleResultMessage(turn, message) {
13642
15249
  turn.diagnostics?.terminalSettled("resolved");
13643
15250
  turn.receivedResult = true;
13644
15251
  turn.receivedPromptResult = true;
15252
+ const interruptRequested = session.interruptRequestedGeneration === turn.turnGeneration;
15253
+ if (interruptRequested)
15254
+ session.interruptRequestedGeneration = undefined;
13645
15255
  debugLog("[session-manager] Query result", {
13646
15256
  sessionId,
13647
15257
  subtype: resultMsg.subtype,
@@ -13716,6 +15326,9 @@ async function handleResultMessage(turn, message) {
13716
15326
  }
13717
15327
  debugLog("[session-manager] Query completed successfully", { sessionId });
13718
15328
  finishTurnInputIfSettled(turn);
15329
+ } else if (!turn.options?.outputSchema && STOPPED_TERMINAL_REASONS.has(resultMsg.terminal_reason ?? "") && interruptRequested) {
15330
+ debugLog("[session-manager] Query stopped by the user", { sessionId });
15331
+ finishTurnInputIfSettled(turn);
13719
15332
  } else {
13720
15333
  console.error("[session-manager] Query error:", resultMsg.subtype, {
13721
15334
  sessionId,
@@ -14060,6 +15673,7 @@ function handlePermissionDenied(turn, message) {
14060
15673
  const reason = boundedNoticeText(denial.decision_reason ?? denial.message);
14061
15674
  if (typeof denial.tool_use_id === "string") {
14062
15675
  commandChangeProbe.discard(denial.tool_use_id);
15676
+ recordClaudeToolDenied(sessionId, denial.tool_use_id, typeof denial.tool_name === "string" ? { toolName: denial.tool_name } : {});
14063
15677
  }
14064
15678
  const marked = typeof denial.tool_use_id === "string" && toolTracker.recordDenial(denial.tool_use_id, {
14065
15679
  reason,
@@ -14159,6 +15773,9 @@ function handleUserMessage(turn, message) {
14159
15773
  toolTracker.setCommandChanges(toolUseId, pending);
14160
15774
  }
14161
15775
  }
15776
+ for (const outcome of claudeToolResultOutcomes(sdkUserMessage)) {
15777
+ recordClaudeToolResult(outcome.callId, outcome.isError);
15778
+ }
14162
15779
  let settledProvisionalTask = false;
14163
15780
  for (const outcome of bashToolResultOutcomes(sdkUserMessage, toolTracker)) {
14164
15781
  if (outcome.launch) {
@@ -14475,12 +16092,13 @@ Please revise the plan based on this feedback.` : `I've reviewed the plan and I
14475
16092
  planApprovalResolvers.delete(approvalId);
14476
16093
  }
14477
16094
  }
14478
- if (claudeWorkflowResultTools(turn.mcpServerNames).includes(toolName) && !session.executionPolicy?.toolPolicy?.deny?.includes(toolName)) {
16095
+ if ((claudeWorkflowResultTools(turn.mcpServerNames).includes(toolName) || claudeArtifactServerTools(turn.mcpServerNames).includes(toolName)) && !session.executionPolicy?.toolPolicy?.deny?.includes(toolName)) {
14479
16096
  return { behavior: "allow", updatedInput: input };
14480
16097
  }
14481
16098
  const turnPolicy = claudeTurnPolicy(session.executionPolicy, turn.options?.readOnly);
14482
16099
  const approvals = turnPolicy?.approvals;
14483
16100
  if (claudeAsksForApproval(turnPolicy, turn.options)) {
16101
+ recordClaudeToolAwaitingApproval(sessionId, permissionContext?.toolUseID, toolName, input);
14484
16102
  return requestClaudeToolApproval(sessionId, toolName, input, permissionContext);
14485
16103
  }
14486
16104
  if (approvals === "deny") {
@@ -14500,7 +16118,13 @@ function createClaudeRuntimeTurn(turn) {
14500
16118
  return {
14501
16119
  handleMessage: (message) => handleTurnMessage(turn, message),
14502
16120
  pushSteerInput: (message) => pushSteerInput(turn, message),
14503
- canUseTool: (toolName, input, permissionContext) => turnCanUseTool(turn, toolName, input, permissionContext),
16121
+ canUseTool: async (toolName, input, permissionContext) => {
16122
+ const decision = await turnCanUseTool(turn, toolName, input, permissionContext);
16123
+ if (decision) {
16124
+ recordClaudePermissionDecision(turn.sessionId, permissionContext?.toolUseID, toolName, input, decision.behavior);
16125
+ }
16126
+ return decision;
16127
+ },
14504
16128
  recordBackgroundTaskHook: turnHook(turn, recordBackgroundTaskHook),
14505
16129
  recordPreCompact: turnHook(turn, recordPreCompact),
14506
16130
  recordPostCompact: turnHook(turn, recordPostCompact),
@@ -14552,9 +16176,24 @@ function endClaudeRuntime(runtime, failure) {
14552
16176
  disarmClaudeRuntimeIdleDetach(runtime);
14553
16177
  runtime.input.close();
14554
16178
  const { session, control } = runtime;
16179
+ const killedBy = session.claudeRuntime === runtime && !runtime.abortController.signal.aborted ? killedClaudeProcessSignal(failure?.error) : undefined;
14555
16180
  if (session.claudeRuntime === runtime)
14556
16181
  session.claudeRuntime = undefined;
14557
16182
  const orphanedForeground = !runtime.waitingTurns.has(runtime.latestTurn) && session.queryControl === control && session.status === "running";
16183
+ const interruptedOutsidePrompt = killedBy && (orphanedForeground || session.abortController === undefined) && (session.status === "running" || (session.retainedContinuationRequestIds?.size ?? 0) > 0 || claudeRuntimeOwesWork(runtime));
16184
+ if (interruptedOutsidePrompt) {
16185
+ session.status = "error";
16186
+ session.error = claudeProcessKilledMessage(killedBy);
16187
+ session.interruption = "restart";
16188
+ session.turnStartedAt = undefined;
16189
+ session.abortController = undefined;
16190
+ session.completionBlockedByBackgroundTasks = false;
16191
+ eventEmitter.emit({
16192
+ type: "session.error",
16193
+ sessionId: session.id,
16194
+ data: { error: session.error }
16195
+ });
16196
+ }
14558
16197
  removeBackgroundTaskCandidatesOwnedBy(session, control);
14559
16198
  forgetSettlingBackgroundTasksOwnedBy(session, control);
14560
16199
  const settled = settleTasksOwnedByClosedControl(session, control, "The Claude session that owned this task ended before it reported a result");
@@ -14571,8 +16210,9 @@ function endClaudeRuntime(runtime, failure) {
14571
16210
  for (const turn of turns) {
14572
16211
  turn.runtimeEnded(turn === failedTurn && failure ? { error: failure.error } : undefined);
14573
16212
  }
14574
- if (orphanedForeground)
16213
+ if (orphanedForeground && !interruptedOutsidePrompt)
14575
16214
  releaseOrphanedForeground(runtime, failure?.error);
16215
+ syncTurnInFlightMark(session);
14576
16216
  }
14577
16217
  function releaseOrphanedForeground(runtime, error) {
14578
16218
  const { session } = runtime;
@@ -14697,6 +16337,7 @@ function markQueryStarted(turn, testHooks) {
14697
16337
  }, { once: true });
14698
16338
  session.finishTurnInputIfSettled = turn.finishTurnInputForThisTurn;
14699
16339
  turn.queryStarted = true;
16340
+ syncTurnInFlightMark(session, { started: true });
14700
16341
  testHooks?.onQueryStarted?.();
14701
16342
  readLiveCommandInventory(session, control);
14702
16343
  reportUltracodeRuntime(session, control);
@@ -14842,6 +16483,7 @@ function settleFailedTurn(turn, error) {
14842
16483
  session.turnStartedAt = turn.beforeStartup.turnStartedAt;
14843
16484
  session.abortController = turn.beforeStartup.abortController;
14844
16485
  session.error = turn.beforeStartup.error;
16486
+ session.interruption = turn.beforeStartup.interruption;
14845
16487
  session.inProgressUsage = turn.beforeStartup.inProgressUsage;
14846
16488
  session.inProgressUsageGeneration = turn.beforeStartup.inProgressUsageGeneration;
14847
16489
  const index = session.messages.indexOf(userMessage);
@@ -14859,12 +16501,17 @@ function settleFailedTurn(turn, error) {
14859
16501
  errorClass: diagnosticErrorClass(error)
14860
16502
  });
14861
16503
  if (session.abortController === abortController) {
16504
+ const killedBy = killedClaudeProcessSignal(error);
16505
+ const failure = killedBy ? claudeProcessKilledMessage(killedBy) : error instanceof Error ? error.message : String(error);
14862
16506
  if (options?.outputSchema && structuredRequestId && !session.structuredOutput) {
14863
- recordStructuredOutput(session, structuredOutputFailure("claude", "provider_error", error instanceof Error ? error.message : String(error), { requestId: structuredRequestId }));
16507
+ recordStructuredOutput(session, structuredOutputFailure("claude", "provider_error", failure, {
16508
+ requestId: structuredRequestId
16509
+ }));
14864
16510
  }
14865
16511
  session.status = "error";
14866
16512
  session.turnStartedAt = undefined;
14867
- session.error = error instanceof Error ? error.message : String(error);
16513
+ session.error = failure;
16514
+ session.interruption = killedBy ? "restart" : undefined;
14868
16515
  session.abortController = undefined;
14869
16516
  session.completionBlockedByBackgroundTasks = false;
14870
16517
  cleanupPendingInteractions(sessionId);
@@ -14904,6 +16551,9 @@ function finalizeTurn(turn) {
14904
16551
  diagnostics
14905
16552
  } = turn;
14906
16553
  turn.queryEnded = true;
16554
+ if (session.interruptRequestedGeneration === turnGeneration) {
16555
+ session.interruptRequestedGeneration = undefined;
16556
+ }
14907
16557
  diagnostics?.close();
14908
16558
  flushDebugLogs();
14909
16559
  abortController.signal.removeEventListener("abort", turn.observeAbort);
@@ -14952,9 +16602,21 @@ function finalizeTurn(turn) {
14952
16602
  }
14953
16603
  reconcileIdleClaudeRuntime(session);
14954
16604
  stream.clearFlushTimer();
16605
+ if (sessions.get(sessionId) === session)
16606
+ syncTurnInFlightMark(session);
14955
16607
  }
14956
16608
 
14957
16609
  // ../../bridges/claude-bridge/src/services/session-manager-prompt.ts
16610
+ function turnWriterLiveness(turn) {
16611
+ const tasks = Object.values(turn.session.backgroundTasks ?? {});
16612
+ if (tasks.some((task) => LIVE_BACKGROUND_TASK_STATUSES.has(task.status)))
16613
+ return "may-write";
16614
+ return turn.runtime?.state === "ended" ? "quiescent" : "unknown";
16615
+ }
16616
+ function recordTurnOutcome(turn, failed) {
16617
+ const phase = turn.abortController.signal.aborted ? "cancelled" : failed ? "failed" : turn.receivedPromptResult ? "succeeded" : "unknown";
16618
+ recordClaudeTurn(turn.sessionId, claudeEvidenceTurnId(turn), phase, turnWriterLiveness(turn));
16619
+ }
14958
16620
  async function sendPrompt(sessionId, prompt, options, testHooks) {
14959
16621
  const admission = admitPrompt(sessionId, options);
14960
16622
  if (!admission)
@@ -14971,11 +16633,16 @@ async function sendPrompt(sessionId, prompt, options, testHooks) {
14971
16633
  userMessage,
14972
16634
  transcriptHydrationFailed
14973
16635
  });
16636
+ let turnRunning = false;
14974
16637
  try {
14975
16638
  const plan = await resolveQueryPlan(turn, sdkTextPrompt, testHooks);
14976
16639
  if (!await runTurnOnRuntime(turn, plan, testHooks))
14977
16640
  return;
16641
+ turnRunning = true;
16642
+ recordClaudeTurn(sessionId, claudeEvidenceTurnId(turn), "running", "may-write");
14978
16643
  await turn.turnEnded;
16644
+ turnRunning = false;
16645
+ recordTurnOutcome(turn, false);
14979
16646
  const followUp = settleTurnEnd(turn);
14980
16647
  if (!followUp)
14981
16648
  return;
@@ -14986,6 +16653,8 @@ async function sendPrompt(sessionId, prompt, options, testHooks) {
14986
16653
  return Promise.reject(repromptError);
14987
16654
  }
14988
16655
  } catch (error) {
16656
+ if (turnRunning)
16657
+ recordTurnOutcome(turn, true);
14989
16658
  settleFailedTurn(turn, error);
14990
16659
  } finally {
14991
16660
  finalizeTurn(turn);
@@ -15032,6 +16701,7 @@ function closeSessionRetainingHistory(sessionId, options = {}) {
15032
16701
  throw new SessionCloseNotConfirmedError("A racing prompt dispatch did not settle");
15033
16702
  }
15034
16703
  forgetPromptDispatchesForSession(sessionId);
16704
+ clearTurnInFlightMarkForClose(session);
15035
16705
  if (sessions.get(sessionId) === session)
15036
16706
  sessions.delete(sessionId);
15037
16707
  return "closed";
@@ -15094,7 +16764,7 @@ config.get("/models", async (c) => {
15094
16764
  var config_default = config;
15095
16765
 
15096
16766
  // ../../bridges/claude-bridge/src/routes/session.ts
15097
- import { randomUUID as randomUUID3 } from "node:crypto";
16767
+ import { randomUUID as randomUUID5 } from "node:crypto";
15098
16768
 
15099
16769
  // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/middleware/body-limit/index.js
15100
16770
  var ERROR_MESSAGE = "Payload Too Large";
@@ -15481,7 +17151,7 @@ sessionActivityBatch.post(SESSION_ACTIVITY_BATCH_PATH, async (c) => {
15481
17151
 
15482
17152
  // ../protocol/src/node/bridge-transcript-summary.ts
15483
17153
  import { Buffer as Buffer3 } from "node:buffer";
15484
- import { createHash as createHash9 } from "node:crypto";
17154
+ import { createHash as createHash11 } from "node:crypto";
15485
17155
 
15486
17156
  // ../protocol/src/node/progressive-transcript.ts
15487
17157
  var BRIDGE_TRANSCRIPT_MAX_MESSAGES = 100;
@@ -15647,7 +17317,7 @@ function digestPayload(payload) {
15647
17317
  const serialized = JSON.stringify(payload);
15648
17318
  return {
15649
17319
  bytes: Buffer3.byteLength(serialized),
15650
- digest: createHash9("sha256").update(serialized).digest("base64url").slice(0, 32)
17320
+ digest: createHash11("sha256").update(serialized).digest("base64url").slice(0, 32)
15651
17321
  };
15652
17322
  }
15653
17323
  function topLevelKey(part, index) {
@@ -16040,7 +17710,7 @@ function registerSessionTranscriptRoutes(app, generation) {
16040
17710
 
16041
17711
  // ../../bridges/claude-bridge/src/routes/session.ts
16042
17712
  var session = new Hono2;
16043
- var TRANSCRIPT_GENERATION = randomUUID3();
17713
+ var TRANSCRIPT_GENERATION = randomUUID5();
16044
17714
  var MAX_IMAGE_ATTACHMENT_BYTES2 = 8 * 1024 * 1024;
16045
17715
  var MAX_QUESTION_ANSWER_REQUEST_BYTES = AGENT_INTERACTION_LIMITS.maxSerializedPayloadBytes + 1024;
16046
17716
  var MAX_CLAUDE_TRANSCRIPT_RESPONSE_BYTES = 16 * 1024 * 1024;
@@ -16083,6 +17753,13 @@ var STALE_PROMPT_STATUS = 409;
16083
17753
  function stalePrompt(message) {
16084
17754
  return { error: message, status: "stale" };
16085
17755
  }
17756
+ function agentMcpArtifactsToken(record) {
17757
+ const artifacts = record.artifacts;
17758
+ if (!artifacts || typeof artifacts !== "object" || Array.isArray(artifacts))
17759
+ return;
17760
+ const token = artifacts.token;
17761
+ return typeof token === "string" && token.length > 0 && token.length <= 1024 ? token : undefined;
17762
+ }
16086
17763
  function errorMessage(error, fallback) {
16087
17764
  return error instanceof Error && error.message ? error.message : fallback;
16088
17765
  }
@@ -16182,6 +17859,7 @@ session.get("/:id", async (c) => {
16182
17859
  createdAt: sessionData.createdAt.toISOString(),
16183
17860
  lastActivity: sessionData.lastActivity.toISOString(),
16184
17861
  error: sessionData.error,
17862
+ ...sessionData.status === "error" && sessionData.interruption ? { interruption: sessionData.interruption } : {},
16185
17863
  structuredOutputRequestId: sessionData.structuredOutputRequestId,
16186
17864
  structuredOutput: sessionData.structuredOutput,
16187
17865
  contextUsage: sessionData.inProgressUsage ?? sessionData.usage,
@@ -16412,10 +18090,12 @@ session.post("/:id/prompt", async (c) => {
16412
18090
  const effort = resolvedEffort;
16413
18091
  const outputSchema = body.outputSchema;
16414
18092
  const agentMcpRecord = body.agentMcp && typeof body.agentMcp === "object" && !Array.isArray(body.agentMcp) ? body.agentMcp : undefined;
18093
+ const artifactsToken = agentMcpRecord ? agentMcpArtifactsToken(agentMcpRecord) : undefined;
16415
18094
  const agentMcp = typeof agentMcpRecord?.url === "string" && typeof agentMcpRecord.token === "string" ? {
16416
18095
  url: agentMcpRecord.url,
16417
18096
  token: agentMcpRecord.token,
16418
- ...agentMcpRecord.design === true ? { design: true } : {}
18097
+ ...agentMcpRecord.design === true ? { design: true } : {},
18098
+ ...artifactsToken ? { artifacts: { token: artifactsToken } } : {}
16419
18099
  } : undefined;
16420
18100
  const requestId = typeof body.requestId === "string" && body.requestId.trim().length > 0 ? body.requestId.trim() : outputSchema === undefined ? undefined : crypto.randomUUID();
16421
18101
  if (outputSchema !== undefined && !isJsonSchema(outputSchema)) {
@@ -16927,6 +18607,68 @@ registerHoldRoute("plan-approvals", "approvalId");
16927
18607
  registerHoldRoute("interactions", "interactionId");
16928
18608
  var session_default = session;
16929
18609
 
18610
+ // ../protocol/src/node/change-tracking/evidence-routes.ts
18611
+ var CHANGE_EVIDENCE_ROUTE = "/change-evidence";
18612
+ var CHANGE_EVIDENCE_BODY_ROUTE = "/change-evidence/body";
18613
+ var CHANGE_EVIDENCE_ACK_ROUTE = "/change-evidence/ack";
18614
+ function boundedInteger3(value, fallback, max) {
18615
+ const parsed = Number(value);
18616
+ if (!Number.isSafeInteger(parsed) || parsed < 0)
18617
+ return fallback;
18618
+ return Math.min(parsed, max);
18619
+ }
18620
+ function changeEvidenceReadBody(spool, query) {
18621
+ const cursor = boundedInteger3(query("cursor"), 0, Number.MAX_SAFE_INTEGER);
18622
+ const limit = boundedInteger3(query("limit"), 256, 1024);
18623
+ const epoch = query("epoch");
18624
+ return spool.read(epoch && epoch !== spool.runtimeEpoch ? 0 : cursor, limit || 256);
18625
+ }
18626
+ function changeEvidenceBodyRouteBody(spool, query) {
18627
+ const id = query("id") ?? "";
18628
+ const bytes = id ? spool.readBody(id) : null;
18629
+ if (!bytes)
18630
+ return { id, state: "missing" };
18631
+ return {
18632
+ id,
18633
+ state: "available",
18634
+ base64: Buffer.from(bytes).toString("base64"),
18635
+ bytes: bytes.length
18636
+ };
18637
+ }
18638
+ function parseChangeEvidenceAck(value) {
18639
+ if (!isPlainRecord(value))
18640
+ return null;
18641
+ const ids = (input) => Array.isArray(input) && input.length <= 1e4 && input.every((id) => typeof id === "string") ? input : null;
18642
+ const retained = ids(value.retainedBodyIds ?? []);
18643
+ const unavailable = ids(value.unavailableBodyIds ?? []);
18644
+ if (typeof value.runtimeEpoch !== "string" || !Number.isSafeInteger(value.upTo) || value.upTo < 0 || !retained || !unavailable) {
18645
+ return null;
18646
+ }
18647
+ return {
18648
+ runtimeEpoch: value.runtimeEpoch,
18649
+ upTo: value.upTo,
18650
+ retainedBodyIds: retained,
18651
+ unavailableBodyIds: unavailable
18652
+ };
18653
+ }
18654
+ function changeEvidenceAckRouteBody(spool, body) {
18655
+ const request = parseChangeEvidenceAck(body);
18656
+ if (!request)
18657
+ return { ok: false, error: "invalid-ack" };
18658
+ return { ok: true, ...spool.acknowledge(request) };
18659
+ }
18660
+
18661
+ // ../../bridges/claude-bridge/src/routes/change-evidence.ts
18662
+ var changeEvidence = new Hono2;
18663
+ changeEvidence.get(CHANGE_EVIDENCE_ROUTE, (c) => c.json(changeEvidenceReadBody(claudeChangeEvidence().spool, (name) => c.req.query(name))));
18664
+ changeEvidence.get(CHANGE_EVIDENCE_BODY_ROUTE, (c) => c.json(changeEvidenceBodyRouteBody(claudeChangeEvidence().spool, (name) => c.req.query(name))));
18665
+ changeEvidence.post(CHANGE_EVIDENCE_ACK_ROUTE, async (c) => {
18666
+ const body = await c.req.json().catch(() => null);
18667
+ const result = changeEvidenceAckRouteBody(claudeChangeEvidence().spool, body);
18668
+ return result.ok ? c.json(result) : c.json(result, 400);
18669
+ });
18670
+ var change_evidence_default = changeEvidence;
18671
+
16930
18672
  // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/utils/stream.js
16931
18673
  var StreamingApi = class {
16932
18674
  writer;
@@ -17411,7 +19153,7 @@ var mcp_default = mcp;
17411
19153
  // ../../bridges/claude-bridge/src/services/slash-commands.ts
17412
19154
  import { open as open3, readdir as readdir2, stat as stat4 } from "node:fs/promises";
17413
19155
  import { StringDecoder } from "node:string_decoder";
17414
- import { join as join9, basename, dirname as dirname2 } from "node:path";
19156
+ import { join as join11, basename, dirname as dirname2 } from "node:path";
17415
19157
  var commandDescriptions = new Map;
17416
19158
  var MAX_COMMAND_DESCRIPTION_CACHE_ENTRIES = 512;
17417
19159
  function rememberCommandDescription(filePath, entry) {
@@ -17516,7 +19258,7 @@ async function scanCommandsDir(commandsDir, prefix) {
17516
19258
  continue;
17517
19259
  const name = basename(entry, ".md");
17518
19260
  const fullName = prefix ? `/${prefix}${name}` : `/${name}`;
17519
- const commandPath = join9(commandsDir, entry);
19261
+ const commandPath = join11(commandsDir, entry);
17520
19262
  livePaths.add(commandPath);
17521
19263
  let description;
17522
19264
  try {
@@ -17537,7 +19279,7 @@ async function discoverSlashCommands(cwd) {
17537
19279
  result.push(cmd);
17538
19280
  }
17539
19281
  };
17540
- const repoCommands = await scanCommandsDir(join9(cwd, ".claude", "commands"), "");
19282
+ const repoCommands = await scanCommandsDir(join11(cwd, ".claude", "commands"), "");
17541
19283
  for (const cmd of repoCommands)
17542
19284
  addCommand(cmd);
17543
19285
  try {
@@ -17545,7 +19287,7 @@ async function discoverSlashCommands(cwd) {
17545
19287
  for (const plugin of plugins) {
17546
19288
  const manifest = await readPluginManifest(plugin.path);
17547
19289
  const pluginName = manifest?.name || plugin.path.split("/").pop() || "unknown";
17548
- const commandsDir = join9(plugin.path, "commands");
19290
+ const commandsDir = join11(plugin.path, "commands");
17549
19291
  const pluginCommands = await scanCommandsDir(commandsDir, `${pluginName}:`);
17550
19292
  for (const cmd of pluginCommands)
17551
19293
  addCommand(cmd);
@@ -19248,6 +20990,7 @@ app.route("/global", health_default);
19248
20990
  app.route("/config", config_default);
19249
20991
  app.route("/session", session_default);
19250
20992
  app.route("/", sessionActivityBatch);
20993
+ app.route("/", change_evidence_default);
19251
20994
  app.route("/event", events_default);
19252
20995
  app.route("/mcp", mcp_default);
19253
20996
  app.route("/plugins", plugins_default);