@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.
Files changed (76) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +1 -1
  3. package/SUPPORT.md +1 -1
  4. package/contracts/stable-api-signatures.json +7 -7
  5. package/dist/acp-cli.js +10 -10
  6. package/dist/approvals/approval-diff.d.ts +2 -1
  7. package/dist/approvals/approval-preview.d.ts +1 -1
  8. package/dist/approvals/auto-approve.d.ts +137 -0
  9. package/dist/audit/index.js +6 -6
  10. package/dist/{audit-pack-e4vaq7np.js → audit-pack-fckpqfsb.js} +3 -3
  11. package/dist/{cli-1cf4dcnj.js → cli-84en174d.js} +14 -8
  12. package/dist/{cli-entry-3s63zn4w.js → cli-entry-10th5awp.js} +16 -15
  13. package/dist/{cli-entry-a890x3kq.js → cli-entry-4g2ewcnw.js} +15 -5
  14. package/dist/{cli-entry-7f0cen32.js → cli-entry-6amx4mcc.js} +209 -68
  15. package/dist/{cli-entry-rgs6bxmy.js → cli-entry-z1pbjrg4.js} +2 -2
  16. package/dist/cli.js +2 -2
  17. package/dist/compat/code-support.js +8 -8
  18. package/dist/engine/acp.js +11 -11
  19. package/dist/engine/client.js +10 -10
  20. package/dist/engine/desktop-state-v1.js +6 -6
  21. package/dist/engine/index.js +15 -15
  22. package/dist/engine/mcp-stdio-v1.js +5 -5
  23. package/dist/engine/service.js +11 -11
  24. package/dist/hostile-repository-demo.js +235 -84
  25. package/dist/{index-mv8sb1ns.js → index-1pn0ecs4.js} +55 -13
  26. package/dist/{index-dtb2c6ds.js → index-2r4f0p4n.js} +2 -2
  27. package/dist/{index-6599mpmy.js → index-4mn6m3bq.js} +3 -3
  28. package/dist/{index-xdbkcccf.js → index-6dxrbnxm.js} +2 -2
  29. package/dist/{index-crta5wzv.js → index-74f9yhfh.js} +4 -4
  30. package/dist/{index-f6mqa5gk.js → index-amjttjaj.js} +11 -39
  31. package/dist/{index-wm9xeq1n.js → index-bt2p8x7r.js} +1 -1
  32. package/dist/{index-sk3cap35.js → index-eps3c3g6.js} +1 -1
  33. package/dist/{index-zxg4ve4j.js → index-fqqz1yt8.js} +77 -4
  34. package/dist/{index-02pqtbbg.js → index-paf7z8kn.js} +10 -10
  35. package/dist/{index-0zs2dz26.js → index-pf73y4gt.js} +18 -9
  36. package/dist/{index-74kkpyqk.js → index-rgqzdgv6.js} +12 -6
  37. package/dist/{index-4fx5f3k5.js → index-rz7wmp71.js} +16 -15
  38. package/dist/{index-d9zkmhz3.js → index-w0m1nckc.js} +127 -28
  39. package/dist/{index-sstgcsr0.js → index-yx52bt4a.js} +1 -1
  40. package/dist/{index-j6zxnc8m.js → index-zrz4rs7p.js} +5 -5
  41. package/dist/index.js +14 -14
  42. package/dist/integrations/mcp-stdio-tools.d.ts +4 -1
  43. package/dist/providers/bedrock.d.ts +1 -1
  44. package/dist/review/reveal-invisible.d.ts +13 -0
  45. package/dist/runtime/budget-ledger-scope.d.ts +14 -0
  46. package/dist/service-cli.js +11 -11
  47. package/dist/{service-client-cli-eqrdc9zq.js → service-client-cli-pp5z4dmf.js} +4 -4
  48. package/dist/tools/workspace.d.ts +2 -2
  49. package/dist/verify-historical-migrations.js +240 -86
  50. package/dist/workspace/edit-contracts.d.ts +2 -2
  51. package/dist/workspace/path-policy.d.ts +2 -1
  52. package/dist/workspace/text-integrity.d.ts +19 -0
  53. package/dist/workspace/workspace.d.ts +21 -3
  54. package/dist/zhx.js +2 -2
  55. package/docs/AUTO_APPROVE.md +51 -0
  56. package/docs/AZURE_OPENAI.md +4 -4
  57. package/docs/BEDROCK.md +4 -4
  58. package/docs/CLI.md +13 -0
  59. package/docs/CODE_RELEASE.md +36 -6
  60. package/docs/CODE_WEB_TASKS.md +1 -1
  61. package/docs/CREDENTIALS.md +1 -1
  62. package/docs/DURABLE_OPERATIONS.md +1 -1
  63. package/docs/ENGINE_API.md +1 -1
  64. package/docs/EXTENSIBILITY.md +7 -3
  65. package/docs/FIRST_USE.md +6 -6
  66. package/docs/MINIMUM_TASK_AUTHORITY.md +5 -5
  67. package/docs/PROJECT_MEMORY.md +1 -1
  68. package/docs/PUBLIC_SECURITY.md +14 -1
  69. package/docs/REPOSITORY_EDITING.md +1 -1
  70. package/docs/TOOL_POLICY.md +7 -1
  71. package/docs/VERTEX.md +2 -2
  72. package/docs/WEB_LIMITS.md +2 -2
  73. package/docs/adr/0002-isolated-mcp-stdio.md +2 -2
  74. package/evaluations/representative-assembly-matrix.json +37 -1
  75. package/examples/README.md +3 -3
  76. package/package.json +16 -15
