@zhivex-ai/harness 0.9.0 → 0.10.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/dist/zhx.js CHANGED
@@ -1,24 +1,24 @@
1
- #!/usr/bin/env bun
2
- // @bun
1
+ #!/usr/bin/env node
3
2
 
4
3
  // src/cli.ts
5
- import { createInterface } from "readline/promises";
6
- import { randomUUID as randomUUID5 } from "crypto";
7
- import { constants as fsConstants2 } from "fs";
8
- import { access, lstat as lstat7, readFile as readFile4, stat as stat4 } from "fs/promises";
9
- import path9 from "path";
4
+ import { createInterface } from "node:readline/promises";
5
+ import { randomUUID as randomUUID5 } from "node:crypto";
6
+ import { constants as fsConstants2, realpathSync as realpathSync2 } from "node:fs";
7
+ import { access, lstat as lstat8, readFile as readFile4, stat as stat4 } from "node:fs/promises";
8
+ import path10 from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
10
  import { z as z5 } from "zod";
11
11
  import {
12
12
  getAgentBudgetStatus as getAgentBudgetStatus3
13
13
  } from "@zhivex-ai/agents";
14
14
 
15
15
  // src/config.ts
16
- import { createHash as createHash2 } from "crypto";
17
- import { realpathSync } from "fs";
18
- import path from "path";
16
+ import { createHash as createHash2 } from "node:crypto";
17
+ import { realpathSync } from "node:fs";
18
+ import path from "node:path";
19
19
 
20
20
  // src/providers.ts
21
- import { createHash } from "crypto";
21
+ import { createHash } from "node:crypto";
22
22
  import { createGemini } from "@zhivex-ai/gemini";
23
23
  import { createMeta } from "@zhivex-ai/meta";
24
24
  import { createOpenAI } from "@zhivex-ai/openai";
@@ -372,11 +372,11 @@ var HARNESS_CONFIG_SCHEMA_VERSION = 4;
372
372
  var HARNESS_STORE_BACKENDS = ["sqlite", "file"];
373
373
  var HARNESS_EXECUTION_BACKENDS = ["none", "oci"];
374
374
  var HARNESS_OCI_RUNTIMES = ["docker", "podman"];
