orkestrator 2.23.0 → 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 +24 -1
- package/bin/orkestrator.js +5 -2
- package/dist/client.js +1521 -779
- package/dist/main.js +50221 -23881
- package/package.json +3 -3
- package/resources/claude-bridge/dist/index.js +1776 -157
- package/resources/codex-bridge/dist/index.js +7512 -5812
|
@@ -2534,6 +2534,9 @@ function claudeSessionPreferencesDir() {
|
|
|
2534
2534
|
function claudeCommandChangesDir() {
|
|
2535
2535
|
return join(claudeHome(), ".claude", "orkestrator", "command-changes");
|
|
2536
2536
|
}
|
|
2537
|
+
function claudeChangeEvidenceDir() {
|
|
2538
|
+
return join(claudeHome(), ".claude", "orkestrator", "change-evidence");
|
|
2539
|
+
}
|
|
2537
2540
|
|
|
2538
2541
|
// ../../bridges/claude-bridge/src/services/session-preferences.ts
|
|
2539
2542
|
function parseTurnInFlightMark(value) {
|
|
@@ -5702,6 +5705,11 @@ function parseMcpToolName(toolName, knownServerNames) {
|
|
|
5702
5705
|
if (!toolName.startsWith("mcp_")) {
|
|
5703
5706
|
return { isMcpTool: false };
|
|
5704
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
|
+
}
|
|
5705
5713
|
const remainder = toolName.slice(4);
|
|
5706
5714
|
if (knownServerNames && knownServerNames.size > 0) {
|
|
5707
5715
|
let matchedServer;
|
|
@@ -7462,7 +7470,7 @@ function noMessages() {
|
|
|
7462
7470
|
// ../../bridges/claude-bridge/src/services/session-manager-persistence.ts
|
|
7463
7471
|
import { constants } from "node:fs";
|
|
7464
7472
|
import { lstat as lstat2, open as open2, realpath } from "node:fs/promises";
|
|
7465
|
-
import { isAbsolute as isAbsolute3, join as
|
|
7473
|
+
import { isAbsolute as isAbsolute3, join as join8, relative as relative2, resolve as resolve3, sep } from "node:path";
|
|
7466
7474
|
|
|
7467
7475
|
// ../protocol/src/node/command-change-journal.ts
|
|
7468
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";
|
|
@@ -7738,11 +7746,31 @@ class WorkspaceChangeProbe {
|
|
|
7738
7746
|
await this.open(cwd, callId, "note");
|
|
7739
7747
|
}
|
|
7740
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) {
|
|
7741
7759
|
await this.opening.get(callId);
|
|
7742
7760
|
const call = this.calls.get(callId);
|
|
7743
7761
|
if (!call)
|
|
7744
7762
|
return;
|
|
7745
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
|
+
});
|
|
7746
7774
|
try {
|
|
7747
7775
|
if (!call.measure || !call.before) {
|
|
7748
7776
|
if (call.repo.keepsBaseline && !call.repo.disabled) {
|
|
@@ -7751,20 +7779,26 @@ class WorkspaceChangeProbe {
|
|
|
7751
7779
|
return;
|
|
7752
7780
|
}
|
|
7753
7781
|
const before = await call.before;
|
|
7754
|
-
if (!before
|
|
7755
|
-
return;
|
|
7782
|
+
if (!before)
|
|
7783
|
+
return unavailable("before-unavailable");
|
|
7784
|
+
if (call.repo.disabled)
|
|
7785
|
+
return unavailable("probe-disabled");
|
|
7756
7786
|
const after = await this.snapshotShared(call.repo, "fresh");
|
|
7757
|
-
if (!after
|
|
7758
|
-
return;
|
|
7787
|
+
if (!after)
|
|
7788
|
+
return unavailable("after-unavailable");
|
|
7789
|
+
if (call.unavailable)
|
|
7790
|
+
return unavailable("baseline-priming");
|
|
7759
7791
|
const change = await this.diff(call.repo, before, after).catch(() => {
|
|
7760
7792
|
return;
|
|
7761
7793
|
});
|
|
7762
7794
|
if (!change)
|
|
7763
|
-
return;
|
|
7764
|
-
|
|
7765
|
-
change
|
|
7766
|
-
|
|
7767
|
-
|
|
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
|
+
};
|
|
7768
7802
|
} finally {
|
|
7769
7803
|
call.repo.open.delete(call);
|
|
7770
7804
|
await this.releaseUnusedObjects(call.repo);
|
|
@@ -7808,6 +7842,7 @@ class WorkspaceChangeProbe {
|
|
|
7808
7842
|
const call = {
|
|
7809
7843
|
id: callId,
|
|
7810
7844
|
repo,
|
|
7845
|
+
mode,
|
|
7811
7846
|
measure,
|
|
7812
7847
|
openedAt: this.now(),
|
|
7813
7848
|
overlapped: false,
|
|
@@ -8035,54 +8070,1470 @@ function parseNameStatusZ(output) {
|
|
|
8035
8070
|
statuses.set(path, status);
|
|
8036
8071
|
}
|
|
8037
8072
|
}
|
|
8038
|
-
return statuses;
|
|
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
|
+
}
|
|
8039
9324
|
}
|
|
8040
|
-
function
|
|
8041
|
-
|
|
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
|
+
});
|
|
8042
9333
|
}
|
|
8043
|
-
function
|
|
8044
|
-
|
|
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
|
+
}
|
|
9393
|
+
}
|
|
9394
|
+
} catch {}
|
|
9395
|
+
return;
|
|
9396
|
+
}
|
|
9397
|
+
function processIsAlive2(pid) {
|
|
9398
|
+
try {
|
|
9399
|
+
process.kill(pid, 0);
|
|
8045
9400
|
return true;
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
return true;
|
|
9401
|
+
} catch (error) {
|
|
9402
|
+
return error.code === "EPERM";
|
|
8049
9403
|
}
|
|
8050
|
-
return false;
|
|
8051
9404
|
}
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
|
|
8066
|
-
|
|
8067
|
-
|
|
8068
|
-
|
|
9405
|
+
var current;
|
|
9406
|
+
function claudeChangeEvidence() {
|
|
9407
|
+
current ??= createChangeEvidence({
|
|
9408
|
+
directory: claimSpoolDirectory()
|
|
9409
|
+
});
|
|
9410
|
+
return current;
|
|
9411
|
+
}
|
|
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);
|
|
9420
|
+
}
|
|
9421
|
+
}
|
|
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);
|
|
8069
9443
|
}
|
|
8070
|
-
if (
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8076
|
-
deletions: binary ? 0 : Number(rawDeletions),
|
|
8077
|
-
...binary ? { binary: true } : {},
|
|
8078
|
-
...previousPath ? { previousPath } : {}
|
|
8079
|
-
});
|
|
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;
|
|
8080
9450
|
}
|
|
8081
|
-
|
|
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;
|
|
8082
9534
|
}
|
|
8083
9535
|
|
|
8084
9536
|
// ../../bridges/claude-bridge/src/services/command-changes.ts
|
|
8085
|
-
import { join as join5 } from "node:path";
|
|
8086
9537
|
var commandChangeProbe = new WorkspaceChangeProbe;
|
|
8087
9538
|
var COMMAND_CHANGE_HOOK_MATCHER = "^(Bash|Edit|Write|MultiEdit|NotebookEdit)$";
|
|
8088
9539
|
var COMMAND_CHANGE_HOOK_TIMEOUT_SECONDS = 60;
|
|
@@ -8096,7 +9547,7 @@ function commandChangeJournal(sdkSessionId) {
|
|
|
8096
9547
|
if (journal) {
|
|
8097
9548
|
journals.delete(sdkSessionId);
|
|
8098
9549
|
} else {
|
|
8099
|
-
journal = new CommandChangeJournal(
|
|
9550
|
+
journal = new CommandChangeJournal(join7(claudeCommandChangesDir(), `${sdkSessionId}.jsonl`));
|
|
8100
9551
|
}
|
|
8101
9552
|
journals.set(sdkSessionId, journal);
|
|
8102
9553
|
while (journals.size > MAX_CACHED_JOURNALS) {
|
|
@@ -8118,7 +9569,11 @@ function isMeasuredCommand(toolName, toolInput) {
|
|
|
8118
9569
|
const input = toolInput && typeof toolInput === "object" ? toolInput : {};
|
|
8119
9570
|
return input.run_in_background !== true;
|
|
8120
9571
|
}
|
|
8121
|
-
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) {
|
|
8122
9577
|
const before = async (input) => {
|
|
8123
9578
|
if (input.hook_event_name !== "PreToolUse" || !input.tool_use_id)
|
|
8124
9579
|
return {};
|
|
@@ -8135,11 +9590,16 @@ function commandChangeHooks(onMeasured) {
|
|
|
8135
9590
|
}
|
|
8136
9591
|
if (!input.tool_use_id)
|
|
8137
9592
|
return {};
|
|
8138
|
-
|
|
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);
|
|
8139
9596
|
if (change && hasMeasuredChanges(change)) {
|
|
8140
9597
|
commandChangeJournal(input.session_id)?.append(input.tool_use_id, change);
|
|
8141
9598
|
onMeasured(input.tool_use_id, change);
|
|
8142
9599
|
}
|
|
9600
|
+
if (observation && conversationId) {
|
|
9601
|
+
recordClaudeObservation(conversationId, input.tool_use_id, observation);
|
|
9602
|
+
}
|
|
8143
9603
|
return {};
|
|
8144
9604
|
};
|
|
8145
9605
|
const matcher = (hook) => ({
|
|
@@ -8837,7 +10297,7 @@ async function assertNoSymlinkComponents(lexicalRoot, targetPath) {
|
|
|
8837
10297
|
const childPath = relative2(lexicalRoot, targetPath);
|
|
8838
10298
|
let currentPath = lexicalRoot;
|
|
8839
10299
|
for (const segment of childPath.split(sep).filter(Boolean)) {
|
|
8840
|
-
currentPath =
|
|
10300
|
+
currentPath = join8(currentPath, segment);
|
|
8841
10301
|
const stats = await lstat2(currentPath).catch((error) => {
|
|
8842
10302
|
throw attachmentErrorForFsFailure(error);
|
|
8843
10303
|
});
|
|
@@ -10366,11 +11826,84 @@ function createClaudeTurn(admission, input) {
|
|
|
10366
11826
|
import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
10367
11827
|
|
|
10368
11828
|
// ../../bridges/claude-bridge/src/services/mcp-config.ts
|
|
10369
|
-
import { createHash as
|
|
10370
|
-
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
|
|
10371
11893
|
var AGENT_MCP_SERVER_NAME = "orkestrator";
|
|
10372
11894
|
var AGENT_MCP_URL_ENV = "ORKESTRATOR_AGENT_MCP_URL";
|
|
10373
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
|
+
}
|
|
10374
11907
|
function getOrkestratorAgentMcpServerFromConnection(connection) {
|
|
10375
11908
|
if (!connection?.url?.trim() || !connection.token?.trim())
|
|
10376
11909
|
return null;
|
|
@@ -10403,7 +11936,7 @@ async function loadGlobalMcpServers() {
|
|
|
10403
11936
|
return servers ?? {};
|
|
10404
11937
|
}
|
|
10405
11938
|
async function loadProjectMcpServers(cwd) {
|
|
10406
|
-
const servers = await readJsonSliceCached(
|
|
11939
|
+
const servers = await readJsonSliceCached(join9(cwd, ".mcp.json"), "mcpServers", (config) => config?.mcpServers);
|
|
10407
11940
|
return servers ?? {};
|
|
10408
11941
|
}
|
|
10409
11942
|
async function loadProjectOverridesFromGlobal2(cwd) {
|
|
@@ -10507,7 +12040,7 @@ async function loadScopedMcpServers(cwd, scope) {
|
|
|
10507
12040
|
const [global2, projectGlobal, projectLocal] = await Promise.all([
|
|
10508
12041
|
readJsonSliceCachedWithDigest(path, "mcpServers", (config) => config?.mcpServers),
|
|
10509
12042
|
scope === "all" ? readJsonSliceCachedWithDigest(path, `projects:${cwd}:mcpServers`, (config) => config?.projects?.[cwd]?.mcpServers) : null,
|
|
10510
|
-
scope === "all" ? readJsonSliceCachedWithDigest(
|
|
12043
|
+
scope === "all" ? readJsonSliceCachedWithDigest(join9(cwd, ".mcp.json"), "mcpServers", (config) => config?.mcpServers) : null
|
|
10511
12044
|
]);
|
|
10512
12045
|
const configs = {
|
|
10513
12046
|
...global2.value,
|
|
@@ -10521,7 +12054,7 @@ async function loadScopedMcpServers(cwd, scope) {
|
|
|
10521
12054
|
return { configs, revision: { fingerprint: revisionFingerprint(sources), sources, scope } };
|
|
10522
12055
|
}
|
|
10523
12056
|
function revisionFingerprint(sources) {
|
|
10524
|
-
return
|
|
12057
|
+
return createHash8("sha256").update(`user=${sources.user}\x00project=${sources.project}`).digest("base64url");
|
|
10525
12058
|
}
|
|
10526
12059
|
async function getMcpRuntimeConfig(cwd, env = process.env, connection, sources = "all") {
|
|
10527
12060
|
const { configs, revision } = await loadScopedMcpServers(cwd, normalizeScope(sources));
|
|
@@ -10535,14 +12068,20 @@ async function getMcpRuntimeConfig(cwd, env = process.env, connection, sources =
|
|
|
10535
12068
|
url: new URL("/design-mcp", agentServer.url).toString()
|
|
10536
12069
|
};
|
|
10537
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];
|
|
10538
12076
|
const research = normalizeScope(sources) === "none" ? coordinatorResearchMcpServersFromEnv(env) : [];
|
|
10539
12077
|
for (const server of research)
|
|
10540
12078
|
servers[server.name] = { type: "http", url: server.url };
|
|
10541
12079
|
return {
|
|
10542
12080
|
servers,
|
|
10543
12081
|
names: new Set([
|
|
10544
|
-
...Object.keys(configs),
|
|
12082
|
+
...Object.keys(configs).filter((name) => name !== ORKESTRATOR_ARTIFACT_MCP_SERVER_NAME),
|
|
10545
12083
|
...agentServer ? [AGENT_MCP_SERVER_NAME, ...connection?.design ? ["orkestrator-design"] : []] : [],
|
|
12084
|
+
...artifactServer ? [ORKESTRATOR_ARTIFACT_MCP_SERVER_NAME] : [],
|
|
10546
12085
|
...research.map((server) => server.name)
|
|
10547
12086
|
]),
|
|
10548
12087
|
revision
|
|
@@ -10550,7 +12089,7 @@ async function getMcpRuntimeConfig(cwd, env = process.env, connection, sources =
|
|
|
10550
12089
|
}
|
|
10551
12090
|
|
|
10552
12091
|
// ../../bridges/claude-bridge/src/services/session-manager-commands.ts
|
|
10553
|
-
import { randomUUID as
|
|
12092
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
10554
12093
|
|
|
10555
12094
|
// ../protocol/src/agent-command-catalogue.ts
|
|
10556
12095
|
var NATIVE_AGENT_COMMAND_CATALOGUE_VERSION = 1;
|
|
@@ -10854,8 +12393,8 @@ function idleSteerPromptReply(prompt, agentLabel) {
|
|
|
10854
12393
|
}
|
|
10855
12394
|
|
|
10856
12395
|
// ../../bridges/claude-bridge/src/services/claude-terminal-commands.ts
|
|
10857
|
-
import { mkdir as
|
|
10858
|
-
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";
|
|
10859
12398
|
var FILE_NAME = "terminal-slash-commands.json";
|
|
10860
12399
|
var MAX_VERSIONS = 4;
|
|
10861
12400
|
var MAX_NAMES = 512;
|
|
@@ -10866,7 +12405,7 @@ var dirty = false;
|
|
|
10866
12405
|
var entries = [];
|
|
10867
12406
|
var writes = Promise.resolve();
|
|
10868
12407
|
function filePath() {
|
|
10869
|
-
return
|
|
12408
|
+
return join10(claudeOrkestratorDir(), FILE_NAME);
|
|
10870
12409
|
}
|
|
10871
12410
|
function validVersion(value) {
|
|
10872
12411
|
return typeof value === "string" && value.length > 0 && value.length <= MAX_VERSION_LENGTH;
|
|
@@ -10921,22 +12460,22 @@ function recordTerminalSlashCommands(version, commands) {
|
|
|
10921
12460
|
writes = writes.then(async () => {
|
|
10922
12461
|
await load();
|
|
10923
12462
|
const directory = claudeOrkestratorDir();
|
|
10924
|
-
await
|
|
12463
|
+
await mkdir4(directory, { recursive: true });
|
|
10925
12464
|
const target = filePath();
|
|
10926
12465
|
const temporary = `${target}.${process.pid}.${Date.now()}.tmp`;
|
|
10927
12466
|
const snapshot = entries;
|
|
10928
|
-
await
|
|
12467
|
+
await writeFile4(temporary, `${JSON.stringify({ versions: snapshot })}
|
|
10929
12468
|
`, {
|
|
10930
12469
|
mode: 384
|
|
10931
12470
|
});
|
|
10932
|
-
await
|
|
12471
|
+
await rename4(temporary, target);
|
|
10933
12472
|
if (entries === snapshot)
|
|
10934
12473
|
dirty = false;
|
|
10935
12474
|
}).catch(() => {});
|
|
10936
12475
|
}
|
|
10937
12476
|
|
|
10938
12477
|
// ../../bridges/claude-bridge/src/services/session-manager-catalog.ts
|
|
10939
|
-
import { createHash as
|
|
12478
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
10940
12479
|
|
|
10941
12480
|
// ../../bridges/claude-bridge/src/services/claude-sign-in.ts
|
|
10942
12481
|
var AUTH_CACHE_TTL_MS = 30000;
|
|
@@ -11303,7 +12842,7 @@ async function steerClaudeSession(sessionId, text, requestId, expectedRunId) {
|
|
|
11303
12842
|
requestId
|
|
11304
12843
|
};
|
|
11305
12844
|
const session = sessions.get(sessionId);
|
|
11306
|
-
const inputDigest =
|
|
12845
|
+
const inputDigest = createHash9("sha256").update(text).digest("hex");
|
|
11307
12846
|
const previous = session?.steerJournal?.get(requestId);
|
|
11308
12847
|
if (previous) {
|
|
11309
12848
|
if (previous.inputDigest !== inputDigest || previous.expectedRunId !== expectedRunId) {
|
|
@@ -11452,7 +12991,7 @@ async function gracefulInterruptClaudeSession(sessionId) {
|
|
|
11452
12991
|
}
|
|
11453
12992
|
|
|
11454
12993
|
// ../../bridges/claude-bridge/src/services/session-manager-commands.ts
|
|
11455
|
-
var CATALOGUE_GENERATION =
|
|
12994
|
+
var CATALOGUE_GENERATION = randomUUID4();
|
|
11456
12995
|
var COMMAND_PROBE_TIMEOUT_MS = 30000;
|
|
11457
12996
|
var MAX_LOCAL_COMMAND_OUTPUT_BYTES = 64 * 1024;
|
|
11458
12997
|
var MAX_MESSAGE_BYTES = 512;
|
|
@@ -12070,55 +13609,8 @@ function createStaleMcpServerHook(mcp) {
|
|
|
12070
13609
|
};
|
|
12071
13610
|
}
|
|
12072
13611
|
|
|
12073
|
-
// ../protocol/src/agent-mail.ts
|
|
12074
|
-
var AGENT_MAIL_MAX_BODY_BYTES = 32 * 1024;
|
|
12075
|
-
var AGENT_MAIL_MAX_STORE_BYTES = 32 * 1024 * 1024;
|
|
12076
|
-
var AGENT_MAIL_DEFAULT_RETENTION_DAYS = 14;
|
|
12077
|
-
var AGENT_MAIL_DEFAULT_AUTONOMOUS_WAKE_BUDGET = 16;
|
|
12078
|
-
var AGENT_MAIL_DEFAULT_HOURLY_WAKE_LIMIT = 40;
|
|
12079
|
-
var AGENT_MAIL_TOOL_NAMES = Object.freeze([
|
|
12080
|
-
"list_mailboxes",
|
|
12081
|
-
"send_message",
|
|
12082
|
-
"check_inbox",
|
|
12083
|
-
"read_message",
|
|
12084
|
-
"ack_message",
|
|
12085
|
-
"reply_message",
|
|
12086
|
-
"get_message_status"
|
|
12087
|
-
]);
|
|
12088
|
-
var DEFAULT_AGENT_MESSAGING_SETTINGS = Object.freeze({
|
|
12089
|
-
enabled: true,
|
|
12090
|
-
allowCrossProject: false,
|
|
12091
|
-
defaultInjectPolicy: "off",
|
|
12092
|
-
wakePeers: false,
|
|
12093
|
-
autonomousWakeBudget: AGENT_MAIL_DEFAULT_AUTONOMOUS_WAKE_BUDGET,
|
|
12094
|
-
hourlyWakeLimit: AGENT_MAIL_DEFAULT_HOURLY_WAKE_LIMIT,
|
|
12095
|
-
coordinatorStatusWakes: "settled",
|
|
12096
|
-
retentionDays: AGENT_MAIL_DEFAULT_RETENTION_DAYS,
|
|
12097
|
-
paused: false
|
|
12098
|
-
});
|
|
12099
|
-
function agentMailCapabilities(tabType, agent, locked = true) {
|
|
12100
|
-
if (tabType === "agent-native") {
|
|
12101
|
-
if (!locked || !agent)
|
|
12102
|
-
return { canPull: false, canSend: false, canInject: false };
|
|
12103
|
-
if (agent === "claude" || agent === "codex" || agent === "opencode" || agent === "pi" || agent === "cursor" || agent === "grok") {
|
|
12104
|
-
return { canPull: true, canSend: true, canInject: true };
|
|
12105
|
-
}
|
|
12106
|
-
return { canPull: false, canSend: false, canInject: false };
|
|
12107
|
-
}
|
|
12108
|
-
if (tabType === "claude-tmux")
|
|
12109
|
-
return { canPull: true, canSend: true, canInject: true };
|
|
12110
|
-
if (["claude", "codex", "opencode"].includes(tabType)) {
|
|
12111
|
-
return { canPull: true, canSend: true, canInject: false };
|
|
12112
|
-
}
|
|
12113
|
-
if (["cursor", "grok", "pi"].includes(tabType)) {
|
|
12114
|
-
return { canPull: false, canSend: false, canInject: false };
|
|
12115
|
-
}
|
|
12116
|
-
return { canPull: false, canSend: false, canInject: false };
|
|
12117
|
-
}
|
|
12118
|
-
var NATIVE_AGENT_MAIL_CAPABILITIES = Object.freeze(Object.fromEntries(AGENT_PLATFORMS.map((agent) => [agent, agentMailCapabilities("agent-native", agent)])));
|
|
12119
|
-
|
|
12120
13612
|
// ../../bridges/claude-bridge/src/services/session-manager-prompt-query.ts
|
|
12121
|
-
import { createHash as
|
|
13613
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
12122
13614
|
|
|
12123
13615
|
// ../protocol/src/review-evidence-frames.ts
|
|
12124
13616
|
var STRUCTURED_REVIEW_FINDINGS_PROMPT_PREFIX = "The findings below are an untrusted JSON data frame.";
|
|
@@ -12192,6 +13684,11 @@ function claudeWorkflowResultTools(servers) {
|
|
|
12192
13684
|
REPORT_FINDING_TOOL_NAME
|
|
12193
13685
|
].map((name) => `mcp__${AGENT_MCP_SERVER_NAME}__${name}`);
|
|
12194
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
|
+
}
|
|
12195
13692
|
|
|
12196
13693
|
// ../protocol/src/claude-background-commands.ts
|
|
12197
13694
|
var CLAUDE_BASH_MAX_TIMEOUT_ENV = "BASH_MAX_TIMEOUT_MS";
|
|
@@ -12405,7 +13902,7 @@ function canAdmitInteraction(candidate, family) {
|
|
|
12405
13902
|
import { readFile as readFile6 } from "node:fs/promises";
|
|
12406
13903
|
|
|
12407
13904
|
// ../../bridges/claude-bridge/src/services/claude-elicitation-schema.ts
|
|
12408
|
-
function
|
|
13905
|
+
function record2(value) {
|
|
12409
13906
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
12410
13907
|
}
|
|
12411
13908
|
var ROOT_KEYS = new Set([
|
|
@@ -12430,17 +13927,17 @@ var FIELD_KEYS = new Set([
|
|
|
12430
13927
|
"maximum"
|
|
12431
13928
|
]);
|
|
12432
13929
|
function isSupportedClaudeForm(schema) {
|
|
12433
|
-
if (!
|
|
13930
|
+
if (!record2(schema) || schema.type !== "object" || Object.keys(schema).some((key) => !ROOT_KEYS.has(key)))
|
|
12434
13931
|
return false;
|
|
12435
13932
|
if (schema.additionalProperties !== undefined && schema.additionalProperties !== false)
|
|
12436
13933
|
return false;
|
|
12437
13934
|
const properties = schema.properties ?? {};
|
|
12438
|
-
if (!
|
|
13935
|
+
if (!record2(properties))
|
|
12439
13936
|
return false;
|
|
12440
13937
|
if (schema.required !== undefined && (!Array.isArray(schema.required) || !schema.required.every((key) => typeof key === "string" && Object.hasOwn(properties, key))))
|
|
12441
13938
|
return false;
|
|
12442
13939
|
return Object.values(properties).every((field) => {
|
|
12443
|
-
if (!
|
|
13940
|
+
if (!record2(field) || Object.keys(field).some((key) => !FIELD_KEYS.has(key)))
|
|
12444
13941
|
return false;
|
|
12445
13942
|
if (!["string", "number", "integer", "boolean"].includes(String(field.type)))
|
|
12446
13943
|
return false;
|
|
@@ -12460,7 +13957,7 @@ function isSupportedClaudeForm(schema) {
|
|
|
12460
13957
|
});
|
|
12461
13958
|
}
|
|
12462
13959
|
function isValidClaudeFormContent(schema, content) {
|
|
12463
|
-
if (!isSupportedClaudeForm(schema) || !
|
|
13960
|
+
if (!isSupportedClaudeForm(schema) || !record2(content))
|
|
12464
13961
|
return false;
|
|
12465
13962
|
const properties = schema.properties ?? {};
|
|
12466
13963
|
if (schema.required?.some((key) => !Object.hasOwn(content, key)))
|
|
@@ -12987,8 +14484,21 @@ function shellTaskHooks(session) {
|
|
|
12987
14484
|
const matcher = { matcher: "^Bash$", hooks: [hook] };
|
|
12988
14485
|
return { PreToolUse: matcher, PostToolUse: matcher, PostToolUseFailure: matcher };
|
|
12989
14486
|
}
|
|
12990
|
-
function commandChangeHookFor(route) {
|
|
12991
|
-
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
|
+
};
|
|
12992
14502
|
}
|
|
12993
14503
|
function turnHook(turn, hook) {
|
|
12994
14504
|
return (hookInput) => hook(turn, hookInput);
|
|
@@ -13035,7 +14545,7 @@ async function resolveQueryPlan(turn, sdkTextPrompt, testHooks) {
|
|
|
13035
14545
|
"Agent"
|
|
13036
14546
|
];
|
|
13037
14547
|
const coordinatorReadOnly = isCoordinatorReadOnlyPolicy(policy);
|
|
13038
|
-
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}`) : [];
|
|
13039
14549
|
const allowedTools = coordinatorReadOnly ? claudeReadOnlyAllowedTools({
|
|
13040
14550
|
agentMcpServerNames: mcpServerNames.has(AGENT_MCP_SERVER_NAME) ? [AGENT_MCP_SERVER_NAME] : [],
|
|
13041
14551
|
policy,
|
|
@@ -13043,7 +14553,8 @@ async function resolveQueryPlan(turn, sdkTextPrompt, testHooks) {
|
|
|
13043
14553
|
}) : [
|
|
13044
14554
|
...policy?.toolPolicy?.allow ?? defaultAllowedTools,
|
|
13045
14555
|
...agentMailTools,
|
|
13046
|
-
...claudeWorkflowResultTools(mcpServerNames)
|
|
14556
|
+
...claudeWorkflowResultTools(mcpServerNames),
|
|
14557
|
+
...claudeArtifactServerTools(mcpServerNames)
|
|
13047
14558
|
];
|
|
13048
14559
|
const disallowedTools = coordinatorReadOnly ? claudeDeniedTools(policy) : claudeDisallowedTools(policy);
|
|
13049
14560
|
const fastMode = options?.fastMode === true;
|
|
@@ -13075,7 +14586,7 @@ async function resolveQueryPlan(turn, sdkTextPrompt, testHooks) {
|
|
|
13075
14586
|
parameterValues: options?.parameterValues,
|
|
13076
14587
|
fastMode
|
|
13077
14588
|
});
|
|
13078
|
-
const mcpServersKey =
|
|
14589
|
+
const mcpServersKey = createHash10("sha256").update(JSON.stringify(mcpServers)).digest("hex");
|
|
13079
14590
|
return {
|
|
13080
14591
|
cwd,
|
|
13081
14592
|
policy,
|
|
@@ -13132,7 +14643,7 @@ function claudeQueryOptions(turn, plan, turnFor, mcp) {
|
|
|
13132
14643
|
} = plan;
|
|
13133
14644
|
const latestTurnHook = (pick) => (...args) => pick(turnFor())(...args);
|
|
13134
14645
|
const backgroundTaskHook = latestTurnHook((target) => target.recordBackgroundTaskHook);
|
|
13135
|
-
const commandChangeHook = commandChangeHookFor(turnFor);
|
|
14646
|
+
const commandChangeHook = commandChangeHookFor(turnFor, session.id);
|
|
13136
14647
|
const shellTaskHook = shellTaskHooks(session);
|
|
13137
14648
|
return {
|
|
13138
14649
|
cwd,
|
|
@@ -13196,8 +14707,8 @@ function claudeQueryOptions(turn, plan, turnFor, mcp) {
|
|
|
13196
14707
|
plugins: pluginCount > 0 ? plugins : undefined,
|
|
13197
14708
|
hooks: {
|
|
13198
14709
|
PreToolUse: [
|
|
13199
|
-
...coordinatorReadOnly && policy ? [{ hooks: [createCoordinatorReadOnlyHook(policy)] }] : [],
|
|
13200
|
-
{ hooks: [createStaleMcpServerHook(mcp)] },
|
|
14710
|
+
...coordinatorReadOnly && policy ? [{ hooks: [recordingHookDenials(createCoordinatorReadOnlyHook(policy), session.id)] }] : [],
|
|
14711
|
+
{ hooks: [recordingHookDenials(createStaleMcpServerHook(mcp), session.id)] },
|
|
13201
14712
|
commandChangeHook.PreToolUse,
|
|
13202
14713
|
shellTaskHook.PreToolUse
|
|
13203
14714
|
],
|
|
@@ -13218,19 +14729,19 @@ function claudeQueryOptions(turn, plan, turnFor, mcp) {
|
|
|
13218
14729
|
}
|
|
13219
14730
|
|
|
13220
14731
|
// ../../bridges/claude-bridge/src/services/diagnostics.ts
|
|
13221
|
-
function
|
|
14732
|
+
function record3(value) {
|
|
13222
14733
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
13223
14734
|
}
|
|
13224
14735
|
function observeClaudeMessage(diagnostics, message) {
|
|
13225
14736
|
diagnostics.activity("message");
|
|
13226
14737
|
diagnostics.streamEvent();
|
|
13227
|
-
if (!
|
|
14738
|
+
if (!record3(message))
|
|
13228
14739
|
return;
|
|
13229
|
-
const body =
|
|
14740
|
+
const body = record3(message.message) ? message.message : undefined;
|
|
13230
14741
|
if (!Array.isArray(body?.content))
|
|
13231
14742
|
return;
|
|
13232
14743
|
for (const block of body.content.slice(0, 512)) {
|
|
13233
|
-
if (!
|
|
14744
|
+
if (!record3(block))
|
|
13234
14745
|
continue;
|
|
13235
14746
|
if (block.type === "tool_use") {
|
|
13236
14747
|
const name = typeof block.name === "string" && block.name.length <= 64 ? block.name.toLowerCase() : "other";
|
|
@@ -13327,6 +14838,14 @@ function handleAssistantMessage(turn, message) {
|
|
|
13327
14838
|
stream.pendingPlanRejectionFeedback = null;
|
|
13328
14839
|
}
|
|
13329
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
|
+
}
|
|
13330
14849
|
if (turn.runtime && turn.thisTurn) {
|
|
13331
14850
|
for (const toolUseId of toolUseIdsFromAssistantMessage(message)) {
|
|
13332
14851
|
recordClaudeToolOwner(turn.runtime, toolUseId, turn.thisTurn);
|
|
@@ -13363,10 +14882,10 @@ function handleAssistantMessage(turn, message) {
|
|
|
13363
14882
|
var MAX_INIT_PLUGINS = 128;
|
|
13364
14883
|
var MAX_INIT_PLUGIN_ERRORS = 64;
|
|
13365
14884
|
var MAX_PLUGIN_ERROR_LENGTH = 1000;
|
|
13366
|
-
function
|
|
14885
|
+
function text2(value, limit) {
|
|
13367
14886
|
return typeof value === "string" && value.length > 0 ? value.slice(0, limit) : undefined;
|
|
13368
14887
|
}
|
|
13369
|
-
function
|
|
14888
|
+
function record4(value) {
|
|
13370
14889
|
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
13371
14890
|
}
|
|
13372
14891
|
function errorNamesRow(pluginId, errorPath, row) {
|
|
@@ -13389,11 +14908,11 @@ function pluginStatusesFromInit(init) {
|
|
|
13389
14908
|
const statuses = [];
|
|
13390
14909
|
const mcpServers = Array.isArray(init.mcp_servers) ? init.mcp_servers : [];
|
|
13391
14910
|
for (const candidate of mcpServers) {
|
|
13392
|
-
const server =
|
|
13393
|
-
const name =
|
|
14911
|
+
const server = record4(candidate);
|
|
14912
|
+
const name = text2(server?.name, 256);
|
|
13394
14913
|
if (!name?.startsWith("plugin:"))
|
|
13395
14914
|
continue;
|
|
13396
|
-
const error =
|
|
14915
|
+
const error = text2(server?.error, MAX_PLUGIN_ERROR_LENGTH);
|
|
13397
14916
|
statuses.push({
|
|
13398
14917
|
name,
|
|
13399
14918
|
status: server?.status === "connected" ? "loaded" : "failed",
|
|
@@ -13403,24 +14922,24 @@ function pluginStatusesFromInit(init) {
|
|
|
13403
14922
|
const plugins = Array.isArray(init.plugins) ? init.plugins : [];
|
|
13404
14923
|
const loadedRows = [];
|
|
13405
14924
|
for (const candidate of plugins) {
|
|
13406
|
-
const plugin =
|
|
13407
|
-
const name =
|
|
14925
|
+
const plugin = record4(candidate);
|
|
14926
|
+
const name = text2(plugin?.name, 256);
|
|
13408
14927
|
if (!name)
|
|
13409
14928
|
continue;
|
|
13410
|
-
const path =
|
|
14929
|
+
const path = text2(plugin?.path, 4096);
|
|
13411
14930
|
loadedRows.push({ name, ...path ? { path } : {}, status: "loaded" });
|
|
13412
14931
|
}
|
|
13413
14932
|
statuses.push(...loadedRows);
|
|
13414
14933
|
const errors = [];
|
|
13415
14934
|
const rawErrors = Array.isArray(init.plugin_errors) ? init.plugin_errors : [];
|
|
13416
14935
|
for (const candidate of rawErrors.slice(0, MAX_INIT_PLUGIN_ERRORS)) {
|
|
13417
|
-
const entry =
|
|
13418
|
-
const pluginId =
|
|
14936
|
+
const entry = record4(candidate);
|
|
14937
|
+
const pluginId = text2(entry?.plugin, 256);
|
|
13419
14938
|
if (!pluginId)
|
|
13420
14939
|
continue;
|
|
13421
|
-
const message =
|
|
13422
|
-
const type =
|
|
13423
|
-
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);
|
|
13424
14943
|
const row = loadedRows.find((candidateRow) => errorNamesRow(pluginId, path, candidateRow));
|
|
13425
14944
|
if (row) {
|
|
13426
14945
|
row.error ??= message;
|
|
@@ -14154,6 +15673,7 @@ function handlePermissionDenied(turn, message) {
|
|
|
14154
15673
|
const reason = boundedNoticeText(denial.decision_reason ?? denial.message);
|
|
14155
15674
|
if (typeof denial.tool_use_id === "string") {
|
|
14156
15675
|
commandChangeProbe.discard(denial.tool_use_id);
|
|
15676
|
+
recordClaudeToolDenied(sessionId, denial.tool_use_id, typeof denial.tool_name === "string" ? { toolName: denial.tool_name } : {});
|
|
14157
15677
|
}
|
|
14158
15678
|
const marked = typeof denial.tool_use_id === "string" && toolTracker.recordDenial(denial.tool_use_id, {
|
|
14159
15679
|
reason,
|
|
@@ -14253,6 +15773,9 @@ function handleUserMessage(turn, message) {
|
|
|
14253
15773
|
toolTracker.setCommandChanges(toolUseId, pending);
|
|
14254
15774
|
}
|
|
14255
15775
|
}
|
|
15776
|
+
for (const outcome of claudeToolResultOutcomes(sdkUserMessage)) {
|
|
15777
|
+
recordClaudeToolResult(outcome.callId, outcome.isError);
|
|
15778
|
+
}
|
|
14256
15779
|
let settledProvisionalTask = false;
|
|
14257
15780
|
for (const outcome of bashToolResultOutcomes(sdkUserMessage, toolTracker)) {
|
|
14258
15781
|
if (outcome.launch) {
|
|
@@ -14569,12 +16092,13 @@ Please revise the plan based on this feedback.` : `I've reviewed the plan and I
|
|
|
14569
16092
|
planApprovalResolvers.delete(approvalId);
|
|
14570
16093
|
}
|
|
14571
16094
|
}
|
|
14572
|
-
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)) {
|
|
14573
16096
|
return { behavior: "allow", updatedInput: input };
|
|
14574
16097
|
}
|
|
14575
16098
|
const turnPolicy = claudeTurnPolicy(session.executionPolicy, turn.options?.readOnly);
|
|
14576
16099
|
const approvals = turnPolicy?.approvals;
|
|
14577
16100
|
if (claudeAsksForApproval(turnPolicy, turn.options)) {
|
|
16101
|
+
recordClaudeToolAwaitingApproval(sessionId, permissionContext?.toolUseID, toolName, input);
|
|
14578
16102
|
return requestClaudeToolApproval(sessionId, toolName, input, permissionContext);
|
|
14579
16103
|
}
|
|
14580
16104
|
if (approvals === "deny") {
|
|
@@ -14594,7 +16118,13 @@ function createClaudeRuntimeTurn(turn) {
|
|
|
14594
16118
|
return {
|
|
14595
16119
|
handleMessage: (message) => handleTurnMessage(turn, message),
|
|
14596
16120
|
pushSteerInput: (message) => pushSteerInput(turn, message),
|
|
14597
|
-
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
|
+
},
|
|
14598
16128
|
recordBackgroundTaskHook: turnHook(turn, recordBackgroundTaskHook),
|
|
14599
16129
|
recordPreCompact: turnHook(turn, recordPreCompact),
|
|
14600
16130
|
recordPostCompact: turnHook(turn, recordPostCompact),
|
|
@@ -15077,6 +16607,16 @@ function finalizeTurn(turn) {
|
|
|
15077
16607
|
}
|
|
15078
16608
|
|
|
15079
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
|
+
}
|
|
15080
16620
|
async function sendPrompt(sessionId, prompt, options, testHooks) {
|
|
15081
16621
|
const admission = admitPrompt(sessionId, options);
|
|
15082
16622
|
if (!admission)
|
|
@@ -15093,11 +16633,16 @@ async function sendPrompt(sessionId, prompt, options, testHooks) {
|
|
|
15093
16633
|
userMessage,
|
|
15094
16634
|
transcriptHydrationFailed
|
|
15095
16635
|
});
|
|
16636
|
+
let turnRunning = false;
|
|
15096
16637
|
try {
|
|
15097
16638
|
const plan = await resolveQueryPlan(turn, sdkTextPrompt, testHooks);
|
|
15098
16639
|
if (!await runTurnOnRuntime(turn, plan, testHooks))
|
|
15099
16640
|
return;
|
|
16641
|
+
turnRunning = true;
|
|
16642
|
+
recordClaudeTurn(sessionId, claudeEvidenceTurnId(turn), "running", "may-write");
|
|
15100
16643
|
await turn.turnEnded;
|
|
16644
|
+
turnRunning = false;
|
|
16645
|
+
recordTurnOutcome(turn, false);
|
|
15101
16646
|
const followUp = settleTurnEnd(turn);
|
|
15102
16647
|
if (!followUp)
|
|
15103
16648
|
return;
|
|
@@ -15108,6 +16653,8 @@ async function sendPrompt(sessionId, prompt, options, testHooks) {
|
|
|
15108
16653
|
return Promise.reject(repromptError);
|
|
15109
16654
|
}
|
|
15110
16655
|
} catch (error) {
|
|
16656
|
+
if (turnRunning)
|
|
16657
|
+
recordTurnOutcome(turn, true);
|
|
15111
16658
|
settleFailedTurn(turn, error);
|
|
15112
16659
|
} finally {
|
|
15113
16660
|
finalizeTurn(turn);
|
|
@@ -15217,7 +16764,7 @@ config.get("/models", async (c) => {
|
|
|
15217
16764
|
var config_default = config;
|
|
15218
16765
|
|
|
15219
16766
|
// ../../bridges/claude-bridge/src/routes/session.ts
|
|
15220
|
-
import { randomUUID as
|
|
16767
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
15221
16768
|
|
|
15222
16769
|
// ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/middleware/body-limit/index.js
|
|
15223
16770
|
var ERROR_MESSAGE = "Payload Too Large";
|
|
@@ -15604,7 +17151,7 @@ sessionActivityBatch.post(SESSION_ACTIVITY_BATCH_PATH, async (c) => {
|
|
|
15604
17151
|
|
|
15605
17152
|
// ../protocol/src/node/bridge-transcript-summary.ts
|
|
15606
17153
|
import { Buffer as Buffer3 } from "node:buffer";
|
|
15607
|
-
import { createHash as
|
|
17154
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
15608
17155
|
|
|
15609
17156
|
// ../protocol/src/node/progressive-transcript.ts
|
|
15610
17157
|
var BRIDGE_TRANSCRIPT_MAX_MESSAGES = 100;
|
|
@@ -15770,7 +17317,7 @@ function digestPayload(payload) {
|
|
|
15770
17317
|
const serialized = JSON.stringify(payload);
|
|
15771
17318
|
return {
|
|
15772
17319
|
bytes: Buffer3.byteLength(serialized),
|
|
15773
|
-
digest:
|
|
17320
|
+
digest: createHash11("sha256").update(serialized).digest("base64url").slice(0, 32)
|
|
15774
17321
|
};
|
|
15775
17322
|
}
|
|
15776
17323
|
function topLevelKey(part, index) {
|
|
@@ -16163,7 +17710,7 @@ function registerSessionTranscriptRoutes(app, generation) {
|
|
|
16163
17710
|
|
|
16164
17711
|
// ../../bridges/claude-bridge/src/routes/session.ts
|
|
16165
17712
|
var session = new Hono2;
|
|
16166
|
-
var TRANSCRIPT_GENERATION =
|
|
17713
|
+
var TRANSCRIPT_GENERATION = randomUUID5();
|
|
16167
17714
|
var MAX_IMAGE_ATTACHMENT_BYTES2 = 8 * 1024 * 1024;
|
|
16168
17715
|
var MAX_QUESTION_ANSWER_REQUEST_BYTES = AGENT_INTERACTION_LIMITS.maxSerializedPayloadBytes + 1024;
|
|
16169
17716
|
var MAX_CLAUDE_TRANSCRIPT_RESPONSE_BYTES = 16 * 1024 * 1024;
|
|
@@ -16206,6 +17753,13 @@ var STALE_PROMPT_STATUS = 409;
|
|
|
16206
17753
|
function stalePrompt(message) {
|
|
16207
17754
|
return { error: message, status: "stale" };
|
|
16208
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
|
+
}
|
|
16209
17763
|
function errorMessage(error, fallback) {
|
|
16210
17764
|
return error instanceof Error && error.message ? error.message : fallback;
|
|
16211
17765
|
}
|
|
@@ -16536,10 +18090,12 @@ session.post("/:id/prompt", async (c) => {
|
|
|
16536
18090
|
const effort = resolvedEffort;
|
|
16537
18091
|
const outputSchema = body.outputSchema;
|
|
16538
18092
|
const agentMcpRecord = body.agentMcp && typeof body.agentMcp === "object" && !Array.isArray(body.agentMcp) ? body.agentMcp : undefined;
|
|
18093
|
+
const artifactsToken = agentMcpRecord ? agentMcpArtifactsToken(agentMcpRecord) : undefined;
|
|
16539
18094
|
const agentMcp = typeof agentMcpRecord?.url === "string" && typeof agentMcpRecord.token === "string" ? {
|
|
16540
18095
|
url: agentMcpRecord.url,
|
|
16541
18096
|
token: agentMcpRecord.token,
|
|
16542
|
-
...agentMcpRecord.design === true ? { design: true } : {}
|
|
18097
|
+
...agentMcpRecord.design === true ? { design: true } : {},
|
|
18098
|
+
...artifactsToken ? { artifacts: { token: artifactsToken } } : {}
|
|
16543
18099
|
} : undefined;
|
|
16544
18100
|
const requestId = typeof body.requestId === "string" && body.requestId.trim().length > 0 ? body.requestId.trim() : outputSchema === undefined ? undefined : crypto.randomUUID();
|
|
16545
18101
|
if (outputSchema !== undefined && !isJsonSchema(outputSchema)) {
|
|
@@ -17051,6 +18607,68 @@ registerHoldRoute("plan-approvals", "approvalId");
|
|
|
17051
18607
|
registerHoldRoute("interactions", "interactionId");
|
|
17052
18608
|
var session_default = session;
|
|
17053
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
|
+
|
|
17054
18672
|
// ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/utils/stream.js
|
|
17055
18673
|
var StreamingApi = class {
|
|
17056
18674
|
writer;
|
|
@@ -17535,7 +19153,7 @@ var mcp_default = mcp;
|
|
|
17535
19153
|
// ../../bridges/claude-bridge/src/services/slash-commands.ts
|
|
17536
19154
|
import { open as open3, readdir as readdir2, stat as stat4 } from "node:fs/promises";
|
|
17537
19155
|
import { StringDecoder } from "node:string_decoder";
|
|
17538
|
-
import { join as
|
|
19156
|
+
import { join as join11, basename, dirname as dirname2 } from "node:path";
|
|
17539
19157
|
var commandDescriptions = new Map;
|
|
17540
19158
|
var MAX_COMMAND_DESCRIPTION_CACHE_ENTRIES = 512;
|
|
17541
19159
|
function rememberCommandDescription(filePath, entry) {
|
|
@@ -17640,7 +19258,7 @@ async function scanCommandsDir(commandsDir, prefix) {
|
|
|
17640
19258
|
continue;
|
|
17641
19259
|
const name = basename(entry, ".md");
|
|
17642
19260
|
const fullName = prefix ? `/${prefix}${name}` : `/${name}`;
|
|
17643
|
-
const commandPath =
|
|
19261
|
+
const commandPath = join11(commandsDir, entry);
|
|
17644
19262
|
livePaths.add(commandPath);
|
|
17645
19263
|
let description;
|
|
17646
19264
|
try {
|
|
@@ -17661,7 +19279,7 @@ async function discoverSlashCommands(cwd) {
|
|
|
17661
19279
|
result.push(cmd);
|
|
17662
19280
|
}
|
|
17663
19281
|
};
|
|
17664
|
-
const repoCommands = await scanCommandsDir(
|
|
19282
|
+
const repoCommands = await scanCommandsDir(join11(cwd, ".claude", "commands"), "");
|
|
17665
19283
|
for (const cmd of repoCommands)
|
|
17666
19284
|
addCommand(cmd);
|
|
17667
19285
|
try {
|
|
@@ -17669,7 +19287,7 @@ async function discoverSlashCommands(cwd) {
|
|
|
17669
19287
|
for (const plugin of plugins) {
|
|
17670
19288
|
const manifest = await readPluginManifest(plugin.path);
|
|
17671
19289
|
const pluginName = manifest?.name || plugin.path.split("/").pop() || "unknown";
|
|
17672
|
-
const commandsDir =
|
|
19290
|
+
const commandsDir = join11(plugin.path, "commands");
|
|
17673
19291
|
const pluginCommands = await scanCommandsDir(commandsDir, `${pluginName}:`);
|
|
17674
19292
|
for (const cmd of pluginCommands)
|
|
17675
19293
|
addCommand(cmd);
|
|
@@ -19372,6 +20990,7 @@ app.route("/global", health_default);
|
|
|
19372
20990
|
app.route("/config", config_default);
|
|
19373
20991
|
app.route("/session", session_default);
|
|
19374
20992
|
app.route("/", sessionActivityBatch);
|
|
20993
|
+
app.route("/", change_evidence_default);
|
|
19375
20994
|
app.route("/event", events_default);
|
|
19376
20995
|
app.route("/mcp", mcp_default);
|
|
19377
20996
|
app.route("/plugins", plugins_default);
|