@zhivex-ai/harness 1.2.0 → 1.3.0-rc.7

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 (164) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +9 -10
  3. package/contracts/engine-api.json +2921 -0
  4. package/contracts/public-api.json +267 -1
  5. package/contracts/stable-api-signatures.json +43 -43
  6. package/dist/acp-cli.js +137 -15002
  7. package/dist/approvals/approval-history.d.ts +22 -0
  8. package/dist/approvals/explicit-review.d.ts +24 -0
  9. package/dist/approvals/host-policy-identity.d.ts +3 -0
  10. package/dist/cli/arguments.d.ts +6 -2
  11. package/dist/cli/checkpoints.d.ts +230 -0
  12. package/dist/cli/cli-options.d.ts +2 -41
  13. package/dist/cli/cli-profiles.d.ts +4 -2
  14. package/dist/cli/cli-stream.d.ts +2 -167
  15. package/dist/cli/governance-report.d.ts +5 -0
  16. package/dist/cli/policy-decisions.d.ts +9 -0
  17. package/dist/cli/policy.d.ts +5 -0
  18. package/dist/cli/presentation.d.ts +3 -1
  19. package/dist/cli/run-document.d.ts +2 -91
  20. package/dist/cli/terminal/terminal-continuation.d.ts +2 -3
  21. package/dist/cli/terminal/terminal-ui.d.ts +4 -0
  22. package/dist/{cli-entry-c2f58zj2.js → cli-entry-ae7x44d4.js} +14969 -10731
  23. package/dist/{cli-entry-427869g9.js → cli-entry-d5788aay.js} +388 -47
  24. package/dist/cli-entry-ev76ezcd.js +656 -0
  25. package/dist/{cli-3jmy4h5h.js → cli-t7hrhayq.js} +918 -2223
  26. package/dist/cli.js +3 -5
  27. package/dist/client/acp-mcp-admission.d.ts +32 -0
  28. package/dist/client/acp-mcp-host.d.ts +19 -0
  29. package/dist/client/acp-mcp-session.d.ts +14 -0
  30. package/dist/client/acp-stdio.d.ts +2 -0
  31. package/dist/client/acp.d.ts +4 -1
  32. package/dist/client/adapter.d.ts +13 -1
  33. package/dist/client/continuation.d.ts +3 -0
  34. package/dist/client/json-contracts.d.ts +107 -4
  35. package/dist/client/local-service.d.ts +2 -0
  36. package/dist/client/protocol.d.ts +127 -1
  37. package/dist/client/run-document.d.ts +91 -0
  38. package/dist/client/service-events.d.ts +4 -0
  39. package/dist/client/stream.d.ts +167 -0
  40. package/dist/compat/cli-options.d.ts +53 -0
  41. package/dist/compat/code-support.d.ts +22 -0
  42. package/dist/compat/code-support.js +74 -0
  43. package/dist/{config-1k9t4jhh.js → config-c002f06f.js} +1 -1
  44. package/dist/config-j3jga5wh.js +61 -0
  45. package/dist/context/adaptive-compaction.d.ts +1 -0
  46. package/dist/context/compaction.d.ts +1 -1
  47. package/dist/context/request-measurements.d.ts +17 -0
  48. package/dist/context/request-projection.d.ts +10 -0
  49. package/dist/context/task-memory.d.ts +1 -0
  50. package/dist/engine/acp.d.ts +6 -0
  51. package/dist/engine/acp.js +205 -0
  52. package/dist/engine/client.d.ts +6 -0
  53. package/dist/engine/client.js +28 -0
  54. package/dist/engine/desktop-providers-v1.d.ts +4 -0
  55. package/dist/engine/desktop-providers-v1.js +18 -0
  56. package/dist/engine/desktop-state-v1.d.ts +10 -0
  57. package/dist/engine/desktop-state-v1.js +168 -0
  58. package/dist/engine/index.d.ts +73 -0
  59. package/dist/engine/index.js +977 -0
  60. package/dist/engine/mcp-stdio-v1.d.ts +11 -0
  61. package/dist/engine/mcp-stdio-v1.js +108 -0
  62. package/dist/engine/models.d.ts +3 -0
  63. package/dist/engine/models.js +894 -0
  64. package/dist/engine/protocol.d.ts +7 -0
  65. package/dist/engine/protocol.js +182 -0
  66. package/dist/engine/service.d.ts +5 -0
  67. package/dist/engine/service.js +28 -0
  68. package/dist/execution/acceptance-snapshot.d.ts +2 -0
  69. package/dist/execution/execution-environment.d.ts +10 -0
  70. package/dist/execution/mcp-oci-channel.d.ts +11 -0
  71. package/dist/execution/mcp-oci-server.d.ts +38 -0
  72. package/dist/execution/mcp-oci-workspace.d.ts +23 -0
  73. package/dist/execution/mcp-resource-recovery.d.ts +19 -0
  74. package/dist/execution/mcp-workspace-snapshot.d.ts +20 -0
  75. package/dist/hostile-repository-demo.js +3368 -1304
  76. package/dist/index-1bx83sw4.js +4 -0
  77. package/dist/index-2gz7jv7g.js +549 -0
  78. package/dist/{cli-entry-b1mfvmxe.js → index-2jrdcj6b.js} +72 -115
  79. package/dist/index-3ah1f369.js +356 -0
  80. package/dist/index-42gp2hy8.js +1555 -0
  81. package/dist/index-4jw3tvy8.js +301 -0
  82. package/dist/index-8705zxpr.js +145 -0
  83. package/dist/index-8vm2m34s.js +2080 -0
  84. package/dist/index-8zzhggxv.js +477 -0
  85. package/dist/index-a97bezbz.js +1073 -0
  86. package/dist/index-gq9xagx2.js +1343 -0
  87. package/dist/index-h0mqx2ed.js +1373 -0
  88. package/dist/index-jgrx804e.js +5589 -0
  89. package/dist/index-qfpz5y2v.js +174 -0
  90. package/dist/index-t60rva31.js +1004 -0
  91. package/dist/index-wg1sd3at.js +80 -0
  92. package/dist/index-xqxeypt5.js +888 -0
  93. package/dist/index-yb239ghk.js +961 -0
  94. package/dist/index-z0qxmkm2.js +3389 -0
  95. package/dist/index-z4e8d0ph.js +1768 -0
  96. package/dist/index.js +251 -18649
  97. package/dist/integrations/mcp-host-session.d.ts +26 -0
  98. package/dist/integrations/mcp-stdio-admission.d.ts +54 -0
  99. package/dist/integrations/mcp-stdio-client.d.ts +44 -0
  100. package/dist/integrations/mcp-stdio-framing.d.ts +18 -0
  101. package/dist/integrations/mcp-stdio-launch.d.ts +20 -0
  102. package/dist/integrations/mcp-stdio-session.d.ts +7 -0
  103. package/dist/integrations/mcp-stdio-tools.d.ts +12 -0
  104. package/dist/internal/desktop/catalog.d.ts +2 -0
  105. package/dist/internal/desktop/persistence.d.ts +2 -3
  106. package/dist/internal/desktop/providers.d.ts +1 -0
  107. package/dist/models/catalog.d.ts +2 -0
  108. package/dist/persistence/governance-context.d.ts +126 -0
  109. package/dist/persistence/governance-report.d.ts +299 -0
  110. package/dist/persistence/host-database-backup.d.ts +28 -0
  111. package/dist/persistence/mcp-execution-journal.d.ts +19 -0
  112. package/dist/persistence/mcp-reconciliation.d.ts +33 -0
  113. package/dist/persistence/mcp-resource-journal.d.ts +54 -0
  114. package/dist/persistence/workspace-checkpoints.d.ts +138 -0
  115. package/dist/providers/anthropic-continuation.d.ts +5 -0
  116. package/dist/providers/providers.d.ts +1 -1
  117. package/dist/providers/vertex-auth.d.ts +27 -0
  118. package/dist/runtime/delegation-contracts.d.ts +9 -0
  119. package/dist/runtime/delegation-correction.d.ts +9 -0
  120. package/dist/runtime/delegation-result.d.ts +73 -0
  121. package/dist/runtime/error-diagnostics.d.ts +86 -0
  122. package/dist/runtime/harness.d.ts +11 -1
  123. package/dist/runtime/policy-decisions.d.ts +66 -0
  124. package/dist/runtime/policy-inspection.d.ts +52 -0
  125. package/dist/runtime/provider-diagnostics.d.ts +1 -0
  126. package/dist/runtime/runtime-instructions.d.ts +8 -0
  127. package/dist/runtime/task-acceptance-checks.d.ts +103 -0
  128. package/dist/runtime/task-acceptance-delivery.d.ts +107 -0
  129. package/dist/runtime/task-acceptance-host.d.ts +20 -0
  130. package/dist/runtime/task-acceptance-record.d.ts +107 -0
  131. package/dist/runtime/task-acceptance-validation.d.ts +25 -0
  132. package/dist/runtime/task-acceptance.d.ts +47 -0
  133. package/dist/runtime/time-to-safe-fix.d.ts +83 -0
  134. package/dist/runtime/tool-policy-file.d.ts +23 -0
  135. package/dist/runtime/tool-policy-paths.d.ts +5 -0
  136. package/dist/runtime/tool-policy.d.ts +6 -0
  137. package/dist/service-cli.js +19 -14504
  138. package/dist/{service-client-cli-yx56n0hc.js → service-client-cli-kns4mf57.js} +307 -248
  139. package/dist/verify-historical-migrations.js +3314 -1251
  140. package/dist/workspace/path-policy.d.ts +4 -0
  141. package/dist/zhx.js +3 -5
  142. package/docs/ACP.md +14 -2
  143. package/docs/ANTHROPIC.md +33 -0
  144. package/docs/CLI.md +132 -0
  145. package/docs/CLIENT_PROTOCOL.md +45 -0
  146. package/docs/CODE_RELEASE.md +93 -0
  147. package/docs/ENGINE_API.md +319 -0
  148. package/docs/EXTENSIBILITY.md +46 -3
  149. package/docs/FIRST_USE.md +6 -6
  150. package/docs/LOCAL_SERVICE.md +13 -0
  151. package/docs/MCP_STDIO.md +20 -0
  152. package/docs/README.md +6 -0
  153. package/docs/STABILITY.md +9 -1
  154. package/docs/SUPPORT_MATRIX.md +1 -1
  155. package/docs/TOOL_POLICY.md +81 -0
  156. package/docs/VERTEX.md +50 -0
  157. package/docs/adr/0002-isolated-mcp-stdio.md +135 -0
  158. package/docs/fixtures/mcp-stdio-contract-v1.json +189 -0
  159. package/docs/support-matrix.json +2 -0
  160. package/evaluations/representative-assembly-matrix.json +64 -1
  161. package/evaluations/task-acceptance-routes.json +7 -0
  162. package/examples/README.md +1 -1
  163. package/package.json +74 -12
  164. package/dist/cli-entry-64pdp3k5.js +0 -17
@@ -1,108 +1,150 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- CLI_OPTION_DEFINITIONS,
4
- formatCliHelp,
5
- resolveHelpTopic,
6
- shortCliHelp,
7
- validateCliCommandOptions
8
- } from "./cli-entry-b1mfvmxe.js";
9
- import {
3
+ APPROVAL_HISTORY_KEY,
10
4
  ActivityHistory,
5
+ CLI_CHANGES_COMMANDS,
6
+ CLI_COMMANDS,
11
7
  CLI_JSON_SCHEMA_VERSION,
8
+ CLI_RUNS_COMMANDS,
9
+ CLI_SESSIONS_COMMANDS,
10
+ CLI_STATE_COMMANDS,
11
+ CliCredentials,
12
12
  CliOciRuntimeAdapter,
13
+ CliUsageError,
13
14
  ConsoleInput,
15
+ CredentialSetupError,
14
16
  DEFAULT_HARNESS_CONTEXT_MANIFEST,
15
17
  FileChangedWhileReadingError,
16
18
  FileSizeLimitError,
17
19
  HARNESS_OPERATIONS_SCHEMA_VERSION,
20
+ HARNESS_RESUME_METADATA_KEY,
21
+ HARNESS_RESUME_METADATA_SCHEMA_VERSION,
18
22
  HARNESS_SESSION_SCHEMA_VERSION,
19
23
  HARNESS_SQLITE_FILE,
20
24
  MAX_HARNESS_CONTEXT_FILE_BYTES,
21
25
  MAX_HARNESS_CONTEXT_TOTAL_BYTES,
22
26
  SqliteDatabase,
27
+ TASK_ACCEPTANCE_EVIDENCE_KEY,
23
28
  TASK_SOURCE_KEY,
24
- ToolActivity,
25
29
  USAGE_LEDGER_KEY,
26
30
  UnsafeFileTypeError,
27
31
  Workspace,
32
+ annotateCliStreamError,
28
33
  appendUserMessage,
34
+ applyCliProfile,
35
+ approvalDecisionViews,
36
+ approvalResponses,
37
+ assertRoutePricingIsSafe,
29
38
  cancelHarnessRun,
30
39
  canonicalizeChangeEnvelope,
31
40
  changeEnvelopePreconditionsSchema,
41
+ changeEnvelopeSchema,
32
42
  chooseReasoning,
33
43
  cleanupHarnessExecutionArtifacts,
34
44
  cleanupHarnessRuns,
45
+ cliStreamErrorSequence,
35
46
  compactHarnessMessages,
36
- compactToolResult,
37
47
  createChangeEnvelope,
48
+ createCliProfile,
49
+ createConfiguredHarness,
38
50
  createHarness,
51
+ createHarnessResumeMetadata,
52
+ createHarnessRouteModels,
53
+ credentialModel,
39
54
  digestChangeEnvelopeArtifact,
40
55
  estimateMessages,
41
56
  fileDigestSchema,
57
+ flushToolActivity,
42
58
  formatAppliedFiles,
43
- formatApproval,
44
59
  formatConsoleHelp,
45
60
  formatConsoleWelcome,
46
- formatTerminalEvent,
47
61
  formatUsageLedger,
48
62
  formatVerificationSummary,
63
+ harnessConfigInput,
64
+ harnessPolicyDecisionEventSchema,
49
65
  harnessToolExecution,
66
+ inspectCliPolicy,
50
67
  inspectHarnessRun,
51
68
  inspectRuntimeDiagnostics,
69
+ inspectRuntimeManifest,
52
70
  inspectUsageLedger,
71
+ issueAutomaticApprovalResponses,
72
+ issueObservedApprovalResponses,
53
73
  listHarnessRuns,
74
+ loadCliProfile,
54
75
  loadHarnessMcpConfiguration,
55
76
  loadHarnessProjectContext,
56
77
  loadModelCatalog,
57
78
  navigateConsole,
79
+ observeCliPolicy,
58
80
  openCliSessionStore,
81
+ openHarnessActivityStore,
59
82
  openHarnessPersistence,
83
+ openWorkspaceCheckpointStore,
84
+ orchestrationObserver,
85
+ parseCliArgs,
86
+ parseHarnessModelRoute,
87
+ persistedCliOptions,
88
+ printTerminalResult,
89
+ publishPendingPolicyDecision,
90
+ readHarnessResumeConfig,
91
+ readHarnessResumeRoutes,
60
92
  readRegularFileChunksNoFollow,
61
93
  readRegularFileNoFollow,
94
+ readTaskAcceptanceLedger,
95
+ requiresExplicitHostReview,
96
+ resolveCliDefaults,
97
+ resolveHarnessModelRoutes,
62
98
  resolvePackageManager,
63
- resolveTerminalApprovals,
99
+ resolvedRouting,
100
+ resumeCommand,
64
101
  runHarness,
65
102
  runHarnessReviewGroup,
66
103
  runPortableProcess,
67
104
  runResultDocument,
68
105
  sanitizeTerminalText,
69
- serializeStreamEvent,
70
- serializeStreamResult,
106
+ serializeHarnessModelRoutes,
71
107
  sqliteAdapter,
108
+ streamSink,
109
+ summarizeApproval,
110
+ taskCheckReceiptSchema,
72
111
  taskSources,
112
+ terminalApprovalResolver,
73
113
  terminalContinuationMessages,
114
+ terminalErrorMessage,
74
115
  terminalRunFailure,
75
116
  terminalSupportsColor,
76
- usagePricingSchema,
117
+ updateCliProfile,
77
118
  validateStateDirectory,
78
119
  verifyChangeEnvelope,
79
- workspaceFilePathSchema
80
- } from "./cli-entry-c2f58zj2.js";
120
+ withTemporaryHarnessProfiles,
121
+ workspaceCheckpointSchema,
122
+ workspaceRestoreOperationSchema
123
+ } from "./cli-entry-ae7x44d4.js";
81
124
  import {
82
125
  BUN_ENGINE_RANGE,
83
126
  HARNESS_VERSION,
84
- NODE_ENGINE_RANGE
85
- } from "./cli-entry-64pdp3k5.js";
127
+ NODE_ENGINE_RANGE,
128
+ formatCliHelp,
129
+ shortCliHelp
130
+ } from "./cli-entry-ev76ezcd.js";
86
131
  import {
87
132
  DEFAULT_HARNESS_MAX_STEPS,
88
133
  DEFAULT_PROVIDER_REGISTRY,
89
- HARNESS_REQUIRED_CAPABILITIES,
90
134
  HARNESS_SUBAGENT_PROFILES,
91
135
  HarnessConfigError,
92
136
  HarnessError,
93
137
  HarnessStateConflictError,
94
138
  HarnessWorkspaceError,
95
139
  PROVIDERS,
96
- createProviderModel,
97
140
  harnessErrorDocument,
98
141
  modelReasoningEfforts,
99
- normalizeHarnessError,
100
142
  parseProvider,
101
143
  providerAvailability,
102
144
  providerDescriptor,
103
145
  reasoningEffortSchema,
104
146
  resolveHarnessConfig
105
- } from "./cli-entry-427869g9.js";
147
+ } from "./cli-entry-d5788aay.js";
106
148
  import {
107
149
  __require
108
150
  } from "./cli-entry-1bx83sw4.js";