@@ -2,7 +2,7 @@ import {
2
2
  CLI_OPTION_DEFINITIONS,
3
3
  resolveHelpTopic,
4
4
  validateCliCommandOptions
5
- } from "./cli-entry-3s63zn4w.js";
5
+ } from "./cli-entry-10th5awp.js";
6
6
  import {
7
7
  DEFAULT_PROVIDER_REGISTRY,
8
8
  HARNESS_EXECUTION_POLICY_VERSION,
@@ -3656,8 +3656,8 @@ var quarantineFileInputSchema = z10.strictObject({
3656
3656
  });
3657
3657
  var restoreFileInputSchema = z10.strictObject({
3658
3658
  quarantineId: z10.string().min(1).max(200).regex(/^[A-Za-z0-9._:-]+$/),
3659
- destination: workspaceFilePathSchema.optional(),
3660
- expectedDigest: fileDigestSchema.optional()
3659
+ destination: workspaceFilePathSchema.describe("Required workspace path for the quarantined bytes. Restore does not fall back to the original path."),
3660
+ expectedDigest: fileDigestSchema.describe("Required sha256 of the quarantined bytes the operator reviewed.")
3661
3661
  });
3662
3662
  var mutationOperationSchema = z10.enum(["create", "update", "move", "quarantine", "restore"]);
3663
3663
  var mutationAuditEntrySchema = z10.strictObject({
@@ -3994,13 +3994,62 @@ var runPortableProcess = async (command, options = {}) => {
3994
3994
  }
3995
3995
  };
3996
3996
 
3997
+ // src/workspace/text-integrity.ts
3998
+ var controlLabel = (code) => code === 0 ? "NUL byte" : `U+${code.toString(16).padStart(4, "0")}`;
3999
+ var firstDisallowedControl = (text) => {
4000
+ let line = 1;
4001
+ for (let index = 0;index < text.length; index += 1) {
4002
+ const code = text.charCodeAt(index);
4003
+ if (code === 10) {
4004
+ line += 1;
4005
+ continue;
4006
+ }
4007
+ if (code === 9 || code === 13)
4008
+ continue;
4009
+ if (code < 32 || code === 127 || code >= 128 && code <= 159)
4010
+ return { line, label: controlLabel(code) };
4011
+ }
4012
+ return;
4013
+ };
4014
+ var firstDisallowedControlBytes = (bytes) => {
4015
+ let line = 1;
4016
+ for (let index = 0;index < bytes.length; index += 1) {
4017
+ const code = bytes[index] ?? 0;
4018
+ if (code === 10) {
4019
+ line += 1;
4020
+ continue;
4021
+ }
4022
+ if (code === 9 || code === 13)
4023
+ continue;
4024
+ if (code < 32 || code === 127)
4025
+ return { line, label: controlLabel(code) };
4026
+ }
4027
+ return;
4028
+ };
4029
+ 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.`;
4030
+ var binaryAfterEditMessage = (hit) => `file is binary after edit: ${hit.label} at L${hit.line}. The edit was reverted. Read the file again before retrying.`;
4031
+ var textGuards = {
4032
+ beforeWrite(content, allowControlBytes) {
4033
+ if (allowControlBytes)
4034
+ return;
4035
+ const problem = firstDisallowedControl(content);
4036
+ if (problem)
4037
+ throw new HarnessWorkspaceError(refuseControlMessage(problem));
4038
+ },
4039
+ afterWrite(bytes, allowControlBytes) {
4040
+ if (allowControlBytes)
4041
+ return;
4042
+ return firstDisallowedControlBytes(bytes);
4043
+ }
4044
+ };
4045
+
3997
4046
  // src/workspace/path-policy.ts
3998
4047
  var HARD_IGNORES = new Set([".git", ".next", ".turbo", ".zhivex-harness", "coverage", "dist", "node_modules"]);
3999
4048
  var isSensitiveName = (name) => {
4000
- const normalized = name.toLocaleLowerCase();
4049
+ const normalized = name.toLowerCase();
4001
4050
  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));
4002
4051
  };
4003
- var isHardIgnored = (relativePath) => relativePath.split("/").some((segment) => HARD_IGNORES.has(segment.toLocaleLowerCase()) || isSensitiveName(segment));
4052
+ var isHardIgnored = (relativePath) => relativePath.split("/").some((segment) => HARD_IGNORES.has(segment.toLowerCase()) || isSensitiveName(segment));
4004
4053
 
4005
4054
  // src/workspace/workspace.ts
4006
4055
  var IGNORE_FILES = [".gitignore", ".zhivex-harnessignore"];
@@ -4280,7 +4329,7 @@ class Workspace {
4280
4329
  throw new HarnessWorkspaceError(`The path escapes the workspace: ${relativePath}`);
4281
4330
  const relative = wirePath(path11.relative(this.root, candidate));
4282
4331
  if (relative && isHardIgnored(relative)) {
4283
- const blocked = relative.split("/").find((segment) => HARD_IGNORES.has(segment.toLocaleLowerCase()) || isSensitiveName(segment));
4332
+ const blocked = relative.split("/").find((segment) => HARD_IGNORES.has(segment.toLowerCase()) || isSensitiveName(segment));
4284
4333
  throw new HarnessWorkspaceError(`The path is protected by the harness policy: ${blocked}`);
4285
4334
  }
4286
4335
  return candidate;
@@ -4323,8 +4372,11 @@ class Workspace {
4323
4372
  maxBytes: MAX_FILE_BYTES
4324
4373
  });
4325
4374
  const { contents } = file;
4326
- if (!allowBinary && contents.includes(0))
4327
- throw new HarnessWorkspaceError("The file appears to be binary and cannot be read as text.");
4375
+ if (!allowBinary && contents.includes(0)) {
4376
+ const hit = firstDisallowedControlBytes(contents);
4377
+ const where = hit ? `: ${hit.label} at L${hit.line}` : "";
4378
+ throw new HarnessWorkspaceError(`The file appears to be binary and cannot be read as text${where}.`);
4379
+ }
4328
4380
  return {
4329
4381
  path: wirePath(path11.relative(this.root, safe.path)),
4330
4382
  absolutePath: safe.path,
@@ -4991,19 +5043,23 @@ class Workspace {
4991
5043
  if (position < 0 || text.indexOf(edit.oldText, position + 1) >= 0) {
4992
5044
  throw new HarnessWorkspaceError("Replacement requires exactly one literal match; include more surrounding context.");
4993
5045
  }
4994
- return [{ path: edit.path, expectedDigest: edit.expectedDigest, content: text.slice(0, position) + edit.newText + text.slice(position + edit.oldText.length) }];
5046
+ return [{
5047
+ path: edit.path,
5048
+ expectedDigest: edit.expectedDigest,
5049
+ content: text.slice(0, position) + edit.newText + text.slice(position + edit.oldText.length)
5050
+ }];
4995
5051
  }
4996
- async applyReplacement(input) {
5052
+ async applyReplacement(input, host = {}) {
4997
5053
  const changes = await this.replacementChanges(input);
4998
- return this.applyPatch({ proposalId: createEditProposal({ changes }).proposalId, changes });
5054
+ return this.applyPatch({ proposalId: createEditProposal({ changes }).proposalId, changes }, host);
4999
5055
  }
5000
- async applyPatch(input) {
5001
- return this.applyPatchWithModes(input, new Map);
5056
+ async applyPatch(input, host = {}) {
5057
+ return this.applyPatchWithModes(input, new Map, undefined, host);
5002
5058
  }
5003
- async applyPatchWithModes(input, modes, assertActive) {
5004
- return withWorkspaceMutation(this.root, () => this.applyPatchWithModesLocked(input, modes, assertActive));
5059
+ async applyPatchWithModes(input, modes, assertActive, host = {}) {
5060
+ return withWorkspaceMutation(this.root, () => this.applyPatchWithModesLocked(input, modes, assertActive, host));
5005
5061
  }
5006
- async applyPatchWithModesLocked(input, modes, assertActive) {
5062
+ async applyPatchWithModesLocked(input, modes, assertActive, host = {}) {
5007
5063
  const proposal = validateEditProposal(input);
5008
5064
  this.checkAuditCapacity(proposal.changes.length);
5009
5065
  const targetPaths = new Set(proposal.changes.map((change) => change.path));
@@ -5048,6 +5104,7 @@ class Workspace {
5048
5104
  throw new HarnessWorkspaceError(`Stale patch mode rejected for ${change.path}: expected ${modeBinding.beforeMode.toString(8)}, found ${before.mode.toString(8)}.`);
5049
5105
  }
5050
5106
  }
5107
+ textGuards.beforeWrite(change.content, host.allowControlBytes === true);
5051
5108
  const temporary = await this.stageFile(safe.path, change.content, modeBinding?.afterMode ?? before?.mode ?? 420);
5052
5109
  prepared.push({
5053
5110
  change,
@@ -5087,6 +5144,13 @@ class Workspace {
5087
5144
  await unlink2(item.temporary);
5088
5145
  }
5089
5146
  committed.push(item);
5147
+ const published = await readRegularFileNoFollow(item.target, {
5148
+ label: "The published file",
5149
+ maxBytes: MAX_FILE_BYTES
5150
+ });
5151
+ const corrupt = textGuards.afterWrite(published.contents, host.allowControlBytes === true);
5152
+ if (corrupt)
5153
+ throw new HarnessWorkspaceError(binaryAfterEditMessage(corrupt));
5090
5154
  await assertActive?.();
5091
5155
  }
5092
5156
  } catch (error) {
@@ -5263,30 +5327,65 @@ class Workspace {
5263
5327
  const audit = await this.audit({ operation: "quarantine", path: parsed.path, beforeDigest: source.digest, quarantineId });
5264
5328
  return { quarantineId, path: parsed.path, digest: source.digest, audit };
5265
5329
  }
5266
- async restoreQuarantined(input) {
5267
- return withWorkspaceMutation(this.root, () => this.restoreQuarantinedLocked(input));
5268
- }
5269
- async restoreQuarantinedLocked(input) {
5270
- const parsed = restoreFileInputSchema.parse(input);
5271
- this.checkAuditCapacity();
5330
+ async readRestorableQuarantine(quarantineId) {
5272
5331
  const directory = await this.secureQuarantineDirectory(false);
5273
5332
  let manifest;
5274
5333
  try {
5275
- const manifestFile = await this.readInternalRegularFile(path11.join(directory, `${parsed.quarantineId}.json`), "Quarantine manifest", 64 * 1024);
5334
+ const manifestFile = await this.readInternalRegularFile(path11.join(directory, `${quarantineId}.json`), "Quarantine manifest", 64 * 1024);
5276
5335
  manifest = JSON.parse(manifestFile.contents.toString("utf8"));
5277
5336
  } catch {
5278
- throw new HarnessWorkspaceError(`Quarantine entry was not found: ${parsed.quarantineId}.`);
5337
+ throw new HarnessWorkspaceError(`Quarantine entry was not found: ${quarantineId}.`);
5279
5338
  }
5280
- if (manifest.schemaVersion !== 1 || manifest.quarantineId !== parsed.quarantineId || manifest.status !== "quarantined") {
5281
- throw new HarnessWorkspaceError(`Quarantine entry is not restorable: ${parsed.quarantineId}.`);
5339
+ if (manifest.schemaVersion !== 1 || manifest.quarantineId !== quarantineId || manifest.status !== "quarantined") {
5340
+ throw new HarnessWorkspaceError(`Quarantine entry is not restorable: ${quarantineId}.`);
5282
5341
  }
5283
- const dataPath = path11.join(directory, `${parsed.quarantineId}.data`);
5342
+ const dataPath = path11.join(directory, `${quarantineId}.data`);
5284
5343
  const dataFile = await this.readInternalRegularFile(dataPath, "Quarantine payload", MAX_FILE_BYTES);
5285
5344
  const digest3 = digestBytes(dataFile.contents);
5286
- if (digest3 !== manifest.digest || parsed.expectedDigest && parsed.expectedDigest !== digest3) {
5345
+ if (digest3 !== manifest.digest) {
5346
+ throw new HarnessWorkspaceError(`Quarantine digest mismatch: ${quarantineId}.`);
5347
+ }
5348
+ return { manifest, dataPath, dataFile, digest: digest3 };
5349
+ }
5350
+ async previewRestore(input) {
5351
+ const parsed = restoreFileInputSchema.parse(input);
5352
+ const quarantined = await this.readRestorableQuarantine(parsed.quarantineId);
5353
+ if (parsed.expectedDigest !== quarantined.digest) {
5287
5354
  throw new HarnessWorkspaceError(`Quarantine digest mismatch: ${parsed.quarantineId}.`);
5288
5355
  }
5289
- const destinationPath = parsed.destination ?? manifest.originalPath;
5356
+ const destination = await this.safePath(parsed.destination, { allowMissing: true });
5357
+ if (destination.exists)
5358
+ throw new HarnessWorkspaceError(`Restore destination already exists: ${parsed.destination}.`);
5359
+ let after;
5360
+ try {
5361
+ after = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(quarantined.dataFile.contents);
5362
+ } catch {
5363
+ throw new HarnessWorkspaceError("Preview requires valid UTF-8 text.");
5364
+ }
5365
+ return {
5366
+ proposalId: parsed.expectedDigest,
5367
+ files: [{
5368
+ path: parsed.destination,
5369
+ expectedDigest: parsed.expectedDigest,
5370
+ before: null,
5371
+ after,
5372
+ afterDigest: quarantined.digest,
5373
+ operation: "restore"
5374
+ }]
5375
+ };
5376
+ }
5377
+ async restoreQuarantined(input) {
5378
+ return withWorkspaceMutation(this.root, () => this.restoreQuarantinedLocked(input));
5379
+ }
5380
+ async restoreQuarantinedLocked(input) {
5381
+ const parsed = restoreFileInputSchema.parse(input);
5382
+ this.checkAuditCapacity();
5383
+ const quarantined = await this.readRestorableQuarantine(parsed.quarantineId);
5384
+ if (parsed.expectedDigest !== quarantined.digest) {
5385
+ throw new HarnessWorkspaceError(`Quarantine digest mismatch: ${parsed.quarantineId}.`);
5386
+ }
5387
+ const { manifest, dataPath, dataFile, digest: digest3 } = quarantined;
5388
+ const destinationPath = parsed.destination;
5290
5389
  const destination = await this.safePath(destinationPath, { allowMissing: true });
5291
5390
  if (destination.exists)
5292
5391
  throw new HarnessWorkspaceError(`Restore destination already exists: ${destinationPath}.`);
@@ -6511,7 +6610,7 @@ var importPatch = async (runId, expectedPatchId, host, base, current2, maxFileWr
6511
6610
  const rollbackErrors = [];
6512
6611
  for (const item of [...quarantined].reverse()) {
6513
6612
  try {
6514
- await host.restoreQuarantined({ quarantineId: item.quarantineId, expectedDigest: item.digest });
6613
+ await host.restoreQuarantined({ quarantineId: item.quarantineId, destination: item.path, expectedDigest: item.digest });
6515
6614
  } catch (rollbackError) {
6516
6615
  rollbackErrors.push(rollbackError);
6517
6616
  }
@@ -7885,6 +7984,79 @@ import { createAgentRunLedger } from "@zhivex-ai/agents/control-plane";
7885
7984
  import { AsyncLocalStorage as AsyncLocalStorage5 } from "node:async_hooks";
7886
7985
  import { randomUUID as randomUUID7 } from "node:crypto";
7887
7986
  import { z as z17 } from "zod";
7987
+
7988
+ // src/runtime/budget-ledger-scope.ts
7989
+ var BUDGET_LEDGER_NAMESPACE = "__zhivex_budget__";
7990
+ var BUDGET_OWNER_NAMESPACE_KEY = "budgetOwnerNamespace";
7991
+ var isReservedBudgetNamespace = (namespace) => namespace === BUDGET_LEDGER_NAMESPACE || (namespace?.startsWith(`${BUDGET_LEDGER_NAMESPACE}:`) ?? false);
7992
+ var budgetLedgerNamespace = (namespace) => {
7993
+ if (!namespace || isReservedBudgetNamespace(namespace)) {
7994
+ throw new HarnessStateConflictError("The budget ledger namespace is reserved.");
7995
+ }
7996
+ return `${BUDGET_LEDGER_NAMESPACE}:${namespace}`;
7997
+ };
7998
+ var budgetLedgerScope = (scope) => {
7999
+ if (scope.namespace && isReservedBudgetNamespace(scope.namespace)) {
8000
+ throw new HarnessStateConflictError("The budget ledger namespace is reserved.");
8001
+ }
8002
+ return {
8003
+ tenantId: scope.tenantId,
8004
+ ...scope.userId ? { userId: scope.userId } : {},
8005
+ namespace: scope.namespace ? budgetLedgerNamespace(scope.namespace) : `${BUDGET_LEDGER_NAMESPACE}:`
8006
+ };
8007
+ };
8008
+ var sameIdentity = (left, right) => left?.tenantId === right.tenantId && (left.userId ?? undefined) === (right.userId ?? undefined);
8009
+ var bindBudgetLedgerStore = (primary, scope, budget = primary) => {
8010
+ const bound = budgetLedgerScope(scope);
8011
+ const ownerNamespace = scope.namespace ?? "";
8012
+ const isSdkBudget = (value) => sameIdentity(value, bound) && value?.namespace === BUDGET_LEDGER_NAMESPACE;
8013
+ const isBoundBudget = (value) => sameIdentity(value, bound) && value?.namespace === bound.namespace;
8014
+ const assertId = (id) => {
8015
+ if (!/^budget_[a-f0-9]{64}$/.test(id))
8016
+ throw new HarnessStateConflictError("Invalid budget ledger identity.");
8017
+ };
8018
+ const bindState = (state) => {
8019
+ const metadata = { ...state.metadata ?? {} };
8020
+ const owner = metadata[BUDGET_OWNER_NAMESPACE_KEY];
8021
+ if (owner !== undefined && owner !== ownerNamespace) {
8022
+ throw new HarnessStateConflictError("Budget ledger is bound to another namespace.");
8023
+ }
8024
+ return {
8025
+ ...state,
8026
+ scope: bound,
8027
+ metadata: { ...metadata, [BUDGET_OWNER_NAMESPACE_KEY]: ownerNamespace }
8028
+ };
8029
+ };
8030
+ const assertStored = (state) => {
8031
+ if (!state)
8032
+ return state;
8033
+ const owner = state.metadata?.[BUDGET_OWNER_NAMESPACE_KEY];
8034
+ if (state.scope?.namespace !== bound.namespace || owner !== ownerNamespace) {
8035
+ throw new HarnessStateConflictError("Budget ledger is not bound to this namespace.");
8036
+ }
8037
+ return state;
8038
+ };
8039
+ return {
8040
+ ...primary,
8041
+ load: async (id, requestedScope) => {
8042
+ if (!isSdkBudget(requestedScope) && !isBoundBudget(requestedScope))
8043
+ return primary.load(id, requestedScope);
8044
+ assertId(id);
8045
+ return assertStored(await budget.load(id, bound));
8046
+ },
8047
+ save: async (state, options) => {
8048
+ if (!isSdkBudget(state.scope) && !isBoundBudget(state.scope))
8049
+ return primary.save(state, options);
8050
+ assertId(state.runId);
8051
+ if (state.provider !== "zhivex" || state.modelId !== "budget-coordinator" || state.metadata?.budgetCoordinator !== true) {
8052
+ throw new HarnessStateConflictError("Only SDK budget ledger states may use the reserved namespace.");
8053
+ }
8054
+ return budget.save(bindState(state), options);
8055
+ }
8056
+ };
8057
+ };
8058
+
8059
+ // src/runtime/task-budget.ts
7888
8060
  import {
7889
8061
  createAgentBudgetCoordinator,
7890
8062
  fingerprintAgentHarness as fingerprintAgentHarness2,
@@ -8003,10 +8175,10 @@ class TaskBudget {
8003
8175
  this.accountRunId = TaskBudget.accountId(scope, taskId);
8004
8176
  this.policy = structuredClone(account.policy);
8005
8177
  const budgetId = this.accountRunId;
8006
- this.coordinator = createAgentBudgetCoordinator({ store, scope, budgetId, limits: this.policy.limits });
8178
+ this.coordinator = createAgentBudgetCoordinator({ store: bindBudgetLedgerStore(store, scope), scope, budgetId, limits: this.policy.limits });
8007
8179
  this.coordinatorId = this.coordinator.id;
8008
8180
  this.budgetRunId = `budget_${fingerprintAgentHarness2({ budgetId, scope }).slice("sha256:".length)}`;
8009
- this.budgetScope = { tenantId: scope.tenantId, ...scope.userId ? { userId: scope.userId } : {}, namespace: "__zhivex_budget__" };
8181
+ this.budgetScope = budgetLedgerScope(scope);
8010
8182
  }
8011
8183
  static accountId(scope, taskId) {
8012
8184
  return `task_budget_${fingerprintAgentHarness2({ scope, taskId }).slice("sha256:".length)}`;
@@ -8521,37 +8693,7 @@ class TaskBudget {
8521
8693
  }
8522
8694
 
8523
8695
  // src/persistence/operations.ts
8524
- var budgetScopeFor = (config) => ({
8525
- tenantId: config.scope.tenantId,
8526
- ...config.scope.userId ? { userId: config.scope.userId } : {},
8527
- namespace: "__zhivex_budget__"
8528
- });
8529
- var withBudgetLedgerStore = (primary, budget, config) => {
8530
- const scope = budgetScopeFor(config);
8531
- const budgetScope = (value) => value?.namespace === scope.namespace && value.tenantId === scope.tenantId && (value.userId ?? undefined) === (scope.userId ?? undefined);
8532
- const assertId = (id) => {
8533
- if (!/^budget_[a-f0-9]{64}$/.test(id))
8534
- throw new HarnessStateConflictError("Invalid budget ledger identity.");
8535
- };
8536
- return {
8537
- ...primary,
8538
- load: async (id, requestedScope) => {
8539
- if (!budgetScope(requestedScope))
8540
- return primary.load(id, requestedScope);
8541
- assertId(id);
8542
- return budget.load(id, requestedScope);
8543
- },
8544
- save: async (state, options) => {
8545
- if (!budgetScope(state.scope))
8546
- return primary.save(state, options);
8547
- assertId(state.runId);
8548
- if (state.provider !== "zhivex" || state.modelId !== "budget-coordinator" || state.metadata?.budgetCoordinator !== true) {
8549
- throw new HarnessStateConflictError("Only SDK budget ledger states may use the reserved namespace.");
8550
- }
8551
- return budget.save(state, options);
8552
- }
8553
- };
8554
- };
8696
+ var withBudgetLedgerStore = (primary, budget, config) => bindBudgetLedgerStore(primary, config.scope, budget);
8555
8697
  var HARNESS_SQLITE_FILE = "operations.sqlite";
8556
8698
  var HARNESS_OPERATIONS_SCHEMA_VERSION = 1;
8557
8699
  var TERMINAL_RUN_STATUSES = ["completed", "failed", "cancelled", "timed_out"];
@@ -8806,13 +8948,12 @@ var migrateLegacyFileRuns = async (directory, target, config) => {
8806
8948
  return result;
8807
8949
  };
8808
8950
  var openHarnessPersistence = async (config, options = {}) => {
8809
- if (config.scope.namespace === "__zhivex_budget__")
8810
- throw new HarnessStateConflictError("The budget ledger namespace is reserved.");
8951
+ const budgetScope = budgetLedgerScope(config.scope);
8811
8952
  await validateStateDirectory(config.workspace, config.stateDirectory);
8812
8953
  await ensurePrivateStateDirectory(config.stateDirectory);
8813
8954
  await protectStateFromGit(config.workspace, config.stateDirectory);
8814
8955
  if (config.storeBackend === "file") {
8815
- const store = withBudgetLedgerStore(createFileAgentRunStore({ directory: config.stateDirectory, scope: config.scope }), createFileAgentRunStore({ directory: config.stateDirectory, scope: budgetScopeFor(config) }), config);
8956
+ const store = withBudgetLedgerStore(createFileAgentRunStore({ directory: config.stateDirectory, scope: config.scope }), createFileAgentRunStore({ directory: config.stateDirectory, scope: budgetScope }), config);
8816
8957
  const migration = shouldMigrateLegacyRuns(config, options.migrateLegacyFileStore) ? await migrateLegacyFileRuns(config.stateDirectory, store, config) : emptyMigration();
8817
8958
  return {
8818
8959
  backend: "file",
@@ -8857,7 +8998,7 @@ var openHarnessPersistence = async (config, options = {}) => {
8857
8998
  database.exec("PRAGMA foreign_keys = ON");
8858
8999
  await chmod3(databasePath, 384);
8859
9000
  const databaseLike = sqliteAdapter(database);
8860
- const store = withBudgetLedgerStore(createSqliteAgentRunStore({ db: databaseLike, scope: config.scope, history: "incremental" }), createSqliteAgentRunStore({ db: databaseLike, scope: budgetScopeFor(config) }), config);
9001
+ const store = withBudgetLedgerStore(createSqliteAgentRunStore({ db: databaseLike, scope: config.scope, history: "incremental" }), createSqliteAgentRunStore({ db: databaseLike, scope: budgetScope }), config);
8861
9002
  database.exec(`CREATE TRIGGER IF NOT EXISTS zhivex_task_budget_retain_run
8862
9003
  BEFORE DELETE ON zhivex_agent_runs
8863
9004
  WHEN json_type(OLD.state_json, '$.metadata.zhivexTaskBudgetAccountV1') IS NOT NULL
@@ -9987,4 +10128,4 @@ var verifyChangeEnvelope = (input, options = {}) => {
9987
10128
  };
9988
10129
  };
9989
10130
 
9990
- export { UnsafeFileTypeError, FileSizeLimitError, FileChangedWhileReadingError, statRegularFileNoFollow, readRegularFileNoFollow, readRegularFileChunksNoFollow, loadCliProfile, createCliProfile, updateCliProfile, applyCliProfile, resolveCliDefaults, parseHarnessModelRoute, resolveHarnessModelRoutes, createHarnessRouteModels, serializeHarnessModelRoutes, CLI_COMMANDS, CLI_RUNS_COMMANDS, CLI_SESSIONS_COMMANDS, CLI_CHANGES_COMMANDS, CLI_STATE_COMMANDS, CliUsageError, parseCliArgs, SqliteDatabase, validateStateDirectory, HARNESS_SESSION_SCHEMA_VERSION, TERMINAL_SESSION_RUN_STATUSES, ensurePrivateDatabase, openCliSessionStore, inspectRuntimeDiagnostics, inspectRuntimeManifest, estimateContextTokens, measureContext, MODEL_BUDGET_KEY, estimateRequestTokens, workBudgetReached, createModelBudget, USAGE_LEDGER_KEY, usagePricingSchema, inspectUsageLedger, formatUsageLedger, runUsageLedgerWithPolicy, UsageLedger, inspectTaskMonetaryUsage, CLI_JSON_SCHEMA_VERSION, CLI_EVENT_SCHEMA_VERSION, serializeStreamEvent, serializeStreamResult, MAX_EDIT_CHANGES, MAX_EDIT_FILE_BYTES, fileDigestSchema, workspaceFilePathSchema, editProposalInputSchema, applyEditProposalInputSchema, moveFileInputSchema, quarantineFileInputSchema, restoreFileInputSchema, createEditProposal, validateEditProposal, editContractDocument, APPROVAL_HISTORY_KEY, approvalInputDigest, readApprovalDecisions, approvalDecisionViews, createHarnessToolPolicy, applyHarnessToolPolicy, bindHarnessPolicyInspection, inspectHarnessPolicy, withRuntimeInstruction, withFreshSystemInstructions, TASK_BUDGET_KEY, TASK_BUDGET_ACCOUNT_KEY, inspectTaskBudgetSummary, assertTaskBudgetBackupLinks, TaskBudget, compileTaskAcceptanceContract, taskCheckReceiptSchema, createTaskAcceptanceChecks, withTaskAcceptanceDelivery, taskAcceptanceChecks, confirmTaskAcceptanceImport, TASK_ACCEPTANCE_KEY, TASK_ACCEPTANCE_EVIDENCE_KEY, readTaskAcceptanceLedger, nextTaskAcceptanceLedger, taskAcceptanceCheckpointStore, TASK_SOURCE_KEY, ASSISTANT_RESPONSE_KEY, assistantResponses, captureAssistantResponses, taskSources, captureTaskSources, createTaskTools, replacementEditSchema, EnvironmentPatchDriftError, harnessToolExecution, effectiveRuntimeBudget, createRuntimeBudget, runtimeManifest, childRuntimeSafety, createCheckpointTokenCap, withWorkspaceMutation, boundedBatches, resolvePackageManager, resolvePackageCheckCommand, runPortableProcess, isHardIgnored, Workspace, CliOciRuntimeAdapter, harnessExecutionSession, createHarnessOciExecutionEnvironment, cleanupHarnessExecutionArtifacts, executionFingerprintInput, createRepairController, createRepairProgress, RUNTIME_DIAGNOSTICS_KEY, assistantResponseCheckpointStore, runtimeCheckpointStore, tokenUsageCheckpointStore, HARNESS_SQLITE_FILE, HARNESS_OPERATIONS_SCHEMA_VERSION, listHarnessRuns, inspectHarnessRun, cancelHarnessRun, cleanupHarnessRuns, sqliteAdapter, openHarnessPersistence, harnessPolicyDecisionEventSchema, observeHarnessPolicyDecisions, publishHarnessPolicyDecision, observeBaselineToolPolicy, publishPendingPolicyDecision, createPolicyDecisionEvidence, changeEnvelopeSchema, digestChangeEnvelopeArtifact, createChangeEnvelope, canonicalizeChangeEnvelope, changeEnvelopePreconditionsSchema, verifyChangeEnvelope, openHarnessActivityStore };
10131
+ export { UnsafeFileTypeError, FileSizeLimitError, FileChangedWhileReadingError, statRegularFileNoFollow, readRegularFileNoFollow, readRegularFileChunksNoFollow, loadCliProfile, createCliProfile, updateCliProfile, applyCliProfile, resolveCliDefaults, parseHarnessModelRoute, resolveHarnessModelRoutes, createHarnessRouteModels, serializeHarnessModelRoutes, CLI_COMMANDS, CLI_RUNS_COMMANDS, CLI_SESSIONS_COMMANDS, CLI_CHANGES_COMMANDS, CLI_STATE_COMMANDS, CliUsageError, parseCliArgs, SqliteDatabase, validateStateDirectory, HARNESS_SESSION_SCHEMA_VERSION, TERMINAL_SESSION_RUN_STATUSES, ensurePrivateDatabase, openCliSessionStore, inspectRuntimeDiagnostics, inspectRuntimeManifest, estimateContextTokens, measureContext, MODEL_BUDGET_KEY, estimateRequestTokens, workBudgetReached, createModelBudget, USAGE_LEDGER_KEY, usagePricingSchema, inspectUsageLedger, formatUsageLedger, runUsageLedgerWithPolicy, UsageLedger, inspectTaskMonetaryUsage, CLI_JSON_SCHEMA_VERSION, CLI_EVENT_SCHEMA_VERSION, serializeStreamEvent, serializeStreamResult, MAX_EDIT_CHANGES, MAX_EDIT_FILE_BYTES, fileDigestSchema, workspaceFilePathSchema, editProposalInputSchema, applyEditProposalInputSchema, moveFileInputSchema, quarantineFileInputSchema, restoreFileInputSchema, createEditProposal, validateEditProposal, editContractDocument, APPROVAL_HISTORY_KEY, approvalInputDigest, readApprovalDecisions, approvalDecisionViews, createHarnessToolPolicy, applyHarnessToolPolicy, bindHarnessPolicyInspection, inspectHarnessPolicy, BUDGET_LEDGER_NAMESPACE, BUDGET_OWNER_NAMESPACE_KEY, isReservedBudgetNamespace, budgetLedgerNamespace, withRuntimeInstruction, withFreshSystemInstructions, TASK_BUDGET_KEY, TASK_BUDGET_ACCOUNT_KEY, inspectTaskBudgetSummary, assertTaskBudgetBackupLinks, TaskBudget, compileTaskAcceptanceContract, taskCheckReceiptSchema, createTaskAcceptanceChecks, withTaskAcceptanceDelivery, taskAcceptanceChecks, confirmTaskAcceptanceImport, TASK_ACCEPTANCE_KEY, TASK_ACCEPTANCE_EVIDENCE_KEY, readTaskAcceptanceLedger, nextTaskAcceptanceLedger, taskAcceptanceCheckpointStore, TASK_SOURCE_KEY, ASSISTANT_RESPONSE_KEY, assistantResponses, captureAssistantResponses, taskSources, captureTaskSources, createTaskTools, replacementEditSchema, EnvironmentPatchDriftError, harnessToolExecution, effectiveRuntimeBudget, createRuntimeBudget, runtimeManifest, childRuntimeSafety, createCheckpointTokenCap, withWorkspaceMutation, boundedBatches, resolvePackageManager, resolvePackageCheckCommand, runPortableProcess, isHardIgnored, Workspace, CliOciRuntimeAdapter, harnessExecutionSession, createHarnessOciExecutionEnvironment, cleanupHarnessExecutionArtifacts, executionFingerprintInput, createRepairController, createRepairProgress, RUNTIME_DIAGNOSTICS_KEY, assistantResponseCheckpointStore, runtimeCheckpointStore, tokenUsageCheckpointStore, HARNESS_SQLITE_FILE, HARNESS_OPERATIONS_SCHEMA_VERSION, listHarnessRuns, inspectHarnessRun, cancelHarnessRun, cleanupHarnessRuns, sqliteAdapter, openHarnessPersistence, harnessPolicyDecisionEventSchema, observeHarnessPolicyDecisions, publishHarnessPolicyDecision, observeBaselineToolPolicy, publishPendingPolicyDecision, createPolicyDecisionEvidence, changeEnvelopeSchema, digestChangeEnvelopeArtifact, createChangeEnvelope, canonicalizeChangeEnvelope, changeEnvelopePreconditionsSchema, verifyChangeEnvelope, openHarnessActivityStore };
@@ -10,10 +10,10 @@ import {
10
10
  readTaskAcceptanceLedger,
11
11
  taskCheckReceiptSchema,
12
12
  verifyChangeEnvelope
13
- } from "./cli-entry-7f0cen32.js";
13
+ } from "./cli-entry-6amx4mcc.js";
14
14
  import {
15
15
  HARNESS_VERSION
16
- } from "./cli-entry-3s63zn4w.js";
16
+ } from "./cli-entry-10th5awp.js";
17
17
 
