@zhivex-ai/harness 1.4.1 → 1.6.2
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/CHANGELOG.md +23 -0
- package/README.md +1 -1
- package/SUPPORT.md +1 -1
- package/contracts/stable-api-signatures.json +7 -7
- package/dist/acp-cli.js +10 -10
- package/dist/approvals/approval-diff.d.ts +2 -1
- package/dist/approvals/approval-preview.d.ts +1 -1
- package/dist/approvals/auto-approve.d.ts +137 -0
- package/dist/audit/index.js +6 -6
- package/dist/{audit-pack-e4vaq7np.js → audit-pack-fckpqfsb.js} +3 -3
- package/dist/{cli-1cf4dcnj.js → cli-84en174d.js} +14 -8
- package/dist/{cli-entry-3s63zn4w.js → cli-entry-10th5awp.js} +16 -15
- package/dist/{cli-entry-a890x3kq.js → cli-entry-4g2ewcnw.js} +15 -5
- package/dist/{cli-entry-7f0cen32.js → cli-entry-6amx4mcc.js} +209 -68
- package/dist/{cli-entry-rgs6bxmy.js → cli-entry-z1pbjrg4.js} +2 -2
- package/dist/cli.js +2 -2
- package/dist/compat/code-support.js +8 -8
- package/dist/engine/acp.js +11 -11
- package/dist/engine/client.js +10 -10
- package/dist/engine/desktop-state-v1.js +6 -6
- package/dist/engine/index.js +15 -15
- package/dist/engine/mcp-stdio-v1.js +5 -5
- package/dist/engine/service.js +11 -11
- package/dist/hostile-repository-demo.js +235 -84
- package/dist/{index-mv8sb1ns.js → index-1pn0ecs4.js} +55 -13
- package/dist/{index-dtb2c6ds.js → index-2r4f0p4n.js} +2 -2
- package/dist/{index-6599mpmy.js → index-4mn6m3bq.js} +3 -3
- package/dist/{index-xdbkcccf.js → index-6dxrbnxm.js} +2 -2
- package/dist/{index-crta5wzv.js → index-74f9yhfh.js} +4 -4
- package/dist/{index-f6mqa5gk.js → index-amjttjaj.js} +11 -39
- package/dist/{index-wm9xeq1n.js → index-bt2p8x7r.js} +1 -1
- package/dist/{index-sk3cap35.js → index-eps3c3g6.js} +1 -1
- package/dist/{index-zxg4ve4j.js → index-fqqz1yt8.js} +77 -4
- package/dist/{index-02pqtbbg.js → index-paf7z8kn.js} +10 -10
- package/dist/{index-0zs2dz26.js → index-pf73y4gt.js} +18 -9
- package/dist/{index-74kkpyqk.js → index-rgqzdgv6.js} +12 -6
- package/dist/{index-4fx5f3k5.js → index-rz7wmp71.js} +16 -15
- package/dist/{index-d9zkmhz3.js → index-w0m1nckc.js} +127 -28
- package/dist/{index-sstgcsr0.js → index-yx52bt4a.js} +1 -1
- package/dist/{index-j6zxnc8m.js → index-zrz4rs7p.js} +5 -5
- package/dist/index.js +14 -14
- package/dist/integrations/mcp-stdio-tools.d.ts +4 -1
- package/dist/providers/bedrock.d.ts +1 -1
- package/dist/review/reveal-invisible.d.ts +13 -0
- package/dist/runtime/budget-ledger-scope.d.ts +14 -0
- package/dist/service-cli.js +11 -11
- package/dist/{service-client-cli-eqrdc9zq.js → service-client-cli-pp5z4dmf.js} +4 -4
- package/dist/tools/workspace.d.ts +2 -2
- package/dist/verify-historical-migrations.js +240 -86
- package/dist/workspace/edit-contracts.d.ts +2 -2
- package/dist/workspace/path-policy.d.ts +2 -1
- package/dist/workspace/text-integrity.d.ts +19 -0
- package/dist/workspace/workspace.d.ts +21 -3
- package/dist/zhx.js +2 -2
- package/docs/AUTO_APPROVE.md +51 -0
- package/docs/AZURE_OPENAI.md +4 -4
- package/docs/BEDROCK.md +4 -4
- package/docs/CLI.md +13 -0
- package/docs/CODE_RELEASE.md +36 -6
- package/docs/CODE_WEB_TASKS.md +1 -1
- package/docs/CREDENTIALS.md +1 -1
- package/docs/DURABLE_OPERATIONS.md +1 -1
- package/docs/ENGINE_API.md +1 -1
- package/docs/EXTENSIBILITY.md +7 -3
- package/docs/FIRST_USE.md +6 -6
- package/docs/MINIMUM_TASK_AUTHORITY.md +5 -5
- package/docs/PROJECT_MEMORY.md +1 -1
- package/docs/PUBLIC_SECURITY.md +14 -1
- package/docs/REPOSITORY_EDITING.md +1 -1
- package/docs/TOOL_POLICY.md +7 -1
- package/docs/VERTEX.md +2 -2
- package/docs/WEB_LIMITS.md +2 -2
- package/docs/adr/0002-isolated-mcp-stdio.md +2 -2
- package/evaluations/representative-assembly-matrix.json +37 -1
- package/examples/README.md +3 -3
- package/package.json +16 -15
|
@@ -3816,8 +3816,8 @@ var quarantineFileInputSchema = z6.strictObject({
|
|
|
3816
3816
|
});
|
|
3817
3817
|
var restoreFileInputSchema = z6.strictObject({
|
|
3818
3818
|
quarantineId: z6.string().min(1).max(200).regex(/^[A-Za-z0-9._:-]+$/),
|
|
3819
|
-
destination: workspaceFilePathSchema.
|
|
3820
|
-
expectedDigest: fileDigestSchema.
|
|
3819
|
+
destination: workspaceFilePathSchema.describe("Required workspace path for the quarantined bytes. Restore does not fall back to the original path."),
|
|
3820
|
+
expectedDigest: fileDigestSchema.describe("Required sha256 of the quarantined bytes the operator reviewed.")
|
|
3821
3821
|
});
|
|
3822
3822
|
var mutationOperationSchema = z6.enum(["create", "update", "move", "quarantine", "restore"]);
|
|
3823
3823
|
var mutationAuditEntrySchema = z6.strictObject({
|
|
@@ -4154,13 +4154,62 @@ var runPortableProcess = async (command, options = {}) => {
|
|
|
4154
4154
|
}
|
|
4155
4155
|
};
|
|
4156
4156
|
|
|
4157
|
+
// src/workspace/text-integrity.ts
|
|
4158
|
+
var controlLabel = (code) => code === 0 ? "NUL byte" : `U+${code.toString(16).padStart(4, "0")}`;
|
|
4159
|
+
var firstDisallowedControl = (text2) => {
|
|
4160
|
+
let line = 1;
|
|
4161
|
+
for (let index = 0;index < text2.length; index += 1) {
|
|
4162
|
+
const code = text2.charCodeAt(index);
|
|
4163
|
+
if (code === 10) {
|
|
4164
|
+
line += 1;
|
|
4165
|
+
continue;
|
|
4166
|
+
}
|
|
4167
|
+
if (code === 9 || code === 13)
|
|
4168
|
+
continue;
|
|
4169
|
+
if (code < 32 || code === 127 || code >= 128 && code <= 159)
|
|
4170
|
+
return { line, label: controlLabel(code) };
|
|
4171
|
+
}
|
|
4172
|
+
return;
|
|
4173
|
+
};
|
|
4174
|
+
var firstDisallowedControlBytes = (bytes) => {
|
|
4175
|
+
let line = 1;
|
|
4176
|
+
for (let index = 0;index < bytes.length; index += 1) {
|
|
4177
|
+
const code = bytes[index] ?? 0;
|
|
4178
|
+
if (code === 10) {
|
|
4179
|
+
line += 1;
|
|
4180
|
+
continue;
|
|
4181
|
+
}
|
|
4182
|
+
if (code === 9 || code === 13)
|
|
4183
|
+
continue;
|
|
4184
|
+
if (code < 32 || code === 127)
|
|
4185
|
+
return { line, label: controlLabel(code) };
|
|
4186
|
+
}
|
|
4187
|
+
return;
|
|
4188
|
+
};
|
|
4189
|
+
var refuseControlMessage = (hit) => `Refusing to write a control byte into a text file: ${hit.label} at L${hit.line}. The file was not changed. Remove the control byte.`;
|
|
4190
|
+
var binaryAfterEditMessage = (hit) => `file is binary after edit: ${hit.label} at L${hit.line}. The edit was reverted. Read the file again before retrying.`;
|
|
4191
|
+
var textGuards = {
|
|
4192
|
+
beforeWrite(content, allowControlBytes) {
|
|
4193
|
+
if (allowControlBytes)
|
|
4194
|
+
return;
|
|
4195
|
+
const problem = firstDisallowedControl(content);
|
|
4196
|
+
if (problem)
|
|
4197
|
+
throw new HarnessWorkspaceError(refuseControlMessage(problem));
|
|
4198
|
+
},
|
|
4199
|
+
afterWrite(bytes, allowControlBytes) {
|
|
4200
|
+
if (allowControlBytes)
|
|
4201
|
+
return;
|
|
4202
|
+
return firstDisallowedControlBytes(bytes);
|
|
4203
|
+
}
|
|
4204
|
+
};
|
|
4205
|
+
|
|
4157
4206
|
// src/workspace/path-policy.ts
|
|
4158
4207
|
var HARD_IGNORES = new Set([".git", ".next", ".turbo", ".zhivex-harness", "coverage", "dist", "node_modules"]);
|
|
4159
4208
|
var isSensitiveName = (name) => {
|
|
4160
|
-
const normalized = name.
|
|
4209
|
+
const normalized = name.toLowerCase();
|
|
4161
4210
|
return normalized === ".env" || normalized.startsWith(".env.") && normalized !== ".env.example" || normalized === ".npmrc" || normalized === "id_rsa" || normalized === "id_ed25519" || [".key", ".pem", ".p12", ".pfx"].some((extension) => normalized.endsWith(extension));
|
|
4162
4211
|
};
|
|
4163
|
-
var isHardIgnored = (relativePath) => relativePath.split("/").some((segment) => HARD_IGNORES.has(segment.
|
|
4212
|
+
var isHardIgnored = (relativePath) => relativePath.split("/").some((segment) => HARD_IGNORES.has(segment.toLowerCase()) || isSensitiveName(segment));
|
|
4164
4213
|
|
|
4165
4214
|
// src/workspace/workspace.ts
|
|
4166
4215
|
var IGNORE_FILES = [".gitignore", ".zhivex-harnessignore"];
|
|
@@ -4440,7 +4489,7 @@ class Workspace {
|
|
|
4440
4489
|
throw new HarnessWorkspaceError(`The path escapes the workspace: ${relativePath}`);
|
|
4441
4490
|
const relative = wirePath(path8.relative(this.root, candidate));
|
|
4442
4491
|
if (relative && isHardIgnored(relative)) {
|
|
4443
|
-
const blocked = relative.split("/").find((segment) => HARD_IGNORES.has(segment.
|
|
4492
|
+
const blocked = relative.split("/").find((segment) => HARD_IGNORES.has(segment.toLowerCase()) || isSensitiveName(segment));
|
|
4444
4493
|
throw new HarnessWorkspaceError(`The path is protected by the harness policy: ${blocked}`);
|
|
4445
4494
|
}
|
|
4446
4495
|
return candidate;
|
|
@@ -4483,8 +4532,11 @@ class Workspace {
|
|
|
4483
4532
|
maxBytes: MAX_FILE_BYTES
|
|
4484
4533
|
});
|
|
4485
4534
|
const { contents } = file;
|
|
4486
|
-
if (!allowBinary && contents.includes(0))
|
|
4487
|
-
|
|
4535
|
+
if (!allowBinary && contents.includes(0)) {
|
|
4536
|
+
const hit = firstDisallowedControlBytes(contents);
|
|
4537
|
+
const where = hit ? `: ${hit.label} at L${hit.line}` : "";
|
|
4538
|
+
throw new HarnessWorkspaceError(`The file appears to be binary and cannot be read as text${where}.`);
|
|
4539
|
+
}
|
|
4488
4540
|
return {
|
|
4489
4541
|
path: wirePath(path8.relative(this.root, safe.path)),
|
|
4490
4542
|
absolutePath: safe.path,
|
|
@@ -5151,19 +5203,23 @@ class Workspace {
|
|
|
5151
5203
|
if (position < 0 || text2.indexOf(edit.oldText, position + 1) >= 0) {
|
|
5152
5204
|
throw new HarnessWorkspaceError("Replacement requires exactly one literal match; include more surrounding context.");
|
|
5153
5205
|
}
|
|
5154
|
-
return [{
|
|
5206
|
+
return [{
|
|
5207
|
+
path: edit.path,
|
|
5208
|
+
expectedDigest: edit.expectedDigest,
|
|
5209
|
+
content: text2.slice(0, position) + edit.newText + text2.slice(position + edit.oldText.length)
|
|
5210
|
+
}];
|
|
5155
5211
|
}
|
|
5156
|
-
async applyReplacement(input) {
|
|
5212
|
+
async applyReplacement(input, host = {}) {
|
|
5157
5213
|
const changes = await this.replacementChanges(input);
|
|
5158
|
-
return this.applyPatch({ proposalId: createEditProposal({ changes }).proposalId, changes });
|
|
5214
|
+
return this.applyPatch({ proposalId: createEditProposal({ changes }).proposalId, changes }, host);
|
|
5159
5215
|
}
|
|
5160
|
-
async applyPatch(input) {
|
|
5161
|
-
return this.applyPatchWithModes(input, new Map);
|
|
5216
|
+
async applyPatch(input, host = {}) {
|
|
5217
|
+
return this.applyPatchWithModes(input, new Map, undefined, host);
|
|
5162
5218
|
}
|
|
5163
|
-
async applyPatchWithModes(input, modes, assertActive) {
|
|
5164
|
-
return withWorkspaceMutation(this.root, () => this.applyPatchWithModesLocked(input, modes, assertActive));
|
|
5219
|
+
async applyPatchWithModes(input, modes, assertActive, host = {}) {
|
|
5220
|
+
return withWorkspaceMutation(this.root, () => this.applyPatchWithModesLocked(input, modes, assertActive, host));
|
|
5165
5221
|
}
|
|
5166
|
-
async applyPatchWithModesLocked(input, modes, assertActive) {
|
|
5222
|
+
async applyPatchWithModesLocked(input, modes, assertActive, host = {}) {
|
|
5167
5223
|
const proposal = validateEditProposal(input);
|
|
5168
5224
|
this.checkAuditCapacity(proposal.changes.length);
|
|
5169
5225
|
const targetPaths = new Set(proposal.changes.map((change) => change.path));
|
|
@@ -5208,6 +5264,7 @@ class Workspace {
|
|
|
5208
5264
|
throw new HarnessWorkspaceError(`Stale patch mode rejected for ${change.path}: expected ${modeBinding.beforeMode.toString(8)}, found ${before.mode.toString(8)}.`);
|
|
5209
5265
|
}
|
|
5210
5266
|
}
|
|
5267
|
+
textGuards.beforeWrite(change.content, host.allowControlBytes === true);
|
|
5211
5268
|
const temporary = await this.stageFile(safe.path, change.content, modeBinding?.afterMode ?? before?.mode ?? 420);
|
|
5212
5269
|
prepared.push({
|
|
5213
5270
|
change,
|
|
@@ -5247,6 +5304,13 @@ class Workspace {
|
|
|
5247
5304
|
await unlink(item.temporary);
|
|
5248
5305
|
}
|
|
5249
5306
|
committed.push(item);
|
|
5307
|
+
const published = await readRegularFileNoFollow(item.target, {
|
|
5308
|
+
label: "The published file",
|
|
5309
|
+
maxBytes: MAX_FILE_BYTES
|
|
5310
|
+
});
|
|
5311
|
+
const corrupt = textGuards.afterWrite(published.contents, host.allowControlBytes === true);
|
|
5312
|
+
if (corrupt)
|
|
5313
|
+
throw new HarnessWorkspaceError(binaryAfterEditMessage(corrupt));
|
|
5250
5314
|
await assertActive?.();
|
|
5251
5315
|
}
|
|
5252
5316
|
} catch (error) {
|
|
@@ -5423,30 +5487,65 @@ class Workspace {
|
|
|
5423
5487
|
const audit = await this.audit({ operation: "quarantine", path: parsed.path, beforeDigest: source.digest, quarantineId });
|
|
5424
5488
|
return { quarantineId, path: parsed.path, digest: source.digest, audit };
|
|
5425
5489
|
}
|
|
5426
|
-
async
|
|
5427
|
-
return withWorkspaceMutation(this.root, () => this.restoreQuarantinedLocked(input));
|
|
5428
|
-
}
|
|
5429
|
-
async restoreQuarantinedLocked(input) {
|
|
5430
|
-
const parsed = restoreFileInputSchema.parse(input);
|
|
5431
|
-
this.checkAuditCapacity();
|
|
5490
|
+
async readRestorableQuarantine(quarantineId) {
|
|
5432
5491
|
const directory = await this.secureQuarantineDirectory(false);
|
|
5433
5492
|
let manifest;
|
|
5434
5493
|
try {
|
|
5435
|
-
const manifestFile = await this.readInternalRegularFile(path8.join(directory, `${
|
|
5494
|
+
const manifestFile = await this.readInternalRegularFile(path8.join(directory, `${quarantineId}.json`), "Quarantine manifest", 64 * 1024);
|
|
5436
5495
|
manifest = JSON.parse(manifestFile.contents.toString("utf8"));
|
|
5437
5496
|
} catch {
|
|
5438
|
-
throw new HarnessWorkspaceError(`Quarantine entry was not found: ${
|
|
5497
|
+
throw new HarnessWorkspaceError(`Quarantine entry was not found: ${quarantineId}.`);
|
|
5439
5498
|
}
|
|
5440
|
-
if (manifest.schemaVersion !== 1 || manifest.quarantineId !==
|
|
5441
|
-
throw new HarnessWorkspaceError(`Quarantine entry is not restorable: ${
|
|
5499
|
+
if (manifest.schemaVersion !== 1 || manifest.quarantineId !== quarantineId || manifest.status !== "quarantined") {
|
|
5500
|
+
throw new HarnessWorkspaceError(`Quarantine entry is not restorable: ${quarantineId}.`);
|
|
5442
5501
|
}
|
|
5443
|
-
const dataPath = path8.join(directory, `${
|
|
5502
|
+
const dataPath = path8.join(directory, `${quarantineId}.data`);
|
|
5444
5503
|
const dataFile = await this.readInternalRegularFile(dataPath, "Quarantine payload", MAX_FILE_BYTES);
|
|
5445
5504
|
const digest2 = digestBytes(dataFile.contents);
|
|
5446
|
-
if (digest2 !== manifest.digest
|
|
5505
|
+
if (digest2 !== manifest.digest) {
|
|
5506
|
+
throw new HarnessWorkspaceError(`Quarantine digest mismatch: ${quarantineId}.`);
|
|
5507
|
+
}
|
|
5508
|
+
return { manifest, dataPath, dataFile, digest: digest2 };
|
|
5509
|
+
}
|
|
5510
|
+
async previewRestore(input) {
|
|
5511
|
+
const parsed = restoreFileInputSchema.parse(input);
|
|
5512
|
+
const quarantined = await this.readRestorableQuarantine(parsed.quarantineId);
|
|
5513
|
+
if (parsed.expectedDigest !== quarantined.digest) {
|
|
5447
5514
|
throw new HarnessWorkspaceError(`Quarantine digest mismatch: ${parsed.quarantineId}.`);
|
|
5448
5515
|
}
|
|
5449
|
-
const
|
|
5516
|
+
const destination = await this.safePath(parsed.destination, { allowMissing: true });
|
|
5517
|
+
if (destination.exists)
|
|
5518
|
+
throw new HarnessWorkspaceError(`Restore destination already exists: ${parsed.destination}.`);
|
|
5519
|
+
let after;
|
|
5520
|
+
try {
|
|
5521
|
+
after = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(quarantined.dataFile.contents);
|
|
5522
|
+
} catch {
|
|
5523
|
+
throw new HarnessWorkspaceError("Preview requires valid UTF-8 text.");
|
|
5524
|
+
}
|
|
5525
|
+
return {
|
|
5526
|
+
proposalId: parsed.expectedDigest,
|
|
5527
|
+
files: [{
|
|
5528
|
+
path: parsed.destination,
|
|
5529
|
+
expectedDigest: parsed.expectedDigest,
|
|
5530
|
+
before: null,
|
|
5531
|
+
after,
|
|
5532
|
+
afterDigest: quarantined.digest,
|
|
5533
|
+
operation: "restore"
|
|
5534
|
+
}]
|
|
5535
|
+
};
|
|
5536
|
+
}
|
|
5537
|
+
async restoreQuarantined(input) {
|
|
5538
|
+
return withWorkspaceMutation(this.root, () => this.restoreQuarantinedLocked(input));
|
|
5539
|
+
}
|
|
5540
|
+
async restoreQuarantinedLocked(input) {
|
|
5541
|
+
const parsed = restoreFileInputSchema.parse(input);
|
|
5542
|
+
this.checkAuditCapacity();
|
|
5543
|
+
const quarantined = await this.readRestorableQuarantine(parsed.quarantineId);
|
|
5544
|
+
if (parsed.expectedDigest !== quarantined.digest) {
|
|
5545
|
+
throw new HarnessWorkspaceError(`Quarantine digest mismatch: ${parsed.quarantineId}.`);
|
|
5546
|
+
}
|
|
5547
|
+
const { manifest, dataPath, dataFile, digest: digest2 } = quarantined;
|
|
5548
|
+
const destinationPath = parsed.destination;
|
|
5450
5549
|
const destination = await this.safePath(destinationPath, { allowMissing: true });
|
|
5451
5550
|
if (destination.exists)
|
|
5452
5551
|
throw new HarnessWorkspaceError(`Restore destination already exists: ${destinationPath}.`);
|
|
@@ -6671,7 +6770,7 @@ var importPatch = async (runId, expectedPatchId, host, base, current, maxFileWri
|
|
|
6671
6770
|
const rollbackErrors = [];
|
|
6672
6771
|
for (const item of [...quarantined].reverse()) {
|
|
6673
6772
|
try {
|
|
6674
|
-
await host.restoreQuarantined({ quarantineId: item.quarantineId, expectedDigest: item.digest });
|
|
6773
|
+
await host.restoreQuarantined({ quarantineId: item.quarantineId, destination: item.path, expectedDigest: item.digest });
|
|
6675
6774
|
} catch (rollbackError) {
|
|
6676
6775
|
rollbackErrors.push(rollbackError);
|
|
6677
6776
|
}
|
|
@@ -7342,6 +7441,79 @@ var taskUsageAdmission = new AsyncLocalStorage3;
|
|
|
7342
7441
|
import { AsyncLocalStorage as AsyncLocalStorage5 } from "node:async_hooks";
|
|
7343
7442
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
7344
7443
|
import { z as z12 } from "zod";
|
|
7444
|
+
|
|
7445
|
+
// src/runtime/budget-ledger-scope.ts
|
|
7446
|
+
var BUDGET_LEDGER_NAMESPACE = "__zhivex_budget__";
|
|
7447
|
+
var BUDGET_OWNER_NAMESPACE_KEY = "budgetOwnerNamespace";
|
|
7448
|
+
var isReservedBudgetNamespace = (namespace) => namespace === BUDGET_LEDGER_NAMESPACE || (namespace?.startsWith(`${BUDGET_LEDGER_NAMESPACE}:`) ?? false);
|
|
7449
|
+
var budgetLedgerNamespace = (namespace) => {
|
|
7450
|
+
if (!namespace || isReservedBudgetNamespace(namespace)) {
|
|
7451
|
+
throw new HarnessStateConflictError("The budget ledger namespace is reserved.");
|
|
7452
|
+
}
|
|
7453
|
+
return `${BUDGET_LEDGER_NAMESPACE}:${namespace}`;
|
|
7454
|
+
};
|
|
7455
|
+
var budgetLedgerScope = (scope) => {
|
|
7456
|
+
if (scope.namespace && isReservedBudgetNamespace(scope.namespace)) {
|
|
7457
|
+
throw new HarnessStateConflictError("The budget ledger namespace is reserved.");
|
|
7458
|
+
}
|
|
7459
|
+
return {
|
|
7460
|
+
tenantId: scope.tenantId,
|
|
7461
|
+
...scope.userId ? { userId: scope.userId } : {},
|
|
7462
|
+
namespace: scope.namespace ? budgetLedgerNamespace(scope.namespace) : `${BUDGET_LEDGER_NAMESPACE}:`
|
|
7463
|
+
};
|
|
7464
|
+
};
|
|
7465
|
+
var sameIdentity = (left, right) => left?.tenantId === right.tenantId && (left.userId ?? undefined) === (right.userId ?? undefined);
|
|
7466
|
+
var bindBudgetLedgerStore = (primary, scope, budget = primary) => {
|
|
7467
|
+
const bound = budgetLedgerScope(scope);
|
|
7468
|
+
const ownerNamespace = scope.namespace ?? "";
|
|
7469
|
+
const isSdkBudget = (value) => sameIdentity(value, bound) && value?.namespace === BUDGET_LEDGER_NAMESPACE;
|
|
7470
|
+
const isBoundBudget = (value) => sameIdentity(value, bound) && value?.namespace === bound.namespace;
|
|
7471
|
+
const assertId = (id) => {
|
|
7472
|
+
if (!/^budget_[a-f0-9]{64}$/.test(id))
|
|
7473
|
+
throw new HarnessStateConflictError("Invalid budget ledger identity.");
|
|
7474
|
+
};
|
|
7475
|
+
const bindState = (state) => {
|
|
7476
|
+
const metadata = { ...state.metadata ?? {} };
|
|
7477
|
+
const owner = metadata[BUDGET_OWNER_NAMESPACE_KEY];
|
|
7478
|
+
if (owner !== undefined && owner !== ownerNamespace) {
|
|
7479
|
+
throw new HarnessStateConflictError("Budget ledger is bound to another namespace.");
|
|
7480
|
+
}
|
|
7481
|
+
return {
|
|
7482
|
+
...state,
|
|
7483
|
+
scope: bound,
|
|
7484
|
+
metadata: { ...metadata, [BUDGET_OWNER_NAMESPACE_KEY]: ownerNamespace }
|
|
7485
|
+
};
|
|
7486
|
+
};
|
|
7487
|
+
const assertStored = (state) => {
|
|
7488
|
+
if (!state)
|
|
7489
|
+
return state;
|
|
7490
|
+
const owner = state.metadata?.[BUDGET_OWNER_NAMESPACE_KEY];
|
|
7491
|
+
if (state.scope?.namespace !== bound.namespace || owner !== ownerNamespace) {
|
|
7492
|
+
throw new HarnessStateConflictError("Budget ledger is not bound to this namespace.");
|
|
7493
|
+
}
|
|
7494
|
+
return state;
|
|
7495
|
+
};
|
|
7496
|
+
return {
|
|
7497
|
+
...primary,
|
|
7498
|
+
load: async (id, requestedScope) => {
|
|
7499
|
+
if (!isSdkBudget(requestedScope) && !isBoundBudget(requestedScope))
|
|
7500
|
+
return primary.load(id, requestedScope);
|
|
7501
|
+
assertId(id);
|
|
7502
|
+
return assertStored(await budget.load(id, bound));
|
|
7503
|
+
},
|
|
7504
|
+
save: async (state, options) => {
|
|
7505
|
+
if (!isSdkBudget(state.scope) && !isBoundBudget(state.scope))
|
|
7506
|
+
return primary.save(state, options);
|
|
7507
|
+
assertId(state.runId);
|
|
7508
|
+
if (state.provider !== "zhivex" || state.modelId !== "budget-coordinator" || state.metadata?.budgetCoordinator !== true) {
|
|
7509
|
+
throw new HarnessStateConflictError("Only SDK budget ledger states may use the reserved namespace.");
|
|
7510
|
+
}
|
|
7511
|
+
return budget.save(bindState(state), options);
|
|
7512
|
+
}
|
|
7513
|
+
};
|
|
7514
|
+
};
|
|
7515
|
+
|
|
7516
|
+
// src/runtime/task-budget.ts
|
|
7345
7517
|
import {
|
|
7346
7518
|
createAgentBudgetCoordinator,
|
|
7347
7519
|
fingerprintAgentHarness as fingerprintAgentHarness2,
|
|
@@ -8871,10 +9043,10 @@ class TaskBudget {
|
|
|
8871
9043
|
this.accountRunId = TaskBudget.accountId(scope, taskId);
|
|
8872
9044
|
this.policy = structuredClone(account.policy);
|
|
8873
9045
|
const budgetId = this.accountRunId;
|
|
8874
|
-
this.coordinator = createAgentBudgetCoordinator({ store, scope, budgetId, limits: this.policy.limits });
|
|
9046
|
+
this.coordinator = createAgentBudgetCoordinator({ store: bindBudgetLedgerStore(store, scope), scope, budgetId, limits: this.policy.limits });
|
|
8875
9047
|
this.coordinatorId = this.coordinator.id;
|
|
8876
9048
|
this.budgetRunId = `budget_${fingerprintAgentHarness2({ budgetId, scope }).slice("sha256:".length)}`;
|
|
8877
|
-
this.budgetScope =
|
|
9049
|
+
this.budgetScope = budgetLedgerScope(scope);
|
|
8878
9050
|
}
|
|
8879
9051
|
static accountId(scope, taskId) {
|
|
8880
9052
|
return `task_budget_${fingerprintAgentHarness2({ scope, taskId }).slice("sha256:".length)}`;
|
|
@@ -12378,6 +12550,7 @@ var record2 = (value) => value !== null && typeof value === "object" && !Array.i
|
|
|
12378
12550
|
var LOCAL_TOOLS = LOCAL_TOOL_NAMES;
|
|
12379
12551
|
var MAX_USER_STEERING = 10;
|
|
12380
12552
|
var CONTEXT_PRIORITY = "Latest user > chronological user steering > historical objective. Assistant never overrides user facts. Questions retain task.";
|
|
12553
|
+
var EARLIER = "[before later user messages] ";
|
|
12381
12554
|
var summarizeHarnessMessages = (messages, maxCharacters = 4000) => {
|
|
12382
12555
|
const redaction = createRedactionPolicy6({ includeEmails: true });
|
|
12383
12556
|
const clean = (text3, limit = 512) => redaction.redactText(text3).replace(/\bBearer\s+\S+/gi, "Bearer [REDACTED]").replace(/\b([A-Z][A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD)|api[_-]?key|access[_-]?token|password)\s*([=:])\s*(?:"[^"]*"|'[^']*'|\S+)/gi, "$1$2[REDACTED]").replace(/\b(?:sk|ghp|gho|github_pat)-[A-Za-z0-9_-]{8,}\b/g, "[REDACTED]").replace(/\s+/g, " ").trim().slice(0, limit);
|
|
@@ -12403,6 +12576,12 @@ var summarizeHarnessMessages = (messages, maxCharacters = 4000) => {
|
|
|
12403
12576
|
omitted = true;
|
|
12404
12577
|
}
|
|
12405
12578
|
};
|
|
12579
|
+
const markRecentEarlier = () => {
|
|
12580
|
+
const marked = recent.map((value) => value.startsWith(EARLIER) ? value : `${EARLIER}${value}`);
|
|
12581
|
+
recent.length = 0;
|
|
12582
|
+
for (const value of marked)
|
|
12583
|
+
add(recent, value, 4);
|
|
12584
|
+
};
|
|
12406
12585
|
const rememberObservation = (tool3, detail) => {
|
|
12407
12586
|
if (typeof tool3 !== "string" || !LOCAL_TOOLS.has(tool3) || typeof detail !== "string")
|
|
12408
12587
|
return;
|
|
@@ -12520,6 +12699,7 @@ var summarizeHarnessMessages = (messages, maxCharacters = 4000) => {
|
|
|
12520
12699
|
objective = text3;
|
|
12521
12700
|
if (historicalObjective)
|
|
12522
12701
|
add(steering, text3, MAX_USER_STEERING);
|
|
12702
|
+
markRecentEarlier();
|
|
12523
12703
|
} else
|
|
12524
12704
|
add(recent, `${message.role}: ${text3}`, 4);
|
|
12525
12705
|
} else if (part.type === "tool-call") {
|
|
@@ -13762,37 +13942,7 @@ import {
|
|
|
13762
13942
|
createSqliteAgentRunStore
|
|
13763
13943
|
} from "@zhivex-ai/agents/ops";
|
|
13764
13944
|
import { createAgentRunLedger } from "@zhivex-ai/agents/control-plane";
|
|
13765
|
-
var
|
|
13766
|
-
tenantId: config.scope.tenantId,
|
|
13767
|
-
...config.scope.userId ? { userId: config.scope.userId } : {},
|
|
13768
|
-
namespace: "__zhivex_budget__"
|
|
13769
|
-
});
|
|
13770
|
-
var withBudgetLedgerStore = (primary, budget, config) => {
|
|
13771
|
-
const scope = budgetScopeFor(config);
|
|
13772
|
-
const budgetScope = (value) => value?.namespace === scope.namespace && value.tenantId === scope.tenantId && (value.userId ?? undefined) === (scope.userId ?? undefined);
|
|
13773
|
-
const assertId = (id) => {
|
|
13774
|
-
if (!/^budget_[a-f0-9]{64}$/.test(id))
|
|
13775
|
-
throw new HarnessStateConflictError("Invalid budget ledger identity.");
|
|
13776
|
-
};
|
|
13777
|
-
return {
|
|
13778
|
-
...primary,
|
|
13779
|
-
load: async (id, requestedScope) => {
|
|
13780
|
-
if (!budgetScope(requestedScope))
|
|
13781
|
-
return primary.load(id, requestedScope);
|
|
13782
|
-
assertId(id);
|
|
13783
|
-
return budget.load(id, requestedScope);
|
|
13784
|
-
},
|
|
13785
|
-
save: async (state, options) => {
|
|
13786
|
-
if (!budgetScope(state.scope))
|
|
13787
|
-
return primary.save(state, options);
|
|
13788
|
-
assertId(state.runId);
|
|
13789
|
-
if (state.provider !== "zhivex" || state.modelId !== "budget-coordinator" || state.metadata?.budgetCoordinator !== true) {
|
|
13790
|
-
throw new HarnessStateConflictError("Only SDK budget ledger states may use the reserved namespace.");
|
|
13791
|
-
}
|
|
13792
|
-
return budget.save(state, options);
|
|
13793
|
-
}
|
|
13794
|
-
};
|
|
13795
|
-
};
|
|
13945
|
+
var withBudgetLedgerStore = (primary, budget, config) => bindBudgetLedgerStore(primary, config.scope, budget);
|
|
13796
13946
|
var HARNESS_SQLITE_FILE = "operations.sqlite";
|
|
13797
13947
|
var HARNESS_OPERATIONS_SCHEMA_VERSION = 1;
|
|
13798
13948
|
var runSummary = (state) => {
|
|
@@ -13963,13 +14113,12 @@ var migrateLegacyFileRuns = async (directory, target, config) => {
|
|
|
13963
14113
|
return result;
|
|
13964
14114
|
};
|
|
13965
14115
|
var openHarnessPersistence = async (config, options = {}) => {
|
|
13966
|
-
|
|
13967
|
-
throw new HarnessStateConflictError("The budget ledger namespace is reserved.");
|
|
14116
|
+
const budgetScope = budgetLedgerScope(config.scope);
|
|
13968
14117
|
await validateStateDirectory(config.workspace, config.stateDirectory);
|
|
13969
14118
|
await ensurePrivateStateDirectory(config.stateDirectory);
|
|
13970
14119
|
await protectStateFromGit(config.workspace, config.stateDirectory);
|
|
13971
14120
|
if (config.storeBackend === "file") {
|
|
13972
|
-
const store = withBudgetLedgerStore(createFileAgentRunStore({ directory: config.stateDirectory, scope: config.scope }), createFileAgentRunStore({ directory: config.stateDirectory, scope:
|
|
14121
|
+
const store = withBudgetLedgerStore(createFileAgentRunStore({ directory: config.stateDirectory, scope: config.scope }), createFileAgentRunStore({ directory: config.stateDirectory, scope: budgetScope }), config);
|
|
13973
14122
|
const migration = shouldMigrateLegacyRuns(config, options.migrateLegacyFileStore) ? await migrateLegacyFileRuns(config.stateDirectory, store, config) : emptyMigration();
|
|
13974
14123
|
return {
|
|
13975
14124
|
backend: "file",
|
|
@@ -14014,7 +14163,7 @@ var openHarnessPersistence = async (config, options = {}) => {
|
|
|
14014
14163
|
database.exec("PRAGMA foreign_keys = ON");
|
|
14015
14164
|
await chmod3(databasePath, 384);
|
|
14016
14165
|
const databaseLike = sqliteAdapter(database);
|
|
14017
|
-
const store = withBudgetLedgerStore(createSqliteAgentRunStore({ db: databaseLike, scope: config.scope, history: "incremental" }), createSqliteAgentRunStore({ db: databaseLike, scope:
|
|
14166
|
+
const store = withBudgetLedgerStore(createSqliteAgentRunStore({ db: databaseLike, scope: config.scope, history: "incremental" }), createSqliteAgentRunStore({ db: databaseLike, scope: budgetScope }), config);
|
|
14018
14167
|
database.exec(`CREATE TRIGGER IF NOT EXISTS zhivex_task_budget_retain_run
|
|
14019
14168
|
BEFORE DELETE ON zhivex_agent_runs
|
|
14020
14169
|
WHEN json_type(OLD.state_json, '$.metadata.zhivexTaskBudgetAccountV1') IS NOT NULL
|
|
@@ -14105,7 +14254,7 @@ var projectMemoryBinding = (workspace, scope) => {
|
|
|
14105
14254
|
segment(scope.userId);
|
|
14106
14255
|
if (scope.namespace !== undefined)
|
|
14107
14256
|
segment(scope.namespace);
|
|
14108
|
-
if (scope.namespace
|
|
14257
|
+
if (isReservedBudgetNamespace(scope.namespace))
|
|
14109
14258
|
throw new HarnessConfigError("Reserved memory namespace.");
|
|
14110
14259
|
const key = (kind, value) => createHash24("sha256").update(kind).update("\x00").update(value).digest("hex");
|
|
14111
14260
|
const workspaceKey = key("workspace", workspace);
|
|
@@ -14663,7 +14812,8 @@ var resolvers = {
|
|
|
14663
14812
|
const parsed = moveFileInputSchema.parse(input);
|
|
14664
14813
|
return [parsed.source, parsed.destination];
|
|
14665
14814
|
},
|
|
14666
|
-
quarantine_file: (input) => [quarantineFileInputSchema.parse(input).path]
|
|
14815
|
+
quarantine_file: (input) => [quarantineFileInputSchema.parse(input).path],
|
|
14816
|
+
restore_file: (input) => [restoreFileInputSchema.parse(input).destination]
|
|
14667
14817
|
};
|
|
14668
14818
|
var validateBuiltinToolPolicy = (policy, tools) => {
|
|
14669
14819
|
for (const rule of policy.rules)
|
|
@@ -14797,7 +14947,7 @@ import { z as z32 } from "zod";
|
|
|
14797
14947
|
// package.json
|
|
14798
14948
|
var package_default = {
|
|
14799
14949
|
name: "@zhivex-ai/harness",
|
|
14800
|
-
version: "1.
|
|
14950
|
+
version: "1.6.2",
|
|
14801
14951
|
description: "Governed, provider-portable runtime for coding agents with durable approvals and isolated repository execution.",
|
|
14802
14952
|
type: "module",
|
|
14803
14953
|
license: "MIT",
|
|
@@ -14897,6 +15047,7 @@ var package_default = {
|
|
|
14897
15047
|
"LICENSE",
|
|
14898
15048
|
"docs/CHANGE_ENVELOPES.md",
|
|
14899
15049
|
"docs/CLI.md",
|
|
15050
|
+
"docs/AUTO_APPROVE.md",
|
|
14900
15051
|
"docs/WEB_LIMITS.md",
|
|
14901
15052
|
"docs/CODE_RELEASE.md",
|
|
14902
15053
|
"docs/CODE_WEB_TASKS.md",
|
|
@@ -15013,26 +15164,26 @@ var package_default = {
|
|
|
15013
15164
|
"smoke:mcp:installed": "bun run build && bun scripts/mcp-installed-smoke.ts"
|
|
15014
15165
|
},
|
|
15015
15166
|
dependencies: {
|
|
15016
|
-
"@zhivex-ai/agents": "1.
|
|
15017
|
-
"@zhivex-ai/anthropic": "0.
|
|
15018
|
-
"@zhivex-ai/azure-openai": "0.
|
|
15019
|
-
"@zhivex-ai/bedrock": "2.0
|
|
15020
|
-
"@zhivex-ai/core": "1.
|
|
15021
|
-
"@zhivex-ai/gemini": "0.
|
|
15022
|
-
"@zhivex-ai/meta": "0.
|
|
15023
|
-
"@zhivex-ai/openai": "0.
|
|
15024
|
-
"@zhivex-ai/qwen": "0.
|
|
15025
|
-
"@zhivex-ai/vertex": "1.
|
|
15026
|
-
"google-auth-library": "11.1
|
|
15027
|
-
zod: "4.6.
|
|
15167
|
+
"@zhivex-ai/agents": "1.12.0",
|
|
15168
|
+
"@zhivex-ai/anthropic": "0.15.0",
|
|
15169
|
+
"@zhivex-ai/azure-openai": "0.8.0",
|
|
15170
|
+
"@zhivex-ai/bedrock": "2.1.0",
|
|
15171
|
+
"@zhivex-ai/core": "1.36.0",
|
|
15172
|
+
"@zhivex-ai/gemini": "0.14.0",
|
|
15173
|
+
"@zhivex-ai/meta": "0.3.0",
|
|
15174
|
+
"@zhivex-ai/openai": "0.17.0",
|
|
15175
|
+
"@zhivex-ai/qwen": "0.18.0",
|
|
15176
|
+
"@zhivex-ai/vertex": "1.3.0",
|
|
15177
|
+
"google-auth-library": "11.3.1",
|
|
15178
|
+
zod: "4.6.7"
|
|
15028
15179
|
},
|
|
15029
15180
|
overrides: {
|
|
15030
|
-
"@zhivex-ai/core": "1.
|
|
15181
|
+
"@zhivex-ai/core": "1.36.0"
|
|
15031
15182
|
},
|
|
15032
15183
|
devDependencies: {
|
|
15033
15184
|
"@agentclientprotocol/sdk": "1.8.0",
|
|
15034
15185
|
"@modelcontextprotocol/server": "2.3.1",
|
|
15035
|
-
"@types/bun": "1.4.
|
|
15186
|
+
"@types/bun": "1.4.3",
|
|
15036
15187
|
"@types/node": "^26.6.5",
|
|
15037
15188
|
typescript: "7.0.2",
|
|
15038
15189
|
"typescript-compiler-api": "npm:typescript@6.0.3"
|
|
@@ -16322,7 +16473,7 @@ var createWorkspaceTools = (workspace, allowedChecks) => ({
|
|
|
16322
16473
|
}),
|
|
16323
16474
|
restore_file: tool5({
|
|
16324
16475
|
name: "restore_file",
|
|
16325
|
-
description: "Restore a quarantined file to
|
|
16476
|
+
description: "Restore a quarantined file to the required destination after the reviewed expectedDigest matches the quarantined bytes. Restore does not fall back to the original path and never overwrites an existing file.",
|
|
16326
16477
|
schema: restoreFileInputSchema,
|
|
16327
16478
|
...mutationApproval,
|
|
16328
16479
|
execute: async (input, context) => serializeJsonValue9(editContractDocument("restore-result", await (harnessExecutionSession(context)?.workspace ?? workspace).restoreQuarantined(input)))
|
|
@@ -3,15 +3,15 @@ import {
|
|
|
3
3
|
combineMcpHostSessions,
|
|
4
4
|
createHarnessMcpTools,
|
|
5
5
|
issueMcpHostSession
|
|
6
|
-
} from "./index-
|
|
6
|
+
} from "./index-2r4f0p4n.js";
|
|
7
7
|
import {
|
|
8
8
|
collectSnapshotInventory,
|
|
9
9
|
readSnapshotFile,
|
|
10
10
|
runPortableProcess
|
|
11
|
-
} from "./index-
|
|
11
|
+
} from "./index-w0m1nckc.js";
|
|
12
12
|
import {
|
|
13
13
|
readRegularFileNoFollow
|
|
14
|
-
} from "./index-
|
|
14
|
+
} from "./index-6dxrbnxm.js";
|
|
15
15
|
|
|
16
16
|
// src/execution/mcp-workspace-snapshot.ts
|
|
17
17
|
import { createHash } from "node:crypto";
|
|
@@ -1254,6 +1254,38 @@ var resultSchema = z3.object({
|
|
|
1254
1254
|
isError: z3.boolean().optional(),
|
|
1255
1255
|
_meta: record.optional()
|
|
1256
1256
|
}).strict().refine((value) => value.content !== undefined || value.structuredContent !== undefined);
|
|
1257
|
+
var ENCODED_MARK = "[ENCODED]";
|
|
1258
|
+
var ENTROPY_MARK = "[HIGH_ENTROPY]";
|
|
1259
|
+
function shannonEntropy(value) {
|
|
1260
|
+
const counts = new Map;
|
|
1261
|
+
for (const char of value)
|
|
1262
|
+
counts.set(char, (counts.get(char) ?? 0) + 1);
|
|
1263
|
+
let entropy = 0;
|
|
1264
|
+
for (const count of counts.values()) {
|
|
1265
|
+
const p = count / value.length;
|
|
1266
|
+
entropy -= p * Math.log2(p);
|
|
1267
|
+
}
|
|
1268
|
+
return entropy;
|
|
1269
|
+
}
|
|
1270
|
+
function secretEncodings(secret) {
|
|
1271
|
+
if (Buffer.byteLength(secret) < 8)
|
|
1272
|
+
return [];
|
|
1273
|
+
const bytes = Buffer.from(secret, "utf8");
|
|
1274
|
+
const base64 = bytes.toString("base64");
|
|
1275
|
+
const base64Url = bytes.toString("base64url");
|
|
1276
|
+
const hex = bytes.toString("hex");
|
|
1277
|
+
return [...new Set([base64, base64.replace(/=+$/u, ""), base64Url, hex, hex.toUpperCase()])].filter((value) => value.length >= 8);
|
|
1278
|
+
}
|
|
1279
|
+
function flagEncodedSecrets(value, secrets) {
|
|
1280
|
+
let text = value;
|
|
1281
|
+
for (const encoding of secrets.flatMap(secretEncodings).sort((left, right) => right.length - left.length)) {
|
|
1282
|
+
text = text.split(encoding).join(ENCODED_MARK);
|
|
1283
|
+
}
|
|
1284
|
+
const flag = (token) => shannonEntropy(token.replace(/=+$/u, "")) >= 3.5 ? ENTROPY_MARK : token;
|
|
1285
|
+
text = text.replace(/[A-Za-z0-9+/_-]{24,}={0,2}/g, flag);
|
|
1286
|
+
text = text.replace(/\b[a-fA-F0-9]{32,}\b/g, flag);
|
|
1287
|
+
return text;
|
|
1288
|
+
}
|
|
1257
1289
|
function compileSchema(schema) {
|
|
1258
1290
|
const pending = [{ value: schema, depth: 0 }];
|
|
1259
1291
|
let nodes = 0;
|
|
@@ -1286,21 +1318,25 @@ async function createIsolatedMcpTools(options) {
|
|
|
1286
1318
|
throw new McpStdioClientError("protocol", message, effectful);
|
|
1287
1319
|
};
|
|
1288
1320
|
const secrets = [...new Set(options.sensitiveValues ?? [])].filter(Boolean).sort((a, b) => b.length - a.length);
|
|
1321
|
+
const flagResults = options.flagEncodedSecrets === true;
|
|
1289
1322
|
const redaction = createRedactionPolicy({ includeEmails: true });
|
|
1290
|
-
const text = (value
|
|
1291
|
-
|
|
1323
|
+
const text = (value, flag) => {
|
|
1324
|
+
const redacted = redaction.redactText(secrets.reduce((current, secret) => current.split(secret).join("[REDACTED]"), value));
|
|
1325
|
+
return flag ? flagEncodedSecrets(redacted, secrets) : redacted;
|
|
1326
|
+
};
|
|
1327
|
+
const scrub = (value, flag, depth = 0, budget = { nodes: 0 }) => {
|
|
1292
1328
|
if (depth > 32 || ++budget.nodes > 20000)
|
|
1293
1329
|
throw new Error("MCP payload complexity limit exceeded.");
|
|
1294
1330
|
if (typeof value === "string")
|
|
1295
|
-
return text(value);
|
|
1331
|
+
return text(value, flag);
|
|
1296
1332
|
if (Array.isArray(value))
|
|
1297
|
-
return value.map((item) => scrub(item, depth + 1, budget));
|
|
1333
|
+
return value.map((item) => scrub(item, flag, depth + 1, budget));
|
|
1298
1334
|
if (value && typeof value === "object")
|
|
1299
|
-
return Object.fromEntries(Object.entries(value).map(([key, item]) => [text(key), scrub(item, depth + 1, budget)]));
|
|
1335
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [text(key, flag), scrub(item, flag, depth + 1, budget)]));
|
|
1300
1336
|
return value;
|
|
1301
1337
|
};
|
|
1302
|
-
const bounded = (value) => {
|
|
1303
|
-
const clean = scrub(value);
|
|
1338
|
+
const bounded = (value, flag) => {
|
|
1339
|
+
const clean = scrub(value, flag);
|
|
1304
1340
|
if (Buffer.byteLength(JSON.stringify(value)) > proposal.limits.maxOutputBytes)
|
|
1305
1341
|
throw new Error("MCP output limit exceeded.");
|
|
1306
1342
|
return clean;
|
|
@@ -1319,7 +1355,7 @@ async function createIsolatedMcpTools(options) {
|
|
|
1319
1355
|
const output = outputValidators.get(input.name);
|
|
1320
1356
|
if (output && !parsed.isError && !output.safeParse(parsed.structuredContent).success)
|
|
1321
1357
|
throw new Error("Output schema");
|
|
1322
|
-
return bounded(parsed);
|
|
1358
|
+
return bounded(parsed, flagResults);
|
|
1323
1359
|
} catch {
|
|
1324
1360
|
return invalidResponse("Isolated MCP result validation failed.", true);
|
|
1325
1361
|
}
|
|
@@ -1330,7 +1366,7 @@ async function createIsolatedMcpTools(options) {
|
|
|
1330
1366
|
async listTools(input, callOptions) {
|
|
1331
1367
|
try {
|
|
1332
1368
|
const response = await options.client.listTools(input, callOptions);
|
|
1333
|
-
const clean = bounded(response);
|
|
1369
|
+
const clean = bounded(response, false);
|
|
1334
1370
|
if (JSON.stringify(clean) !== JSON.stringify(response))
|
|
1335
1371
|
throw new Error("Sensitive discovery");
|
|
1336
1372
|
const parsed = listing.parse(response);
|
|
@@ -1722,7 +1758,13 @@ async function launchMcpToolsOwned(options, groupRecoveryComplete) {
|
|
|
1722
1758
|
return value;
|
|
1723
1759
|
} });
|
|
1724
1760
|
try {
|
|
1725
|
-
const tools = await createIsolatedMcpTools({
|
|
1761
|
+
const tools = await createIsolatedMcpTools({
|
|
1762
|
+
client,
|
|
1763
|
+
proposal,
|
|
1764
|
+
sensitiveValues,
|
|
1765
|
+
journal,
|
|
1766
|
+
flagEncodedSecrets: Object.keys(proposal.secretReferences).length > 0
|
|
1767
|
+
});
|
|
1726
1768
|
const hostSession = issueMcpHostSession({ tools, ...journal, proposal, close: () => client.close() });
|
|
1727
1769
|
return Object.freeze({ tools, hostSession, close: () => closeMcpHostSession(hostSession) });
|
|
1728
1770
|
} catch {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
withWorkspaceMutation
|
|
3
|
-
} from "./index-
|
|
3
|
+
} from "./index-w0m1nckc.js";
|
|
4
4
|
import {
|
|
5
5
|
TERMINAL_SESSION_RUN_STATUSES
|
|
6
|
-
} from "./index-
|
|
6
|
+
} from "./index-fqqz1yt8.js";
|
|
7
7
|
import {
|
|
8
8
|
SqliteDatabase,
|
|
9
9
|
createEditProposal,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
readRegularFileNoFollow,
|
|
12
12
|
statRegularFileNoFollow,
|
|
13
13
|
workspaceFilePathSchema
|
|
14
|
-
} from "./index-
|
|
14
|
+
} from "./index-6dxrbnxm.js";
|
|
15
15
|
|
|
16
16
|
// src/persistence/workspace-checkpoints.ts
|
|
17
17
|
import { createHash, randomUUID } from "node:crypto";
|