@@ -111,1577 +153,6 @@ import {
111
153
  import { realpathSync } from "node:fs";
112
154
  import { fileURLToPath } from "node:url";
113
155
 
114
- // src/cli/cli-profiles.ts
115
- import { constants as fsConstants } from "node:fs";
116
- import { lstat, mkdir, open, unlink, rename } from "node:fs/promises";
117
- import { randomUUID } from "node:crypto";
118
- import os from "node:os";
119
- import path from "node:path";
120
- import { z } from "zod";
121
- var CLI_PROFILE_SCHEMA_VERSION = 1;
122
- var CLI_PROFILE_CONFIG_DIRECTORY_ENV = "ZHIVEX_HARNESS_CONFIG_DIR";
123
- var MAX_CLI_PROFILE_BYTES = 16 * 1024;
124
- var profileNamePattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
125
- var cliProfileSchema = z.strictObject({
126
- schemaVersion: z.literal(CLI_PROFILE_SCHEMA_VERSION),
127
- provider: z.enum(PROVIDERS),
128
- reasoningEffort: reasoningEffortSchema.optional(),
129
- model: z.string().min(1).max(512).refine((value) => !/[\u0000-\u001f\u007f]/.test(value), "model must contain printable characters")
130
- });
131
- var absoluteDirectory = (value, source, pathApi = path) => {
132
- if (!pathApi.isAbsolute(value)) {
133
- throw new HarnessConfigError(`${source} must be an absolute path.`);
134
- }
135
- return pathApi.normalize(value);
136
- };
137
- var validateCliProfileName = (name) => {
138
- if (!profileNamePattern.test(name)) {
139
- throw new HarnessConfigError("Profile names must use 1-64 letters, digits, dot, underscore, or hyphen and start with a letter or digit.");
140
- }
141
- return name;
142
- };
143
- var resolveCliProfileConfigDirectory = (context = {}) => {
144
- const env = context.env ?? process.env;
145
- const platform = context.platform ?? process.platform;
146
- const homeDirectory = context.homeDirectory ?? os.homedir();
147
- const pathApi = platform === "win32" ? path.win32 : path;
148
- const override = env[CLI_PROFILE_CONFIG_DIRECTORY_ENV]?.trim();
149
- if (override) {
150
- return absoluteDirectory(override, CLI_PROFILE_CONFIG_DIRECTORY_ENV, pathApi);
151
- }
152
- if (platform === "darwin") {
153
- return pathApi.join(homeDirectory, "Library", "Application Support", "zhivex-harness");
154
- }
155
- if (platform === "win32") {
156
- const appData = env.APPDATA?.trim();
157
- if (!appData) {
158
- throw new HarnessConfigError(`APPDATA is required to locate Zhivex Harness profiles on ${platform}.`);
159
- }
160
- return pathApi.join(absoluteDirectory(appData, "APPDATA", pathApi), "zhivex-harness");
161
- }
162
- const xdgConfigHome = env.XDG_CONFIG_HOME?.trim();
163
- const configHome = xdgConfigHome ? absoluteDirectory(xdgConfigHome, "XDG_CONFIG_HOME", pathApi) : pathApi.join(homeDirectory, ".config");
164
- return pathApi.join(configHome, "zhivex-harness");
165
- };
166
- var resolveCliProfilePath = (name, context = {}) => path.join(resolveCliProfileConfigDirectory(context), "profiles", `${validateCliProfileName(name)}.json`);
167
- var validatePrivateDirectory = async (directory) => {
168
- const directoryStat = await lstat(directory);
169
- if (!directoryStat.isDirectory() || directoryStat.isSymbolicLink()) {
170
- throw new HarnessConfigError(`CLI profile directory must be a real directory: ${directory}`);
171
- }
172
- if ((directoryStat.mode & 18) !== 0) {
173
- throw new HarnessConfigError(`CLI profile directory must not be writable by group or others: ${directory}`);
174
- }
175
- };
176
- var ensurePrivateDirectory = async (directory) => {
177
- try {
178
- await validatePrivateDirectory(directory);
179
- return;
180
- } catch (error) {
181
- if (error.code !== "ENOENT")
182
- throw error;
183
- }
184
- await mkdir(directory, { recursive: true, mode: 448 });
185
- await validatePrivateDirectory(directory);
186
- };
187
- var profileReadError = (profilePath, error) => {
188
- if (error instanceof UnsafeFileTypeError) {
189
- throw new HarnessConfigError(`CLI profile must be a regular non-linked file: ${profilePath}`);
190
- }
191
- if (error instanceof FileSizeLimitError) {
192
- throw new HarnessConfigError(`CLI profile exceeds the ${MAX_CLI_PROFILE_BYTES}-byte limit: ${profilePath}`);
193
- }
194
- if (error instanceof FileChangedWhileReadingError) {
195
- throw new HarnessConfigError(`CLI profile changed while it was being read: ${profilePath}`);
196
- }
197
- const code = error && typeof error === "object" && "code" in error ? error.code : undefined;
198
- if (code === "ENOENT") {
199
- throw new HarnessConfigError(`CLI profile was not found: ${profilePath}. Create it with zhx init --profile <name>.`);
200
- }
201
- throw error;
202
- };
203
- var loadCliProfile = async (name, context = {}) => {
204
- const profilePath = resolveCliProfilePath(name, context);
205
- let file;
206
- try {
207
- await validatePrivateDirectory(resolveCliProfileConfigDirectory(context));
208
- await validatePrivateDirectory(path.dirname(profilePath));
209
- file = await readRegularFileNoFollow(profilePath, {
210
- label: `CLI profile ${name}`,
211
- maxBytes: MAX_CLI_PROFILE_BYTES,
212
- requireSingleLink: true
213
- });
214
- } catch (error) {
215
- return profileReadError(profilePath, error);
216
- }
217
- if ((file.stat.mode & 63) !== 0) {
218
- throw new HarnessConfigError(`CLI profile permissions are too broad; expected owner-only access: ${profilePath}`);
219
- }
220
- let document;
221
- try {
222
- document = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(file.contents));
223
- } catch {
224
- throw new HarnessConfigError(`CLI profile is not valid UTF-8 JSON: ${profilePath}`);
225
- }
226
- const parsed = cliProfileSchema.safeParse(document);
227
- if (!parsed.success) {
228
- throw new HarnessConfigError(`CLI profile is invalid: ${parsed.error.issues[0]?.message ?? "schema mismatch"}`);
229
- }
230
- return parsed.data;
231
- };
232
- var createCliProfile = async (name, input, context = {}) => {
233
- const parsed = cliProfileSchema.safeParse({
234
- schemaVersion: CLI_PROFILE_SCHEMA_VERSION,
235
- provider: input.provider,
236
- model: input.model,
237
- ...input.reasoningEffort ? { reasoningEffort: input.reasoningEffort } : {}
238
- });
239
- if (!parsed.success) {
240
- throw new HarnessConfigError(`CLI profile is invalid: ${parsed.error.issues[0]?.message ?? "schema mismatch"}`);
241
- }
242
- const profile = parsed.data;
243
- const profilePath = resolveCliProfilePath(name, context);
244
- await ensurePrivateDirectory(resolveCliProfileConfigDirectory(context));
245
- await ensurePrivateDirectory(path.dirname(profilePath));
246
- let handle;
247
- try {
248
- handle = await open(profilePath, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_NOFOLLOW, 384);
249
- } catch (error) {
250
- if (error.code === "EEXIST") {
251
- throw new HarnessConfigError(`CLI profile already exists: ${profilePath}. Use zhx init --profile ${name} --update, or choose another --profile name.`);
252
- }
253
- throw error;
254
- }
255
- let completed = false;
256
- try {
257
- await handle.chmod(384);
258
- await handle.writeFile(`${JSON.stringify(profile, null, 2)}
259
- `, "utf8");
260
- await handle.sync();
261
- const fileStat = await handle.stat();
262
- if (!fileStat.isFile() || fileStat.nlink !== 1 || (fileStat.mode & 63) !== 0) {
263
- throw new HarnessConfigError(`CLI profile could not be created safely: ${profilePath}`);
264
- }
265
- completed = true;
266
- } finally {
267
- try {
268
- await handle.close();
269
- } finally {
270
- if (!completed)
271
- await unlink(profilePath).catch(() => {
272
- return;
273
- });
274
- }
275
- }
276
- return { path: profilePath, profile };
277
- };
278
- var updateCliProfile = async (name, input, context = {}) => {
279
- await loadCliProfile(name, context);
280
- const profilePath = resolveCliProfilePath(name, context);
281
- const temporary = await createCliProfile(`update-${randomUUID()}`, input, context);
282
- try {
283
- await loadCliProfile(name, context);
284
- await rename(temporary.path, profilePath);
285
- return { path: profilePath, profile: temporary.profile };
286
- } finally {
287
- await unlink(temporary.path).catch((error) => {
288
- if (error.code !== "ENOENT")
289
- throw error;
290
- });
291
- }
292
- };
293
- var applyCliProfile = async (options, context = {}) => {
294
- if (!options.profile)
295
- return options;
296
- const profile = await loadCliProfile(options.profile, context);
297
- return {
298
- provider: profile.provider,
299
- model: profile.model,
300
- ...profile.reasoningEffort && (!options.provider || options.provider === profile.provider) && (!options.model || options.model === profile.model) ? { reasoningEffort: profile.reasoningEffort } : {},
301
- ...options
302
- };
303
- };
304
- var resolveCliDefaults = async (options, context = {}) => {
305
- const env = context.env ?? process.env;
306
- if (options.profile || options.provider || options.model || env.ZHIVEX_HARNESS_PROVIDER || env.ZHIVEX_HARNESS_MODEL)
307
- return options;
308
- try {
309
- await lstat(resolveCliProfilePath("default", context));
310
- } catch (error) {
311
- if (error.code !== "ENOENT")
312
- throw error;
313
- const configured = providerAvailability(env).filter((provider) => provider.configured);
314
- return configured.length === 1 ? { ...options, provider: configured[0].id } : options;
315
- }
316
- return { ...options, profile: "default" };
317
- };
318
-
319
- // src/cli/arguments.ts
320
- import { z as z2 } from "zod";
321
-
322
- // src/providers/routing.ts
323
- var routeError = (value) => new Error(`Invalid route: ${value}. Use <profile>=<provider>[:<model>].`);
324
- var parseHarnessModelRoute = (value, registry = DEFAULT_PROVIDER_REGISTRY) => {
325
- const separator = value.indexOf("=");
326
- if (separator <= 0 || separator === value.length - 1) {
327
- throw routeError(value);
328
- }
329
- const profile = value.slice(0, separator).trim().toLowerCase();
330
- if (!HARNESS_SUBAGENT_PROFILES.includes(profile)) {
331
- throw routeError(value);
332
- }
333
- const target = value.slice(separator + 1).trim();
334
- const modelSeparator = target.indexOf(":");
335
- const providerInput = (modelSeparator === -1 ? target : target.slice(0, modelSeparator)).trim();
336
- const provider = registry.parse(providerInput);
337
- const configuredModel = modelSeparator === -1 ? "" : target.slice(modelSeparator + 1).trim();
338
- if (modelSeparator !== -1 && !configuredModel) {
339
- throw routeError(value);
340
- }
341
- const model = configuredModel || registry.descriptor(provider).defaultModel;
342
- if (/[\u0000-\u001f\u007f]/.test(model) || model.length > 256) {
343
- throw routeError(value);
344
- }
345
- return Object.freeze({
346
- profile,
347
- provider,
348
- model
349
- });
350
- };
351
- var resolveHarnessModelRoutes = (values = [], registry = DEFAULT_PROVIDER_REGISTRY) => {
352
- const routes = new Map;
353
- for (const value of values) {
354
- const route = parseHarnessModelRoute(value, registry);
355
- if (routes.has(route.profile)) {
356
- throw new Error(`Duplicate model route for ${route.profile}.`);
357
- }
358
- routes.set(route.profile, route);
359
- }
360
- return routes;
361
- };
362
- var createHarnessRouteModels = (routes, env = process.env, registry = DEFAULT_PROVIDER_REGISTRY) => Object.fromEntries([...routes].map(([profile, route]) => [
363
- profile,
364
- registry.createModel(route, env)
365
- ]));
366
- var serializeHarnessModelRoutes = (routes) => Object.fromEntries([...routes].map(([profile, route]) => [
367
- profile,
368
- { provider: route.provider, model: route.model }
369
- ]));
370
-
371
- // src/cli/arguments.ts
372
- var CLI_COMMANDS = ["init", "run", "review", "chat", "providers", "doctor", "resume", "runs", "sessions", "changes", "state", "help", "version"];
373
- var CLI_RUNS_COMMANDS = ["list", "inspect", "cancel", "cleanup", "export"];
374
- var CLI_SESSIONS_COMMANDS = ["list", "inspect", "rename", "fork", "archive"];
375
- var CLI_CHANGES_COMMANDS = ["create", "verify"];
376
- var CLI_STATE_COMMANDS = ["status", "export", "import"];
377
- var COMMANDS = new Set(CLI_COMMANDS);
378
- var RUNS_COMMANDS = new Set(CLI_RUNS_COMMANDS);
379
- var SESSIONS_COMMANDS = new Set(CLI_SESSIONS_COMMANDS);
380
- var CHANGES_COMMANDS = new Set(CLI_CHANGES_COMMANDS);
381
- var STATE_COMMANDS = new Set(CLI_STATE_COMMANDS);
382
- var CLI_TIMESTAMP_SCHEMA = z2.iso.datetime({ precision: 3 });
383
- var RUN_STATUSES = new Set([
384
- "queued",
385
- "running",
386
- "completed",
387
- "suspended",
388
- "waiting_approval",
389
- "cancel_requested",
390
- "failed",
391
- "cancelled",
392
- "timed_out"
393
- ]);
394
-
395
- class CliUsageError extends HarnessError {
396
- constructor(message) {
397
- super(message, { code: "CLI_USAGE_INVALID", category: "usage" });
398
- this.name = "CliUsageError";
399
- }
400
- }
401
- var optionValue = (argv, index, name) => {
402
- const value = argv[index + 1];
403
- if (!value || value.startsWith("-")) {
404
- throw new CliUsageError(`Missing value for ${name}.`);
405
- }
406
- return value;
407
- };
408
- var parseCliArgs = (argv) => {
409
- const booleanOptions = new Set(["--token-budget", "--no-token-budget", "--yes", "--approve", "--deny", "--json", "--jsonl", "--continue", "--cascade", "--final", "--apply", "--help", "--version", "--no-project-context", "--update"]);
410
- const separator = argv.indexOf("--");
411
- argv = argv.flatMap((arg, index) => {
412
- const equals = arg.indexOf("=");
413
- const name = arg.slice(0, equals);
414
- return (separator < 0 || index < separator) && equals > 0 && name in CLI_OPTION_DEFINITIONS && !booleanOptions.has(name) ? [name, arg.slice(equals + 1)] : [arg];
415
- });
416
- let command = "run";
417
- let commandWasExplicit = false;
418
- const positional = [];
419
- const options = {
420
- command,
421
- cascade: false,
422
- final: false,
423
- apply: false,
424
- yes: false,
425
- json: false,
426
- jsonl: false,
427
- continueSession: false,
428
- implicitCommand: true
429
- };
430
- let positionalOnly = false;
431
- const optionCounts = new Map;
432
- const helpIndex = argv.findIndex((arg, index) => (arg === "--help" || arg === "-h") && (argv.indexOf("--") < 0 || index < argv.indexOf("--")));
433
- if (helpIndex >= 0) {
434
- const words = [];
435
- for (let index = 0;index < argv.length && argv[index] !== "--"; index++) {
436
- const arg = argv[index];
437
- if (arg.startsWith("-")) {
438
- if (arg in CLI_OPTION_DEFINITIONS && !booleanOptions.has(arg) && argv[index + 1] && !argv[index + 1].startsWith("-"))
439
- index++;
440
- } else
441
- words.push(arg);
442
- }
443
- const first = words[0];
444
- const parts = first === "help" ? words.slice(1) : first && COMMANDS.has(first) ? words.slice(0, ["runs", "sessions", "changes", "state"].includes(first) ? 2 : 1) : [];
445
- try {
446
- const topic = resolveHelpTopic(parts, parts.length > 0);
447
- return { ...options, command: "help", ...topic ? { helpTopic: topic } : {} };
448
- } catch (error) {
449
- throw new CliUsageError(error instanceof Error ? error.message : String(error));
450
- }
451
- }
452
- for (let index = 0;index < argv.length; index += 1) {
453
- const argument = argv[index];
454
- if (!argument) {
455
- continue;
456
- }
457
- if (positionalOnly) {
458
- positional.push(argument);
459
- continue;
460
- }
461
- if (argument === "--") {
462
- positionalOnly = true;
463
- continue;
464
- }
465
- const canonicalOption = argument === "-h" ? "--help" : argument === "-v" ? "--version" : argument;
466
- if (canonicalOption in CLI_OPTION_DEFINITIONS) {
467
- optionCounts.set(canonicalOption, (optionCounts.get(canonicalOption) ?? 0) + 1);
468
- }
469
- if (!commandWasExplicit && COMMANDS.has(argument)) {
470
- command = argument;
471
- options.command = command;
472
- commandWasExplicit = true;
473
- continue;
474
- }
475
- switch (argument) {
476
- case "--update":
477
- options.updateProfile = true;
478
- break;
479
- case "-":
480
- positional.push(argument);
481
- break;
482
- case "--profile":
483
- try {
484
- options.profile = validateCliProfileName(optionValue(argv, index, argument));
485
- } catch (error) {
486
- throw new CliUsageError(error instanceof Error ? error.message : String(error));
487
- }
488
- index += 1;
489
- break;
490
- case "--provider":
491
- try {
492
- options.provider = parseProvider(optionValue(argv, index, argument));
493
- } catch (error) {
494
- throw new CliUsageError(error instanceof Error ? error.message : String(error));
495
- }
496
- index += 1;
497
- break;
498
- case "--model":
499
- options.model = optionValue(argv, index, argument);
500
- index += 1;
501
- break;
502
- case "--route": {
503
- const value = optionValue(argv, index, argument);
504
- try {
505
- parseHarnessModelRoute(value);
506
- } catch (error) {
507
- throw new CliUsageError(error instanceof Error ? error.message : String(error));
508
- }
509
- options.routes ??= [];
510
- options.routes.push(value);
511
- if (options.routes.length > HARNESS_SUBAGENT_PROFILES.length) {
512
- throw new CliUsageError(`--route cannot be repeated more than ${HARNESS_SUBAGENT_PROFILES.length} times.`);
513
- }
514
- index += 1;
515
- break;
516
- }
517
- case "--workspace":
518
- options.workspace = optionValue(argv, index, argument);
519
- index += 1;
520
- break;
521
- case "--state-dir":
522
- options.stateDirectory = optionValue(argv, index, argument);
523
- index += 1;
524
- break;
525
- case "--mcp-config":
526
- options.mcpConfigPath = optionValue(argv, index, argument);
527
- index += 1;
528
- break;
529
- case "--context-config":
530
- options.contextConfigPath = optionValue(argv, index, argument);
531
- index += 1;
532
- break;
533
- case "--token-budget":
534
- options.unlimitedTokens = false;
535
- break;
536
- case "--no-token-budget":
537
- options.unlimitedTokens = true;
538
- break;
539
- case "--no-project-context":
540
- options.projectContext = false;
541
- break;
542
- case "--patch":
543
- options.patchPath = optionValue(argv, index, argument);
544
- index += 1;
545
- break;
546
- case "--preconditions":
547
- options.preconditionsPath = optionValue(argv, index, argument);
548
- index += 1;
549
- break;
550
- case "--now": {
551
- const value = optionValue(argv, index, argument);
552
- if (!CLI_TIMESTAMP_SCHEMA.safeParse(value).success) {
553
- throw new CliUsageError("--now must be a millisecond-precision ISO-8601 UTC timestamp.");
554
- }
555
- options.verificationTime = value;
556
- index += 1;
557
- break;
558
- }
559
- case "--reasoning":
560
- options.reasoningEffort = reasoningEffortSchema.parse(optionValue(argv, index, argument));
561
- index += 1;
562
- break;
563
- case "--require-verified-delivery":
564
- options.requireVerifiedDelivery = true;
565
- break;
566
- case "--execution":
567
- options.executionBackend = optionValue(argv, index, argument);
568
- index += 1;
569
- break;
570
- case "--oci-runtime":
571
- options.ociRuntime = optionValue(argv, index, argument);
572
- index += 1;
573
- break;
574
- case "--oci-image":
575
- options.ociImage = optionValue(argv, index, argument);
576
- index += 1;
577
- break;
578
- case "--oci-allow-command": {
579
- const value = optionValue(argv, index, argument);
580
- if (!/^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$/.test(value)) {
581
- throw new CliUsageError("--oci-allow-command requires a bare executable name.");
582
- }
583
- options.ociAllowedCommands ??= [];
584
- options.ociAllowedCommands.push(value);
585
- index += 1;
586
- break;
587
- }
588
- case "--oci-shell":
589
- options.ociShellMode = optionValue(argv, index, argument);
590
- if (!["deny", "ask"].includes(options.ociShellMode)) {
591
- throw new CliUsageError("--oci-shell must be deny or ask.");
592
- }
593
- index += 1;
594
- break;
595
- case "--store":
596
- options.storeBackend = optionValue(argv, index, argument);
597
- index += 1;
598
- break;
599
- case "--tenant":
600
- options.tenantId = optionValue(argv, index, argument);
601
- index += 1;
602
- break;
603
- case "--user":
604
- options.userId = optionValue(argv, index, argument);
605
- index += 1;
606
- break;
607
- case "--namespace":
608
- options.namespace = optionValue(argv, index, argument);
609
- index += 1;
610
- break;
611
- case "--idempotency-key":
612
- options.idempotencyKey = optionValue(argv, index, argument);
613
- if (options.idempotencyKey.length > 256) {
614
- throw new CliUsageError("--idempotency-key cannot exceed 256 characters.");
615
- }
616
- index += 1;
617
- break;
618
- case "--max-steps": {
619
- const value = optionValue(argv, index, argument);
620
- options.maxSteps = Number(value);
621
- if (!Number.isSafeInteger(options.maxSteps) || options.maxSteps < 1) {
622
- throw new CliUsageError("--max-steps must be a positive safe integer.");
623
- }
624
- index += 1;
625
- break;
626
- }
627
- case "--timeout-ms":
628
- case "--max-tool-calls":
629
- case "--max-tool-errors":
630
- case "--context-tokens":
631
- case "--max-input-tokens":
632
- case "--max-output-tokens":
633
- case "--max-total-tokens":
634
- case "--subagent-max-steps":
635
- case "--subagent-max-tool-calls":
636
- case "--subagent-max-tool-errors":
637
- case "--subagent-max-input-tokens":
638
- case "--subagent-max-output-tokens":
639
- case "--subagent-max-total-tokens":
640
- case "--subagent-timeout-ms":
641
- case "--max-parallel-reviews": {
642
- const value = Number(optionValue(argv, index, argument));
643
- if (!Number.isSafeInteger(value) || value < 0) {
644
- throw new CliUsageError(`${argument} must be a non-negative integer.`);
645
- }
646
- if (argument === "--timeout-ms")
647
- options.timeoutMs = value;
648
- if (argument === "--max-tool-calls")
649
- options.maxToolCalls = value;
650
- if (argument === "--max-tool-errors")
651
- options.maxToolErrors = value;
652
- if (argument === "--context-tokens")
653
- options.compactionMaxEstimatedInputTokens = value;
654
- if (argument === "--max-input-tokens")
655
- options.maxInputTokens = value;
656
- if (argument === "--max-output-tokens")
657
- options.maxOutputTokens = value;
658
- if (argument === "--max-total-tokens")
659
- options.maxTotalTokens = value;
660
- if (argument === "--subagent-max-steps")
661
- options.subagentMaxSteps = value;
662
- if (argument === "--subagent-max-tool-calls")
663
- options.subagentMaxToolCalls = value;
664
- if (argument === "--subagent-max-tool-errors")
665
- options.subagentMaxToolErrors = value;
666
- if (argument === "--subagent-max-input-tokens")
667
- options.subagentMaxInputTokens = value;
668
- if (argument === "--subagent-max-output-tokens")
669
- options.subagentMaxOutputTokens = value;
670
- if (argument === "--subagent-max-total-tokens")
671
- options.subagentMaxTotalTokens = value;
672
- if (argument === "--subagent-timeout-ms")
673
- options.subagentTimeoutMs = value;
674
- if (argument === "--max-parallel-reviews")
675
- options.maxParallelReviews = value;
676
- index += 1;
677
- break;
678
- }
679
- case "--oci-max-process-runtime-ms":
680
- case "--oci-max-process-output-bytes":
681
- case "--oci-max-memory-mb":
682
- case "--oci-max-pids":
683
- case "--oci-max-cpus":
684
- case "--oci-max-workspace-bytes":
685
- case "--oci-max-file-write-bytes":
686
- case "--oci-tmpfs-mb": {
687
- const value = Number(optionValue(argv, index, argument));
688
- if (!Number.isSafeInteger(value) || value < 1) {
689
- throw new CliUsageError(`${argument} must be a positive integer.`);
690
- }
691
- if (argument === "--oci-max-process-runtime-ms")
692
- options.ociMaxProcessRuntimeMs = value;
693
- if (argument === "--oci-max-process-output-bytes")
694
- options.ociMaxProcessOutputBytes = value;
695
- if (argument === "--oci-max-memory-mb")
696
- options.ociMaxMemoryMb = value;
697
- if (argument === "--oci-max-pids")
698
- options.ociMaxPids = value;
699
- if (argument === "--oci-max-cpus")
700
- options.ociMaxCpus = value;
701
- if (argument === "--oci-max-workspace-bytes")
702
- options.ociMaxWorkspaceBytes = value;
703
- if (argument === "--oci-max-file-write-bytes")
704
- options.ociMaxFileWriteBytes = value;
705
- if (argument === "--oci-tmpfs-mb")
706
- options.ociTmpfsMb = value;
707
- index += 1;
708
- break;
709
- }
710
- case "--search":
711
- options.sessionSearch = optionValue(argv, index, argument);
712
- index += 1;
713
- break;
714
- case "--pricing-file":
715
- options.pricingFile = optionValue(argv, index, argument);
716
- index += 1;
717
- break;
718
- case "--compaction-model":
719
- options.compactionModel = optionValue(argv, index, argument);
720
- index += 1;
721
- break;
722
- case "--compaction-provider":
723
- options.compactionProvider = parseProvider(optionValue(argv, index, argument));
724
- index += 1;
725
- break;
726
- case "--usage-limit-usd": {
727
- const value = Number(optionValue(argv, index, argument));
728
- if (!Number.isFinite(value) || value <= 0)
729
- throw new CliUsageError("--usage-limit-usd must be positive USD.");
730
- options.usageLimitUsd = value;
731
- index += 1;
732
- break;
733
- }
734
- case "--max-cost-usd":
735
- case "--input-cost-per-million":
736
- case "--output-cost-per-million": {
737
- const value = Number(optionValue(argv, index, argument));
738
- if (!Number.isFinite(value) || value < 0) {
739
- throw new CliUsageError(`${argument} must be a non-negative number.`);
740
- }
741
- if (argument === "--max-cost-usd")
742
- options.maxCostUsd = value;
743
- if (argument === "--input-cost-per-million")
744
- options.inputCostPerMillion = value;
745
- if (argument === "--output-cost-per-million")
746
- options.outputCostPerMillion = value;
747
- index += 1;
748
- break;
749
- }
750
- case "--allow-check": {
751
- const value = optionValue(argv, index, argument);
752
- if (!/^[A-Za-z0-9][A-Za-z0-9:_-]{0,63}$/.test(value)) {
753
- throw new CliUsageError("--allow-check must use 1-64 letters, digits, colon, underscore, or hyphen.");
754
- }
755
- options.allowedChecks ??= [];
756
- options.allowedChecks.push(value);
757
- if (options.allowedChecks.length > 50) {
758
- throw new CliUsageError("--allow-check cannot be repeated more than 50 times.");
759
- }
760
- index += 1;
761
- break;
762
- }
763
- case "--require-capability": {
764
- const value = optionValue(argv, index, argument);
765
- if (!HARNESS_REQUIRED_CAPABILITIES.includes(value)) {
766
- throw new CliUsageError(`--require-capability must be one of: ${HARNESS_REQUIRED_CAPABILITIES.join(", ")}.`);
767
- }
768
- options.requiredCapabilities ??= [];
769
- options.requiredCapabilities.push(value);
770
- index += 1;
771
- break;
772
- }
773
- case "--subagent": {
774
- const value = optionValue(argv, index, argument);
775
- if (!HARNESS_SUBAGENT_PROFILES.includes(value)) {
776
- throw new CliUsageError(`--subagent must be one of: ${HARNESS_SUBAGENT_PROFILES.join(", ")}.`);
777
- }
778
- options.subagentProfiles ??= [];
779
- options.subagentProfiles.push(value);
780
- index += 1;
781
- break;
782
- }
783
- case "--reviewer": {
784
- const value = optionValue(argv, index, argument);
785
- if (value !== "explorer" && value !== "reviewer") {
786
- throw new CliUsageError("--reviewer must be explorer or reviewer.");
787
- }
788
- options.reviewers ??= [];
789
- options.reviewers.push(value);
790
- index += 1;
791
- break;
792
- }
793
- case "--approval-mode": {
794
- const mode = optionValue(argv, index, argument);
795
- if (mode !== "ask" && mode !== "auto" && mode !== "restricted")
796
- throw new CliUsageError("--approval-mode must be ask, auto or restricted.");
797
- options.approvalMode = mode;
798
- options.yes = mode === "auto";
799
- index += 1;
800
- break;
801
- }
802
- case "--yes":
803
- options.yes = true;
804
- break;
805
- case "--approve":
806
- if (options.approve === false) {
807
- throw new CliUsageError("You cannot combine --approve and --deny.");
808
- }
809
- options.approve = true;
810
- break;
811
- case "--deny":
812
- if (options.approve === true) {
813
- throw new CliUsageError("You cannot combine --approve and --deny.");
814
- }
815
- options.approve = false;
816
- break;
817
- case "--json":
818
- options.json = true;
819
- break;
820
- case "--jsonl":
821
- options.jsonl = true;
822
- break;
823
- case "--service":
824
- options.serviceFile = optionValue(argv, index, argument);
825
- index += 1;
826
- break;
827
- case "--session":
828
- options.sessionId = optionValue(argv, index, argument);
829
- index += 1;
830
- break;
831
- case "--continue":
832
- options.continueSession = true;
833
- break;
834
- case "--status": {
835
- const value = optionValue(argv, index, argument);
836
- if (!RUN_STATUSES.has(value)) {
837
- throw new CliUsageError(`Unsupported run status: ${value}.`);
838
- }
839
- options.statuses ??= [];
840
- options.statuses.push(value);
841
- index += 1;
842
- break;
843
- }
844
- case "--limit": {
845
- const value = Number(optionValue(argv, index, argument));
846
- if (!Number.isSafeInteger(value) || value < 1 || value > 1000) {
847
- throw new CliUsageError("--limit must be an integer between 1 and 1000.");
848
- }
849
- options.limit = value;
850
- index += 1;
851
- break;
852
- }
853
- case "--cursor":
854
- options.cursor = optionValue(argv, index, argument);
855
- index += 1;
856
- break;
857
- case "--before": {
858
- const value = optionValue(argv, index, argument);
859
- const timestamp = /^\d+$/.test(value) ? Number(value) : Date.parse(value);
860
- if (!Number.isFinite(timestamp) || timestamp < 0) {
861
- throw new CliUsageError("--before must be an ISO-8601 date or millisecond timestamp.");
862
- }
863
- options.before = timestamp;
864
- index += 1;
865
- break;
866
- }
867
- case "--reason":
868
- options.reason = optionValue(argv, index, argument);
869
- if (options.reason.length > 500) {
870
- throw new CliUsageError("--reason cannot exceed 500 characters.");
871
- }
872
- index += 1;
873
- break;
874
- case "--cascade":
875
- options.cascade = true;
876
- break;
877
- case "--final":
878
- options.final = true;
879
- break;
880
- case "--apply":
881
- options.apply = true;
882
- break;
883
- case "--help":
884
- case "-h":
885
- options.command = "help";
886
- break;
887
- case "--version":
888
- case "-v":
889
- options.command = "version";
890
- break;
891
- default:
892
- if (argument.startsWith("-")) {
893
- throw new CliUsageError(`Unknown option: ${argument}`);
894
- }
895
- positional.push(argument);
896
- }
897
- }
898
- if (options.command === "help") {
899
- try {
900
- validateCliCommandOptions("help", optionCounts);
901
- const topic = resolveHelpTopic(command === "help" ? positional : [command, ...positional], commandWasExplicit);
902
- if (topic)
903
- options.helpTopic = topic;
904
- } catch (error) {
905
- throw new CliUsageError(error instanceof Error ? error.message : String(error));
906
- }
907
- return options;
908
- }
909
- options.implicitCommand = !commandWasExplicit;
910
- if (options.json && options.jsonl) {
911
- throw new CliUsageError("You cannot combine --json and --jsonl.");
912
- }
913
- if (options.sessionId && options.continueSession) {
914
- throw new CliUsageError("You cannot combine --session and --continue.");
915
- }
916
- if (options.jsonl && options.command !== "run" && options.command !== "resume") {
917
- throw new CliUsageError("--jsonl is supported by run and resume.");
918
- }
919
- if (options.command !== "changes" && (options.patchPath || options.preconditionsPath || options.verificationTime)) {
920
- throw new CliUsageError("--patch, --preconditions, and --now are supported only by changes.");
921
- }
922
- if (options.command === "resume") {
923
- const runId = positional.shift();
924
- if (runId) {
925
- options.runId = runId;
926
- }
927
- if (positional.length > 0) {
928
- throw new CliUsageError("resume accepts exactly one runId.");
929
- }
930
- } else if (options.command === "runs") {
931
- const runsCommand = positional.shift();
932
- if (!runsCommand || !RUNS_COMMANDS.has(runsCommand)) {
933
- throw new CliUsageError("runs requires one of: list, inspect, cancel, cleanup, export.");
934
- }
935
- options.runsCommand = runsCommand;
936
- if (runsCommand === "inspect" || runsCommand === "cancel" || runsCommand === "export") {
937
- const runId = positional.shift();
938
- if (!runId) {
939
- throw new CliUsageError(`runs ${runsCommand} requires a runId.`);
940
- }
941
- options.runId = runId;
942
- }
943
- if (positional.length > 0) {
944
- throw new CliUsageError(`runs ${runsCommand} received unexpected positional arguments.`);
945
- }
946
- } else if (options.command === "sessions") {
947
- const sessionsCommand = positional.shift();
948
- if (!sessionsCommand || !SESSIONS_COMMANDS.has(sessionsCommand)) {
949
- throw new CliUsageError("sessions requires one of: list, inspect, rename, fork, archive.");
950
- }
951
- options.sessionsCommand = sessionsCommand;
952
- if (sessionsCommand !== "list") {
953
- const sessionId = positional.shift();
954
- if (!sessionId)
955
- throw new CliUsageError(`sessions ${sessionsCommand} requires a sessionId.`);
956
- options.sessionId = sessionId;
957
- }
958
- if (sessionsCommand === "rename") {
959
- const title = positional.join(" ").trim();
960
- if (!title)
961
- throw new CliUsageError("sessions rename requires a title.");
962
- options.sessionTitle = title;
963
- positional.length = 0;
964
- }
965
- if (positional.length > 0) {
966
- throw new CliUsageError(`sessions ${sessionsCommand} received unexpected positional arguments.`);
967
- }
968
- } else if (options.command === "changes") {
969
- const changesCommand = positional.shift();
970
- if (!changesCommand || !CHANGES_COMMANDS.has(changesCommand)) {
971
- throw new CliUsageError("changes requires one of: create, verify.");
972
- }
973
- options.changesCommand = changesCommand;
974
- const artifactPath = positional.shift();
975
- if (!artifactPath) {
976
- throw new CliUsageError(`changes ${changesCommand} requires an input JSON file.`);
977
- }
978
- options.artifactPath = artifactPath;
979
- if (changesCommand === "create" && (options.preconditionsPath || options.verificationTime)) {
980
- throw new CliUsageError("--preconditions and --now are only supported by changes verify.");
981
- }
982
- if (positional.length > 0) {
983
- throw new CliUsageError(`changes ${changesCommand} received unexpected positional arguments.`);
984
- }
985
- } else if (options.command === "state") {
986
- const stateCommand = positional.shift();
987
- if (!stateCommand || !STATE_COMMANDS.has(stateCommand)) {
988
- throw new CliUsageError("state requires one of: status, export, import.");
989
- }
990
- options.stateCommand = stateCommand;
991
- if (stateCommand !== "status") {
992
- const backupPath = positional.shift();
993
- if (!backupPath)
994
- throw new CliUsageError(`state ${stateCommand} requires a backup JSON path.`);
995
- options.backupPath = backupPath;
996
- }
997
- if (positional.length > 0) {
998
- throw new CliUsageError(`state ${stateCommand} received unexpected positional arguments.`);
999
- }
1000
- } else if (options.command === "run" || options.command === "review") {
1001
- if (positional.includes("-") && positional.length !== 1) {
1002
- throw new CliUsageError("Use - alone to read the task from stdin, or provide a task argument.");
1003
- }
1004
- const prompt = positional.join(" ").trim();
1005
- if (prompt) {
1006
- options.prompt = prompt;
1007
- }
1008
- } else if (positional.length > 0) {
1009
- throw new CliUsageError(`${options.command} does not accept positional arguments.`);
1010
- }
1011
- if (options.implicitCommand && options.command === "run" && !options.prompt && (options.continueSession || options.sessionId) && !options.json && !options.jsonl)
1012
- options.command = "chat";
1013
- if (options.serviceFile) {
1014
- const allowed = new Set(["--service", "--session", "--idempotency-key", "--yes", "--json", "--jsonl", "--approve", "--deny", "--search", "--continue"]);
1015
- for (const arg of optionCounts.keys())
1016
- if (arg.startsWith("--") && !allowed.has(arg))
1017
- throw new CliUsageError(`Service mode does not accept ${arg}; runtime configuration belongs to the service host.`);
1018
- } else if (["run", "resume"].includes(options.command) && optionCounts.has("--session")) {
1019
- throw new CliUsageError("--session for run/resume requires --service.");
1020
- }
1021
- const commandKey = options.command === "runs" ? `runs:${options.runsCommand}` : options.command === "sessions" ? `sessions:${options.sessionsCommand}` : options.command === "changes" ? `changes:${options.changesCommand}` : options.command === "state" ? `state:${options.stateCommand}` : options.command;
1022
- try {
1023
- validateCliCommandOptions(commandKey, optionCounts);
1024
- } catch (error) {
1025
- throw new CliUsageError(error instanceof Error ? error.message : String(error));
1026
- }
1027
- if (options.command === "review") {
1028
- const invalidRoute = options.routes?.map((route) => parseHarnessModelRoute(route)).find((route) => route.profile !== "explorer" && route.profile !== "reviewer");
1029
- if (invalidRoute) {
1030
- throw new CliUsageError(`review cannot route the unused ${invalidRoute.profile} profile; use explorer or reviewer.`);
1031
- }
1032
- }
1033
- return options;
1034
- };
1035
-
1036
- // src/cli/terminal/terminal-table.ts
1037
- var segments = new Intl.Segmenter(undefined, { granularity: "grapheme" });
1038
- var graphemes = (value) => [...segments.segment(value)].map((item) => item.segment);
1039
- var terminalCellWidth = (value) => graphemes(value).reduce((width, part) => {
1040
- if (/^[\p{Mark}\u200d\ufe0f]+$/u.test(part))
1041
- return width;
1042
- const code = part.codePointAt(0);
1043
- const wide = /\p{Extended_Pictographic}|\p{Regional_Indicator}|\u20e3/u.test(part) || code >= 4352 && (code <= 4447 || code === 9001 || code === 9002 || code >= 11904 && code <= 42191 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65040 && code <= 65135 || code >= 65281 && code <= 65376 || code >= 65504 && code <= 65510 || code >= 131072);
1044
- return width + (wide ? 2 : 1);
1045
- }, 0);
1046
- function tableCells(line) {
1047
- let text = line.trim();
1048
- if (text.startsWith("|"))
1049
- text = text.slice(1);
1050
- const cells = [];
1051
- let cell = "", ticks = 0;
1052
- for (let i = 0;i < text.length; i++) {
1053
- const char = text[i];
1054
- if (char === "\\" && text[i + 1] === "|") {
1055
- cell += "|";
1056
- i++;
1057
- continue;
1058
- }
1059
- if (char === "`") {
1060
- let count = 1;
1061
- while (text[i + count] === "`")
1062
- count++;
1063
- ticks = ticks === count ? 0 : ticks || count;
1064
- cell += "`".repeat(count);
1065
- i += count - 1;
1066
- continue;
1067
- }
1068
- if (char === "|" && !ticks) {
1069
- cells.push(cell.trim());
1070
- cell = "";
1071
- } else
1072
- cell += char;
1073
- }
1074
- if (cell.trim() || !text.endsWith("|"))
1075
- cells.push(cell.trim());
1076
- return cells;
1077
- }
1078
- var isTableSeparator = (line, columns) => {
1079
- const cells = tableCells(line);
1080
- return cells.length === columns && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
1081
- };
1082
- var plainCell = (value) => value.replace(/(`+)(.*?)\1/g, "$2").replace(/\*\*(.+?)\*\*/g, "$1").replace(/\s+/g, " ").trim();
1083
- function wrap(value, width) {
1084
- const lines = [];
1085
- let line = "", used = 0;
1086
- for (const word of value.split(" ")) {
1087
- const size = terminalCellWidth(word);
1088
- if (line && used + 1 + size > width) {
1089
- lines.push(line);
1090
- line = "";
1091
- used = 0;
1092
- }
1093
- if (line) {
1094
- line += " ";
1095
- used++;
1096
- }
1097
- for (const part of graphemes(word)) {
1098
- const size2 = terminalCellWidth(part);
1099
- if (line && used + size2 > width) {
1100
- lines.push(line);
1101
- line = "";
1102
- used = 0;
1103
- }
1104
- line += part;
1105
- used += size2;
1106
- }
1107
- }
1108
- if (line || !lines.length)
1109
- lines.push(line);
1110
- return lines;
1111
- }
1112
- function renderTerminalTable(lines, columns, color) {
1113
- const rows = [lines[0], ...lines.slice(2)].map((line) => tableCells(line).map(plainCell));
1114
- const count = rows[0].length;
1115
- const width = Math.max(4, Math.min(240, Number.isFinite(columns) ? Math.floor(columns) - 1 : 79));
1116
- const bold = (value) => color ? `\x1B[1m${value}\x1B[0m` : value;
1117
- if (width < count * 7 + 1) {
1118
- if (rows.length === 1)
1119
- return rows[0].map((cell) => bold(wrap(cell, width).join(`
1120
- `))).join(`
1121
- `) + `
1122
- `;
1123
- return rows.slice(1).map((row) => row.map((cell, i) => [bold(wrap(rows[0][i] + ":", width).join(`
1124
- `)), ...wrap(cell, width)].join(`
1125
- `)).join(`
1126
- `)).join(`
1127
-
1128
- `) + `
1129
- `;
1130
- }
1131
- const desired = Array.from({ length: count }, (_, i) => Math.max(4, ...rows.map((row) => terminalCellWidth(row[i] ?? ""))));
1132
- const widths = Array(count).fill(4);
1133
- let remaining = width - 7 * count - 1;
1134
- while (remaining-- > 0) {
1135
- let smallest = -1;
1136
- for (let i = 0;i < count; i++)
1137
- if (widths[i] < desired[i] && (smallest < 0 || widths[i] < widths[smallest]))
1138
- smallest = i;
1139
- if (smallest < 0)
1140
- break;
1141
- widths[smallest]++;
1142
- }
1143
- const align = tableCells(lines[1]).map((cell) => cell.endsWith(":") ? cell.startsWith(":") ? "center" : "right" : "left");
1144
- const border = (left, mid, right) => left + widths.map((size) => "─".repeat(size + 2)).join(mid) + right;
1145
- const rendered = [border("┌", "┬", "┐")];
1146
- rows.forEach((row, index) => {
1147
- const cells = widths.map((size, i) => wrap(row[i] ?? "", size));
1148
- for (let line = 0;line < Math.max(...cells.map((cell) => cell.length)); line++) {
1149
- rendered.push("│ " + cells.map((cell, i) => {
1150
- const text = cell[line] ?? "", spare = widths[i] - terminalCellWidth(text);
1151
- const left = align[i] === "right" ? spare : align[i] === "center" ? Math.floor(spare / 2) : 0;
1152
- const padded = " ".repeat(left) + text + " ".repeat(spare - left);
1153
- return index === 0 ? bold(padded) : padded;
1154
- }).join(" │ ") + " │");
1155
- }
1156
- if (index === 0)
1157
- rendered.push(border("├", "┼", "┤"));
1158
- });
1159
- rendered.push(border("└", "┴", "┘"));
1160
- return rendered.join(`
1161
- `) + `
1162
- `;
1163
- }
1164
-
1165
- // src/cli/terminal/terminal-markdown.ts
1166
- class TerminalMarkdown {
1167
- output;
1168
- color;
1169
- columns;
1170
- pending = "";
1171
- code = false;
1172
- heading = false;
1173
- lineStart = true;
1174
- timer;
1175
- tableLines = [];
1176
- constructor(output, color, columns = () => 80) {
1177
- this.output = output;
1178
- this.color = color;
1179
- this.columns = columns;
1180
- }
1181
- drain(final = false) {
1182
- let rendered = "", activeStyle = 0;
1183
- const emit = (text, style = this.code ? 36 : this.heading ? 1 : 0) => {
1184
- if (this.color && style !== activeStyle) {
1185
- if (activeStyle)
1186
- rendered += "\x1B[0m";
1187
- if (style)
1188
- rendered += `\x1B[${style}m`;
1189
- activeStyle = style;
1190
- }
1191
- rendered += text;
1192
- };
1193
- const finishTable = () => {
1194
- if (!this.tableLines.length)
1195
- return;
1196
- emit(this.tableLines.length > 1 ? renderTerminalTable(this.tableLines, this.columns(), this.color) : this.tableLines[0], 0);
1197
- this.tableLines = [];
1198
- };
1199
- while (this.pending) {
1200
- if (!final && this.pending.length === 1 && /[\uD800-\uDBFF]/.test(this.pending))
1201
- break;
1202
- if (this.lineStart) {
1203
- if (!this.code && (this.pending.startsWith("|") || this.tableLines.length)) {
1204
- const end = this.pending.indexOf(`
1205
- `);
1206
- if (this.pending.startsWith("|") && end < 0 && !final && this.pending.length < 8192)
1207
- break;
1208
- const line = this.pending.slice(0, end < 0 ? this.pending.length : end + 1);
1209
- const count = this.tableLines.length ? tableCells(this.tableLines[0]).length : 0;
1210
- const accepted = (end >= 0 || final) && this.pending.startsWith("|") && line.length <= 8192 && (this.tableLines.length === 0 ? tableCells(line).length > 0 : this.tableLines.length === 1 ? isTableSeparator(line, count) : tableCells(line).length === count);
1211
- if (accepted && this.tableLines.length < 128) {
1212
- this.tableLines.push(line);
1213
- this.pending = this.pending.slice(line.length);
1214
- continue;
1215
- }
1216
- finishTable();
1217
- }
1218
- if (!final && (/^`{1,2}$/.test(this.pending) || /^#{1,6}$/.test(this.pending)))
1219
- break;
1220
- if (this.pending.startsWith("```")) {
1221
- const end = this.pending.indexOf(`
1222
- `);
1223
- if (end < 0 && !final && this.pending.length < 128)
1224
- break;
1225
- const length = end < 0 ? this.pending.length : end;
1226
- emit(this.pending.slice(0, length), 90);
1227
- this.pending = this.pending.slice(length);
1228
- this.code = !this.code;
1229
- this.lineStart = false;
1230
- continue;
1231
- }
1232
- if (!this.code) {
1233
- const heading = /^#{1,6} /.exec(this.pending);
1234
- if (heading) {
1235
- this.heading = true;
1236
- this.pending = this.pending.slice(heading[0].length);
1237
- this.lineStart = false;
1238
- continue;
1239
- }
1240
- }
1241
- this.lineStart = false;
1242
- }
1243
- if (this.pending[0] === `
1244
- `) {
1245
- emit(`
1246
- `, 0);
1247
- this.pending = this.pending.slice(1);
1248
- this.lineStart = true;
1249
- this.heading = false;
1250
- continue;
1251
- }
1252
- if (this.color && !this.code) {
1253
- if (this.pending === "*" && !final)
1254
- break;
1255
- const marker = this.pending.startsWith("**") ? "**" : this.pending[0] === "`" ? "`" : undefined;
1256
- if (marker) {
1257
- const end = this.pending.indexOf(marker, marker.length);
1258
- const newline = this.pending.indexOf(`
1259
- `);
1260
- if (end > marker.length && (newline < 0 || end < newline)) {
1261
- emit(this.pending.slice(marker.length, end), marker === "`" ? 36 : 1);
1262
- this.pending = this.pending.slice(end + marker.length);
1263
- continue;
1264
- }
1265
- if (!final && newline < 0 && this.pending.length < 2048)
1266
- break;
1267
- emit(marker);
1268
- this.pending = this.pending.slice(marker.length);
1269
- continue;
1270
- }
1271
- }
1272
- const point = String.fromCodePoint(this.pending.codePointAt(0));
1273
- emit(point);
1274
- this.pending = this.pending.slice(point.length);
1275
- }
1276
- if (final)
1277
- finishTable();
1278
- if (activeStyle)
1279
- rendered += "\x1B[0m";
1280
- if (rendered)
1281
- this.output(rendered);
1282
- }
1283
- write(delta) {
1284
- this.pending += sanitizeTerminalText(delta);
1285
- if (this.pending.includes(`
1286
- `) || this.pending.length >= 2048)
1287
- this.drain();
1288
- if (this.pending && !this.timer) {
1289
- this.timer = setTimeout(() => {
1290
- this.timer = undefined;
1291
- this.drain();
1292
- }, 24);
1293
- this.timer.unref();
1294
- }
1295
- }
1296
- flush() {
1297
- clearTimeout(this.timer);
1298
- this.timer = undefined;
1299
- this.drain(true);
1300
- }
1301
- }
1302
-
1303
- // src/cli/presentation.ts
1304
- import { createInterface } from "node:readline/promises";
1305
- import {
1306
- createRedactionPolicy
1307
- } from "@zhivex-ai/agents";
1308
-
1309
- // src/cli/resume-metadata.ts
1310
- var HARNESS_RESUME_METADATA_KEY = "zhivexHarnessResume";
1311
- var HARNESS_RESUME_METADATA_SCHEMA_VERSION = 1;
1312
- var harnessConfigInput = (config) => ({
1313
- schemaVersion: config.schemaVersion,
1314
- provider: config.provider,
1315
- model: config.model,
1316
- ...config.reasoningEffort ? { reasoningEffort: config.reasoningEffort } : {},
1317
- workspace: config.workspace,
1318
- stateDirectory: config.stateDirectory,
1319
- storeBackend: config.storeBackend,
1320
- tenantId: config.scope.tenantId,
1321
- ...config.scope.userId ? { userId: config.scope.userId } : {},
1322
- ...config.scope.namespace ? { namespace: config.scope.namespace } : {},
1323
- requireVerifiedDelivery: config.requireVerifiedDelivery,
1324
- maxSteps: config.maxSteps,
1325
- timeoutMs: config.timeoutMs,
1326
- maxToolCalls: config.budget.maxToolCalls,
1327
- maxToolErrors: config.budget.maxToolErrors,
1328
- ...config.budget.unlimitedTokens !== undefined ? { unlimitedTokens: config.budget.unlimitedTokens } : {},
1329
- maxInputTokens: config.budget.maxInputTokens,
1330
- maxOutputTokens: config.budget.maxOutputTokens,
1331
- maxTotalTokens: config.budget.maxTotalTokens,
1332
- ...config.costBudget ? {
1333
- maxCostUsd: config.costBudget.maxCostUsd,
1334
- inputCostPerMillion: config.costBudget.inputCostPer1kTokens * 1000,
1335
- outputCostPerMillion: config.costBudget.outputCostPer1kTokens * 1000
1336
- } : {},
1337
- compactionMaxMessages: config.compaction.maxMessages,
1338
- compactionMaxEstimatedInputTokens: config.compaction.maxEstimatedInputTokens,
1339
- compactionKeepRecentMessages: config.compaction.keepRecentMessages,
1340
- ...config.compaction.model ? { compactionModel: config.compaction.model.model, compactionProvider: config.compaction.model.provider } : {},
1341
- allowedChecks: [...config.allowedChecks],
1342
- requiredCapabilities: [...config.requiredCapabilities],
1343
- subagentProfiles: [...config.orchestration.profiles],
1344
- subagentMaxSteps: config.orchestration.childBudget.maxSteps,
1345
- subagentMaxToolCalls: config.orchestration.childBudget.maxToolCalls,
1346
- subagentMaxToolErrors: config.orchestration.childBudget.maxToolErrors,
1347
- subagentMaxInputTokens: config.orchestration.childBudget.maxInputTokens,
1348
- subagentMaxOutputTokens: config.orchestration.childBudget.maxOutputTokens,
1349
- subagentMaxTotalTokens: config.orchestration.childBudget.maxTotalTokens,
1350
- subagentTimeoutMs: config.orchestration.childTimeoutMs,
1351
- maxParallelReviews: config.orchestration.maxParallelReviews,
1352
- projectContext: config.context.enabled,
1353
- contextConfigPath: config.context.configPath,
1354
- executionBackend: config.execution.backend,
1355
- ...config.execution.backend === "oci" ? {
1356
- ociRuntime: config.execution.runtime,
1357
- ociImage: config.execution.image,
1358
- ociAllowedCommands: [...config.execution.allowedCommands],
1359
- ociShellMode: config.execution.shellMode,
1360
- ociMaxProcessRuntimeMs: config.execution.maxProcessRuntimeMs,
1361
- ociMaxProcessOutputBytes: config.execution.maxProcessOutputBytes,
1362
- ociMaxMemoryMb: config.execution.maxMemoryMb,
1363
- ociMaxPids: config.execution.maxPids,
1364
- ociMaxCpus: config.execution.maxCpus,
1365
- ociMaxWorkspaceBytes: config.execution.maxWorkspaceBytes,
1366
- ociMaxFileWriteBytes: config.execution.maxFileWriteBytes,
1367
- ociTmpfsMb: config.execution.tmpfsMb
1368
- } : {},
1369
- ...config.mcpConfigPath ? { mcpConfigPath: config.mcpConfigPath } : {}
1370
- });
1371
- var persistedCliOptions = (input) => input ? {
1372
- ...input,
1373
- ...input.allowedChecks ? { allowedChecks: [...input.allowedChecks] } : {},
1374
- ...input.requiredCapabilities ? { requiredCapabilities: [...input.requiredCapabilities] } : {},
1375
- ...input.subagentProfiles ? { subagentProfiles: [...input.subagentProfiles] } : {},
1376
- ...input.ociAllowedCommands ? { ociAllowedCommands: [...input.ociAllowedCommands] } : {}
1377
- } : {};
1378
- var createHarnessResumeMetadata = (config, routes) => ({
1379
- [HARNESS_RESUME_METADATA_KEY]: JSON.parse(JSON.stringify({
1380
- schemaVersion: HARNESS_RESUME_METADATA_SCHEMA_VERSION,
1381
- config,
1382
- ...routes && routes.size > 0 ? { routes: serializeHarnessModelRoutes(routes) } : {}
1383
- }))
1384
- });
1385
- var readHarnessResumeDocument = (state) => {
1386
- const value = state.metadata?.[HARNESS_RESUME_METADATA_KEY];
1387
- if (value === undefined)
1388
- return;
1389
- if (!value || typeof value !== "object" || Array.isArray(value)) {
1390
- throw new Error("Persisted harness resume metadata is invalid.");
1391
- }
1392
- const document = value;
1393
- if (document.schemaVersion !== HARNESS_RESUME_METADATA_SCHEMA_VERSION || !document.config || typeof document.config !== "object" || Array.isArray(document.config)) {
1394
- throw new Error("Persisted harness resume metadata is invalid.");
1395
- }
1396
- return document;
1397
- };
1398
- var readHarnessResumeConfig = (state) => {
1399
- const document = readHarnessResumeDocument(state);
1400
- if (!document)
1401
- return;
1402
- try {
1403
- return harnessConfigInput(document.config);
1404
- } catch {
1405
- throw new Error("Persisted harness resume metadata is invalid.");
1406
- }
1407
- };
1408
- var readHarnessResumeRoutes = (state) => {
1409
- const document = readHarnessResumeDocument(state);
1410
- if (!document?.routes)
1411
- return resolveHarnessModelRoutes();
1412
- if (typeof document.routes !== "object" || Array.isArray(document.routes)) {
1413
- throw new Error("Persisted harness routing metadata is invalid.");
1414
- }
1415
- try {
1416
- const values = Object.entries(document.routes).map(([profile, target]) => {
1417
- if (!target || typeof target !== "object" || Array.isArray(target))
1418
- throw new Error("invalid");
1419
- const route = target;
1420
- if (typeof route.provider !== "string" || typeof route.model !== "string")
1421
- throw new Error("invalid");
1422
- return `${profile}=${route.provider}:${route.model}`;
1423
- });
1424
- return resolveHarnessModelRoutes(values);
1425
- } catch {
1426
- throw new Error("Persisted harness routing metadata is invalid.");
1427
- }
1428
- };
1429
- var shellArgument = (value) => `'${value.replaceAll("'", `'\\''`)}'`;
1430
- var resumeCommand = (runId, config, approve = true) => [
1431
- "zhivex-harness",
1432
- "resume",
1433
- shellArgument(runId),
1434
- approve ? "--approve" : "--deny",
1435
- "--workspace",
1436
- shellArgument(config.workspace),
1437
- "--state-dir",
1438
- shellArgument(config.stateDirectory),
1439
- "--store",
1440
- config.storeBackend,
1441
- "--tenant",
1442
- shellArgument(config.scope.tenantId),
1443
- ...config.scope.userId ? ["--user", shellArgument(config.scope.userId)] : [],
1444
- ...config.scope.namespace ? ["--namespace", shellArgument(config.scope.namespace)] : []
1445
- ].join(" ");
1446
-
1447
- // src/cli/presentation.ts
1448
- var CLI_STREAM_ERROR_SEQUENCE = Symbol("cliStreamErrorSequence");
1449
- var terminalErrorRedaction = createRedactionPolicy({ includeEmails: true });
1450
- var annotateCliStreamError = (error, lastSequence) => {
1451
- const normalized = error instanceof Error ? error : normalizeHarnessError(error);
1452
- Object.defineProperty(normalized, CLI_STREAM_ERROR_SEQUENCE, {
1453
- configurable: true,
1454
- enumerable: false,
1455
- value: (lastSequence ?? 0) + 1
1456
- });
1457
- return normalized;
1458
- };
1459
- var cliStreamErrorSequence = (error) => error && typeof error === "object" && Number.isSafeInteger(error[CLI_STREAM_ERROR_SEQUENCE]) ? error[CLI_STREAM_ERROR_SEQUENCE] : 1;
1460
- var terminalErrorMessage = (error) => terminalErrorRedaction.redactText(error instanceof Error ? error.message : String(error));
1461
- var summarizeApproval = (approval) => {
1462
- return formatApproval(approval);
1463
- };
1464
- var approvalResponses = (approvals, approve, reason) => approvals.map((approval) => ({
1465
- provider: approval.provider,
1466
- approvalRequestId: approval.id,
1467
- approve,
1468
- reason
1469
- }));
1470
- var terminalApprovalResolver = (automaticallyApprove, ask, ui) => {
1471
- return async (approvals) => {
1472
- if (automaticallyApprove === "restricted")
1473
- return approvalResponses(approvals, false, "Denied by restricted approval mode.");
1474
- if (automaticallyApprove === true || automaticallyApprove === "auto") {
1475
- return approvalResponses(approvals, true, "Approved by automatic mode.");
1476
- }
1477
- if (!ask && (!process.stdin.isTTY || !process.stdout.isTTY))
1478
- return;
1479
- const readline = ask ? undefined : createInterface({ input: process.stdin, output: process.stdout });
1480
- try {
1481
- return await resolveTerminalApprovals(approvals, {
1482
- ask: ask ?? ((text) => readline.question(text)),
1483
- write: (text) => process.stderr.write(text),
1484
- ...ui
1485
- });
1486
- } finally {
1487
- readline?.close();
1488
- }
1489
- };
1490
- };
1491
- var printTerminalResult = (result, harness, output, tracker) => {
1492
- tracker.markdown?.flush();
1493
- const document = runResultDocument(result, harness);
1494
- if (output.jsonl) {
1495
- tracker.sequence = (tracker.sequence ?? 0) + 1;
1496
- process.stdout.write(`${serializeStreamResult(document, tracker.sequence)}
1497
- `);
1498
- return;
1499
- }
1500
- if (output.json) {
1501
- process.stdout.write(`${JSON.stringify(document, null, 2)}
1502
- `);
1503
- return;
1504
- }
1505
- if (!tracker.streamedText && result.outputText) {
1506
- process.stdout.write(sanitizeTerminalText(result.outputText));
1507
- }
1508
- if (result.outputText || tracker.streamedText) {
1509
- process.stdout.write(`
1510
- `);
1511
- }
1512
- process.stderr.write(`
1513
- run ${result.state.runId} · ${result.state.provider}/${result.state.modelId} · ${result.status} · ${result.steps.length} steps · ${harness.workspace.mutationAudit().length} mutations
1514
- `);
1515
- if (result.state.metadata?.[USAGE_LEDGER_KEY])
1516
- process.stderr.write(formatUsageLedger(result.state.metadata[USAGE_LEDGER_KEY]) + `
1517
- `);
1518
- process.stderr.write(formatVerificationSummary(result.toolResults) + `
1519
- `);
1520
- if (result.status === "waiting_approval") {
1521
- for (const approval of result.state.pendingApprovals) {
1522
- process.stderr.write(`
1523
- Pending approval:
1524
- ${summarizeApproval(approval)}
1525
- `);
1526
- }
1527
- process.stderr.write(`The state was persisted. Resume with: ${resumeCommand(result.state.runId, harness.config)}
1528
- `);
1529
- }
1530
- };
1531
- var proseLineOpen = new WeakSet;
1532
- var conversationStarted = new WeakSet;
1533
- var toolActivities = new WeakMap;
1534
- var lastTerminalFailures = new WeakMap;
1535
- var flushToolActivity = (tracker) => toolActivities.get(tracker)?.flush();
1536
- var streamSink = (output, tracker, compact = false) => async (event) => {
1537
- if (!output.json && !output.jsonl)
1538
- tracker.activityHistory?.observe(event);
1539
- if (output.jsonl) {
1540
- tracker.sequence = (tracker.sequence ?? 0) + 1;
1541
- process.stdout.write(`${serializeStreamEvent(event, tracker.sequence)}
1542
- `);
1543
- return;
1544
- }
1545
- if (!output.json && compact && event.type === "agent-run-update")
1546
- return;
1547
- if (!output.json && compact) {
1548
- let activity = toolActivities.get(tracker);
1549
- if (!activity) {
1550
- activity = new ToolActivity((text) => {
1551
- process.stderr.write(text);
1552
- }, Boolean(process.stderr.isTTY && process.stdout.isTTY), () => process.stderr.columns || 80, tracker.inputStatus);
1553
- toolActivities.set(tracker, activity);
1554
- }
1555
- if (event.type === "agent-compaction") {
1556
- tracker.markdown?.flush();
1557
- if (proseLineOpen.has(tracker)) {
1558
- process.stdout.write(`
1559
- `);
1560
- proseLineOpen.delete(tracker);
1561
- }
1562
- activity.phase("Updating context");
1563
- return;
1564
- }
1565
- if (event.type === "agent-step-start") {
1566
- tracker.markdown?.flush();
1567
- if (proseLineOpen.has(tracker)) {
1568
- process.stdout.write(`
1569
- `);
1570
- proseLineOpen.delete(tracker);
1571
- }
1572
- activity.phase("Waiting for model response", event.stepIndex);
1573
- return;
1574
- }
1575
- if (event.type === "agent-run-start") {
1576
- lastTerminalFailures.delete(tracker);
1577
- activity.phase("Waiting for model response", event.currentStep);
1578
- return;
1579
- }
1580
- if (event.type === "tool-call") {
1581
- tracker.markdown?.flush();
1582
- if (proseLineOpen.has(tracker)) {
1583
- process.stdout.write(`
1584
- `);
1585
- proseLineOpen.delete(tracker);
1586
- }
1587
- activity.start(event.toolCall.name);
1588
- return;
1589
- }
1590
- if (event.type === "tool-result") {
1591
- activity.finish(event.toolResult.toolName, !event.toolResult.isError);
1592
- const detail = compactToolResult(event.toolResult);
1593
- const check = ["run_check", "run_environment_command"].includes(event.toolResult.toolName) || event.toolResult.toolName.startsWith("verify_and_apply_");
1594
- if (detail && !check) {
1595
- activity.pause();
1596
- tracker.markdown?.flush();
1597
- if (proseLineOpen.has(tracker)) {
1598
- process.stdout.write(`
1599
- `);
1600
- proseLineOpen.delete(tracker);
1601
- }
1602
- process.stderr.write(`${detail}
1603
- `);
1604
- return;
1605
- }
1606
- if (!check)
1607
- return;
1608
- activity.pause();
1609
- } else if (["agent-run-finish", "error", "tool-approval-request", "agent-approval-request"].includes(event.type)) {
1610
- tracker.markdown?.flush();
1611
- if (proseLineOpen.has(tracker)) {
1612
- process.stdout.write(`
1613
- `);
1614
- proseLineOpen.delete(tracker);
1615
- }
1616
- activity.flush();
1617
- } else if (!["provider-data", "finish", "agent-step-start", "agent-step-finish"].includes(event.type)) {
1618
- activity.pause();
1619
- }
1620
- }
1621
- if (!output.json && event.type === "text-delta") {
1622
- if (compact && !conversationStarted.has(tracker)) {
1623
- process.stdout.write(terminalSupportsColor(Boolean(process.stdout.isTTY)) ? `
1624
- \x1B[1mZhivex\x1B[0m
1625
- ` : `
1626
- Zhivex
1627
- `);
1628
- conversationStarted.add(tracker);
1629
- }
1630
- if (event.textDelta.endsWith(`
1631
- `))
1632
- proseLineOpen.delete(tracker);
1633
- else if (event.textDelta)
1634
- proseLineOpen.add(tracker);
1635
- tracker.streamedText = true;
1636
- if (process.stdout.isTTY) {
1637
- tracker.markdown ??= new TerminalMarkdown((text) => {
1638
- process.stdout.write(text);
1639
- }, terminalSupportsColor(true), () => process.stdout.columns || 80);
1640
- tracker.markdown.write(event.textDelta);
1641
- } else
1642
- process.stdout.write(sanitizeTerminalText(event.textDelta));
1643
- return;
1644
- }
1645
- if (!output.json) {
1646
- if (compact && ["provider-data", "finish", "agent-run-start", "agent-step-start", "agent-step-finish", "tool-approval-request", "agent-approval-request", "agent-approval-resolved"].includes(event.type))
1647
- return;
1648
- if (compact && event.type === "agent-run-finish" && event.status === "waiting_approval")
1649
- return;
1650
- tracker.markdown?.flush();
1651
- proseLineOpen.delete(tracker);
1652
- let line = formatTerminalEvent(event, {
1653
- color: terminalSupportsColor(Boolean(process.stderr.isTTY))
1654
- });
1655
- if (compact && event.type === "tool-result" && event.toolResult.isError && line?.endsWith(" · error")) {
1656
- line = compactToolResult(event.toolResult);
1657
- }
1658
- if ((event.type === "error" || event.type === "agent-run-finish" && event.status === "failed") && line) {
1659
- const cause = terminalRunFailure(event.type === "error" ? event.error : event.state.error);
1660
- if (lastTerminalFailures.get(tracker) === cause)
1661
- return;
1662
- lastTerminalFailures.set(tracker, cause);
1663
- } else if (event.type === "agent-run-start")
1664
- lastTerminalFailures.delete(tracker);
1665
- if (line)
1666
- process.stderr.write(`
1667
- ${line}
1668
- `);
1669
- }
1670
- };
1671
- var orchestrationObserver = (json) => async (event) => {
1672
- if (json)
1673
- return;
1674
- if (event.type === "subagent-start") {
1675
- process.stderr.write(`
1676
- subagent start · ${event.childAgentId ?? event.toolName}
1677
- `);
1678
- } else if (event.type === "subagent-finish") {
1679
- process.stderr.write(`
1680
- subagent finish · ${event.childRun.agentId ?? event.childRun.toolName} · ${event.childRun.status}
1681
- `);
1682
- }
1683
- };
1684
-
1685
156
  // src/cli/setup.ts
1686
157
  var providersDocument = (env = process.env) => ({
1687
158
  schemaVersion: CLI_JSON_SCHEMA_VERSION,
@@ -1763,7 +234,7 @@ var initializeCli = async (options, context = {}) => {
1763
234
  process.stdout.write([
1764
235
  `${options.updateProfile ? "Updated" : "Created"} personal profile ${profileName} at ${created.path}.`,
1765
236
  `Provider: ${created.profile.provider} · Model: ${created.profile.model} · Reasoning: ${created.profile.reasoningEffort ?? "default"}`,
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.`,
237
+ provider === "vertex" ? "Vertex uses host ADC. Set GOOGLE_CLOUD_PROJECT and VERTEX_LOCATION; route presence does not verify credentials or model access." : 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.`,
1767
238
  "Next:",
1768
239
  ` ${doctorCommand}`,
1769
240
  ` ${runCommand}`,
@@ -1781,7 +252,7 @@ var listProviders = (json) => {
1781
252
  return;
1782
253
  }
1783
254
  for (const provider of document.providers) {
1784
- process.stdout.write(`${provider.id.padEnd(7)} ${provider.defaultModel.padEnd(22)} ${provider.support.padEnd(11)} ${provider.configured ? "configured" : `missing ${provider.credentialNames.join("/")}`}
255
+ process.stdout.write(`${provider.id.padEnd(7)} ${provider.defaultModel.padEnd(22)} ${provider.support.padEnd(11)} ${provider.id === "vertex" ? provider.configured ? "route configured; ADC not checked" : "missing GOOGLE_CLOUD_PROJECT/VERTEX_LOCATION" : provider.configured ? "configured" : `missing ${provider.credentialNames.join("/")}`}
1785
256
  `);
1786
257
  }
1787
258
  };
@@ -1801,13 +272,25 @@ Everyday commands:
1801
272
  zhx chat [options] [--continue|--session <id>]
1802
273
  zhx providers [--json]
1803
274
  zhx doctor [options] [--json]
275
+ zhx policy [options] [--json]
1804
276
 
1805
277
  Advanced operations and automation:
1806
278
  zhx resume [options] <runId> --approve|--deny
1807
279
  zhx runs list [--status <status>] [--limit <n>] [--json]
1808
280
  zhx sessions list|inspect|rename|fork|archive
1809
281
  zhx sessions list --search <literal title or session ID>
282
+ zhx checkpoints storage
283
+ zhx checkpoints prune-review <checkpoint:id|restore:id> [...]
284
+ zhx checkpoints prune-apply <reviewedPlanId> <checkpoint:id|restore:id> [...]
285
+ zhx checkpoints list <sessionId>
286
+ zhx checkpoints capture <sessionId> <turnId> <path> [paths...]
287
+ zhx checkpoints inspect <checkpointId>
288
+ zhx checkpoints prepare <checkpointId>
289
+ zhx checkpoints review <operationId>
290
+ zhx checkpoints apply <operationId> <reviewedProposalId>
291
+ zhx checkpoints recover <operationId> <existingForkSessionId>
1810
292
  zhx run --pricing-file <prices.json> --usage-limit-usd <amount> <task>
293
+ zhx run --tool-policy <absolute-policy.json> <task>
1811
294
  zhx changes create <input.json> --patch <artifact>
1812
295
  zhx changes verify <envelope.json> --patch <artifact> [--preconditions <file>]
1813
296
  zhx state status
@@ -1905,376 +388,10 @@ Credentials:
1905
388
  Qwen: DASHSCOPE_API_KEY or QWEN_API_KEY
1906
389
  Gemini: GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY`;
1907
390
 
1908
- // src/cli/qwen-connection.ts
1909
- var regions = {
1910
- singapore: ["Singapore", "ap-southeast-1", "dashscope-intl.aliyuncs.com"],
1911
- beijing: ["Beijing", "cn-beijing", "dashscope.aliyuncs.com"],
1912
- "hong-kong": ["Hong Kong", "cn-hongkong", "cn-hongkong.dashscope.aliyuncs.com"],
1913
- tokyo: ["Tokyo", "ap-northeast-1", ""],
1914
- frankfurt: ["Frankfurt", "eu-central-1", ""],
1915
- virginia: ["Virginia", "us-east-1", ""]
1916
- };
1917
- function qwenEndpoint(connection) {
1918
- if (connection.service === "token-plan")
1919
- return "https://token-plan.maas.qwencloudapi.com/compatible-mode/v1";
1920
- if (connection.service !== "api" || !Object.hasOwn(regions, connection.region))
1921
- throw new Error("Invalid Qwen connection.");
1922
- const [, region, legacy] = regions[connection.region];
1923
- if (connection.workspace !== undefined && !/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(connection.workspace))
1924
- throw new Error("Invalid Qwen workspace ID.");
1925
- const host = connection.workspace ? `${connection.workspace}.${region}.maas.aliyuncs.com` : legacy;
1926
- if (!host)
1927
- throw new Error("This Qwen region requires a workspace ID.");
1928
- return `https://${host}/compatible-mode/v1`;
1929
- }
1930
- function decodeQwenCredential(value) {
1931
- if (!value.startsWith("{"))
1932
- return { key: value, connection: { service: "api", region: "singapore" } };
1933
- const parsed = JSON.parse(value);
1934
- if (parsed.version !== 1 || typeof parsed.key !== "string" || !parsed.connection)
1935
- throw new Error("Invalid Qwen credential record.");
1936
- qwenEndpoint(parsed.connection);
1937
- return { key: parsed.key, connection: parsed.connection };
1938
- }
1939
- async function selectQwenConnection(input) {
1940
- const service = await input.select("Qwen / Service", [
1941
- { value: "api", label: "Standard API / Alibaba Cloud Model Studio", detail: "Use the region where your API key was created" },
1942
- { value: "token-plan", label: "QwenCloud Token Plan", detail: "Separate subscription endpoint and credential" }
1943
- ]);
1944
- if (!service)
1945
- return;
1946
- if (service === "token-plan")
1947
- return { service };
1948
- const region = await input.select("Qwen / API region", Object.entries(regions).map(([value, [label]]) => ({ value, label })));
1949
- if (!region)
1950
- return;
1951
- let workspace;
1952
- const dedicated = !regions[region][2] || await input.select("Qwen / Endpoint", [
1953
- { value: false, label: "DashScope endpoint", detail: "Existing regional API endpoint" },
1954
- { value: true, label: "Workspace endpoint", detail: "Enter your workspace ID" },
1955
- { value: "cancel", label: "Back" }
1956
- ]);
1957
- if (dedicated === undefined || dedicated === "cancel")
1958
- return;
1959
- if (dedicated) {
1960
- if (!input.question)
1961
- throw new Error("Workspace setup requires interactive input.");
1962
- workspace = (await input.question("Workspace ID (not the API key): ")).trim();
1963
- if (!workspace)
1964
- return;
1965
- }
1966
- const connection = { service: "api", region, ...workspace ? { workspace } : {} };
1967
- qwenEndpoint(connection);
1968
- return connection;
1969
- }
1970
-
1971
- // src/cli/cli-credentials.ts
1972
- class CredentialSetupError extends Error {
1973
- }
1974
- var credentialService = "ai.zhivex.harness.cli";
1975
- var validCredential = (value) => /^[\x21-\x7e]{1,8192}$/.test(value);
1976
- var nativeCredentialEntry = async (provider) => {
1977
- if (!PROVIDERS.includes(provider))
1978
- throw new Error("Unsupported credential provider.");
1979
- if (!["darwin", "linux"].includes(process.platform) || process.env.ZHIVEX_HARNESS_CREDENTIAL_STORE === "disabled") {
1980
- throw new Error("Secure credential storage is unavailable.");
1981
- }
1982
- const { AsyncEntry } = await import("@napi-rs/keyring");
1983
- const entry = new AsyncEntry(credentialService, provider, { linux: { store: "secret-service" } });
1984
- return {
1985
- getPassword: () => entry.getPassword(AbortSignal.timeout(30000)),
1986
- setPassword: (value) => entry.setPassword(value, AbortSignal.timeout(30000)),
1987
- deleteCredential: () => entry.deleteCredential(AbortSignal.timeout(30000))
1988
- };
1989
- };
1990
-
1991
- class CliCredentials {
1992
- entry;
1993
- environment;
1994
- write;
1995
- temporary = new Map;
1996
- revision = 0;
1997
- qwenManaged = false;
1998
- qwenOverrideAccepted = false;
1999
- constructor(entry = nativeCredentialEntry, environment = process.env, write = (text) => {
2000
- process.stderr.write(text);
2001
- }) {
2002
- this.entry = entry;
2003
- this.environment = environment;
2004
- this.write = write;
2005
- }
2006
- environmentKey(provider) {
2007
- return providerDescriptor(provider).credentialNames.map((name) => this.environment[name]?.trim()).find(Boolean);
2008
- }
2009
- validate(value) {
2010
- if (!validCredential(value))
2011
- throw new Error("Invalid API key: use 1–8192 printable characters without whitespace.");
2012
- return value;
2013
- }
2014
- async saved(provider) {
2015
- try {
2016
- const value = await (await this.entry(provider)).getPassword();
2017
- if (value == null)
2018
- return;
2019
- this.validate(provider === "qwen" ? decodeQwenCredential(value).key : value);
2020
- return value;
2021
- } catch {
2022
- throw new Error("Secure storage is unavailable or locked. Unlock it, use a temporary key, or configure the environment.");
2023
- }
2024
- }
2025
- destinations = new Map;
2026
- destination(provider) {
2027
- return this.destinations.get(provider) ?? "environment-defined or provider default";
2028
- }
2029
- sources = new Map;
2030
- source(provider) {
2031
- return this.sources.get(provider) ?? "missing";
2032
- }
2033
- endpointOverride(provider) {
2034
- const variables = {
2035
- openai: ["OPENAI_BASE_URL"],
2036
- meta: ["META_BASE_URL"],
2037
- gemini: ["GEMINI_BASE_URL"],
2038
- qwen: ["QWEN_BASE_URL", "QWEN_REGION", "QWEN_WORKSPACE_ID"]
2039
- };
2040
- return (variables[provider] ?? []).some((name) => this.environment[name]?.trim());
2041
- }
2042
- async inspect(provider) {
2043
- if (this.environmentKey(provider) && !(provider === "qwen" && this.qwenManaged))
2044
- return { source: "environment", configured: true };
2045
- if (this.endpointOverride(provider) && !(provider === "qwen" && this.qwenOverrideAccepted))
2046
- return { source: "blocked", configured: false };
2047
- if (this.temporary.has(provider))
2048
- return { source: "temporary", configured: true };
2049
- try {
2050
- return await this.saved(provider) ? { source: "keychain", configured: true } : { source: "missing", configured: false };
2051
- } catch {
2052
- return { source: "unavailable", configured: false };
2053
- }
2054
- }
2055
- async configure(provider, input) {
2056
- for (;; ) {
2057
- try {
2058
- return await this.configureOnce(provider, input);
2059
- } catch (error) {
2060
- if (!(error instanceof CredentialSetupError) && !(error instanceof Error && error.name === "AbortError"))
2061
- throw error;
2062
- this.write(error instanceof CredentialSetupError ? `${error.message}
2063
- ` : `Key entry cancelled.
2064
- `);
2065
- const action = await input.select("Credentials / Recovery", [
2066
- { value: "temporary", label: "Use a temporary key", detail: "Enter a key for this CLI session only" },
2067
- { value: "retry", label: "Retry credential setup" },
2068
- { value: "cancel", label: "Cancel setup" }
2069
- ]);
2070
- if (!action || action === "cancel")
2071
- return false;
2072
- if (action === "temporary") {
2073
- try {
2074
- return await this.configureOnce(provider, input, "temporary");
2075
- } catch (retryError) {
2076
- if (!(retryError instanceof CredentialSetupError) && !(retryError instanceof Error && retryError.name === "AbortError"))
2077
- throw retryError;
2078
- this.write(`Temporary key entry was not completed. Try again or go back.
2079
- `);
2080
- }
2081
- }
2082
- }
2083
- }
2084
- }
2085
- async configureOnce(provider, input, mode) {
2086
- if (provider !== "qwen" && this.environmentKey(provider)) {
2087
- this.write(`An environment key takes precedence. Remove it from the launching shell to use managed credentials.
2088
- `);
2089
- }
2090
- const action = mode ?? await input.select(`Credentials / ${provider}`, [
2091
- { value: "save", label: "Save or replace in system keychain" },
2092
- { value: "temporary", label: "Temporary key for this CLI session" },
2093
- { value: "delete", label: "Remove saved and temporary key" },
2094
- { value: "cancel", label: "Back" }
2095
- ]);
2096
- if (!action || action === "cancel")
2097
- return false;
2098
- if (action === "delete") {
2099
- const confirm = await input.select(`Remove managed key for ${provider}?`, [
2100
- { value: false, label: "Keep key" },
2101
- { value: true, label: "Remove key" }
2102
- ]);
2103
- if (!confirm)
2104
- return false;
2105
- try {
2106
- await (await this.entry(provider)).deleteCredential();
2107
- this.write(`Saved key removed (or already absent).
2108
- `);
2109
- } catch {
2110
- this.write(`Saved key could not be removed: secure storage is unavailable or locked.
2111
- `);
2112
- }
2113
- this.temporary.delete(provider);
2114
- if (provider === "qwen") {
2115
- this.qwenManaged = false;
2116
- this.qwenOverrideAccepted = false;
2117
- }
2118
- this.revision++;
2119
- this.write(`Temporary key cleared. Environment credentials are unchanged.
2120
- `);
2121
- return true;
2122
- }
2123
- const connection = provider === "qwen" ? await selectQwenConnection(input) : undefined;
2124
- if (provider === "qwen" && !connection)
2125
- return false;
2126
- if (connection)
2127
- this.write(`Selected Qwen destination: ${qwenEndpoint(connection)}
2128
- This connection will replace Qwen environment settings for this CLI session.
2129
- `);
2130
- let target;
2131
- if (action === "save") {
2132
- try {
2133
- target = await this.entry(provider);
2134
- } catch {
2135
- throw new CredentialSetupError("Secure storage is unavailable. Unlock it and retry, or choose temporary use.");
2136
- }
2137
- }
2138
- let secret = await input.secret("API key (hidden; Enter submits, Ctrl+C cancels): ");
2139
- if (!secret)
2140
- return false;
2141
- try {
2142
- secret = this.validate(secret);
2143
- } catch {
2144
- throw new CredentialSetupError("Invalid API key. Enter printable characters without whitespace.");
2145
- }
2146
- if (connection)
2147
- secret = JSON.stringify({ version: 1, key: secret, connection });
2148
- if (action === "save") {
2149
- try {
2150
- await target.setPassword(secret);
2151
- } catch {
2152
- throw new CredentialSetupError("API key was not saved: secure storage is unavailable or locked. No plaintext fallback was used.");
2153
- }
2154
- this.temporary.delete(provider);
2155
- this.write(`API key saved in the system keychain.
2156
- `);
2157
- } else {
2158
- this.temporary.set(provider, secret);
2159
- this.write(`API key is available only for this CLI session.
2160
- `);
2161
- }
2162
- if (connection) {
2163
- this.qwenManaged = true;
2164
- this.qwenOverrideAccepted = true;
2165
- }
2166
- this.revision++;
2167
- return true;
2168
- }
2169
- async providerEnvironment(provider, input) {
2170
- if (this.environmentKey(provider) && !(provider === "qwen" && this.qwenManaged)) {
2171
- this.destinations.delete(provider);
2172
- this.sources.set(provider, "environment");
2173
- return { ...this.environment };
2174
- }
2175
- if (provider !== "qwen" && this.endpointOverride(provider)) {
2176
- throw new CredentialSetupError("Managed keys require the provider's default endpoint. Remove endpoint overrides or supply an explicit environment key.");
2177
- }
2178
- let key = this.temporary.get(provider);
2179
- if (!key) {
2180
- try {
2181
- key = await this.saved(provider);
2182
- } catch {
2183
- this.write(`Secure storage is unavailable or locked; temporary use remains available.
2184
- `);
2185
- }
2186
- }
2187
- if (!key) {
2188
- this.write(`No API key available for ${provider}.
2189
- `);
2190
- if (!await this.configure(provider, input))
2191
- throw new CredentialSetupError("Credential setup cancelled. No provider request was sent.");
2192
- key = this.temporary.get(provider) ?? await this.saved(provider);
2193
- }
2194
- if (!key)
2195
- throw new CredentialSetupError("No API key available. No provider request was sent.");
2196
- if (provider === "qwen") {
2197
- const record = decodeQwenCredential(key);
2198
- const endpoint = qwenEndpoint(record.connection);
2199
- if (this.endpointOverride(provider) && !this.qwenOverrideAccepted) {
2200
- const useManaged = await input.select("Qwen / Environment conflicts with saved connection", [
2201
- { value: false, label: "Cancel; keep environment settings" },
2202
- { value: true, label: "Use saved connection for this session", detail: endpoint }
2203
- ]);
2204
- if (!useManaged)
2205
- throw new CredentialSetupError("Qwen connection selection cancelled. No provider request was sent.");
2206
- this.qwenOverrideAccepted = true;
2207
- }
2208
- const env = { ...this.environment };
2209
- for (const name2 of ["QWEN_BASE_URL", "QWEN_REGION", "QWEN_WORKSPACE_ID", "QWEN_API_KEY", "DASHSCOPE_API_KEY"])
2210
- delete env[name2];
2211
- this.destinations.set(provider, endpoint);
2212
- env.QWEN_BASE_URL = endpoint;
2213
- env.DASHSCOPE_API_KEY = record.key;
2214
- this.sources.set(provider, this.temporary.has(provider) ? "temporary" : "keychain");
2215
- return env;
2216
- }
2217
- this.sources.set(provider, this.temporary.has(provider) ? "temporary" : "keychain");
2218
- const name = providerDescriptor(provider).credentialNames[0];
2219
- return { ...this.environment, [name]: key };
2220
- }
2221
- clear() {
2222
- this.destinations.clear();
2223
- this.temporary.clear();
2224
- this.sources.clear();
2225
- this.qwenManaged = false;
2226
- this.qwenOverrideAccepted = false;
2227
- }
2228
- }
2229
- async function credentialModel(config, env, modelFactory) {
2230
- const { createProviderModel: createProviderModel2 } = await import("./config-1k9t4jhh.js");
2231
- const { wrapLanguageModel } = await import("@zhivex-ai/core");
2232
- const secrets = providerDescriptor(config.provider).credentialNames.map((name) => env[name]).filter((value) => !!value);
2233
- const safeError = (error) => {
2234
- let message = error instanceof Error ? error.message : "Provider request failed.";
2235
- for (const secret of secrets)
2236
- message = message.split(secret).join("[REDACTED]");
2237
- const safe = new Error(message);
2238
- if (error instanceof Error && error.name === "AbortError")
2239
- safe.name = "AbortError";
2240
- return safe;
2241
- };
2242
- const model = (modelFactory ?? createProviderModel2)(config, env);
2243
- return wrapLanguageModel(model, [{
2244
- name: "cli-credential-errors",
2245
- wrapGenerate: async (_context, next) => {
2246
- try {
2247
- return await next();
2248
- } catch (error) {
2249
- throw safeError(error);
2250
- }
2251
- },
2252
- wrapStream: async (_context, next) => {
2253
- let events;
2254
- try {
2255
- events = await next();
2256
- } catch (error) {
2257
- throw safeError(error);
2258
- }
2259
- return async function* () {
2260
- try {
2261
- for await (const event of events) {
2262
- if (event.type === "error")
2263
- throw safeError(event.error);
2264
- yield event;
2265
- }
2266
- } catch (error) {
2267
- throw safeError(error);
2268
- }
2269
- }();
2270
- }
2271
- }]);
2272
- }
2273
-
2274
391
  // src/cli/doctor.ts
