@zhivex-ai/harness 1.4.1 → 1.5.0
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 +3 -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/audit/index.js +6 -6
- package/dist/{audit-pack-e4vaq7np.js → audit-pack-kvw7ng34.js} +3 -3
- package/dist/{cli-entry-rgs6bxmy.js → cli-entry-75j3r49b.js} +2 -2
- package/dist/{cli-entry-7f0cen32.js → cli-entry-928p8h41.js} +135 -58
- package/dist/{cli-entry-a890x3kq.js → cli-entry-h2yc9x37.js} +7 -5
- package/dist/{cli-entry-3s63zn4w.js → cli-entry-q8bgkn9m.js} +1 -1
- package/dist/{cli-1cf4dcnj.js → cli-vyrytn3m.js} +14 -8
- 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 +138 -60
- package/dist/{index-dtb2c6ds.js → index-5zbq9ztj.js} +2 -2
- package/dist/{index-6599mpmy.js → index-63gbp0cz.js} +3 -3
- package/dist/{index-xdbkcccf.js → index-6dxrbnxm.js} +2 -2
- package/dist/{index-0zs2dz26.js → index-6fp7kdpx.js} +10 -9
- package/dist/{index-mv8sb1ns.js → index-96ezrvg2.js} +55 -13
- 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-d9zkmhz3.js → index-ggstwr0v.js} +53 -18
- package/dist/{index-j6zxnc8m.js → index-jpdvn09j.js} +5 -5
- package/dist/{index-4fx5f3k5.js → index-pq3kz1r8.js} +1 -1
- package/dist/{index-02pqtbbg.js → index-vc7yxz50.js} +10 -10
- package/dist/{index-crta5wzv.js → index-vvzc5ezz.js} +4 -4
- package/dist/{index-f6mqa5gk.js → index-wwthv10a.js} +11 -39
- package/dist/{index-74kkpyqk.js → index-xfedewpt.js} +12 -6
- package/dist/{index-sstgcsr0.js → index-yx52bt4a.js} +1 -1
- package/dist/index.js +14 -14
- package/dist/integrations/mcp-stdio-tools.d.ts +4 -1
- 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-v6qptg9x.js} +4 -4
- package/dist/tools/workspace.d.ts +2 -2
- package/dist/verify-historical-migrations.js +143 -62
- package/dist/workspace/edit-contracts.d.ts +2 -2
- package/dist/workspace/path-policy.d.ts +2 -1
- package/dist/workspace/workspace.d.ts +13 -0
- package/dist/zhx.js +2 -2
- package/docs/CODE_RELEASE.md +28 -6
- package/docs/CODE_WEB_TASKS.md +1 -1
- package/docs/ENGINE_API.md +1 -1
- package/docs/EXTENSIBILITY.md +6 -2
- package/docs/FIRST_USE.md +6 -6
- 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 +4 -1
- package/docs/adr/0002-isolated-mcp-stdio.md +1 -1
- package/evaluations/representative-assembly-matrix.json +10 -1
- package/examples/README.md +3 -3
- package/package.json +1 -1
|
@@ -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({
|
|
@@ -4157,10 +4157,10 @@ var runPortableProcess = async (command, options = {}) => {
|
|
|
4157
4157
|
// src/workspace/path-policy.ts
|
|
4158
4158
|
var HARD_IGNORES = new Set([".git", ".next", ".turbo", ".zhivex-harness", "coverage", "dist", "node_modules"]);
|
|
4159
4159
|
var isSensitiveName = (name) => {
|
|
4160
|
-
const normalized = name.
|
|
4160
|
+
const normalized = name.toLowerCase();
|
|
4161
4161
|
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
4162
|
};
|
|
4163
|
-
var isHardIgnored = (relativePath) => relativePath.split("/").some((segment) => HARD_IGNORES.has(segment.
|
|
4163
|
+
var isHardIgnored = (relativePath) => relativePath.split("/").some((segment) => HARD_IGNORES.has(segment.toLowerCase()) || isSensitiveName(segment));
|
|
4164
4164
|
|
|
4165
4165
|
// src/workspace/workspace.ts
|
|
4166
4166
|
var IGNORE_FILES = [".gitignore", ".zhivex-harnessignore"];
|
|
@@ -4440,7 +4440,7 @@ class Workspace {
|
|
|
4440
4440
|
throw new HarnessWorkspaceError(`The path escapes the workspace: ${relativePath}`);
|
|
4441
4441
|
const relative = wirePath(path8.relative(this.root, candidate));
|
|
4442
4442
|
if (relative && isHardIgnored(relative)) {
|
|
4443
|
-
const blocked = relative.split("/").find((segment) => HARD_IGNORES.has(segment.
|
|
4443
|
+
const blocked = relative.split("/").find((segment) => HARD_IGNORES.has(segment.toLowerCase()) || isSensitiveName(segment));
|
|
4444
4444
|
throw new HarnessWorkspaceError(`The path is protected by the harness policy: ${blocked}`);
|
|
4445
4445
|
}
|
|
4446
4446
|
return candidate;
|
|
@@ -5423,30 +5423,65 @@ class Workspace {
|
|
|
5423
5423
|
const audit = await this.audit({ operation: "quarantine", path: parsed.path, beforeDigest: source.digest, quarantineId });
|
|
5424
5424
|
return { quarantineId, path: parsed.path, digest: source.digest, audit };
|
|
5425
5425
|
}
|
|
5426
|
-
async
|
|
5427
|
-
return withWorkspaceMutation(this.root, () => this.restoreQuarantinedLocked(input));
|
|
5428
|
-
}
|
|
5429
|
-
async restoreQuarantinedLocked(input) {
|
|
5430
|
-
const parsed = restoreFileInputSchema.parse(input);
|
|
5431
|
-
this.checkAuditCapacity();
|
|
5426
|
+
async readRestorableQuarantine(quarantineId) {
|
|
5432
5427
|
const directory = await this.secureQuarantineDirectory(false);
|
|
5433
5428
|
let manifest;
|
|
5434
5429
|
try {
|
|
5435
|
-
const manifestFile = await this.readInternalRegularFile(path8.join(directory, `${
|
|
5430
|
+
const manifestFile = await this.readInternalRegularFile(path8.join(directory, `${quarantineId}.json`), "Quarantine manifest", 64 * 1024);
|
|
5436
5431
|
manifest = JSON.parse(manifestFile.contents.toString("utf8"));
|
|
5437
5432
|
} catch {
|
|
5438
|
-
throw new HarnessWorkspaceError(`Quarantine entry was not found: ${
|
|
5433
|
+
throw new HarnessWorkspaceError(`Quarantine entry was not found: ${quarantineId}.`);
|
|
5439
5434
|
}
|
|
5440
|
-
if (manifest.schemaVersion !== 1 || manifest.quarantineId !==
|
|
5441
|
-
throw new HarnessWorkspaceError(`Quarantine entry is not restorable: ${
|
|
5435
|
+
if (manifest.schemaVersion !== 1 || manifest.quarantineId !== quarantineId || manifest.status !== "quarantined") {
|
|
5436
|
+
throw new HarnessWorkspaceError(`Quarantine entry is not restorable: ${quarantineId}.`);
|
|
5442
5437
|
}
|
|
5443
|
-
const dataPath = path8.join(directory, `${
|
|
5438
|
+
const dataPath = path8.join(directory, `${quarantineId}.data`);
|
|
5444
5439
|
const dataFile = await this.readInternalRegularFile(dataPath, "Quarantine payload", MAX_FILE_BYTES);
|
|
5445
5440
|
const digest2 = digestBytes(dataFile.contents);
|
|
5446
|
-
if (digest2 !== manifest.digest
|
|
5441
|
+
if (digest2 !== manifest.digest) {
|
|
5442
|
+
throw new HarnessWorkspaceError(`Quarantine digest mismatch: ${quarantineId}.`);
|
|
5443
|
+
}
|
|
5444
|
+
return { manifest, dataPath, dataFile, digest: digest2 };
|
|
5445
|
+
}
|
|
5446
|
+
async previewRestore(input) {
|
|
5447
|
+
const parsed = restoreFileInputSchema.parse(input);
|
|
5448
|
+
const quarantined = await this.readRestorableQuarantine(parsed.quarantineId);
|
|
5449
|
+
if (parsed.expectedDigest !== quarantined.digest) {
|
|
5447
5450
|
throw new HarnessWorkspaceError(`Quarantine digest mismatch: ${parsed.quarantineId}.`);
|
|
5448
5451
|
}
|
|
5449
|
-
const
|
|
5452
|
+
const destination = await this.safePath(parsed.destination, { allowMissing: true });
|
|
5453
|
+
if (destination.exists)
|
|
5454
|
+
throw new HarnessWorkspaceError(`Restore destination already exists: ${parsed.destination}.`);
|
|
5455
|
+
let after;
|
|
5456
|
+
try {
|
|
5457
|
+
after = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(quarantined.dataFile.contents);
|
|
5458
|
+
} catch {
|
|
5459
|
+
throw new HarnessWorkspaceError("Preview requires valid UTF-8 text.");
|
|
5460
|
+
}
|
|
5461
|
+
return {
|
|
5462
|
+
proposalId: parsed.expectedDigest,
|
|
5463
|
+
files: [{
|
|
5464
|
+
path: parsed.destination,
|
|
5465
|
+
expectedDigest: parsed.expectedDigest,
|
|
5466
|
+
before: null,
|
|
5467
|
+
after,
|
|
5468
|
+
afterDigest: quarantined.digest,
|
|
5469
|
+
operation: "restore"
|
|
5470
|
+
}]
|
|
5471
|
+
};
|
|
5472
|
+
}
|
|
5473
|
+
async restoreQuarantined(input) {
|
|
5474
|
+
return withWorkspaceMutation(this.root, () => this.restoreQuarantinedLocked(input));
|
|
5475
|
+
}
|
|
5476
|
+
async restoreQuarantinedLocked(input) {
|
|
5477
|
+
const parsed = restoreFileInputSchema.parse(input);
|
|
5478
|
+
this.checkAuditCapacity();
|
|
5479
|
+
const quarantined = await this.readRestorableQuarantine(parsed.quarantineId);
|
|
5480
|
+
if (parsed.expectedDigest !== quarantined.digest) {
|
|
5481
|
+
throw new HarnessWorkspaceError(`Quarantine digest mismatch: ${parsed.quarantineId}.`);
|
|
5482
|
+
}
|
|
5483
|
+
const { manifest, dataPath, dataFile, digest: digest2 } = quarantined;
|
|
5484
|
+
const destinationPath = parsed.destination;
|
|
5450
5485
|
const destination = await this.safePath(destinationPath, { allowMissing: true });
|
|
5451
5486
|
if (destination.exists)
|
|
5452
5487
|
throw new HarnessWorkspaceError(`Restore destination already exists: ${destinationPath}.`);
|
|
@@ -6671,7 +6706,7 @@ var importPatch = async (runId, expectedPatchId, host, base, current, maxFileWri
|
|
|
6671
6706
|
const rollbackErrors = [];
|
|
6672
6707
|
for (const item of [...quarantined].reverse()) {
|
|
6673
6708
|
try {
|
|
6674
|
-
await host.restoreQuarantined({ quarantineId: item.quarantineId, expectedDigest: item.digest });
|
|
6709
|
+
await host.restoreQuarantined({ quarantineId: item.quarantineId, destination: item.path, expectedDigest: item.digest });
|
|
6675
6710
|
} catch (rollbackError) {
|
|
6676
6711
|
rollbackErrors.push(rollbackError);
|
|
6677
6712
|
}
|
|
@@ -7342,6 +7377,79 @@ var taskUsageAdmission = new AsyncLocalStorage3;
|
|
|
7342
7377
|
import { AsyncLocalStorage as AsyncLocalStorage5 } from "node:async_hooks";
|
|
7343
7378
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
7344
7379
|
import { z as z12 } from "zod";
|
|
7380
|
+
|
|
7381
|
+
// src/runtime/budget-ledger-scope.ts
|
|
7382
|
+
var BUDGET_LEDGER_NAMESPACE = "__zhivex_budget__";
|
|
7383
|
+
var BUDGET_OWNER_NAMESPACE_KEY = "budgetOwnerNamespace";
|
|
7384
|
+
var isReservedBudgetNamespace = (namespace) => namespace === BUDGET_LEDGER_NAMESPACE || (namespace?.startsWith(`${BUDGET_LEDGER_NAMESPACE}:`) ?? false);
|
|
7385
|
+
var budgetLedgerNamespace = (namespace) => {
|
|
7386
|
+
if (!namespace || isReservedBudgetNamespace(namespace)) {
|
|
7387
|
+
throw new HarnessStateConflictError("The budget ledger namespace is reserved.");
|
|
7388
|
+
}
|
|
7389
|
+
return `${BUDGET_LEDGER_NAMESPACE}:${namespace}`;
|
|
7390
|
+
};
|
|
7391
|
+
var budgetLedgerScope = (scope) => {
|
|
7392
|
+
if (scope.namespace && isReservedBudgetNamespace(scope.namespace)) {
|
|
7393
|
+
throw new HarnessStateConflictError("The budget ledger namespace is reserved.");
|
|
7394
|
+
}
|
|
7395
|
+
return {
|
|
7396
|
+
tenantId: scope.tenantId,
|
|
7397
|
+
...scope.userId ? { userId: scope.userId } : {},
|
|
7398
|
+
namespace: scope.namespace ? budgetLedgerNamespace(scope.namespace) : `${BUDGET_LEDGER_NAMESPACE}:`
|
|
7399
|
+
};
|
|
7400
|
+
};
|
|
7401
|
+
var sameIdentity = (left, right) => left?.tenantId === right.tenantId && (left.userId ?? undefined) === (right.userId ?? undefined);
|
|
7402
|
+
var bindBudgetLedgerStore = (primary, scope, budget = primary) => {
|
|
7403
|
+
const bound = budgetLedgerScope(scope);
|
|
7404
|
+
const ownerNamespace = scope.namespace ?? "";
|
|
7405
|
+
const isSdkBudget = (value) => sameIdentity(value, bound) && value?.namespace === BUDGET_LEDGER_NAMESPACE;
|
|
7406
|
+
const isBoundBudget = (value) => sameIdentity(value, bound) && value?.namespace === bound.namespace;
|
|
7407
|
+
const assertId = (id) => {
|
|
7408
|
+
if (!/^budget_[a-f0-9]{64}$/.test(id))
|
|
7409
|
+
throw new HarnessStateConflictError("Invalid budget ledger identity.");
|
|
7410
|
+
};
|
|
7411
|
+
const bindState = (state) => {
|
|
7412
|
+
const metadata = { ...state.metadata ?? {} };
|
|
7413
|
+
const owner = metadata[BUDGET_OWNER_NAMESPACE_KEY];
|
|
7414
|
+
if (owner !== undefined && owner !== ownerNamespace) {
|
|
7415
|
+
throw new HarnessStateConflictError("Budget ledger is bound to another namespace.");
|
|
7416
|
+
}
|
|
7417
|
+
return {
|
|
7418
|
+
...state,
|
|
7419
|
+
scope: bound,
|
|
7420
|
+
metadata: { ...metadata, [BUDGET_OWNER_NAMESPACE_KEY]: ownerNamespace }
|
|
7421
|
+
};
|
|
7422
|
+
};
|
|
7423
|
+
const assertStored = (state) => {
|
|
7424
|
+
if (!state)
|
|
7425
|
+
return state;
|
|
7426
|
+
const owner = state.metadata?.[BUDGET_OWNER_NAMESPACE_KEY];
|
|
7427
|
+
if (state.scope?.namespace !== bound.namespace || owner !== ownerNamespace) {
|
|
7428
|
+
throw new HarnessStateConflictError("Budget ledger is not bound to this namespace.");
|
|
7429
|
+
}
|
|
7430
|
+
return state;
|
|
7431
|
+
};
|
|
7432
|
+
return {
|
|
7433
|
+
...primary,
|
|
7434
|
+
load: async (id, requestedScope) => {
|
|
7435
|
+
if (!isSdkBudget(requestedScope) && !isBoundBudget(requestedScope))
|
|
7436
|
+
return primary.load(id, requestedScope);
|
|
7437
|
+
assertId(id);
|
|
7438
|
+
return assertStored(await budget.load(id, bound));
|
|
7439
|
+
},
|
|
7440
|
+
save: async (state, options) => {
|
|
7441
|
+
if (!isSdkBudget(state.scope) && !isBoundBudget(state.scope))
|
|
7442
|
+
return primary.save(state, options);
|
|
7443
|
+
assertId(state.runId);
|
|
7444
|
+
if (state.provider !== "zhivex" || state.modelId !== "budget-coordinator" || state.metadata?.budgetCoordinator !== true) {
|
|
7445
|
+
throw new HarnessStateConflictError("Only SDK budget ledger states may use the reserved namespace.");
|
|
7446
|
+
}
|
|
7447
|
+
return budget.save(bindState(state), options);
|
|
7448
|
+
}
|
|
7449
|
+
};
|
|
7450
|
+
};
|
|
7451
|
+
|
|
7452
|
+
// src/runtime/task-budget.ts
|
|
7345
7453
|
import {
|
|
7346
7454
|
createAgentBudgetCoordinator,
|
|
7347
7455
|
fingerprintAgentHarness as fingerprintAgentHarness2,
|
|
@@ -8871,10 +8979,10 @@ class TaskBudget {
|
|
|
8871
8979
|
this.accountRunId = TaskBudget.accountId(scope, taskId);
|
|
8872
8980
|
this.policy = structuredClone(account.policy);
|
|
8873
8981
|
const budgetId = this.accountRunId;
|
|
8874
|
-
this.coordinator = createAgentBudgetCoordinator({ store, scope, budgetId, limits: this.policy.limits });
|
|
8982
|
+
this.coordinator = createAgentBudgetCoordinator({ store: bindBudgetLedgerStore(store, scope), scope, budgetId, limits: this.policy.limits });
|
|
8875
8983
|
this.coordinatorId = this.coordinator.id;
|
|
8876
8984
|
this.budgetRunId = `budget_${fingerprintAgentHarness2({ budgetId, scope }).slice("sha256:".length)}`;
|
|
8877
|
-
this.budgetScope =
|
|
8985
|
+
this.budgetScope = budgetLedgerScope(scope);
|
|
8878
8986
|
}
|
|
8879
8987
|
static accountId(scope, taskId) {
|
|
8880
8988
|
return `task_budget_${fingerprintAgentHarness2({ scope, taskId }).slice("sha256:".length)}`;
|
|
@@ -13762,37 +13870,7 @@ import {
|
|
|
13762
13870
|
createSqliteAgentRunStore
|
|
13763
13871
|
} from "@zhivex-ai/agents/ops";
|
|
13764
13872
|
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
|
-
};
|
|
13873
|
+
var withBudgetLedgerStore = (primary, budget, config) => bindBudgetLedgerStore(primary, config.scope, budget);
|
|
13796
13874
|
var HARNESS_SQLITE_FILE = "operations.sqlite";
|
|
13797
13875
|
var HARNESS_OPERATIONS_SCHEMA_VERSION = 1;
|
|
13798
13876
|
var runSummary = (state) => {
|
|
@@ -13963,13 +14041,12 @@ var migrateLegacyFileRuns = async (directory, target, config) => {
|
|
|
13963
14041
|
return result;
|
|
13964
14042
|
};
|
|
13965
14043
|
var openHarnessPersistence = async (config, options = {}) => {
|
|
13966
|
-
|
|
13967
|
-
throw new HarnessStateConflictError("The budget ledger namespace is reserved.");
|
|
14044
|
+
const budgetScope = budgetLedgerScope(config.scope);
|
|
13968
14045
|
await validateStateDirectory(config.workspace, config.stateDirectory);
|
|
13969
14046
|
await ensurePrivateStateDirectory(config.stateDirectory);
|
|
13970
14047
|
await protectStateFromGit(config.workspace, config.stateDirectory);
|
|
13971
14048
|
if (config.storeBackend === "file") {
|
|
13972
|
-
const store = withBudgetLedgerStore(createFileAgentRunStore({ directory: config.stateDirectory, scope: config.scope }), createFileAgentRunStore({ directory: config.stateDirectory, scope:
|
|
14049
|
+
const store = withBudgetLedgerStore(createFileAgentRunStore({ directory: config.stateDirectory, scope: config.scope }), createFileAgentRunStore({ directory: config.stateDirectory, scope: budgetScope }), config);
|
|
13973
14050
|
const migration = shouldMigrateLegacyRuns(config, options.migrateLegacyFileStore) ? await migrateLegacyFileRuns(config.stateDirectory, store, config) : emptyMigration();
|
|
13974
14051
|
return {
|
|
13975
14052
|
backend: "file",
|
|
@@ -14014,7 +14091,7 @@ var openHarnessPersistence = async (config, options = {}) => {
|
|
|
14014
14091
|
database.exec("PRAGMA foreign_keys = ON");
|
|
14015
14092
|
await chmod3(databasePath, 384);
|
|
14016
14093
|
const databaseLike = sqliteAdapter(database);
|
|
14017
|
-
const store = withBudgetLedgerStore(createSqliteAgentRunStore({ db: databaseLike, scope: config.scope, history: "incremental" }), createSqliteAgentRunStore({ db: databaseLike, scope:
|
|
14094
|
+
const store = withBudgetLedgerStore(createSqliteAgentRunStore({ db: databaseLike, scope: config.scope, history: "incremental" }), createSqliteAgentRunStore({ db: databaseLike, scope: budgetScope }), config);
|
|
14018
14095
|
database.exec(`CREATE TRIGGER IF NOT EXISTS zhivex_task_budget_retain_run
|
|
14019
14096
|
BEFORE DELETE ON zhivex_agent_runs
|
|
14020
14097
|
WHEN json_type(OLD.state_json, '$.metadata.zhivexTaskBudgetAccountV1') IS NOT NULL
|
|
@@ -14105,7 +14182,7 @@ var projectMemoryBinding = (workspace, scope) => {
|
|
|
14105
14182
|
segment(scope.userId);
|
|
14106
14183
|
if (scope.namespace !== undefined)
|
|
14107
14184
|
segment(scope.namespace);
|
|
14108
|
-
if (scope.namespace
|
|
14185
|
+
if (isReservedBudgetNamespace(scope.namespace))
|
|
14109
14186
|
throw new HarnessConfigError("Reserved memory namespace.");
|
|
14110
14187
|
const key = (kind, value) => createHash24("sha256").update(kind).update("\x00").update(value).digest("hex");
|
|
14111
14188
|
const workspaceKey = key("workspace", workspace);
|
|
@@ -14663,7 +14740,8 @@ var resolvers = {
|
|
|
14663
14740
|
const parsed = moveFileInputSchema.parse(input);
|
|
14664
14741
|
return [parsed.source, parsed.destination];
|
|
14665
14742
|
},
|
|
14666
|
-
quarantine_file: (input) => [quarantineFileInputSchema.parse(input).path]
|
|
14743
|
+
quarantine_file: (input) => [quarantineFileInputSchema.parse(input).path],
|
|
14744
|
+
restore_file: (input) => [restoreFileInputSchema.parse(input).destination]
|
|
14667
14745
|
};
|
|
14668
14746
|
var validateBuiltinToolPolicy = (policy, tools) => {
|
|
14669
14747
|
for (const rule of policy.rules)
|
|
@@ -14797,7 +14875,7 @@ import { z as z32 } from "zod";
|
|
|
14797
14875
|
// package.json
|
|
14798
14876
|
var package_default = {
|
|
14799
14877
|
name: "@zhivex-ai/harness",
|
|
14800
|
-
version: "1.
|
|
14878
|
+
version: "1.5.0",
|
|
14801
14879
|
description: "Governed, provider-portable runtime for coding agents with durable approvals and isolated repository execution.",
|
|
14802
14880
|
type: "module",
|
|
14803
14881
|
license: "MIT",
|
|
@@ -16322,7 +16400,7 @@ var createWorkspaceTools = (workspace, allowedChecks) => ({
|
|
|
16322
16400
|
}),
|
|
16323
16401
|
restore_file: tool5({
|
|
16324
16402
|
name: "restore_file",
|
|
16325
|
-
description: "Restore a quarantined file to
|
|
16403
|
+
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
16404
|
schema: restoreFileInputSchema,
|
|
16327
16405
|
...mutationApproval,
|
|
16328
16406
|
execute: async (input, context) => serializeJsonValue9(editContractDocument("restore-result", await (harnessExecutionSession(context)?.workspace ?? workspace).restoreQuarantined(input)))
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
withWorkspaceMutation
|
|
3
|
-
} from "./index-
|
|
3
|
+
} from "./index-ggstwr0v.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";
|
|
@@ -96,8 +96,8 @@ var quarantineFileInputSchema = z.strictObject({
|
|
|
96
96
|
});
|
|
97
97
|
var restoreFileInputSchema = z.strictObject({
|
|
98
98
|
quarantineId: z.string().min(1).max(200).regex(/^[A-Za-z0-9._:-]+$/),
|
|
99
|
-
destination: workspaceFilePathSchema.
|
|
100
|
-
expectedDigest: fileDigestSchema.
|
|
99
|
+
destination: workspaceFilePathSchema.describe("Required workspace path for the quarantined bytes. Restore does not fall back to the original path."),
|
|
100
|
+
expectedDigest: fileDigestSchema.describe("Required sha256 of the quarantined bytes the operator reviewed.")
|
|
101
101
|
});
|
|
102
102
|
var mutationOperationSchema = z.enum(["create", "update", "move", "quarantine", "restore"]);
|
|
103
103
|
var mutationAuditEntrySchema = z.strictObject({
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
runtimeManifest,
|
|
21
21
|
taskSources,
|
|
22
22
|
tokenUsageCheckpointStore
|
|
23
|
-
} from "./index-
|
|
23
|
+
} from "./index-wwthv10a.js";
|
|
24
24
|
import {
|
|
25
25
|
HARNESS_MCP_CONFIG_SCHEMA_VERSION,
|
|
26
26
|
closeMcpHostSession,
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
mcpConfigurationFingerprintInput,
|
|
30
30
|
normalizeHarnessMcpConfiguration,
|
|
31
31
|
resolveMcpHostSession
|
|
32
|
-
} from "./index-
|
|
32
|
+
} from "./index-5zbq9ztj.js";
|
|
33
33
|
import {
|
|
34
34
|
EnvironmentPatchDriftError,
|
|
35
35
|
Workspace,
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
isHardIgnored,
|
|
41
41
|
replacementEditSchema,
|
|
42
42
|
resolvePackageCheckCommand
|
|
43
|
-
} from "./index-
|
|
43
|
+
} from "./index-ggstwr0v.js";
|
|
44
44
|
import {
|
|
45
45
|
DEFAULT_PROVIDER_REGISTRY,
|
|
46
46
|
HARNESS_CONFIG_SCHEMA_VERSION,
|
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
publishHarnessPolicyDecision,
|
|
61
61
|
publishPendingPolicyDecision,
|
|
62
62
|
readApprovalDecisions
|
|
63
|
-
} from "./index-
|
|
63
|
+
} from "./index-bt2p8x7r.js";
|
|
64
64
|
import {
|
|
65
65
|
MODEL_BUDGET_KEY,
|
|
66
66
|
TASK_ACCEPTANCE_EVIDENCE_KEY,
|
|
@@ -85,10 +85,10 @@ import {
|
|
|
85
85
|
withFreshSystemInstructions,
|
|
86
86
|
withRuntimeInstruction,
|
|
87
87
|
workBudgetReached
|
|
88
|
-
} from "./index-
|
|
88
|
+
} from "./index-fqqz1yt8.js";
|
|
89
89
|
import {
|
|
90
90
|
HARNESS_VERSION
|
|
91
|
-
} from "./index-
|
|
91
|
+
} from "./index-pq3kz1r8.js";
|
|
92
92
|
import {
|
|
93
93
|
FileSizeLimitError,
|
|
94
94
|
MAX_EDIT_CHANGES,
|
|
@@ -108,7 +108,7 @@ import {
|
|
|
108
108
|
validateEditProposal,
|
|
109
109
|
withTaskAcceptanceDelivery,
|
|
110
110
|
workspaceFilePathSchema
|
|
111
|
-
} from "./index-
|
|
111
|
+
} from "./index-6dxrbnxm.js";
|
|
112
112
|
import {
|
|
113
113
|
HarnessConfigError,
|
|
114
114
|
HarnessError,
|
|
@@ -4199,7 +4199,8 @@ var resolvers = {
|
|
|
4199
4199
|
const parsed = moveFileInputSchema.parse(input);
|
|
4200
4200
|
return [parsed.source, parsed.destination];
|
|
4201
4201
|
},
|
|
4202
|
-
quarantine_file: (input) => [quarantineFileInputSchema.parse(input).path]
|
|
4202
|
+
quarantine_file: (input) => [quarantineFileInputSchema.parse(input).path],
|
|
4203
|
+
restore_file: (input) => [restoreFileInputSchema.parse(input).destination]
|
|
4203
4204
|
};
|
|
4204
4205
|
var validateBuiltinToolPolicy = (policy, tools) => {
|
|
4205
4206
|
for (const rule of policy.rules)
|
|
@@ -4656,7 +4657,7 @@ var createWorkspaceTools = (workspace, allowedChecks) => ({
|
|
|
4656
4657
|
}),
|
|
4657
4658
|
restore_file: tool4({
|
|
4658
4659
|
name: "restore_file",
|
|
4659
|
-
description: "Restore a quarantined file to
|
|
4660
|
+
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.",
|
|
4660
4661
|
schema: restoreFileInputSchema,
|
|
4661
4662
|
...mutationApproval,
|
|
4662
4663
|
execute: async (input, context) => serializeJsonValue6(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-5zbq9ztj.js";
|
|
7
7
|
import {
|
|
8
8
|
collectSnapshotInventory,
|
|
9
9
|
readSnapshotFile,
|
|
10
10
|
runPortableProcess
|
|
11
|
-
} from "./index-
|
|
11
|
+
} from "./index-ggstwr0v.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 {
|