18
18
  // src/persistence/governance-report.ts
19
19
  import { createHash as createHash2 } from "node:crypto";
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  HARNESS_VERSION,
4
4
  NODE_ENGINE_RANGE,
5
5
  shortCliHelp
6
- } from "./cli-entry-3s63zn4w.js";
6
+ } from "./cli-entry-10th5awp.js";
7
7
  import {
8
8
  __require
9
9
  } from "./cli-entry-1bx83sw4.js";
@@ -41,6 +41,6 @@ if (argv.length === 1 && ["--version", "-v", "version"].includes(argv[0])) {
41
41
  process.stdout.write(`${shortCliHelp(HARNESS_VERSION)}
42
42
  `);
43
43
  } else {
44
- const { runCli } = await import("./cli-1cf4dcnj.js");
44
+ const { runCli } = await import("./cli-84en174d.js");
45
45
  await runCli(argv);
46
46
  }
@@ -13,18 +13,18 @@ import {
13
13
  readHarnessTaskDraft,
14
14
  requestHarnessTaskCancellation,
15
15
  runHarnessTask
16
- } from "../index-0zs2dz26.js";
16
+ } from "../index-pf73y4gt.js";
17
17
  import {
18
18
  TASK_SOURCE_KEY,
19
19
  harnessToolExecution,
20
20
  taskSources
21
- } from "../index-f6mqa5gk.js";
22
- import"../index-dtb2c6ds.js";
21
+ } from "../index-amjttjaj.js";
22
+ import"../index-2r4f0p4n.js";
23
23
  import {
24
24
  resolvePackageCheckCommand,
25
25
  resolvePackageManager,
26
26
  runPortableProcess
27
- } from "../index-d9zkmhz3.js";
27
+ } from "../index-w0m1nckc.js";
28
28
  import {
29
29
  DEFAULT_HARNESS_MAX_STEPS,
30
30
  modelReasoningEfforts,
@@ -36,7 +36,7 @@ import {
36
36
  catalogModels,
37
37
  modelDescription
38
38
  } from "../index-mzydbzp8.js";
39
- import"../index-wm9xeq1n.js";
39
+ import"../index-bt2p8x7r.js";
40
40
  import {
41
41
  TASK_ACCEPTANCE_EVIDENCE_KEY,
42
42
  TASK_BUDGET_KEY,
@@ -46,15 +46,15 @@ import {
46
46
  inspectTaskBudgetSummary,
47
47
  inspectUsageLedger,
48
48
  usagePricingSchema
49
- } from "../index-zxg4ve4j.js";
50
- import"../index-4fx5f3k5.js";
49
+ } from "../index-fqqz1yt8.js";
50
+ import"../index-rz7wmp71.js";
51
51
  import {
52
52
  FileChangedWhileReadingError,
53
53
  FileSizeLimitError,
54
54
  SqliteDatabase,
55
55
  UnsafeFileTypeError,
56
56
  readRegularFileNoFollow
57
- } from "../index-xdbkcccf.js";
57
+ } from "../index-6dxrbnxm.js";
58
58
  import"../index-bpc596v3.js";
