@zhivex-ai/harness 1.2.0-rc.9 → 1.2.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +7 -7
  3. package/contracts/public-api.json +5 -0
  4. package/contracts/stable-api-signatures.json +28 -28
  5. package/dist/acp-cli.js +804 -545
  6. package/dist/cli/arguments.d.ts +2 -0
  7. package/dist/cli/cli-credentials.d.ts +1 -1
  8. package/dist/cli/cli-options.d.ts +1 -1
  9. package/dist/cli/cli-profiles.d.ts +15 -0
  10. package/dist/cli/cli-stream.d.ts +3 -3
  11. package/dist/cli/console/console-commands.d.ts +3 -3
  12. package/dist/cli/console/console-input.d.ts +22 -3
  13. package/dist/cli/console/console-navigation.d.ts +3 -0
  14. package/dist/cli/console/console-presentation.d.ts +8 -2
  15. package/dist/cli/console/console-reasoning.d.ts +3 -0
  16. package/dist/cli/presentation.d.ts +3 -0
  17. package/dist/cli/run-document.d.ts +4 -4
  18. package/dist/cli/terminal/activity-history.d.ts +15 -0
  19. package/dist/cli/terminal/tool-activity.d.ts +3 -1
  20. package/dist/{cli-2dxcfrz5.js → cli-3jmy4h5h.js} +348 -187
  21. package/dist/{cli-entry-t3bay5k5.js → cli-entry-427869g9.js} +287 -180
  22. package/dist/{cli-entry-0mhbpe8j.js → cli-entry-b1mfvmxe.js} +2 -1
  23. package/dist/{cli-entry-gddtj1db.js → cli-entry-c2f58zj2.js} +699 -139
  24. package/dist/cli.js +2 -2
  25. package/dist/client/json-contracts.d.ts +20 -0
  26. package/dist/{config-w1npe8mp.js → config-1k9t4jhh.js} +1 -1
  27. package/dist/context/task-memory.d.ts +2 -2
  28. package/dist/hostile-repository-demo.js +661 -448
  29. package/dist/index.js +1517 -1074
  30. package/dist/persistence/operations.d.ts +5 -1
  31. package/dist/persistence/state-backup-stream.d.ts +4 -0
  32. package/dist/providers/meta-replay.d.ts +1 -1
  33. package/dist/providers/qwen-context.d.ts +2 -0
  34. package/dist/providers/qwen-hosted-policy.d.ts +5 -0
  35. package/dist/providers/reasoning.d.ts +18 -0
  36. package/dist/runtime/config.d.ts +3 -0
  37. package/dist/runtime/error-diagnostics.d.ts +2 -0
  38. package/dist/runtime/harness.d.ts +1 -1
  39. package/dist/runtime/model-edit-references.d.ts +4 -2
  40. package/dist/runtime/runtime-policy.d.ts +5 -4
  41. package/dist/runtime/time-to-safe-fix.d.ts +6 -1
  42. package/dist/service-cli.js +820 -575
  43. package/dist/{service-client-cli-1c9j6xah.js → service-client-cli-yx56n0hc.js} +66 -8
  44. package/dist/tools/execution.d.ts +8 -8
  45. package/dist/tools/workspace.d.ts +16 -16
  46. package/dist/verify-historical-migrations.js +830 -574
  47. package/dist/workspace/file-security.d.ts +6 -0
  48. package/dist/zhx.js +2 -2
  49. package/docs/CLI.md +52 -1
  50. package/docs/DURABLE_OPERATIONS.md +8 -0
  51. package/docs/EXTENSIBILITY.md +1 -1
  52. package/docs/FIRST_USE.md +6 -6
  53. package/docs/MODEL_CATALOG.md +28 -1
  54. package/evaluations/representative-assembly-matrix.json +46 -1
  55. package/evaluations/representative-repositories-v2.jsonl +7 -0
  56. package/examples/README.md +4 -4
  57. package/package.json +7 -7
