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.
- package/README.md +29 -1
- package/bin/orkestrator.js +5 -2
- package/dist/client.js +1563 -763
- package/dist/main.js +53035 -25212
- package/package.json +3 -3
- package/resources/claude-bridge/dist/index.js +1925 -182
- package/resources/codex-bridge/dist/index.js +7512 -5812
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
7689
|
-
change
|
|
7690
|
-
|
|
7691
|
-
|
|
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-${
|
|
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
|
-
|
|
7965
|
-
|
|
9405
|
+
var current;
|
|
9406
|
+
function claudeChangeEvidence() {
|
|
9407
|
+
current ??= createChangeEvidence({
|
|
9408
|
+
directory: claimSpoolDirectory()
|
|
9409
|
+
});
|
|
9410
|
+
return current;
|
|
7966
9411
|
}
|
|
7967
|
-
function
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
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
|
|
7977
|
-
|
|
7978
|
-
const
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
|
|
7983
|
-
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
|
|
7989
|
-
|
|
7990
|
-
|
|
7991
|
-
|
|
7992
|
-
|
|
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 (
|
|
7995
|
-
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
10288
|
-
import { join as
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
10776
|
-
import { join as
|
|
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
|
|
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
|
|
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
|
|
12467
|
+
await writeFile4(temporary, `${JSON.stringify({ versions: snapshot })}
|
|
10847
12468
|
`, {
|
|
10848
12469
|
mode: 384
|
|
10849
12470
|
});
|
|
10850
|
-
await
|
|
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
|
|
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 =
|
|
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
|
|
11359
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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) || !
|
|
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 =
|
|
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
|
|
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 (!
|
|
14738
|
+
if (!record3(message))
|
|
13144
14739
|
return;
|
|
13145
|
-
const body =
|
|
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 (!
|
|
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
|
|
14885
|
+
function text2(value, limit) {
|
|
13283
14886
|
return typeof value === "string" && value.length > 0 ? value.slice(0, limit) : undefined;
|
|
13284
14887
|
}
|
|
13285
|
-
function
|
|
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 =
|
|
13309
|
-
const name =
|
|
14911
|
+
const server = record4(candidate);
|
|
14912
|
+
const name = text2(server?.name, 256);
|
|
13310
14913
|
if (!name?.startsWith("plugin:"))
|
|
13311
14914
|
continue;
|
|
13312
|
-
const error =
|
|
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 =
|
|
13323
|
-
const name =
|
|
14925
|
+
const plugin = record4(candidate);
|
|
14926
|
+
const name = text2(plugin?.name, 256);
|
|
13324
14927
|
if (!name)
|
|
13325
14928
|
continue;
|
|
13326
|
-
const path =
|
|
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 =
|
|
13334
|
-
const pluginId =
|
|
14936
|
+
const entry = record4(candidate);
|
|
14937
|
+
const pluginId = text2(entry?.plugin, 256);
|
|
13335
14938
|
if (!pluginId)
|
|
13336
14939
|
continue;
|
|
13337
|
-
const message =
|
|
13338
|
-
const type =
|
|
13339
|
-
const path =
|
|
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) =>
|
|
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",
|
|
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 =
|
|
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
|
|
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
|
|
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:
|
|
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 =
|
|
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
|
|
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 =
|
|
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(
|
|
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 =
|
|
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);
|