@zhivex-ai/harness 1.2.0-rc.12 → 1.2.0-rc.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ All notable changes to Zhivex Harness are documented in this file.
4
4
 
5
5
  The project follows Semantic Versioning. During `0.x`, minor releases may change user-facing contracts when the change is documented with a migration note. Patch releases remain backwards compatible bug fixes.
6
6
 
7
+ ## 1.2.0-rc.13 - 2026-09-26
8
+
9
+ - Store SQLite run history incrementally with published Core 1.26.0 and Agents 1.10.1. Keep the 4 MiB limit on the active checkpoint while retaining steps, tool results and compaction history separately; unchanged history is not rewritten.
10
+ - Export backups above 64 MiB through a bounded, versioned segment stream, preserving legacy JSON imports, checksums and atomic publication.
11
+ - Preserve full history through reopen and portable backup/restore, including legacy inline states. Report state-limit failures with bounded diagnostics and retain the SDK terminal failure checkpoint.
12
+ - Add a 200-read, 99-compaction regression covering more than 11 MB of hydrated history with a checkpoint below 48 KB. Full hydrated loads and disk retention remain proportional to history size.
13
+ - Prepare fresh exact-artifact, live and representative certification for RC13; RC12 publication evidence remains historical.
14
+
7
15
  ## 1.2.0-rc.12 - 2026-09-26
8
16
 
9
17
  - Preserve optional edit/retry fields in OpenAI Responses tool schemas; keep runtime validation, read-bound digests and approval checks unchanged.
package/README.md CHANGED
@@ -11,12 +11,12 @@ implement a change. Conversation context retains decisions, hypotheses and bound
11
11
  diagnostic excerpts across compaction. Edits and command execution keep their
12
12
  own permission checks; a conversational summary never grants approval.
13
13
 
14
- Version `1.2.0-rc.12` is prepared as an unpublished release candidate for npm `next`,
15
- with task continuity, dependency inspection and quieter console progress improvements. Certification
14
+ Version `1.2.0-rc.13` is prepared as an unpublished release candidate for npm `next`,
15
+ with incremental durable history and bounded active checkpoints for long conversations. Certification
16
16
  and publication remain pending. The
17
17
  [repository release status](https://raw.githubusercontent.com/Zhivex/zhivex-harness/main/release-status.json)
18
18
  preserves historical stable evidence. The installation commands below target
19
- 1.2.0-rc.12 once published; stable users can install `@zhivex-ai/harness@latest`.
19
+ 1.2.0-rc.13 once published; stable users can install `@zhivex-ai/harness@latest`.
20
20
  Desktop retains its separate private alpha status.
21
21
 
22
22
  ## Quick start
@@ -24,7 +24,7 @@ Desktop retains its separate private alpha status.
24
24
  Requires Node.js 22.13.0 or newer, Git and a provider API key.
25
25
 
26
26
  ```sh
27
- npm install -g @zhivex-ai/harness@1.2.0-rc.12
27
+ npm install -g @zhivex-ai/harness@1.2.0-rc.13
28
28
  cd /path/to/your/project
29
29
  zhx
30
30
  ```
package/dist/acp-cli.js CHANGED
@@ -2484,6 +2484,31 @@ var readRegularFileNoFollow = async (target, options) => {
2484
2484
  await handle.close();
2485
2485
  }
2486
2486
  };
2487
+ async function* readRegularFileChunksNoFollow(target, options) {
2488
+ const handle = await openRegularFileNoFollow(target, options.label);
2489
+ try {
2490
+ const before = await handle.stat();
2491
+ if (!before.isFile() || options.requireSingleLink && before.nlink !== 1)
2492
+ throw invalidFileError(options.label);
2493
+ if (options.requirePrivate && (before.mode & 63) !== 0)
2494
+ throw new Error(`${options.label} permissions must not grant group or other access.`);
2495
+ let offset = 0;
2496
+ while (offset < before.size) {
2497
+ const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, before.size - offset));
2498
+ const { bytesRead: bytesRead2 } = await handle.read(buffer, 0, buffer.length, offset);
2499
+ if (!bytesRead2)
2500
+ throw new FileChangedWhileReadingError(options.label);
2501
+ offset += bytesRead2;
2502
+ yield buffer.subarray(0, bytesRead2);
2503
+ }
2504
+ const probe = Buffer.allocUnsafe(1);
2505
+ const { bytesRead } = await handle.read(probe, 0, 1, offset);
2506
+ if (bytesRead || !sameOpenFile(before, await handle.stat()))
2507
+ throw new FileChangedWhileReadingError(options.label);
2508
+ } finally {
2509
+ await handle.close();
2510
+ }
2511
+ }
2487
2512
 
2488
2513
  // src/cli/cli-profiles.ts
2489
2514
  var CLI_PROFILE_SCHEMA_VERSION = 1;