@@ -5,8 +5,9 @@ import {
5
5
  resolveHelpTopic,
6
6
  shortCliHelp,
7
7
  validateCliCommandOptions
8
- } from "./cli-entry-0mhbpe8j.js";
8
+ } from "./cli-entry-b1mfvmxe.js";
9
9
  import {
10
+ ActivityHistory,
10
11
  CLI_JSON_SCHEMA_VERSION,
11
12
  CliOciRuntimeAdapter,
12
13
  ConsoleInput,
@@ -20,6 +21,7 @@ import {
20
21
  MAX_HARNESS_CONTEXT_TOTAL_BYTES,
21
22
  SqliteDatabase,
22
23
  TASK_SOURCE_KEY,
24
+ ToolActivity,
23
25
  USAGE_LEDGER_KEY,
24
26
  UnsafeFileTypeError,
25
27
  Workspace,
@@ -27,16 +29,18 @@ import {
27
29
  cancelHarnessRun,
28
30
  canonicalizeChangeEnvelope,
29
31
  changeEnvelopePreconditionsSchema,
32
+ chooseReasoning,
30
33
  cleanupHarnessExecutionArtifacts,
31
34
  cleanupHarnessRuns,
32
35
  compactHarnessMessages,
36
+ compactToolResult,
33
37
  createChangeEnvelope,
34
38
  createHarness,
35
39
  digestChangeEnvelopeArtifact,
36
40
  estimateMessages,
37
41
  fileDigestSchema,
42
+ formatAppliedFiles,
38
43
  formatApproval,
39
- formatComposer,
40
44
  formatConsoleHelp,
41
45
  formatConsoleWelcome,
42
46
  formatTerminalEvent,
@@ -53,6 +57,7 @@ import {
53
57
  navigateConsole,
54
58
  openCliSessionStore,
55
59
  openHarnessPersistence,
60
+ readRegularFileChunksNoFollow,
56
61
  readRegularFileNoFollow,
57
62
  resolvePackageManager,
58
63
  resolveTerminalApprovals,
@@ -63,6 +68,7 @@ import {
63
68
  sanitizeTerminalText,
64
69
  serializeStreamEvent,
65
70
  serializeStreamResult,
71
+ sqliteAdapter,
66
72
  taskSources,
67
73
  terminalContinuationMessages,
68
74
  terminalRunFailure,
@@ -71,7 +77,7 @@ import {
71
77
  validateStateDirectory,
72
78
  verifyChangeEnvelope,
73
79
  workspaceFilePathSchema
74
- } from "./cli-entry-gddtj1db.js";
80
+ } from "./cli-entry-c2f58zj2.js";
75
81
  import {
76
82
  BUN_ENGINE_RANGE,
77
83
  HARNESS_VERSION,
@@ -89,12 +95,14 @@ import {
89
95
  PROVIDERS,
90
96
  createProviderModel,
91
97
  harnessErrorDocument,
98
+ modelReasoningEfforts,
92
99
  normalizeHarnessError,
93
100
  parseProvider,
94
101
  providerAvailability,
95
102
  providerDescriptor,
103
+ reasoningEffortSchema,
96
104
  resolveHarnessConfig
97
- } from "./cli-entry-t3bay5k5.js";
105
+ } from "./cli-entry-427869g9.js";
98
106
  import {
99
107
  __require
100
108
  } from "./cli-entry-1bx83sw4.js";
@@ -117,6 +125,7 @@ var profileNamePattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
117
125
  var cliProfileSchema = z.strictObject({
118
126
  schemaVersion: z.literal(CLI_PROFILE_SCHEMA_VERSION),
119
127
  provider: z.enum(PROVIDERS),
128
+ reasoningEffort: reasoningEffortSchema.optional(),
120
129
  model: z.string().min(1).max(512).refine((value) => !/[\u0000-\u001f\u007f]/.test(value), "model must contain printable characters")
121
130
  });
122
131
  var absoluteDirectory = (value, source, pathApi = path) => {
@@ -224,7 +233,8 @@ var createCliProfile = async (name, input, context = {}) => {
224
233
  const parsed = cliProfileSchema.safeParse({
225
234
  schemaVersion: CLI_PROFILE_SCHEMA_VERSION,
226
235
  provider: input.provider,
227
- model: input.model
236
+ model: input.model,
237
+ ...input.reasoningEffort ? { reasoningEffort: input.reasoningEffort } : {}
228
238
  });
229
239
  if (!parsed.success) {
230
240
  throw new HarnessConfigError(`CLI profile is invalid: ${parsed.error.issues[0]?.message ?? "schema mismatch"}`);
@@ -287,6 +297,7 @@ var applyCliProfile = async (options, context = {}) => {
287
297
  return {
288
298
  provider: profile.provider,
289
299
  model: profile.model,
300
+ ...profile.reasoningEffort && (!options.provider || options.provider === profile.provider) && (!options.model || options.model === profile.model) ? { reasoningEffort: profile.reasoningEffort } : {},
290
301
  ...options
291
302
  };
292
303
  };
@@ -545,6 +556,10 @@ var parseCliArgs = (argv) => {
545
556
  index += 1;
546
557
  break;
547
558
  }
559
+ case "--reasoning":
560
+ options.reasoningEffort = reasoningEffortSchema.parse(optionValue(argv, index, argument));
561
+ index += 1;
562
+ break;
548
563
  case "--require-verified-delivery":
549
564
  options.requireVerifiedDelivery = true;
550
565
  break;
@@ -1018,138 +1033,6 @@ var parseCliArgs = (argv) => {
1018
1033
  return options;
1019
1034
  };
1020
1035
 
1021
- // src/cli/terminal/tool-activity.ts
1022
- var labels = {
1023
- read_file: "Reading a file",
1024
- read_files: "Reading files",
1025
- read_dependency: "Inspecting a dependency",
1026
- search_files: "Searching files",
1027
- search_many: "Searching the project",
1028
- list_files: "Listing files",
1029
- run_check: "Running checks",
1030
- run_environment_command: "Running a command",
1031
- apply_patch: "Applying edits",
1032
- apply_reviewed_edits: "Applying reviewed edits",
1033
- apply_reviewed_replacement: "Applying a reviewed replacement",
1034
- git_diff: "Reviewing changes"
1035
- };
1036
- var summaries = {
1037
- read_file: "reads",
1038
- read_files: "reads",
1039
- read_dependency: "dependency inspections",
1040
- search_files: "searches",
1041
- search_many: "searches",
1042
- list_files: "listings",
1043
- git_diff: "diffs"
1044
- };
1045
-
1046
- class ToolActivity {
1047
- write;
1048
- tty;
1049
- columns;
1050
- active = 0;
1051
- visible = false;
1052
- label = "Working";
1053
- completed = new Map;
1054
- started = Date.now();
1055
- timer;
1056
- constructor(write, tty, columns = () => 80) {
1057
- this.write = write;
1058
- this.tty = tty;
1059
- this.columns = columns;
1060
- }
1061
- phase(label, _step) {
1062
- this.clear();
1063
- this.label = label;
1064
- this.begin();
1065
- }
1066
- start(name) {
1067
- this.active++;
1068
- this.label = labels[name ?? ""] ?? "Running a tool";
1069
- this.begin();
1070
- }
1071
- finish(name, success = true) {
1072
- this.active = Math.max(0, this.active - 1);
1073
- if (success && !["run_check", "run_environment_command"].includes(name) && !name.startsWith("verify_and_apply_") && !editLabels[name]) {
1074
- const label = summaries[name] ?? "other operations";
1075
- this.completed.set(label, (this.completed.get(label) ?? 0) + 1);
1076
- }
1077
- this.label = this.active ? this.label : "Waiting for model response";
1078
- this.begin();
1079
- }
1080
- begin() {
1081
- this.started = Date.now();
1082
- this.render();
1083
- if (this.tty && !this.timer) {
1084
- this.timer = setInterval(() => this.render(), 250);
1085
- this.timer.unref();
1086
- }
1087
- }
1088
- render() {
1089
- if (!this.tty)
1090
- return;
1091
- const elapsed = Math.floor((Date.now() - this.started) / 1000);
1092
- const done = [...this.completed.values()].reduce((sum, count) => sum + count, 0);
1093
- const text = `… ${this.label}${elapsed ? ` · ${elapsed}s` : ""}${done ? ` · ${done} tools done` : ""}`;
1094
- this.write(`\r\x1B[2K${Array.from(text).slice(0, Math.max(1, this.columns() - 1)).join("")}`);
1095
- this.visible = true;
1096
- }
1097
- pause() {
1098
- this.clear();
1099
- }
1100
- clear() {
1101
- clearInterval(this.timer);
1102
- this.timer = undefined;
1103
- if (this.visible)
1104
- this.write("\r\x1B[2K");
1105
- this.visible = false;
1106
- }
1107
- flush() {
1108
- this.clear();
1109
- if (this.completed.size) {
1110
- const details = [...this.completed].map(([label, count]) => `${label}: ${count}`).join(" · ");
1111
- this.write(`✓ Activity · ${details}
1112
- `);
1113
- }
1114
- this.completed.clear();
1115
- }
1116
- }
1117
- var editLabels = {
1118
- apply_patch: "Edits applied",
1119
- apply_reviewed_edits: "Reviewed edits applied",
1120
- apply_reviewed_replacement: "Reviewed replacement applied",
1121
- apply_environment_patch: "Environment changes imported"
1122
- };
1123
- var compactToolResult = (result) => {
1124
- if (!result.isError)
1125
- return editLabels[result.toolName] ? `✓ ${editLabels[result.toolName]}` : undefined;
1126
- const error = result.error && typeof result.error === "object" ? result.error : {};
1127
- const message = typeof error.message === "string" ? error.message : "";
1128
- let reason = "operation failed; inspect diagnostics for details";
1129
- if (error.code === "TOOL_INPUT_VALIDATION_ERROR")
1130
- reason = "invalid arguments; the assistant can correct and retry";
1131
- else if (error.code === "ENOENT" || /^ENOENT: /.test(message))
1132
- reason = "file not found; list files to locate the correct path";
1133
- else if (message.startsWith("Dependency package or path was not found in ") || message.startsWith("Dependency path was not found in "))
1134
- reason = "dependency path not found; list the package files before retrying";
1135
- else if (message.startsWith("Dependency ") && message.includes(" is not installed in this workspace's node_modules."))
1136
- reason = "dependency not installed; check the project installation";
1137
- else if (message.startsWith("Dependency paths must be package-relative;"))
1138
- reason = "dependency path is outside the permitted boundary; use a package-relative path";
1139
- else if (/^The path is protected by the harness policy: node_modules(?:\/|$)/.test(message))
1140
- reason = "dependency access requires the dependency tools";
1141
- else if (/^The path is protected by the harness policy: /.test(message))
1142
- reason = "path protected by workspace policy";
1143
- else if (message === "Dependency access only permits package.json and TypeScript declaration files.")
1144
- reason = "dependency file type is not permitted";
1145
- else if (message === "Dependency directory links are not allowed.")
1146
- reason = "dependency link is outside the permitted boundary";
1147
- else if (message.startsWith("Dependency directory changed during inspection."))
1148
- reason = "dependency changed during inspection; retry the read";
1149
- const operation = labels[result.toolName] ?? "Tool operation";
1150
- return `✗ ${operation} · ${reason}`;
1151
- };
1152
-
1153
1036
  // src/cli/terminal/terminal-table.ts
1154
1037
  var segments = new Intl.Segmenter(undefined, { granularity: "grapheme" });
1155
1038
  var graphemes = (value) => [...segments.segment(value)].map((item) => item.segment);
@@ -1430,6 +1313,7 @@ var harnessConfigInput = (config) => ({
1430
1313
  schemaVersion: config.schemaVersion,
1431
1314
  provider: config.provider,
1432
1315
  model: config.model,
1316
+ ...config.reasoningEffort ? { reasoningEffort: config.reasoningEffort } : {},
1433
1317
  workspace: config.workspace,
1434
1318
  stateDirectory: config.stateDirectory,
1435
1319
  storeBackend: config.storeBackend,
@@ -1650,6 +1534,8 @@ var toolActivities = new WeakMap;
1650
1534
  var lastTerminalFailures = new WeakMap;
1651
1535
  var flushToolActivity = (tracker) => toolActivities.get(tracker)?.flush();
1652
1536
  var streamSink = (output, tracker, compact = false) => async (event) => {
1537
+ if (!output.json && !output.jsonl)
1538
+ tracker.activityHistory?.observe(event);
1653
1539
  if (output.jsonl) {
1654
1540
  tracker.sequence = (tracker.sequence ?? 0) + 1;
1655
1541
  process.stdout.write(`${serializeStreamEvent(event, tracker.sequence)}
@@ -1663,7 +1549,7 @@ var streamSink = (output, tracker, compact = false) => async (event) => {
1663
1549
  if (!activity) {
1664
1550
  activity = new ToolActivity((text) => {
1665
1551
  process.stderr.write(text);
1666
- }, Boolean(process.stderr.isTTY && process.stdout.isTTY), () => process.stderr.columns || 80);
1552
+ }, Boolean(process.stderr.isTTY && process.stdout.isTTY), () => process.stderr.columns || 80, tracker.inputStatus);
1667
1553
  toolActivities.set(tracker, activity);
1668
1554
  }
1669
1555
  if (event.type === "agent-compaction") {
@@ -1734,7 +1620,9 @@ var streamSink = (output, tracker, compact = false) => async (event) => {
1734
1620
  }
1735
1621
  if (!output.json && event.type === "text-delta") {
1736
1622
  if (compact && !conversationStarted.has(tracker)) {
1737
- process.stdout.write(`
1623
+ process.stdout.write(terminalSupportsColor(Boolean(process.stdout.isTTY)) ? `
1624
+ \x1B[1mZhivex\x1B[0m
1625
+ ` : `
1738
1626
  Zhivex
1739
1627
  `);
1740
1628
  conversationStarted.add(tracker);
@@ -1810,7 +1698,10 @@ var initializeCli = async (options, context = {}) => {
1810
1698
  } catch (error) {
1811
1699
  throw new CliUsageError(error instanceof Error ? error.message : String(error));
1812
1700
  }
1701
+ let reasoningEffort = options.reasoningEffort ?? existing?.reasoningEffort;
1813
1702
  let model = options.model ?? (existing ? existing.provider === provider ? existing.model : providerDescriptor(provider).defaultModel : process.env.ZHIVEX_HARNESS_MODEL ?? providerDescriptor(provider).defaultModel);
1703
+ if (existing && (existing.provider !== provider || existing.model !== model) && options.reasoningEffort === undefined)
1704
+ reasoningEffort = undefined;
1814
1705
  if (!options.json && process.stdin.isTTY && process.stdout.isTTY) {
1815
1706
  const input = new ConsoleInput(process.stdin, process.stdout);
1816
1707
  try {
@@ -1818,7 +1709,7 @@ var initializeCli = async (options, context = {}) => {
1818
1709
  const providers = [...providerAvailability()].sort((a, b) => Number(b.configured) - Number(a.configured) || Number(b.id === provider) - Number(a.id === provider));
1819
1710
  const selected = await navigateConsole(input, {
1820
1711
  entry: options.provider ? "model" : "provider",
1821
- current: { provider, model },
1712
+ current: { provider, model, ...reasoningEffort ? { reasoningEffort } : {} },
1822
1713
  providers,
1823
1714
  sessions: async () => []
1824
1715
  });
@@ -1829,12 +1720,16 @@ var initializeCli = async (options, context = {}) => {
1829
1720
  }
1830
1721
  provider = parseProvider(selected.provider);
1831
1722
  model = options.model ?? selected.model;
1723
+ reasoningEffort = options.reasoningEffort ?? selected.reasoningEffort;
1832
1724
  }
1833
1725
  } finally {
1834
1726
  input.close();
1835
1727
  }
1836
1728
  }
1837
- const created = await (options.updateProfile ? updateCliProfile : createCliProfile)(profileName, { provider, model });
1729
+ if (reasoningEffort && !modelReasoningEfforts(provider, model).includes(reasoningEffort)) {
1730
+ throw new CliUsageError(`Reasoning effort ${reasoningEffort} is not declared for ${provider}/${model}. Use default or a supported level.`);
1731
+ }
1732
+ const created = await (options.updateProfile ? updateCliProfile : createCliProfile)(profileName, { provider, model, ...reasoningEffort ? { reasoningEffort } : {} });
1838
1733
  const availability = providerAvailability().find((candidate) => candidate.id === provider);
1839
1734
  const doctorCommand = `zhx doctor --profile ${profileName}`;
1840
1735
  const runCommand = `zhx --profile ${profileName} "inspect this repository"`;
@@ -1846,7 +1741,8 @@ var initializeCli = async (options, context = {}) => {
1846
1741
  path: created.path,
1847
1742
  schemaVersion: created.profile.schemaVersion,
1848
1743
  provider: created.profile.provider,
1849
- model: created.profile.model
1744
+ model: created.profile.model,
1745
+ reasoningEffort: created.profile.reasoningEffort ?? "default"
1850
1746
  },
1851
1747
  credential: {
1852
1748
  configured: availability?.configured ?? false,
@@ -1860,18 +1756,18 @@ var initializeCli = async (options, context = {}) => {
1860
1756
  return true;
1861
1757
  }
1862
1758
  if (context.onboarding) {
1863
- process.stdout.write(`Provider and model saved. Next: configure credentials for ${provider}.
1759
+ process.stdout.write(`Provider and model saved. Reasoning: ${created.profile.reasoningEffort ?? "default"}. Next: configure credentials for ${provider}.
1864
1760
  `);
1865
1761
  return true;
1866
1762
  }
1867
1763
  process.stdout.write([
1868
1764
  `${options.updateProfile ? "Updated" : "Created"} personal profile ${profileName} at ${created.path}.`,
1869
- `Provider: ${created.profile.provider} · Model: ${created.profile.model}`,
1765
+ `Provider: ${created.profile.provider} · Model: ${created.profile.model} · Reasoning: ${created.profile.reasoningEffort ?? "default"}`,
1870
1766
  availability?.configured ? "Provider credential detected; no secret value was stored or printed." : `Open zhx to configure a managed key, or set ${availability?.credentialNames.join(" or ") || "the provider credential"} for automation.`,
1871
1767
  "Next:",
1872
1768
  ` ${doctorCommand}`,
1873
1769
  ` ${runCommand}`,
1874
- "Profiles contain provider and model only. A later bare interactive console uses the default profile automatically. Change it with zhx init --update."
1770
+ "Profiles contain provider, model and optional reasoning effort. A later bare interactive console uses the default profile automatically. Change it with zhx init --update."
1875
1771
  ].join(`
1876
1772
  `) + `
1877
1773
  `);
@@ -1925,6 +1821,7 @@ Options (automation and advanced configuration):
1925
1821
  --update Update an existing profile (init only)
1926
1822
  --provider <${PROVIDERS.join("|")}> Provider (default: openai)
1927
1823
  --model <id> Override the default model
1824
+ --reasoning <level> default, none, minimal, low, medium, high, xhigh, max (model-dependent)
1928
1825
  --compaction-model <id> Opt into model-assisted compaction with this model
1929
1826
  --compaction-provider <id> Compaction provider (defaults to the primary provider)
1930
1827
  --route <role=provider[:model]> Route a subagent role; repeatable
@@ -2330,7 +2227,7 @@ This connection will replace Qwen environment settings for this CLI session.
2330
2227
  }
2331
2228
  }
2332
2229
  async function credentialModel(config, env, modelFactory) {
2333
- const { createProviderModel: createProviderModel2 } = await import("./config-w1npe8mp.js");
2230
+ const { createProviderModel: createProviderModel2 } = await import("./config-1k9t4jhh.js");
2334
2231
  const { wrapLanguageModel } = await import("@zhivex-ai/core");
2335
2232
  const secrets = providerDescriptor(config.provider).credentialNames.map((name) => env[name]).filter((value) => !!value);
2336
2233
  const safeError = (error) => {
@@ -2943,6 +2840,7 @@ var consoleBudgetOptions = (options, env = process.env) => ({
2943
2840
  var restoreConsoleOptions = (current, saved) => ({
2944
2841
  ...current,
2945
2842
  ...saved,
2843
+ reasoningEffort: saved.reasoningEffort ?? "default",
2946
2844
  unlimitedTokens: saved.unlimitedTokens ?? false
2947
2845
  });
2948
2846
  var formatConsoleBudget = (config, usage) => {
@@ -3105,8 +3003,8 @@ var manageSessions = async (options) => {
3105
3003
  let document;
3106
3004
  switch (options.sessionsCommand) {
3107
3005
  case "list": {
3108
- const summaries2 = await store.list({ ...options.limit ? { limit: options.limit } : {}, ...options.sessionSearch ? { search: options.sessionSearch } : {} });
3109
- const indexed = (await Promise.all(summaries2.map((summary) => store.get(summary.sessionId)))).filter((value) => Boolean(value));
3006
+ const summaries = await store.list({ ...options.limit ? { limit: options.limit } : {}, ...options.sessionSearch ? { search: options.sessionSearch } : {} });
3007
+ const indexed = (await Promise.all(summaries.map((summary) => store.get(summary.sessionId)))).filter((value) => Boolean(value));
3110
3008
  const sessions = [];
3111
3009
  for (const selected of indexed)
3112
3010
  sessions.push(await refreshDurableStatus(selected));
@@ -3448,6 +3346,7 @@ var chat = async (options) => {
3448
3346
  const sessionStore = await openSessionStoreForConfig(baseConfig);
3449
3347
  const readline = new ConsoleInput(process.stdin, process.stdout);
3450
3348
  const attachments = new ConsoleAttachments;
3349
+ const activityHistory = new ActivityHistory;
3451
3350
  const credentials = { store: new CliCredentials, input: readline };
3452
3351
  let credentialsRevision = credentials.store.revision;
3453
3352
  let activeController;
@@ -3464,9 +3363,11 @@ Stopping the active operation; waiting for durable state and cleanup.
3464
3363
  const abortable = async (operation) => {
3465
3364
  const controller = new AbortController;
3466
3365
  activeController = controller;
3366
+ readline.startBackground();
3467
3367
  try {
3468
3368
  return await operation(controller.signal);
3469
3369
  } finally {
3370
+ readline.stopBackground();
3470
3371
  activeController = undefined;
3471
3372
  }
3472
3373
  };
@@ -3523,6 +3424,37 @@ Stopping the active operation; waiting for durable state and cleanup.
3523
3424
  sessionStore.close();
3524
3425
  throw error;
3525
3426
  }
3427
+ const consoleApprovals = async (approvals, context) => {
3428
+ readline.stopBackground();
3429
+ try {
3430
+ return await terminalApprovalResolver(options.approvalMode ?? options.yes, (question) => readline.question(question), { select: (title, items) => readline.select(title, items), workspace: harness.config.workspace, sessionGrants })(approvals, context);
3431
+ } finally {
3432
+ if (activeController && !activeController.signal.aborted)
3433
+ readline.startBackground();
3434
+ }
3435
+ };
3436
+ const createTracker = () => {
3437
+ let offset = harness.workspace.mutationAudit().length;
3438
+ const tracker = {
3439
+ streamedText: false,
3440
+ activityHistory,
3441
+ inputStatus: () => readline.backgroundStatus
3442
+ };
3443
+ return { tracker, onEvent: async (event) => {
3444
+ await streamSink({ json: false, jsonl: false }, tracker, !verbose)(event);
3445
+ if (event.type === "tool-result") {
3446
+ const audit = harness.workspace.mutationAudit();
3447
+ const receipt = formatAppliedFiles(audit.slice(offset));
3448
+ offset = audit.length;
3449
+ if (receipt) {
3450
+ flushToolActivity(tracker);
3451
+ tracker.markdown?.flush();
3452
+ process.stderr.write(receipt);
3453
+ activityHistory.add(receipt.trim());
3454
+ }
3455
+ }
3456
+ } };
3457
+ };
3526
3458
  const refreshSession = async () => {
3527
3459
  const refreshed = await sessionStore.get(session.sessionId);
3528
3460
  if (!refreshed)
@@ -3568,6 +3500,7 @@ Stopping the active operation; waiting for durable state and cleanup.
3568
3500
  session = selected;
3569
3501
  attachments.clear();
3570
3502
  readline.clearHistory();
3503
+ activityHistory.clear();
3571
3504
  messages = state?.messages ?? [];
3572
3505
  retainedTasks = taskSources(state?.metadata);
3573
3506
  };
@@ -3579,7 +3512,7 @@ Stopping the active operation; waiting for durable state and cleanup.
3579
3512
  `);
3580
3513
  return;
3581
3514
  }
3582
- const tracker = { streamedText: false };
3515
+ const { tracker, onEvent } = createTracker();
3583
3516
  session = await sessionStore.updateRun(session.sessionId, state.runId, { status: "running" });
3584
3517
  let result;
3585
3518
  try {
@@ -3589,8 +3522,8 @@ Stopping the active operation; waiting for durable state and cleanup.
3589
3522
  abortSignal,
3590
3523
  approvals: approvalResponses(state.pendingApprovals, approve, approve ? "Approved inline in chat." : "Denied inline in chat.")
3591
3524
  }, {
3592
- onEvent: streamSink({ json: false, jsonl: false }, tracker, !verbose),
3593
- resolveApprovals: terminalApprovalResolver(options.approvalMode ?? options.yes, (question) => readline.question(question), { select: (title, items) => readline.select(title, items), workspace: harness.config.workspace, sessionGrants })
3525
+ onEvent,
3526
+ resolveApprovals: consoleApprovals
3594
3527
  }));
3595
3528
  } catch {
3596
3529
  flushToolActivity(tracker);
@@ -3640,6 +3573,8 @@ Last failure: ${terminalRunFailure(failedState.error)} (${failedState.currentSte
3640
3573
  }, { color: terminalSupportsColor(Boolean(process.stderr.isTTY)), columns: process.stdout.columns ?? 80 }) + `
3641
3574
  `);
3642
3575
  process.stderr.write(`Ready · credential: ${credentials.store.source(harness.config.provider)} · account access is not checked until your first task.
3576
+ `);
3577
+ process.stderr.write(`While working: type a draft, Enter queues the next task, Up recalls the last queued task. Ctrl+C stops and clears the queue.
3643
3578
  `);
3644
3579
  process.stderr.write(options.approvalMode === "restricted" ? `Restricted mode: additional approvals are denied.
3645
3580
  ` : options.yes ? `Automatic approvals are enabled within workspace and execution policies.
@@ -3663,16 +3598,17 @@ ${summarizeApproval(approval)}
3663
3598
  await showSessionState();
3664
3599
  for (;; ) {
3665
3600
  try {
3666
- process.stdout.write(formatComposer({
3601
+ const pendingTurn = await hasActiveTurn();
3602
+ readline.setQueueEnabled(!pendingTurn);
3603
+ const submitted = await readline.compose({
3667
3604
  model: `${harness.config.provider}/${harness.config.model}`,
3605
+ reasoning: harness.config.reasoningEffort ?? "default",
3668
3606
  ...session.title ? { title: session.title } : {},
3669
3607
  status: session.runs.at(-1)?.status === "waiting_approval" ? "approval pending · /pending" : "ready",
3670
3608
  attachments: attachments.list().length,
3671
3609
  automaticApprovals: options.yes === true,
3672
3610
  ...options.approvalMode ? { approvalMode: options.approvalMode } : {}
3673
- }, process.stdout.columns));
3674
- const submitted = await readline.question(`
3675
- > `, true);
3611
+ });
3676
3612
  const literalInput = readline.lastSubmissionWasPaste || submitted.includes(`
3677
3613
  `);
3678
3614
  let prompt = literalInput ? submitted : submitted.trim();
@@ -3683,7 +3619,7 @@ ${summarizeApproval(approval)}
3683
3619
  if (["/menu", "/provider", "/providers", "/model", "/models"].includes(command)) {
3684
3620
  const selection = await navigateConsole(readline, {
3685
3621
  entry: command === "/menu" ? "menu" : command.startsWith("/provider") ? "provider" : "model",
3686
- current: { provider: harness.config.provider, model: harness.config.model },
3622
+ current: { provider: harness.config.provider, model: harness.config.model, reasoningEffort: harness.config.reasoningEffort ?? "default" },
3687
3623
  providers: providerAvailability(),
3688
3624
  sessions: async () => (await sessionStore.list({ limit: 200 })).map((item) => ({ value: item.sessionId, label: item.title ?? "Untitled conversation", detail: item.sessionId }))
3689
3625
  });
@@ -3700,9 +3636,9 @@ ${summarizeApproval(approval)}
3700
3636
  continue;
3701
3637
  }
3702
3638
  const portableMessages = compactHarnessMessages(messages);
3703
- await replaceHarness({ ...runtimeOptions, provider: parseProvider(selection.provider), model: selection.model }, routes);
3639
+ await replaceHarness({ ...runtimeOptions, provider: parseProvider(selection.provider), model: selection.model, reasoningEffort: selection.reasoningEffort ?? "default" }, routes);
3704
3640
  messages = portableMessages;
3705
- process.stderr.write(`Next turn: ${selection.provider}/${selection.model}; context was compacted.
3641
+ process.stderr.write(`Next turn: ${selection.provider}/${selection.model} · reasoning ${selection.reasoningEffort ?? "default"}; context was compacted.
3706
3642
  `);
3707
3643
  continue;
3708
3644
  }
@@ -3841,8 +3777,8 @@ ${summarizeApproval(approval)}
3841
3777
  continue;
3842
3778
  }
3843
3779
  if (command === "/sessions" || command.startsWith("/sessions ")) {
3844
- const summaries2 = await sessionStore.list({ search: command.slice(9).trim(), limit: 200 });
3845
- for (const summary of summaries2) {
3780
+ const summaries = await sessionStore.list({ search: command.slice(9).trim(), limit: 200 });
3781
+ for (const summary of summaries) {
3846
3782
  const selected = await sessionStore.get(summary.sessionId);
3847
3783
  if (!selected)
3848
3784
  continue;
@@ -3850,7 +3786,7 @@ ${summarizeApproval(approval)}
3850
3786
  process.stdout.write(sanitizeTerminalText(`${summary.sessionId} · ${summary.title ?? "(untitled)"} · ${summary.runCount} runs · ${durable?.status ?? "empty"}`) + `
3851
3787
  `);
3852
3788
  }
3853
- if (!summaries2.length)
3789
+ if (!summaries.length)
3854
3790
  process.stdout.write(`No sessions match in this workspace and scope.
3855
3791
  `);
3856
3792
  continue;
@@ -3891,12 +3827,30 @@ ${summarizeApproval(approval)}
3891
3827
  retainedTasks = [];
3892
3828
  attachments.clear();
3893
3829
  readline.clearHistory();
3830
+ activityHistory.clear();
3894
3831
  process.stderr.write(`Context cleared.
3895
3832
  `);
3896
3833
  continue;
3897
3834
  }
3898
3835
  if (command === "/status") {
3899
3836
  process.stderr.write(`${await statusLine()}
3837
+ `);
3838
+ continue;
3839
+ }
3840
+ if (command === "/activity" || command === "/activity clear") {
3841
+ if (command.endsWith(" clear"))
3842
+ activityHistory.clear();
3843
+ process.stdout.write(activityHistory.render());
3844
+ continue;
3845
+ }
3846
+ if (command === "/queue clear") {
3847
+ readline.clearQueue();
3848
+ process.stdout.write(`Queued tasks and draft cleared.
3849
+ `);
3850
+ continue;
3851
+ }
3852
+ if (command === "/queue") {
3853
+ process.stdout.write(readline.queueSummary() + `Use /queue clear to discard queued tasks.
3900
3854
  `);
3901
3855
  continue;
3902
3856
  }
@@ -3944,7 +3898,7 @@ ${summarizeApproval(approval)}
3944
3898
  const provider = parseProvider(value);
3945
3899
  const model = DEFAULT_PROVIDER_REGISTRY.descriptor(provider).defaultModel;
3946
3900
  const portableMessages = compactHarnessMessages(messages);
3947
- await replaceHarness({ ...runtimeOptions, provider, model }, routes);
3901
+ await replaceHarness({ ...runtimeOptions, provider, model, reasoningEffort: "default" }, routes);
3948
3902
  messages = portableMessages;
3949
3903
  process.stderr.write(`Next turn: ${provider}/${model}; context was compacted for a safe handoff.
3950
3904
  `);
@@ -3959,9 +3913,28 @@ ${summarizeApproval(approval)}
3959
3913
  continue;
3960
3914
  }
3961
3915
  const portableMessages = compactHarnessMessages(messages);
3962
- await replaceHarness({ ...runtimeOptions, model: value }, routes);
3916
+ const reasoningEffort = await chooseReasoning(readline, harness.config.provider, value);
3917
+ if (!reasoningEffort)
3918
+ continue;
3919
+ await replaceHarness({ ...runtimeOptions, model: value, reasoningEffort }, routes);
3963
3920
  messages = portableMessages;
3964
3921
  process.stderr.write(`Next turn: ${harness.config.provider}/${value}; context was compacted.
3922
+ `);
3923
+ continue;
3924
+ }
3925
+ if (command === "/reasoning" || command.startsWith("/reasoning ")) {
3926
+ const active2 = await hasActiveTurn();
3927
+ if (active2) {
3928
+ process.stderr.write(`Cannot change reasoning while run ${active2.runId} is ${active2.status}.
3929
+ `);
3930
+ continue;
3931
+ }
3932
+ const argument = command.slice("/reasoning".length).trim();
3933
+ const effort = argument ? reasoningEffortSchema.parse(argument) : await chooseReasoning(readline, harness.config.provider, harness.config.model, harness.config.reasoningEffort);
3934
+ if (!effort)
3935
+ continue;
3936
+ await replaceHarness({ ...runtimeOptions, reasoningEffort: effort }, routes);
3937
+ process.stderr.write(`Reasoning for next turns: ${effort}.
3965
3938
  `);
3966
3939
  continue;
3967
3940
  }
@@ -4119,7 +4092,7 @@ ${sanitizeTerminalText(prompt)}
4119
4092
  status: "created"
4120
4093
  });
4121
4094
  const turn = session.runs.at(-1);
4122
- const tracker = { streamedText: false };
4095
+ const { tracker, onEvent } = createTracker();
4123
4096
  const progress = consoleProgressGuard();
4124
4097
  let markedRunning = false;
4125
4098
  let result;
@@ -4167,9 +4140,9 @@ ${sanitizeTerminalText(prompt)}
4167
4140
  session = await sessionStore.updateRun(session.sessionId, runId, { status: "running" });
4168
4141
  markedRunning = true;
4169
4142
  }
4170
- await streamSink({ json: false, jsonl: false }, tracker, !verbose)(event);
4143
+ await onEvent(event);
4171
4144
  },
4172
- resolveApprovals: terminalApprovalResolver(options.approvalMode ?? options.yes, (question) => readline.question(question), { select: (title, items) => readline.select(title, items), workspace: harness.config.workspace, sessionGrants })
4145
+ resolveApprovals: consoleApprovals
4173
4146
  }));
4174
4147
  } catch (error) {
4175
4148
  flushToolActivity(tracker);
@@ -4597,6 +4570,175 @@ var manageChanges = async (options) => {
4597
4570
  process.exitCode = CLI_EXIT_CODES.runtimeError;
4598
4571
  };
4599
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
+
4600
4742
  // src/persistence/workspace-checkpoints.ts
4601
4743
  import { createHash as createHash2, randomUUID as randomUUID3 } from "node:crypto";
4602
4744
  import { z as z3 } from "zod";
@@ -4628,6 +4770,7 @@ import { chmod, link, lstat as lstat3, open as open2, realpath, unlink as unlink
4628
4770
  import path5 from "node:path";
4629
4771
  import { z as z4 } from "zod";
4630
4772
  import { normalizeAgentRunState } from "@zhivex-ai/agents";
4773
+ import { createSqliteAgentRunStore } from "@zhivex-ai/agents/ops";
4631
4774
  var HARNESS_STATE_BACKUP_SCHEMA_VERSION = 1;
4632
4775
  var HARNESS_STATE_BACKUP_MAX_BYTES = 64 * 1024 * 1024;
4633
4776
  var terminalRunStatuses = new Set(["completed", "failed", "cancelled", "timed_out"]);
@@ -4762,7 +4905,12 @@ var canonicalJson = (value) => {
4762
4905
  return `[${value.map(canonicalJson).join(",")}]`;
4763
4906
  return `{${Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`).join(",")}}`;
4764
4907
  };
4765
- 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
+ };
4766
4914
  var stableKey = (kind, value) => createHash3("sha256").update(kind).update("\x00").update(value).digest("hex");
4767
4915
  var scopeValue = (config) => `${config.scope.tenantId}\x00${config.scope.userId ?? ""}\x00${config.scope.namespace ?? ""}`;
4768
4916
  var scopePrefix = (config) => `${encodeURIComponent(config.scope.namespace ?? "default")}:${encodeURIComponent(config.scope.tenantId)}:${encodeURIComponent(config.scope.userId ?? "*")}:`;
@@ -4794,8 +4942,8 @@ var validateBudgetLedger = (config, run, identities) => {
4794
4942
  if (!/^sha256:[a-f0-9]{64}$/.test(id) || !value || typeof value !== "object" || Array.isArray(value) || value.status !== "confirmed") {
4795
4943
  throw new HarnessStateConflictError("Budget ledger retains unresolved reservations or unknown consumption.");
4796
4944
  }
4797
- const tokens = value.tokens;
4798
- 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) {
4799
4947
  throw new HarnessStateConflictError("Invalid budget ledger token receipt.");
4800
4948
  }
4801
4949
  }
@@ -4897,8 +5045,13 @@ var readPayload = async (config, recordedWorkspace, accessLease) => {
4897
5045
  if (recordedWorkspace && database.query("SELECT version FROM zhivex_cli_session_schema WHERE singleton=1").get()?.version !== HARNESS_SESSION_SCHEMA_VERSION)
4898
5046
  throw new HarnessStateConflictError("Archived session schema is incompatible.");
4899
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;
4900
5049
  const runRows = allRunRows.flatMap((row) => {
4901
- 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}`);
4902
5055
  return stateMatchesScope(config, state) ? [{ row, state }] : [];