59
59
  import"../index-fm84men1.js";
60
60
  import"../index-1bx83sw4.js";
@@ -2,7 +2,7 @@ import {
2
2
  discardMcpWorkspaceSnapshot,
3
3
  launchDockerMcpGroup,
4
4
  prepareMcpWorkspaceSnapshot
5
- } from "../index-mv8sb1ns.js";
5
+ } from "../index-1pn0ecs4.js";
6
6
  import {
7
7
  createAcpConnection,
8
8
  issueAcpMcpSessionProvider,
@@ -10,21 +10,21 @@ import {
10
10
  } from "../index-3ah1f369.js";
11
11
  import {
12
12
  createHarnessClientAdapter
13
- } from "../index-02pqtbbg.js";
14
- import"../index-6599mpmy.js";
13
+ } from "../index-paf7z8kn.js";
14
+ import"../index-4mn6m3bq.js";
15
15
  import {
16
16
  createHarness
17
- } from "../index-0zs2dz26.js";
18
- import"../index-f6mqa5gk.js";
19
- import"../index-dtb2c6ds.js";
20
- import"../index-d9zkmhz3.js";
17
+ } from "../index-pf73y4gt.js";
18
+ import"../index-amjttjaj.js";
19
+ import"../index-2r4f0p4n.js";
20
+ import"../index-w0m1nckc.js";
21
21
  import"../index-1265j7t2.js";