2275
- import { constants as fsConstants2 } from "node:fs";
2276
- import { access, lstat as lstat2, stat } from "node:fs/promises";
2277
- import path2 from "node:path";
392
+ import { constants as fsConstants } from "node:fs";
393
+ import { access, lstat, stat } from "node:fs/promises";
394
+ import path from "node:path";
2278
395
 
2279
396
  // src/cli/errors.ts
2280
397
  var cliRecoveryHint = (error, argv) => {
@@ -2348,7 +465,7 @@ var inspectWorkspace = async (workspace) => {
2348
465
  if (!workspaceStat.isDirectory()) {
2349
466
  return diagnostic("workspace", "fail", "Workspace is not a directory.", { path: workspace });
2350
467
  }
2351
- await access(workspace, fsConstants2.R_OK);
468
+ await access(workspace, fsConstants.R_OK);
2352
469
  return diagnostic("workspace", "pass", "Workspace exists and is readable.", { path: workspace });
2353
470
  } catch (error) {
2354
471
  const code = error.code ?? "UNKNOWN";
@@ -2378,7 +495,7 @@ var inspectGit = async (workspace) => {
2378
495
  }
2379
496
  };
2380
497
  var inspectScripts = async (workspace, allowedChecks) => {
2381
- const packagePath = path2.join(workspace, "package.json");
498
+ const packagePath = path.join(workspace, "package.json");
2382
499
  try {
2383
500
  const packageFile = await readRegularFileNoFollow(packagePath, {
2384
501
  label: "package.json",
@@ -2407,17 +524,17 @@ var inspectScripts = async (workspace, allowedChecks) => {
2407
524
  }
2408
525
  };
2409
526
  var isInsidePath = (root, candidate) => {
2410
- const relative = path2.relative(root, candidate);
2411
- return relative === "" || !relative.startsWith(`..${path2.sep}`) && relative !== ".." && !path2.isAbsolute(relative);
527
+ const relative = path.relative(root, candidate);
528
+ return relative === "" || !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative);
2412
529
  };
2413
530
  var containsSymlink = async (candidate, trustedPrefix) => {
2414
- const parsed = path2.parse(candidate);
2415
- const parts = trustedPrefix ? path2.relative(trustedPrefix, candidate).split(path2.sep).filter(Boolean) : candidate.slice(parsed.root.length).split(path2.sep).filter(Boolean).slice(1);
2416
- let current = trustedPrefix ?? path2.join(parsed.root, candidate.slice(parsed.root.length).split(path2.sep).filter(Boolean)[0] ?? "");
531
+ const parsed = path.parse(candidate);
532
+ const parts = trustedPrefix ? path.relative(trustedPrefix, candidate).split(path.sep).filter(Boolean) : candidate.slice(parsed.root.length).split(path.sep).filter(Boolean).slice(1);
533
+ let current = trustedPrefix ?? path.join(parsed.root, candidate.slice(parsed.root.length).split(path.sep).filter(Boolean)[0] ?? "");
2417
534
  for (const part of parts) {
2418
- current = path2.join(current, part);
535
+ current = path.join(current, part);
2419
536
  try {
2420
- if ((await lstat2(current)).isSymbolicLink()) {
537
+ if ((await lstat(current)).isSymbolicLink()) {
2421
538
  return true;
2422
539
  }
2423
540
  } catch (error) {
@@ -2438,7 +555,7 @@ var nearestExistingPath = async (candidate) => {
2438
555
  if (error.code !== "ENOENT") {
2439
556
  throw error;
2440
557
  }
2441
- const parent = path2.dirname(current);
558
+ const parent = path.dirname(current);
2442
559
  if (parent === current) {
2443
560
  throw error;
2444
561
  }
@@ -2454,8 +571,8 @@ var inspectStateDirectory = async (workspace, stateDirectory) => {
2454
571
  });
2455
572
  }
2456
573
  if (isInsidePath(workspace, stateDirectory)) {
2457
- const segments2 = path2.relative(workspace, stateDirectory).split(path2.sep).filter(Boolean);
2458
- const sensitiveSegment = segments2.find(isSensitiveStateSegment);
574
+ const segments = path.relative(workspace, stateDirectory).split(path.sep).filter(Boolean);
575
+ const sensitiveSegment = segments.find(isSensitiveStateSegment);
2459
576
  if (sensitiveSegment) {
2460
577
  return diagnostic("state-directory", "fail", "State directory is inside a sensitive workspace path.", {
2461
578
  path: stateDirectory,
@@ -2479,7 +596,7 @@ var inspectStateDirectory = async (workspace, stateDirectory) => {
2479
596
  writable: false
2480
597
  });
2481
598
  }
2482
- await access(existing.path, fsConstants2.R_OK | fsConstants2.W_OK);
599
+ await access(existing.path, fsConstants.R_OK | fsConstants.W_OK);
2483
600
  return diagnostic("state-directory", "pass", "State directory is safe and writable.", {
2484
601
  path: stateDirectory,
2485
602
  exists: existing.path === stateDirectory,
@@ -2501,9 +618,9 @@ var inspectOperationsStore = async (stateDirectory, storeBackend) => {
2501
618
  migrationAvailable: true
2502
619
  });
2503
620
  }
2504
- const databasePath = path2.join(stateDirectory, "operations.sqlite");
621
+ const databasePath = path.join(stateDirectory, "operations.sqlite");
2505
622
  try {
2506
- const entry = await lstat2(databasePath);
623
+ const entry = await lstat(databasePath);
2507
624
  if (entry.isSymbolicLink() || !entry.isFile()) {
2508
625
  return diagnostic("operations-store", "fail", "SQLite state path is not a regular file.", {
2509
626
  backend: storeBackend,
@@ -2618,7 +735,7 @@ var inspectProjectContext = async (config) => {
2618
735
  }
2619
736
  try {
2620
737
  const workspace = await Workspace.open(config.workspace);
2621
- const manifestPath = path2.relative(config.workspace, config.context.configPath).split(path2.sep).join("/");
738
+ const manifestPath = path.relative(config.workspace, config.context.configPath).split(path.sep).join("/");
2622
739
  const bundle = await loadHarnessProjectContext(workspace, {
2623
740
  manifestPath,
2624
741
  requireManifest: manifestPath !== DEFAULT_HARNESS_CONTEXT_MANIFEST
@@ -2707,7 +824,7 @@ var createDoctorReport = async (options = {}, context = {}) => {
2707
824
  const credential = selected ? context.credentialStatus : undefined;
2708
825
  const configured = credential?.configured ?? provider.configured;
2709
826
  const status = invalidRegion || invalidEndpoint || !configured ? selected ? "fail" : "warn" : provider.support === "provisional" ? "warn" : "pass";
2710
- const message = invalidEndpoint ? `${provider.name} custom endpoint configuration is invalid.` : invalidRegion ? "Qwen region configuration is invalid." : credential ? credential.configured ? `${provider.name} credential present (${credential.source}); account access has not been checked.${provider.support === "provisional" ? " Live support is provisional." : ""}` : credential.source === "blocked" ? `${provider.name} managed credentials require the default endpoint. Remove endpoint overrides or provide an environment key.` : `${provider.name} credential ${credential.source === "unavailable" ? "could not be checked: keychain unavailable or locked" : "is missing"}. Run zhx to configure a key, or set ${provider.credentialNames.join(" or ")} for automation.` : provider.configured ? provider.support === "provisional" ? `${provider.name} credentials are present, but live support is provisional.` : `${provider.name} credentials are present.` : `${provider.name} credentials are missing. Set ${provider.credentialNames.join(" or ")} in the environment and rerun zhx doctor.`;
827
+ const message = provider.id === "vertex" ? configured ? "Vertex project/location configured; host ADC and model access have not been checked. Live support is provisional." : "Vertex requires GOOGLE_CLOUD_PROJECT and VERTEX_LOCATION plus host Application Default Credentials. No API key is stored by zhx." : invalidEndpoint ? `${provider.name} custom endpoint configuration is invalid.` : invalidRegion ? "Qwen region configuration is invalid." : credential ? credential.configured ? `${provider.name} credential present (${credential.source}); account access has not been checked.${provider.support === "provisional" ? " Live support is provisional." : ""}` : credential.source === "blocked" ? `${provider.name} managed credentials require the default endpoint. Remove endpoint overrides or provide an environment key.` : `${provider.name} credential ${credential.source === "unavailable" ? "could not be checked: keychain unavailable or locked" : "is missing"}. Run zhx to configure a key, or set ${provider.credentialNames.join(" or ")} for automation.` : provider.configured ? provider.support === "provisional" ? `${provider.name} credentials are present, but live support is provisional.` : `${provider.name} credentials are present.` : `${provider.name} credentials are missing. Set ${provider.credentialNames.join(" or ")} in the environment and rerun zhx doctor.`;
2711
828
  checks.push(diagnostic(`provider:${provider.id}`, status, message, {
2712
829
  provider: provider.id,
2713
830
  selected,
@@ -2911,16 +1028,16 @@ var formatConsoleDiff = (diff, color) => diff.split(`
2911
1028
  class ConsoleAttachments {
2912
1029
  files = new Map;
2913
1030
  list() {
2914
- return [...this.files.values()].map(({ path: path3, digest, truncated }) => ({ path: path3, digest, truncated }));
1031
+ return [...this.files.values()].map(({ path: path2, digest, truncated }) => ({ path: path2, digest, truncated }));
2915
1032
  }
2916
1033
  clear() {
2917
1034
  this.files.clear();
2918
1035
  }
2919
- remove(path3) {
2920
- return this.files.delete(path3);
1036
+ remove(path2) {
1037
+ return this.files.delete(path2);
2921
1038
  }
2922
- async add(workspace, path3) {
2923
- const file = await workspace.readFile(path3);
1039
+ async add(workspace, path2) {
1040
+ const file = await workspace.readFile(path2);
2924
1041
  const next = new Map(this.files).set(file.path, file);
2925
1042
  if (next.size > 8 || [...next.values()].reduce((sum, value) => sum + Buffer.byteLength(value.content), 0) > 64 * 1024) {
2926
1043
  throw new Error("Attachments exceed 8 files or 64 KiB; use a smaller selection.");
@@ -2944,7 +1061,7 @@ ${JSON.stringify([...this.files.values()])}`;
2944
1061
  }
2945
1062
 
2946
1063
  // src/cli/console.ts
2947
- import { randomUUID as randomUUID2 } from "node:crypto";
1064
+ import { randomUUID } from "node:crypto";
2948
1065
 
2949
1066
  // src/cli/session-management.ts
2950
1067
  var openSessionStoreForConfig = (config) => openCliSessionStore({
@@ -3060,76 +1177,6 @@ var updateIndexedSessionRun = async (config, runId, status) => {
3060
1177
  }
3061
1178
  };
3062
1179
 
3063
- // src/cli/routing.ts
3064
- var resolvedRouting = (options) => {
3065
- return resolveHarnessModelRoutes(options.routes);
3066
- };
3067
- var assertRoutePricingIsSafe = (routes, costBudget) => {
3068
- if (routes.size > 0 && costBudget !== undefined) {
3069
- throw new CliUsageError("Cost budgets cannot be combined with model routes until pricing is configured per role.");
3070
- }
3071
- };
3072
- var withTemporaryHarnessProfiles = async (profiles, configure, operation) => {
3073
- await configure(profiles);
3074
- try {
3075
- return await operation();
3076
- } finally {
3077
- await configure([]);
3078
- }
3079
- };
3080
-
3081
- // src/cli/configured-harness.ts
3082
- import path3 from "node:path";
3083
- var createConfiguredHarness = async (options, extraProfiles = [], persistedRoutes, credentials) => {
3084
- const routes = persistedRoutes ?? resolvedRouting(options);
3085
- const resolvedConfig = resolveHarnessConfig(options);
3086
- if (options.usageLimitUsd !== undefined && resolvedConfig.costBudget)
3087
- throw new CliUsageError("Choose one monetary policy: --usage-limit-usd or the legacy cost budget.");
3088
- assertRoutePricingIsSafe(routes, resolvedConfig.costBudget);
3089
- const profiles = [...new Set([
3090
- ...options.subagentProfiles ?? [],
3091
- ...extraProfiles,
3092
- ...routes.keys()
3093
- ])];
3094
- const quietTelemetry = options.json || options.jsonl;
3095
- const providerEnv = credentials ? await credentials.store.providerEnvironment(resolvedConfig.provider, credentials.input) : undefined;
3096
- const transportEnv = { ...process.env };
3097
- const bindTransport = (env) => {
3098
- for (const name of ["QWEN_BASE_URL", "QWEN_REGION", "QWEN_WORKSPACE_ID"]) {
3099
- if (env[name] === undefined)
3100
- delete transportEnv[name];
3101
- else
3102
- transportEnv[name] = env[name];
3103
- }
3104
- };
3105
- if (providerEnv && resolvedConfig.provider === "qwen")
3106
- bindTransport(providerEnv);
3107
- const routeModels = {};
3108
- for (const [role, route] of routes) {
3109
- const routeEnv = credentials ? await credentials.store.providerEnvironment(route.provider, credentials.input) : process.env;
3110
- if (route.provider === "qwen")
3111
- bindTransport(routeEnv);
3112
- routeModels[role] = credentials ? await credentialModel(route, routeEnv) : createProviderModel(route, routeEnv);
3113
- }
3114
- const compactionEnv = resolvedConfig.compaction.model && credentials ? await credentials.store.providerEnvironment(resolvedConfig.compaction.model.provider, credentials.input) : undefined;
3115
- if (compactionEnv && resolvedConfig.compaction.model?.provider === "qwen")
3116
- bindTransport(compactionEnv);
3117
- const compactionModelInstance = resolvedConfig.compaction.model && compactionEnv ? await credentialModel(resolvedConfig.compaction.model, compactionEnv) : undefined;
3118
- const harness = await createHarness({
3119
- ...options,
3120
- usageAccounting: {
3121
- ...options.pricingFile ? { pricing: usagePricingSchema.parse(JSON.parse((await readRegularFileNoFollow(path3.resolve(options.pricingFile), { maxBytes: 128 * 1024, label: "Usage pricing" })).contents.toString("utf8"))) } : {},
3122
- ...options.usageLimitUsd !== undefined ? { limitUsd: options.usageLimitUsd } : {}
3123
- },
3124
- subagentProfiles: profiles,
3125
- subagentModels: routeModels,
3126
- ...compactionModelInstance ? { compactionModelInstance } : {},
3127
- ...providerEnv ? { env: transportEnv, modelInstance: await credentialModel(resolvedConfig, providerEnv) } : {},
3128
- onTelemetryEvent: orchestrationObserver(quietTelemetry)
3129
- });
3130
- return { harness, routes };
3131
- };
3132
-
3133
1180
  // src/models/compaction-recommendations.ts
3134
1181
  import { recommendAuxiliaryModel } from "@zhivex-ai/core";
3135
1182
 
@@ -3328,7 +1375,7 @@ async function handleConsoleCompaction(command, deps) {
3328
1375
  const provider = value.slice(0, separator);
3329
1376
  const model = value.slice(separator + 1);
3330
1377
  if (separator < 1 || !PROVIDERS.includes(provider) || !/^[a-zA-Z0-9][a-zA-Z0-9._:/-]{0,159}$/.test(model)) {
3331
- write("Use /compaction provider:model with openai, qwen, gemini or meta; /compaction off disables model-assisted compaction.");
1378
+ write("Use /compaction provider:model with openai, qwen, gemini, meta, anthropic or vertex; /compaction off disables model-assisted compaction.");
3332
1379
  return true;
3333
1380
  }
3334
1381
  await deps.replaceOptions({ ...deps.options, compactionProvider: provider, compactionModel: model });
@@ -3427,7 +1474,7 @@ Stopping the active operation; waiting for durable state and cleanup.
3427
1474
  const consoleApprovals = async (approvals, context) => {
3428
1475
  readline.stopBackground();
3429
1476
  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);
1477
+ return await terminalApprovalResolver(options.approvalMode ?? options.yes, (question) => readline.question(question), { select: (title, items) => readline.select(title, items), workspace: harness.config.workspace, sessionGrants }, harness)(approvals, context);
3431
1478
  } finally {
3432
1479
  if (activeController && !activeController.signal.aborted)
3433
1480
  readline.startBackground();
@@ -3516,14 +1563,37 @@ Stopping the active operation; waiting for durable state and cleanup.
3516
1563
  session = await sessionStore.updateRun(session.sessionId, state.runId, { status: "running" });
3517
1564
  let result;
3518
1565
  try {
3519
- result = await abortable((abortSignal) => runHarness(harness, {
3520
- state,
3521
- toolExecution: { ...harnessToolExecution },
3522
- abortSignal,
3523
- approvals: approvalResponses(state.pendingApprovals, approve, approve ? "Approved inline in chat." : "Denied inline in chat.")
3524
- }, {
3525
- onEvent,
3526
- resolveApprovals: consoleApprovals
1566
+ result = await abortable((abortSignal) => observeCliPolicy({}, tracker, async () => {
1567
+ for (const pending of state.pendingApprovals) {
1568
+ await publishPendingPolicyDecision(harness, pending.name, pending.kind === "subagent");
1569
+ }
1570
+ let approvals;
1571
+ if (approve && requiresExplicitHostReview(harness)) {
1572
+ readline.stopBackground();
1573
+ try {
1574
+ approvals = await terminalApprovalResolver("ask", (question) => readline.question(question), {
1575
+ select: (title, items) => readline.select(title, items),
1576
+ workspace: harness.config.workspace,
1577
+ sessionGrants
1578
+ }, harness)(state.pendingApprovals, state);
1579
+ } finally {
1580
+ if (!abortSignal.aborted)
1581
+ readline.startBackground();
1582
+ }
1583
+ if (!approvals)
1584
+ throw new Error("EXPLICIT_REVIEW_REQUIRED");
1585
+ } else {
1586
+ approvals = issueObservedApprovalResponses(harness, state, approvalResponses(state.pendingApprovals, approve, approve ? "Approved inline in chat." : "Denied inline in chat."), "cli-console-command", state.pendingApprovals.map(() => "interactive"));
1587
+ }
1588
+ return runHarness(harness, {
1589
+ state,
1590
+ toolExecution: { ...harnessToolExecution },
1591
+ abortSignal,
1592
+ approvals
1593
+ }, {
1594
+ onEvent,
1595
+ resolveApprovals: consoleApprovals
1596
+ });
3527
1597
  }));
3528
1598
  } catch {
3529
1599
  flushToolActivity(tracker);
@@ -4084,7 +2154,7 @@ ${sanitizeTerminalText(prompt)}
4084
2154
  }
4085
2155
  readline.rememberPrompt(prompt, literalInput || command === "/paste");
4086
2156
  prompt = await attachments.prompt(harness.workspace, prompt);
4087
- const runId = `run_${randomUUID2()}`;
2157
+ const runId = `run_${randomUUID()}`;
4088
2158
  session = await sessionStore.appendRun(session.sessionId, {
4089
2159
  runId,
4090
2160
  provider: harness.config.provider,
@@ -4097,7 +2167,7 @@ ${sanitizeTerminalText(prompt)}
4097
2167
  let markedRunning = false;
4098
2168
  let result;
4099
2169
  try {
4100
- result = await abortable((abortSignal) => runHarness(harness, messages.length === 0 ? {
2170
+ result = await abortable((abortSignal) => observeCliPolicy({}, tracker, () => runHarness(harness, messages.length === 0 ? {
4101
2171
  runId,
4102
2172
  toolExecution: { ...harnessToolExecution },
4103
2173
  abortSignal: AbortSignal.any([abortSignal, progress.signal]),
@@ -4143,7 +2213,7 @@ ${sanitizeTerminalText(prompt)}
4143
2213
  await onEvent(event);
4144
2214
  },
4145
2215
  resolveApprovals: consoleApprovals
4146
- }));
2216
+ })));
4147
2217
  } catch (error) {
4148
2218
  flushToolActivity(tracker);
4149
2219
  tracker.markdown?.flush();
@@ -4214,7 +2284,7 @@ var runOnce = async (options) => {
4214
2284
  const tracker = { streamedText: false };
4215
2285
  let closeAttempted = false;
4216
2286
  try {
4217
- const result = await runHarness(harness, {
2287
+ const result = await observeCliPolicy(options, tracker, () => runHarness(harness, {
4218
2288
  prompt: options.prompt,
4219
2289
  toolExecution: { ...harnessToolExecution },
4220
2290
  scope: harness.config.scope,
@@ -4222,8 +2292,8 @@ var runOnce = async (options) => {
4222
2292
  ...options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}
4223
2293
  }, {
4224
2294
  onEvent: streamSink(options, tracker),
4225
- resolveApprovals: terminalApprovalResolver(options.approvalMode ?? options.yes)
4226
- });
2295
+ resolveApprovals: terminalApprovalResolver(options.approvalMode ?? options.yes, undefined, undefined, harness)
2296
+ }));
4227
2297
  closeAttempted = true;
4228
2298
  await harness.close();
4229
2299
  printTerminalResult(result, harness, options, tracker);
@@ -4362,13 +2432,21 @@ ${summarizeApproval(approval)}
4362
2432
  const tracker = { streamedText: false };
4363
2433
  let harnessCloseAttempted = false;
4364
2434
  try {
4365
- const result = await runHarness(harness, {
4366
- state,
4367
- toolExecution: { ...harnessToolExecution },
4368
- approvals: approvalResponses(state.pendingApprovals, approve, approve ? "Approved by resume --approve." : "Denied by resume --deny.")
4369
- }, {
4370
- onEvent: streamSink(options, tracker),
4371
- resolveApprovals: terminalApprovalResolver(approve ? "auto" : "restricted")
2435
+ const result = await observeCliPolicy(options, tracker, async () => {
2436
+ for (const pending of state.pendingApprovals)
2437
+ await publishPendingPolicyDecision(harness, pending.name, pending.kind === "subagent");
2438
+ const explicitReview = requiresExplicitHostReview(harness);
2439
+ const resumeApprovals = approve && explicitReview ? await terminalApprovalResolver("ask", undefined, undefined, harness)(state.pendingApprovals, state) : issueAutomaticApprovalResponses(harness, state, approvalResponses(state.pendingApprovals, approve, approve ? "Approved by resume --approve." : "Denied by resume --deny."), "cli-resume");
2440
+ if (!resumeApprovals)
2441
+ throw new CliUsageError("EXPLICIT_REVIEW_REQUIRED: resume needs an interactive review channel; operation remains pending.");
2442
+ return runHarness(harness, {
2443
+ state,
2444
+ toolExecution: { ...harnessToolExecution },
2445
+ approvals: resumeApprovals
2446
+ }, {
2447
+ onEvent: streamSink(options, tracker),
2448
+ resolveApprovals: terminalApprovalResolver(approve ? explicitReview ? "ask" : "auto" : "restricted", undefined, undefined, harness)
2449
+ });
4372
2450
  });
4373
2451
  await updateIndexedSessionRun(harness.config, result.state.runId, result.status);
4374
2452
  harnessCloseAttempted = true;
@@ -4396,6 +2474,505 @@ ${summarizeApproval(approval)}
4396
2474
  }
4397
2475
  };
4398
2476
 
2477
+ // src/cli/governance-report.ts
2478
+ import path2 from "node:path";
2479
+
2480
+ // src/persistence/governance-report.ts
2481
+ import { createHash as createHash3 } from "node:crypto";
2482
+ import { z as z2 } from "zod";
2483
+
2484
+ // src/persistence/governance-context.ts
2485
+ import { createHash as createHash2 } from "node:crypto";
2486
+ import { z } from "zod";
2487
+ var digest = z.string().regex(/^sha256:[a-f0-9]{64}$/);
2488
+ var count = z.number().int().nonnegative().safe();
2489
+ var governanceReference = (kind, value) => "sha256:" + createHash2("sha256").update(JSON.stringify([kind, value])).digest("hex");
2490
+ var governanceSessionSchema = z.strictObject({ availability: z.enum(["recorded", "unavailable"]), reference: digest.nullable(), revision: count.nullable() });
2491
+ var governanceHistorySchema = z.strictObject({
2492
+ availability: z.enum(["recorded", "unavailable"]),
2493
+ retention: z.enum(["unknown", "retained", "expired"]),
2494
+ incomplete: z.boolean(),
2495
+ policyDecisions: z.array(z.strictObject({
2496
+ runReference: digest,
2497
+ sequence: count,
2498
+ toolReference: digest,
2499
+ phase: z.enum(["approval-request", "tool-entry"]),
2500
+ decision: z.enum(["allow", "ask_user", "deny"]),
2501
+ policyDigest: digest.nullable(),
2502
+ source: z.enum(["operator-file", "application", "baseline"]),
2503
+ ruleReferences: z.array(digest).max(128),
2504
+ explicitReviewRequired: z.boolean()
2505
+ })).max(512)
2506
+ });
2507
+ var governanceEnvelopeSchema = z.strictObject({
2508
+ source: z.literal("caller-supplied"),
2509
+ envelopeId: digest.nullable(),
2510
+ patchReference: digest.nullable(),
2511
+ patchDigest: digest.nullable(),
2512
+ linkedRunReferences: z.array(digest).max(64),
2513
+ schemaValid: z.boolean(),
2514
+ integrity: z.enum(["valid", "invalid"]),
2515
+ expiration: z.enum(["unknown", "current", "expired", "not-yet-valid"]),
2516
+ checkedAt: z.iso.datetime(),
2517
+ expiredApprovals: count,
2518
+ authenticity: z.literal("not-verified"),
2519
+ patchBytes: z.literal("not-verified")
2520
+ });
2521
+ async function readGovernanceSession(source, rootRunId, runIds) {
2522
+ const session = { availability: "unavailable", reference: null, revision: null };
2523
+ const history = { availability: "unavailable", retention: "unknown", incomplete: true, policyDecisions: [] };
2524
+ if (!source)
2525
+ return { session, history, verify: async () => {} };
2526
+ const initial = await source.index.get(source.id);
2527
+ if (!initial || initial.sessionId !== source.id || !initial.runs.some((run) => run.runId === rootRunId))
2528
+ throw new Error("GOVERNANCE_SESSION_MISMATCH");
2529
+ const captured = JSON.stringify(initial);
2530
+ session.availability = "recorded";
2531
+ session.reference = governanceReference("session", initial.sessionId);
2532
+ session.revision = initial.revision;
2533
+ const pages = [];
2534
+ if (source.history) {
2535
+ let cursor = 0;
2536
+ history.availability = "recorded";
2537
+ history.retention = "retained";
2538
+ history.incomplete = false;
2539
+ for (let pageIndex = 0;pageIndex < 50; pageIndex++) {
2540
+ const page = source.history.replay(source.id, cursor);
2541
+ pages.push({ cursor, digest: governanceReference("history-page", JSON.stringify(page)) });
2542
+ if (page.schemaVersion !== 1 || !Number.isSafeInteger(page.nextCursor) || page.nextCursor < cursor || page.events.length > 200)
2543
+ throw new Error("GOVERNANCE_HISTORY_INVALID");
2544
+ if (page.cursorExpired || page.policyEvidenceIncomplete) {
2545
+ history.retention = "expired";
2546
+ history.incomplete = true;
2547
+ }
2548
+ let lastSequence = cursor;
2549
+ for (const event of page.events) {
2550
+ if (event.sessionId !== source.id || !Number.isSafeInteger(event.sequence) || event.sequence <= lastSequence || event.sequence > page.nextCursor)
2551
+ throw new Error("GOVERNANCE_HISTORY_INVALID");
2552
+ lastSequence = event.sequence;
2553
+ if (!runIds.has(event.runId) || event.activity.type !== "policy-decision")
2554
+ continue;
2555
+ const parsed = harnessPolicyDecisionEventSchema.safeParse(event.activity);
2556
+ if (!parsed.success) {
2557
+ history.incomplete = true;
2558
+ continue;
2559
+ }
2560
+ if (history.policyDecisions.length === 512) {
2561
+ history.incomplete = true;
2562
+ continue;
2563
+ }
2564
+ const value = parsed.data;
2565
+ history.policyDecisions.push({
2566
+ runReference: governanceReference("run", event.runId),
2567
+ sequence: event.sequence,
2568
+ toolReference: governanceReference("tool", value.toolName),
2569
+ phase: value.phase,
2570
+ decision: value.decision,
2571
+ policyDigest: value.policyDigest,
2572
+ source: value.source,
2573
+ ruleReferences: value.ruleIds.map((rule) => governanceReference("rule", rule)),
2574
+ explicitReviewRequired: value.explicitReviewRequired
2575
+ });
2576
+ }
2577
+ if (page.cursorExpired || !page.hasMore)
2578
+ break;
2579
+ if (page.nextCursor === cursor)
2580
+ throw new Error("GOVERNANCE_HISTORY_INVALID");
2581
+ cursor = page.nextCursor;
2582
+ if (pageIndex === 49)
2583
+ history.incomplete = true;
2584
+ }
2585
+ }
2586
+ return { session, history, verify: async () => {
2587
+ for (const page of pages)
2588
+ if (governanceReference("history-page", JSON.stringify(source.history.replay(source.id, page.cursor))) !== page.digest)
2589
+ throw new Error("GOVERNANCE_STATE_CHANGED");
2590
+ if (JSON.stringify(await source.index.get(source.id)) !== captured)
2591
+ throw new Error("GOVERNANCE_STATE_CHANGED");
2592
+ } };
2593
+ }
2594
+ function projectGovernanceEnvelopes(envelopes, patches, now) {
2595
+ if (envelopes.length > 32)
2596
+ throw new Error("GOVERNANCE_ENVELOPE_LIMIT");
2597
+ return envelopes.map((input) => {
2598
+ if (Buffer.byteLength(JSON.stringify(input) ?? "") > 256 * 1024)
2599
+ throw new Error("GOVERNANCE_ENVELOPE_LIMIT");
2600
+ const parsed = changeEnvelopeSchema.safeParse(input);
2601
+ const proof = verifyChangeEnvelope(input, { now });
2602
+ const envelope = parsed.success ? parsed.data : undefined;
2603
+ return governanceEnvelopeSchema.parse({
2604
+ source: "caller-supplied",
2605
+ envelopeId: envelope?.envelopeId ?? null,
2606
+ patchReference: envelope ? governanceReference("patch", envelope.patch.patchId) : null,
2607
+ patchDigest: envelope?.patch.patchDigest ?? null,
2608
+ linkedRunReferences: envelope ? [...patches.get(envelope.patch.patchId) ?? []] : [],
2609
+ schemaValid: parsed.success,
2610
+ integrity: proof.integrity.envelopeDigestValid && proof.integrity.evidenceDigestValid ? "valid" : "invalid",
2611
+ expiration: !envelope ? "unknown" : proof.expiration.envelopeNotYetValid ? "not-yet-valid" : proof.expiration.envelopeExpired ? "expired" : "current",
2612
+ checkedAt: proof.expiration.checkedAt,
2613
+ expiredApprovals: proof.expiration.expiredApprovalIds.length,
2614
+ authenticity: "not-verified",
2615
+ patchBytes: "not-verified"
2616
+ });
2617
+ });
2618
+ }
2619
+
2620
+ // src/persistence/governance-report.ts
2621
+ var digest2 = z2.string().regex(/^sha256:[a-f0-9]{64}$/);
2622
+ var version = z2.string().regex(/^\d+\.\d+\.\d+(?:[-+][a-zA-Z0-9.-]+)?$/);
2623
+ var bindingSchema = z2.object({ schemaVersion: z2.literal(1), id: z2.literal("zhivex-harness"), version, fingerprint: digest2, algorithm: z2.literal("sha256") });
2624
+ var count2 = z2.number().int().nonnegative().safe();
2625
+ var status = z2.enum(["created", "running", "waiting_approval", "cancel_requested", "completed", "failed", "cancelled", "timed_out", "unknown"]);
2626
+ var availability = z2.enum(["recorded", "unavailable", "invalid"]);
2627
+ var providerReason = z2.enum(["empty_arguments", "invalid_json", "arguments_too_large", "incomplete_arguments", "inconsistent_metadata", "response_failed", "response_incomplete", "stream_truncated"]);
2628
+ var deliveryReason = z2.enum(["TASK_ACCEPTANCE_NO_DELIVERY_EVIDENCE", "TASK_ACCEPTANCE_SCOPE_VIOLATION", "TASK_ACCEPTANCE_RUN_NOT_COMPLETED", "TASK_ACCEPTANCE_RUN_NOT_FINISHED", "TASK_ACCEPTANCE_IMPORT_UNCONFIRMED", "TASK_ACCEPTANCE_CHECKS_MISSING", "TASK_ACCEPTANCE_DELIVERY_DRIFT", "TASK_ACCEPTANCE_DELIVERY_INSPECTION_FAILED", "TASK_ACCEPTANCE_HUMAN_REVIEW_REQUIRED", "TASK_ACCEPTANCE_VERIFIED"]);
2629
+ var deliveryStatus = z2.enum(["pending", "incomplete", "failed", "pending_review", "verified", "unknown"]);
2630
+ var checkSchema = z2.strictObject({
2631
+ reference: digest2,
2632
+ argvDigest: digest2,
2633
+ patchId: digest2,
2634
+ snapshotDigest: digest2,
2635
+ exitCode: z2.number().int(),
2636
+ timedOut: z2.boolean(),
2637
+ unchanged: z2.boolean()
2638
+ });
2639
+ var deliverySchema = z2.object({
2640
+ schemaVersion: z2.literal(1),
2641
+ contractRevision: count2,
2642
+ contractDigest: digest2,
2643
+ status: deliveryStatus,
2644
+ checks: z2.array(taskCheckReceiptSchema).max(32),
2645
+ delivery: z2.object({ runId: z2.string().max(512), executionIdentity: z2.string().max(512), patchId: digest2, snapshotDigest: digest2 }).optional()
2646
+ });
2647
+ var approvalSchema = z2.strictObject({
2648
+ reference: digest2,
2649
+ toolReference: digest2,
2650
+ approved: z2.boolean(),
2651
+ origin: z2.enum(["interactive", "automatic", "application", "unknown"]),
2652
+ channelReference: digest2,
2653
+ policyDigest: digest2.nullable(),
2654
+ status: z2.enum(["rejected", "approved", "applied", "succeeded", "failed", "unknown"]),
2655
+ proposalId: digest2.nullable(),
2656
+ exitCode: z2.number().int().nullable(),
2657
+ timedOut: z2.boolean().nullable(),
2658
+ effects: z2.array(z2.strictObject({ pathReference: digest2, beforeDigest: digest2.nullable(), afterDigest: digest2.nullable() })).max(50)
2659
+ });
2660
+ var delegationReason = z2.enum(["valid", "invalid_json", "invalid_schema", "task_mismatch", "incomplete", "duplicate_claim", "path_denied", "evidence_missing", "coverage_missing", "missing_child", "child_identity_unavailable"]);
2661
+ var delegationSchema = z2.strictObject({
2662
+ taskReference: digest2,
2663
+ childReference: digest2.nullable(),
2664
+ recordedAcceptance: z2.boolean(),
2665
+ reason: delegationReason,
2666
+ semanticReview: z2.enum(["pending", "not_requested"]),
2667
+ correctionsUsed: count2.nullable()
2668
+ });
2669
+ var delegationInput = z2.object({ schemaVersion: z2.literal(1), evaluations: z2.array(z2.object({
2670
+ taskId: z2.string().max(128),
2671
+ childRunId: z2.string().max(512).nullable(),
2672
+ childStatus: z2.string().max(32),
2673
+ accepted: z2.boolean(),
2674
+ reason: delegationReason,
2675
+ semanticReview: z2.enum(["pending", "not_requested"]),
2676
+ correctionsUsed: count2.optional()
2677
+ })).max(64) });
2678
+ var runSchema = z2.strictObject({
2679
+ reference: digest2,
2680
+ parentReference: digest2.nullable(),
2681
+ revision: count2.nullable(),
2682
+ status,
2683
+ finalized: z2.boolean(),
2684
+ failure: z2.enum(["run_failed", "cancelled", "timed_out", "not_reported"]).nullable(),
2685
+ failureDetails: z2.strictObject({ providerToolReason: providerReason.nullable(), diagnosticReference: digest2.nullable(), deliveryReason: deliveryReason.nullable() }),
2686
+ identity: z2.literal("not-verified"),
2687
+ runtimePolicy: z2.enum(["assistant-v3-durable-closure"]).nullable(),
2688
+ contract: z2.strictObject({
2689
+ availability,
2690
+ digest: digest2.nullable(),
2691
+ revision: count2.nullable(),
2692
+ requiredChecks: count2.nullable(),
2693
+ humanReviewsPending: count2.nullable(),
2694
+ allowedWritePathReferences: z2.array(digest2).max(256),
2695
+ protectedPathReferences: z2.array(digest2).max(256),
2696
+ checks: z2.array(z2.strictObject({ reference: digest2, argvDigest: digest2, kind: z2.enum(["argv", "package-script"]), backend: z2.enum(["none", "oci"]), approval: z2.literal("required") })).max(32),
2697
+ humanReviewReferences: z2.array(digest2).max(32)
2698
+ }),
2699
+ delivery: z2.strictObject({
2700
+ availability,
2701
+ status: deliveryStatus,
2702
+ patchId: digest2.nullable(),
2703
+ snapshotDigest: digest2.nullable(),
2704
+ interpretation: z2.literal("recorded-checkpoint-not-current-workspace-verification"),
2705
+ checks: z2.array(checkSchema).max(32)
2706
+ }),
2707
+ approvals: z2.strictObject({ availability, items: z2.array(approvalSchema).max(512) }),
2708
+ usage: z2.strictObject({
2709
+ availability: z2.enum(["recorded", "unavailable"]),
2710
+ calls: count2.nullable(),
2711
+ inputTokens: count2.nullable(),
2712
+ outputTokens: count2.nullable(),
2713
+ complete: z2.boolean().nullable(),
2714
+ estimatedUsd: z2.number().finite().nonnegative().nullable(),
2715
+ costKind: z2.literal("estimate-not-invoice")
2716
+ }),
2717
+ journal: z2.strictObject({ availability: z2.enum(["recorded", "unavailable"]), entries: count2, failed: count2, unfinished: count2 }),
2718
+ delegations: z2.strictObject({ availability, interpretation: z2.literal("recorded-checkpoint-not-semantic-review"), items: z2.array(delegationSchema).max(64) }),
2719
+ children: z2.array(digest2).max(64)
2720
+ });
2721
+ var harnessGovernanceReportSchema = z2.strictObject({
2722
+ schemaVersion: z2.literal(1),
2723
+ kind: z2.literal("harness-governance-report"),
2724
+ exporterVersion: z2.string().regex(/^\d+\.\d+\.\d+(?:[-+][a-zA-Z0-9.-]+)?$/),
2725
+ executionArtifact: z2.strictObject({ version: version.nullable(), digest: z2.null(), availability: z2.enum(["recorded-version", "unavailable"]), configurationFingerprint: digest2.nullable(), authenticity: z2.literal("not-verified") }),
2726
+ rootReference: digest2,
2727
+ identity: z2.literal("not-verified"),
2728
+ referencePolicy: z2.literal("sha256-domain-separated-pseudonyms-not-anonymity"),
2729
+ session: governanceSessionSchema,
2730
+ eventHistory: governanceHistorySchema,
2731
+ changeEnvelopes: z2.array(governanceEnvelopeSchema).max(32),
2732
+ runs: z2.array(runSchema).max(64),
2733
+ missingChildren: z2.array(digest2).max(64),
2734
+ limitations: z2.array(z2.enum(["tree_limit", "missing_child", "invalid_contract", "invalid_delivery", "invalid_approvals", "journal_unavailable"])).max(6)
2735
+ });
2736
+ var MAX_JOURNAL = 2048;
2737
+ var MAX_REPORT_BYTES = 2 * 1024 * 1024;
2738
+ var sameScope = (state, scope) => !state.scope || state.scope.tenantId === scope?.tenantId && state.scope.userId === scope?.userId && state.scope.namespace === scope?.namespace;
2739
+ function projectRun(state, journal, limitations) {
2740
+ const runStatus = status.safeParse(state.status);
2741
+ const stateStatus = runStatus.success ? runStatus.data : "unknown";
2742
+ let ledger;
2743
+ let contractAvailability = "unavailable";
2744
+ try {
2745
+ ledger = readTaskAcceptanceLedger(state);
2746
+ if (ledger)
2747
+ contractAvailability = "recorded";
2748
+ } catch {
2749
+ contractAvailability = "invalid";
2750
+ limitations.add("invalid_contract");
2751
+ }
2752
+ const contract = ledger?.revisions.at(-1);
2753
+ const rawDelivery = state.metadata?.[TASK_ACCEPTANCE_EVIDENCE_KEY];
2754
+ const parsedDelivery = deliverySchema.safeParse(rawDelivery);
2755
+ const bound = parsedDelivery.success && contract && parsedDelivery.data.contractDigest === contract.digest && parsedDelivery.data.contractRevision === contract.revision && (!parsedDelivery.data.delivery || parsedDelivery.data.delivery.runId === state.runId);
2756
+ const candidate = bound ? parsedDelivery.data : undefined;
2757
+ const verified = candidate?.status === "verified" || candidate?.status === "pending_review";
2758
+ const validSuccess = !verified || stateStatus === "completed" && candidate.delivery && contract && contract.contract.requiredChecks.every((required) => candidate.checks.some((check) => check.checkId === required.id && check.runId === state.runId && check.argvDigest === "sha256:" + createHash3("sha256").update(JSON.stringify([required.command, ...required.args])).digest("hex") && check.contractDigest === contract.digest && check.contractRevision === contract.revision && check.exitCode === 0 && !check.timedOut && check.unchanged && check.patchId === candidate.delivery.patchId && check.snapshotDigest === candidate.delivery.snapshotDigest && check.executionIdentity === candidate.delivery.executionIdentity)) && candidate.status === "pending_review" === contract.contract.humanReview.length > 0;
2759
+ const delivery = validSuccess ? candidate : undefined;
2760
+ if (rawDelivery !== undefined && !delivery)
2761
+ limitations.add("invalid_delivery");
2762
+ const approvals = { availability: state.metadata?.[APPROVAL_HISTORY_KEY] === undefined ? "unavailable" : "recorded", items: [] };
2763
+ try {
2764
+ for (let offset = 0;offset < 512; offset += 25) {
2765
+ const rows = approvalDecisionViews(state, journal ?? [], offset);
2766
+ for (const row of rows)
2767
+ approvals.items.push({
2768
+ reference: governanceReference("approval", row.approvalId),
2769
+ toolReference: governanceReference("tool", row.name),
2770
+ approved: row.approved,
2771
+ origin: row.provenance?.origin ?? "unknown",
2772
+ channelReference: governanceReference("channel", row.provenance?.channel ?? "unknown"),
2773
+ policyDigest: row.provenance?.policyDigest ?? null,
2774
+ status: row.status,
2775
+ proposalId: row.evidence?.proposalId ?? null,
2776
+ exitCode: row.evidence?.exitCode ?? null,
2777
+ timedOut: row.evidence?.timedOut ?? null,
2778
+ effects: (row.evidence?.effects ?? []).map((effect) => ({ pathReference: governanceReference("path", effect.path), beforeDigest: effect.beforeDigest, afterDigest: effect.afterDigest }))
2779
+ });
2780
+ if (rows.length < 25)
2781
+ break;
2782
+ }
2783
+ } catch {
2784
+ approvals.availability = "invalid";
2785
+ approvals.items = [];
2786
+ limitations.add("invalid_approvals");
2787
+ }
2788
+ const parsedUsage = inspectUsageLedger(state.metadata?.[USAGE_LEDGER_KEY]);
2789
+ const usage = parsedUsage?.runId === state.runId ? parsedUsage : null;
2790
+ const rawDelegations = state.metadata?.zhivexDelegationAcceptanceV1;
2791
+ const delegations = delegationInput.safeParse(rawDelegations);
2792
+ const manifest = inspectRuntimeManifest(state.metadata?.effectiveRuntime);
2793
+ return {
2794
+ reference: governanceReference("run", state.runId),
2795
+ parentReference: state.parentRunId ? governanceReference("run", state.parentRunId) : null,
2796
+ revision: count2.safeParse(state.revision).success ? state.revision : null,
2797
+ status: stateStatus,
2798
+ finalized: ["completed", "failed", "cancelled", "timed_out"].includes(stateStatus),
2799
+ failure: stateStatus === "failed" ? "run_failed" : stateStatus === "cancelled" ? "cancelled" : stateStatus === "timed_out" ? "timed_out" : state.error ? "not_reported" : null,
2800
+ failureDetails: {
2801
+ providerToolReason: providerReason.safeParse(state.error?.reason).success ? state.error.reason : null,
2802
+ diagnosticReference: state.error?.diagnosticCode ? governanceReference("diagnostic", state.error.diagnosticCode) : null,
2803
+ deliveryReason: deliveryReason.safeParse(rawDelivery && typeof rawDelivery === "object" && !Array.isArray(rawDelivery) ? rawDelivery.reason : undefined).success ? rawDelivery.reason : null
2804
+ },
2805
+ identity: "not-verified",
2806
+ runtimePolicy: manifest?.policyVersion ?? null,
2807
+ contract: {
2808
+ availability: contractAvailability,
2809
+ digest: contract?.digest ?? null,
2810
+ revision: contract?.revision ?? null,
2811
+ requiredChecks: contract?.contract.requiredChecks.length ?? null,
2812
+ humanReviewsPending: contract?.contract.humanReview.length ?? null,
2813
+ allowedWritePathReferences: contract?.contract.allowedWritePaths.map((file) => governanceReference("path", file)) ?? [],
2814
+ protectedPathReferences: contract?.contract.protectedFiles.map((file) => governanceReference("path", file)) ?? [],
2815
+ checks: contract?.contract.requiredChecks.map((check) => ({ reference: governanceReference("check", check.id), argvDigest: "sha256:" + createHash3("sha256").update(JSON.stringify([check.command, ...check.args])).digest("hex"), kind: check.kind, backend: check.execution.backend, approval: check.execution.approval })) ?? [],
2816
+ humanReviewReferences: contract?.contract.humanReview.map((review) => governanceReference("review", review.id)) ?? []
2817
+ },
2818
+ delivery: {
2819
+ availability: delivery ? "recorded" : rawDelivery === undefined ? "unavailable" : "invalid",
2820
+ status: delivery?.status ?? "unknown",
2821
+ patchId: delivery?.delivery?.patchId ?? null,
2822
+ snapshotDigest: delivery?.delivery?.snapshotDigest ?? null,
2823
+ interpretation: "recorded-checkpoint-not-current-workspace-verification",
2824
+ checks: (delivery?.checks ?? []).filter((check) => check.runId === state.runId && check.contractDigest === contract?.digest && check.contractRevision === contract?.revision).map((check) => ({ reference: governanceReference("check", check.checkId), argvDigest: check.argvDigest, patchId: check.patchId, snapshotDigest: check.snapshotDigest, exitCode: check.exitCode, timedOut: check.timedOut, unchanged: check.unchanged }))
2825
+ },
2826
+ approvals,
2827
+ usage: {
2828
+ availability: usage ? "recorded" : "unavailable",
2829
+ calls: usage?.calls ?? null,
2830
+ inputTokens: usage?.inputTokens ?? null,
2831
+ outputTokens: usage?.outputTokens ?? null,
2832
+ complete: usage?.usageComplete ?? null,
2833
+ estimatedUsd: usage?.estimatedUsd ?? null,
2834
+ costKind: "estimate-not-invoice"
2835
+ },
2836
+ journal: {
2837
+ availability: journal ? "recorded" : "unavailable",
2838
+ entries: journal?.length ?? 0,
2839
+ failed: journal?.filter((entry) => entry.status === "failed").length ?? 0,
2840
+ unfinished: journal?.filter((entry) => entry.status !== "completed" && entry.status !== "failed").length ?? 0
2841
+ },
2842
+ delegations: {
2843
+ availability: rawDelegations === undefined ? "unavailable" : delegations.success ? "recorded" : "invalid",
2844
+ interpretation: "recorded-checkpoint-not-semantic-review",
2845
+ items: delegations.success ? delegations.data.evaluations.map((item) => ({
2846
+ taskReference: governanceReference("task", item.taskId),
2847
+ childReference: item.childRunId ? governanceReference("run", item.childRunId) : null,
2848
+ recordedAcceptance: item.accepted,
2849
+ reason: item.reason,
2850
+ semanticReview: item.semanticReview,
2851
+ correctionsUsed: item.correctionsUsed ?? null
2852
+ })) : []
2853
+ },
2854
+ children: (state.childRuns ?? []).slice(0, 64).map((child) => governanceReference("run", child.runId))
2855
+ };
2856
+ }
2857
+ async function exportHarnessGovernanceReport(store, scope, runId, options = {}) {
2858
+ const limitations = new Set;
2859
+ const runs = [], missingChildren = [];
2860
+ const observed = [];
2861
+ const queue = [{ id: runId }], seen = new Set;
2862
+ while (queue.length && runs.length + missingChildren.length < 64) {
2863
+ const item = queue.shift();
2864
+ if (seen.has(item.id))
2865
+ continue;
2866
+ seen.add(item.id);
2867
+ const loaded = await store.load(item.id, scope);
2868
+ const state = loaded ? structuredClone(loaded) : undefined;
2869
+ if (!state || state.runId !== item.id || !sameScope(state, scope) || item.parent !== undefined && state.parentRunId !== item.parent) {
2870
+ if (item.parent === undefined)
2871
+ throw new Error("GOVERNANCE_RUN_UNAVAILABLE");
2872
+ missingChildren.push(governanceReference("run", item.id));
2873
+ limitations.add("missing_child");
2874
+ continue;
2875
+ }
2876
+ const loadedJournal = await store.listToolCalls?.(state.runId, scope);
2877
+ const journal = loadedJournal ? structuredClone(loadedJournal) : undefined;
2878
+ if (journal && journal.length > MAX_JOURNAL)
2879
+ throw new Error("GOVERNANCE_JOURNAL_LIMIT");
2880
+ if (journal?.some((entry) => entry.runId !== state.runId))
2881
+ throw new Error("GOVERNANCE_JOURNAL_SCOPE");
2882
+ if (!journal)
2883
+ limitations.add("journal_unavailable");
2884
+ observed.push({ state, journal });
2885
+ runs.push(projectRun(state, journal, limitations));
2886
+ if ((state.childRuns?.length ?? 0) > 64)
2887
+ limitations.add("tree_limit");
2888
+ for (const child of (state.childRuns ?? []).slice(0, 64))
2889
+ queue.push({ id: child.runId, parent: state.runId });
2890
+ }
2891
+ if (queue.length)
2892
+ limitations.add("tree_limit");
2893
+ const context = await readGovernanceSession(options.session, runId, new Set(observed.map((item) => item.state.runId)));
2894
+ for (const { state, journal } of observed) {
2895
+ const current = await store.load(state.runId, scope);
2896
+ const currentJournal = await store.listToolCalls?.(state.runId, scope);
2897
+ if (!current || JSON.stringify(current) !== JSON.stringify(state) || JSON.stringify(currentJournal) !== JSON.stringify(journal))
2898
+ throw new Error("GOVERNANCE_STATE_CHANGED");
2899
+ }
2900
+ await context.verify();
2901
+ const patches = new Map;
2902
+ for (const run of runs)
2903
+ for (const patch of new Set([run.delivery.patchId, ...run.approvals.items.map((row) => row.proposalId)].filter((id) => id !== null)))
2904
+ patches.set(patch, [...patches.get(patch) ?? [], run.reference]);
2905
+ const binding = bindingSchema.safeParse(observed[0]?.state.harness);
2906
+ const report = harnessGovernanceReportSchema.parse({
2907
+ schemaVersion: 1,
2908
+ kind: "harness-governance-report",
2909
+ exporterVersion: HARNESS_VERSION,
2910
+ executionArtifact: { version: binding.success ? binding.data.version : null, digest: null, availability: binding.success ? "recorded-version" : "unavailable", configurationFingerprint: binding.success ? binding.data.fingerprint : null, authenticity: "not-verified" },
2911
+ rootReference: governanceReference("run", runId),
2912
+ identity: "not-verified",
2913
+ referencePolicy: "sha256-domain-separated-pseudonyms-not-anonymity",
2914
+ session: context.session,
2915
+ eventHistory: context.history,
2916
+ changeEnvelopes: projectGovernanceEnvelopes(options.changeEnvelopes ?? [], patches, options.now ?? Date.now()),
2917
+ runs,
2918
+ missingChildren,
2919
+ limitations: [...limitations].sort()
2920
+ });
2921
+ if (Buffer.byteLength(JSON.stringify(report)) > MAX_REPORT_BYTES)
2922
+ throw new Error("GOVERNANCE_REPORT_LIMIT");
2923
+ return report;
2924
+ }
2925
+ function renderHarnessGovernanceMarkdown(input) {
2926
+ const report = harnessGovernanceReportSchema.parse(input);
2927
+ return [
2928
+ "# Execution and governance report",
2929
+ "",
2930
+ `Schema: ${report.schemaVersion}. Exporter: ${report.exporterVersion}.`,
2931
+ `Identity: not verified. Recorded execution version: ${report.executionArtifact.version ?? "unavailable"}. Artifact digest: unavailable. Hash references are pseudonyms, not signatures.`,
2932
+ "Delivery describes a recorded checkpoint; export does not recheck the workspace.",
2933
+ "",
2934
+ "| Run reference | Execution | Recorded delivery | Usage |",
2935
+ "| --- | --- | --- | --- |",
2936
+ ...report.runs.map((run) => `| ${run.reference} | ${run.status} | ${run.delivery.status} | ${run.usage.availability === "unavailable" ? "unavailable" : `${run.usage.inputTokens} input / ${run.usage.outputTokens} output (${run.usage.complete ? "complete" : "incomplete"})`} |`),
2937
+ "",
2938
+ `Missing children: ${report.missingChildren.length}. Limitations: ${report.limitations.join(", ") || "none detected"}.`,
2939
+ `Session: ${report.session.availability}. Event history: ${report.eventHistory.availability}. Retention: ${report.eventHistory.retention}. Incomplete: ${report.eventHistory.incomplete}.`,
2940
+ `Change envelopes: ${report.changeEnvelopes.length}. Envelope integrity does not verify patch bytes or authenticity. Estimates are not invoices.`,
2941
+ ""
2942
+ ].join(`
2943
+ `);
2944
+ }
2945
+
2946
+ // src/cli/governance-report.ts
2947
+ async function governanceReportCli(options, config, store) {
2948
+ let index;
2949
+ let history;
2950
+ try {
2951
+ const envelopes = [];
2952
+ if (options.reportEnvelopePath) {
2953
+ const data = await readRegularFileNoFollow(path2.resolve(options.reportEnvelopePath), { label: "Governance envelope", maxBytes: 256 * 1024 });
2954
+ try {
2955
+ envelopes.push(JSON.parse(data.contents.toString("utf8")));
2956
+ } catch {
2957
+ throw new CliUsageError("Governance envelope must contain valid JSON.");
2958
+ }
2959
+ }
2960
+ if (options.sessionId) {
2961
+ index = await openCliSessionStore({ workspace: config.workspace, stateDirectory: config.stateDirectory, scope: config.scope });
2962
+ history = await openHarnessActivityStore(config);
2963
+ }
2964
+ const report = await exportHarnessGovernanceReport(store, config.scope, options.runId, {
2965
+ changeEnvelopes: envelopes,
2966
+ ...index && history && options.sessionId ? { session: { id: options.sessionId, index, history } } : {}
2967
+ });
2968
+ return options.json ? JSON.stringify(report, null, 2) + `
2969
+ ` : renderHarnessGovernanceMarkdown(report);
2970
+ } finally {
2971
+ history?.close();
2972
+ index?.close();
2973
+ }
2974
+ }
2975
+
4399
2976
  // src/cli/run-management.ts
4400
2977
  var printRunsDocument = (document, json) => {
4401
2978
  if (json || !document || typeof document !== "object") {
@@ -4448,6 +3025,11 @@ var manageRuns = async (options) => {
4448
3025
  case "inspect":
4449
3026
  document = await inspectHarnessRun(persistence.store, config, options.runId);
4450
3027
  break;
3028
+ case "report": {
3029
+ const report = await governanceReportCli(options, config, persistence.store);
3030
+ process.stdout.write(report);
3031
+ return;
3032
+ }
4451
3033
  case "export": {
4452
3034
  const inspection = await inspectHarnessRun(persistence.store, config, options.runId);
4453
3035
  document = { ...inspection, kind: "run-export" };
@@ -4484,11 +3066,11 @@ var manageRuns = async (options) => {
4484
3066
  };
4485
3067
 
4486
3068
  // src/cli/changes.ts
4487
- import path4 from "node:path";
3069
+ import path3 from "node:path";
4488
3070
  var MAX_CHANGE_ENVELOPE_JSON_BYTES = 4 * 1024 * 1024;
4489
3071
  var MAX_CHANGE_ARTIFACT_BYTES = 64 * 1024 * 1024;
4490
3072
  var readStableCliArtifact = async (filePath, maxBytes) => {
4491
- const absolute = path4.resolve(process.cwd(), filePath);
3073
+ const absolute = path3.resolve(process.cwd(), filePath);
4492
3074
  try {
4493
3075
  return (await readRegularFileNoFollow(absolute, {
4494
3076
  label: `Change artifact ${filePath}`,
@@ -4739,162 +3321,138 @@ async function readBackupTransport(source, legacyMaxBytes) {
4739
3321
  return root;
4740
3322
  }
4741
3323
 
4742
- // src/persistence/workspace-checkpoints.ts
4743
- import { createHash as createHash2, randomUUID as randomUUID3 } from "node:crypto";
4744
- import { z as z3 } from "zod";
4745
- var fileSchema = z3.strictObject({
4746
- path: workspaceFilePathSchema,
4747
- digest: fileDigestSchema,
4748
- content: z3.string().max(65536),
4749
- mode: z3.number().int().min(0).max(511)
4750
- }).refine((file) => Buffer.byteLength(file.content) <= 65536 && !file.content.includes("\x00") && file.digest === `sha256:${createHash2("sha256").update(file.content).digest("hex")}`, "Invalid checkpoint content identity.");
4751
- var workspaceCheckpointSchema = z3.strictObject({
4752
- id: z3.string().uuid(),
4753
- sessionId: z3.string(),
4754
- turnId: z3.string(),
4755
- files: z3.array(fileSchema).min(1).max(20)
4756
- });
4757
- var workspaceRestoreOperationSchema = z3.strictObject({
4758
- id: z3.string().uuid(),
4759
- checkpoint: workspaceCheckpointSchema,
4760
- proposalId: fileDigestSchema,
4761
- expected: z3.record(z3.string(), fileDigestSchema),
4762
- stage: z3.enum(["prepared", "forking", "forked", "applying", "applied", "completed"]),
4763
- forkSessionId: z3.string().optional()
4764
- });
4765
-
4766
3324
  // src/persistence/state-backup.ts
4767
- import { createHash as createHash3, randomUUID as randomUUID4 } from "node:crypto";
4768
- import { constants as fsConstants3 } from "node:fs";
4769
- import { chmod, link, lstat as lstat3, open as open2, realpath, unlink as unlink2 } from "node:fs/promises";
4770
- import path5 from "node:path";
4771
- import { z as z4 } from "zod";
3325
+ import { createHash as createHash4, randomUUID as randomUUID2 } from "node:crypto";
3326
+ import { constants as fsConstants2 } from "node:fs";
3327
+ import { chmod, link, lstat as lstat2, open, realpath, unlink } from "node:fs/promises";
3328
+ import path4 from "node:path";
3329
+ import { z as z3 } from "zod";
4772
3330
  import { normalizeAgentRunState } from "@zhivex-ai/agents";
4773
3331
  import { createSqliteAgentRunStore } from "@zhivex-ai/agents/ops";
4774
3332
  var HARNESS_STATE_BACKUP_SCHEMA_VERSION = 1;
4775
3333
  var HARNESS_STATE_BACKUP_MAX_BYTES = 64 * 1024 * 1024;
4776
3334
  var terminalRunStatuses = new Set(["completed", "failed", "cancelled", "timed_out"]);
4777
3335
  var terminalSessionStatuses = new Set(["completed", "failed", "cancelled", "timed_out"]);
4778
- var digestSchema = z4.string().regex(/^sha256:[a-f0-9]{64}$/);
4779
- var bindingKeySchema = z4.string().regex(/^[a-f0-9]{64}$/);
4780
- var jsonRecordSchema = z4.record(z4.string(), z4.unknown());
4781
- var durableScopeSchema = z4.object({
4782
- tenantId: z4.string().min(1),
4783
- userId: z4.string().min(1).optional(),
4784
- namespace: z4.string().min(1).optional()
3336
+ var digestSchema = z3.string().regex(/^sha256:[a-f0-9]{64}$/);
3337
+ var bindingKeySchema = z3.string().regex(/^[a-f0-9]{64}$/);
3338
+ var jsonRecordSchema = z3.record(z3.string(), z3.unknown());
3339
+ var durableScopeSchema = z3.object({
3340
+ tenantId: z3.string().min(1),
3341
+ userId: z3.string().min(1).optional(),
3342
+ namespace: z3.string().min(1).optional()
4785
3343
  }).strict();
4786
- var journalEntrySchema = z4.object({
4787
- runId: z4.string().min(1),
3344
+ var journalEntrySchema = z3.object({
3345
+ runId: z3.string().min(1),
4788
3346
  scope: durableScopeSchema,
4789
- toolCallId: z4.string().min(1),
4790
- toolName: z4.string().min(1),
4791
- status: z4.enum(["pending", "running", "completed", "failed"]),
4792
- idempotencyKey: z4.string().min(1),
4793
- revision: z4.number().int().nonnegative(),
4794
- input: z4.json().optional(),
4795
- output: z4.json().optional(),
4796
- error: z4.object({ message: z4.string() }).strict().optional(),
4797
- startedAt: z4.number().nonnegative().optional(),
4798
- completedAt: z4.number().nonnegative().optional(),
4799
- updatedAt: z4.number().nonnegative()
3347
+ toolCallId: z3.string().min(1),
3348
+ toolName: z3.string().min(1),
3349
+ status: z3.enum(["pending", "running", "completed", "failed"]),
3350
+ idempotencyKey: z3.string().min(1),
3351
+ revision: z3.number().int().nonnegative(),
3352
+ input: z3.json().optional(),
3353
+ output: z3.json().optional(),
3354
+ error: z3.object({ message: z3.string() }).strict().optional(),
3355
+ startedAt: z3.number().nonnegative().optional(),
3356
+ completedAt: z3.number().nonnegative().optional(),
3357
+ updatedAt: z3.number().nonnegative()
4800
3358
  }).passthrough();
4801
- var runRecordSchema = z4.object({
4802
- key: z4.string().min(1).max(1024),
3359
+ var runRecordSchema = z3.object({
3360
+ key: z3.string().min(1).max(1024),
4803
3361
  state: jsonRecordSchema,
4804
- updatedAt: z4.number().int().nonnegative()
3362
+ updatedAt: z3.number().int().nonnegative()
4805
3363
  }).strict();
4806
- var journalRecordSchema = z4.object({
4807
- runKey: z4.string().min(1).max(1024),
4808
- toolCallId: z4.string().min(1).max(1024),
3364
+ var journalRecordSchema = z3.object({
3365
+ runKey: z3.string().min(1).max(1024),
3366
+ toolCallId: z3.string().min(1).max(1024),
4809
3367
  entry: journalEntrySchema,
4810
- revision: z4.number().int().nonnegative(),
4811
- updatedAt: z4.number().int().nonnegative()
3368
+ revision: z3.number().int().nonnegative(),
3369
+ updatedAt: z3.number().int().nonnegative()
4812
3370
  }).strict();
4813
- var idempotencyRecordSchema = z4.object({
4814
- key: z4.string().min(1).max(1024),
4815
- runKey: z4.string().min(1).max(1024),
4816
- updatedAt: z4.number().int().nonnegative()
3371
+ var idempotencyRecordSchema = z3.object({
3372
+ key: z3.string().min(1).max(1024),
3373
+ runKey: z3.string().min(1).max(1024),
3374
+ updatedAt: z3.number().int().nonnegative()
4817
3375
  }).strict();
4818
- var parentRecordSchema = z4.object({
4819
- runKey: z4.string().min(1).max(1024),
4820
- parentRunKey: z4.string().min(1).max(1024),
4821
- updatedAt: z4.number().int().nonnegative()
3376
+ var parentRecordSchema = z3.object({
3377
+ runKey: z3.string().min(1).max(1024),
3378
+ parentRunKey: z3.string().min(1).max(1024),
3379
+ updatedAt: z3.number().int().nonnegative()
4822
3380
  }).strict();
4823
- var memoryRecordSchema = z4.object({
4824
- key: z4.string().min(1).max(1024),
4825
- messages: z4.array(z4.unknown()),
4826
- updatedAt: z4.number().int().nonnegative()
3381
+ var memoryRecordSchema = z3.object({
3382
+ key: z3.string().min(1).max(1024),
3383
+ messages: z3.array(z3.unknown()),
3384
+ updatedAt: z3.number().int().nonnegative()
4827
3385
  }).strict();
4828
- var sessionRecordSchema = z4.object({
4829
- sessionId: z4.string().min(1).max(260),
3386
+ var sessionRecordSchema = z3.object({
3387
+ sessionId: z3.string().min(1).max(260),
4830
3388
  workspaceKey: bindingKeySchema,
4831
3389
  scopeKey: bindingKeySchema,
4832
- title: z4.string().nullable(),
4833
- parentSessionId: z4.string().nullable(),
4834
- forkedFromTurnId: z4.string().nullable(),
4835
- revision: z4.number().int().nonnegative(),
4836
- activitySequence: z4.number().int().nonnegative(),
4837
- createdAt: z4.number().int().nonnegative(),
4838
- updatedAt: z4.number().int().nonnegative(),
4839
- archivedAt: z4.number().int().nonnegative().nullable(),
4840
- deletedAt: z4.number().int().nonnegative().nullable(),
4841
- metadataBytes: z4.number().int().nonnegative()
3390
+ title: z3.string().nullable(),
3391
+ parentSessionId: z3.string().nullable(),
3392
+ forkedFromTurnId: z3.string().nullable(),
3393
+ revision: z3.number().int().nonnegative(),
3394
+ activitySequence: z3.number().int().nonnegative(),
3395
+ createdAt: z3.number().int().nonnegative(),
3396
+ updatedAt: z3.number().int().nonnegative(),
3397
+ archivedAt: z3.number().int().nonnegative().nullable(),
3398
+ deletedAt: z3.number().int().nonnegative().nullable(),
3399
+ metadataBytes: z3.number().int().nonnegative()
4842
3400
  }).strict();
4843
- var sessionRunRecordSchema = z4.object({
4844
- turnId: z4.string().min(1).max(260),
4845
- sourceTurnId: z4.string().nullable(),
4846
- sessionId: z4.string().min(1).max(260),
4847
- ordinal: z4.number().int().nonnegative(),
4848
- runId: z4.string().min(1).max(260),
4849
- provider: z4.string().min(1).max(260),
4850
- model: z4.string().min(1).max(260),
4851
- role: z4.string().nullable(),
4852
- status: z4.string().min(1),
4853
- createdAt: z4.number().int().nonnegative(),
4854
- updatedAt: z4.number().int().nonnegative(),
4855
- completedAt: z4.number().int().nonnegative().nullable(),
4856
- metadataBytes: z4.number().int().nonnegative()
3401
+ var sessionRunRecordSchema = z3.object({
3402
+ turnId: z3.string().min(1).max(260),
3403
+ sourceTurnId: z3.string().nullable(),
3404
+ sessionId: z3.string().min(1).max(260),
3405
+ ordinal: z3.number().int().nonnegative(),
3406
+ runId: z3.string().min(1).max(260),
3407
+ provider: z3.string().min(1).max(260),
3408
+ model: z3.string().min(1).max(260),
3409
+ role: z3.string().nullable(),
3410
+ status: z3.string().min(1),
3411
+ createdAt: z3.number().int().nonnegative(),
3412
+ updatedAt: z3.number().int().nonnegative(),
3413
+ completedAt: z3.number().int().nonnegative().nullable(),
3414
+ metadataBytes: z3.number().int().nonnegative()
4857
3415
  }).strict();
4858
- var checkpointRecordSchema = z4.discriminatedUnion("kind", [
4859
- z4.object({
4860
- id: z4.string().uuid(),
3416
+ var checkpointRecordSchema = z3.discriminatedUnion("kind", [
3417
+ z3.object({
3418
+ id: z3.string().uuid(),
4861
3419
  workspaceKey: bindingKeySchema,
4862
3420
  scopeKey: bindingKeySchema,
4863
- kind: z4.literal("checkpoint"),
3421
+ kind: z3.literal("checkpoint"),
4864
3422
  body: workspaceCheckpointSchema
4865
3423
  }).strict(),
4866
- z4.object({
4867
- id: z4.string().uuid(),
3424
+ z3.object({
3425
+ id: z3.string().uuid(),
4868
3426
  workspaceKey: bindingKeySchema,
4869
3427
  scopeKey: bindingKeySchema,
4870
- kind: z4.literal("restore"),
3428
+ kind: z3.literal("restore"),
4871
3429
  body: workspaceRestoreOperationSchema
4872
3430
  }).strict()
4873
3431
  ]);
4874
3432
  var checkpointTableSql = "CREATE TABLE IF NOT EXISTS zhivex_workspace_checkpoints (id TEXT PRIMARY KEY, workspace_key TEXT NOT NULL, scope_key TEXT NOT NULL, kind TEXT NOT NULL, body TEXT NOT NULL)";
4875
- var stateBackupPayloadSchema = z4.object({
4876
- schemaVersion: z4.literal(HARNESS_STATE_BACKUP_SCHEMA_VERSION),
4877
- kind: z4.literal("state-backup"),
4878
- createdAt: z4.iso.datetime({ precision: 3 }),
4879
- binding: z4.object({
3433
+ var stateBackupPayloadSchema = z3.object({
3434
+ schemaVersion: z3.literal(HARNESS_STATE_BACKUP_SCHEMA_VERSION),
3435
+ kind: z3.literal("state-backup"),
3436
+ createdAt: z3.iso.datetime({ precision: 3 }),
3437
+ binding: z3.object({
4880
3438
  workspaceKey: bindingKeySchema,
4881
3439
  scopeKey: bindingKeySchema,
4882
- scopePrefix: z4.string().min(1).max(1024)
3440
+ scopePrefix: z3.string().min(1).max(1024)
4883
3441
  }).strict(),
4884
- schemas: z4.object({
4885
- operations: z4.literal(HARNESS_OPERATIONS_SCHEMA_VERSION),
4886
- sessions: z4.literal(HARNESS_SESSION_SCHEMA_VERSION)
3442
+ schemas: z3.object({
3443
+ operations: z3.literal(HARNESS_OPERATIONS_SCHEMA_VERSION),
3444
+ sessions: z3.literal(HARNESS_SESSION_SCHEMA_VERSION)
4887
3445
  }).strict(),
4888
- records: z4.object({
4889
- runs: z4.array(runRecordSchema),
4890
- toolJournal: z4.array(journalRecordSchema),
4891
- idempotency: z4.array(idempotencyRecordSchema),
4892
- parents: z4.array(parentRecordSchema),
4893
- memory: z4.array(memoryRecordSchema),
4894
- sessions: z4.array(sessionRecordSchema),
4895
- sessionRuns: z4.array(sessionRunRecordSchema),
4896
- workspaceCheckpoints: z4.array(checkpointRecordSchema).max(100).optional(),
4897
- budgetLedgers: z4.array(runRecordSchema).optional()
3446
+ records: z3.object({
3447
+ runs: z3.array(runRecordSchema),
3448
+ toolJournal: z3.array(journalRecordSchema),
3449
+ idempotency: z3.array(idempotencyRecordSchema),
3450
+ parents: z3.array(parentRecordSchema),
3451
+ memory: z3.array(memoryRecordSchema),
3452
+ sessions: z3.array(sessionRecordSchema),
3453
+ sessionRuns: z3.array(sessionRunRecordSchema),
3454
+ workspaceCheckpoints: z3.array(checkpointRecordSchema).max(100).optional(),
3455
+ budgetLedgers: z3.array(runRecordSchema).optional()
4898
3456
  }).strict()
4899
3457
  }).strict();
4900
3458
  var stateBackupBundleSchema = stateBackupPayloadSchema.extend({ checksum: digestSchema }).strict();
@@ -4906,12 +3464,12 @@ var canonicalJson = (value) => {
4906
3464
  return `{${Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`).join(",")}}`;
4907
3465
  };
4908
3466
  var checksumPayload = (payload) => {
4909
- const hash = createHash3("sha256");
3467
+ const hash = createHash4("sha256");
4910
3468
  for (const chunk of canonicalChunks(payload))
4911
3469
  hash.update(chunk);
4912
3470
  return `sha256:${hash.digest("hex")}`;
4913
3471
  };
4914
- var stableKey = (kind, value) => createHash3("sha256").update(kind).update("\x00").update(value).digest("hex");
3472
+ var stableKey = (kind, value) => createHash4("sha256").update(kind).update("\x00").update(value).digest("hex");
4915
3473
  var scopeValue = (config) => `${config.scope.tenantId}\x00${config.scope.userId ?? ""}\x00${config.scope.namespace ?? ""}`;
4916
3474
  var scopePrefix = (config) => `${encodeURIComponent(config.scope.namespace ?? "default")}:${encodeURIComponent(config.scope.tenantId)}:${encodeURIComponent(config.scope.userId ?? "*")}:`;
4917
3475
  var stateMatchesScope = (config, state) => {
@@ -4950,7 +3508,7 @@ var validateBudgetLedger = (config, run, identities) => {
4950
3508
  return identity;
4951
3509
  };
4952
3510
  var bindingForConfig = async (config) => ({
4953
- workspaceKey: stableKey("workspace", await realpath(path5.resolve(config.workspace))),
3511
+ workspaceKey: stableKey("workspace", await realpath(path4.resolve(config.workspace))),
4954
3512
  scopeKey: stableKey("scope", scopeValue(config)),
4955
3513
  scopePrefix: scopePrefix(config)
4956
3514
  });
@@ -4975,7 +3533,7 @@ var prepareDatabase = async (config) => {
4975
3533
  scope: config.scope
4976
3534
  });
4977
3535
  sessions.close();
4978
- return path5.join(await realpath(config.stateDirectory), HARNESS_SQLITE_FILE);
3536
+ return path4.join(await realpath(config.stateDirectory), HARNESS_SQLITE_FILE);
4979
3537
  };
4980
3538
  var locateExistingDatabase = async (config) => {
4981
3539
  if (config.storeBackend !== "sqlite") {
@@ -4990,9 +3548,9 @@ var locateExistingDatabase = async (config) => {
4990
3548
  return;
4991
3549
  throw error;
4992
3550
  }
4993
- const databasePath = path5.join(canonicalStateDirectory, HARNESS_SQLITE_FILE);
3551
+ const databasePath = path4.join(canonicalStateDirectory, HARNESS_SQLITE_FILE);
4994
3552
  try {
4995
- const entry = await lstat3(databasePath);
3553
+ const entry = await lstat2(databasePath);
4996
3554
  if (entry.isSymbolicLink() || !entry.isFile()) {
4997
3555
  throw new HarnessWorkspaceError(`The SQLite state path must be a real file: ${databasePath}.`);
4998
3556
  }
@@ -5112,7 +3670,7 @@ var readPayload = async (config, recordedWorkspace, accessLease) => {
5112
3670
  const memoryKeys = new Set(runs.map((run) => `${binding.scopePrefix}${String(run.state.runId)}`));
5113
3671
  const memory = database.query("SELECT memory_key, messages_json, updated_at_ms FROM zhivex_agent_memory ORDER BY memory_key").all().filter((row) => memoryKeys.has(row.memory_key)).map((row) => ({
5114
3672
  key: row.memory_key,
5115
- messages: z4.array(z4.unknown()).parse(JSON.parse(row.messages_json)),
3673
+ messages: z3.array(z3.unknown()).parse(JSON.parse(row.messages_json)),
5116
3674
  updatedAt: row.updated_at_ms
5117
3675
  }));
5118
3676
  const sessionRows = database.query("SELECT * FROM zhivex_cli_sessions ORDER BY session_id").all().filter((row) => row.workspace_key === binding.workspaceKey && row.scope_key === binding.scopeKey);
@@ -5190,11 +3748,11 @@ var createHarnessStateBackup = async (config) => {
5190
3748
  return stateBackupBundleSchema.parse({ ...payload, checksum: checksumPayload(payload) });
5191
3749
  };
5192
3750
  var writePrivateBackup = async (target, contents) => {
5193
- const requested = path5.resolve(target);
5194
- const parent = path5.dirname(requested);
3751
+ const requested = path4.resolve(target);
3752
+ const parent = path4.dirname(requested);
5195
3753
  let parentHandle;
5196
3754
  try {
5197
- parentHandle = await open2(parent, fsConstants3.O_RDONLY | fsConstants3.O_NOFOLLOW);
3755
+ parentHandle = await open(parent, fsConstants2.O_RDONLY | fsConstants2.O_NOFOLLOW);
5198
3756
  if (!(await parentHandle.stat()).isDirectory()) {
5199
3757
  throw new Error("Backup parent is not a directory.");
5200
3758
  }
@@ -5204,12 +3762,12 @@ var writePrivateBackup = async (target, contents) => {
5204
3762
  cause: error
5205
3763
  });
5206
3764
  }
5207
- const staged = path5.join(parent, `.zhivex-state-backup-${randomUUID4()}.tmp`);
3765
+ const staged = path4.join(parent, `.zhivex-state-backup-${randomUUID2()}.tmp`);
5208
3766
  let handle;
5209
3767
  let published = false;
5210
3768
  let stagedExists = false;
5211
3769
  try {
5212
- handle = await open2(staged, fsConstants3.O_WRONLY | fsConstants3.O_CREAT | fsConstants3.O_EXCL | fsConstants3.O_NOFOLLOW, 384);
3770
+ handle = await open(staged, fsConstants2.O_WRONLY | fsConstants2.O_CREAT | fsConstants2.O_EXCL | fsConstants2.O_NOFOLLOW, 384);
5213
3771
  stagedExists = true;
5214
3772
  let pending = [];
5215
3773
  let pendingBytes = 0;
@@ -5230,19 +3788,19 @@ var writePrivateBackup = async (target, contents) => {
5230
3788
  handle = undefined;
5231
3789
  await link(staged, requested);
5232
3790
  published = true;
5233
- await unlink2(staged);
3791
+ await unlink(staged);
5234
3792
  stagedExists = false;
5235
3793
  await parentHandle.sync();
5236
3794
  } catch (error) {
5237
3795
  if (published)
5238
- await unlink2(requested).catch(() => {
3796
+ await unlink(requested).catch(() => {
5239
3797
  return;
5240
3798
  });
5241
3799
  throw error;
5242
3800
  } finally {
5243
3801
  await handle?.close();
5244
3802
  if (stagedExists)
5245
- await unlink2(staged).catch(() => {
3803
+ await unlink(staged).catch(() => {
5246
3804
  return;
5247
3805
  });
5248
3806
  await parentHandle.close();
@@ -5260,7 +3818,7 @@ var exportHarnessStateBackup = async (config, target) => {
5260
3818
  return {
5261
3819
  schemaVersion: HARNESS_STATE_BACKUP_SCHEMA_VERSION,
5262
3820
  kind: "state-export",
5263
- path: path5.resolve(target),
3821
+ path: path4.resolve(target),
5264
3822
  checksum: bundle.checksum,
5265
3823
  counts: Object.fromEntries(Object.entries(bundle.records).map(([name, rows]) => [name, rows?.length ?? 0]))
5266
3824
  };
@@ -5268,7 +3826,7 @@ var exportHarnessStateBackup = async (config, target) => {
5268
3826
  var readHarnessStateBackup = async (source) => {
5269
3827
  let input;
5270
3828
  try {
5271
- input = await readBackupTransport(path5.resolve(source), HARNESS_STATE_BACKUP_MAX_BYTES);
3829
+ input = await readBackupTransport(path4.resolve(source), HARNESS_STATE_BACKUP_MAX_BYTES);
5272
3830
  } catch (error) {
5273
3831
  if (error instanceof SyntaxError) {
5274
3832
  throw new HarnessStateConflictError("Harness state backup is not valid JSON.", { cause: error });
@@ -5394,7 +3952,7 @@ var validateBundleBinding = async (config, bundle) => {
5394
3952
  const turn = bundle.records.sessionRuns.find((run) => run.turnId === checkpoint.turnId && run.sessionId === checkpoint.sessionId);
5395
3953
  if (!turn || !terminalSessionStatuses.has(turn.status))
5396
3954
  throw new HarnessStateConflictError("Checkpoint targets a missing terminal turn.");
5397
- if (new Set(checkpoint.files.map((file) => file.path)).size !== checkpoint.files.length || checkpoint.files.some((file) => `sha256:${createHash3("sha256").update(file.content).digest("hex")}` !== file.digest || Buffer.byteLength(file.content) > 65536)) {
3955
+ if (new Set(checkpoint.files.map((file) => file.path)).size !== checkpoint.files.length || checkpoint.files.some((file) => `sha256:${createHash4("sha256").update(file.content).digest("hex")}` !== file.digest || Buffer.byteLength(file.content) > 65536)) {
5398
3956
  throw new HarnessStateConflictError("Checkpoint content identity is invalid.");
5399
3957
  }
5400
3958
  if (record.kind === "restore") {
@@ -5638,6 +4196,134 @@ var manageState = async (options) => {
5638
4196
  `);
5639
4197
  };
5640
4198
 
4199
+ // src/cli/checkpoints.ts
4200
+ async function checkpointCliDocument(options) {
4201
+ const config = resolveHarnessConfig(options);
4202
+ const workspace = await Workspace.open(config.workspace);
4203
+ const sessions = await openCliSessionStore({ workspace: workspace.root, stateDirectory: config.stateDirectory, scope: config.scope });
4204
+ let store;
4205
+ const [target = "", second, ...paths] = options.checkpointArguments ?? [];
4206
+ if (!target && options.checkpointsCommand !== "storage") {
4207
+ sessions.close();
4208
+ throw new CliUsageError("Checkpoint target is required.");
4209
+ }
4210
+ const assertTerminal = async (sessionId) => {
4211
+ const session = await sessions.get(sessionId);
4212
+ if (!session)
4213
+ throw new Error("Checkpoint conversation was not found.");
4214
+ const persistence = await openHarnessPersistence(config);
4215
+ try {
4216
+ for (const run of session.runs) {
4217
+ const state = await persistence.store.load(run.runId, config.scope);
4218
+ if (!state || !["completed", "failed", "cancelled", "timed_out"].includes(state.status)) {
4219
+ throw new Error("Checkpoint operation requires all conversation runs to be terminal.");
4220
+ }
4221
+ }
4222
+ } finally {
4223
+ persistence.close();
4224
+ }
4225
+ return session;
4226
+ };
4227
+ try {
4228
+ store = await openWorkspaceCheckpointStore(workspace, sessions);
4229
+ switch (options.checkpointsCommand) {
4230
+ case "storage":
4231
+ return { kind: "checkpoint-storage", ...store.storageStatus() };
4232
+ case "prune-review":
4233
+ case "prune-apply": {
4234
+ const applying = options.checkpointsCommand === "prune-apply";
4235
+ const selected = (options.checkpointArguments ?? []).slice(applying ? 1 : 0);
4236
+ const selection = { checkpointIds: [], completedRestoreIds: [] };
4237
+ for (const item of selected) {
4238
+ const match = /^(checkpoint|restore):([0-9a-f-]+)$/i.exec(item);
4239
+ if (!match)
4240
+ throw new CliUsageError("Select exact IDs as checkpoint:<id> or restore:<id>.");
4241
+ (match[1].toLowerCase() === "checkpoint" ? selection.checkpointIds : selection.completedRestoreIds).push(match[2]);
4242
+ }
4243
+ const plan = applying ? await store.applyRetention(selection, fileDigestSchema.parse(target)) : store.prepareRetention(selection);
4244
+ return { kind: applying ? "checkpoint-pruned" : "checkpoint-prune-review", ...plan };
4245
+ }
4246
+ case "list":
4247
+ return { kind: "checkpoints", checkpoints: store.listCheckpoints(target), restores: store.listRestores(target) };
4248
+ case "capture": {
4249
+ await assertTerminal(target);
4250
+ const checkpoint = await store.capture({ sessionId: target, turnId: second, paths });
4251
+ return { kind: "checkpoint", inspection: await store.inspectCheckpoint(checkpoint.id) };
4252
+ }
4253
+ case "inspect":
4254
+ return { kind: "checkpoint", inspection: await store.inspectCheckpoint(target) };
4255
+ case "prepare": {
4256
+ const inspection = await store.inspectCheckpoint(target);
4257
+ await assertTerminal(inspection.checkpoint.sessionId);
4258
+ const expected = {};
4259
+ for (const file of inspection.files) {
4260
+ if (file.status !== "available")
4261
+ throw new Error("Checkpoint contains unavailable files or mode conflicts; inspect it before proceeding.");
4262
+ expected[file.path] = file.expectedDigest;
4263
+ }
4264
+ const { operation } = await store.prepareRestore(target, expected);
4265
+ return {
4266
+ kind: "restore-review",
4267
+ operationId: operation.id,
4268
+ stage: operation.stage,
4269
+ reviewedProposalId: operation.proposalId,
4270
+ diff: await store.previewRestore(operation.id),
4271
+ coverage: inspection.coverage
4272
+ };
4273
+ }
4274
+ case "review": {
4275
+ const operation = store.getOperation(target);
4276
+ let diff;
4277
+ try {
4278
+ diff = await store.previewRestore(target);
4279
+ } catch {}
4280
+ return {
4281
+ kind: "restore-review",
4282
+ operationId: operation.id,
4283
+ stage: operation.stage,
4284
+ reviewedProposalId: operation.proposalId,
4285
+ forkSessionId: operation.forkSessionId,
4286
+ previewStatus: diff ? "available" : "unavailable",
4287
+ ...diff ? { diff } : {},
4288
+ coverage: (await store.inspectCheckpoint(operation.checkpoint.id)).coverage
4289
+ };
4290
+ }
4291
+ case "recover": {
4292
+ const operation = store.getOperation(target);
4293
+ await assertTerminal(operation.checkpoint.sessionId);
4294
+ const recovered = await store.recoverFork(target, second);
4295
+ return { kind: "restore-recovery", operationId: recovered.id, stage: recovered.stage, forkSessionId: recovered.forkSessionId };
4296
+ }
4297
+ case "apply": {
4298
+ const operation = store.getOperation(target);
4299
+ await assertTerminal(operation.checkpoint.sessionId);
4300
+ const completed = await store.applyRestore(target, fileDigestSchema.parse(second));
4301
+ const session = await sessions.get(completed.forkSessionId);
4302
+ if (!session)
4303
+ throw new Error("Restore derivative is unavailable; inspect the operation before retrying.");
4304
+ return { kind: "restore-completed", operationId: completed.id, stage: completed.stage, session };
4305
+ }
4306
+ default:
4307
+ throw new CliUsageError("Unknown checkpoint command.");
4308
+ }
4309
+ } finally {
4310
+ store?.close();
4311
+ sessions.close();
4312
+ }
4313
+ }
4314
+ async function manageCheckpoints(options) {
4315
+ const document = await checkpointCliDocument(options);
4316
+ const encoded = JSON.stringify({ schemaVersion: 1, ...document }, null, 2);
4317
+ process.stdout.write((options.json ? encoded : sanitizeTerminalText(encoded)) + `
4318
+ `);
4319
+ if (document.kind === "restore-completed" && document.session) {
4320
+ if (!options.json)
4321
+ process.stdout.write(`Continue in the restored conversation: zhx --session ${document.session.sessionId}
4322
+ `);
4323
+ return document.session;
4324
+ }
4325
+ }
4326
+
5641
4327
  // src/cli/task-input.ts
5642
4328
  var MAX_STDIN_TASK_BYTES = 1024 * 1024;
5643
4329
  async function readStdinTask(input = process.stdin) {
@@ -5674,7 +4360,7 @@ var main = async (argv = process.argv.slice(2)) => {
5674
4360
  parsedOptions = { ...parsedOptions, prompt: await readStdinTask() };
5675
4361
  }
5676
4362
  if (parsedOptions.serviceFile) {
5677
- const { runServiceCli } = await import("./service-client-cli-yx56n0hc.js");
4363
+ const { runServiceCli } = await import("./service-client-cli-kns4mf57.js");
5678
4364
  await runServiceCli(parsedOptions, annotateCliStreamError);
5679
4365
  return;
5680
4366
  }
@@ -5704,6 +4390,9 @@ var main = async (argv = process.argv.slice(2)) => {
5704
4390
  case "providers":
5705
4391
  listProviders(options.json);
5706
4392
  return;
4393
+ case "policy":
4394
+ await inspectCliPolicy(options);
4395
+ return;
5707
4396
  case "doctor":
5708
4397
  await doctor(options);
5709
4398
  return;
@@ -5719,6 +4408,12 @@ var main = async (argv = process.argv.slice(2)) => {
5719
4408
  case "sessions":
5720
4409
  await manageSessions(options);
5721
4410
  return;
4411
+ case "checkpoints": {
4412
+ const restored = await manageCheckpoints(options);
4413
+ if (restored && interactive && !options.json)
4414
+ await chat({ ...options, command: "chat", sessionId: restored.sessionId });
4415
+ return;
4416
+ }
5722
4417
  case "changes":
5723
4418
  await manageChanges(options);
5724
4419
  return;