4903
5056
  });
4904
5057
  const runs = runRows.map(({ row, state }) => {
@@ -5058,7 +5211,19 @@ var writePrivateBackup = async (target, contents) => {
5058
5211
  try {
5059
5212
  handle = await open2(staged, fsConstants3.O_WRONLY | fsConstants3.O_CREAT | fsConstants3.O_EXCL | fsConstants3.O_NOFOLLOW, 384);
5060
5213
  stagedExists = true;
5061
- 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");
5062
5227
  await handle.sync();
5063
5228
  await chmod(staged, 384);
5064
5229
  await handle.close();
@@ -5085,12 +5250,13 @@ var writePrivateBackup = async (target, contents) => {
5085
5250
  };
5086
5251
  var exportHarnessStateBackup = async (config, target) => {
5087
5252
  const bundle = await createHarnessStateBackup(config);
5088
- const serialized = `${JSON.stringify(bundle, null, 2)}
5089
- `;
5090
- if (Buffer.byteLength(serialized) > HARNESS_STATE_BACKUP_MAX_BYTES) {
5091
- 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;
5092
5258
  }
5093
- await writePrivateBackup(target, serialized);
5259
+ await writePrivateBackup(target, bytes > HARNESS_STATE_BACKUP_MAX_BYTES ? segmentedChunks(bundle) : canonicalChunks(bundle));
5094
5260
  return {
5095
5261
  schemaVersion: HARNESS_STATE_BACKUP_SCHEMA_VERSION,
5096
5262
  kind: "state-export",
@@ -5100,19 +5266,14 @@ var exportHarnessStateBackup = async (config, target) => {
5100
5266
  };
5101
5267
  };
5102
5268
  var readHarnessStateBackup = async (source) => {
5103
- const { contents, stat: stat2 } = await readRegularFileNoFollow(path5.resolve(source), {
5104
- label: "Harness state backup",
5105
- maxBytes: HARNESS_STATE_BACKUP_MAX_BYTES,
5106
- requireSingleLink: true
5107
- });
5108
- if ((stat2.mode & 63) !== 0) {
5109
- throw new HarnessWorkspaceError("Harness state backup permissions must not grant group or other access.");
5110
- }
5111
5269
  let input;
5112
5270
  try {
5113
- input = JSON.parse(contents.toString("utf8"));
5271
+ input = await readBackupTransport(path5.resolve(source), HARNESS_STATE_BACKUP_MAX_BYTES);
5114
5272
  } catch (error) {
5115
- 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;
5116
5277
  }
5117
5278
  return validateStateBackupBundle(input);
5118
5279
  };
@@ -5513,7 +5674,7 @@ var main = async (argv = process.argv.slice(2)) => {
5513
5674
  parsedOptions = { ...parsedOptions, prompt: await readStdinTask() };
5514
5675
  }
5515
5676
  if (parsedOptions.serviceFile) {
5516
- const { runServiceCli } = await import("./service-client-cli-1c9j6xah.js");
5677
+ const { runServiceCli } = await import("./service-client-cli-yx56n0hc.js");
5517
5678
  await runServiceCli(parsedOptions, annotateCliStreamError);
5518
5679
  return;
5519
5680
  }