22
22
  import"../index-c39197wq.js";
23
23
  import"../index-mzydbzp8.js";
24
- import"../index-wm9xeq1n.js";
25
- import"../index-zxg4ve4j.js";
26
- import"../index-4fx5f3k5.js";
27
- import"../index-xdbkcccf.js";
24
+ import"../index-bt2p8x7r.js";
25
+ import"../index-fqqz1yt8.js";
26
+ import"../index-rz7wmp71.js";
27
+ import"../index-6dxrbnxm.js";
28
28
  import"../index-4gt045j0.js";
29
29
  import"../index-bpc596v3.js";
30
30
  import"../index-fm84men1.js";
@@ -2,19 +2,19 @@ import {
2
2
  createHarnessClientAdapter,
3
3
  runResultDocument,
4
4
  terminalContinuationMessages
5
- } from "../index-02pqtbbg.js";
6
- import"../index-6599mpmy.js";
7
- import"../index-0zs2dz26.js";
8
- import"../index-f6mqa5gk.js";
9
- import"../index-dtb2c6ds.js";
10
- import"../index-d9zkmhz3.js";
5
+ } from "../index-paf7z8kn.js";
6
+ import"../index-4mn6m3bq.js";
7
+ import"../index-pf73y4gt.js";
8
+ import"../index-amjttjaj.js";
9
+ import"../index-2r4f0p4n.js";
10
+ import"../index-w0m1nckc.js";
11
11
  import"../index-1265j7t2.js";