@@ -11786,7 +11811,7 @@ var openHarnessPersistence = async (config, options = {}) => {
11786
11811
  database.exec("PRAGMA foreign_keys = ON");
11787
11812
  await chmod3(databasePath, 384);
11788
11813
  const databaseLike = sqliteAdapter(database);
11789
- const store = withBudgetLedgerStore(createSqliteAgentRunStore({ db: databaseLike, scope: config.scope }), createSqliteAgentRunStore({ db: databaseLike, scope: budgetScopeFor(config) }), config);
11814
+ const store = withBudgetLedgerStore(createSqliteAgentRunStore({ db: databaseLike, scope: config.scope, history: "incremental" }), createSqliteAgentRunStore({ db: databaseLike, scope: budgetScopeFor(config) }), config);
11790
11815
  const memory = createSqliteAgentMemoryStore({ db: databaseLike, scope: config.scope });
11791
11816
  const migration = shouldMigrateLegacyRuns(config, options.migrateLegacyFileStore) ? await migrateLegacyFileRuns(config.stateDirectory, store, config) : emptyMigration();
11792
11817
  return {
@@ -14855,6 +14880,9 @@ var terminalRunFailure = (error) => {
14855
14880
  const streamOverflow = /^Stream replay buffer exceeded its limit of (\d+) events\.$/.exec(message);
14856
14881
  if (streamOverflow)
14857
14882
  return `event replay limit reached · ${streamOverflow[1]} events`;
14883
+ const stateOverflow = /^Agent run state is (\d+) bytes and exceeds maxStateBytes=(\d+)\. Offload large tool outputs to artifacts or raise the explicit limit\.$/.exec(message);
14884
+ if (stateOverflow)
14885
+ return `durable state limit reached · ${stateOverflow[1]} / ${stateOverflow[2]} bytes · inspect /status before /continue`;
14858
14886
  if (/^Tool is not registered \(sha256:[a-f0-9]{64}\)\.$/.test(message)) {
14859
14887
  return "requested tool is not registered · use an available tool with its exact name";
14860
14888
  }
@@ -57,6 +57,7 @@ import {
57
57
  navigateConsole,
58
58
  openCliSessionStore,
59
59
  openHarnessPersistence,
60
+ readRegularFileChunksNoFollow,
60
61
  readRegularFileNoFollow,
61
62
  resolvePackageManager,
62
63
  resolveTerminalApprovals,
@@ -67,6 +68,7 @@ import {
67
68
  sanitizeTerminalText,
68
69
  serializeStreamEvent,
69
70
  serializeStreamResult,
71
+ sqliteAdapter,
70
72
  taskSources,
71
73
  terminalContinuationMessages,
72
74
  terminalRunFailure,
@@ -75,7 +77,7 @@ import {
75
77
  validateStateDirectory,
76
78
  verifyChangeEnvelope,
77
79
  workspaceFilePathSchema
78
- } from "./cli-entry-b0dtz99m.js";
80
+ } from "./cli-entry-c2f58zj2.js";
79
81
  import {
80
82
  BUN_ENGINE_RANGE,
81
83
  HARNESS_VERSION,
@@ -4568,6 +4570,175 @@ var manageChanges = async (options) => {
4568
4570
  process.exitCode = CLI_EXIT_CODES.runtimeError;
4569
4571
  };
4570
4572
 
4573
+ // src/persistence/state-backup-stream.ts
4574
+ var MAGIC = `ZHIVEX-STATE-SEGMENTS-1
4575
+ `;
4576
+ var STRING_CHARS = 8192;
4577
+ var MAX_FRAME_BYTES = 64 * 1024;
4578
+ function* canonicalChunks(value) {
4579
+ if (typeof value === "string") {
4580
+ yield '"';
4581
+ for (let i = 0;i < value.length; ) {
4582
+ let end = Math.min(i + STRING_CHARS, value.length);
4583
+ if (end < value.length && /[\uD800-\uDBFF]/.test(value[end - 1]))
4584
+ end--;
4585
+ yield JSON.stringify(value.slice(i, end)).slice(1, -1);
4586
+ i = end;
4587
+ }
4588
+ yield '"';
4589
+ } else if (value === null || typeof value !== "object") {
4590
+ yield JSON.stringify(value);
4591
+ } else if (Array.isArray(value)) {
4592
+ yield "[";
4593
+ for (let i = 0;i < value.length; i++) {
4594
+ if (i)
4595
+ yield ",";
4596
+ yield* canonicalChunks(value[i]);
4597
+ }
4598
+ yield "]";
4599
+ } else {
4600
+ yield "{";
4601
+ let first = true;
4602
+ for (const key of Object.keys(value).sort()) {
4603
+ if (!first)
4604
+ yield ",";
4605
+ first = false;
4606
+ yield* canonicalChunks(key);
4607
+ yield ":";
4608
+ yield* canonicalChunks(value[key]);
4609
+ }
4610
+ yield "}";
4611
+ }
4612
+ }
4613
+ function* tokens(value) {
4614
+ if (typeof value === "string") {
4615
+ for (let i = 0;i < value.length; i += STRING_CHARS)
4616
+ yield ["text", value.slice(i, i + STRING_CHARS)];
4617
+ yield ["string"];
4618
+ } else if (value === null || typeof value !== "object")
4619
+ yield ["value", value];
4620
+ else {
4621
+ yield [Array.isArray(value) ? "array" : "object"];
4622
+ if (Array.isArray(value))
4623
+ for (const item of value)
4624
+ yield* tokens(item);
4625
+ else
4626
+ for (const [key, item] of Object.entries(value)) {
4627
+ yield* tokens(key);
4628
+ yield* tokens(item);
4629
+ }
4630
+ yield ["end"];
4631
+ }
4632
+ }
4633
+ function* segmentedChunks(value) {
4634
+ yield MAGIC;
4635
+ for (const token of tokens(value))
4636
+ yield `${JSON.stringify(token)}
4637
+ `;
4638
+ }
4639
+ async function readBackupTransport(source, legacyMaxBytes) {
4640
+ const fail = () => {
4641
+ throw new Error("Harness state backup segment stream is invalid or incomplete.");
4642
+ };
4643
+ const stack = [];
4644
+ let root;
4645
+ let hasRoot = false;
4646
+ let pieces = [];
4647
+ let pending = Buffer.alloc(0);
4648
+ let segmented;
4649
+ const legacy = [];
4650
+ let legacyBytes = 0;
4651
+ const append = (value) => {
4652
+ const parent = stack.at(-1);
4653
+ if (!parent) {
4654
+ if (hasRoot)
4655
+ fail();
4656
+ root = value;
4657
+ hasRoot = true;
4658
+ } else if (Array.isArray(parent.value))
4659
+ parent.value.push(value);
4660
+ else if (parent.key === undefined) {
4661
+ if (typeof value !== "string" || Object.hasOwn(parent.value, value))
4662
+ fail();
4663
+ parent.key = value;
4664
+ } else {
4665
+ Object.defineProperty(parent.value, parent.key, { value, enumerable: true, writable: true, configurable: true });
4666
+ delete parent.key;
4667
+ }
4668
+ };
4669
+ const consume = (line) => {
4670
+ if (line.length > MAX_FRAME_BYTES)
4671
+ fail();
4672
+ const token = JSON.parse(line.toString("utf8"));
4673
+ if (!Array.isArray(token))
4674
+ fail();
4675
+ const frame = token;
4676
+ const [kind, value] = frame;
4677
+ if (kind === "text" && frame.length === 2 && typeof value === "string") {
4678
+ pieces.push(value);
4679
+ return;
4680
+ }
4681
+ if (kind === "string" && frame.length === 1) {
4682
+ append(pieces.join(""));
4683
+ pieces = [];
4684
+ return;
4685
+ }
4686
+ if (pieces.length)
4687
+ fail();
4688
+ if (kind === "value" && frame.length === 2 && (value === null || typeof value === "boolean" || typeof value === "number"))
4689
+ append(value);
4690
+ else if ((kind === "array" || kind === "object") && frame.length === 1) {
4691
+ if (stack.length >= 512)
4692
+ fail();
4693
+ const value2 = kind === "array" ? [] : {};
4694
+ append(value2);
4695
+ stack.push({ value: value2 });
4696
+ } else if (kind === "end" && frame.length === 1) {
4697
+ const parent = stack.pop();
4698
+ if (!parent || parent.key !== undefined)
4699
+ fail();
4700
+ } else
4701
+ fail();
4702
+ };
4703
+ for await (const chunk of readRegularFileChunksNoFollow(source, {
4704
+ label: "Harness state backup",
4705
+ requireSingleLink: true,
4706
+ requirePrivate: true
4707
+ })) {
4708
+ pending = Buffer.concat([pending, chunk]);
4709
+ if (segmented === undefined) {
4710
+ if (pending.length < Buffer.byteLength(MAGIC))
4711
+ continue;
4712
+ segmented = pending.subarray(0, MAGIC.length).toString() === MAGIC;
4713
+ if (segmented)
4714
+ pending = pending.subarray(MAGIC.length);
4715
+ }
4716
+ if (!segmented) {
4717
+ legacyBytes += pending.length;
4718
+ if (legacyBytes > legacyMaxBytes)
4719
+ throw new Error("Harness state backup exceeds the legacy size limit.");
4720
+ legacy.push(pending);
4721
+ pending = Buffer.alloc(0);
4722
+ continue;
4723
+ }
4724
+ let newline;
4725
+ while ((newline = pending.indexOf(10)) !== -1) {
4726
+ consume(pending.subarray(0, newline));
4727
+ pending = pending.subarray(newline + 1);
4728
+ }
4729
+ if (pending.length > MAX_FRAME_BYTES)
4730
+ fail();
4731
+ }
4732
+ if (!segmented) {
4733
+ if (legacyBytes + pending.length > legacyMaxBytes)
4734
+ throw new Error("Harness state backup exceeds the legacy size limit.");
4735
+ return JSON.parse(Buffer.concat([...legacy, pending]).toString("utf8"));
4736
+ }
4737
+ if (pending.length || stack.length || pieces.length || !hasRoot)
4738
+ fail();
4739
+ return root;
4740
+ }
4741
+
4571
4742
  // src/persistence/workspace-checkpoints.ts
4572
4743
  import { createHash as createHash2, randomUUID as randomUUID3 } from "node:crypto";
4573
4744
  import { z as z3 } from "zod";
@@ -4599,6 +4770,7 @@ import { chmod, link, lstat as lstat3, open as open2, realpath, unlink as unlink
4599
4770
  import path5 from "node:path";
4600
4771
  import { z as z4 } from "zod";
4601
4772
  import { normalizeAgentRunState } from "@zhivex-ai/agents";
4773
+ import { createSqliteAgentRunStore } from "@zhivex-ai/agents/ops";
4602
4774
  var HARNESS_STATE_BACKUP_SCHEMA_VERSION = 1;
4603
4775
  var HARNESS_STATE_BACKUP_MAX_BYTES = 64 * 1024 * 1024;
4604
4776
  var terminalRunStatuses = new Set(["completed", "failed", "cancelled", "timed_out"]);
@@ -4733,7 +4905,12 @@ var canonicalJson = (value) => {
4733
4905
  return `[${value.map(canonicalJson).join(",")}]`;
4734
4906
  return `{${Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`).join(",")}}`;
4735
4907
  };
4736
- var checksumPayload = (payload) => `sha256:${createHash3("sha256").update(canonicalJson(payload)).digest("hex")}`;
4908
+ var checksumPayload = (payload) => {
4909
+ const hash = createHash3("sha256");
4910
+ for (const chunk of canonicalChunks(payload))
4911
+ hash.update(chunk);
4912
+ return `sha256:${hash.digest("hex")}`;
4913
+ };
4737
4914
  var stableKey = (kind, value) => createHash3("sha256").update(kind).update("\x00").update(value).digest("hex");
4738
4915
  var scopeValue = (config) => `${config.scope.tenantId}\x00${config.scope.userId ?? ""}\x00${config.scope.namespace ?? ""}`;
4739
4916
  var scopePrefix = (config) => `${encodeURIComponent(config.scope.namespace ?? "default")}:${encodeURIComponent(config.scope.tenantId)}:${encodeURIComponent(config.scope.userId ?? "*")}:`;
@@ -4765,8 +4942,8 @@ var validateBudgetLedger = (config, run, identities) => {
4765
4942
  if (!/^sha256:[a-f0-9]{64}$/.test(id) || !value || typeof value !== "object" || Array.isArray(value) || value.status !== "confirmed") {
4766
4943
  throw new HarnessStateConflictError("Budget ledger retains unresolved reservations or unknown consumption.");
4767
4944
  }
4768
- const tokens = value.tokens;
4769
- if (!tokens || typeof tokens !== "object" || Array.isArray(tokens) || ![tokens.inputTokens, tokens.outputTokens, tokens.totalTokens].every((amount) => typeof amount === "number" && Number.isSafeInteger(amount) && amount >= 0) || tokens.totalTokens < tokens.inputTokens + tokens.outputTokens) {
4945
+ const tokens2 = value.tokens;
4946
+ if (!tokens2 || typeof tokens2 !== "object" || Array.isArray(tokens2) || ![tokens2.inputTokens, tokens2.outputTokens, tokens2.totalTokens].every((amount) => typeof amount === "number" && Number.isSafeInteger(amount) && amount >= 0) || tokens2.totalTokens < tokens2.inputTokens + tokens2.outputTokens) {
4770
4947
  throw new HarnessStateConflictError("Invalid budget ledger token receipt.");
4771
4948
  }
4772
4949
  }
@@ -4868,8 +5045,13 @@ var readPayload = async (config, recordedWorkspace, accessLease) => {
4868
5045
  if (recordedWorkspace && database.query("SELECT version FROM zhivex_cli_session_schema WHERE singleton=1").get()?.version !== HARNESS_SESSION_SCHEMA_VERSION)
4869
5046
  throw new HarnessStateConflictError("Archived session schema is incompatible.");
4870
5047
  const allRunRows = database.query("SELECT run_id, state_json, updated_at_ms FROM zhivex_agent_runs ORDER BY run_id").all();
5048
+ let historyReader;
4871
5049
  const runRows = allRunRows.flatMap((row) => {
4872
- const state = validatedRunState(parseJsonRecord(row.state_json, `Run ${row.run_id}`), `Run ${row.run_id}`);
5050
+ const record = parseJsonRecord(row.state_json, `Run ${row.run_id}`);
5051
+ const hydrated = record.checkpointHistory ? (historyReader ??= createSqliteAgentRunStore({ db: sqliteAdapter(database), history: "incremental" })).load(String(record.runId), record.scope) : record;
5052
+ if (hydrated instanceof Promise || !hydrated)
5053
+ throw new HarnessStateConflictError("Cannot hydrate state backup history.");
5054
+ const state = validatedRunState(hydrated, `Run ${row.run_id}`);
4873
5055
  return stateMatchesScope(config, state) ? [{ row, state }] : [];
4874
5056
  });
4875
5057
  const runs = runRows.map(({ row, state }) => {
@@ -5029,7 +5211,19 @@ var writePrivateBackup = async (target, contents) => {
5029
5211
  try {
5030
5212
  handle = await open2(staged, fsConstants3.O_WRONLY | fsConstants3.O_CREAT | fsConstants3.O_EXCL | fsConstants3.O_NOFOLLOW, 384);
5031
5213
  stagedExists = true;
5032
- await handle.writeFile(contents, "utf8");
5214
+ let pending = [];
5215
+ let pendingBytes = 0;
5216
+ for (const chunk of contents) {
5217
+ pending.push(chunk);
5218
+ pendingBytes += Buffer.byteLength(chunk);
5219
+ if (pendingBytes >= 64 * 1024) {
5220
+ await handle.writeFile(pending.join(""), "utf8");
5221
+ pending = [];
5222
+ pendingBytes = 0;
5223
+ }
5224
+ }
5225
+ if (pending.length)
5226
+ await handle.writeFile(pending.join(""), "utf8");
5033
5227
  await handle.sync();
5034
5228
  await chmod(staged, 384);
5035
5229
  await handle.close();
@@ -5056,12 +5250,13 @@ var writePrivateBackup = async (target, contents) => {
5056
5250
  };
5057
5251
  var exportHarnessStateBackup = async (config, target) => {
5058
5252
  const bundle = await createHarnessStateBackup(config);
5059
- const serialized = `${JSON.stringify(bundle, null, 2)}
5060
- `;
5061
- if (Buffer.byteLength(serialized) > HARNESS_STATE_BACKUP_MAX_BYTES) {
5062
- throw new HarnessStateConflictError("State backup exceeds the supported size limit.");
5253
+ let bytes = 0;
5254
+ for (const chunk of canonicalChunks(bundle)) {
5255
+ bytes += Buffer.byteLength(chunk);
5256
+ if (bytes > HARNESS_STATE_BACKUP_MAX_BYTES)
5257
+ break;
5063
5258
  }
5064
- await writePrivateBackup(target, serialized);
5259
+ await writePrivateBackup(target, bytes > HARNESS_STATE_BACKUP_MAX_BYTES ? segmentedChunks(bundle) : canonicalChunks(bundle));
5065
5260
  return {
5066
5261
  schemaVersion: HARNESS_STATE_BACKUP_SCHEMA_VERSION,
5067
5262
  kind: "state-export",
@@ -5071,19 +5266,14 @@ var exportHarnessStateBackup = async (config, target) => {
5071
5266
  };
5072
5267
  };
5073
5268
  var readHarnessStateBackup = async (source) => {
5074
- const { contents, stat: stat2 } = await readRegularFileNoFollow(path5.resolve(source), {
5075
- label: "Harness state backup",
5076
- maxBytes: HARNESS_STATE_BACKUP_MAX_BYTES,
5077
- requireSingleLink: true
5078
- });
5079
- if ((stat2.mode & 63) !== 0) {
5080
- throw new HarnessWorkspaceError("Harness state backup permissions must not grant group or other access.");
5081
- }
5082
5269
  let input;
5083
5270
  try {
5084
- input = JSON.parse(contents.toString("utf8"));
5271
+ input = await readBackupTransport(path5.resolve(source), HARNESS_STATE_BACKUP_MAX_BYTES);
5085
5272
  } catch (error) {
5086
- throw new HarnessStateConflictError("Harness state backup is not valid JSON.", { cause: error });
5273
+ if (error instanceof SyntaxError) {
5274
+ throw new HarnessStateConflictError("Harness state backup is not valid JSON.", { cause: error });
5275
+ }
5276
+ throw error;
5087
5277
  }
5088
5278
  return validateStateBackupBundle(input);
5089
5279
  };
@@ -5484,7 +5674,7 @@ var main = async (argv = process.argv.slice(2)) => {
5484
5674
  parsedOptions = { ...parsedOptions, prompt: await readStdinTask() };
5485
5675
  }
5486
5676
  if (parsedOptions.serviceFile) {
5487
- const { runServiceCli } = await import("./service-client-cli-50sympxb.js");
5677
+ const { runServiceCli } = await import("./service-client-cli-yx56n0hc.js");
5488
5678
  await runServiceCli(parsedOptions, annotateCliStreamError);
5489
5679
  return;
5490
5680
  }
@@ -2075,6 +2075,9 @@ var terminalRunFailure = (error) => {
2075
2075
  const streamOverflow = /^Stream replay buffer exceeded its limit of (\d+) events\.$/.exec(message);
2076
2076
  if (streamOverflow)
2077
2077
  return `event replay limit reached · ${streamOverflow[1]} events`;
2078
+ const stateOverflow = /^Agent run state is (\d+) bytes and exceeds maxStateBytes=(\d+)\. Offload large tool outputs to artifacts or raise the explicit limit\.$/.exec(message);
2079
+ if (stateOverflow)
2080
+ return `durable state limit reached · ${stateOverflow[1]} / ${stateOverflow[2]} bytes · inspect /status before /continue`;
2078
2081
  if (/^Tool is not registered \(sha256:[a-f0-9]{64}\)\.$/.test(message)) {
2079
2082
  return "requested tool is not registered · use an available tool with its exact name";
2080
2083
  }
@@ -3503,6 +3506,31 @@ var readRegularFileNoFollow = async (target, options) => {
3503
3506
  await handle.close();
3504
3507
  }
3505
3508
  };
3509
+ async function* readRegularFileChunksNoFollow(target, options) {
3510
+ const handle = await openRegularFileNoFollow(target, options.label);
3511
+ try {
3512
+ const before = await handle.stat();
3513
+ if (!before.isFile() || options.requireSingleLink && before.nlink !== 1)
3514
+ throw invalidFileError(options.label);
3515
+ if (options.requirePrivate && (before.mode & 63) !== 0)
3516
+ throw new Error(`${options.label} permissions must not grant group or other access.`);
3517
+ let offset = 0;
3518
+ while (offset < before.size) {
3519
+ const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, before.size - offset));
3520
+ const { bytesRead: bytesRead2 } = await handle.read(buffer, 0, buffer.length, offset);
3521
+ if (!bytesRead2)
3522
+ throw new FileChangedWhileReadingError(options.label);
3523
+ offset += bytesRead2;
3524
+ yield buffer.subarray(0, bytesRead2);
3525
+ }
3526
+ const probe = Buffer.allocUnsafe(1);
3527
+ const { bytesRead } = await handle.read(probe, 0, 1, offset);
3528
+ if (bytesRead || !sameOpenFile(before, await handle.stat()))
3529
+ throw new FileChangedWhileReadingError(options.label);
3530
+ } finally {
3531
+ await handle.close();
3532
+ }
3533
+ }
3506
3534
 
3507
3535
  // src/integrations/mcp.ts
3508
3536
  import { lstat as lstat5, realpath as realpath2 } from "node:fs/promises";
@@ -9347,7 +9375,7 @@ var openHarnessPersistence = async (config, options = {}) => {
9347
9375
  database.exec("PRAGMA foreign_keys = ON");
9348
9376
  await chmod3(databasePath, 384);
9349
9377
  const databaseLike = sqliteAdapter(database);
9350
- const store = withBudgetLedgerStore(createSqliteAgentRunStore({ db: databaseLike, scope: config.scope }), createSqliteAgentRunStore({ db: databaseLike, scope: budgetScopeFor(config) }), config);
9378
+ const store = withBudgetLedgerStore(createSqliteAgentRunStore({ db: databaseLike, scope: config.scope, history: "incremental" }), createSqliteAgentRunStore({ db: databaseLike, scope: budgetScopeFor(config) }), config);
9351
9379
  const memory = createSqliteAgentMemoryStore({ db: databaseLike, scope: config.scope });
9352
9380
  const migration = shouldMigrateLegacyRuns(config, options.migrateLegacyFileStore) ? await migrateLegacyFileRuns(config.stateDirectory, store, config) : emptyMigration();
9353
9381
  return {
@@ -13571,4 +13599,4 @@ var verifyChangeEnvelope = (input, options = {}) => {
13571
13599
  };
13572
13600
  };
13573
13601
 
13574
- export { UnsafeFileTypeError, FileSizeLimitError, FileChangedWhileReadingError, readRegularFileNoFollow, ToolActivity, compactToolResult, SqliteDatabase, validateStateDirectory, HARNESS_SESSION_SCHEMA_VERSION, openCliSessionStore, inspectRuntimeDiagnostics, USAGE_LEDGER_KEY, usagePricingSchema, inspectUsageLedger, formatUsageLedger, CLI_JSON_SCHEMA_VERSION, CLI_EVENT_SCHEMA_VERSION, serializeStreamEvent, serializeStreamResult, runResultDocument, sanitizeTerminalText, formatApproval, resolveTerminalApprovals, terminalSupportsColor, formatVerificationSummary, terminalRunFailure, formatTerminalEvent, formatConsoleHelp, ConsoleInput, chooseReasoning, loadModelCatalog, navigateConsole, loadHarnessMcpConfiguration, resolvePackageManager, fileDigestSchema, workspaceFilePathSchema, runPortableProcess, Workspace, CliOciRuntimeAdapter, cleanupHarnessExecutionArtifacts, DEFAULT_HARNESS_CONTEXT_MANIFEST, MAX_HARNESS_CONTEXT_FILE_BYTES, MAX_HARNESS_CONTEXT_TOTAL_BYTES, loadHarnessProjectContext, ActivityHistory, formatAppliedFiles, harnessToolExecution, terminalContinuationMessages, TASK_SOURCE_KEY, taskSources, formatConsoleWelcome, estimateMessages, HARNESS_SQLITE_FILE, HARNESS_OPERATIONS_SCHEMA_VERSION, listHarnessRuns, inspectHarnessRun, cancelHarnessRun, cleanupHarnessRuns, openHarnessPersistence, runHarnessReviewGroup, compactHarnessMessages, createHarness, runHarness, appendUserMessage, changeEnvelopeSchema, digestChangeEnvelopeArtifact, createChangeEnvelope, canonicalizeChangeEnvelope, changeEnvelopePreconditionsSchema, verifyChangeEnvelope };
13602
+ export { UnsafeFileTypeError, FileSizeLimitError, FileChangedWhileReadingError, readRegularFileNoFollow, readRegularFileChunksNoFollow, ToolActivity, compactToolResult, SqliteDatabase, validateStateDirectory, HARNESS_SESSION_SCHEMA_VERSION, openCliSessionStore, inspectRuntimeDiagnostics, USAGE_LEDGER_KEY, usagePricingSchema, inspectUsageLedger, formatUsageLedger, CLI_JSON_SCHEMA_VERSION, CLI_EVENT_SCHEMA_VERSION, serializeStreamEvent, serializeStreamResult, runResultDocument, sanitizeTerminalText, formatApproval, resolveTerminalApprovals, terminalSupportsColor, formatVerificationSummary, terminalRunFailure, formatTerminalEvent, formatConsoleHelp, ConsoleInput, chooseReasoning, loadModelCatalog, navigateConsole, loadHarnessMcpConfiguration, resolvePackageManager, fileDigestSchema, workspaceFilePathSchema, runPortableProcess, Workspace, CliOciRuntimeAdapter, cleanupHarnessExecutionArtifacts, DEFAULT_HARNESS_CONTEXT_MANIFEST, MAX_HARNESS_CONTEXT_FILE_BYTES, MAX_HARNESS_CONTEXT_TOTAL_BYTES, loadHarnessProjectContext, ActivityHistory, formatAppliedFiles, harnessToolExecution, terminalContinuationMessages, TASK_SOURCE_KEY, taskSources, formatConsoleWelcome, estimateMessages, HARNESS_SQLITE_FILE, HARNESS_OPERATIONS_SCHEMA_VERSION, listHarnessRuns, inspectHarnessRun, cancelHarnessRun, cleanupHarnessRuns, sqliteAdapter, openHarnessPersistence, runHarnessReviewGroup, compactHarnessMessages, createHarness, runHarness, appendUserMessage, changeEnvelopeSchema, digestChangeEnvelopeArtifact, createChangeEnvelope, canonicalizeChangeEnvelope, changeEnvelopePreconditionsSchema, verifyChangeEnvelope };
package/dist/cli.js CHANGED
@@ -18,6 +18,6 @@ if (argv.length === 1 && ["--version", "-v", "version"].includes(argv[0])) {
18
18
  process.stdout.write(`${shortCliHelp(HARNESS_VERSION)}
19
19
  `);
20
20
  } else {
21
- const { runCli } = await import("./cli-3qwxrh91.js");
21
+ const { runCli } = await import("./cli-3jmy4h5h.js");
22
22
  await runCli(argv);
23
23
  }
@@ -10397,7 +10397,7 @@ var openHarnessPersistence = async (config, options = {}) => {
10397
10397
  database.exec("PRAGMA foreign_keys = ON");
10398
10398
  await chmod3(databasePath, 384);
10399
10399
  const databaseLike = sqliteAdapter(database);
10400
- const store = withBudgetLedgerStore(createSqliteAgentRunStore({ db: databaseLike, scope: config.scope }), createSqliteAgentRunStore({ db: databaseLike, scope: budgetScopeFor(config) }), config);
10400
+ const store = withBudgetLedgerStore(createSqliteAgentRunStore({ db: databaseLike, scope: config.scope, history: "incremental" }), createSqliteAgentRunStore({ db: databaseLike, scope: budgetScopeFor(config) }), config);
10401
10401
  const memory = createSqliteAgentMemoryStore({ db: databaseLike, scope: config.scope });
10402
10402
  const migration = shouldMigrateLegacyRuns(config, options.migrateLegacyFileStore) ? await migrateLegacyFileRuns(config.stateDirectory, store, config) : emptyMigration();
10403
10403
  return {
package/dist/index.js CHANGED
@@ -3885,6 +3885,9 @@ var terminalRunFailure = (error) => {
3885
3885
  const streamOverflow = /^Stream replay buffer exceeded its limit of (\d+) events\.$/.exec(message);
3886
3886
  if (streamOverflow)
3887
3887
  return `event replay limit reached · ${streamOverflow[1]} events`;
3888
+ const stateOverflow = /^Agent run state is (\d+) bytes and exceeds maxStateBytes=(\d+)\. Offload large tool outputs to artifacts or raise the explicit limit\.$/.exec(message);
3889
+ if (stateOverflow)
3890
+ return `durable state limit reached · ${stateOverflow[1]} / ${stateOverflow[2]} bytes · inspect /status before /continue`;
3888
3891
  if (/^Tool is not registered \(sha256:[a-f0-9]{64}\)\.$/.test(message)) {
3889
3892
  return "requested tool is not registered · use an available tool with its exact name";
3890
3893
  }
@@ -4899,11 +4902,6 @@ var openCliSessionStore = async (options) => {
4899
4902
  return store;
4900
4903
  };
4901
4904
  var openSessionStore = openCliSessionStore;
4902
- // src/persistence/workspace-checkpoints.ts
4903
- import { createHash as createHash7, randomUUID as randomUUID2 } from "node:crypto";
4904
- import path9 from "node:path";
4905
- import { z as z6 } from "zod";
4906
-
4907
4905
  // src/workspace/file-security.ts
4908
4906
  import { constants as fsConstants } from "node:fs";
4909
4907
  import { open as open3 } from "node:fs/promises";
@@ -5014,6 +5012,205 @@ var readRegularFileNoFollow = async (target, options) => {
5014
5012
  await handle.close();
5015
5013
  }
5016
5014
  };
5015
+ async function* readRegularFileChunksNoFollow(target, options) {
5016
+ const handle = await openRegularFileNoFollow(target, options.label);
5017
+ try {
5018
+ const before = await handle.stat();
5019
+ if (!before.isFile() || options.requireSingleLink && before.nlink !== 1)
5020
+ throw invalidFileError(options.label);
5021
+ if (options.requirePrivate && (before.mode & 63) !== 0)
5022
+ throw new Error(`${options.label} permissions must not grant group or other access.`);
5023
+ let offset = 0;
5024
+ while (offset < before.size) {
5025
+ const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, before.size - offset));
5026
+ const { bytesRead: bytesRead2 } = await handle.read(buffer, 0, buffer.length, offset);
5027
+ if (!bytesRead2)
5028
+ throw new FileChangedWhileReadingError(options.label);
5029
+ offset += bytesRead2;
5030
+ yield buffer.subarray(0, bytesRead2);
5031
+ }
5032
+ const probe = Buffer.allocUnsafe(1);
5033
+ const { bytesRead } = await handle.read(probe, 0, 1, offset);
5034
+ if (bytesRead || !sameOpenFile(before, await handle.stat()))
5035
+ throw new FileChangedWhileReadingError(options.label);
5036
+ } finally {
5037
+ await handle.close();
5038
+ }
5039
+ }
5040
+
5041
+ // src/persistence/state-backup-stream.ts
5042
+ var MAGIC = `ZHIVEX-STATE-SEGMENTS-1
5043
+ `;
5044
+ var STRING_CHARS = 8192;
5045
+ var MAX_FRAME_BYTES = 64 * 1024;
5046
+ function* canonicalChunks(value) {
5047
+ if (typeof value === "string") {
5048
+ yield '"';
5049
+ for (let i = 0;i < value.length; ) {
5050
+ let end = Math.min(i + STRING_CHARS, value.length);
5051
+ if (end < value.length && /[\uD800-\uDBFF]/.test(value[end - 1]))
5052
+ end--;
5053
+ yield JSON.stringify(value.slice(i, end)).slice(1, -1);
5054
+ i = end;
5055
+ }
5056
+ yield '"';
5057
+ } else if (value === null || typeof value !== "object") {
5058
+ yield JSON.stringify(value);
5059
+ } else if (Array.isArray(value)) {
5060
+ yield "[";
5061
+ for (let i = 0;i < value.length; i++) {
5062
+ if (i)
5063
+ yield ",";
5064
+ yield* canonicalChunks(value[i]);
5065
+ }
5066
+ yield "]";
5067
+ } else {
5068
+ yield "{";
5069
+ let first = true;
5070
+ for (const key of Object.keys(value).sort()) {
5071
+ if (!first)
5072
+ yield ",";
5073
+ first = false;
5074
+ yield* canonicalChunks(key);
5075
+ yield ":";
5076
+ yield* canonicalChunks(value[key]);
5077
+ }
5078
+ yield "}";
5079
+ }
5080
+ }
5081
+ function* tokens(value) {
5082
+ if (typeof value === "string") {
5083
+ for (let i = 0;i < value.length; i += STRING_CHARS)
5084
+ yield ["text", value.slice(i, i + STRING_CHARS)];
5085
+ yield ["string"];
5086
+ } else if (value === null || typeof value !== "object")
5087
+ yield ["value", value];
5088
+ else {
5089
+ yield [Array.isArray(value) ? "array" : "object"];
5090
+ if (Array.isArray(value))
5091
+ for (const item of value)
5092
+ yield* tokens(item);
5093
+ else
5094
+ for (const [key, item] of Object.entries(value)) {
5095
+ yield* tokens(key);
5096
+ yield* tokens(item);
5097
+ }
5098
+ yield ["end"];
5099
+ }
5100
+ }
5101
+ function* segmentedChunks(value) {
5102
+ yield MAGIC;
5103
+ for (const token of tokens(value))
5104
+ yield `${JSON.stringify(token)}
5105
+ `;
5106
+ }
5107
+ async function readBackupTransport(source, legacyMaxBytes) {
5108
+ const fail = () => {
5109
+ throw new Error("Harness state backup segment stream is invalid or incomplete.");
5110
+ };
5111
+ const stack = [];
5112
+ let root;
5113
+ let hasRoot = false;
5114
+ let pieces = [];
5115
+ let pending = Buffer.alloc(0);
5116
+ let segmented;
5117
+ const legacy = [];
5118
+ let legacyBytes = 0;
5119
+ const append = (value) => {
5120
+ const parent = stack.at(-1);
5121
+ if (!parent) {
5122
+ if (hasRoot)
5123
+ fail();
5124
+ root = value;
5125
+ hasRoot = true;
5126
+ } else if (Array.isArray(parent.value))
5127
+ parent.value.push(value);
5128
+ else if (parent.key === undefined) {
5129
+ if (typeof value !== "string" || Object.hasOwn(parent.value, value))
5130
+ fail();
5131
+ parent.key = value;
5132
+ } else {
5133
+ Object.defineProperty(parent.value, parent.key, { value, enumerable: true, writable: true, configurable: true });
5134
+ delete parent.key;
5135
+ }
5136
+ };
5137
+ const consume = (line) => {
5138
+ if (line.length > MAX_FRAME_BYTES)
5139
+ fail();
5140
+ const token = JSON.parse(line.toString("utf8"));
5141
+ if (!Array.isArray(token))
5142
+ fail();
5143
+ const frame = token;
5144
+ const [kind, value] = frame;
5145
+ if (kind === "text" && frame.length === 2 && typeof value === "string") {
5146
+ pieces.push(value);
5147
+ return;
5148
+ }
5149
+ if (kind === "string" && frame.length === 1) {
5150
+ append(pieces.join(""));
5151
+ pieces = [];
5152
+ return;
5153
+ }
5154
+ if (pieces.length)
5155
+ fail();
5156
+ if (kind === "value" && frame.length === 2 && (value === null || typeof value === "boolean" || typeof value === "number"))
5157
+ append(value);
5158
+ else if ((kind === "array" || kind === "object") && frame.length === 1) {
5159
+ if (stack.length >= 512)
5160
+ fail();
5161
+ const value2 = kind === "array" ? [] : {};
5162
+ append(value2);
5163
+ stack.push({ value: value2 });
5164
+ } else if (kind === "end" && frame.length === 1) {
5165
+ const parent = stack.pop();
5166
+ if (!parent || parent.key !== undefined)
5167
+ fail();
5168
+ } else
5169
+ fail();
5170
+ };
5171
+ for await (const chunk of readRegularFileChunksNoFollow(source, {
5172
+ label: "Harness state backup",
5173
+ requireSingleLink: true,
5174
+ requirePrivate: true
5175
+ })) {
5176
+ pending = Buffer.concat([pending, chunk]);
5177
+ if (segmented === undefined) {
5178
+ if (pending.length < Buffer.byteLength(MAGIC))
5179
+ continue;
5180
+ segmented = pending.subarray(0, MAGIC.length).toString() === MAGIC;
5181
+ if (segmented)
5182
+ pending = pending.subarray(MAGIC.length);
5183
+ }
5184
+ if (!segmented) {
5185
+ legacyBytes += pending.length;
5186
+ if (legacyBytes > legacyMaxBytes)
5187
+ throw new Error("Harness state backup exceeds the legacy size limit.");
5188
+ legacy.push(pending);
5189
+ pending = Buffer.alloc(0);
5190
+ continue;
5191
+ }
5192
+ let newline;
5193
+ while ((newline = pending.indexOf(10)) !== -1) {
5194
+ consume(pending.subarray(0, newline));
5195
+ pending = pending.subarray(newline + 1);
5196
+ }
5197
+ if (pending.length > MAX_FRAME_BYTES)
5198
+ fail();
5199
+ }
5200
+ if (!segmented) {
5201
+ if (legacyBytes + pending.length > legacyMaxBytes)
5202
+ throw new Error("Harness state backup exceeds the legacy size limit.");
5203
+ return JSON.parse(Buffer.concat([...legacy, pending]).toString("utf8"));
5204
+ }
5205
+ if (pending.length || stack.length || pieces.length || !hasRoot)
5206
+ fail();
5207
+ return root;
5208
+ }
5209
+
5210
+ // src/persistence/workspace-checkpoints.ts
5211
+ import { createHash as createHash7, randomUUID as randomUUID2 } from "node:crypto";
5212
+ import path9 from "node:path";
5213
+ import { z as z6 } from "zod";
5017
5214
 
5018
5215
  // src/workspace/workspace-mutation-lock.ts
5019
5216
  import { createHash as createHash6 } from "node:crypto";
@@ -5277,6 +5474,7 @@ import { chmod as chmod4, link as link2, lstat as lstat10, open as open7, realpa
5277
5474
  import path14 from "node:path";
5278
5475
  import { z as z16 } from "zod";
5279
5476
  import { normalizeAgentRunState } from "@zhivex-ai/agents";
5477
+ import { createSqliteAgentRunStore as createSqliteAgentRunStore2 } from "@zhivex-ai/agents/ops";
5280
5478
 
5281
5479
  // src/runtime/runtime-policy.ts
5282
5480
  import { createBudgetGuard, createProductionSafetyPolicy } from "@zhivex-ai/agents";
@@ -10344,7 +10542,7 @@ var openHarnessPersistence = async (config, options = {}) => {
10344
10542
  database.exec("PRAGMA foreign_keys = ON");
10345
10543
  await chmod3(databasePath, 384);
10346
10544
  const databaseLike = sqliteAdapter(database);
10347
- const store = withBudgetLedgerStore(createSqliteAgentRunStore({ db: databaseLike, scope: config.scope }), createSqliteAgentRunStore({ db: databaseLike, scope: budgetScopeFor(config) }), config);
10545
+ const store = withBudgetLedgerStore(createSqliteAgentRunStore({ db: databaseLike, scope: config.scope, history: "incremental" }), createSqliteAgentRunStore({ db: databaseLike, scope: budgetScopeFor(config) }), config);
10348
10546
  const memory = createSqliteAgentMemoryStore({ db: databaseLike, scope: config.scope });
10349
10547
  const migration = shouldMigrateLegacyRuns(config, options.migrateLegacyFileStore) ? await migrateLegacyFileRuns(config.stateDirectory, store, config) : emptyMigration();
10350
10548
  return {
@@ -10496,7 +10694,12 @@ var canonicalJson2 = (value) => {
10496
10694
  return `[${value.map(canonicalJson2).join(",")}]`;
10497
10695
  return `{${Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson2(entry)}`).join(",")}}`;
10498
10696
  };
10499
- var checksumPayload = (payload) => `sha256:${createHash13("sha256").update(canonicalJson2(payload)).digest("hex")}`;
10697
+ var checksumPayload = (payload) => {
10698
+ const hash2 = createHash13("sha256");
10699
+ for (const chunk of canonicalChunks(payload))
10700
+ hash2.update(chunk);
10701
+ return `sha256:${hash2.digest("hex")}`;
10702
+ };
10500
10703
  var stableKey2 = (kind, value) => createHash13("sha256").update(kind).update("\x00").update(value).digest("hex");
10501
10704
  var scopeValue2 = (config) => `${config.scope.tenantId}\x00${config.scope.userId ?? ""}\x00${config.scope.namespace ?? ""}`;
10502
10705
  var scopePrefix = (config) => `${encodeURIComponent(config.scope.namespace ?? "default")}:${encodeURIComponent(config.scope.tenantId)}:${encodeURIComponent(config.scope.userId ?? "*")}:`;
@@ -10528,8 +10731,8 @@ var validateBudgetLedger = (config, run, identities) => {
10528
10731
  if (!/^sha256:[a-f0-9]{64}$/.test(id) || !value || typeof value !== "object" || Array.isArray(value) || value.status !== "confirmed") {
10529
10732
  throw new HarnessStateConflictError("Budget ledger retains unresolved reservations or unknown consumption.");
10530
10733
  }
10531
- const tokens = value.tokens;
10532
- if (!tokens || typeof tokens !== "object" || Array.isArray(tokens) || ![tokens.inputTokens, tokens.outputTokens, tokens.totalTokens].every((amount) => typeof amount === "number" && Number.isSafeInteger(amount) && amount >= 0) || tokens.totalTokens < tokens.inputTokens + tokens.outputTokens) {
10734
+ const tokens2 = value.tokens;
10735
+ if (!tokens2 || typeof tokens2 !== "object" || Array.isArray(tokens2) || ![tokens2.inputTokens, tokens2.outputTokens, tokens2.totalTokens].every((amount) => typeof amount === "number" && Number.isSafeInteger(amount) && amount >= 0) || tokens2.totalTokens < tokens2.inputTokens + tokens2.outputTokens) {
10533
10736
  throw new HarnessStateConflictError("Invalid budget ledger token receipt.");
10534
10737
  }
10535
10738
  }
@@ -10631,8 +10834,13 @@ var readPayload = async (config, recordedWorkspace, accessLease) => {
10631
10834
  if (recordedWorkspace && database.query("SELECT version FROM zhivex_cli_session_schema WHERE singleton=1").get()?.version !== HARNESS_SESSION_SCHEMA_VERSION)
10632
10835
  throw new HarnessStateConflictError("Archived session schema is incompatible.");
10633
10836
  const allRunRows = database.query("SELECT run_id, state_json, updated_at_ms FROM zhivex_agent_runs ORDER BY run_id").all();
10837
+ let historyReader;
10634
10838
  const runRows = allRunRows.flatMap((row) => {
10635
- const state = validatedRunState(parseJsonRecord(row.state_json, `Run ${row.run_id}`), `Run ${row.run_id}`);
10839
+ const record2 = parseJsonRecord(row.state_json, `Run ${row.run_id}`);
10840
+ const hydrated = record2.checkpointHistory ? (historyReader ??= createSqliteAgentRunStore2({ db: sqliteAdapter(database), history: "incremental" })).load(String(record2.runId), record2.scope) : record2;
10841
+ if (hydrated instanceof Promise || !hydrated)
10842
+ throw new HarnessStateConflictError("Cannot hydrate state backup history.");
10843
+ const state = validatedRunState(hydrated, `Run ${row.run_id}`);
10636
10844
  return stateMatchesScope(config, state) ? [{ row, state }] : [];
10637
10845
  });
10638
10846
  const runs = runRows.map(({ row, state }) => {
@@ -10792,7 +11000,19 @@ var writePrivateBackup = async (target, contents) => {
10792
11000
  try {
10793
11001
  handle = await open7(staged, fsConstants3.O_WRONLY | fsConstants3.O_CREAT | fsConstants3.O_EXCL | fsConstants3.O_NOFOLLOW, 384);
10794
11002
  stagedExists = true;
10795
- await handle.writeFile(contents, "utf8");
11003
+ let pending = [];
11004
+ let pendingBytes = 0;
11005
+ for (const chunk of contents) {
11006
+ pending.push(chunk);
11007
+ pendingBytes += Buffer.byteLength(chunk);
11008
+ if (pendingBytes >= 64 * 1024) {
11009
+ await handle.writeFile(pending.join(""), "utf8");
11010
+ pending = [];
11011
+ pendingBytes = 0;
11012
+ }
11013
+ }
11014
+ if (pending.length)
11015
+ await handle.writeFile(pending.join(""), "utf8");
10796
11016
  await handle.sync();
10797
11017
  await chmod4(staged, 384);
10798
11018
  await handle.close();
@@ -10819,12 +11039,13 @@ var writePrivateBackup = async (target, contents) => {
10819
11039
  };
10820
11040
  var exportHarnessStateBackup = async (config, target) => {
10821
11041
  const bundle = await createHarnessStateBackup(config);
10822
- const serialized = `${JSON.stringify(bundle, null, 2)}
10823
- `;
10824
- if (Buffer.byteLength(serialized) > HARNESS_STATE_BACKUP_MAX_BYTES) {
10825
- throw new HarnessStateConflictError("State backup exceeds the supported size limit.");
11042
+ let bytes = 0;
11043
+ for (const chunk of canonicalChunks(bundle)) {
11044
+ bytes += Buffer.byteLength(chunk);
11045
+ if (bytes > HARNESS_STATE_BACKUP_MAX_BYTES)
11046
+ break;
10826
11047
  }
10827
- await writePrivateBackup(target, serialized);
11048
+ await writePrivateBackup(target, bytes > HARNESS_STATE_BACKUP_MAX_BYTES ? segmentedChunks(bundle) : canonicalChunks(bundle));
10828
11049
  return {
10829
11050
  schemaVersion: HARNESS_STATE_BACKUP_SCHEMA_VERSION,
10830
11051
  kind: "state-export",
@@ -10834,19 +11055,14 @@ var exportHarnessStateBackup = async (config, target) => {
10834
11055
  };
10835
11056
  };
10836
11057
  var readHarnessStateBackup = async (source) => {
10837
- const { contents, stat: stat4 } = await readRegularFileNoFollow(path14.resolve(source), {
10838
- label: "Harness state backup",
10839
- maxBytes: HARNESS_STATE_BACKUP_MAX_BYTES,
10840
- requireSingleLink: true
10841
- });
10842
- if ((stat4.mode & 63) !== 0) {
10843
- throw new HarnessWorkspaceError("Harness state backup permissions must not grant group or other access.");
10844
- }
10845
11058
  let input;
10846
11059
  try {
10847
- input = JSON.parse(contents.toString("utf8"));
11060
+ input = await readBackupTransport(path14.resolve(source), HARNESS_STATE_BACKUP_MAX_BYTES);
10848
11061
  } catch (error) {
10849
- throw new HarnessStateConflictError("Harness state backup is not valid JSON.", { cause: error });
11062
+ if (error instanceof SyntaxError) {
11063
+ throw new HarnessStateConflictError("Harness state backup is not valid JSON.", { cause: error });
11064
+ }
11065
+ throw error;
10850
11066
  }
10851
11067
  return validateStateBackupBundle(input);
10852
11068
  };
@@ -1,6 +1,8 @@
1
1
  import { type AgentRunState, type AgentStatus } from "@zhivex-ai/agents";
2
- import type { AgentMemoryStore, AgentRunStore } from "@zhivex-ai/agents/ops";
2
+ import type { AgentMemoryStore, AgentRunStore, SqliteAgentRunStoreOptions } from "@zhivex-ai/agents/ops";
3
3
  import { type HarnessConfig, type HarnessStoreBackend } from "../runtime/config.js";
4
+ import { SqliteDatabase } from "./sqlite-database.js";
5
+ type SqliteDatabaseLike = SqliteAgentRunStoreOptions["db"];
4
6
  export declare const HARNESS_SQLITE_FILE = "operations.sqlite";
5
7
  export declare const HARNESS_OPERATIONS_SCHEMA_VERSION: 1;
6
8
  export declare const TERMINAL_RUN_STATUSES: readonly ["completed", "failed", "cancelled", "timed_out"];
@@ -313,7 +315,9 @@ export declare const cleanupHarnessRuns: (store: AgentRunStore, config: HarnessC
313
315
  statuses: AgentStatus[];
314
316
  deleted: number;
315
317
  }>;
318
+ export declare const sqliteAdapter: (database: SqliteDatabase) => SqliteDatabaseLike;
316
319
  export declare const migrateLegacyFileRuns: (directory: string, target: AgentRunStore, config: HarnessConfig) => Promise<HarnessMigrationResult>;
317
320
  export declare const openHarnessPersistence: (config: HarnessConfig, options?: {
318
321
  migrateLegacyFileStore?: boolean;
319
322
  }) => Promise<HarnessPersistence>;
323
+ export {};
@@ -0,0 +1,4 @@
1
+ /** Canonical JSON without allocating a serialized copy of the entire bundle. */
2
+ export declare function canonicalChunks(value: unknown): Generator<string>;
3
+ export declare function segmentedChunks(value: unknown): Generator<string>;
4
+ export declare function readBackupTransport(source: string, legacyMaxBytes: number): Promise<unknown>;
@@ -4579,6 +4579,31 @@ var readRegularFileNoFollow = async (target, options) => {
4579
4579
  await handle.close();
4580
4580
  }
4581
4581
  };
4582
+ async function* readRegularFileChunksNoFollow(target, options) {
4583
+ const handle = await openRegularFileNoFollow(target, options.label);
4584
+ try {
4585
+ const before = await handle.stat();
4586
+ if (!before.isFile() || options.requireSingleLink && before.nlink !== 1)
4587
+ throw invalidFileError(options.label);
4588
+ if (options.requirePrivate && (before.mode & 63) !== 0)
4589
+ throw new Error(`${options.label} permissions must not grant group or other access.`);
4590
+ let offset = 0;
4591
+ while (offset < before.size) {
4592
+ const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, before.size - offset));
4593
+ const { bytesRead: bytesRead2 } = await handle.read(buffer, 0, buffer.length, offset);
4594
+ if (!bytesRead2)
4595
+ throw new FileChangedWhileReadingError(options.label);
4596
+ offset += bytesRead2;
4597
+ yield buffer.subarray(0, bytesRead2);
4598
+ }
4599
+ const probe = Buffer.allocUnsafe(1);
4600
+ const { bytesRead } = await handle.read(probe, 0, 1, offset);
4601
+ if (bytesRead || !sameOpenFile(before, await handle.stat()))
4602
+ throw new FileChangedWhileReadingError(options.label);
4603
+ } finally {
4604
+ await handle.close();
4605
+ }
4606
+ }
4582
4607
 
4583
4608
  // src/workspace/workspace-mutation-lock.ts
4584
4609
  init_errors();
@@ -10587,7 +10612,7 @@ var openHarnessPersistence = async (config, options = {}) => {
10587
10612
  database.exec("PRAGMA foreign_keys = ON");
10588
10613
  await chmod3(databasePath, 384);
10589
10614
  const databaseLike = sqliteAdapter(database);
10590
- const store = withBudgetLedgerStore(createSqliteAgentRunStore({ db: databaseLike, scope: config.scope }), createSqliteAgentRunStore({ db: databaseLike, scope: budgetScopeFor(config) }), config);
10615
+ const store = withBudgetLedgerStore(createSqliteAgentRunStore({ db: databaseLike, scope: config.scope, history: "incremental" }), createSqliteAgentRunStore({ db: databaseLike, scope: budgetScopeFor(config) }), config);
10591
10616
  const memory = createSqliteAgentMemoryStore({ db: databaseLike, scope: config.scope });
10592
10617
  const migration = shouldMigrateLegacyRuns(config, options.migrateLegacyFileStore) ? await migrateLegacyFileRuns(config.stateDirectory, store, config) : emptyMigration();
10593
10618
  return {
@@ -11,7 +11,7 @@ import {
11
11
  navigateConsole,
12
12
  sanitizeTerminalText,
13
13
  serializeStreamResult
14
- } from "./cli-entry-b0dtz99m.js";
14
+ } from "./cli-entry-c2f58zj2.js";
15
15
  import {
16
16
  HARNESS_VERSION
17
17
  } from "./cli-entry-64pdp3k5.js";
@@ -8163,7 +8163,7 @@ var openHarnessPersistence = async (config, options = {}) => {
8163
8163
  database.exec("PRAGMA foreign_keys = ON");
8164
8164
  await chmod3(databasePath, 384);
8165
8165
  const databaseLike = sqliteAdapter(database);
8166
- const store = withBudgetLedgerStore(createSqliteAgentRunStore({ db: databaseLike, scope: config.scope }), createSqliteAgentRunStore({ db: databaseLike, scope: budgetScopeFor(config) }), config);
8166
+ const store = withBudgetLedgerStore(createSqliteAgentRunStore({ db: databaseLike, scope: config.scope, history: "incremental" }), createSqliteAgentRunStore({ db: databaseLike, scope: budgetScopeFor(config) }), config);
8167
8167
  const memory = createSqliteAgentMemoryStore({ db: databaseLike, scope: config.scope });
8168
8168
  const migration = shouldMigrateLegacyRuns(config, options.migrateLegacyFileStore) ? await migrateLegacyFileRuns(config.stateDirectory, store, config) : emptyMigration();
8169
8169
  return {
@@ -8180,6 +8180,45 @@ var openHarnessPersistence = async (config, options = {}) => {
8180
8180
  }
8181
8181
  };
8182
8182
 
8183
+ // src/persistence/state-backup-stream.ts
8184
+ var STRING_CHARS = 8192;
8185
+ var MAX_FRAME_BYTES = 64 * 1024;
8186
+ function* canonicalChunks(value) {
8187
+ if (typeof value === "string") {
8188
+ yield '"';
8189
+ for (let i = 0;i < value.length; ) {
8190
+ let end = Math.min(i + STRING_CHARS, value.length);
8191
+ if (end < value.length && /[\uD800-\uDBFF]/.test(value[end - 1]))
8192
+ end--;
8193
+ yield JSON.stringify(value.slice(i, end)).slice(1, -1);
8194
+ i = end;
8195
+ }
8196
+ yield '"';
8197
+ } else if (value === null || typeof value !== "object") {
8198
+ yield JSON.stringify(value);
8199
+ } else if (Array.isArray(value)) {
8200
+ yield "[";
8201
+ for (let i = 0;i < value.length; i++) {
8202
+ if (i)
8203
+ yield ",";
8204
+ yield* canonicalChunks(value[i]);
8205
+ }
8206
+ yield "]";
8207
+ } else {
8208
+ yield "{";
8209
+ let first = true;
8210
+ for (const key of Object.keys(value).sort()) {
8211
+ if (!first)
8212
+ yield ",";
8213
+ first = false;
8214
+ yield* canonicalChunks(key);
8215
+ yield ":";
8216
+ yield* canonicalChunks(value[key]);
8217
+ }
8218
+ yield "}";
8219
+ }
8220
+ }
8221
+
8183
8222
  // src/persistence/workspace-checkpoints.ts
8184
8223
  import { createHash as createHash11, randomUUID as randomUUID6 } from "node:crypto";
8185
8224
  import { z as z13 } from "zod";
@@ -8210,6 +8249,7 @@ import { chmod as chmod4, link as link2, lstat as lstat10, open as open7, realpa
8210
8249
  import path13 from "node:path";
8211
8250
  import { z as z14 } from "zod";
8212
8251
  import { normalizeAgentRunState } from "@zhivex-ai/agents";
8252
+ import { createSqliteAgentRunStore as createSqliteAgentRunStore2 } from "@zhivex-ai/agents/ops";
8213
8253
  var HARNESS_STATE_BACKUP_SCHEMA_VERSION = 1;
8214
8254
  var HARNESS_STATE_BACKUP_MAX_BYTES = 64 * 1024 * 1024;
8215
8255
  var terminalRunStatuses = new Set(["completed", "failed", "cancelled", "timed_out"]);
@@ -8336,14 +8376,12 @@ var stateBackupPayloadSchema = z14.object({
8336
8376
  }).strict()
8337
8377
  }).strict();
8338
8378
  var stateBackupBundleSchema = stateBackupPayloadSchema.extend({ checksum: digestSchema2 }).strict();
8339
- var canonicalJson = (value) => {
8340
- if (value === null || typeof value !== "object")
8341
- return JSON.stringify(value);
8342
- if (Array.isArray(value))
8343
- return `[${value.map(canonicalJson).join(",")}]`;
8344
- return `{${Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`).join(",")}}`;
8379
+ var checksumPayload = (payload) => {
8380
+ const hash2 = createHash12("sha256");
8381
+ for (const chunk of canonicalChunks(payload))
8382
+ hash2.update(chunk);
8383
+ return `sha256:${hash2.digest("hex")}`;
8345
8384
  };
8346
- var checksumPayload = (payload) => `sha256:${createHash12("sha256").update(canonicalJson(payload)).digest("hex")}`;
8347
8385
  var stableKey2 = (kind, value) => createHash12("sha256").update(kind).update("\x00").update(value).digest("hex");
8348
8386
  var scopeValue2 = (config) => `${config.scope.tenantId}\x00${config.scope.userId ?? ""}\x00${config.scope.namespace ?? ""}`;
8349
8387
  var scopePrefix = (config) => `${encodeURIComponent(config.scope.namespace ?? "default")}:${encodeURIComponent(config.scope.tenantId)}:${encodeURIComponent(config.scope.userId ?? "*")}:`;
@@ -8478,8 +8516,13 @@ var readPayload = async (config, recordedWorkspace, accessLease) => {
8478
8516
  if (recordedWorkspace && database.query("SELECT version FROM zhivex_cli_session_schema WHERE singleton=1").get()?.version !== HARNESS_SESSION_SCHEMA_VERSION)
8479
8517
  throw new HarnessStateConflictError("Archived session schema is incompatible.");
8480
8518
  const allRunRows = database.query("SELECT run_id, state_json, updated_at_ms FROM zhivex_agent_runs ORDER BY run_id").all();
8519
+ let historyReader;
8481
8520
  const runRows = allRunRows.flatMap((row) => {
8482
- const state = validatedRunState(parseJsonRecord(row.state_json, `Run ${row.run_id}`), `Run ${row.run_id}`);
8521
+ const record2 = parseJsonRecord(row.state_json, `Run ${row.run_id}`);
8522
+ const hydrated = record2.checkpointHistory ? (historyReader ??= createSqliteAgentRunStore2({ db: sqliteAdapter(database), history: "incremental" })).load(String(record2.runId), record2.scope) : record2;
8523
+ if (hydrated instanceof Promise || !hydrated)
8524
+ throw new HarnessStateConflictError("Cannot hydrate state backup history.");
8525
+ const state = validatedRunState(hydrated, `Run ${row.run_id}`);
8483
8526
  return stateMatchesScope(config, state) ? [{ row, state }] : [];
8484
8527
  });
8485
8528
  const runs = runRows.map(({ row, state }) => {
@@ -10030,11 +10073,11 @@ var createEmptyHarnessContextBundle = () => {
10030
10073
  schemaVersion: HARNESS_CONTEXT_BUNDLE_SCHEMA_VERSION,
10031
10074
  scope: "project",
10032
10075
  ...identity,
10033
- fingerprint: digestBytes2(canonicalJson2(harnessContextFingerprintInput(identity)))
10076
+ fingerprint: digestBytes2(canonicalJson(harnessContextFingerprintInput(identity)))
10034
10077
  };
10035
10078
  };
10036
10079
  var digestBytes2 = (value) => `sha256:${createHash16("sha256").update(value).digest("hex")}`;
10037
- var canonicalJson2 = (value) => JSON.stringify(value);
10080
+ var canonicalJson = (value) => JSON.stringify(value);
10038
10081
  var validateRelativePath = (value) => {
10039
10082
  const parsed = workspaceFilePathSchema.safeParse(value);
10040
10083
  if (!parsed.success) {
@@ -10276,7 +10319,7 @@ var loadHarnessProjectContext = async (workspace, options = {}) => {
10276
10319
  sources,
10277
10320
  skills
10278
10321
  };
10279
- const fingerprint = digestBytes2(canonicalJson2(harnessContextFingerprintInput(identity)));
10322
+ const fingerprint = digestBytes2(canonicalJson(harnessContextFingerprintInput(identity)));
10280
10323
  return {
10281
10324
  schemaVersion: HARNESS_CONTEXT_BUNDLE_SCHEMA_VERSION,
10282
10325
  scope: "project",
@@ -12649,16 +12692,16 @@ ${contextInstructions}` : ""}${enabledDelegations}`,
12649
12692
  }
12650
12693
  };
12651
12694
  };
12652
- var canonicalJson3 = (value) => {
12695
+ var canonicalJson2 = (value) => {
12653
12696
  if (value === null || typeof value !== "object")
12654
12697
  return JSON.stringify(value);
12655
12698
  if (Array.isArray(value))
12656
- return `[${value.map(canonicalJson3).join(",")}]`;
12699
+ return `[${value.map(canonicalJson2).join(",")}]`;
12657
12700
  const record4 = value;
12658
- return `{${Object.keys(record4).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson3(record4[key])}`).join(",")}}`;
12701
+ return `{${Object.keys(record4).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson2(record4[key])}`).join(",")}}`;
12659
12702
  };
12660
12703
  var localApprovalResolutionPayload = (inputDigest, approve, reason) => JSON.stringify({ inputDigest, approve, reason: reason ?? null });
12661
- var terminalToolCallId = (runId, step, providerToolCallId, toolName2, input) => `tool_${createHash22("sha256").update(`${runId}\x00${step}\x00${providerToolCallId}\x00${toolName2}\x00${canonicalJson3(input)}`).digest("hex")}`;
12704
+ var terminalToolCallId = (runId, step, providerToolCallId, toolName2, input) => `tool_${createHash22("sha256").update(`${runId}\x00${step}\x00${providerToolCallId}\x00${toolName2}\x00${canonicalJson2(input)}`).digest("hex")}`;
12662
12705
  var recoverableTerminalStaleDigest = (toolName2, message) => toolName2 === "verify_and_apply_reviewed_edits" && /^Stale patch rejected for .+\.$/.test(message);
12663
12706
  var patchIdMismatch = (error) => {
12664
12707
  for (let depth = 0;depth < 8 && error instanceof Error; depth++, error = error.cause) {
@@ -12736,7 +12779,7 @@ var executeTerminalReceiptToolOwned = async (harness, waiting, approval, respons
12736
12779
  definition.approvalVersion,
12737
12780
  `environment:${session.binding.fingerprint}`
12738
12781
  ].filter(Boolean).join("|") || "1";
12739
- const inputDigest = createHash22("sha256").update(canonicalJson3({
12782
+ const inputDigest = createHash22("sha256").update(canonicalJson2({
12740
12783
  runId: waiting.state.runId,
12741
12784
  step: approval.step,
12742
12785
  toolCallId: approval.toolCallId,
@@ -12744,7 +12787,7 @@ var executeTerminalReceiptToolOwned = async (harness, waiting, approval, respons
12744
12787
  input: serializedInput,
12745
12788
  toolVersion
12746
12789
  })).digest("hex");
12747
- if (approval.id !== `approval_${inputDigest}` || approval.inputDigest !== inputDigest || approval.toolVersion !== toolVersion || approval.arguments !== canonicalJson3(serializedInput)) {
12790
+ if (approval.id !== `approval_${inputDigest}` || approval.inputDigest !== inputDigest || approval.toolVersion !== toolVersion || approval.arguments !== canonicalJson2(serializedInput)) {
12748
12791
  await session.release?.({ status: "failed", error: { message: "Approval binding mismatch." } });
12749
12792
  throw new Error("The terminal tool approval is stale or does not match the current runtime binding.");
12750
12793
  }
@@ -12941,7 +12984,7 @@ var executeTerminalReceiptToolOwned = async (harness, waiting, approval, respons
12941
12984
  finishedAt: now,
12942
12985
  toolResults: [...step.toolResults, toolResult]
12943
12986
  } : step);
12944
- const outputText = canonicalJson3(output);
12987
+ const outputText = canonicalJson2(output);
12945
12988
  const previousRevision = waiting.state.revision ?? 0;
12946
12989
  const state = {
12947
12990
  ...waiting.state,
@@ -27,3 +27,9 @@ export declare const statRegularFileNoFollow: (target: string, options: Pick<Rea
27
27
  * time-of-check/time-of-use windows.
28
28
  */
29
29
  export declare const readRegularFileNoFollow: (target: string, options: ReadRegularFileNoFollowOptions) => Promise<StableRegularFile>;
30
+ /** Descriptor-bound bounded reads; identity is revalidated before successful EOF. */
31
+ export declare function readRegularFileChunksNoFollow(target: string, options: {
32
+ label: string;
33
+ requireSingleLink?: boolean;
34
+ requirePrivate?: boolean;
35
+ }): AsyncGenerator<Buffer>;
package/dist/zhx.js CHANGED
@@ -18,6 +18,6 @@ if (argv.length === 1 && ["--version", "-v", "version"].includes(argv[0])) {
18
18
  process.stdout.write(`${shortCliHelp(HARNESS_VERSION)}
19
19
  `);
20
20
  } else {
21
- const { runCli } = await import("./cli-3qwxrh91.js");
21
+ const { runCli } = await import("./cli-3jmy4h5h.js");
22
22
  await runCli(argv);
23
23
  }
@@ -64,6 +64,14 @@ zhx sessions archive <sessionId>
64
64
 
65
65
  The store uses SQLite WAL transactions, optimistic revisions, workspace/scope hashes, owner-only permissions, soft deletion/retention, and bounded session/run/metadata/index sizes. It rejects a new run while the latest session run is active or waiting for approval. Fork and archive operations also require a terminal branch point.
66
66
 
67
+ SQLite runs store steps, tool results and compaction records separately from the active checkpoint. Large repeated text is retained in per-run content-addressed artifacts. Checkpoint and history changes commit in the same transaction; unchanged historical records are not rewritten. The 4 MiB state limit applies to the active checkpoint, including current messages, approvals, metadata and counters. Conversation compaction still controls model input size independently.
68
+
69
+ Loading a run reconstructs its complete state for compatibility with approvals, recovery and verification. Programmatic history inspection can use `store.loadHistory(runId, { field: "toolResults", offset: 0, limit: 50 }, scope)` to read a page. Full hydrated states and total disk usage can grow with the run; run retention remains necessary. File and in-memory stores retain the full-state size limit.
70
+
71
+ Logical backups contain hydrated history rather than database references, so restoration does not require the original artifact tables. Exports up to 64 MiB retain the legacy JSON format; larger exports use the versioned `ZHIVEX-STATE-SEGMENTS-1` transport with bounded frames, including split strings. RC13 reads both formats and verifies the same logical checksum before import. Older versions cannot read segmented exports. File I/O and checksum serialization are incremental; the public backup/import APIs still hydrate the complete logical bundle in memory. Segmented files have no aggregate 64 MiB ceiling. A state-limit failure records a bounded terminal diagnostic on the last durable checkpoint; the unsaved payload is not claimed as preserved. Use `/status` to inspect the failed run before `/continue`. Recovery retains tool journal receipts and never re-executes an operation itself.
72
+
73
+ Incremental SQLite history uses the published Core 1.26.0 and Agents 1.10.1 packages. No local dependency patch is required. Install this checkout with `bun install --frozen-lockfile`.
74
+
67
75
  ## Provider handoff safety
68
76
 
69
77
  Every console turn preallocates a new run ID and binds it permanently to one provider/model. Provider or model changes take effect only on the next run and are blocked while an approval is pending. Before transferring context to another provider/model, the console creates a bounded deterministic redacted summary: text is truncated and common credentials are removed, while tool inputs, outputs, and provider payloads are replaced by tool/type names.
@@ -8,7 +8,7 @@ Built-in registrations are Meta, Qwen, OpenAI, and Gemini. Each registration own
8
8
 
9
9
  Library callers can use `DEFAULT_PROVIDER_REGISTRY`, `BUILTIN_PROVIDER_REGISTRATIONS`, `createProviderRegistry`, or `DEFAULT_PROVIDER_REGISTRY.extend(...)`, then pass the registry to `resolveHarnessConfig`, `createProviderModel`, provider diagnostics, or `createHarness({ providerRegistry })`. Registration IDs, environment-variable names, defaults, diagnostics, and factories are validated before they become selectable.
10
10
 
11
- The coordinated stable SDK batch pins `@zhivex-ai/agents@1.10.0`, `@zhivex-ai/core@1.25.0`, `@zhivex-ai/meta@0.2.8`, `@zhivex-ai/qwen@0.16.2`, `@zhivex-ai/openai@0.13.6`, and `@zhivex-ai/gemini@0.12.3`. Qwen 0.16.2 types malformed Responses tool arguments and malformed SSE events; Core 1.25.0 satisfies its published dependency range. These exact versions are pinned in the lockfile. Stable SDK publication does not replace Harness release-bound certification. Qwen `0.11.x` normalizes missing, placeholder, and continuation tool-call identifiers inside the adapter; Harness retains its deterministic provider-boundary normalizer as defense in depth until the exact candidate passes the complete release-bound representative matrix. A Core override keeps one runtime contract identity across every adapter, while Core `1.13.0` corrects compaction usage accounting and interaction-group retention. OpenAI `0.11.2` preserves synthetic assistant history in Responses, so the harness no longer requires its compaction transport or accounting workarounds.
11
+ The coordinated stable SDK batch pins `@zhivex-ai/agents@1.10.1`, `@zhivex-ai/core@1.26.0`, `@zhivex-ai/meta@0.2.8`, `@zhivex-ai/qwen@0.16.2`, `@zhivex-ai/openai@0.13.6`, and `@zhivex-ai/gemini@0.12.3`. Qwen 0.16.2 types malformed Responses tool arguments and malformed SSE events; Core 1.26.0 satisfies its published dependency range. These exact versions are pinned in the lockfile. Stable SDK publication does not replace Harness release-bound certification. Qwen `0.11.x` normalizes missing, placeholder, and continuation tool-call identifiers inside the adapter; Harness retains its deterministic provider-boundary normalizer as defense in depth until the exact candidate passes the complete release-bound representative matrix. A Core override keeps one runtime contract identity across every adapter, while Core `1.13.0` corrects compaction usage accounting and interaction-group retention. OpenAI `0.11.2` preserves synthetic assistant history in Responses, so the harness no longer requires its compaction transport or accounting workarounds.
12
12
 
13
13
  OpenAI is based on the GPT-5.6 family: `gpt-5.6-luna` is the default, with `gpt-5.6-terra` and `gpt-5.6-sol` available through `--model`. Availability remains organization-dependent while the upstream family is in limited preview, and the adapter uses the Responses API by default.
14
14
 
package/docs/FIRST_USE.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # First use
2
2
 
3
- This guide targets the unpublished `1.2.0-rc.12` candidate. Installation commands become available after protected publication to npm `next`; stable users can install `@zhivex-ai/harness@latest`.
3
+ This guide targets the unpublished `1.2.0-rc.13` candidate. Installation commands become available after protected publication to npm `next`; stable users can install `@zhivex-ai/harness@latest`.
4
4
 
5
5
  ## Install and start
6
6
 
7
7
  Install Node.js 22.13.0 or newer and Git. Then:
8
8
 
9
9
  ```sh
10
- npm install -g @zhivex-ai/harness@1.2.0-rc.12
10
+ npm install -g @zhivex-ai/harness@1.2.0-rc.13
11
11
  cd /path/to/your/project
12
12
  zhx
13
13
  ```
@@ -82,16 +82,16 @@ For command execution, configure [OCI isolation](EXECUTION_ENVIRONMENTS.md).
82
82
  With Bun 1.4.0 or newer and a supported Node runtime:
83
83
 
84
84
  ```sh
85
- bun add --global @zhivex-ai/harness@1.2.0-rc.12
85
+ bun add --global @zhivex-ai/harness@1.2.0-rc.13
86
86
  zhx
87
87
  ```
88
88
 
89
89
  For a disposable version/help/environment check without a global installation:
90
90
 
91
91
  ```sh
92
- bunx @zhivex-ai/harness@1.2.0-rc.12 --version
93
- bunx @zhivex-ai/harness@1.2.0-rc.12 --help
94
- bunx @zhivex-ai/harness@1.2.0-rc.12 doctor
92
+ bunx @zhivex-ai/harness@1.2.0-rc.13 --version
93
+ bunx @zhivex-ai/harness@1.2.0-rc.13 --help
94
+ bunx @zhivex-ai/harness@1.2.0-rc.13 doctor
95
95
  ```
96
96
 
97
97
  For source development, use the [contributor guide](https://github.com/Zhivex/zhivex-harness/blob/main/CONTRIBUTING.md).
@@ -43,7 +43,8 @@
43
43
  "v1.2.0-rc.9",
44
44
  "v1.2.0-rc.10",
45
45
  "v1.2.0-rc.11",
46
- "v1.2.0-rc.12"
46
+ "v1.2.0-rc.12",
47
+ "v1.2.0-rc.13"
47
48
  ],
48
49
  "expectedModels": [
49
50
  {
@@ -381,6 +382,14 @@
381
382
  "qwen": "qwen3.8-flash",
382
383
  "openai": "gpt-6-luna"
383
384
  }
385
+ },
386
+ {
387
+ "releaseTag": "v1.2.0-rc.13",
388
+ "models": {
389
+ "meta": "muse-spark-1.3-contributor",
390
+ "qwen": "qwen3.8-flash",
391
+ "openai": "gpt-6-luna"
392
+ }
384
393
  }
385
394
  ],
386
395
  "expectedCases": [
@@ -8,13 +8,13 @@ Requirements: Bun 1.4.0+, Node.js 22.13.0+, and Git on macOS or Linux. This inst
8
8
  harness_demo_dir=$(mktemp -d)
9
9
  cd "$harness_demo_dir"
10
10
  printf '{"name":"harness-first-run","private":true}\n' > package.json
11
- bun add --exact --ignore-scripts @zhivex-ai/harness@1.2.0-rc.12
11
+ bun add --exact --ignore-scripts @zhivex-ai/harness@1.2.0-rc.13
12
12
  ./node_modules/.bin/zhx --version
13
13
  ./node_modules/.bin/zhx --help
14
14
  ./node_modules/.bin/zhx doctor --provider openai --json
15
15
  ```
16
16
 
17
- Version must print `1.2.0-rc.12`; help lists commands. Doctor does not contact a provider. Without `OPENAI_API_KEY`, it reports the missing credential and exits with code `3`; inspect the JSON checks before proceeding. A directory without Git initialization can also produce a diagnostic warning. No paid model call is required for these checks. Both `zhx` and `zhivex-harness` are installed aliases; Bun manages installation, and their shebang runs Node.
17
+ Version must print `1.2.0-rc.13`; help lists commands. Doctor does not contact a provider. Without `OPENAI_API_KEY`, it reports the missing credential and exits with code `3`; inspect the JSON checks before proceeding. A directory without Git initialization can also produce a diagnostic warning. No paid model call is required for these checks. Both `zhx` and `zhivex-harness` are installed aliases; Bun manages installation, and their shebang runs Node.
18
18
 
19
19
  To continue in your actual project, use the [global installation and credential setup](../README.md#installation), then `zhx init --profile daily --provider openai` and `zhx doctor --profile daily`. Real tasks require the selected provider credential in the process environment. Profiles store provider/model choices, never credentials. See [CLI](../docs/CLI.md) and [support](../SUPPORT.md).
20
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhivex-ai/harness",
3
- "version": "1.2.0-rc.12",
3
+ "version": "1.2.0-rc.13",
4
4
  "description": "Governed, provider-portable runtime for coding agents with durable approvals and isolated repository execution.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -146,8 +146,8 @@
146
146
  "release:prepare": "bun run scripts/prepare-release.ts"
147
147
  },
148
148
  "dependencies": {
149
- "@zhivex-ai/agents": "1.10.0",
150
- "@zhivex-ai/core": "1.25.0",
149
+ "@zhivex-ai/agents": "1.10.1",
150
+ "@zhivex-ai/core": "1.26.0",
151
151
  "@zhivex-ai/gemini": "0.12.3",
152
152
  "@zhivex-ai/meta": "0.2.8",
153
153
  "@zhivex-ai/openai": "0.13.6",
@@ -155,7 +155,7 @@
155
155
  "zod": "4.6.5"
156
156
  },
157
157
  "overrides": {
158
- "@zhivex-ai/core": "1.25.0"
158
+ "@zhivex-ai/core": "1.26.0"
159
159
  },
160
160
  "devDependencies": {
161
161
  "@modelcontextprotocol/server": "2.0.0",