375
- var HARNESS_EXECUTION_POLICY_VERSION = "2026-08-20-v2";
375
+ var HARNESS_EXECUTION_POLICY_VERSION = "2026-08-21-v3";
376
376
  var DEFAULT_OCI_EXECUTION = {
377
377
  runtime: "docker",
378
- image: "oven/bun:1.3.7-slim",
379
- allowedCommands: ["bun"],
378
+ image: "node:24-bookworm-slim",
379
+ allowedCommands: ["node", "npm"],
380
380
  maxProcessRuntimeMs: 120000,
381
381
  maxProcessOutputBytes: 20000,
382
382
  maxMemoryMb: 1024,
@@ -501,8 +501,8 @@ var resolveOciAllowedCommands = (configured) => {
501
501
  if (invalid) {
502
502
  throw new Error(`Invalid OCI command name: ${invalid}. Use a bare executable name without a path.`);
503
503
  }
504
- if (!commands.includes("bun")) {
505
- throw new Error("ociAllowedCommands must include bun so declared package checks remain available.");
504
+ if (!commands.some((command) => ["npm", "pnpm", "yarn", "bun"].includes(command))) {
505
+ throw new Error("ociAllowedCommands must include npm, pnpm, yarn, or bun so declared package checks remain available.");
506
506
  }
507
507
  return commands;
508
508
  };
@@ -657,7 +657,7 @@ var createProviderModel = (config, env = process.env, registry = DEFAULT_PROVIDE
657
657
  var providerAvailability = (env = process.env, registry = DEFAULT_PROVIDER_REGISTRY) => registry.availability(env);
658
658
 
659
659
  // src/harness.ts
660
- import { createHash as createHash7 } from "crypto";
660
+ import { createHash as createHash7 } from "node:crypto";
661
661
  import {
662
662
  Agent,
663
663
  applySafetyPolicyToAgent as applySafetyPolicyToAgent2,
@@ -727,8 +727,8 @@ var selectHarnessModel = (candidates, required) => {
727
727
  };
728
728
 
729
729
  // src/edit-contracts.ts
730
- import { createHash as createHash3 } from "crypto";
731
- import path2 from "path";
730
+ import { createHash as createHash3 } from "node:crypto";
731
+ import path2 from "node:path";
732
732
  import { z } from "zod";
733
733
  var EDIT_CONTRACT_SCHEMA_VERSION = 1;
734
734
  var EDIT_DIGEST_ALGORITHM = "sha256";
@@ -862,9 +862,256 @@ var editContractDocument = (kind, result) => ({
862
862
  });
863
863
 
864
864
  // src/workspace.ts
865
- import { createHash as createHash4, randomUUID } from "crypto";
866
- import { link, lstat, mkdir, open, readFile, readdir, realpath, rename, stat, unlink } from "fs/promises";
867
- import path3 from "path";
865
+ import { createHash as createHash4, randomUUID } from "node:crypto";
866
+ import { link, lstat as lstat2, mkdir, open, readFile, readdir, realpath, rename, stat, unlink } from "node:fs/promises";
867
+ import path4 from "node:path";
868
+
869
+ // src/package-manager.ts
870
+ import { lstat } from "node:fs/promises";
871
+ import path3 from "node:path";
872
+ var HARNESS_PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
873
+ var LOCKFILES = {
874
+ npm: ["package-lock.json", "npm-shrinkwrap.json"],
875
+ pnpm: ["pnpm-lock.yaml"],
876
+ yarn: ["yarn.lock"],
877
+ bun: ["bun.lock", "bun.lockb"]
878
+ };
879
+ var isPackageManager = (value) => HARNESS_PACKAGE_MANAGERS.includes(value);
880
+ var parsePackageManager = (value) => {
881
+ if (value === undefined)
882
+ return;
883
+ if (typeof value !== "string") {
884
+ throw new Error("package.json packageManager must be a string when present.");
885
+ }
886
+ const match = /^(npm|pnpm|yarn|bun)@([^\s]+)$/.exec(value.trim());
887
+ if (!match?.[1] || !match[2]) {
888
+ throw new Error("package.json packageManager must pin npm, pnpm, yarn, or bun using <manager>@<version>.");
889
+ }
890
+ if (!isPackageManager(match[1])) {
891
+ throw new Error(`Unsupported package manager: ${match[1]}.`);
892
+ }
893
+ return match[1];
894
+ };
895
+ var existingLockfiles = async (root) => {
896
+ const matches = [];
897
+ for (const manager of HARNESS_PACKAGE_MANAGERS) {
898
+ for (const file of LOCKFILES[manager]) {
899
+ try {
900
+ const entry = await lstat(path3.join(root, file));
901
+ if (entry.isSymbolicLink()) {
902
+ throw new Error(`Package-manager lockfile must not be a symbolic link: ${file}.`);
903
+ }
904
+ if (entry.isFile())
905
+ matches.push({ manager, file });
906
+ } catch (error) {
907
+ if (error.code !== "ENOENT")
908
+ throw error;
909
+ }
910
+ }
911
+ }
912
+ return matches;
913
+ };
914
+ var resolvePackageManager = async (root, manifest) => {
915
+ const declared = parsePackageManager(manifest.packageManager);
916
+ if (declared) {
917
+ return {
918
+ manager: declared,
919
+ source: "packageManager",
920
+ evidence: manifest.packageManager
921
+ };
922
+ }
923
+ const lockfiles = await existingLockfiles(root);
924
+ const managers = [...new Set(lockfiles.map((entry) => entry.manager))];
925
+ if (managers.length > 1) {
926
+ throw new Error(`Ambiguous package manager: found lockfiles for ${managers.join(", ")}; declare packageManager explicitly.`);
927
+ }
928
+ const detected = managers[0];
929
+ if (detected) {
930
+ return {
931
+ manager: detected,
932
+ source: "lockfile",
933
+ evidence: lockfiles.filter((entry) => entry.manager === detected).map((entry) => entry.file).join(",")
934
+ };
935
+ }
936
+ return { manager: "npm", source: "default" };
937
+ };
938
+ var runCommand = (manager, check) => {
939
+ switch (manager) {
940
+ case "bun":
941
+ return ["bun", "--no-env-file", "run", check];
942
+ case "npm":
943
+ return ["npm", "--ignore-scripts", "run", check];
944
+ case "pnpm":
945
+ return ["pnpm", "--ignore-scripts", "run", check];
946
+ case "yarn":
947
+ return ["yarn", "run", check];
948
+ }
949
+ };
950
+ var resolvePackageCheckCommand = async (root, manifest, check, expectedScript, allowedChecks) => {
951
+ if (!/^[A-Za-z0-9:_-]+$/.test(check) || !allowedChecks.includes(check)) {
952
+ throw new Error(`The check "${check}" is not in the explicit allowlist.`);
953
+ }
954
+ const scripts = manifest.scripts && typeof manifest.scripts === "object" ? manifest.scripts : {};
955
+ const actual = scripts[check];
956
+ if (typeof actual !== "string") {
957
+ throw new Error(`package.json does not define the "${check}" script.`);
958
+ }
959
+ if (actual !== expectedScript) {
960
+ throw new Error(`The "${check}" script changed or does not match expectedScript.`);
961
+ }
962
+ for (const lifecycleName of [`pre${check}`, `post${check}`]) {
963
+ if (typeof scripts[lifecycleName] === "string") {
964
+ throw new Error(`The approved "${check}" check has an implicit ${lifecycleName} lifecycle script; approve a standalone script instead.`);
965
+ }
966
+ }
967
+ const resolution = await resolvePackageManager(root, manifest);
968
+ return { ...resolution, command: runCommand(resolution.manager, check) };
969
+ };
970
+
971
+ // src/process-runtime.ts
972
+ import { spawn } from "node:child_process";
973
+ var DEFAULT_MAX_OUTPUT_CHARACTERS = 20000;
974
+ var DEFAULT_TERMINATION_GRACE_MS = 1000;
975
+ var PORTABLE_ENVIRONMENT_KEYS = [
976
+ "CI",
977
+ "FORCE_COLOR",
978
+ "LANG",
979
+ "LC_ALL",
980
+ "NO_COLOR",
981
+ "PATH",
982
+ "TEMP",
983
+ "TERM",
984
+ "TMP",
985
+ "TMPDIR"
986
+ ];
987
+
988
+ class BoundedTextCapture {
989
+ decoder = new TextDecoder;
990
+ maxCharacters;
991
+ output = "";
992
+ totalCharacters = 0;
993
+ constructor(maxCharacters) {
994
+ this.maxCharacters = maxCharacters;
995
+ }
996
+ write(chunk) {
997
+ this.append(this.decoder.decode(chunk, { stream: true }));
998
+ }
999
+ finish() {
1000
+ this.append(this.decoder.decode());
1001
+ return this.totalCharacters <= this.maxCharacters ? this.output : `${this.output}
1002
+ … output truncated (${this.totalCharacters - this.maxCharacters} characters omitted)`;
1003
+ }
1004
+ append(value) {
1005
+ this.totalCharacters += value.length;
1006
+ if (this.output.length < this.maxCharacters) {
1007
+ this.output += value.slice(0, this.maxCharacters - this.output.length);
1008
+ }
1009
+ }
1010
+ }
1011
+ var processEnvironment = (additional) => {
1012
+ const env = {};
1013
+ for (const key of PORTABLE_ENVIRONMENT_KEYS) {
1014
+ const value = process.env[key];
1015
+ if (value !== undefined)
1016
+ env[key] = value;
1017
+ }
1018
+ for (const [key, value] of Object.entries(additional ?? {})) {
1019
+ if (value === undefined)
1020
+ delete env[key];
1021
+ else
1022
+ env[key] = value;
1023
+ }
1024
+ return env;
1025
+ };
1026
+ var exitCodeForSignal = (signal) => {
1027
+ if (signal === "SIGKILL")
1028
+ return 137;
1029
+ if (signal === "SIGTERM")
1030
+ return 143;
1031
+ return 1;
1032
+ };
1033
+ var abortReason = (signal) => signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted.", "AbortError");
1034
+ var runPortableProcess = async (command, options = {}) => {
1035
+ if (command.length === 0 || !command[0])
1036
+ throw new Error("The process command must not be empty.");
1037
+ if (options.signal?.aborted)
1038
+ throw abortReason(options.signal);
1039
+ const maxOutputCharacters = options.maxOutputCharacters ?? DEFAULT_MAX_OUTPUT_CHARACTERS;
1040
+ if (!Number.isSafeInteger(maxOutputCharacters) || maxOutputCharacters < 0) {
1041
+ throw new Error("maxOutputCharacters must be a non-negative safe integer.");
1042
+ }
1043
+ if (options.timeoutMs !== undefined && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs <= 0)) {
1044
+ throw new Error("timeoutMs must be a positive safe integer.");
1045
+ }
1046
+ const argv = [...command];
1047
+ const stdout = new BoundedTextCapture(maxOutputCharacters);
1048
+ const stderr = new BoundedTextCapture(maxOutputCharacters);
1049
+ const child = spawn(argv[0], argv.slice(1), {
1050
+ ...options.cwd ? { cwd: options.cwd } : {},
1051
+ env: processEnvironment(options.env),
1052
+ shell: false,
1053
+ stdio: ["ignore", "pipe", "pipe"],
1054
+ windowsHide: true
1055
+ });
1056
+ let timedOut = false;
1057
+ let aborted = false;
1058
+ let terminationTimer;
1059
+ child.stdout.on("data", (chunk) => stdout.write(chunk));
1060
+ child.stderr?.on("data", (chunk) => stderr.write(chunk));
1061
+ const terminate = () => {
1062
+ if (child.exitCode !== null || child.signalCode !== null)
1063
+ return;
1064
+ child.kill("SIGTERM");
1065
+ terminationTimer = setTimeout(() => {
1066
+ if (child.exitCode === null && child.signalCode === null)
1067
+ child.kill("SIGKILL");
1068
+ }, DEFAULT_TERMINATION_GRACE_MS);
1069
+ terminationTimer.unref?.();
1070
+ };
1071
+ const handleAbort = () => {
1072
+ aborted = true;
1073
+ terminate();
1074
+ };
1075
+ options.signal?.addEventListener("abort", handleAbort, { once: true });
1076
+ const timeout = options.timeoutMs === undefined ? undefined : setTimeout(() => {
1077
+ timedOut = true;
1078
+ terminate();
1079
+ }, options.timeoutMs);
1080
+ timeout?.unref?.();
1081
+ try {
1082
+ return await new Promise((resolve, reject) => {
1083
+ let processError;
1084
+ child.once("error", (error) => {
1085
+ processError = error;
1086
+ });
1087
+ child.once("close", (exitCode, signal) => {
1088
+ if (processError) {
1089
+ reject(processError);
1090
+ return;
1091
+ }
1092
+ if (aborted && options.signal) {
1093
+ reject(abortReason(options.signal));
1094
+ return;
1095
+ }
1096
+ resolve({
1097
+ command: argv,
1098
+ exitCode: exitCode ?? exitCodeForSignal(signal),
1099
+ stdout: stdout.finish(),
1100
+ stderr: options.stderr === "ignore" ? "" : stderr.finish(),
1101
+ timedOut
1102
+ });
1103
+ });
1104
+ });
1105
+ } finally {
1106
+ if (timeout)
1107
+ clearTimeout(timeout);
1108
+ if (terminationTimer)
1109
+ clearTimeout(terminationTimer);
1110
+ options.signal?.removeEventListener("abort", handleAbort);
1111
+ }
1112
+ };
1113
+
1114
+ // src/workspace.ts
868
1115
  var HARD_IGNORES = new Set([".git", ".next", ".turbo", ".zhivex-harness", "coverage", "dist", "node_modules"]);
869
1116
  var IGNORE_FILES = [".gitignore", ".zhivex-harnessignore"];
870
1117
  var DEFAULT_CHECKS = ["test", "typecheck", "lint", "build"];
@@ -880,13 +1127,13 @@ var isSensitiveName = (name) => {
880
1127
  };
881
1128
  var isHardIgnored = (relativePath) => relativePath.split("/").some((segment) => HARD_IGNORES.has(segment.toLocaleLowerCase()) || isSensitiveName(segment));
882
1129
  var isInside = (root, candidate) => {
883
- const relative = path3.relative(root, candidate);
884
- return relative === "" || !relative.startsWith(`..${path3.sep}`) && relative !== ".." && !path3.isAbsolute(relative);
1130
+ const relative = path4.relative(root, candidate);
1131
+ return relative === "" || !relative.startsWith(`..${path4.sep}`) && relative !== ".." && !path4.isAbsolute(relative);
885
1132
  };
886
- var wirePath = (value) => value.split(path3.sep).join("/");
1133
+ var wirePath = (value) => value.split(path4.sep).join("/");
887
1134
  var digestBytes = (value) => `sha256:${createHash4("sha256").update(value).digest("hex")}`;
888
1135
  var truncate = (value, maxLength = MAX_TOOL_OUTPUT) => value.length <= maxLength ? value : `${value.slice(0, maxLength)}
889
- \u2026 output truncated (${value.length - maxLength} characters omitted)`;
1136
+ … output truncated (${value.length - maxLength} characters omitted)`;
890
1137
  var cursorEncode = (value) => Buffer.from(JSON.stringify(value)).toString("base64url");
891
1138
  var cursorDecode = (value) => {
892
1139
  try {
@@ -987,24 +1234,7 @@ var isIgnoredByRules = (relativePath, isDirectory, rules) => {
987
1234
  return ignored;
988
1235
  };
989
1236
  var spawnBounded = async (command, cwd, timeoutMs = 120000) => {
990
- const allowed = ["CI", "FORCE_COLOR", "LANG", "LC_ALL", "NO_COLOR", "PATH", "TEMP", "TERM", "TMP", "TMPDIR"];
991
- const env = Object.fromEntries(allowed.flatMap((key) => process.env[key] ? [[key, process.env[key]]] : []));
992
- const child = Bun.spawn(command, { cwd, env, stdin: "ignore", stdout: "pipe", stderr: "pipe" });
993
- let timedOut = false;
994
- const timeout = setTimeout(() => {
995
- timedOut = true;
996
- child.kill();
997
- }, timeoutMs);
998
- try {
999
- const [stdout, stderr, exitCode] = await Promise.all([
1000
- new Response(child.stdout).text(),
1001
- new Response(child.stderr).text(),
1002
- child.exited
1003
- ]);
1004
- return { command, exitCode, stdout: truncate(stdout), stderr: truncate(stderr), timedOut };
1005
- } finally {
1006
- clearTimeout(timeout);
1007
- }
1237
+ return await runPortableProcess(command, { cwd, timeoutMs });
1008
1238
  };
1009
1239
 
1010
1240
  class Workspace {
@@ -1019,7 +1249,7 @@ class Workspace {
1019
1249
  this.root = root;
1020
1250
  }
1021
1251
  static async open(root) {
1022
- const resolved = await realpath(path3.resolve(root));
1252
+ const resolved = await realpath(path4.resolve(root));
1023
1253
  if (!(await stat(resolved)).isDirectory())
1024
1254
  throw new Error(`The workspace is not a directory: ${root}`);
1025
1255
  return new Workspace(resolved);
@@ -1027,10 +1257,10 @@ class Workspace {
1027
1257
  lexicalPath(relativePath) {
1028
1258
  if (!relativePath || relativePath.includes("\x00"))
1029
1259
  throw new Error("The path must be a valid relative path.");
1030
- const candidate = path3.resolve(this.root, relativePath);
1260
+ const candidate = path4.resolve(this.root, relativePath);
1031
1261
  if (!isInside(this.root, candidate))
1032
1262
  throw new Error(`The path escapes the workspace: ${relativePath}`);
1033
- const relative = wirePath(path3.relative(this.root, candidate));
1263
+ const relative = wirePath(path4.relative(this.root, candidate));
1034
1264
  if (relative && isHardIgnored(relative)) {
1035
1265
  const blocked = relative.split("/").find((segment) => HARD_IGNORES.has(segment.toLocaleLowerCase()) || isSensitiveName(segment));
1036
1266
  throw new Error(`The path is protected by the harness policy: ${blocked}`);
@@ -1041,13 +1271,13 @@ class Workspace {
1041
1271
  const candidate = this.lexicalPath(relativePath);
1042
1272
  if (candidate === this.root && options.requireFile)
1043
1273
  throw new Error("The workspace root is not a regular file.");
1044
- const segments = path3.relative(this.root, candidate).split(path3.sep).filter(Boolean);
1274
+ const segments = path4.relative(this.root, candidate).split(path4.sep).filter(Boolean);
1045
1275
  let current = this.root;
1046
1276
  for (let index = 0;index < segments.length; index += 1) {
1047
- current = path3.join(current, segments[index]);
1277
+ current = path4.join(current, segments[index]);
1048
1278
  let entry;
1049
1279
  try {
1050
- entry = await lstat(current);
1280
+ entry = await lstat2(current);
1051
1281
  } catch (error) {
1052
1282
  if (options.allowMissing && error.code === "ENOENT") {
1053
1283
  return { path: candidate, exists: false };
@@ -1068,18 +1298,18 @@ class Workspace {
1068
1298
  async readStableFile(relativePath, allowBinary = true) {
1069
1299
  this.indexMetrics.stableFileReads += 1;
1070
1300
  const safe = await this.safePath(relativePath, { requireFile: true });
1071
- const before = await lstat(safe.path);
1301
+ const before = await lstat2(safe.path);
1072
1302
  if (before.size > MAX_FILE_BYTES)
1073
1303
  throw new Error(`The file exceeds the ${MAX_FILE_BYTES}-byte limit.`);
1074
1304
  const contents = await readFile(safe.path);
1075
- const after = await lstat(safe.path);
1305
+ const after = await lstat2(safe.path);
1076
1306
  if (before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size || before.mtimeMs !== after.mtimeMs) {
1077
1307
  throw new Error(`The file changed while it was being read: ${relativePath}`);
1078
1308
  }
1079
1309
  if (!allowBinary && contents.includes(0))
1080
1310
  throw new Error("The file appears to be binary and cannot be read as text.");
1081
1311
  return {
1082
- path: wirePath(path3.relative(this.root, safe.path)),
1312
+ path: wirePath(path4.relative(this.root, safe.path)),
1083
1313
  absolutePath: safe.path,
1084
1314
  contents,
1085
1315
  digest: digestBytes(contents),
@@ -1105,21 +1335,21 @@ class Workspace {
1105
1335
  const rules = [...inherited];
1106
1336
  for (const filename of IGNORE_FILES) {
1107
1337
  try {
1108
- const ignorePath = path3.join(directory, filename);
1109
- const before = await lstat(ignorePath);
1338
+ const ignorePath = path4.join(directory, filename);
1339
+ const before = await lstat2(ignorePath);
1110
1340
  if (before.isSymbolicLink() || !before.isFile())
1111
1341
  continue;
1112
1342
  if (before.size <= MAX_FILE_BYTES) {
1113
1343
  const contents = await readFile(ignorePath);
1114
- const after = await lstat(ignorePath);
1344
+ const after = await lstat2(ignorePath);
1115
1345
  if (after.isSymbolicLink() || !after.isFile() || !this.sameFingerprint(after, this.fingerprint("", "ignore-file", before))) {
1116
- throw new Error(`The ignore file changed while the workspace was being indexed: ${wirePath(path3.relative(this.root, ignorePath))}`);
1346
+ throw new Error(`The ignore file changed while the workspace was being indexed: ${wirePath(path4.relative(this.root, ignorePath))}`);
1117
1347
  }
1118
1348
  const digest2 = digestBytes(contents);
1119
- ignoreFiles.push(this.fingerprint(wirePath(path3.relative(this.root, ignorePath)), "ignore-file", after, digest2));
1349
+ ignoreFiles.push(this.fingerprint(wirePath(path4.relative(this.root, ignorePath)), "ignore-file", after, digest2));
1120
1350
  rules.push(...parseIgnoreRules(contents.toString("utf8"), base));
1121
1351
  } else {
1122
- ignoreFiles.push(this.fingerprint(wirePath(path3.relative(this.root, ignorePath)), "ignore-file", before));
1352
+ ignoreFiles.push(this.fingerprint(wirePath(path4.relative(this.root, ignorePath)), "ignore-file", before));
1123
1353
  }
1124
1354
  } catch (error) {
1125
1355
  if (error.code !== "ENOENT")
@@ -1133,7 +1363,7 @@ class Workspace {
1133
1363
  const directories = [];
1134
1364
  const ignoreFiles = [];
1135
1365
  const walk = async (directory, base, inherited) => {
1136
- const directoryEntry = await lstat(directory);
1366
+ const directoryEntry = await lstat2(directory);
1137
1367
  if (directoryEntry.isSymbolicLink() || !directoryEntry.isDirectory()) {
1138
1368
  throw new Error(`A workspace directory changed while it was being indexed: ${base || "."}`);
1139
1369
  }
@@ -1144,7 +1374,7 @@ class Workspace {
1144
1374
  const relative = base ? `${base}/${entry.name}` : entry.name;
1145
1375
  if (isHardIgnored(relative) || entry.isSymbolicLink() || isIgnoredByRules(relative, entry.isDirectory(), rules))
1146
1376
  continue;
1147
- const absolute = path3.join(directory, entry.name);
1377
+ const absolute = path4.join(directory, entry.name);
1148
1378
  if (entry.isDirectory())
1149
1379
  await walk(absolute, relative, rules);
1150
1380
  else if (entry.isFile())
@@ -1158,19 +1388,19 @@ class Workspace {
1158
1388
  async isWorkspaceIndexFresh(index) {
1159
1389
  try {
1160
1390
  for (const fingerprint of index.directories) {
1161
- const absolute = fingerprint.path === "." ? this.root : path3.join(this.root, fingerprint.path);
1162
- const entry = await lstat(absolute);
1391
+ const absolute = fingerprint.path === "." ? this.root : path4.join(this.root, fingerprint.path);
1392
+ const entry = await lstat2(absolute);
1163
1393
  if (entry.isSymbolicLink() || !entry.isDirectory() || !this.sameFingerprint(entry, fingerprint))
1164
1394
  return false;
1165
1395
  }
1166
1396
  for (const fingerprint of index.ignoreFiles) {
1167
- const absolute = path3.join(this.root, fingerprint.path);
1168
- const before = await lstat(absolute);
1397
+ const absolute = path4.join(this.root, fingerprint.path);
1398
+ const before = await lstat2(absolute);
1169
1399
  if (before.isSymbolicLink() || !before.isFile() || !this.sameFingerprint(before, fingerprint))
1170
1400
  return false;
1171
1401
  if (fingerprint.digest) {
1172
1402
  const contents = await readFile(absolute);
1173
- const after = await lstat(absolute);
1403
+ const after = await lstat2(absolute);
1174
1404
  if (after.isSymbolicLink() || !after.isFile() || !this.sameFingerprint(after, fingerprint) || digestBytes(contents) !== fingerprint.digest)
1175
1405
  return false;
1176
1406
  }
@@ -1244,18 +1474,19 @@ class Workspace {
1244
1474
  }
1245
1475
  async listFiles(relativePath = ".", options = {}) {
1246
1476
  const start = await this.safePath(relativePath);
1247
- if (!(await lstat(start.path)).isDirectory())
1477
+ if (!(await lstat2(start.path)).isDirectory())
1248
1478
  throw new Error("list_files requires a directory.");
1249
- const requestPath = wirePath(path3.relative(this.root, start.path)) || ".";
1479
+ const requestPath = wirePath(path4.relative(this.root, start.path)) || ".";
1250
1480
  const input = typeof options === "number" ? { limit: options } : options;
1251
1481
  const limit = input.limit ?? 200;
1482
+ const includeDigests = input.includeDigests ?? true;
1252
1483
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 5000)
1253
1484
  throw new Error("limit must be between 1 and 5000.");
1254
1485
  let after = "";
1255
1486
  let cursorIndexVersion;
1256
1487
  if (input.cursor) {
1257
1488
  const parsed = cursorDecode(input.cursor);
1258
- if (parsed.v !== 2 || parsed.kind !== "list" || parsed.path !== requestPath || parsed.limit !== limit || typeof parsed.indexVersion !== "string" || typeof parsed.after !== "string") {
1489
+ if (parsed.v !== 2 || parsed.kind !== "list" || parsed.path !== requestPath || parsed.limit !== limit || parsed.includeDigests !== includeDigests || typeof parsed.indexVersion !== "string" || typeof parsed.after !== "string") {
1259
1490
  throw new Error("The pagination cursor does not match this list request.");
1260
1491
  }
1261
1492
  after = parsed.after;
@@ -1279,16 +1510,28 @@ class Workspace {
1279
1510
  if (truncated)
1280
1511
  selected.pop();
1281
1512
  const files = [];
1282
- for (const candidate of selected) {
1283
- const file = await this.readStableFile(candidate.path);
1284
- files.push({ path: file.path, size: file.contents.byteLength, digest: file.digest });
1513
+ if (includeDigests) {
1514
+ for (const candidate of selected) {
1515
+ const file = await this.readStableFile(candidate.path);
1516
+ files.push({ path: file.path, size: file.contents.byteLength, digest: file.digest });
1517
+ }
1518
+ } else {
1519
+ files.push(...selected.map(({ path: candidatePath }) => ({ path: candidatePath })));
1285
1520
  }
1286
1521
  const last = selected.at(-1);
1287
1522
  return {
1288
1523
  files,
1289
1524
  truncated,
1290
1525
  ...truncated && last ? {
1291
- nextCursor: cursorEncode({ v: 2, kind: "list", path: requestPath, limit, after: last.path, indexVersion: index.version })
1526
+ nextCursor: cursorEncode({
1527
+ v: 2,
1528
+ kind: "list",
1529
+ path: requestPath,
1530
+ limit,
1531
+ includeDigests,
1532
+ after: last.path,
1533
+ indexVersion: index.version
1534
+ })
1292
1535
  } : {}
1293
1536
  };
1294
1537
  }
@@ -1320,7 +1563,7 @@ class Workspace {
1320
1563
  }
1321
1564
  const ordered = requests.map((request) => ({
1322
1565
  ...request,
1323
- path: wirePath(path3.relative(this.root, this.lexicalPath(request.path)))
1566
+ path: wirePath(path4.relative(this.root, this.lexicalPath(request.path)))
1324
1567
  })).sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0) || (a.startLine ?? 1) - (b.startLine ?? 1) || (a.endLine ?? 0) - (b.endLine ?? 0));
1325
1568
  const stableFiles = new Map;
1326
1569
  let totalBytes = 0;
@@ -1355,9 +1598,9 @@ class Workspace {
1355
1598
  if (!query || query.length > 200)
1356
1599
  throw new Error("The search query must be between 1 and 200 characters.");
1357
1600
  const start = await this.safePath(relativePath);
1358
- if (!(await lstat(start.path)).isDirectory())
1601
+ if (!(await lstat2(start.path)).isDirectory())
1359
1602
  throw new Error("search_files requires a directory.");
1360
- const requestPath = wirePath(path3.relative(this.root, start.path)) || ".";
1603
+ const requestPath = wirePath(path4.relative(this.root, start.path)) || ".";
1361
1604
  const limit = options.limit ?? options.maxMatches ?? 100;
1362
1605
  if (!Number.isSafeInteger(limit) || limit < 1 || limit > 500)
1363
1606
  throw new Error("limit must be between 1 and 500.");
@@ -1455,9 +1698,9 @@ class Workspace {
1455
1698
  };
1456
1699
  });
1457
1700
  const start = await this.safePath(relativePath);
1458
- if (!(await lstat(start.path)).isDirectory())
1701
+ if (!(await lstat2(start.path)).isDirectory())
1459
1702
  throw new Error("searchMany requires a directory.");
1460
- const requestPath = wirePath(path3.relative(this.root, start.path)) || ".";
1703
+ const requestPath = wirePath(path4.relative(this.root, start.path)) || ".";
1461
1704
  const prefix = requestPath === "." ? "" : `${requestPath}/`;
1462
1705
  const index = await this.getWorkspaceIndex();
1463
1706
  let candidateIndex = requestPath === "." ? 0 : firstPathAtLeast(index.files, prefix);
@@ -1506,16 +1749,16 @@ class Workspace {
1506
1749
  return this.auditEntries.map((entry) => ({ ...entry }));
1507
1750
  }
1508
1751
  async secureWorkspaceDirectory(directory, mode = 493) {
1509
- const resolvedInput = path3.resolve(directory);
1752
+ const resolvedInput = path4.resolve(directory);
1510
1753
  if (!isInside(this.root, resolvedInput)) {
1511
1754
  throw new Error("A mutation directory escapes the workspace.");
1512
1755
  }
1513
- const segments = path3.relative(this.root, resolvedInput).split(path3.sep).filter(Boolean);
1756
+ const segments = path4.relative(this.root, resolvedInput).split(path4.sep).filter(Boolean);
1514
1757
  let current = this.root;
1515
1758
  for (const segment of segments) {
1516
- current = path3.join(current, segment);
1759
+ current = path4.join(current, segment);
1517
1760
  try {
1518
- const entry = await lstat(current);
1761
+ const entry = await lstat2(current);
1519
1762
  if (entry.isSymbolicLink())
1520
1763
  throw new Error("A mutation directory must not contain a symbolic link.");
1521
1764
  if (!entry.isDirectory())
@@ -1528,7 +1771,7 @@ class Workspace {
1528
1771
  } catch (mkdirError) {
1529
1772
  if (mkdirError.code !== "EEXIST")
1530
1773
  throw mkdirError;
1531
- const raced = await lstat(current);
1774
+ const raced = await lstat2(current);
1532
1775
  if (raced.isSymbolicLink() || !raced.isDirectory()) {
1533
1776
  throw new Error("A mutation directory changed during creation.");
1534
1777
  }
@@ -1541,8 +1784,8 @@ class Workspace {
1541
1784
  return canonical;
1542
1785
  }
1543
1786
  async stageFile(target, contents, mode) {
1544
- const directory = await this.secureWorkspaceDirectory(path3.dirname(target));
1545
- const temporary = path3.join(directory, `.${path3.basename(target)}.zhivex-${randomUUID()}.tmp`);
1787
+ const directory = await this.secureWorkspaceDirectory(path4.dirname(target));
1788
+ const temporary = path4.join(directory, `.${path4.basename(target)}.zhivex-${randomUUID()}.tmp`);
1546
1789
  const handle = await open(temporary, "wx", mode);
1547
1790
  try {
1548
1791
  await handle.writeFile(contents);
@@ -1701,7 +1944,7 @@ class Workspace {
1701
1944
  const destination = await this.safePath(parsed.destination, { allowMissing: true });
1702
1945
  if (destination.exists)
1703
1946
  throw new Error(`Move destination already exists: ${parsed.destination}.`);
1704
- await this.secureWorkspaceDirectory(path3.dirname(destination.path));
1947
+ await this.secureWorkspaceDirectory(path4.dirname(destination.path));
1705
1948
  const destinationRecheck = await this.safePath(parsed.destination, { allowMissing: true });
1706
1949
  if (destinationRecheck.exists)
1707
1950
  throw new Error(`Move destination already exists: ${parsed.destination}.`);
@@ -1729,9 +1972,9 @@ class Workspace {
1729
1972
  const segments = [".zhivex-harness", "quarantine"];
1730
1973
  let current = this.root;
1731
1974
  for (const segment of segments) {
1732
- current = path3.join(current, segment);
1975
+ current = path4.join(current, segment);
1733
1976
  try {
1734
- const entry = await lstat(current);
1977
+ const entry = await lstat2(current);
1735
1978
  if (entry.isSymbolicLink()) {
1736
1979
  throw new Error("Harness quarantine must not resolve through a symbolic link.");
1737
1980
  }
@@ -1746,7 +1989,7 @@ class Workspace {
1746
1989
  } catch (mkdirError) {
1747
1990
  if (mkdirError.code !== "EEXIST")
1748
1991
  throw mkdirError;
1749
- const racedEntry = await lstat(current);
1992
+ const racedEntry = await lstat2(current);
1750
1993
  if (racedEntry.isSymbolicLink() || !racedEntry.isDirectory()) {
1751
1994
  throw new Error("Harness quarantine path changed during creation.");
1752
1995
  }
@@ -1760,19 +2003,19 @@ class Workspace {
1760
2003
  }
1761
2004
  async writeManifest(manifest) {
1762
2005
  const directory = await this.secureQuarantineDirectory(true);
1763
- const target = path3.join(directory, `${manifest.quarantineId}.json`);
2006
+ const target = path4.join(directory, `${manifest.quarantineId}.json`);
1764
2007
  const temporary = await this.stageFile(target, `${JSON.stringify(manifest, null, 2)}
1765
2008
  `, 384);
1766
2009
  await rename(temporary, target);
1767
2010
  }
1768
2011
  async readInternalRegularFile(target, label, maxBytes) {
1769
- const before = await lstat(target);
2012
+ const before = await lstat2(target);
1770
2013
  if (before.isSymbolicLink() || !before.isFile())
1771
2014
  throw new Error(`${label} must be a regular non-symlink file.`);
1772
2015
  if (before.size > maxBytes)
1773
2016
  throw new Error(`${label} exceeds its size limit.`);
1774
2017
  const contents = await readFile(target);
1775
- const after = await lstat(target);
2018
+ const after = await lstat2(target);
1776
2019
  if (after.isSymbolicLink() || !after.isFile() || before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size || before.mtimeMs !== after.mtimeMs) {
1777
2020
  throw new Error(`${label} changed while it was being read.`);
1778
2021
  }
@@ -1785,7 +2028,7 @@ class Workspace {
1785
2028
  throw new Error(`Stale quarantine rejected for ${parsed.path}.`);
1786
2029
  const quarantineId = `${Date.now()}-${randomUUID()}`;
1787
2030
  const directory = await this.secureQuarantineDirectory(true);
1788
- const dataPath = path3.join(directory, `${quarantineId}.data`);
2031
+ const dataPath = path4.join(directory, `${quarantineId}.data`);
1789
2032
  if ((await this.readStableFile(parsed.path)).digest !== parsed.expectedDigest)
1790
2033
  throw new Error(`Stale quarantine rejected for ${parsed.path}.`);
1791
2034
  await link(source.absolutePath, dataPath);
@@ -1807,7 +2050,7 @@ class Workspace {
1807
2050
  await unlink(source.absolutePath);
1808
2051
  } catch (error) {
1809
2052
  await unlink(dataPath).catch(() => {});
1810
- await unlink(path3.join(directory, `${quarantineId}.json`)).catch(() => {});
2053
+ await unlink(path4.join(directory, `${quarantineId}.json`)).catch(() => {});
1811
2054
  throw error;
1812
2055
  }
1813
2056
  this.invalidateWorkspaceIndex();
@@ -1819,7 +2062,7 @@ class Workspace {
1819
2062
  const directory = await this.secureQuarantineDirectory(false);
1820
2063
  let manifest;
1821
2064
  try {
1822
- const manifestFile = await this.readInternalRegularFile(path3.join(directory, `${parsed.quarantineId}.json`), "Quarantine manifest", 64 * 1024);
2065
+ const manifestFile = await this.readInternalRegularFile(path4.join(directory, `${parsed.quarantineId}.json`), "Quarantine manifest", 64 * 1024);
1823
2066
  manifest = JSON.parse(manifestFile.contents.toString("utf8"));
1824
2067
  } catch {
1825
2068
  throw new Error(`Quarantine entry was not found: ${parsed.quarantineId}.`);
@@ -1827,7 +2070,7 @@ class Workspace {
1827
2070
  if (manifest.schemaVersion !== 1 || manifest.quarantineId !== parsed.quarantineId || manifest.status !== "quarantined") {
1828
2071
  throw new Error(`Quarantine entry is not restorable: ${parsed.quarantineId}.`);
1829
2072
  }
1830
- const dataPath = path3.join(directory, `${parsed.quarantineId}.data`);
2073
+ const dataPath = path4.join(directory, `${parsed.quarantineId}.data`);
1831
2074
  const dataFile = await this.readInternalRegularFile(dataPath, "Quarantine payload", MAX_FILE_BYTES);
1832
2075
  const digest2 = digestBytes(dataFile.contents);
1833
2076
  if (digest2 !== manifest.digest || parsed.expectedDigest && parsed.expectedDigest !== digest2) {
@@ -1837,16 +2080,16 @@ class Workspace {
1837
2080
  const destination = await this.safePath(destinationPath, { allowMissing: true });
1838
2081
  if (destination.exists)
1839
2082
  throw new Error(`Restore destination already exists: ${destinationPath}.`);
1840
- await this.secureWorkspaceDirectory(path3.dirname(destination.path));
2083
+ await this.secureWorkspaceDirectory(path4.dirname(destination.path));
1841
2084
  const destinationRecheck = await this.safePath(destinationPath, { allowMissing: true });
1842
2085
  if (destinationRecheck.exists)
1843
2086
  throw new Error(`Restore destination already exists: ${destinationPath}.`);
1844
- const dataRecheck = await lstat(dataPath);
2087
+ const dataRecheck = await lstat2(dataPath);
1845
2088
  if (dataRecheck.isSymbolicLink() || !dataRecheck.isFile() || dataRecheck.dev !== dataFile.stat.dev || dataRecheck.ino !== dataFile.stat.ino || dataRecheck.size !== dataFile.stat.size || dataRecheck.mtimeMs !== dataFile.stat.mtimeMs) {
1846
2089
  throw new Error("Quarantine payload changed before restore commit.");
1847
2090
  }
1848
2091
  await link(dataPath, destination.path);
1849
- const linkedDestination = await lstat(destination.path);
2092
+ const linkedDestination = await lstat2(destination.path);
1850
2093
  if (linkedDestination.isSymbolicLink() || !linkedDestination.isFile() || linkedDestination.dev !== dataFile.stat.dev || linkedDestination.ino !== dataFile.stat.ino || digestBytes(await readFile(destination.path)) !== digest2) {
1851
2094
  await unlink(destination.path).catch(() => {});
1852
2095
  throw new Error("Restore destination did not match the quarantined payload.");
@@ -1884,24 +2127,17 @@ class Workspace {
1884
2127
  return { quarantineId: parsed.quarantineId, path: destinationPath, digest: digest2, audit };
1885
2128
  }
1886
2129
  async runCheck(check, expectedScript, allowedChecks = DEFAULT_CHECKS) {
1887
- if (!/^[A-Za-z0-9:_-]+$/.test(check) || !allowedChecks.includes(check)) {
1888
- throw new Error(`The check "${check}" is not in the explicit allowlist.`);
1889
- }
1890
- let scripts = {};
2130
+ let manifest;
1891
2131
  try {
1892
- const packageJson = JSON.parse((await this.readStableFile("package.json", false)).contents.toString("utf8"));
1893
- scripts = packageJson.scripts ?? {};
2132
+ manifest = JSON.parse((await this.readStableFile("package.json", false)).contents.toString("utf8"));
1894
2133
  } catch (error) {
1895
2134
  if (error.code === "ENOENT")
1896
2135
  throw new Error("The workspace does not contain a package.json file.");
1897
2136
  throw error;
1898
2137
  }
1899
- if (!scripts[check])
1900
- throw new Error(`package.json does not define the "${check}" script.`);
1901
- if (scripts[check] !== expectedScript)
1902
- throw new Error(`The "${check}" script changed or does not match expectedScript.`);
2138
+ const resolved = await resolvePackageCheckCommand(this.root, manifest, check, expectedScript, allowedChecks);
1903
2139
  try {
1904
- return await spawnBounded(["bun", "--no-env-file", "run", check], this.root);
2140
+ return await spawnBounded(resolved.command, this.root);
1905
2141
  } finally {
1906
2142
  this.invalidateWorkspaceIndex();
1907
2143
  }
@@ -1917,8 +2153,8 @@ class Workspace {
1917
2153
  }
1918
2154
 
1919
2155
  // src/operations.ts
1920
- import { chmod, lstat as lstat3, mkdir as mkdir2, open as open2 } from "fs/promises";
1921
- import path5 from "path";
2156
+ import { chmod, lstat as lstat4, mkdir as mkdir2, open as open2 } from "node:fs/promises";
2157
+ import path6 from "node:path";
1922
2158
  import {
1923
2159
  cancelAgentRun,
1924
2160
  cancelAgentRunTree,
@@ -1935,29 +2171,95 @@ import {
1935
2171
  createSqliteAgentRunStore
1936
2172
  } from "@zhivex-ai/agents/ops";
1937
2173
  import { createAgentRunLedger } from "@zhivex-ai/agents/control-plane";
1938
- import { Database } from "bun:sqlite";
2174
+
2175
+ // src/sqlite-database.ts
2176
+ import { createRequire } from "node:module";
2177
+ var require2 = createRequire(import.meta.url);
2178
+ var DatabaseSyncConstructor;
2179
+ var loadDatabaseSync = () => {
2180
+ DatabaseSyncConstructor ??= require2("node:sqlite").DatabaseSync;
2181
+ return DatabaseSyncConstructor;
2182
+ };
2183
+ var normalizeNumberedParameters = (sql) => {
2184
+ const indexes = new Set;
2185
+ const normalizedSql = sql.replace(/\?([1-9]\d*)/g, (_match, rawIndex) => {
2186
+ const index = Number(rawIndex);
2187
+ indexes.add(index);
2188
+ return `$__zhivex_${index}`;
2189
+ });
2190
+ return {
2191
+ sql: normalizedSql,
2192
+ indexes: [...indexes].sort((left, right) => left - right)
2193
+ };
2194
+ };
2195
+ var numberedBindings = (indexes, parameters) => {
2196
+ if (indexes.length === 0)
2197
+ return;
2198
+ const maximumIndex = indexes.at(-1);
2199
+ if (parameters.length !== maximumIndex) {
2200
+ throw new Error(`SQLite numbered parameters require exactly ${maximumIndex} binding${maximumIndex === 1 ? "" : "s"}; received ${parameters.length}.`);
2201
+ }
2202
+ return Object.fromEntries(indexes.map((index) => [`$__zhivex_${index}`, parameters[index - 1]]));
2203
+ };
2204
+
2205
+ class SqliteDatabase {
2206
+ #database;
2207
+ constructor(databasePath, options = {}) {
2208
+ options.create;
2209
+ options.strict;
2210
+ const DatabaseSync = loadDatabaseSync();
2211
+ this.#database = new DatabaseSync(databasePath, {
2212
+ ...options.readonly === undefined ? {} : { readOnly: options.readonly }
2213
+ });
2214
+ }
2215
+ exec(sql) {
2216
+ this.#database.exec(sql);
2217
+ }
2218
+ query(sql) {
2219
+ const normalized = normalizeNumberedParameters(sql);
2220
+ const statement = this.#database.prepare(normalized.sql);
2221
+ const invoke = (method, parameters) => {
2222
+ const bindings = numberedBindings(normalized.indexes, parameters);
2223
+ return bindings ? method(bindings) : method(...parameters);
2224
+ };
2225
+ return {
2226
+ run: (...parameters) => invoke(statement.run.bind(statement), parameters),
2227
+ get: (...parameters) => invoke(statement.get.bind(statement), parameters),
2228
+ all: (...parameters) => invoke(statement.all.bind(statement), parameters)
2229
+ };
2230
+ }
2231
+ close(throwOnError = true) {
2232
+ try {
2233
+ this.#database.close();
2234
+ } catch (error) {
2235
+ if (throwOnError) {
2236
+ throw error;
2237
+ }
2238
+ }
2239
+ }
2240
+ }
1939
2241
 
1940
2242
  // src/state-directory.ts
1941
- import { lstat as lstat2 } from "fs/promises";
1942
- import path4 from "path";
2243
+ import { lstat as lstat3 } from "node:fs/promises";
2244
+ import path5 from "node:path";
1943
2245
  var SENSITIVE_STATE_SEGMENTS = new Set([".git", ".env", ".npmrc", "dist", "node_modules", "src"]);
1944
2246
  var isInside2 = (root, candidate) => {
1945
- const relative = path4.relative(root, candidate);
1946
- return relative === "" || !relative.startsWith(`..${path4.sep}`) && relative !== ".." && !path4.isAbsolute(relative);
2247
+ const relative = path5.relative(root, candidate);
2248
+ return relative === "" || !relative.startsWith(`..${path5.sep}`) && relative !== ".." && !path5.isAbsolute(relative);
1947
2249
  };
1948
2250
  var validateStateDirectory = async (workspace, stateDirectory) => {
1949
- if (stateDirectory === workspace || stateDirectory === path4.parse(stateDirectory).root) {
2251
+ if (stateDirectory === workspace || stateDirectory === path5.parse(stateDirectory).root) {
1950
2252
  throw new Error("The state directory cannot be the workspace or filesystem root.");
1951
2253
  }
1952
2254
  const insideWorkspace = isInside2(workspace, stateDirectory);
1953
- const relativeSegments = insideWorkspace ? path4.relative(workspace, stateDirectory).split(path4.sep).filter(Boolean) : stateDirectory.slice(path4.parse(stateDirectory).root.length).split(path4.sep).filter(Boolean);
2255
+ const relativeSegments = insideWorkspace ? path5.relative(workspace, stateDirectory).split(path5.sep).filter(Boolean) : stateDirectory.slice(path5.parse(stateDirectory).root.length).split(path5.sep).filter(Boolean);
1954
2256
  const sensitiveSegment = insideWorkspace ? relativeSegments.find((segment) => SENSITIVE_STATE_SEGMENTS.has(segment)) : undefined;
1955
2257
  if (sensitiveSegment) {
1956
2258
  throw new Error(`The state directory is inside the protected workspace path: ${sensitiveSegment}.`);
1957
2259
  }
1958
2260
  if (!insideWorkspace) {
1959
2261
  try {
1960
- const externalEntry = await lstat2(stateDirectory);
2262
+ const externalEntry = await lstat3(stateDirectory);
1961
2263
  if (externalEntry.isSymbolicLink()) {
1962
2264
  throw new Error(`The state directory must not be a symbolic link: ${stateDirectory}.`);
1963
2265
  }
@@ -1973,9 +2275,9 @@ var validateStateDirectory = async (workspace, stateDirectory) => {
1973
2275
  }
1974
2276
  let current = workspace;
1975
2277
  for (const segment of relativeSegments) {
1976
- current = path4.join(current, segment);
2278
+ current = path5.join(current, segment);
1977
2279
  try {
1978
- const entry = await lstat2(current);
2280
+ const entry = await lstat3(current);
1979
2281
  if (entry.isSymbolicLink()) {
1980
2282
  throw new Error(`The state directory must not resolve through a symbolic link: ${current}.`);
1981
2283
  }
@@ -2162,7 +2464,7 @@ var sqliteAdapter = (database) => ({
2162
2464
  });
2163
2465
  var ensurePrivateStateDirectory = async (directory) => {
2164
2466
  await mkdir2(directory, { recursive: true, mode: 448 });
2165
- const entry = await lstat3(directory);
2467
+ const entry = await lstat4(directory);
2166
2468
  if (entry.isSymbolicLink() || !entry.isDirectory()) {
2167
2469
  throw new Error(`The state directory must be a real directory: ${directory}.`);
2168
2470
  }
@@ -2218,17 +2520,17 @@ var openHarnessPersistence = async (config, options = {}) => {
2218
2520
  backend: "file",
2219
2521
  store,
2220
2522
  memory: createFileAgentMemoryStore({
2221
- directory: path5.join(config.stateDirectory, "memory"),
2523
+ directory: path6.join(config.stateDirectory, "memory"),
2222
2524
  scope: config.scope
2223
2525
  }),
2224
2526
  migration,
2225
2527
  close() {}
2226
2528
  };
2227
2529
  }
2228
- const databasePath = path5.join(config.stateDirectory, HARNESS_SQLITE_FILE);
2530
+ const databasePath = path6.join(config.stateDirectory, HARNESS_SQLITE_FILE);
2229
2531
  let databaseEntry;
2230
2532
  try {
2231
- databaseEntry = await lstat3(databasePath);
2533
+ databaseEntry = await lstat4(databasePath);
2232
2534
  } catch (error) {
2233
2535
  if (error.code !== "ENOENT") {
2234
2536
  throw error;
@@ -2241,12 +2543,12 @@ var openHarnessPersistence = async (config, options = {}) => {
2241
2543
  throw creationError;
2242
2544
  }
2243
2545
  }
2244
- databaseEntry = await lstat3(databasePath);
2546
+ databaseEntry = await lstat4(databasePath);
2245
2547
  }
2246
2548
  if (databaseEntry.isSymbolicLink() || !databaseEntry.isFile()) {
2247
2549
  throw new Error(`The SQLite state path must be a real file: ${databasePath}.`);
2248
2550
  }
2249
- const database = new Database(databasePath, { create: false, strict: true });
2551
+ const database = new SqliteDatabase(databasePath, { create: false, strict: true });
2250
2552
  let closed = false;
2251
2553
  const closeDatabase = () => {
2252
2554
  if (!closed) {
@@ -2255,7 +2557,7 @@ var openHarnessPersistence = async (config, options = {}) => {
2255
2557
  }
2256
2558
  };
2257
2559
  try {
2258
- const openedEntry = await lstat3(databasePath);
2560
+ const openedEntry = await lstat4(databasePath);
2259
2561
  if (openedEntry.isSymbolicLink() || !openedEntry.isFile() || openedEntry.dev !== databaseEntry.dev || openedEntry.ino !== databaseEntry.ino) {
2260
2562
  throw new Error(`The SQLite state path changed while it was being opened: ${databasePath}.`);
2261
2563
  }
@@ -2282,8 +2584,8 @@ var openHarnessPersistence = async (config, options = {}) => {
2282
2584
  };
2283
2585
 
2284
2586
  // src/mcp.ts
2285
- import { lstat as lstat4, readFile as readFile2, realpath as realpath2 } from "fs/promises";
2286
- import path6 from "path";
2587
+ import { lstat as lstat5, readFile as readFile2, realpath as realpath2 } from "node:fs/promises";
2588
+ import path7 from "node:path";
2287
2589
  import {
2288
2590
  createMcpToolSet,
2289
2591
  serializeJsonValue
@@ -2291,15 +2593,19 @@ import {
2291
2593
  import { z as z2 } from "zod";
2292
2594
 
2293
2595
  // src/version.ts
2294
- import { createRequire } from "module";
2295
- var packageMetadata = createRequire(import.meta.url)("../package.json");
2596
+ import { createRequire as createRequire2 } from "node:module";
2597
+ var packageMetadata = createRequire2(import.meta.url)("../package.json");
2296
2598
  if (typeof packageMetadata.version !== "string" || packageMetadata.version.length === 0) {
2297
2599
  throw new Error("package.json must contain a non-empty version.");
2298
2600
  }
2601
+ if (typeof packageMetadata.engines?.node !== "string" || packageMetadata.engines.node.length === 0) {
2602
+ throw new Error("package.json must contain a non-empty engines.node requirement.");
2603
+ }
2299
2604
  if (typeof packageMetadata.engines?.bun !== "string" || packageMetadata.engines.bun.length === 0) {
2300
2605
  throw new Error("package.json must contain a non-empty engines.bun requirement.");
2301
2606
  }
2302
2607
  var HARNESS_VERSION = packageMetadata.version;
2608
+ var NODE_ENGINE_RANGE = packageMetadata.engines.node;
2303
2609
  var BUN_ENGINE_RANGE = packageMetadata.engines.bun;
2304
2610
 
2305
2611
  // src/mcp.ts
@@ -2333,8 +2639,8 @@ var configurationSchema = z2.object({
2333
2639
  servers: z2.array(serverSchema).max(20)
2334
2640
  }).strict();
2335
2641
  var isInsidePath = (root, candidate) => {
2336
- const relative = path6.relative(root, candidate);
2337
- return relative === "" || relative !== ".." && !relative.startsWith(`..${path6.sep}`) && !path6.isAbsolute(relative);
2642
+ const relative = path7.relative(root, candidate);
2643
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path7.sep}`) && !path7.isAbsolute(relative);
2338
2644
  };
2339
2645
  var validatedHttpUrl = (value, serverName) => {
2340
2646
  let url;
@@ -2418,7 +2724,7 @@ var normalizeHarnessMcpConfiguration = (value) => {
2418
2724
  return { schemaVersion: HARNESS_MCP_CONFIG_SCHEMA_VERSION, servers };
2419
2725
  };
2420
2726
  var loadHarnessMcpConfiguration = async (workspace, configPath) => {
2421
- const entry = await lstat4(configPath);
2727
+ const entry = await lstat5(configPath);
2422
2728
  if (entry.isSymbolicLink() || !entry.isFile()) {
2423
2729
  throw new Error(`MCP configuration must be a regular non-symlink file: ${configPath}.`);
2424
2730
  }
@@ -2673,7 +2979,7 @@ var createHarnessMcpTools = async (configuration, options = {}) => {
2673
2979
  var mcpConfigurationFingerprintInput = (configuration) => serializeJsonValue(configuration);
2674
2980
 
2675
2981
  // src/orchestration.ts
2676
- import { createHash as createHash5, randomUUID as randomUUID2 } from "crypto";
2982
+ import { createHash as createHash5, randomUUID as randomUUID2 } from "node:crypto";
2677
2983
  import {
2678
2984
  applySafetyPolicyToAgent,
2679
2985
  createBudgetGuard,
@@ -2698,8 +3004,8 @@ var HARNESS_SUBAGENT_PROFILE_DESCRIPTORS = [
2698
3004
  {
2699
3005
  id: "tester",
2700
3006
  toolName: "delegate_tester",
2701
- description: "Delegate focused verification to a tester with read-only inspection and approved Bun checks.",
2702
- instructions: "Verify the delegated behavior with repository evidence and declared Bun checks. Do not edit files. Report exact failures and limits.",
3007
+ description: "Delegate focused verification to a tester with read-only inspection and approved package-manager checks.",
3008
+ instructions: "Verify the delegated behavior with repository evidence and declared package-manager checks. Do not edit files. Report exact failures and limits.",
2703
3009
  toolNames: ["list_files", "read_file", "read_files", "search_files", "search_many", "run_check", "git_diff"]
2704
3010
  },
2705
3011
  {
@@ -2844,12 +3150,13 @@ var runHarnessReviewGroup = async (runtime, input, profiles = ["explorer", "revi
2844
3150
  };
2845
3151
 
2846
3152
  // src/execution-environment.ts
2847
- import { createHash as createHash6, randomUUID as randomUUID3 } from "crypto";
2848
- import { constants as fsConstants } from "fs";
3153
+ import { createHash as createHash6, randomUUID as randomUUID3 } from "node:crypto";
3154
+ import { spawn as spawn2 } from "node:child_process";
3155
+ import { constants as fsConstants } from "node:fs";
2849
3156
  import {
2850
3157
  chmod as chmod2,
2851
3158
  copyFile,
2852
- lstat as lstat5,
3159
+ lstat as lstat6,
2853
3160
  mkdir as mkdir3,
2854
3161
  readFile as readFile3,
2855
3162
  readdir as readdir2,
@@ -2857,12 +3164,13 @@ import {
2857
3164
  rename as rename2,
2858
3165
  rm,
2859
3166
  writeFile
2860
- } from "fs/promises";
2861
- import path7 from "path";
3167
+ } from "node:fs/promises";
3168
+ import path8 from "node:path";
2862
3169
  import { createAgentExecutionEnvironmentBinding } from "@zhivex-ai/agents/beta";
2863
3170
  var HARNESS_EXECUTION_ARTIFACT_SCHEMA_VERSION = 1;
2864
3171
  var HARNESS_OCI_LABEL = "com.zhivex.harness.execution";
2865
3172
  var HARNESS_OCI_LABEL_VALUE = "v1";
3173
+ var HARNESS_OCI_OWNER_PID_LABEL = "com.zhivex.harness.owner-pid";
2866
3174
  var TERMINAL_STATUSES = new Set([
2867
3175
  "completed",
2868
3176
  "failed",
@@ -2877,6 +3185,7 @@ var BUILT_IN_TOOL_NAMES = new Set([
2877
3185
  "search_many",
2878
3186
  "propose_edits",
2879
3187
  "apply_patch",
3188
+ "apply_reviewed_edits",
2880
3189
  "move_file",
2881
3190
  "quarantine_file",
2882
3191
  "restore_file",
@@ -2884,19 +3193,22 @@ var BUILT_IN_TOOL_NAMES = new Set([
2884
3193
  "mutation_audit",
2885
3194
  "git_diff",
2886
3195
  "run_environment_command",
3196
+ "run_environment_batch",
2887
3197
  "inspect_environment_patch",
2888
3198
  "apply_environment_patch",
3199
+ "verify_and_apply_environment_patch",
3200
+ "verify_and_apply_reviewed_edits",
2889
3201
  "environment_status"
2890
3202
  ]);
2891
3203
  var digest2 = (value) => `sha256:${createHash6("sha256").update(value).digest("hex")}`;
2892
3204
  var runHash = (runId) => createHash6("sha256").update(runId).digest("hex").slice(0, 24);
2893
3205
  var isInside3 = (root, candidate) => {
2894
- const relative = path7.relative(root, candidate);
2895
- return relative === "" || relative !== ".." && !relative.startsWith(`..${path7.sep}`) && !path7.isAbsolute(relative);
3206
+ const relative = path8.relative(root, candidate);
3207
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path8.sep}`) && !path8.isAbsolute(relative);
2896
3208
  };
2897
3209
  var privateDirectory = async (directory) => {
2898
3210
  await mkdir3(directory, { recursive: true, mode: 448 });
2899
- const entry = await lstat5(directory);
3211
+ const entry = await lstat6(directory);
2900
3212
  if (entry.isSymbolicLink() || !entry.isDirectory()) {
2901
3213
  throw new Error(`Execution artifact directory must be a real directory: ${directory}.`);
2902
3214
  }
@@ -2909,14 +3221,19 @@ var atomicJson = async (target, value) => {
2909
3221
  await rename2(temporary, target);
2910
3222
  };
2911
3223
  var commandDisplay = (command) => command.map((value) => /^[A-Za-z0-9_./:=+-]+$/.test(value) ? value : JSON.stringify(value)).join(" ");
3224
+ var elapsedMilliseconds = (startedAt) => Number(process.hrtime.bigint() - startedAt) / 1e6;
2912
3225
  var spawnBounded2 = async (command, options) => {
2913
- const child = Bun.spawn(command, {
3226
+ const child = spawn2(command[0], command.slice(1), {
2914
3227
  ...options.cwd ? { cwd: options.cwd } : {},
2915
3228
  env: options.env ?? {},
2916
- stdin: "ignore",
2917
- stdout: "pipe",
2918
- stderr: "pipe"
3229
+ stdio: [options.input === undefined ? "ignore" : "pipe", "pipe", "pipe"]
2919
3230
  });
3231
+ if (options.input !== undefined) {
3232
+ child.stdin?.on("error", () => {
3233
+ return;
3234
+ });
3235
+ child.stdin?.end(options.input);
3236
+ }
2920
3237
  let timedOut = false;
2921
3238
  let cancelled = false;
2922
3239
  let outputLimitExceeded = false;
@@ -2944,22 +3261,19 @@ var spawnBounded2 = async (command, options) => {
2944
3261
  options.abortSignal?.addEventListener("abort", onAbort, { once: true });
2945
3262
  }
2946
3263
  const consume = async (stream) => {
2947
- const reader = stream.getReader();
2948
3264
  const chunks = [];
2949
3265
  let omittedBytes = 0;
2950
- for (;; ) {
2951
- const next = await reader.read();
2952
- if (next.done)
2953
- break;
2954
- totalOutputBytes += next.value.byteLength;
3266
+ for await (const value of stream) {
3267
+ const chunk = Buffer.isBuffer(value) ? value : Buffer.from(value);
3268
+ totalOutputBytes += chunk.byteLength;
2955
3269
  if (retainedOutputBytes < options.maxOutputBytes) {
2956
3270
  const remaining = options.maxOutputBytes - retainedOutputBytes;
2957
- const kept = next.value.byteLength <= remaining ? next.value : next.value.slice(0, remaining);
3271
+ const kept = chunk.byteLength <= remaining ? chunk : chunk.subarray(0, remaining);
2958
3272
  chunks.push(kept);
2959
3273
  retainedOutputBytes += kept.byteLength;
2960
- omittedBytes += next.value.byteLength - kept.byteLength;
3274
+ omittedBytes += chunk.byteLength - kept.byteLength;
2961
3275
  } else {
2962
- omittedBytes += next.value.byteLength;
3276
+ omittedBytes += chunk.byteLength;
2963
3277
  }
2964
3278
  if (totalOutputBytes > options.maxOutputBytes) {
2965
3279
  outputLimitExceeded = true;
@@ -2968,13 +3282,17 @@ var spawnBounded2 = async (command, options) => {
2968
3282
  }
2969
3283
  const text = new TextDecoder().decode(Buffer.concat(chunks));
2970
3284
  return omittedBytes > 0 ? `${text}
2971
- \u2026 output limit exceeded (${omittedBytes} bytes omitted)` : text;
3285
+ … output limit exceeded (${omittedBytes} bytes omitted)` : text;
2972
3286
  };
3287
+ const exited = new Promise((resolve, reject) => {
3288
+ child.once("error", reject);
3289
+ child.once("exit", (exitCode) => resolve(exitCode ?? 137));
3290
+ });
2973
3291
  try {
2974
3292
  const [stdout, stderr, exitCode] = await Promise.all([
2975
3293
  consume(child.stdout),
2976
3294
  consume(child.stderr),
2977
- child.exited
3295
+ exited
2978
3296
  ]);
2979
3297
  return {
2980
3298
  command,
@@ -2998,6 +3316,16 @@ var parseRuntimeVersion = (stdout, runtime) => {
2998
3316
  };
2999
3317
  var validContainerIds = (stdout) => stdout.split(/\r?\n/).map((value) => value.trim()).filter((value) => /^[a-f0-9]{12,64}$/.test(value));
3000
3318
  var validVolumeNames = (stdout) => stdout.split(/\r?\n/).map((value) => value.trim()).filter((value) => /^zhivex-harness-[A-Za-z0-9_.-]+$/.test(value));
3319
+ var hostProcessIsAlive = (pid) => {
3320
+ if (!Number.isSafeInteger(pid) || pid < 1)
3321
+ return false;
3322
+ try {
3323
+ process.kill(pid, 0);
3324
+ return true;
3325
+ } catch (error) {
3326
+ return error.code === "EPERM";
3327
+ }
3328
+ };
3001
3329
  var MAX_SYNC_ENTRIES = 1e4;
3002
3330
  var updateSnapshotMetadata = (target, kind, relative, entry) => {
3003
3331
  target.update([
@@ -3020,7 +3348,7 @@ var validateSynchronizedSnapshot = async (stagedRoot, previousRoot, maxWorkspace
3020
3348
  const visit = async (directory, relativeDirectory = "") => {
3021
3349
  for (const name of (await readdir2(directory)).sort()) {
3022
3350
  const relative = relativeDirectory ? `${relativeDirectory}/${name}` : name;
3023
- const absolute = path7.join(directory, name);
3351
+ const absolute = path8.join(directory, name);
3024
3352
  if (!isInside3(stagedRoot, absolute) || relative.length > 1024) {
3025
3353
  throw new Error(`OCI workspace export contains an invalid path: ${relative}.`);
3026
3354
  }
@@ -3032,7 +3360,7 @@ var validateSynchronizedSnapshot = async (stagedRoot, previousRoot, maxWorkspace
3032
3360
  await rm(absolute, { recursive: true, force: true });
3033
3361
  continue;
3034
3362
  }
3035
- const entry = await lstat5(absolute);
3363
+ const entry = await lstat6(absolute);
3036
3364
  if (entry.isSymbolicLink()) {
3037
3365
  throw new Error(`OCI workspace export contains a symbolic link: ${relative}.`);
3038
3366
  }
@@ -3046,7 +3374,7 @@ var validateSynchronizedSnapshot = async (stagedRoot, previousRoot, maxWorkspace
3046
3374
  throw new Error(`OCI workspace export contains a special or linked file: ${relative}.`);
3047
3375
  }
3048
3376
  const contents = await readFile3(absolute);
3049
- const after = await lstat5(absolute);
3377
+ const after = await lstat6(absolute);
3050
3378
  if (after.isSymbolicLink() || !after.isFile() || after.nlink !== 1 || entry.dev !== after.dev || entry.ino !== after.ino || entry.size !== after.size || entry.mtimeMs !== after.mtimeMs || entry.ctimeMs !== after.ctimeMs || (entry.mode & 511) !== (after.mode & 511)) {
3051
3379
  throw new Error(`OCI workspace export changed while it was being validated: ${relative}.`);
3052
3380
  }
@@ -3058,10 +3386,10 @@ var validateSynchronizedSnapshot = async (stagedRoot, previousRoot, maxWorkspace
3058
3386
  updateSnapshotMetadata(metadata, "file", relative, after);
3059
3387
  seal.update(`file\x00${relative}\x00${entry.mode & 511}\x00${entry.size}\x00${contentDigest}\x00`);
3060
3388
  if (entry.size > maxFileWriteBytes) {
3061
- const previous = path7.join(previousRoot, ...relative.split("/"));
3389
+ const previous = path8.join(previousRoot, ...relative.split("/"));
3062
3390
  let unchanged = false;
3063
3391
  try {
3064
- const previousEntry = await lstat5(previous);
3392
+ const previousEntry = await lstat6(previous);
3065
3393
  unchanged = previousEntry.isFile() && !previousEntry.isSymbolicLink() && previousEntry.size === entry.size && digest2(await readFile3(previous)) === contentDigest;
3066
3394
  } catch (error) {
3067
3395
  if (error.code !== "ENOENT")
@@ -3090,7 +3418,7 @@ var snapshotMetadataFingerprint = async (root, maxWorkspaceBytes) => {
3090
3418
  const relative = relativeDirectory ? `${relativeDirectory}/${name}` : name;
3091
3419
  if (relative === "node_modules")
3092
3420
  continue;
3093
- const absolute = path7.join(directory, name);
3421
+ const absolute = path8.join(directory, name);
3094
3422
  if (!isInside3(root, absolute) || relative.length > 1024) {
3095
3423
  throw new Error(`OCI workspace snapshot contains an invalid path: ${relative}.`);
3096
3424
  }
@@ -3098,7 +3426,7 @@ var snapshotMetadataFingerprint = async (root, maxWorkspaceBytes) => {
3098
3426
  if (entries > MAX_SYNC_ENTRIES) {
3099
3427
  throw new Error(`OCI workspace snapshot exceeds the ${MAX_SYNC_ENTRIES}-entry limit.`);
3100
3428
  }
3101
- const entry = await lstat5(absolute);
3429
+ const entry = await lstat6(absolute);
3102
3430
  if (entry.isSymbolicLink()) {
3103
3431
  throw new Error(`OCI workspace snapshot contains a symbolic link: ${relative}.`);
3104
3432
  }
@@ -3133,14 +3461,37 @@ var replaceSnapshot = async (snapshotRoot, stagedRoot) => {
3133
3461
  }
3134
3462
  await rm(backupRoot, { recursive: true, force: true });
3135
3463
  };
3136
- var OCI_WORKSPACE_SEAL_SCRIPT = [
3464
+ var OCI_BACKGROUND_PROCESS_MARKER = "ZHIVEX_HARNESS_BACKGROUND_PROCESS";
3465
+ var OCI_COMMAND_TIMEOUT_MARKER = "ZHIVEX_HARNESS_COMMAND_TIMEOUT";
3466
+ var OCI_ATTESTATION_TIMEOUT_MS = 60000;
3467
+ var OCI_ATTESTATION_PROTOCOL_BYTES = 160;
3468
+ var OCI_ATTESTATION_PATTERN = /\nZHIVEX_HARNESS_ATTESTATION:(sha256:[a-f0-9]{64})\n$/;
3469
+ var OCI_COMMAND_RUNNER_SCRIPT = [
3137
3470
  "import { createHash } from 'node:crypto';",
3471
+ "import { spawn } from 'node:child_process';",
3472
+ "import { readFileSync } from 'node:fs';",
3138
3473
  "import { lstat, readFile, readdir, readlink, rm } from 'node:fs/promises';",
3139
3474
  "import path from 'node:path';",
3475
+ "const request=JSON.parse(readFileSync(0,'utf8'));",
3476
+ "if(!Array.isArray(request.commands)||request.commands.length===0||!request.commands.every((command)=>Array.isArray(command)&&command.length>0&&command.every((value)=>typeof value==='string')))throw new Error('invalid OCI command batch');",
3477
+ "if(!Number.isSafeInteger(request.maxProcessRuntimeMs)||request.maxProcessRuntimeMs<1)throw new Error('invalid OCI command timeout');",
3478
+ "const deadline=Date.now()+request.maxProcessRuntimeMs;",
3479
+ "const run=async(command,timeoutMs)=>await new Promise((resolve,reject)=>{",
3480
+ "const child=spawn(command[0],command.slice(1),{stdio:['ignore','inherit','inherit'],detached:true});",
3481
+ "let settled=false;let timedOut=false;",
3482
+ "const finish=(value,error)=>{if(settled)return;settled=true;clearTimeout(timer);error?reject(error):resolve(value);};",
3483
+ "const timer=setTimeout(()=>{timedOut=true;try{process.kill(-child.pid,'SIGKILL');}catch{}},timeoutMs);",
3484
+ "child.once('error',(error)=>finish(undefined,error));",
3485
+ "child.once('exit',(code)=>finish({exitCode:timedOut?124:(code??137),timedOut}));",
3486
+ "});",
3487
+ "for(const command of request.commands){const remaining=deadline-Date.now();if(remaining<=0){console.error('ZHIVEX_HARNESS_COMMAND_TIMEOUT');process.exit(124);}const result=await run(command,remaining);if(result.timedOut){console.error('ZHIVEX_HARNESS_COMMAND_TIMEOUT');process.exit(124);}if(result.exitCode!==0)process.exit(result.exitCode);}",
3488
+ "const unexpectedProcesses=async()=>(await readdir('/proc')).filter((name)=>/^\\d+$/.test(name)).map(Number).filter((pid)=>pid!==1&&pid!==process.pid);",
3489
+ "if((await unexpectedProcesses()).length>0){console.error('ZHIVEX_HARNESS_BACKGROUND_PROCESS');process.exit(126);}",
3140
3490
  "const root='/workspace';",
3141
3491
  "const seal=createHash('sha256');",
3142
3492
  "const modules=path.join(root,'node_modules');",
3143
- "if(process.env.ZHIVEX_HARNESS_HAS_DEPENDENCIES==='1'){const entry=await lstat(modules);if(!entry.isSymbolicLink()||(await readlink(modules))!=='/dependencies')throw new Error('read-only dependency mount was replaced');}else{await rm(modules,{recursive:true,force:true});}",
3493
+ "try{",
3494
+ "if(request.hasDependencies===true){const entry=await lstat(modules);if(!entry.isSymbolicLink()||(await readlink(modules))!=='/dependencies')throw new Error('read-only dependency mount was replaced');}else{await rm(modules,{recursive:true,force:true});}",
3144
3495
  "const digest=(value)=>`sha256:${createHash('sha256').update(value).digest('hex')}`;",
3145
3496
  "const same=(before,after)=>before.dev===after.dev&&before.ino===after.ino&&before.size===after.size&&before.mtimeMs===after.mtimeMs&&before.ctimeMs===after.ctimeMs&&(before.mode&0o777)===(after.mode&0o777);",
3146
3497
  "const visit=async(directory,base='')=>{",
@@ -3159,8 +3510,10 @@ var OCI_WORKSPACE_SEAL_SCRIPT = [
3159
3510
  "}",
3160
3511
  "};",
3161
3512
  "await visit(root);",
3513
+ "if((await unexpectedProcesses()).length>0){console.error('ZHIVEX_HARNESS_BACKGROUND_PROCESS');process.exit(126);}",
3162
3514
  "for(const name of await readdir('/tmp'))await rm(path.join('/tmp',name),{recursive:true,force:true});",
3163
- "console.log(`sha256:${seal.digest('hex')}`);"
3515
+ "process.stdout.write(`\\nZHIVEX_HARNESS_ATTESTATION:sha256:${seal.digest('hex')}\\n`);",
3516
+ "}catch(error){console.error(`ZHIVEX_HARNESS_ATTESTATION_FAILURE:${error instanceof Error?error.message:String(error)}`);process.exit(127);}"
3164
3517
  ].join("");
3165
3518
  var OCI_SESSION_CONTROLLER_SCRIPT = "setInterval(()=>{},2147483647);";
3166
3519
 
@@ -3277,6 +3630,8 @@ ${result.stdout}`)) {
3277
3630
  `${HARNESS_OCI_LABEL}=${HARNESS_OCI_LABEL_VALUE}`,
3278
3631
  "--label",
3279
3632
  `com.zhivex.harness.run=${runHash(request.runId)}`,
3633
+ "--label",
3634
+ `${HARNESS_OCI_OWNER_PID_LABEL}=${process.pid}`,
3280
3635
  volumeName
3281
3636
  ];
3282
3637
  const args = [
@@ -3311,15 +3666,16 @@ ${result.stdout}`)) {
3311
3666
  "--env",
3312
3667
  "TMPDIR=/tmp",
3313
3668
  "--env",
3314
- "BUN_INSTALL_CACHE_DIR=/tmp/bun-cache",
3669
+ "NPM_CONFIG_CACHE=/tmp/npm-cache",
3315
3670
  "--env",
3316
3671
  "CI=1",
3317
3672
  ...request.dependencyRoot ? ["--volume", `${request.dependencyRoot}:/dependencies:ro`] : [],
3318
3673
  "--workdir",
3319
3674
  "/workspace",
3320
3675
  "--entrypoint",
3321
- "bun",
3676
+ "node",
3322
3677
  request.imageId,
3678
+ "--input-type=module",
3323
3679
  "-e",
3324
3680
  OCI_SESSION_CONTROLLER_SCRIPT
3325
3681
  ];
@@ -3332,7 +3688,7 @@ ${result.stdout}`)) {
3332
3688
  await this.cli([
3333
3689
  "cp",
3334
3690
  "-a",
3335
- `${request.snapshotRoot}${path7.sep}.`,
3691
+ `${request.snapshotRoot}${path8.sep}.`,
3336
3692
  `${name}:/workspace`
3337
3693
  ], 60000);
3338
3694
  if (request.dependencyRoot) {
@@ -3343,20 +3699,19 @@ ${result.stdout}`)) {
3343
3699
  "--user",
3344
3700
  `${uid}:${gid}`,
3345
3701
  name,
3346
- "bun",
3702
+ "node",
3703
+ "--input-type=module",
3347
3704
  "-e",
3348
3705
  "import { symlink } from 'node:fs/promises'; await symlink('/dependencies', '/workspace/node_modules', 'dir')"
3349
3706
  ]);
3350
3707
  }
3351
- await this.cli(["pause", name]);
3352
3708
  const session = {
3353
3709
  key: runHash(request.runId),
3354
3710
  requestFingerprint,
3355
3711
  name,
3356
3712
  volumeName,
3357
3713
  snapshotSeal,
3358
- hostFingerprint,
3359
- paused: true
3714
+ hostFingerprint
3360
3715
  };
3361
3716
  this.sessions.set(session.key, session);
3362
3717
  return session;
@@ -3369,109 +3724,100 @@ ${result.stdout}`)) {
3369
3724
  throw error;
3370
3725
  }
3371
3726
  }
3372
- async onlyControllerProcessRemains(session) {
3373
- const result = await this.cli(this.runtime === "podman" ? ["top", session.name, "pid"] : ["top", session.name, "-eo", "pid"]);
3374
- const processIds = result.stdout.split(/\r?\n/).filter((line) => /^\s*\d+\s*$/.test(line));
3375
- return processIds.length === 1;
3376
- }
3377
- async containerWorkspaceSeal(session, uid, gid, hasDependencies) {
3378
- if (session.paused) {
3379
- await this.cli(["unpause", session.name]);
3380
- session.paused = false;
3381
- }
3382
- try {
3383
- const result = await this.cli([
3384
- "exec",
3385
- "--workdir",
3386
- "/workspace",
3387
- "--user",
3388
- `${uid}:${gid}`,
3389
- "--env",
3390
- `ZHIVEX_HARNESS_HAS_DEPENDENCIES=${hasDependencies ? "1" : "0"}`,
3391
- session.name,
3392
- "bun",
3393
- "-e",
3394
- OCI_WORKSPACE_SEAL_SCRIPT
3395
- ], 60000, 20000);
3396
- const seal = result.stdout.trim();
3397
- if (!/^sha256:[a-f0-9]{64}$/.test(seal)) {
3398
- throw new Error("OCI workspace seal helper returned an invalid digest.");
3399
- }
3400
- return seal;
3401
- } finally {
3402
- if (!session.paused) {
3403
- await this.cli(["pause", session.name]);
3404
- session.paused = true;
3405
- }
3406
- }
3407
- }
3408
3727
  async runInSession(request, session, sessionReused) {
3728
+ const totalStartedAt = process.hrtime.bigint();
3409
3729
  const uid = process.getuid?.() ?? 65532;
3410
3730
  const gid = process.getgid?.() ?? 65532;
3411
- if (session.paused) {
3412
- await this.cli(["unpause", session.name]);
3413
- session.paused = false;
3414
- }
3415
3731
  let forcedRemoval;
3416
3732
  const forceRemove = () => {
3417
3733
  forcedRemoval ??= this.forceRemove(session.name);
3418
3734
  };
3735
+ const commandStartedAt = process.hrtime.bigint();
3419
3736
  const result = await spawnBounded2([
3420
3737
  this.runtime,
3421
3738
  "exec",
3739
+ "-i",
3422
3740
  "--workdir",
3423
3741
  "/workspace",
3424
3742
  "--user",
3425
3743
  `${uid}:${gid}`,
3426
3744
  session.name,
3427
- ...request.command
3745
+ "node",
3746
+ "--input-type=module",
3747
+ "-e",
3748
+ OCI_COMMAND_RUNNER_SCRIPT
3428
3749
  ], {
3429
3750
  env: this.hostEnvironment,
3430
- timeoutMs: request.limits.maxProcessRuntimeMs,
3431
- maxOutputBytes: request.limits.maxProcessOutputBytes,
3751
+ timeoutMs: request.limits.maxProcessRuntimeMs + OCI_ATTESTATION_TIMEOUT_MS,
3752
+ maxOutputBytes: request.limits.maxProcessOutputBytes + OCI_ATTESTATION_PROTOCOL_BYTES,
3753
+ input: JSON.stringify({
3754
+ commands: request.commands,
3755
+ maxProcessRuntimeMs: request.limits.maxProcessRuntimeMs,
3756
+ hasDependencies: Boolean(request.dependencyRoot)
3757
+ }),
3432
3758
  ...request.abortSignal ? { abortSignal: request.abortSignal } : {},
3433
3759
  onStop: forceRemove
3434
3760
  });
3435
- if (result.exitCode !== 0 || result.timedOut || result.cancelled || result.outputLimitExceeded) {
3761
+ const commandAndAttestationMs = elapsedMilliseconds(commandStartedAt);
3762
+ const attestationMatch = result.exitCode === 0 ? result.stdout.match(OCI_ATTESTATION_PATTERN) : null;
3763
+ const commandResult = attestationMatch ? { ...result, stdout: result.stdout.slice(0, -attestationMatch[0].length) } : result;
3764
+ if (Buffer.byteLength(commandResult.stdout) + Buffer.byteLength(commandResult.stderr) > request.limits.maxProcessOutputBytes) {
3765
+ commandResult.outputLimitExceeded = true;
3766
+ }
3767
+ const command = request.commands.length === 1 ? request.commands[0] : ["<oci-batch>", String(request.commands.length)];
3768
+ const backgroundProcess = commandResult.exitCode === 126 && commandResult.stderr.includes(OCI_BACKGROUND_PROCESS_MARKER);
3769
+ const commandTimedOut = commandResult.exitCode === 124 && commandResult.stderr.includes(OCI_COMMAND_TIMEOUT_MARKER);
3770
+ const attestationFailure = commandResult.exitCode === 127 ? commandResult.stderr.match(/ZHIVEX_HARNESS_ATTESTATION_FAILURE:([^\r\n]*)/) : null;
3771
+ if (commandResult.exitCode !== 0 || commandResult.timedOut || commandResult.cancelled || commandResult.outputLimitExceeded) {
3436
3772
  forceRemove();
3437
3773
  await forcedRemoval;
3438
3774
  await this.destroySession(session);
3775
+ if (attestationFailure) {
3776
+ throw new Error(`OCI workspace attestation failed: ${attestationFailure[1] ?? "unknown failure"}.`);
3777
+ }
3439
3778
  return {
3440
- ...result,
3441
- command: request.command,
3779
+ ...commandResult,
3780
+ command,
3781
+ commands: request.commands,
3782
+ ...commandTimedOut ? { timedOut: true, exitCode: 124 } : {},
3783
+ ...backgroundProcess ? {
3784
+ exitCode: 126,
3785
+ stderr: [
3786
+ commandResult.stderr.replace(OCI_BACKGROUND_PROCESS_MARKER, "").trim(),
3787
+ "OCI command left background processes running; its workspace changes were discarded."
3788
+ ].filter(Boolean).join(`
3789
+ `)
3790
+ } : {},
3442
3791
  sessionReused,
3443
3792
  workspacePublished: false,
3444
- workspaceExported: false
3793
+ workspaceExported: false,
3794
+ phaseLatencies: {
3795
+ commandAndAttestationMs,
3796
+ totalMs: elapsedMilliseconds(totalStartedAt)
3797
+ }
3445
3798
  };
3446
3799
  }
3447
3800
  try {
3448
- if (!await this.onlyControllerProcessRemains(session)) {
3449
- await this.destroySession(session);
3450
- return {
3451
- ...result,
3452
- command: request.command,
3453
- exitCode: 126,
3454
- stderr: [
3455
- result.stderr,
3456
- "OCI command left background processes running; its workspace changes were discarded."
3457
- ].filter(Boolean).join(`
3458
- `),
3459
- sessionReused,
3460
- workspacePublished: false,
3461
- workspaceExported: false
3462
- };
3801
+ if (!attestationMatch) {
3802
+ throw new Error("OCI command attestation returned an invalid workspace seal.");
3463
3803
  }
3464
- const workspaceSeal = await this.containerWorkspaceSeal(session, uid, gid, Boolean(request.dependencyRoot));
3804
+ const workspaceSeal = attestationMatch[1];
3465
3805
  if (workspaceSeal === session.snapshotSeal) {
3466
3806
  return {
3467
- ...result,
3468
- command: request.command,
3807
+ ...commandResult,
3808
+ command,
3809
+ commands: request.commands,
3469
3810
  sessionReused,
3470
3811
  workspacePublished: true,
3471
- workspaceExported: false
3812
+ workspaceExported: false,
3813
+ phaseLatencies: {
3814
+ commandAndAttestationMs,
3815
+ totalMs: elapsedMilliseconds(totalStartedAt)
3816
+ }
3472
3817
  };
3473
3818
  }
3474
- const stagedRoot = path7.join(path7.dirname(request.snapshotRoot), `.workspace-sync-${randomUUID3()}`);
3819
+ const workspaceExportStartedAt = process.hrtime.bigint();
3820
+ const stagedRoot = path8.join(path8.dirname(request.snapshotRoot), `.workspace-sync-${randomUUID3()}`);
3475
3821
  try {
3476
3822
  await privateDirectory(stagedRoot);
3477
3823
  await this.cli(["cp", `${session.name}:/workspace/.`, stagedRoot], 60000);
@@ -3483,37 +3829,62 @@ ${result.stdout}`)) {
3483
3829
  await rm(stagedRoot, { recursive: true, force: true });
3484
3830
  }
3485
3831
  return {
3486
- ...result,
3487
- command: request.command,
3832
+ ...commandResult,
3833
+ command,
3834
+ commands: request.commands,
3488
3835
  sessionReused,
3489
3836
  workspacePublished: true,
3490
- workspaceExported: true
3837
+ workspaceExported: true,
3838
+ phaseLatencies: {
3839
+ commandAndAttestationMs,
3840
+ workspaceExportMs: elapsedMilliseconds(workspaceExportStartedAt),
3841
+ totalMs: elapsedMilliseconds(totalStartedAt)
3842
+ }
3491
3843
  };
3492
3844
  } catch (error) {
3493
3845
  await this.destroySession(session);
3494
3846
  throw error;
3495
3847
  }
3496
3848
  }
3497
- async runLocked(request) {
3849
+ async runBatchLocked(request) {
3850
+ if (request.commands.length === 0 || request.commands.some((command) => command.length === 0)) {
3851
+ throw new Error("OCI command batch must contain at least one non-empty argv command.");
3852
+ }
3853
+ const totalStartedAt = process.hrtime.bigint();
3498
3854
  const key = runHash(request.runId);
3499
3855
  const requestFingerprint = this.requestFingerprint(request);
3500
3856
  let session = this.sessions.get(key);
3501
3857
  let sessionReused = Boolean(session);
3502
3858
  let validated;
3859
+ let hostSynchronizationMs;
3860
+ let sessionCreationMs;
3503
3861
  if (session) {
3862
+ const synchronizationStartedAt = process.hrtime.bigint();
3504
3863
  const hostFingerprint = await snapshotMetadataFingerprint(request.snapshotRoot, request.limits.maxWorkspaceBytes);
3505
3864
  if (session.requestFingerprint !== requestFingerprint || session.hostFingerprint !== hostFingerprint) {
3506
3865
  await this.destroySession(session);
3507
3866
  session = undefined;
3508
3867
  sessionReused = false;
3509
3868
  }
3869
+ hostSynchronizationMs = elapsedMilliseconds(synchronizationStartedAt);
3510
3870
  }
3511
3871
  if (!session) {
3872
+ const creationStartedAt = process.hrtime.bigint();
3512
3873
  validated = await validateSynchronizedSnapshot(request.snapshotRoot, request.snapshotRoot, request.limits.maxWorkspaceBytes, request.limits.maxFileWriteBytes);
3513
3874
  session = await this.createSession(request, requestFingerprint, validated.seal, validated.metadataFingerprint);
3875
+ sessionCreationMs = elapsedMilliseconds(creationStartedAt);
3514
3876
  }
3515
3877
  try {
3516
- return await this.runInSession(request, session, sessionReused);
3878
+ const result = await this.runInSession(request, session, sessionReused);
3879
+ return {
3880
+ ...result,
3881
+ phaseLatencies: {
3882
+ ...result.phaseLatencies,
3883
+ ...hostSynchronizationMs !== undefined ? { hostSynchronizationMs } : {},
3884
+ ...sessionCreationMs !== undefined ? { sessionCreationMs } : {},
3885
+ totalMs: elapsedMilliseconds(totalStartedAt)
3886
+ }
3887
+ };
3517
3888
  } catch (error) {
3518
3889
  if (this.sessions.get(key) === session)
3519
3890
  await this.destroySession(session).catch(() => {
@@ -3523,7 +3894,12 @@ ${result.stdout}`)) {
3523
3894
  }
3524
3895
  }
3525
3896
  async run(request) {
3526
- return this.withRunLock(request.runId, () => this.runLocked(request));
3897
+ const { command, ...shared } = request;
3898
+ const result = await this.runBatch({ ...shared, commands: [command] });
3899
+ return { ...result, command };
3900
+ }
3901
+ async runBatch(request) {
3902
+ return this.withRunLock(request.runId, () => this.runBatchLocked(request));
3527
3903
  }
3528
3904
  async removeByFilters(filters) {
3529
3905
  const listed = await spawnBounded2([this.runtime, "ps", "-aq", ...filters.flatMap((filter) => ["--filter", filter])], {
@@ -3569,6 +3945,39 @@ ${result.stdout}`)) {
3569
3945
  }
3570
3946
  return removed;
3571
3947
  }
3948
+ async removeRunningOrphans() {
3949
+ const listed = await spawnBounded2([
3950
+ this.runtime,
3951
+ "ps",
3952
+ "-q",
3953
+ "--filter",
3954
+ `label=${HARNESS_OCI_LABEL}=${HARNESS_OCI_LABEL_VALUE}`,
3955
+ "--filter",
3956
+ "status=running"
3957
+ ], {
3958
+ env: this.hostEnvironment,
3959
+ timeoutMs: 30000,
3960
+ maxOutputBytes: 1024 * 1024
3961
+ });
3962
+ if (listed.exitCode !== 0)
3963
+ return 0;
3964
+ let removed = 0;
3965
+ for (const id of validContainerIds(listed.stdout)) {
3966
+ const inspected = await this.cli([
3967
+ "inspect",
3968
+ id,
3969
+ "--format",
3970
+ "{{json .Config.Labels}}"
3971
+ ], 30000, 20000);
3972
+ const labels = JSON.parse(inspected.stdout);
3973
+ const ownerPid = Number(labels[HARNESS_OCI_OWNER_PID_LABEL]);
3974
+ if (hostProcessIsAlive(ownerPid))
3975
+ continue;
3976
+ await this.forceRemove(id);
3977
+ removed += 1;
3978
+ }
3979
+ return removed;
3980
+ }
3572
3981
  async removeRunContainers(runId) {
3573
3982
  const key = runHash(runId);
3574
3983
  const session = this.sessions.get(key);
@@ -3586,14 +3995,19 @@ ${result.stdout}`)) {
3586
3995
  return cachedResources + containers + volumes;
3587
3996
  }
3588
3997
  async cleanupOrphans() {
3589
- this.sessions.clear();
3590
3998
  let removed = 0;
3999
+ for (const session of [...this.sessions.values()]) {
4000
+ await this.destroySession(session);
4001
+ removed += 2;
4002
+ }
4003
+ this.sessions.clear();
3591
4004
  for (const status of ["created", "exited", "dead", "paused"]) {
3592
4005
  removed += await this.removeByFilters([
3593
4006
  `label=${HARNESS_OCI_LABEL}=${HARNESS_OCI_LABEL_VALUE}`,
3594
4007
  `status=${status}`
3595
4008
  ]);
3596
4009
  }
4010
+ removed += await this.removeRunningOrphans();
3597
4011
  removed += await this.removeVolumesByFilters([
3598
4012
  `label=${HARNESS_OCI_LABEL}=${HARNESS_OCI_LABEL_VALUE}`
3599
4013
  ]);
@@ -3615,10 +4029,10 @@ var collectSnapshotInventory = async (workspace, metrics) => {
3615
4029
  if (metrics)
3616
4030
  metrics.inventoryPages += 1;
3617
4031
  for (const file of page.files) {
3618
- const absolute = path7.join(workspace.root, ...file.path.split("/"));
4032
+ const absolute = path8.join(workspace.root, ...file.path.split("/"));
3619
4033
  if (!isInside3(workspace.root, absolute))
3620
4034
  throw new Error(`Snapshot file escaped its workspace: ${file.path}.`);
3621
- const entry = await lstat5(absolute);
4035
+ const entry = await lstat6(absolute);
3622
4036
  if (entry.isSymbolicLink() || !entry.isFile()) {
3623
4037
  throw new Error(`Snapshot source is not a regular file: ${file.path}.`);
3624
4038
  }
@@ -3637,16 +4051,16 @@ var collectSnapshotInventory = async (workspace, metrics) => {
3637
4051
  return files;
3638
4052
  };
3639
4053
  var readSnapshotFile = async (workspace, expected, metrics) => {
3640
- const absolute = path7.join(workspace.root, ...expected.path.split("/"));
4054
+ const absolute = path8.join(workspace.root, ...expected.path.split("/"));
3641
4055
  if (!isInside3(workspace.root, absolute)) {
3642
4056
  throw new Error(`Snapshot file escaped its workspace: ${expected.path}.`);
3643
4057
  }
3644
- const before = await lstat5(absolute);
4058
+ const before = await lstat6(absolute);
3645
4059
  if (before.isSymbolicLink() || !before.isFile()) {
3646
4060
  throw new Error(`Snapshot source is not a regular file: ${expected.path}.`);
3647
4061
  }
3648
4062
  const contents = await readFile3(absolute);
3649
- const after = await lstat5(absolute);
4063
+ const after = await lstat6(absolute);
3650
4064
  if (before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size || before.mtimeMs !== after.mtimeMs || (before.mode & 511) !== (after.mode & 511)) {
3651
4065
  throw new Error(`Snapshot source changed while reading: ${expected.path}.`);
3652
4066
  }
@@ -3684,11 +4098,11 @@ var copyWorkspaceSnapshot = async (source, baseRoot, snapshotRoot, maxWorkspaceB
3684
4098
  }
3685
4099
  for (const expected of files.values()) {
3686
4100
  const file = await readSnapshotFile(source, expected, metrics);
3687
- const baseTarget = path7.join(baseRoot, ...file.path.split("/"));
3688
- const snapshotTarget = path7.join(snapshotRoot, ...file.path.split("/"));
4101
+ const baseTarget = path8.join(baseRoot, ...file.path.split("/"));
4102
+ const snapshotTarget = path8.join(snapshotRoot, ...file.path.split("/"));
3689
4103
  await Promise.all([
3690
- mkdir3(path7.dirname(baseTarget), { recursive: true, mode: 448 }),
3691
- mkdir3(path7.dirname(snapshotTarget), { recursive: true, mode: 448 })
4104
+ mkdir3(path8.dirname(baseTarget), { recursive: true, mode: 448 }),
4105
+ mkdir3(path8.dirname(snapshotTarget), { recursive: true, mode: 448 })
3692
4106
  ]);
3693
4107
  await writeFile(baseTarget, file.contents, { mode: file.mode });
3694
4108
  await chmod2(baseTarget, file.mode);
@@ -3911,7 +4325,7 @@ var createHarnessOciExecutionEnvironment = async (options) => {
3911
4325
  }
3912
4326
  };
3913
4327
  const binding = createAgentExecutionEnvironmentBinding(manifest);
3914
- const environmentRoot = path7.join(options.stateDirectory, "environments");
4328
+ const environmentRoot = path8.join(options.stateDirectory, "environments");
3915
4329
  return {
3916
4330
  manifest,
3917
4331
  image,
@@ -3931,14 +4345,14 @@ var createHarnessOciExecutionEnvironment = async (options) => {
3931
4345
  workspaceExports: 0
3932
4346
  };
3933
4347
  await privateDirectory(environmentRoot);
3934
- const directory = path7.join(environmentRoot, runHash(request.runId));
4348
+ const directory = path8.join(environmentRoot, runHash(request.runId));
3935
4349
  await privateDirectory(directory);
3936
- const metadataPath = path7.join(directory, "environment.json");
3937
- const baseRoot = path7.join(directory, "base");
3938
- const snapshotRoot = path7.join(directory, "workspace");
4350
+ const metadataPath = path8.join(directory, "environment.json");
4351
+ const baseRoot = path8.join(directory, "base");
4352
+ const snapshotRoot = path8.join(directory, "workspace");
3939
4353
  let metadata;
3940
4354
  try {
3941
- const entry = await lstat5(metadataPath);
4355
+ const entry = await lstat6(metadataPath);
3942
4356
  if (entry.isSymbolicLink() || !entry.isFile())
3943
4357
  throw new Error("Execution metadata must be a regular file.");
3944
4358
  metadata = JSON.parse(await readFile3(metadataPath, "utf8"));
@@ -3967,10 +4381,10 @@ var createHarnessOciExecutionEnvironment = async (options) => {
3967
4381
  await atomicJson(metadataPath, metadata);
3968
4382
  const workspace = await Workspace.open(snapshotRoot);
3969
4383
  const base = await Workspace.open(baseRoot);
3970
- const dependencyPath = path7.join(options.workspace.root, "node_modules");
4384
+ const dependencyPath = path8.join(options.workspace.root, "node_modules");
3971
4385
  let dependencyRoot;
3972
4386
  try {
3973
- const entry = await lstat5(dependencyPath);
4387
+ const entry = await lstat6(dependencyPath);
3974
4388
  if (entry.isSymbolicLink() || !entry.isDirectory()) {
3975
4389
  throw new Error("node_modules must be a real directory before it can be mounted read-only.");
3976
4390
  }
@@ -3980,31 +4394,33 @@ var createHarnessOciExecutionEnvironment = async (options) => {
3980
4394
  throw error;
3981
4395
  }
3982
4396
  }
3983
- const runCommand = async (command, args, context) => {
4397
+ const validateCommand = (command, args) => {
3984
4398
  if (!options.config.allowedCommands.includes(command)) {
3985
4399
  throw new Error(`The OCI command "${command}" is not in the explicit allowlist.`);
3986
4400
  }
3987
4401
  if (args.length > 256 || args.some((value) => value.length > 8192 || value.includes("\x00"))) {
3988
4402
  throw new Error("OCI command arguments exceed the bounded argument contract.");
3989
4403
  }
3990
- const result = await runtime.run({
3991
- runId: request.runId,
3992
- snapshotRoot,
3993
- ...dependencyRoot ? { dependencyRoot } : {},
3994
- command: [command, ...args],
3995
- imageId: image.imageId,
3996
- limits: {
3997
- maxProcessRuntimeMs: options.config.maxProcessRuntimeMs,
3998
- maxProcessOutputBytes: options.config.maxProcessOutputBytes,
3999
- maxMemoryMb: options.config.maxMemoryMb,
4000
- maxPids: options.config.maxPids,
4001
- maxCpus: options.config.maxCpus,
4002
- maxWorkspaceBytes: options.config.maxWorkspaceBytes,
4003
- maxFileWriteBytes: options.config.maxFileWriteBytes,
4004
- tmpfsMb: options.config.tmpfsMb
4005
- },
4006
- ...context?.abortSignal ?? request.abortSignal ? { abortSignal: context?.abortSignal ?? request.abortSignal } : {}
4007
- });
4404
+ return [command, ...args];
4405
+ };
4406
+ const sharedRunRequest = (context) => ({
4407
+ runId: request.runId,
4408
+ snapshotRoot,
4409
+ ...dependencyRoot ? { dependencyRoot } : {},
4410
+ imageId: image.imageId,
4411
+ limits: {
4412
+ maxProcessRuntimeMs: options.config.maxProcessRuntimeMs,
4413
+ maxProcessOutputBytes: options.config.maxProcessOutputBytes,
4414
+ maxMemoryMb: options.config.maxMemoryMb,
4415
+ maxPids: options.config.maxPids,
4416
+ maxCpus: options.config.maxCpus,
4417
+ maxWorkspaceBytes: options.config.maxWorkspaceBytes,
4418
+ maxFileWriteBytes: options.config.maxFileWriteBytes,
4419
+ tmpfsMb: options.config.tmpfsMb
4420
+ },
4421
+ ...context?.abortSignal ?? request.abortSignal ? { abortSignal: context?.abortSignal ?? request.abortSignal } : {}
4422
+ });
4423
+ const normalizeResult = (result) => {
4008
4424
  if (result.sessionReused === false)
4009
4425
  ioMetrics.containerStarts += 1;
4010
4426
  if (result.sessionReused === true)
@@ -4025,7 +4441,45 @@ var createHarnessOciExecutionEnvironment = async (options) => {
4025
4441
  ...result.outputLimitExceeded ? ["OCI command exceeded the output limit."] : []
4026
4442
  ].filter(Boolean).join(`
4027
4443
  `),
4028
- timedOut: result.timedOut
4444
+ timedOut: result.timedOut,
4445
+ ...result.phaseLatencies ? { phaseLatencies: result.phaseLatencies } : {},
4446
+ ...result.sessionReused !== undefined ? { sessionReused: result.sessionReused } : {},
4447
+ ...result.workspacePublished !== undefined ? { workspacePublished: result.workspacePublished } : {},
4448
+ ...result.workspaceExported !== undefined ? { workspaceExported: result.workspaceExported } : {}
4449
+ };
4450
+ };
4451
+ const runCommand2 = async (command, args, context) => normalizeResult(await runtime.run({
4452
+ ...sharedRunRequest(context),
4453
+ command: validateCommand(command, args)
4454
+ }));
4455
+ const runCommandBatch = async (commands, context) => {
4456
+ if (commands.length === 0 || commands.length > 32) {
4457
+ throw new Error("OCI command batch must contain between 1 and 32 commands.");
4458
+ }
4459
+ if (commands.reduce((total, item) => total + item.args.length, 0) > 256) {
4460
+ throw new Error("OCI command batch exceeds the bounded argument-count contract.");
4461
+ }
4462
+ const argvCommands = commands.map(({ command, args }) => validateCommand(command, args));
4463
+ if (runtime.runBatch) {
4464
+ return normalizeResult(await runtime.runBatch({
4465
+ ...sharedRunRequest(context),
4466
+ commands: argvCommands
4467
+ }));
4468
+ }
4469
+ const results = [];
4470
+ for (const argv of argvCommands) {
4471
+ const result = await runCommand2(argv[0], argv.slice(1), context);
4472
+ results.push(result);
4473
+ if (result.exitCode !== 0)
4474
+ break;
4475
+ }
4476
+ return {
4477
+ command: ["<oci-batch>", String(commands.length)],
4478
+ exitCode: results.at(-1)?.exitCode ?? 1,
4479
+ stdout: results.map((result) => result.stdout).filter(Boolean).join(""),
4480
+ stderr: results.map((result) => result.stderr).filter(Boolean).join(`
4481
+ `),
4482
+ timedOut: results.some((result) => result.timedOut)
4029
4483
  };
4030
4484
  };
4031
4485
  const session = {
@@ -4045,6 +4499,22 @@ var createHarnessOciExecutionEnvironment = async (options) => {
4045
4499
  return { decision: "deny", reason: "The requested OCI executable is not allowlisted." };
4046
4500
  }
4047
4501
  }
4502
+ if (name === "verify_and_apply_environment_patch" || name === "verify_and_apply_reviewed_edits") {
4503
+ const input = authorization.input;
4504
+ const validBoundInput = name === "verify_and_apply_environment_patch" ? typeof input.patchId === "string" : Array.isArray(input.changes) && input.changes.length > 0;
4505
+ if (!validBoundInput || typeof input.command !== "string" || !options.config.allowedCommands.includes(input.command) || !Array.isArray(input.args)) {
4506
+ return { decision: "deny", reason: "The verified OCI transaction is invalid or contains a non-allowlisted executable." };
4507
+ }
4508
+ }
4509
+ if (name === "run_environment_batch") {
4510
+ const input = authorization.input;
4511
+ if (!Array.isArray(input.commands) || input.commands.length < 1 || input.commands.length > 32 || input.commands.some((item) => {
4512
+ const candidate = item;
4513
+ return typeof candidate.command !== "string" || !options.config.allowedCommands.includes(candidate.command) || !Array.isArray(candidate.args);
4514
+ })) {
4515
+ return { decision: "deny", reason: "The requested OCI command batch is invalid or contains a non-allowlisted executable." };
4516
+ }
4517
+ }
4048
4518
  return {
4049
4519
  decision: "allow",
4050
4520
  metadata: {
@@ -4077,26 +4547,20 @@ var createHarnessOciExecutionEnvironment = async (options) => {
4077
4547
  io: { ...ioMetrics }
4078
4548
  };
4079
4549
  },
4080
- runCommand,
4550
+ runCommand: runCommand2,
4551
+ runCommandBatch,
4081
4552
  async runCheck(check, expectedScript, allowedChecks, context) {
4082
- if (!/^[A-Za-z0-9:_-]+$/.test(check) || !allowedChecks.includes(check)) {
4083
- throw new Error(`The check "${check}" is not in the explicit allowlist.`);
4084
- }
4085
4553
  let manifestDocument;
4086
4554
  try {
4087
- manifestDocument = JSON.parse(await readFile3(path7.join(snapshotRoot, "package.json"), "utf8"));
4555
+ manifestDocument = JSON.parse(await readFile3(path8.join(snapshotRoot, "package.json"), "utf8"));
4088
4556
  } catch (error) {
4089
4557
  if (error.code === "ENOENT") {
4090
4558
  throw new Error("The environment snapshot does not contain package.json.");
4091
4559
  }
4092
4560
  throw error;
4093
4561
  }
4094
- const actual = manifestDocument.scripts?.[check];
4095
- if (!actual)
4096
- throw new Error(`package.json does not define the "${check}" script.`);
4097
- if (actual !== expectedScript)
4098
- throw new Error(`The "${check}" script changed or does not match expectedScript.`);
4099
- return runCommand("bun", ["--no-env-file", "run", check], context);
4562
+ const resolved = await resolvePackageCheckCommand(snapshotRoot, manifestDocument, check, expectedScript, allowedChecks);
4563
+ return runCommand2(resolved.command[0], resolved.command.slice(1), context);
4100
4564
  },
4101
4565
  async inspectPatch() {
4102
4566
  const patch = await createEnvironmentPatch(request.runId, base, workspace, options.config.maxFileWriteBytes, ioMetrics);
@@ -4122,7 +4586,7 @@ var createHarnessOciExecutionEnvironment = async (options) => {
4122
4586
  };
4123
4587
  };
4124
4588
  var cleanupHarnessExecutionArtifacts = async (stateDirectory, before) => {
4125
- const root = path7.join(stateDirectory, "environments");
4589
+ const root = path8.join(stateDirectory, "environments");
4126
4590
  const result = { scanned: 0, deleted: 0, skipped: 0 };
4127
4591
  let entries;
4128
4592
  try {
@@ -4138,15 +4602,15 @@ var cleanupHarnessExecutionArtifacts = async (stateDirectory, before) => {
4138
4602
  continue;
4139
4603
  }
4140
4604
  result.scanned += 1;
4141
- const directory = path7.join(root, entry.name);
4605
+ const directory = path8.join(root, entry.name);
4142
4606
  const resolved = await realpath3(directory);
4143
4607
  if (!isInside3(root, resolved)) {
4144
4608
  result.skipped += 1;
4145
4609
  continue;
4146
4610
  }
4147
4611
  try {
4148
- const metadataPath = path7.join(directory, "environment.json");
4149
- const metadataEntry = await lstat5(metadataPath);
4612
+ const metadataPath = path8.join(directory, "environment.json");
4613
+ const metadataEntry = await lstat6(metadataPath);
4150
4614
  if (metadataEntry.isSymbolicLink() || !metadataEntry.isFile())
4151
4615
  throw new Error("unsafe metadata");
4152
4616
  const metadata = JSON.parse(await readFile3(metadataPath, "utf8"));
@@ -4172,7 +4636,7 @@ var describeOciCommand = (command, args) => commandDisplay([command, ...args]);
4172
4636
 
4173
4637
  // src/harness.ts
4174
4638
  var APPROVAL_VERSION = "2026-08-17-v5";
4175
- var TOOL_CONTRACT_VERSION = "workspace-batch-v2";
4639
+ var TOOL_CONTRACT_VERSION = "workspace-verified-transaction-v3";
4176
4640
  var createHarnessBinding = (config, mcpConfiguration, model, subagentModels, providerTransportFingerprint, executionEnvironment) => ({
4177
4641
  schemaVersion: 1,
4178
4642
  id: "zhivex-harness",
@@ -4202,14 +4666,17 @@ var HARNESS_INSTRUCTIONS = `You are Zhivex Harness, a provider-portable coding a
4202
4666
 
4203
4667
  Rules:
4204
4668
  - Match the user's language.
4205
- - Inspect the repository before proposing changes. Prefer search_many and read_files for independent lookups, then use search_files or read_file when pagination or one focused read is clearer.
4669
+ - Inspect the repository before proposing changes. Use list_files with includeDigests=false for fast topology discovery, then prefer search_many and read_files for independent lookups. Omit cursor on the first paginated call and only reuse an exact nextCursor returned by the immediately preceding matching request. Read the exact digest before proposing any edit.
4206
4670
  - Use only workspace-relative paths. Never request or expose secrets.
4207
4671
  - Make the smallest coherent change that fully addresses the task.
4208
4672
  - For every file edit, first read its digest and call propose_edits. Apply exactly that reviewed proposal with apply_patch.
4209
4673
  - apply_patch, move_file, quarantine_file, restore_file, and run_check require explicit approval from the operator.
4674
+ - apply_reviewed_edits is the one-step equivalent of propose_edits plus apply_patch: its complete digest-bound changes are the approval payload and are applied atomically only after approval.
4675
+ - verify_and_apply_reviewed_edits requests one approval for complete digest-bound edits plus exact verifier argv, applies them to a clean OCI snapshot, rejects verifier-created drift, and imports only after exit code 0.
4210
4676
  - Calling an approval-gated tool is how you request that approval: call the tool with its complete reviewed arguments, then let the runtime pause before execution. Never replace the tool call with a textual approval request.
4211
4677
  - With enforced OCI execution enabled, workspace tools operate on an ephemeral snapshot. Use inspect_environment_patch and obtain a separate approval through apply_environment_patch before changing the host workspace.
4212
- - run_environment_command executes one allowlisted argv command without a host shell. Network, privileges, resources, environment variables, and output are bounded by the OCI policy.
4678
+ - run_environment_command executes one allowlisted argv command without a host shell. Prefer run_environment_batch for a reviewed sequence that can share one final workspace attestation. Network, privileges, resources, environment variables, and output are bounded by the OCI policy.
4679
+ - verify_and_apply_environment_patch binds one reviewed patch and verifier argv, runs that verifier inside OCI, rejects any verifier-created patch drift, and imports only after exit code 0.
4213
4680
  - Never overwrite stale content. If an expected digest no longer matches, inspect the file again and create a new proposal.
4214
4681
  - Deletions are recoverable: use quarantine_file, never permanent deletion. Use restore_file to recover quarantined content.
4215
4682
  - Never claim a check passed unless run_check returned exitCode 0.
@@ -4248,20 +4715,29 @@ var mutationApproval = {
4248
4715
  approvalVersion: APPROVAL_VERSION,
4249
4716
  metadata: toolMetadata(["filesystem", "write"], "high")
4250
4717
  };
4718
+ var verifierCommandSchema = z3.strictObject({
4719
+ command: z3.string().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9._+-]*$/),
4720
+ args: z3.array(z3.string().max(8192)).max(256).default([])
4721
+ });
4722
+ var verifiedReviewedEditsInputSchema = editProposalInputSchema.extend({
4723
+ command: verifierCommandSchema.shape.command,
4724
+ args: verifierCommandSchema.shape.args
4725
+ });
4251
4726
  var createWorkspaceTools = (workspace, allowedChecks) => ({
4252
4727
  list_files: tool({
4253
4728
  name: "list_files",
4254
- description: "List regular files with content digests using a stable cursor. Build artifacts, dependencies, Git internals, and harness state are ignored.",
4729
+ description: "List regular files using a stable cursor. Omit cursor on the first call; on later pages pass only the exact nextCursor returned by the preceding matching request. Set includeDigests=false for fast path-only topology discovery; keep it true when size and content digests are required. Build artifacts, dependencies, Git internals, and harness state are ignored.",
4255
4730
  schema: z3.object({
4256
4731
  path: z3.string().min(1).default("."),
4257
4732
  limit: z3.number().int().min(1).max(500).default(200),
4258
- cursor: z3.string().min(1).max(2000).optional()
4733
+ includeDigests: z3.boolean().default(true),
4734
+ cursor: z3.string().min(1).max(2000).optional().describe("Omit on the first page. For a later page, pass only the exact nextCursor returned by the preceding matching list_files result.")
4259
4735
  }),
4260
4736
  metadata: readOnlyMetadata,
4261
- execute: async ({ path: path8, limit, cursor }, context) => serializeJsonValue2(await (harnessExecutionSession(context)?.workspace ?? workspace).listFiles(path8, {
4262
- limit,
4263
- ...cursor ? { cursor } : {}
4264
- }))
4737
+ execute: async ({ path: path9, limit, includeDigests, cursor }, context) => {
4738
+ const selectedWorkspace = harnessExecutionSession(context)?.workspace ?? workspace;
4739
+ return serializeJsonValue2(await (includeDigests ? selectedWorkspace.listFiles(path9, { limit, includeDigests: true, ...cursor ? { cursor } : {} }) : selectedWorkspace.listFiles(path9, { limit, includeDigests: false, ...cursor ? { cursor } : {} })));
4740
+ }
4265
4741
  }),
4266
4742
  read_file: tool({
4267
4743
  name: "read_file",
@@ -4272,7 +4748,7 @@ var createWorkspaceTools = (workspace, allowedChecks) => ({
4272
4748
  endLine: z3.number().int().min(1).optional()
4273
4749
  }),
4274
4750
  metadata: readOnlyMetadata,
4275
- execute: async ({ path: path8, startLine, endLine }, context) => serializeJsonValue2(await (harnessExecutionSession(context)?.workspace ?? workspace).readFile(path8, startLine, endLine))
4751
+ execute: async ({ path: path9, startLine, endLine }, context) => serializeJsonValue2(await (harnessExecutionSession(context)?.workspace ?? workspace).readFile(path9, startLine, endLine))
4276
4752
  }),
4277
4753
  read_files: tool({
4278
4754
  name: "read_files",
@@ -4285,8 +4761,8 @@ var createWorkspaceTools = (workspace, allowedChecks) => ({
4285
4761
  })).min(1).max(20)
4286
4762
  }),
4287
4763
  metadata: readOnlyMetadata,
4288
- execute: async ({ files }, context) => serializeJsonValue2(await (harnessExecutionSession(context)?.workspace ?? workspace).readFiles(files.map(({ path: path8, startLine, endLine }) => ({
4289
- path: path8,
4764
+ execute: async ({ files }, context) => serializeJsonValue2(await (harnessExecutionSession(context)?.workspace ?? workspace).readFiles(files.map(({ path: path9, startLine, endLine }) => ({
4765
+ path: path9,
4290
4766
  startLine,
4291
4767
  ...endLine !== undefined ? { endLine } : {}
4292
4768
  }))))
@@ -4302,7 +4778,7 @@ var createWorkspaceTools = (workspace, allowedChecks) => ({
4302
4778
  cursor: z3.string().min(1).max(2000).optional()
4303
4779
  }),
4304
4780
  metadata: readOnlyMetadata,
4305
- execute: async ({ query, path: path8, caseSensitive, limit, cursor }, context) => serializeJsonValue2(await (harnessExecutionSession(context)?.workspace ?? workspace).searchFiles(query, path8, {
4781
+ execute: async ({ query, path: path9, caseSensitive, limit, cursor }, context) => serializeJsonValue2(await (harnessExecutionSession(context)?.workspace ?? workspace).searchFiles(query, path9, {
4306
4782
  caseSensitive,
4307
4783
  limit,
4308
4784
  ...cursor ? { cursor } : {}
@@ -4328,7 +4804,7 @@ var createWorkspaceTools = (workspace, allowedChecks) => ({
4328
4804
  }
4329
4805
  }),
4330
4806
  metadata: readOnlyMetadata,
4331
- execute: async ({ queries, path: path8, limitPerQuery }, context) => serializeJsonValue2(await (harnessExecutionSession(context)?.workspace ?? workspace).searchMany(queries, path8, { limitPerQuery }))
4807
+ execute: async ({ queries, path: path9, limitPerQuery }, context) => serializeJsonValue2(await (harnessExecutionSession(context)?.workspace ?? workspace).searchMany(queries, path9, { limitPerQuery }))
4332
4808
  }),
4333
4809
  propose_edits: tool({
4334
4810
  name: "propose_edits",
@@ -4350,6 +4826,19 @@ var createWorkspaceTools = (workspace, allowedChecks) => ({
4350
4826
  return serializeJsonValue2(editContractDocument("patch-result", await (harnessExecutionSession(context)?.workspace ?? workspace).applyPatch(proposal)));
4351
4827
  }
4352
4828
  }),
4829
+ apply_reviewed_edits: tool({
4830
+ name: "apply_reviewed_edits",
4831
+ description: "Request approval for complete digest-bound changes and atomically apply them without copying a separate proposalId between model turns. The exact paths, expected digests, and contents are the approval payload.",
4832
+ schema: editProposalInputSchema,
4833
+ ...mutationApproval,
4834
+ execute: async ({ changes }, context) => {
4835
+ const proposal = createEditProposal({ changes });
4836
+ return serializeJsonValue2(editContractDocument("patch-result", await (harnessExecutionSession(context)?.workspace ?? workspace).applyPatch({
4837
+ proposalId: proposal.proposalId,
4838
+ changes
4839
+ })));
4840
+ }
4841
+ }),
4353
4842
  move_file: tool({
4354
4843
  name: "move_file",
4355
4844
  description: "Move one regular file without overwriting the destination. The source must still match expectedDigest.",
@@ -4373,7 +4862,7 @@ var createWorkspaceTools = (workspace, allowedChecks) => ({
4373
4862
  }),
4374
4863
  run_check: tool({
4375
4864
  name: "run_check",
4376
- description: `Run one explicitly allowed package.json script through Bun (${allowedChecks.join(", ")}). Read package.json first and pass its exact script text as expectedScript so the operator can review the command. No arbitrary shell or .env loading is exposed.`,
4865
+ description: `Run one explicitly allowed package.json script through the repository package manager (${allowedChecks.join(", ")}). Read package.json first and pass its exact script text as expectedScript so the operator can review the command. No arbitrary shell or implicit lifecycle hook is exposed.`,
4377
4866
  schema: z3.object({
4378
4867
  check: z3.string().min(1).max(100).regex(/^[A-Za-z0-9:_-]+$/),
4379
4868
  expectedScript: z3.string().min(1).max(2000)
@@ -4425,6 +4914,29 @@ var createExecutionEnvironmentTools = (workspace) => ({
4425
4914
  metadata: toolMetadata(["code-execution", "filesystem"], "high"),
4426
4915
  execute: async ({ command, args }, context) => serializeJsonValue2(await requireExecutionSession(context).runCommand(command, args, context))
4427
4916
  }),
4917
+ run_environment_batch: tool({
4918
+ name: "run_environment_batch",
4919
+ description: "Run 1 to 32 reviewed allowlisted argv commands sequentially inside one enforced OCI cycle. Execution stops on the first failure and the workspace is attested and published only after the batch succeeds; no host shell or network is exposed.",
4920
+ schema: z3.strictObject({
4921
+ commands: z3.array(z3.strictObject({
4922
+ command: z3.string().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9._+-]*$/),
4923
+ args: z3.array(z3.string().max(8192)).max(256).default([])
4924
+ })).min(1).max(32)
4925
+ }).superRefine((input, context) => {
4926
+ if (input.commands.reduce((total, command) => total + command.args.length, 0) > 256) {
4927
+ context.addIssue({
4928
+ code: "custom",
4929
+ path: ["commands"],
4930
+ message: "run_environment_batch allows at most 256 aggregate arguments."
4931
+ });
4932
+ }
4933
+ }),
4934
+ requiresApproval: true,
4935
+ approvalMode: "interrupt",
4936
+ approvalVersion: APPROVAL_VERSION,
4937
+ metadata: toolMetadata(["code-execution", "filesystem"], "high"),
4938
+ execute: async ({ commands }, context) => serializeJsonValue2(await requireExecutionSession(context).runCommandBatch(commands, context))
4939
+ }),
4428
4940
  environment_status: tool({
4429
4941
  name: "environment_status",
4430
4942
  description: "Inspect the immutable image binding and enforced policy for the active run without exposing host paths or environment variables.",
@@ -4445,6 +4957,83 @@ var createExecutionEnvironmentTools = (workspace) => ({
4445
4957
  schema: z3.strictObject({ patchId: fileDigestSchema }),
4446
4958
  ...mutationApproval,
4447
4959
  execute: async ({ patchId }, context) => serializeJsonValue2(await requireExecutionSession(context).importPatch(workspace, patchId))
4960
+ }),
4961
+ verify_and_apply_environment_patch: tool({
4962
+ name: "verify_and_apply_environment_patch",
4963
+ description: "Request one approval to verify an already inspected content-bound OCI patch with exact allowlisted argv and import it only when verification succeeds without changing the reviewed patch.",
4964
+ schema: z3.strictObject({
4965
+ patchId: fileDigestSchema,
4966
+ command: z3.string().min(1).max(64).regex(/^[A-Za-z0-9][A-Za-z0-9._+-]*$/),
4967
+ args: z3.array(z3.string().max(8192)).max(256).default([])
4968
+ }),
4969
+ requiresApproval: true,
4970
+ approvalMode: "interrupt",
4971
+ approvalVersion: "2026-08-21-verify-and-apply-v1",
4972
+ metadata: toolMetadata(["code-execution", "filesystem"], "high"),
4973
+ execute: async ({ patchId, command, args }, context) => {
4974
+ const session = requireExecutionSession(context);
4975
+ const beforeVerification = await session.inspectPatch();
4976
+ if (beforeVerification.patchId !== patchId) {
4977
+ throw new Error("The OCI patch changed after review; inspect it again before verification and import.");
4978
+ }
4979
+ const verification = await session.runCommand(command, args, context);
4980
+ if (verification.exitCode !== 0) {
4981
+ throw new Error(`The approved verifier failed with exit code ${verification.exitCode}; the host workspace was not changed.`);
4982
+ }
4983
+ const afterVerification = await session.inspectPatch();
4984
+ if (afterVerification.patchId !== patchId) {
4985
+ throw new Error("The verifier changed the reviewed OCI patch; the host workspace was not changed.");
4986
+ }
4987
+ const imported = await session.importPatch(workspace, patchId);
4988
+ return serializeJsonValue2({
4989
+ schemaVersion: 1,
4990
+ kind: "verified-environment-patch-import",
4991
+ patchId,
4992
+ verification,
4993
+ imported
4994
+ });
4995
+ }
4996
+ }),
4997
+ verify_and_apply_reviewed_edits: tool({
4998
+ name: "verify_and_apply_reviewed_edits",
4999
+ description: "Request one approval for complete digest-bound edits and exact verifier argv. The transaction requires a clean OCI snapshot, applies the edits atomically, rejects verifier-created drift, and imports the reviewed patch only after exit code 0.",
5000
+ schema: verifiedReviewedEditsInputSchema,
5001
+ requiresApproval: true,
5002
+ approvalMode: "interrupt",
5003
+ approvalVersion: "2026-08-21-verify-reviewed-edits-v1",
5004
+ metadata: toolMetadata(["code-execution", "filesystem", "write"], "high"),
5005
+ execute: async ({ changes, command, args }, context) => {
5006
+ const session = requireExecutionSession(context);
5007
+ const initialPatch = await session.inspectPatch();
5008
+ if (initialPatch.entries.length !== 0) {
5009
+ throw new Error("The verified edit transaction requires a clean OCI snapshot; the host workspace was not changed.");
5010
+ }
5011
+ const proposal = createEditProposal({ changes });
5012
+ await session.workspace.applyPatch({ proposalId: proposal.proposalId, changes });
5013
+ const reviewedPatch = await session.inspectPatch();
5014
+ const approvedPaths = [...new Set(changes.map((change) => change.path))].sort();
5015
+ const reviewedPaths = reviewedPatch.entries.map((entry) => entry.path).sort();
5016
+ if (JSON.stringify(reviewedPaths) !== JSON.stringify(approvedPaths)) {
5017
+ throw new Error("The OCI patch does not match the approved edit paths; the host workspace was not changed.");
5018
+ }
5019
+ const verification = await session.runCommand(command, args, context);
5020
+ if (verification.exitCode !== 0) {
5021
+ throw new Error(`The approved verifier failed with exit code ${verification.exitCode}; the host workspace was not changed.`);
5022
+ }
5023
+ const afterVerification = await session.inspectPatch();
5024
+ if (afterVerification.patchId !== reviewedPatch.patchId) {
5025
+ throw new Error("The verifier changed the reviewed OCI patch; the host workspace was not changed.");
5026
+ }
5027
+ const imported = await session.importPatch(workspace, reviewedPatch.patchId);
5028
+ return serializeJsonValue2({
5029
+ schemaVersion: 1,
5030
+ kind: "verified-reviewed-edit-import",
5031
+ proposalId: proposal.proposalId,
5032
+ patchId: reviewedPatch.patchId,
5033
+ verification,
5034
+ imported
5035
+ });
5036
+ }
4448
5037
  })
4449
5038
  });
4450
5039
  var estimateMessageTokens = (messages) => Math.max(1, Math.ceil(JSON.stringify(messages).length / 4));
@@ -4455,7 +5044,7 @@ var summarizeHarnessMessages = (messages) => {
4455
5044
  const parts = (record.parts ?? []).map((part) => {
4456
5045
  if (part.type === "text" && typeof part.text === "string") {
4457
5046
  const text = redaction.redactText(part.text).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();
4458
- return text.length > 160 ? `${text.slice(0, 160)}\u2026` : text;
5047
+ return text.length > 160 ? `${text.slice(0, 160)}…` : text;
4459
5048
  }
4460
5049
  if (part.type === "tool-call") {
4461
5050
  const call = part.toolCall;
@@ -4471,7 +5060,7 @@ var summarizeHarnessMessages = (messages) => {
4471
5060
  });
4472
5061
  const summary = summaries.join(`
4473
5062
  `);
4474
- return summary.length > 4000 ? `${summary.slice(0, 4000)}\u2026` : summary;
5063
+ return summary.length > 4000 ? `${summary.slice(0, 4000)}…` : summary;
4475
5064
  };
4476
5065
  var compactHarnessMessages = (messages) => {
4477
5066
  if (messages.length === 0)
@@ -4676,6 +5265,272 @@ Available bounded delegations: ${config.orchestration.profiles.map((profile) =>
4676
5265
  }
4677
5266
  };
4678
5267
  };
5268
+ var canonicalJson = (value) => {
5269
+ if (value === null || typeof value !== "object")
5270
+ return JSON.stringify(value);
5271
+ if (Array.isArray(value))
5272
+ return `[${value.map(canonicalJson).join(",")}]`;
5273
+ const record = value;
5274
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
5275
+ };
5276
+ var localApprovalResolutionPayload = (inputDigest, approve, reason) => JSON.stringify({ inputDigest, approve, reason: reason ?? null });
5277
+ var terminalToolCallId = (runId, step, providerToolCallId, toolName2, input) => `tool_${createHash7("sha256").update(`${runId}\x00${step}\x00${providerToolCallId}\x00${toolName2}\x00${canonicalJson(input)}`).digest("hex")}`;
5278
+ var recoverableTerminalStaleDigest = (toolName2, message) => toolName2 === "verify_and_apply_reviewed_edits" && /^Stale patch rejected for .+\.$/.test(message);
5279
+ var executeTerminalReceiptTool = async (harness, waiting, approval, response) => {
5280
+ if (approval.kind !== "local-tool" || response.provider !== approval.provider || response.approvalRequestId !== approval.id || !response.approve || !approval.toolCallId || approval.step === undefined) {
5281
+ throw new Error("Terminal receipt finalization requires one matching approved local-tool request.");
5282
+ }
5283
+ const candidate = harness.agent.tools?.[approval.name];
5284
+ if (!candidate || !("execute" in candidate)) {
5285
+ throw new Error(`Approved terminal tool ${approval.name} is not locally callable.`);
5286
+ }
5287
+ const definition = candidate;
5288
+ const parsedInput = definition.schema.parse(JSON.parse(approval.arguments));
5289
+ const serializedInput = serializeJsonValue2(parsedInput);
5290
+ const session = await harness.executionEnvironment?.acquire({
5291
+ runId: waiting.state.runId,
5292
+ ...waiting.state.agentId ? { agentId: waiting.state.agentId } : {},
5293
+ ...waiting.state.scope ? { scope: waiting.state.scope } : {},
5294
+ ...waiting.state.metadata ? { metadata: waiting.state.metadata } : {}
5295
+ });
5296
+ if (!session)
5297
+ throw new Error("Terminal receipt finalization requires an active execution environment.");
5298
+ const toolVersion = [
5299
+ definition.approvalVersion,
5300
+ `environment:${session.binding.fingerprint}`
5301
+ ].filter(Boolean).join("|") || "1";
5302
+ const inputDigest = createHash7("sha256").update(canonicalJson({
5303
+ runId: waiting.state.runId,
5304
+ step: approval.step,
5305
+ toolCallId: approval.toolCallId,
5306
+ toolName: approval.name,
5307
+ input: serializedInput,
5308
+ toolVersion
5309
+ })).digest("hex");
5310
+ if (approval.id !== `approval_${inputDigest}` || approval.inputDigest !== inputDigest || approval.toolVersion !== toolVersion || approval.arguments !== canonicalJson(serializedInput)) {
5311
+ await session.release?.({ status: "failed", error: { message: "Approval binding mismatch." } });
5312
+ throw new Error("The terminal tool approval is stale or does not match the current runtime binding.");
5313
+ }
5314
+ if (harness.agent.toolApprovalSigner) {
5315
+ if (!approval.signature) {
5316
+ await session.release?.({ status: "failed", error: { message: "Missing approval signature." } });
5317
+ throw new Error("The terminal tool approval is missing its required signature.");
5318
+ }
5319
+ const valid = harness.agent.toolApprovalSigner.verify ? await harness.agent.toolApprovalSigner.verify(inputDigest, approval.signature) : await harness.agent.toolApprovalSigner.sign(inputDigest) === approval.signature;
5320
+ if (!valid) {
5321
+ await session.release?.({ status: "failed", error: { message: "Invalid approval signature." } });
5322
+ throw new Error("The terminal tool approval signature is invalid.");
5323
+ }
5324
+ }
5325
+ const toolCall = { id: approval.toolCallId, name: approval.name, input: serializedInput };
5326
+ const context = {
5327
+ runId: waiting.state.runId,
5328
+ ...waiting.state.agentId ? { agentId: waiting.state.agentId } : {},
5329
+ ...harness.agent.name ? { agentName: harness.agent.name } : {},
5330
+ ...waiting.state.scope ? { scope: waiting.state.scope } : {},
5331
+ ...waiting.state.metadata ? { metadata: waiting.state.metadata } : {},
5332
+ executionEnvironment: session,
5333
+ toolCall,
5334
+ step: approval.step,
5335
+ model: harness.agent.model,
5336
+ idempotencyKey: `${waiting.state.runId}:${approval.toolCallId}`
5337
+ };
5338
+ const authorization = {
5339
+ manifest: session.manifest,
5340
+ binding: session.binding,
5341
+ tool: definition,
5342
+ toolCall,
5343
+ input: parsedInput,
5344
+ context,
5345
+ phase: "execute"
5346
+ };
5347
+ const decision = await session.authorize(authorization);
5348
+ if (decision.decision === "deny") {
5349
+ await session.release?.({ status: "failed", error: { message: decision.reason } });
5350
+ throw new Error(`Terminal tool execution was denied by the execution environment: ${decision.reason}`);
5351
+ }
5352
+ const resolutionSignature = harness.agent.toolApprovalSigner ? await harness.agent.toolApprovalSigner.sign(localApprovalResolutionPayload(inputDigest, response.approve, response.reason)) : undefined;
5353
+ const durableId = terminalToolCallId(waiting.state.runId, approval.step, approval.toolCallId, approval.name, serializedInput);
5354
+ const idempotencyKey = `${waiting.state.runId}:${durableId}`;
5355
+ const journalCandidate = {
5356
+ runId: waiting.state.runId,
5357
+ ...waiting.state.scope ? { scope: waiting.state.scope } : {},
5358
+ toolCallId: durableId,
5359
+ toolName: approval.name,
5360
+ status: "pending",
5361
+ idempotencyKey,
5362
+ revision: 0,
5363
+ input: serializedInput,
5364
+ updatedAt: Date.now()
5365
+ };
5366
+ const journal = harness.store.claimToolExecution ? await harness.store.claimToolExecution(journalCandidate) : undefined;
5367
+ let output;
5368
+ try {
5369
+ if (journal && !journal.claimed) {
5370
+ if (journal.entry.status === "completed") {
5371
+ output = journal.entry.output ?? null;
5372
+ } else if (journal.entry.status === "failed") {
5373
+ throw new Error(journal.entry.error?.message ?? `Tool "${approval.name}" previously failed.`);
5374
+ } else {
5375
+ throw new Error(`Tool "${approval.name}" has an indeterminate durable execution.`);
5376
+ }
5377
+ } else {
5378
+ output = serializeJsonValue2(await session.execute(authorization, () => definition.execute(parsedInput, { ...context, idempotencyKey })));
5379
+ if (journal?.claimed && harness.store.completeToolExecution) {
5380
+ await harness.store.completeToolExecution({
5381
+ ...journal.entry,
5382
+ status: "completed",
5383
+ output,
5384
+ completedAt: Date.now(),
5385
+ updatedAt: Date.now()
5386
+ }, { expectedRevision: journal.entry.revision });
5387
+ }
5388
+ }
5389
+ } catch (error) {
5390
+ const message = error instanceof Error ? error.message : String(error);
5391
+ if (journal?.claimed && harness.store.completeToolExecution) {
5392
+ await Promise.resolve(harness.store.completeToolExecution({
5393
+ ...journal.entry,
5394
+ status: "failed",
5395
+ error: { message },
5396
+ completedAt: Date.now(),
5397
+ updatedAt: Date.now()
5398
+ }, { expectedRevision: journal.entry.revision })).catch(() => {
5399
+ return;
5400
+ });
5401
+ }
5402
+ await session.release?.({
5403
+ status: "failed",
5404
+ error: { message }
5405
+ });
5406
+ if (!recoverableTerminalStaleDigest(approval.name, message))
5407
+ throw error;
5408
+ const now2 = Date.now();
5409
+ const toolResult2 = {
5410
+ toolCallId: approval.toolCallId,
5411
+ toolName: approval.name,
5412
+ error: { message },
5413
+ isError: true
5414
+ };
5415
+ const messages2 = [
5416
+ ...waiting.messages,
5417
+ { role: "tool", parts: [{ type: "tool-result", toolResult: toolResult2 }] }
5418
+ ];
5419
+ const steps2 = waiting.steps.map((step) => step.index === approval.step ? {
5420
+ ...step,
5421
+ status: "completed",
5422
+ finishedAt: now2,
5423
+ toolResults: [...step.toolResults, toolResult2]
5424
+ } : step);
5425
+ const previousRevision2 = waiting.state.revision ?? 0;
5426
+ const {
5427
+ finalOutput: _finalOutput,
5428
+ finishReason: _finishReason,
5429
+ providerFinishReason: _providerFinishReason,
5430
+ error: _stateError,
5431
+ ...resumableState
5432
+ } = waiting.state;
5433
+ const state2 = {
5434
+ ...resumableState,
5435
+ revision: previousRevision2 + 1,
5436
+ status: "running",
5437
+ messages: messages2,
5438
+ steps: steps2,
5439
+ toolResults: [...waiting.toolResults, toolResult2],
5440
+ pendingApprovals: [],
5441
+ approvalHistory: [
5442
+ ...waiting.state.approvalHistory ?? [],
5443
+ {
5444
+ requestId: approval.id,
5445
+ kind: "local-tool",
5446
+ provider: approval.provider,
5447
+ approve: true,
5448
+ ...response.reason ? { reason: response.reason } : {},
5449
+ toolCallId: approval.toolCallId,
5450
+ step: approval.step,
5451
+ inputDigest,
5452
+ toolVersion,
5453
+ ...resolutionSignature ? { signature: resolutionSignature } : {},
5454
+ resolvedAt: now2
5455
+ }
5456
+ ],
5457
+ updatedAt: now2
5458
+ };
5459
+ await harness.store.save(state2, { expectedRevision: previousRevision2 });
5460
+ return {
5461
+ status: "running",
5462
+ outputText: state2.outputText,
5463
+ ...waiting.usage ? { usage: waiting.usage } : {},
5464
+ messages: messages2,
5465
+ steps: steps2,
5466
+ toolResults: state2.toolResults,
5467
+ state: state2
5468
+ };
5469
+ }
5470
+ const now = Date.now();
5471
+ const toolResult = {
5472
+ toolCallId: approval.toolCallId,
5473
+ toolName: approval.name,
5474
+ output,
5475
+ isError: false
5476
+ };
5477
+ const messages = [
5478
+ ...waiting.messages,
5479
+ { role: "tool", parts: [{ type: "tool-result", toolResult }] }
5480
+ ];
5481
+ const steps = waiting.steps.map((step) => step.index === approval.step ? {
5482
+ ...step,
5483
+ status: "completed",
5484
+ finishedAt: now,
5485
+ toolResults: [...step.toolResults, toolResult]
5486
+ } : step);
5487
+ const outputText = canonicalJson(output);
5488
+ const previousRevision = waiting.state.revision ?? 0;
5489
+ const state = {
5490
+ ...waiting.state,
5491
+ revision: previousRevision + 1,
5492
+ status: "completed",
5493
+ messages,
5494
+ steps,
5495
+ toolResults: [...waiting.toolResults, toolResult],
5496
+ outputText,
5497
+ finalOutput: output,
5498
+ finishReason: "stop",
5499
+ providerFinishReason: "terminal-tool-receipt",
5500
+ pendingApprovals: [],
5501
+ approvalHistory: [
5502
+ ...waiting.state.approvalHistory ?? [],
5503
+ {
5504
+ requestId: approval.id,
5505
+ kind: "local-tool",
5506
+ provider: approval.provider,
5507
+ approve: true,
5508
+ ...response.reason ? { reason: response.reason } : {},
5509
+ toolCallId: approval.toolCallId,
5510
+ step: approval.step,
5511
+ inputDigest,
5512
+ toolVersion,
5513
+ ...resolutionSignature ? { signature: resolutionSignature } : {},
5514
+ resolvedAt: now
5515
+ }
5516
+ ],
5517
+ updatedAt: now
5518
+ };
5519
+ await harness.store.save(state, { expectedRevision: previousRevision });
5520
+ await session.release?.({ status: "completed" });
5521
+ return {
5522
+ status: "completed",
5523
+ outputText,
5524
+ finalOutput: output,
5525
+ finishReason: "stop",
5526
+ providerFinishReason: "terminal-tool-receipt",
5527
+ ...waiting.usage ? { usage: waiting.usage } : {},
5528
+ messages,
5529
+ steps,
5530
+ toolResults: state.toolResults,
5531
+ state
5532
+ };
5533
+ };
4679
5534
  var runHarness = async (harness, input, options = {}) => {
4680
5535
  let nextInput = input;
4681
5536
  const continuationOptions = {
@@ -4716,6 +5571,17 @@ var runHarness = async (harness, input, options = {}) => {
4716
5571
  if (!approvals) {
4717
5572
  return result;
4718
5573
  }
5574
+ const terminalTools = new Set(options.terminalReceiptTools ?? []);
5575
+ if (result.state.pendingApprovals.length === 1 && approvals.length === 1 && approvals[0]?.approve === true && terminalTools.has(result.state.pendingApprovals[0].name)) {
5576
+ const terminalResult = await executeTerminalReceiptTool(harness, result, result.state.pendingApprovals[0], approvals[0]);
5577
+ if (terminalResult.status === "completed")
5578
+ return terminalResult;
5579
+ nextInput = {
5580
+ ...continuationOptions,
5581
+ state: terminalResult.state
5582
+ };
5583
+ continue;
5584
+ }
4719
5585
  nextInput = {
4720
5586
  ...continuationOptions,
4721
5587
  state: result.state,
@@ -4903,11 +5769,10 @@ var streamResultDocument = (result, sequence = 0) => {
4903
5769
  var serializeStreamResult = (result, sequence = 0) => JSON.stringify(streamResultDocument(result, sequence));
4904
5770
 
4905
5771
  // src/sessions.ts
4906
- import { createHash as createHash8, randomUUID as randomUUID4 } from "crypto";
4907
- import { chmod as chmod3, lstat as lstat6, mkdir as mkdir4, open as open3, realpath as realpath4, stat as stat3 } from "fs/promises";
4908
- import path8 from "path";
5772
+ import { createHash as createHash8, randomUUID as randomUUID4 } from "node:crypto";
5773
+ import { chmod as chmod3, lstat as lstat7, mkdir as mkdir4, open as open3, realpath as realpath4, stat as stat3 } from "node:fs/promises";
5774
+ import path9 from "node:path";
4909
5775
  import { createRedactionPolicy as createRedactionPolicy3 } from "@zhivex-ai/agents";
4910
- import { Database as Database2 } from "bun:sqlite";
4911
5776
  var HARNESS_SESSION_INDEX_FILE = "operations.sqlite";
4912
5777
  var HARNESS_SESSION_SCHEMA_VERSION = 1;
4913
5778
  var SESSION_RUN_STATUSES = [
@@ -5011,19 +5876,19 @@ var sessionId = () => `ses_${randomUUID4()}`;
5011
5876
  var turnId = () => `turn_${randomUUID4()}`;
5012
5877
  var isMissing = (error) => error.code === "ENOENT";
5013
5878
  var ensurePrivateDatabase = async (workspace, stateDirectory) => {
5014
- const requestedStateDirectory = path8.resolve(stateDirectory);
5879
+ const requestedStateDirectory = path9.resolve(stateDirectory);
5015
5880
  await validateStateDirectory(workspace, requestedStateDirectory);
5016
5881
  await mkdir4(requestedStateDirectory, { recursive: true, mode: 448 });
5017
- const directoryEntry = await lstat6(requestedStateDirectory);
5882
+ const directoryEntry = await lstat7(requestedStateDirectory);
5018
5883
  if (directoryEntry.isSymbolicLink() || !directoryEntry.isDirectory()) {
5019
5884
  throw new Error(`The session state directory must be a real directory: ${requestedStateDirectory}.`);
5020
5885
  }
5021
5886
  await chmod3(requestedStateDirectory, 448);
5022
5887
  const canonicalStateDirectory2 = await realpath4(requestedStateDirectory);
5023
- const databasePath = path8.join(canonicalStateDirectory2, HARNESS_SESSION_INDEX_FILE);
5888
+ const databasePath = path9.join(canonicalStateDirectory2, HARNESS_SESSION_INDEX_FILE);
5024
5889
  let databaseEntry;
5025
5890
  try {
5026
- databaseEntry = await lstat6(databasePath);
5891
+ databaseEntry = await lstat7(databasePath);
5027
5892
  } catch (error) {
5028
5893
  if (!isMissing(error)) {
5029
5894
  throw error;
@@ -5036,7 +5901,7 @@ var ensurePrivateDatabase = async (workspace, stateDirectory) => {
5036
5901
  throw creationError;
5037
5902
  }
5038
5903
  }
5039
- databaseEntry = await lstat6(databasePath);
5904
+ databaseEntry = await lstat7(databasePath);
5040
5905
  }
5041
5906
  if (databaseEntry.isSymbolicLink() || !databaseEntry.isFile()) {
5042
5907
  throw new Error(`The session SQLite path must be a real file: ${databasePath}.`);
@@ -5060,7 +5925,7 @@ var toRunReference = (row) => ({
5060
5925
  var metadataSize = (input) => Object.values(input).reduce((total, value) => total + (typeof value === "string" ? byteLength(value) : 0), 0);
5061
5926
  var isTerminal = (status) => TERMINAL_STATUSES2.has(status);
5062
5927
  var openCliSessionStore = async (options) => {
5063
- const workspace = await realpath4(path8.resolve(options.workspace));
5928
+ const workspace = await realpath4(path9.resolve(options.workspace));
5064
5929
  const workspaceEntry = await stat3(workspace);
5065
5930
  if (!workspaceEntry.isDirectory()) {
5066
5931
  throw new Error(`The session workspace is not a directory: ${workspace}.`);
@@ -5075,7 +5940,7 @@ var openCliSessionStore = async (options) => {
5075
5940
  const maxIndexBytes = boundedInteger("maxIndexBytes", options.maxIndexBytes, DEFAULT_MAX_INDEX_BYTES, 1024);
5076
5941
  const now = options.now ?? Date.now;
5077
5942
  const { databasePath, databaseEntry } = await ensurePrivateDatabase(workspace, options.stateDirectory);
5078
- const database = new Database2(databasePath, { create: false, strict: true });
5943
+ const database = new SqliteDatabase(databasePath, { create: false, strict: true });
5079
5944
  let closed = false;
5080
5945
  const assertOpen = () => {
5081
5946
  if (closed) {
@@ -5195,7 +6060,7 @@ var openCliSessionStore = async (options) => {
5195
6060
  `).run(candidateTurnId, sourceTurnId ?? null, candidateSessionId, sequence, runId, provider, model, role, status, createdAt, isTerminal(status) ? createdAt : null, entryBytes);
5196
6061
  };
5197
6062
  try {
5198
- const openedEntry = await lstat6(databasePath);
6063
+ const openedEntry = await lstat7(databasePath);
5199
6064
  if (openedEntry.isSymbolicLink() || !openedEntry.isFile() || openedEntry.dev !== databaseEntry.dev || openedEntry.ino !== databaseEntry.ino) {
5200
6065
  throw new Error(`The session SQLite path changed while it was being opened: ${databasePath}.`);
5201
6066
  }
@@ -5554,7 +6419,7 @@ var openCliSessionStore = async (options) => {
5554
6419
  var openSessionStore = openCliSessionStore;
5555
6420
 
5556
6421
  // src/change-envelope.ts
5557
- import { createHash as createHash9 } from "crypto";
6422
+ import { createHash as createHash9 } from "node:crypto";
5558
6423
  import { z as z4 } from "zod";
5559
6424
  var CHANGE_ENVELOPE_SCHEMA_VERSION = 1;
5560
6425
  var CHANGE_ENVELOPE_DIGEST_ALGORITHM = "sha256";
@@ -5567,7 +6432,7 @@ var boundedIdentifierSchema = z4.string().min(1).max(200).regex(/^[A-Za-z0-9][A-
5567
6432
  var approvalScopeSchema = z4.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._:/*-]*$/, "Approval scope must contain only bounded token characters.");
5568
6433
  var attestationFormatSchema = z4.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9.+_-]*$/);
5569
6434
  var mediaTypeSchema = z4.string().min(3).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*\/[A-Za-z0-9][A-Za-z0-9!#$&^_.+-]*$/);
5570
- var addDuplicateIssues = (values, key, context, path9) => {
6435
+ var addDuplicateIssues = (values, key, context, path10) => {
5571
6436
  const seen = new Set;
5572
6437
  for (let index = 0;index < values.length; index += 1) {
5573
6438
  const value = values[index];
@@ -5577,22 +6442,22 @@ var addDuplicateIssues = (values, key, context, path9) => {
5577
6442
  if (seen.has(identifier2)) {
5578
6443
  context.addIssue({
5579
6444
  code: "custom",
5580
- path: [index, path9],
5581
- message: `Duplicate ${path9}: ${identifier2}.`
6445
+ path: [index, path10],
6446
+ message: `Duplicate ${path10}: ${identifier2}.`
5582
6447
  });
5583
6448
  }
5584
6449
  seen.add(identifier2);
5585
6450
  }
5586
6451
  };
5587
- var addCanonicalOrderIssue = (values, key, context, path9) => {
6452
+ var addCanonicalOrderIssue = (values, key, context, path10) => {
5588
6453
  for (let index = 1;index < values.length; index += 1) {
5589
6454
  const previous = values[index - 1];
5590
6455
  const current = values[index];
5591
6456
  if (previous !== undefined && current !== undefined && key(previous) >= key(current)) {
5592
6457
  context.addIssue({
5593
6458
  code: "custom",
5594
- path: [index, path9],
5595
- message: `${path9} entries must be unique and in canonical ascending order.`
6459
+ path: [index, path10],
6460
+ message: `${path10} entries must be unique and in canonical ascending order.`
5596
6461
  });
5597
6462
  }
5598
6463
  }
@@ -5760,7 +6625,7 @@ var changeEnvelopeSchema = z4.strictObject({
5760
6625
  }
5761
6626
  }
5762
6627
  });
5763
- var canonicalJson = (value) => {
6628
+ var canonicalJson2 = (value) => {
5764
6629
  if (value === null || typeof value === "boolean" || typeof value === "string") {
5765
6630
  return JSON.stringify(value);
5766
6631
  }
@@ -5770,15 +6635,15 @@ var canonicalJson = (value) => {
5770
6635
  return JSON.stringify(Object.is(value, -0) ? 0 : value);
5771
6636
  }
5772
6637
  if (Array.isArray(value)) {
5773
- return `[${value.map((entry) => canonicalJson(entry)).join(",")}]`;
6638
+ return `[${value.map((entry) => canonicalJson2(entry)).join(",")}]`;
5774
6639
  }
5775
6640
  if (typeof value === "object") {
5776
6641
  const record = value;
5777
- return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
6642
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson2(record[key])}`).join(",")}}`;
5778
6643
  }
5779
6644
  throw new TypeError(`Canonical JSON cannot encode ${typeof value}.`);
5780
6645
  };
5781
- var digestCanonical = (value) => `sha256:${createHash9("sha256").update(canonicalJson(value)).digest("hex")}`;
6646
+ var digestCanonical = (value) => `sha256:${createHash9("sha256").update(canonicalJson2(value)).digest("hex")}`;
5782
6647
  var digestChangeEnvelopeArtifact = (value) => `sha256:${createHash9("sha256").update(value).digest("hex")}`;
5783
6648
  var evidencePayload = (checks) => ({
5784
6649
  schemaVersion: CHANGE_ENVELOPE_SCHEMA_VERSION,
@@ -5826,7 +6691,7 @@ var createChangeEnvelope = (input) => {
5826
6691
  envelopeId: digestCanonical(payload)
5827
6692
  });
5828
6693
  };
5829
- var canonicalizeChangeEnvelope = (input) => canonicalJson(changeEnvelopeSchema.parse(input));
6694
+ var canonicalizeChangeEnvelope = (input) => canonicalJson2(changeEnvelopeSchema.parse(input));
5830
6695
  var computeChangeEnvelopeDigest = (input) => {
5831
6696
  const parsed = changeEnvelopeSchema.parse(input);
5832
6697
  return digestCanonical(envelopePayload(parsed));
@@ -5960,11 +6825,11 @@ var verifyChangeEnvelope = (input, options = {}) => {
5960
6825
  });
5961
6826
  }
5962
6827
  const preconditions = parsedOptions.preconditions;
5963
- const comparePrecondition = (field, expected, actual, path9) => {
6828
+ const comparePrecondition = (field, expected, actual, path10) => {
5964
6829
  if (expected !== undefined && expected !== actual) {
5965
6830
  issues.push({
5966
6831
  code: "precondition-mismatch",
5967
- path: path9,
6832
+ path: path10,
5968
6833
  message: `Change envelope precondition does not match: ${field}.`,
5969
6834
  expected,
5970
6835
  actual
@@ -6715,7 +7580,7 @@ Options:
6715
7580
  --execution <none|oci> Enforced execution backend (default: none)
6716
7581
  --oci-runtime <docker|podman> Local OCI runtime (default: docker)
6717
7582
  --oci-image <reference> Preloaded immutable-capable OCI image
6718
- --oci-allow-command <name> Allow one argv executable in OCI; repeatable, must include bun
7583
+ --oci-allow-command <name> Allow one argv executable in OCI; repeatable, include a package manager
6719
7584
  --oci-max-process-runtime-ms <n> Per-command timeout (default: 120000)
6720
7585
  --oci-max-process-output-bytes <n> Combined output ceiling (default: 20000)
6721
7586
  --oci-max-memory-mb <n> Container memory ceiling (default: 1024)
@@ -6767,7 +7632,7 @@ Credentials:
6767
7632
  Gemini: GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY`;
6768
7633
  var summarizeApproval = (approval) => {
6769
7634
  const mustShowCompleteProposal = approval.name === "apply_patch";
6770
- const argumentsText = !mustShowCompleteProposal && approval.arguments.length > 1200 ? `${approval.arguments.slice(0, 1200)}\u2026` : approval.arguments;
7635
+ const argumentsText = !mustShowCompleteProposal && approval.arguments.length > 1200 ? `${approval.arguments.slice(0, 1200)}…` : approval.arguments;
6771
7636
  return `[${approval.kind ?? "provider"}] ${approval.name}
6772
7637
  ${argumentsText}`;
6773
7638
  };
@@ -6889,7 +7754,7 @@ var printTerminalResult = (result, harness, output, tracker) => {
6889
7754
  `);
6890
7755
  }
6891
7756
  process.stderr.write(`
6892
- run ${result.state.runId} \xB7 ${result.state.provider}/${result.state.modelId} \xB7 ${result.status} \xB7 ${result.steps.length} steps \xB7 ${harness.workspace.mutationAudit().length} mutations
7757
+ run ${result.state.runId} · ${result.state.provider}/${result.state.modelId} · ${result.status} · ${result.steps.length} steps · ${harness.workspace.mutationAudit().length} mutations
6893
7758
  `);
6894
7759
  if (result.status === "waiting_approval") {
6895
7760
  for (const approval of result.state.pendingApprovals) {
@@ -6919,11 +7784,11 @@ var orchestrationObserver = (json) => async (event) => {
6919
7784
  return;
6920
7785
  if (event.type === "subagent-start") {
6921
7786
  process.stderr.write(`
6922
- subagent start \xB7 ${event.childAgentId ?? event.toolName}
7787
+ subagent start · ${event.childAgentId ?? event.toolName}
6923
7788
  `);
6924
7789
  } else if (event.type === "subagent-finish") {
6925
7790
  process.stderr.write(`
6926
- subagent finish \xB7 ${event.childRun.agentId ?? event.childRun.toolName} \xB7 ${event.childRun.status}
7791
+ subagent finish · ${event.childRun.agentId ?? event.childRun.toolName} · ${event.childRun.status}
6927
7792
  `);
6928
7793
  }
6929
7794
  };
@@ -7189,9 +8054,9 @@ var chat = async (options) => {
7189
8054
  const current = await refreshSession();
7190
8055
  const latest = current.runs.at(-1);
7191
8056
  const routeText = routes.size === 0 ? "default" : [...routes.values()].map((route) => `${route.profile}=${route.provider}:${route.model}`).join(", ");
7192
- return `session ${current.sessionId} \xB7 ${harness.config.provider}/${harness.config.model}` + ` \xB7 ${messages.length} messages \xB7 routes ${routeText}` + `${latest ? ` \xB7 last ${latest.runId} (${latest.status})` : ""}`;
8057
+ return `session ${current.sessionId} · ${harness.config.provider}/${harness.config.model}` + ` · ${messages.length} messages · routes ${routeText}` + `${latest ? ` · last ${latest.runId} (${latest.status})` : ""}`;
7193
8058
  };
7194
- process.stderr.write(`Zhivex Harness ${HARNESS_VERSION} \xB7 ${harness.config.provider}/${harness.config.model}
8059
+ process.stderr.write(`Zhivex Harness ${HARNESS_VERSION} · ${harness.config.provider}/${harness.config.model}
7195
8060
  ` + `session ${session.sessionId}
7196
8061
  ` + `Type /help for console commands.
7197
8062
  `);
@@ -7206,9 +8071,9 @@ var chat = async (options) => {
7206
8071
  break;
7207
8072
  }
7208
8073
  if (prompt === "/help") {
7209
- process.stderr.write(`/provider [id] \xB7 /model [id] \xB7 /route [role=provider[:model]] \xB7 /status
7210
- ` + `/diff \xB7 /review <task> \xB7 /resume <last|sessionId> \xB7 /compact
7211
- ` + `/new [title] \xB7 /rename <title> \xB7 /clear \xB7 /exit
8074
+ process.stderr.write(`/provider [id] · /model [id] · /route [role=provider[:model]] · /status
8075
+ ` + `/diff · /review <task> · /resume <last|sessionId> · /compact
8076
+ ` + `/new [title] · /rename <title> · /clear · /exit
7212
8077
  `);
7213
8078
  continue;
7214
8079
  }
@@ -7487,7 +8352,7 @@ var parseNumericVersion = (version) => {
7487
8352
  const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
7488
8353
  return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : undefined;
7489
8354
  };
7490
- var satisfiesBunEngine = (version, range) => {
8355
+ var satisfiesEngine = (version, range) => {
7491
8356
  const minimumMatch = /^>=(\d+\.\d+\.\d+)$/.exec(range.trim());
7492
8357
  const actual = parseNumericVersion(version);
7493
8358
  const minimum = minimumMatch ? parseNumericVersion(minimumMatch[1] ?? "") : undefined;
@@ -7519,36 +8384,28 @@ var inspectWorkspace = async (workspace) => {
7519
8384
  };
7520
8385
  var inspectGit = async (workspace) => {
7521
8386
  try {
7522
- const versionProcess = Bun.spawn(["git", "--version"], {
7523
- stdin: "ignore",
7524
- stdout: "pipe",
8387
+ const versionProcess = await runPortableProcess(["git", "--version"], {
8388
+ timeoutMs: 15000,
8389
+ maxOutputCharacters: 4096,
7525
8390
  stderr: "ignore"
7526
8391
  });
7527
- const [versionOutput, versionExitCode] = await Promise.all([
7528
- new Response(versionProcess.stdout).text(),
7529
- versionProcess.exited
7530
- ]);
7531
- if (versionExitCode !== 0) {
8392
+ if (versionProcess.exitCode !== 0) {
7532
8393
  return diagnostic("git", "warn", "Git is unavailable.", { installed: false });
7533
8394
  }
7534
- const repositoryProcess = Bun.spawn(["git", "-C", workspace, "rev-parse", "--is-inside-work-tree"], {
7535
- env: { PATH: process.env.PATH ?? "", GIT_TERMINAL_PROMPT: "0" },
7536
- stdin: "ignore",
7537
- stdout: "pipe",
8395
+ const repositoryProcess = await runPortableProcess(["git", "-C", workspace, "rev-parse", "--is-inside-work-tree"], {
8396
+ env: { GIT_TERMINAL_PROMPT: "0" },
8397
+ timeoutMs: 15000,
8398
+ maxOutputCharacters: 4096,
7538
8399
  stderr: "ignore"
7539
8400
  });
7540
- const [repositoryOutput, repositoryExitCode] = await Promise.all([
7541
- new Response(repositoryProcess.stdout).text(),
7542
- repositoryProcess.exited
7543
- ]);
7544
- const repository = repositoryExitCode === 0 && repositoryOutput.trim() === "true";
7545
- return diagnostic("git", repository ? "pass" : "warn", repository ? "Git is installed and the workspace is a repository." : "Git is installed, but the workspace is not a repository.", { installed: true, repository, version: versionOutput.trim() });
8401
+ const repository = repositoryProcess.exitCode === 0 && repositoryProcess.stdout.trim() === "true";
8402
+ return diagnostic("git", repository ? "pass" : "warn", repository ? "Git is installed and the workspace is a repository." : "Git is installed, but the workspace is not a repository.", { installed: true, repository, version: versionProcess.stdout.trim() });
7546
8403
  } catch {
7547
8404
  return diagnostic("git", "warn", "Git is unavailable.", { installed: false });
7548
8405
  }
7549
8406
  };
7550
8407
  var inspectScripts = async (workspace, allowedChecks) => {
7551
- const packagePath = path9.join(workspace, "package.json");
8408
+ const packagePath = path10.join(workspace, "package.json");
7552
8409
  try {
7553
8410
  const packageStat = await stat4(packagePath);
7554
8411
  if (!packageStat.isFile() || packageStat.size > 1024 * 1024) {
@@ -7561,7 +8418,15 @@ var inspectScripts = async (workspace, allowedChecks) => {
7561
8418
  const scripts = packageJson.scripts && typeof packageJson.scripts === "object" ? packageJson.scripts : {};
7562
8419
  const available = allowedChecks.filter((name) => typeof scripts[name] === "string");
7563
8420
  const missing = allowedChecks.filter((name) => typeof scripts[name] !== "string");
7564
- return diagnostic("scripts", available.length > 0 ? "pass" : "warn", available.length > 0 ? "Supported Bun check scripts were found." : "No supported Bun check scripts were found.", { packageJson: true, available, missing });
8421
+ const packageManager = await resolvePackageManager(workspace, packageJson);
8422
+ return diagnostic("scripts", available.length > 0 ? "pass" : "warn", available.length > 0 ? `Supported ${packageManager.manager} check scripts were found.` : `No supported ${packageManager.manager} check scripts were found.`, {
8423
+ packageJson: true,
8424
+ packageManager: packageManager.manager,
8425
+ packageManagerSource: packageManager.source,
8426
+ ...packageManager.evidence ? { packageManagerEvidence: packageManager.evidence } : {},
8427
+ available,
8428
+ missing
8429
+ });
7565
8430
  } catch (error) {
7566
8431
  const code = error instanceof SyntaxError ? "INVALID_JSON" : error.code ?? "UNKNOWN";
7567
8432
  return diagnostic("scripts", "warn", "package.json could not be inspected.", {
@@ -7572,17 +8437,17 @@ var inspectScripts = async (workspace, allowedChecks) => {
7572
8437
  }
7573
8438
  };
7574
8439
  var isInsidePath2 = (root, candidate) => {
7575
- const relative = path9.relative(root, candidate);
7576
- return relative === "" || !relative.startsWith(`..${path9.sep}`) && relative !== ".." && !path9.isAbsolute(relative);
8440
+ const relative = path10.relative(root, candidate);
8441
+ return relative === "" || !relative.startsWith(`..${path10.sep}`) && relative !== ".." && !path10.isAbsolute(relative);
7577
8442
  };
7578
8443
  var containsSymlink = async (candidate, trustedPrefix) => {
7579
- const parsed = path9.parse(candidate);
7580
- const parts = trustedPrefix ? path9.relative(trustedPrefix, candidate).split(path9.sep).filter(Boolean) : candidate.slice(parsed.root.length).split(path9.sep).filter(Boolean).slice(1);
7581
- let current = trustedPrefix ?? path9.join(parsed.root, candidate.slice(parsed.root.length).split(path9.sep).filter(Boolean)[0] ?? "");
8444
+ const parsed = path10.parse(candidate);
8445
+ const parts = trustedPrefix ? path10.relative(trustedPrefix, candidate).split(path10.sep).filter(Boolean) : candidate.slice(parsed.root.length).split(path10.sep).filter(Boolean).slice(1);
8446
+ let current = trustedPrefix ?? path10.join(parsed.root, candidate.slice(parsed.root.length).split(path10.sep).filter(Boolean)[0] ?? "");
7582
8447
  for (const part of parts) {
7583
- current = path9.join(current, part);
8448
+ current = path10.join(current, part);
7584
8449
  try {
7585
- if ((await lstat7(current)).isSymbolicLink()) {
8450
+ if ((await lstat8(current)).isSymbolicLink()) {
7586
8451
  return true;
7587
8452
  }
7588
8453
  } catch (error) {
@@ -7603,7 +8468,7 @@ var nearestExistingPath = async (candidate) => {
7603
8468
  if (error.code !== "ENOENT") {
7604
8469
  throw error;
7605
8470
  }
7606
- const parent = path9.dirname(current);
8471
+ const parent = path10.dirname(current);
7607
8472
  if (parent === current) {
7608
8473
  throw error;
7609
8474
  }
@@ -7619,7 +8484,7 @@ var inspectStateDirectory = async (workspace, stateDirectory) => {
7619
8484
  });
7620
8485
  }
7621
8486
  if (isInsidePath2(workspace, stateDirectory)) {
7622
- const segments = path9.relative(workspace, stateDirectory).split(path9.sep).filter(Boolean);
8487
+ const segments = path10.relative(workspace, stateDirectory).split(path10.sep).filter(Boolean);
7623
8488
  const sensitiveSegment = segments.find(isSensitiveStateSegment);
7624
8489
  if (sensitiveSegment) {
7625
8490
  return diagnostic("state-directory", "fail", "State directory is inside a sensitive workspace path.", {
@@ -7666,9 +8531,9 @@ var inspectOperationsStore = async (stateDirectory, storeBackend) => {
7666
8531
  migrationAvailable: true
7667
8532
  });
7668
8533
  }
7669
- const databasePath = path9.join(stateDirectory, "operations.sqlite");
8534
+ const databasePath = path10.join(stateDirectory, "operations.sqlite");
7670
8535
  try {
7671
- const entry = await lstat7(databasePath);
8536
+ const entry = await lstat8(databasePath);
7672
8537
  if (entry.isSymbolicLink() || !entry.isFile()) {
7673
8538
  return diagnostic("operations-store", "fail", "SQLite state path is not a regular file.", {
7674
8539
  backend: storeBackend,
@@ -7796,11 +8661,11 @@ var createDoctorReport = async (options = {}, context = {}) => {
7796
8661
  ...env.ZHIVEX_HARNESS_MCP_CONFIG ? { mcpConfigPath: env.ZHIVEX_HARNESS_MCP_CONFIG } : {},
7797
8662
  ...options
7798
8663
  });
7799
- const bunVersion = context.bunVersion ?? Bun.version;
8664
+ const runtime = context.nodeVersion ? { name: "node", version: context.nodeVersion, required: NODE_ENGINE_RANGE } : context.bunVersion ? { name: "bun", version: context.bunVersion, required: BUN_ENGINE_RANGE } : process.versions.bun ? { name: "bun", version: process.versions.bun, required: BUN_ENGINE_RANGE } : { name: "node", version: process.versions.node, required: NODE_ENGINE_RANGE };
7800
8665
  const providers = providerAvailability(env);
7801
8666
  const selectedProvider = providers.find((provider) => provider.id === config.provider);
7802
8667
  const checks = [];
7803
- checks.push(diagnostic("bun", satisfiesBunEngine(bunVersion, BUN_ENGINE_RANGE) ? "pass" : "fail", satisfiesBunEngine(bunVersion, BUN_ENGINE_RANGE) ? "Bun satisfies the package engine requirement." : "Bun does not satisfy the package engine requirement.", { version: bunVersion, required: BUN_ENGINE_RANGE }));
8668
+ checks.push(diagnostic(runtime.name, satisfiesEngine(runtime.version, runtime.required) ? "pass" : "fail", satisfiesEngine(runtime.version, runtime.required) ? `${runtime.name === "node" ? "Node.js" : "Bun"} satisfies the package engine requirement.` : `${runtime.name === "node" ? "Node.js" : "Bun"} does not satisfy the package engine requirement.`, { version: runtime.version, required: runtime.required, primary: runtime.name === "node" }));
7804
8669
  checks.push(await inspectWorkspace(config.workspace));
7805
8670
  checks.push(await inspectGit(config.workspace));
7806
8671
  checks.push(await inspectScripts(config.workspace, config.allowedChecks));
@@ -7860,7 +8725,7 @@ var createDoctorReport = async (options = {}, context = {}) => {
7860
8725
  };
7861
8726
  };
7862
8727
  var formatDoctorReport = (report) => {
7863
- const symbols = { pass: "\u2713", warn: "!", fail: "\u2717" };
8728
+ const symbols = { pass: "✓", warn: "!", fail: "✗" };
7864
8729
  const lines = [
7865
8730
  `Zhivex Harness doctor v${report.harnessVersion}`,
7866
8731
  ...report.checks.map((check) => `${symbols[check.status]} ${check.id}: ${check.message}`),
@@ -7898,7 +8763,7 @@ var printRunsDocument = (document, json) => {
7898
8763
  }
7899
8764
  if (record.kind === "run-inspection") {
7900
8765
  const run = record.run;
7901
- process.stdout.write(`${String(run.runId)} \xB7 ${String(run.status)} \xB7 ${String(run.provider)}/${String(run.model)} \xB7 ${String(run.steps)} steps \xB7 ${String(run.toolCalls)} tools
8766
+ process.stdout.write(`${String(run.runId)} · ${String(run.status)} · ${String(run.provider)}/${String(run.model)} · ${String(run.steps)} steps · ${String(run.toolCalls)} tools
7902
8767
  `);
7903
8768
  return;
7904
8769
  }
@@ -7971,14 +8836,14 @@ var printSessionDocument = (document, json) => {
7971
8836
  if (record.kind === "session-list") {
7972
8837
  for (const session2 of record.sessions ?? []) {
7973
8838
  const latest = session2.runs.at(-1);
7974
- process.stdout.write(`${session2.sessionId} ${session2.title ?? "(untitled)"} \xB7 ${session2.runs.length} runs` + `${latest ? ` \xB7 ${latest.provider}/${latest.model} \xB7 ${latest.status}` : ""}
8839
+ process.stdout.write(`${session2.sessionId} ${session2.title ?? "(untitled)"} · ${session2.runs.length} runs` + `${latest ? ` · ${latest.provider}/${latest.model} · ${latest.status}` : ""}
7975
8840
  `);
7976
8841
  }
7977
8842
  return;
7978
8843
  }
7979
8844
  const session = record.session;
7980
8845
  if (session) {
7981
- process.stdout.write(`${session.sessionId} \xB7 ${session.title ?? "(untitled)"} \xB7 ${session.runs.length} runs` + `${session.archivedAt ? " \xB7 archived" : ""}
8846
+ process.stdout.write(`${session.sessionId} · ${session.title ?? "(untitled)"} · ${session.runs.length} runs` + `${session.archivedAt ? " · archived" : ""}
7982
8847
  `);
7983
8848
  return;
7984
8849
  }
@@ -8049,9 +8914,9 @@ var cliExitCodeForError = (error) => error instanceof CliUsageError ? CLI_EXIT_C
8049
8914
  var MAX_CHANGE_ENVELOPE_JSON_BYTES = 4 * 1024 * 1024;
8050
8915
  var MAX_CHANGE_ARTIFACT_BYTES = 64 * 1024 * 1024;
8051
8916
  var readStableCliArtifact = async (filePath, maxBytes) => {
8052
- const absolute = path9.resolve(process.cwd(), filePath);
8917
+ const absolute = path10.resolve(process.cwd(), filePath);
8053
8918
  try {
8054
- const before = await lstat7(absolute);
8919
+ const before = await lstat8(absolute);
8055
8920
  if (before.isSymbolicLink() || !before.isFile()) {
8056
8921
  throw new CliUsageError(`Change artifact must be a regular non-symlink file: ${filePath}`);
8057
8922
  }
@@ -8059,7 +8924,7 @@ var readStableCliArtifact = async (filePath, maxBytes) => {
8059
8924
  throw new CliUsageError(`Change artifact exceeds the ${maxBytes}-byte limit: ${filePath}`);
8060
8925
  }
8061
8926
  const contents = await readFile4(absolute);
8062
- const after = await lstat7(absolute);
8927
+ const after = await lstat8(absolute);
8063
8928
  if (before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs) {
8064
8929
  throw new CliUsageError(`Change artifact changed while it was being read: ${filePath}`);
8065
8930
  }
@@ -8173,7 +9038,16 @@ var main = async (argv = process.argv.slice(2)) => {
8173
9038
  await reviewOnce(options);
8174
9039
  }
8175
9040
  };
8176
- if (import.meta.main) {
9041
+ var isMainModule = (() => {
9042
+ if (!process.argv[1])
9043
+ return false;
9044
+ try {
9045
+ return realpathSync2(process.argv[1]) === realpathSync2(fileURLToPath(import.meta.url));
9046
+ } catch {
9047
+ return false;
9048
+ }
9049
+ })();
9050
+ if (isMainModule) {
8177
9051
  main().catch((error) => {
8178
9052
  const message = error instanceof Error ? error.message : String(error);
8179
9053
  process.stderr.write(`Error: ${message}
@@ -8182,22 +9056,22 @@ if (import.meta.main) {
8182
9056
  });
8183
9057
  }
8184
9058
  export {
8185
- withTemporaryHarnessProfiles,
8186
- summarizeApproval,
8187
- runResultDocument,
8188
- resumeCommand,
8189
- readHarnessResumeRoutes,
8190
- readHarnessResumeConfig,
8191
- providersDocument,
8192
- parseCliArgs,
8193
- main,
8194
- formatDoctorReport,
8195
- createHarnessResumeMetadata,
8196
- createDoctorReport,
8197
- cliExitCodeForError,
8198
- HARNESS_RESUME_METADATA_SCHEMA_VERSION,
8199
- HARNESS_RESUME_METADATA_KEY,
8200
- CliUsageError,
9059
+ CLI_EXIT_CODES,
8201
9060
  CLI_JSON_SCHEMA_VERSION,
8202
- CLI_EXIT_CODES
9061
+ CliUsageError,
9062
+ HARNESS_RESUME_METADATA_KEY,
9063
+ HARNESS_RESUME_METADATA_SCHEMA_VERSION,
9064
+ cliExitCodeForError,
9065
+ createDoctorReport,
9066
+ createHarnessResumeMetadata,
9067
+ formatDoctorReport,
9068
+ main,
9069
+ parseCliArgs,
9070
+ providersDocument,
9071
+ readHarnessResumeConfig,
9072
+ readHarnessResumeRoutes,
9073
+ resumeCommand,
9074
+ runResultDocument,
9075
+ summarizeApproval,
9076
+ withTemporaryHarnessProfiles
8203
9077
  };