12
12
  import"../index-c39197wq.js";
13
13
  import"../index-mzydbzp8.js";
14
- import"../index-wm9xeq1n.js";
15
- import"../index-zxg4ve4j.js";
16
- import"../index-4fx5f3k5.js";
17
- import"../index-xdbkcccf.js";
14
+ import"../index-bt2p8x7r.js";
15
+ import"../index-fqqz1yt8.js";
16
+ import"../index-rz7wmp71.js";
17
+ import"../index-6dxrbnxm.js";
18
18
  import {
19
19
  HARNESS_CLIENT_PROTOCOL_VERSION,
20
20
  harnessClientCommandSchema,
@@ -2,18 +2,18 @@ import {
2
2
  createArchivedHarnessStateBackup,
3
3
  createHarnessStateBackup,
4
4
  validateRecordedWorkspace
5
- } from "../index-74kkpyqk.js";
6
- import"../index-6599mpmy.js";
5
+ } from "../index-rgqzdgv6.js";
6
+ import"../index-4mn6m3bq.js";
7
7
  import {
8
8
  HARNESS_SQLITE_FILE
9
- } from "../index-f6mqa5gk.js";
10
- import"../index-d9zkmhz3.js";
9
+ } from "../index-amjttjaj.js";
10
+ import"../index-w0m1nckc.js";
11
11
  import"../index-1265j7t2.js";
12
12
  import"../index-c39197wq.js";
13
13
  import"../index-mzydbzp8.js";
14
14
  import {
15
15
  validateStateDirectory
16
- } from "../index-zxg4ve4j.js";
16
+ } from "../index-fqqz1yt8.js";
17
17
  import {
18
18
  SqliteDatabase,
19
19
  acquireSqliteAccess,
@@ -22,7 +22,7 @@ import {
22
22
  protectPersistenceSecret,
23
23
  readRegularFileNoFollow,
24
24
  statRegularFileNoFollow
25
- } from "../index-xdbkcccf.js";
25
+ } from "../index-6dxrbnxm.js";
26
26
  import"../index-bpc596v3.js";
27
27
  import"../index-fm84men1.js";
28
28
  import"../index-1bx83sw4.js";