@zhivex-ai/harness 1.3.0-rc.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +12 -10
  3. package/SECURITY.md +1 -1
  4. package/SUPPORT.md +4 -4
  5. package/contracts/engine-api.json +334 -0
  6. package/contracts/public-api.json +267 -1
  7. package/contracts/stable-api-signatures.json +43 -43
  8. package/dist/acp-cli.js +54 -18
  9. package/dist/approvals/approval-history.d.ts +22 -0
  10. package/dist/approvals/explicit-review.d.ts +24 -0
  11. package/dist/approvals/host-policy-identity.d.ts +3 -0
  12. package/dist/cli/arguments.d.ts +6 -2
  13. package/dist/cli/checkpoints.d.ts +230 -0
  14. package/dist/cli/cli-profiles.d.ts +4 -2
  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/terminal/terminal-ui.d.ts +4 -0
  20. package/dist/{cli-entry-xmf48p6a.js → cli-entry-1gfg3brj.js} +14921 -10692
  21. package/dist/cli-entry-bx3vjw7h.js +656 -0
  22. package/dist/{index-fvca1xea.js → cli-entry-d5788aay.js} +339 -58
  23. package/dist/{cli-qgwvtk0w.js → cli-etsmtst3.js} +918 -2222
  24. package/dist/cli.js +3 -5
  25. package/dist/client/acp-mcp-admission.d.ts +32 -0
  26. package/dist/client/acp-mcp-host.d.ts +19 -0
  27. package/dist/client/acp-mcp-session.d.ts +14 -0
  28. package/dist/client/acp-stdio.d.ts +2 -0
  29. package/dist/client/acp.d.ts +4 -1
  30. package/dist/client/adapter.d.ts +13 -1
  31. package/dist/client/json-contracts.d.ts +107 -4
  32. package/dist/client/local-service.d.ts +2 -0
  33. package/dist/client/protocol.d.ts +127 -1
  34. package/dist/client/service-events.d.ts +4 -0
  35. package/dist/compat/cli-options.d.ts +13 -1
  36. package/dist/compat/code-support.js +23 -87
  37. package/dist/{config-q32jm1e6.js → config-c002f06f.js} +1 -1
  38. package/dist/{config-6gtpxvjv.js → config-j3jga5wh.js} +3 -1
  39. package/dist/context/adaptive-compaction.d.ts +1 -0
  40. package/dist/context/compaction.d.ts +1 -1
  41. package/dist/context/request-measurements.d.ts +17 -0
  42. package/dist/context/request-projection.d.ts +10 -0
  43. package/dist/context/task-memory.d.ts +1 -0
  44. package/dist/engine/acp.d.ts +2 -0
  45. package/dist/engine/acp.js +197 -1
  46. package/dist/engine/client.js +11 -4
  47. package/dist/engine/desktop-providers-v1.d.ts +4 -0
  48. package/dist/engine/desktop-providers-v1.js +18 -0
  49. package/dist/engine/desktop-state-v1.d.ts +10 -0
  50. package/dist/engine/desktop-state-v1.js +168 -0
  51. package/dist/engine/index.d.ts +12 -0
  52. package/dist/engine/index.js +571 -71
  53. package/dist/engine/mcp-stdio-v1.d.ts +11 -0
  54. package/dist/engine/mcp-stdio-v1.js +108 -0
  55. package/dist/engine/models.d.ts +3 -0
  56. package/dist/engine/models.js +894 -0
  57. package/dist/engine/protocol.d.ts +1 -0
  58. package/dist/engine/protocol.js +24 -0
  59. package/dist/engine/service.js +12 -5
  60. package/dist/execution/acceptance-snapshot.d.ts +2 -0
  61. package/dist/execution/execution-environment.d.ts +10 -0
  62. package/dist/execution/mcp-oci-channel.d.ts +11 -0
  63. package/dist/execution/mcp-oci-server.d.ts +38 -0
  64. package/dist/execution/mcp-oci-workspace.d.ts +23 -0
  65. package/dist/execution/mcp-resource-recovery.d.ts +19 -0
  66. package/dist/execution/mcp-workspace-snapshot.d.ts +20 -0
  67. package/dist/hostile-repository-demo.js +3169 -1163
  68. package/dist/{index-f4sb0e2q.js → index-2jrdcj6b.js} +17 -2
  69. package/dist/{index-77h9qr5h.js → index-3ah1f369.js} +40 -10
  70. package/dist/{index-pgqz45fj.js → index-42gp2hy8.js} +197 -1091
  71. package/dist/{index-41fkzt91.js → index-4jw3tvy8.js} +10 -2
  72. package/dist/index-6m08wvab.js +1768 -0
  73. package/dist/index-8705zxpr.js +145 -0
  74. package/dist/{index-d0wrghff.js → index-8vm2m34s.js} +225 -1416
  75. package/dist/index-8zzhggxv.js +477 -0
  76. package/dist/index-a97bezbz.js +1073 -0
  77. package/dist/index-azmwjp95.js +1004 -0
  78. package/dist/{cli-entry-0mmeag20.js → index-gq9xagx2.js} +122 -836
  79. package/dist/index-h0mqx2ed.js +1373 -0
  80. package/dist/index-hnaxfpb5.js +5589 -0
  81. package/dist/{index-2aymdsqc.js → index-qfpz5y2v.js} +24 -0
  82. package/dist/{index-kfgt9yqq.js → index-w2sj0wkw.js} +33 -10
  83. package/dist/index-wg1sd3at.js +80 -0
  84. package/dist/index-xqxeypt5.js +888 -0
  85. package/dist/index-yb239ghk.js +961 -0
  86. package/dist/index-z0qxmkm2.js +3389 -0
  87. package/dist/index.js +85 -72
  88. package/dist/integrations/mcp-host-session.d.ts +26 -0
  89. package/dist/integrations/mcp-stdio-admission.d.ts +54 -0
  90. package/dist/integrations/mcp-stdio-client.d.ts +44 -0
  91. package/dist/integrations/mcp-stdio-framing.d.ts +18 -0
  92. package/dist/integrations/mcp-stdio-launch.d.ts +20 -0
  93. package/dist/integrations/mcp-stdio-session.d.ts +7 -0
  94. package/dist/integrations/mcp-stdio-tools.d.ts +12 -0
  95. package/dist/internal/desktop/catalog.d.ts +2 -0
  96. package/dist/internal/desktop/persistence.d.ts +2 -3
  97. package/dist/internal/desktop/providers.d.ts +1 -0
  98. package/dist/models/catalog.d.ts +2 -0
  99. package/dist/persistence/governance-context.d.ts +126 -0
  100. package/dist/persistence/governance-report.d.ts +299 -0
  101. package/dist/persistence/host-database-backup.d.ts +28 -0
  102. package/dist/persistence/mcp-execution-journal.d.ts +19 -0
  103. package/dist/persistence/mcp-reconciliation.d.ts +33 -0
  104. package/dist/persistence/mcp-resource-journal.d.ts +54 -0
  105. package/dist/persistence/workspace-checkpoints.d.ts +138 -0
  106. package/dist/providers/anthropic-continuation.d.ts +5 -0
  107. package/dist/providers/providers.d.ts +1 -1
  108. package/dist/providers/vertex-auth.d.ts +27 -0
  109. package/dist/runtime/delegation-contracts.d.ts +9 -0
  110. package/dist/runtime/delegation-correction.d.ts +9 -0
  111. package/dist/runtime/delegation-result.d.ts +73 -0
  112. package/dist/runtime/error-diagnostics.d.ts +86 -0
  113. package/dist/runtime/harness.d.ts +11 -1
  114. package/dist/runtime/policy-decisions.d.ts +66 -0
  115. package/dist/runtime/policy-inspection.d.ts +52 -0
  116. package/dist/runtime/provider-diagnostics.d.ts +1 -0
  117. package/dist/runtime/runtime-instructions.d.ts +8 -0
  118. package/dist/runtime/task-acceptance-checks.d.ts +103 -0
  119. package/dist/runtime/task-acceptance-delivery.d.ts +107 -0
  120. package/dist/runtime/task-acceptance-host.d.ts +20 -0
  121. package/dist/runtime/task-acceptance-record.d.ts +107 -0
  122. package/dist/runtime/task-acceptance-validation.d.ts +25 -0
  123. package/dist/runtime/task-acceptance.d.ts +47 -0
  124. package/dist/runtime/time-to-safe-fix.d.ts +83 -0
  125. package/dist/runtime/tool-policy-file.d.ts +23 -0
  126. package/dist/runtime/tool-policy-paths.d.ts +5 -0
  127. package/dist/runtime/tool-policy.d.ts +6 -0
  128. package/dist/service-cli.js +14 -7
  129. package/dist/{service-client-cli-gs78wrxe.js → service-client-cli-3365rfav.js} +307 -248
  130. package/dist/verify-historical-migrations.js +3134 -1129
  131. package/dist/workspace/path-policy.d.ts +4 -0
  132. package/dist/zhx.js +3 -5
  133. package/docs/ACP.md +14 -2
  134. package/docs/ANTHROPIC.md +33 -0
  135. package/docs/CLI.md +132 -0
  136. package/docs/CLIENT_PROTOCOL.md +45 -0
  137. package/docs/CODE_RELEASE.md +35 -18
  138. package/docs/ENGINE_API.md +216 -7
  139. package/docs/EXTENSIBILITY.md +46 -3
  140. package/docs/FIRST_USE.md +6 -6
  141. package/docs/LOCAL_SERVICE.md +13 -0
  142. package/docs/MCP_STDIO.md +20 -0
  143. package/docs/README.md +1 -0
  144. package/docs/STABILITY.md +4 -2
  145. package/docs/SUPPORT_MATRIX.md +1 -1
  146. package/docs/TOOL_POLICY.md +81 -0
  147. package/docs/VERTEX.md +50 -0
  148. package/docs/adr/0002-isolated-mcp-stdio.md +135 -0
  149. package/docs/fixtures/mcp-stdio-contract-v1.json +189 -0
  150. package/docs/support-matrix.json +2 -0
  151. package/evaluations/representative-assembly-matrix.json +46 -1
  152. package/evaluations/task-acceptance-routes.json +7 -0
  153. package/examples/README.md +3 -3
  154. package/package.json +43 -12
  155. package/dist/cli-entry-64pdp3k5.js +0 -17
  156. package/dist/cli-entry-7h9savv5.js +0 -375
  157. package/dist/index-5wjwe91x.js +0 -9434
@@ -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-7h9savv5.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-xmf48p6a.js";
120
+ withTemporaryHarnessProfiles,
121
+ workspaceCheckpointSchema,
122
+ workspaceRestoreOperationSchema
123
+ } from "./cli-entry-1gfg3brj.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-bx3vjw7h.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-0mmeag20.js";
147
+ } from "./cli-entry-d5788aay.js";
106
148
  import {
107
149
  __require
108
150
  } from "./cli-entry-1bx83sw4.js";
@@ -111,1576 +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
- // src/cli/terminal/terminal-table.ts
1036
- var segments = new Intl.Segmenter(undefined, { granularity: "grapheme" });
1037
- var graphemes = (value) => [...segments.segment(value)].map((item) => item.segment);
1038
- var terminalCellWidth = (value) => graphemes(value).reduce((width, part) => {
1039
- if (/^[\p{Mark}\u200d\ufe0f]+$/u.test(part))
1040
- return width;
1041
- const code = part.codePointAt(0);
1042
- 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);
1043
- return width + (wide ? 2 : 1);
1044
- }, 0);
1045
- function tableCells(line) {
1046
- let text = line.trim();
1047
- if (text.startsWith("|"))
1048
- text = text.slice(1);
1049
- const cells = [];
1050
- let cell = "", ticks = 0;
1051
- for (let i = 0;i < text.length; i++) {
1052
- const char = text[i];
1053
- if (char === "\\" && text[i + 1] === "|") {
1054
- cell += "|";
1055
- i++;
1056
- continue;
1057
- }
1058
- if (char === "`") {
1059
- let count = 1;
1060
- while (text[i + count] === "`")
1061
- count++;
1062
- ticks = ticks === count ? 0 : ticks || count;
1063
- cell += "`".repeat(count);
1064
- i += count - 1;
1065
- continue;
1066
- }
1067
- if (char === "|" && !ticks) {
1068
- cells.push(cell.trim());
1069
- cell = "";
1070
- } else
1071
- cell += char;
1072
- }
1073
- if (cell.trim() || !text.endsWith("|"))
1074
- cells.push(cell.trim());
1075
- return cells;
1076
- }
1077
- var isTableSeparator = (line, columns) => {
1078
- const cells = tableCells(line);
1079
- return cells.length === columns && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
1080
- };
1081
- var plainCell = (value) => value.replace(/(`+)(.*?)\1/g, "$2").replace(/\*\*(.+?)\*\*/g, "$1").replace(/\s+/g, " ").trim();
1082
- function wrap(value, width) {
1083
- const lines = [];
1084
- let line = "", used = 0;
1085
- for (const word of value.split(" ")) {
1086
- const size = terminalCellWidth(word);
1087
- if (line && used + 1 + size > width) {
1088
- lines.push(line);
1089
- line = "";
1090
- used = 0;
1091
- }
1092
- if (line) {
1093
- line += " ";
1094
- used++;
1095
- }
1096
- for (const part of graphemes(word)) {
1097
- const size2 = terminalCellWidth(part);
1098
- if (line && used + size2 > width) {
1099
- lines.push(line);
1100
- line = "";
1101
- used = 0;
1102
- }
1103
- line += part;
1104
- used += size2;
1105
- }
1106
- }
1107
- if (line || !lines.length)
1108
- lines.push(line);
1109
- return lines;
1110
- }
1111
- function renderTerminalTable(lines, columns, color) {
1112
- const rows = [lines[0], ...lines.slice(2)].map((line) => tableCells(line).map(plainCell));
1113
- const count = rows[0].length;
1114
- const width = Math.max(4, Math.min(240, Number.isFinite(columns) ? Math.floor(columns) - 1 : 79));
1115
- const bold = (value) => color ? `\x1B[1m${value}\x1B[0m` : value;
1116
- if (width < count * 7 + 1) {
1117
- if (rows.length === 1)
1118
- return rows[0].map((cell) => bold(wrap(cell, width).join(`
1119
- `))).join(`
1120
- `) + `
1121
- `;
1122
- return rows.slice(1).map((row) => row.map((cell, i) => [bold(wrap(rows[0][i] + ":", width).join(`
1123
- `)), ...wrap(cell, width)].join(`
1124
- `)).join(`
1125
- `)).join(`
1126
-
1127
- `) + `
1128
- `;
1129
- }
1130
- const desired = Array.from({ length: count }, (_, i) => Math.max(4, ...rows.map((row) => terminalCellWidth(row[i] ?? ""))));
1131
- const widths = Array(count).fill(4);
1132
- let remaining = width - 7 * count - 1;
1133
- while (remaining-- > 0) {
1134
- let smallest = -1;
1135
- for (let i = 0;i < count; i++)
1136
- if (widths[i] < desired[i] && (smallest < 0 || widths[i] < widths[smallest]))
1137
- smallest = i;
1138
- if (smallest < 0)
1139
- break;
1140
- widths[smallest]++;
1141
- }
1142
- const align = tableCells(lines[1]).map((cell) => cell.endsWith(":") ? cell.startsWith(":") ? "center" : "right" : "left");
1143
- const border = (left, mid, right) => left + widths.map((size) => "─".repeat(size + 2)).join(mid) + right;
1144
- const rendered = [border("┌", "┬", "┐")];
1145
- rows.forEach((row, index) => {
1146
- const cells = widths.map((size, i) => wrap(row[i] ?? "", size));
1147
- for (let line = 0;line < Math.max(...cells.map((cell) => cell.length)); line++) {
1148
- rendered.push("│ " + cells.map((cell, i) => {
1149
- const text = cell[line] ?? "", spare = widths[i] - terminalCellWidth(text);
1150
- const left = align[i] === "right" ? spare : align[i] === "center" ? Math.floor(spare / 2) : 0;
1151
- const padded = " ".repeat(left) + text + " ".repeat(spare - left);
1152
- return index === 0 ? bold(padded) : padded;
1153
- }).join(" │ ") + " │");
1154
- }
1155
- if (index === 0)
1156
- rendered.push(border("├", "┼", "┤"));
1157
- });
1158
- rendered.push(border("└", "┴", "┘"));
1159
- return rendered.join(`
1160
- `) + `
1161
- `;
1162
- }
1163
-
1164
- // src/cli/terminal/terminal-markdown.ts
1165
- class TerminalMarkdown {
1166
- output;
1167
- color;
1168
- columns;
1169
- pending = "";
1170
- code = false;
1171
- heading = false;
1172
- lineStart = true;
1173
- timer;
1174
- tableLines = [];
1175
- constructor(output, color, columns = () => 80) {
1176
- this.output = output;
1177
- this.color = color;
1178
- this.columns = columns;
1179
- }
1180
- drain(final = false) {
1181
- let rendered = "", activeStyle = 0;
1182
- const emit = (text, style = this.code ? 36 : this.heading ? 1 : 0) => {
1183
- if (this.color && style !== activeStyle) {
1184
- if (activeStyle)
1185
- rendered += "\x1B[0m";
1186
- if (style)
1187
- rendered += `\x1B[${style}m`;
1188
- activeStyle = style;
1189
- }
1190
- rendered += text;
1191
- };
1192
- const finishTable = () => {
1193
- if (!this.tableLines.length)
1194
- return;
1195
- emit(this.tableLines.length > 1 ? renderTerminalTable(this.tableLines, this.columns(), this.color) : this.tableLines[0], 0);
1196
- this.tableLines = [];
1197
- };
1198
- while (this.pending) {
1199
- if (!final && this.pending.length === 1 && /[\uD800-\uDBFF]/.test(this.pending))
1200
- break;
1201
- if (this.lineStart) {
1202
- if (!this.code && (this.pending.startsWith("|") || this.tableLines.length)) {
1203
- const end = this.pending.indexOf(`
1204
- `);
1205
- if (this.pending.startsWith("|") && end < 0 && !final && this.pending.length < 8192)
1206
- break;
1207
- const line = this.pending.slice(0, end < 0 ? this.pending.length : end + 1);
1208
- const count = this.tableLines.length ? tableCells(this.tableLines[0]).length : 0;
1209
- 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);
1210
- if (accepted && this.tableLines.length < 128) {
1211
- this.tableLines.push(line);
1212
- this.pending = this.pending.slice(line.length);
1213
- continue;
1214
- }
1215
- finishTable();
1216
- }
1217
- if (!final && (/^`{1,2}$/.test(this.pending) || /^#{1,6}$/.test(this.pending)))
1218
- break;
1219
- if (this.pending.startsWith("```")) {
1220
- const end = this.pending.indexOf(`
1221
- `);
1222
- if (end < 0 && !final && this.pending.length < 128)
1223
- break;
1224
- const length = end < 0 ? this.pending.length : end;
1225
- emit(this.pending.slice(0, length), 90);
1226
- this.pending = this.pending.slice(length);
1227
- this.code = !this.code;
1228
- this.lineStart = false;
1229
- continue;
1230
- }
1231
- if (!this.code) {
1232
- const heading = /^#{1,6} /.exec(this.pending);
1233
- if (heading) {
1234
- this.heading = true;
1235
- this.pending = this.pending.slice(heading[0].length);
1236
- this.lineStart = false;
1237
- continue;
1238
- }
1239
- }
1240
- this.lineStart = false;
1241
- }
1242
- if (this.pending[0] === `
1243
- `) {
1244
- emit(`
1245
- `, 0);
1246
- this.pending = this.pending.slice(1);
1247
- this.lineStart = true;
1248
- this.heading = false;
1249
- continue;
1250
- }
1251
- if (this.color && !this.code) {
1252
- if (this.pending === "*" && !final)
1253
- break;
1254
- const marker = this.pending.startsWith("**") ? "**" : this.pending[0] === "`" ? "`" : undefined;
1255
- if (marker) {
1256
- const end = this.pending.indexOf(marker, marker.length);
1257
- const newline = this.pending.indexOf(`
1258
- `);
1259
- if (end > marker.length && (newline < 0 || end < newline)) {
1260
- emit(this.pending.slice(marker.length, end), marker === "`" ? 36 : 1);
1261
- this.pending = this.pending.slice(end + marker.length);
1262
- continue;
1263
- }
1264
- if (!final && newline < 0 && this.pending.length < 2048)
1265
- break;
1266
- emit(marker);
1267
- this.pending = this.pending.slice(marker.length);
1268
- continue;
1269
- }
1270
- }
1271
- const point = String.fromCodePoint(this.pending.codePointAt(0));
1272
- emit(point);
1273
- this.pending = this.pending.slice(point.length);
1274
- }
1275
- if (final)
1276
- finishTable();
1277
- if (activeStyle)
1278
- rendered += "\x1B[0m";
1279
- if (rendered)
1280
- this.output(rendered);
1281
- }
1282
- write(delta) {
1283
- this.pending += sanitizeTerminalText(delta);
1284
- if (this.pending.includes(`
1285
- `) || this.pending.length >= 2048)
1286
- this.drain();
1287
- if (this.pending && !this.timer) {
1288
- this.timer = setTimeout(() => {
1289
- this.timer = undefined;
1290
- this.drain();
1291
- }, 24);
1292
- this.timer.unref();
1293
- }
1294
- }
1295
- flush() {
1296
- clearTimeout(this.timer);
1297
- this.timer = undefined;
1298
- this.drain(true);
1299
- }
1300
- }
1301
-
1302
- // src/cli/presentation.ts
1303
- import { createInterface } from "node:readline/promises";
1304
- import {
1305
- createRedactionPolicy
1306
- } from "@zhivex-ai/agents";
1307
-
1308
- // src/cli/resume-metadata.ts
1309
- var HARNESS_RESUME_METADATA_KEY = "zhivexHarnessResume";
1310
- var HARNESS_RESUME_METADATA_SCHEMA_VERSION = 1;
1311
- var harnessConfigInput = (config) => ({
1312
- schemaVersion: config.schemaVersion,
1313
- provider: config.provider,
1314
- model: config.model,
1315
- ...config.reasoningEffort ? { reasoningEffort: config.reasoningEffort } : {},
1316
- workspace: config.workspace,
1317
- stateDirectory: config.stateDirectory,
1318
- storeBackend: config.storeBackend,
1319
- tenantId: config.scope.tenantId,
1320
- ...config.scope.userId ? { userId: config.scope.userId } : {},
1321
- ...config.scope.namespace ? { namespace: config.scope.namespace } : {},
1322
- requireVerifiedDelivery: config.requireVerifiedDelivery,
1323
- maxSteps: config.maxSteps,
1324
- timeoutMs: config.timeoutMs,
1325
- maxToolCalls: config.budget.maxToolCalls,
1326
- maxToolErrors: config.budget.maxToolErrors,
1327
- ...config.budget.unlimitedTokens !== undefined ? { unlimitedTokens: config.budget.unlimitedTokens } : {},
1328
- maxInputTokens: config.budget.maxInputTokens,
1329
- maxOutputTokens: config.budget.maxOutputTokens,
1330
- maxTotalTokens: config.budget.maxTotalTokens,
1331
- ...config.costBudget ? {
1332
- maxCostUsd: config.costBudget.maxCostUsd,
1333
- inputCostPerMillion: config.costBudget.inputCostPer1kTokens * 1000,
1334
- outputCostPerMillion: config.costBudget.outputCostPer1kTokens * 1000
1335
- } : {},
1336
- compactionMaxMessages: config.compaction.maxMessages,
1337
- compactionMaxEstimatedInputTokens: config.compaction.maxEstimatedInputTokens,
1338
- compactionKeepRecentMessages: config.compaction.keepRecentMessages,
1339
- ...config.compaction.model ? { compactionModel: config.compaction.model.model, compactionProvider: config.compaction.model.provider } : {},
1340
- allowedChecks: [...config.allowedChecks],
1341
- requiredCapabilities: [...config.requiredCapabilities],
1342
- subagentProfiles: [...config.orchestration.profiles],
1343
- subagentMaxSteps: config.orchestration.childBudget.maxSteps,
1344
- subagentMaxToolCalls: config.orchestration.childBudget.maxToolCalls,
1345
- subagentMaxToolErrors: config.orchestration.childBudget.maxToolErrors,
1346
- subagentMaxInputTokens: config.orchestration.childBudget.maxInputTokens,
1347
- subagentMaxOutputTokens: config.orchestration.childBudget.maxOutputTokens,
1348
- subagentMaxTotalTokens: config.orchestration.childBudget.maxTotalTokens,
1349
- subagentTimeoutMs: config.orchestration.childTimeoutMs,
1350
- maxParallelReviews: config.orchestration.maxParallelReviews,
1351
- projectContext: config.context.enabled,
1352
- contextConfigPath: config.context.configPath,
1353
- executionBackend: config.execution.backend,
1354
- ...config.execution.backend === "oci" ? {
1355
- ociRuntime: config.execution.runtime,
1356
- ociImage: config.execution.image,
1357
- ociAllowedCommands: [...config.execution.allowedCommands],
1358
- ociShellMode: config.execution.shellMode,
1359
- ociMaxProcessRuntimeMs: config.execution.maxProcessRuntimeMs,
1360
- ociMaxProcessOutputBytes: config.execution.maxProcessOutputBytes,
1361
- ociMaxMemoryMb: config.execution.maxMemoryMb,
1362
- ociMaxPids: config.execution.maxPids,
1363
- ociMaxCpus: config.execution.maxCpus,
1364
- ociMaxWorkspaceBytes: config.execution.maxWorkspaceBytes,
1365
- ociMaxFileWriteBytes: config.execution.maxFileWriteBytes,
1366
- ociTmpfsMb: config.execution.tmpfsMb
1367
- } : {},
1368
- ...config.mcpConfigPath ? { mcpConfigPath: config.mcpConfigPath } : {}
1369
- });
1370
- var persistedCliOptions = (input) => input ? {
1371
- ...input,
1372
- ...input.allowedChecks ? { allowedChecks: [...input.allowedChecks] } : {},
1373
- ...input.requiredCapabilities ? { requiredCapabilities: [...input.requiredCapabilities] } : {},
1374
- ...input.subagentProfiles ? { subagentProfiles: [...input.subagentProfiles] } : {},
1375
- ...input.ociAllowedCommands ? { ociAllowedCommands: [...input.ociAllowedCommands] } : {}
1376
- } : {};
1377
- var createHarnessResumeMetadata = (config, routes) => ({
1378
- [HARNESS_RESUME_METADATA_KEY]: JSON.parse(JSON.stringify({
1379
- schemaVersion: HARNESS_RESUME_METADATA_SCHEMA_VERSION,
1380
- config,
1381
- ...routes && routes.size > 0 ? { routes: serializeHarnessModelRoutes(routes) } : {}
1382
- }))
1383
- });
1384
- var readHarnessResumeDocument = (state) => {
1385
- const value = state.metadata?.[HARNESS_RESUME_METADATA_KEY];
1386
- if (value === undefined)
1387
- return;
1388
- if (!value || typeof value !== "object" || Array.isArray(value)) {
1389
- throw new Error("Persisted harness resume metadata is invalid.");
1390
- }
1391
- const document = value;
1392
- if (document.schemaVersion !== HARNESS_RESUME_METADATA_SCHEMA_VERSION || !document.config || typeof document.config !== "object" || Array.isArray(document.config)) {
1393
- throw new Error("Persisted harness resume metadata is invalid.");
1394
- }
1395
- return document;
1396
- };
1397
- var readHarnessResumeConfig = (state) => {
1398
- const document = readHarnessResumeDocument(state);
1399
- if (!document)
1400
- return;
1401
- try {
1402
- return harnessConfigInput(document.config);
1403
- } catch {
1404
- throw new Error("Persisted harness resume metadata is invalid.");
1405
- }
1406
- };
1407
- var readHarnessResumeRoutes = (state) => {
1408
- const document = readHarnessResumeDocument(state);
1409
- if (!document?.routes)
1410
- return resolveHarnessModelRoutes();
1411
- if (typeof document.routes !== "object" || Array.isArray(document.routes)) {
1412
- throw new Error("Persisted harness routing metadata is invalid.");
1413
- }
1414
- try {
1415
- const values = Object.entries(document.routes).map(([profile, target]) => {
1416
- if (!target || typeof target !== "object" || Array.isArray(target))
1417
- throw new Error("invalid");
1418
- const route = target;
1419
- if (typeof route.provider !== "string" || typeof route.model !== "string")
1420
- throw new Error("invalid");
1421
- return `${profile}=${route.provider}:${route.model}`;
1422
- });
1423
- return resolveHarnessModelRoutes(values);
1424
- } catch {
1425
- throw new Error("Persisted harness routing metadata is invalid.");
1426
- }
1427
- };
1428
- var shellArgument = (value) => `'${value.replaceAll("'", `'\\''`)}'`;
1429
- var resumeCommand = (runId, config, approve = true) => [
1430
- "zhivex-harness",
1431
- "resume",
1432
- shellArgument(runId),
1433
- approve ? "--approve" : "--deny",
1434
- "--workspace",
1435
- shellArgument(config.workspace),
1436
- "--state-dir",
1437
- shellArgument(config.stateDirectory),
1438
- "--store",
1439
- config.storeBackend,
1440
- "--tenant",
1441
- shellArgument(config.scope.tenantId),
1442
- ...config.scope.userId ? ["--user", shellArgument(config.scope.userId)] : [],
1443
- ...config.scope.namespace ? ["--namespace", shellArgument(config.scope.namespace)] : []
1444
- ].join(" ");
1445
-
1446
- // src/cli/presentation.ts
1447
- var CLI_STREAM_ERROR_SEQUENCE = Symbol("cliStreamErrorSequence");
1448
- var terminalErrorRedaction = createRedactionPolicy({ includeEmails: true });
1449
- var annotateCliStreamError = (error, lastSequence) => {
1450
- const normalized = error instanceof Error ? error : normalizeHarnessError(error);
1451
- Object.defineProperty(normalized, CLI_STREAM_ERROR_SEQUENCE, {
1452
- configurable: true,
1453
- enumerable: false,
1454
- value: (lastSequence ?? 0) + 1
1455
- });
1456
- return normalized;
1457
- };
1458
- var cliStreamErrorSequence = (error) => error && typeof error === "object" && Number.isSafeInteger(error[CLI_STREAM_ERROR_SEQUENCE]) ? error[CLI_STREAM_ERROR_SEQUENCE] : 1;
1459
- var terminalErrorMessage = (error) => terminalErrorRedaction.redactText(error instanceof Error ? error.message : String(error));
1460
- var summarizeApproval = (approval) => {
1461
- return formatApproval(approval);
1462
- };
1463
- var approvalResponses = (approvals, approve, reason) => approvals.map((approval) => ({
1464
- provider: approval.provider,
1465
- approvalRequestId: approval.id,
1466
- approve,
1467
- reason
1468
- }));
1469
- var terminalApprovalResolver = (automaticallyApprove, ask, ui) => {
1470
- return async (approvals) => {
1471
- if (automaticallyApprove === "restricted")
1472
- return approvalResponses(approvals, false, "Denied by restricted approval mode.");
1473
- if (automaticallyApprove === true || automaticallyApprove === "auto") {
1474
- return approvalResponses(approvals, true, "Approved by automatic mode.");
1475
- }
1476
- if (!ask && (!process.stdin.isTTY || !process.stdout.isTTY))
1477
- return;
1478
- const readline = ask ? undefined : createInterface({ input: process.stdin, output: process.stdout });
1479
- try {
1480
- return await resolveTerminalApprovals(approvals, {
1481
- ask: ask ?? ((text) => readline.question(text)),
1482
- write: (text) => process.stderr.write(text),
1483
- ...ui
1484
- });
1485
- } finally {
1486
- readline?.close();
1487
- }
1488
- };
1489
- };
1490
- var printTerminalResult = (result, harness, output, tracker) => {
1491
- tracker.markdown?.flush();
1492
- const document = runResultDocument(result, harness);
1493
- if (output.jsonl) {
1494
- tracker.sequence = (tracker.sequence ?? 0) + 1;
1495
- process.stdout.write(`${serializeStreamResult(document, tracker.sequence)}
1496
- `);
1497
- return;
1498
- }
1499
- if (output.json) {
1500
- process.stdout.write(`${JSON.stringify(document, null, 2)}
1501
- `);
1502
- return;
1503
- }
1504
- if (!tracker.streamedText && result.outputText) {
1505
- process.stdout.write(sanitizeTerminalText(result.outputText));
1506
- }
1507
- if (result.outputText || tracker.streamedText) {
1508
- process.stdout.write(`
1509
- `);
1510
- }
1511
- process.stderr.write(`
1512
- run ${result.state.runId} · ${result.state.provider}/${result.state.modelId} · ${result.status} · ${result.steps.length} steps · ${harness.workspace.mutationAudit().length} mutations
1513
- `);
1514
- if (result.state.metadata?.[USAGE_LEDGER_KEY])
1515
- process.stderr.write(formatUsageLedger(result.state.metadata[USAGE_LEDGER_KEY]) + `
1516
- `);
1517
- process.stderr.write(formatVerificationSummary(result.toolResults) + `
1518
- `);
1519
- if (result.status === "waiting_approval") {
1520
- for (const approval of result.state.pendingApprovals) {
1521
- process.stderr.write(`
1522
- Pending approval:
1523
- ${summarizeApproval(approval)}
1524
- `);
1525
- }
1526
- process.stderr.write(`The state was persisted. Resume with: ${resumeCommand(result.state.runId, harness.config)}
1527
- `);
1528
- }
1529
- };
1530
- var proseLineOpen = new WeakSet;
1531
- var conversationStarted = new WeakSet;
1532
- var toolActivities = new WeakMap;
1533
- var lastTerminalFailures = new WeakMap;
1534
- var flushToolActivity = (tracker) => toolActivities.get(tracker)?.flush();
1535
- var streamSink = (output, tracker, compact = false) => async (event) => {
1536
- if (!output.json && !output.jsonl)
1537
- tracker.activityHistory?.observe(event);
1538
- if (output.jsonl) {
1539
- tracker.sequence = (tracker.sequence ?? 0) + 1;
1540
- process.stdout.write(`${serializeStreamEvent(event, tracker.sequence)}
1541
- `);
1542
- return;
1543
- }
1544
- if (!output.json && compact && event.type === "agent-run-update")
1545
- return;
1546
- if (!output.json && compact) {
1547
- let activity = toolActivities.get(tracker);
1548
- if (!activity) {
1549
- activity = new ToolActivity((text) => {
1550
- process.stderr.write(text);
1551
- }, Boolean(process.stderr.isTTY && process.stdout.isTTY), () => process.stderr.columns || 80, tracker.inputStatus);
1552
- toolActivities.set(tracker, activity);
1553
- }
1554
- if (event.type === "agent-compaction") {
1555
- tracker.markdown?.flush();
1556
- if (proseLineOpen.has(tracker)) {
1557
- process.stdout.write(`
1558
- `);
1559
- proseLineOpen.delete(tracker);
1560
- }
1561
- activity.phase("Updating context");
1562
- return;
1563
- }
1564
- if (event.type === "agent-step-start") {
1565
- tracker.markdown?.flush();
1566
- if (proseLineOpen.has(tracker)) {
1567
- process.stdout.write(`
1568
- `);
1569
- proseLineOpen.delete(tracker);
1570
- }
1571
- activity.phase("Waiting for model response", event.stepIndex);
1572
- return;
1573
- }
1574
- if (event.type === "agent-run-start") {
1575
- lastTerminalFailures.delete(tracker);
1576
- activity.phase("Waiting for model response", event.currentStep);
1577
- return;
1578
- }
1579
- if (event.type === "tool-call") {
1580
- tracker.markdown?.flush();
1581
- if (proseLineOpen.has(tracker)) {
1582
- process.stdout.write(`
1583
- `);
1584
- proseLineOpen.delete(tracker);
1585
- }
1586
- activity.start(event.toolCall.name);
1587
- return;
1588
- }
1589
- if (event.type === "tool-result") {
1590
- activity.finish(event.toolResult.toolName, !event.toolResult.isError);
1591
- const detail = compactToolResult(event.toolResult);
1592
- const check = ["run_check", "run_environment_command"].includes(event.toolResult.toolName) || event.toolResult.toolName.startsWith("verify_and_apply_");
1593
- if (detail && !check) {
1594
- activity.pause();
1595
- tracker.markdown?.flush();
1596
- if (proseLineOpen.has(tracker)) {
1597
- process.stdout.write(`
1598
- `);
1599
- proseLineOpen.delete(tracker);
1600
- }
1601
- process.stderr.write(`${detail}
1602
- `);
1603
- return;
1604
- }
1605
- if (!check)
1606
- return;
1607
- activity.pause();
1608
- } else if (["agent-run-finish", "error", "tool-approval-request", "agent-approval-request"].includes(event.type)) {
1609
- tracker.markdown?.flush();
1610
- if (proseLineOpen.has(tracker)) {
1611
- process.stdout.write(`
1612
- `);
1613
- proseLineOpen.delete(tracker);
1614
- }
1615
- activity.flush();
1616
- } else if (!["provider-data", "finish", "agent-step-start", "agent-step-finish"].includes(event.type)) {
1617
- activity.pause();
1618
- }
1619
- }
1620
- if (!output.json && event.type === "text-delta") {
1621
- if (compact && !conversationStarted.has(tracker)) {
1622
- process.stdout.write(terminalSupportsColor(Boolean(process.stdout.isTTY)) ? `
1623
- \x1B[1mZhivex\x1B[0m
1624
- ` : `
1625
- Zhivex
1626
- `);
1627
- conversationStarted.add(tracker);
1628
- }
1629
- if (event.textDelta.endsWith(`
1630
- `))
1631
- proseLineOpen.delete(tracker);
1632
- else if (event.textDelta)
1633
- proseLineOpen.add(tracker);
1634
- tracker.streamedText = true;
1635
- if (process.stdout.isTTY) {
1636
- tracker.markdown ??= new TerminalMarkdown((text) => {
1637
- process.stdout.write(text);
1638
- }, terminalSupportsColor(true), () => process.stdout.columns || 80);
1639
- tracker.markdown.write(event.textDelta);
1640
- } else
1641
- process.stdout.write(sanitizeTerminalText(event.textDelta));
1642
- return;
1643
- }
1644
- if (!output.json) {
1645
- 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))
1646
- return;
1647
- if (compact && event.type === "agent-run-finish" && event.status === "waiting_approval")
1648
- return;
1649
- tracker.markdown?.flush();
1650
- proseLineOpen.delete(tracker);
1651
- let line = formatTerminalEvent(event, {
1652
- color: terminalSupportsColor(Boolean(process.stderr.isTTY))
1653
- });
1654
- if (compact && event.type === "tool-result" && event.toolResult.isError && line?.endsWith(" · error")) {
1655
- line = compactToolResult(event.toolResult);
1656
- }
1657
- if ((event.type === "error" || event.type === "agent-run-finish" && event.status === "failed") && line) {
1658
- const cause = terminalRunFailure(event.type === "error" ? event.error : event.state.error);
1659
- if (lastTerminalFailures.get(tracker) === cause)
1660
- return;
1661
- lastTerminalFailures.set(tracker, cause);
1662
- } else if (event.type === "agent-run-start")
1663
- lastTerminalFailures.delete(tracker);
1664
- if (line)
1665
- process.stderr.write(`
1666
- ${line}
1667
- `);
1668
- }
1669
- };
1670
- var orchestrationObserver = (json) => async (event) => {
1671
- if (json)
1672
- return;
1673
- if (event.type === "subagent-start") {
1674
- process.stderr.write(`
1675
- subagent start · ${event.childAgentId ?? event.toolName}
1676
- `);
1677
- } else if (event.type === "subagent-finish") {
1678
- process.stderr.write(`
1679
- subagent finish · ${event.childRun.agentId ?? event.childRun.toolName} · ${event.childRun.status}
1680
- `);
1681
- }
1682
- };
1683
-
1684
156
  // src/cli/setup.ts
1685
157
  var providersDocument = (env = process.env) => ({
1686
158
  schemaVersion: CLI_JSON_SCHEMA_VERSION,
@@ -1762,7 +234,7 @@ var initializeCli = async (options, context = {}) => {
1762
234
  process.stdout.write([
1763
235
  `${options.updateProfile ? "Updated" : "Created"} personal profile ${profileName} at ${created.path}.`,
1764
236
  `Provider: ${created.profile.provider} · Model: ${created.profile.model} · Reasoning: ${created.profile.reasoningEffort ?? "default"}`,
1765
- 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.`,
1766
238
  "Next:",
1767
239
  ` ${doctorCommand}`,
1768
240
  ` ${runCommand}`,
@@ -1780,7 +252,7 @@ var listProviders = (json) => {
1780
252
  return;
1781
253
  }
1782
254
  for (const provider of document.providers) {
1783
- 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("/")}`}
1784
256
  `);
1785
257
  }
1786
258
  };
@@ -1800,13 +272,25 @@ Everyday commands:
1800
272
  zhx chat [options] [--continue|--session <id>]
1801
273
  zhx providers [--json]
1802
274
  zhx doctor [options] [--json]
275
+ zhx policy [options] [--json]
1803
276
 
1804
277
  Advanced operations and automation:
1805
278
  zhx resume [options] <runId> --approve|--deny
1806
279
  zhx runs list [--status <status>] [--limit <n>] [--json]
1807
280
  zhx sessions list|inspect|rename|fork|archive
1808
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>
1809
292
  zhx run --pricing-file <prices.json> --usage-limit-usd <amount> <task>
293
+ zhx run --tool-policy <absolute-policy.json> <task>
1810
294
  zhx changes create <input.json> --patch <artifact>
1811
295
  zhx changes verify <envelope.json> --patch <artifact> [--preconditions <file>]
1812
296
  zhx state status
@@ -1904,376 +388,10 @@ Credentials:
1904
388
  Qwen: DASHSCOPE_API_KEY or QWEN_API_KEY
1905
389
  Gemini: GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY`;
1906
390
 
1907
- // src/cli/qwen-connection.ts
1908
- var regions = {
1909
- singapore: ["Singapore", "ap-southeast-1", "dashscope-intl.aliyuncs.com"],
1910
- beijing: ["Beijing", "cn-beijing", "dashscope.aliyuncs.com"],
1911
- "hong-kong": ["Hong Kong", "cn-hongkong", "cn-hongkong.dashscope.aliyuncs.com"],
1912
- tokyo: ["Tokyo", "ap-northeast-1", ""],
1913
- frankfurt: ["Frankfurt", "eu-central-1", ""],
1914
- virginia: ["Virginia", "us-east-1", ""]
1915
- };
1916
- function qwenEndpoint(connection) {
1917
- if (connection.service === "token-plan")
1918
- return "https://token-plan.maas.qwencloudapi.com/compatible-mode/v1";
1919
- if (connection.service !== "api" || !Object.hasOwn(regions, connection.region))
1920
- throw new Error("Invalid Qwen connection.");
1921
- const [, region, legacy] = regions[connection.region];
1922
- if (connection.workspace !== undefined && !/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(connection.workspace))
1923
- throw new Error("Invalid Qwen workspace ID.");
1924
- const host = connection.workspace ? `${connection.workspace}.${region}.maas.aliyuncs.com` : legacy;
1925
- if (!host)
1926
- throw new Error("This Qwen region requires a workspace ID.");
1927
- return `https://${host}/compatible-mode/v1`;
1928
- }
1929
- function decodeQwenCredential(value) {
1930
- if (!value.startsWith("{"))
1931
- return { key: value, connection: { service: "api", region: "singapore" } };
1932
- const parsed = JSON.parse(value);
1933
- if (parsed.version !== 1 || typeof parsed.key !== "string" || !parsed.connection)
1934
- throw new Error("Invalid Qwen credential record.");
1935
- qwenEndpoint(parsed.connection);
1936
- return { key: parsed.key, connection: parsed.connection };
1937
- }
1938
- async function selectQwenConnection(input) {
1939
- const service = await input.select("Qwen / Service", [
1940
- { value: "api", label: "Standard API / Alibaba Cloud Model Studio", detail: "Use the region where your API key was created" },
1941
- { value: "token-plan", label: "QwenCloud Token Plan", detail: "Separate subscription endpoint and credential" }
1942
- ]);
1943
- if (!service)
1944
- return;
1945
- if (service === "token-plan")
1946
- return { service };
1947
- const region = await input.select("Qwen / API region", Object.entries(regions).map(([value, [label]]) => ({ value, label })));
1948
- if (!region)
1949
- return;
1950
- let workspace;
1951
- const dedicated = !regions[region][2] || await input.select("Qwen / Endpoint", [
1952
- { value: false, label: "DashScope endpoint", detail: "Existing regional API endpoint" },
1953
- { value: true, label: "Workspace endpoint", detail: "Enter your workspace ID" },
1954
- { value: "cancel", label: "Back" }
1955
- ]);
1956
- if (dedicated === undefined || dedicated === "cancel")
1957
- return;
1958
- if (dedicated) {
1959
- if (!input.question)
1960
- throw new Error("Workspace setup requires interactive input.");
1961
- workspace = (await input.question("Workspace ID (not the API key): ")).trim();
1962
- if (!workspace)
1963
- return;
1964
- }
1965
- const connection = { service: "api", region, ...workspace ? { workspace } : {} };
1966
- qwenEndpoint(connection);
1967
- return connection;
1968
- }
1969
-
1970
- // src/cli/cli-credentials.ts
1971
- class CredentialSetupError extends Error {
1972
- }
1973
- var credentialService = "ai.zhivex.harness.cli";
1974
- var validCredential = (value) => /^[\x21-\x7e]{1,8192}$/.test(value);
1975
- var nativeCredentialEntry = async (provider) => {
1976
- if (!PROVIDERS.includes(provider))
1977
- throw new Error("Unsupported credential provider.");
1978
- if (!["darwin", "linux"].includes(process.platform) || process.env.ZHIVEX_HARNESS_CREDENTIAL_STORE === "disabled") {
1979
- throw new Error("Secure credential storage is unavailable.");
1980
- }
1981
- const { AsyncEntry } = await import("@napi-rs/keyring");
1982
- const entry = new AsyncEntry(credentialService, provider, { linux: { store: "secret-service" } });
1983
- return {
1984
- getPassword: () => entry.getPassword(AbortSignal.timeout(30000)),
1985
- setPassword: (value) => entry.setPassword(value, AbortSignal.timeout(30000)),
1986
- deleteCredential: () => entry.deleteCredential(AbortSignal.timeout(30000))
1987
- };
1988
- };
1989
-
1990
- class CliCredentials {
1991
- entry;
1992
- environment;
1993
- write;
1994
- temporary = new Map;
1995
- revision = 0;
1996
- qwenManaged = false;
1997
- qwenOverrideAccepted = false;
1998
- constructor(entry = nativeCredentialEntry, environment = process.env, write = (text) => {
1999
- process.stderr.write(text);
2000
- }) {
2001
- this.entry = entry;
2002
- this.environment = environment;
2003
- this.write = write;
2004
- }
2005
- environmentKey(provider) {
2006
- return providerDescriptor(provider).credentialNames.map((name) => this.environment[name]?.trim()).find(Boolean);
2007
- }
2008
- validate(value) {
2009
- if (!validCredential(value))
2010
- throw new Error("Invalid API key: use 1–8192 printable characters without whitespace.");
2011
- return value;
2012
- }
2013
- async saved(provider) {
2014
- try {
2015
- const value = await (await this.entry(provider)).getPassword();
2016
- if (value == null)
2017
- return;
2018
- this.validate(provider === "qwen" ? decodeQwenCredential(value).key : value);
2019
- return value;
2020
- } catch {
2021
- throw new Error("Secure storage is unavailable or locked. Unlock it, use a temporary key, or configure the environment.");
2022
- }
2023
- }
2024
- destinations = new Map;
2025
- destination(provider) {
2026
- return this.destinations.get(provider) ?? "environment-defined or provider default";
2027
- }
2028
- sources = new Map;
2029
- source(provider) {
2030
- return this.sources.get(provider) ?? "missing";
2031
- }
2032
- endpointOverride(provider) {
2033
- const variables = {
2034
- openai: ["OPENAI_BASE_URL"],
2035
- meta: ["META_BASE_URL"],
2036
- gemini: ["GEMINI_BASE_URL"],
2037
- qwen: ["QWEN_BASE_URL", "QWEN_REGION", "QWEN_WORKSPACE_ID"]
2038
- };
2039
- return (variables[provider] ?? []).some((name) => this.environment[name]?.trim());
2040
- }
2041
- async inspect(provider) {
2042
- if (this.environmentKey(provider) && !(provider === "qwen" && this.qwenManaged))
2043
- return { source: "environment", configured: true };
2044
- if (this.endpointOverride(provider) && !(provider === "qwen" && this.qwenOverrideAccepted))
2045
- return { source: "blocked", configured: false };
2046
- if (this.temporary.has(provider))
2047
- return { source: "temporary", configured: true };
2048
- try {
2049
- return await this.saved(provider) ? { source: "keychain", configured: true } : { source: "missing", configured: false };
2050
- } catch {
2051
- return { source: "unavailable", configured: false };
2052
- }
2053
- }
2054
- async configure(provider, input) {
2055
- for (;; ) {
2056
- try {
2057
- return await this.configureOnce(provider, input);
2058
- } catch (error) {
2059
- if (!(error instanceof CredentialSetupError) && !(error instanceof Error && error.name === "AbortError"))
2060
- throw error;
2061
- this.write(error instanceof CredentialSetupError ? `${error.message}
2062
- ` : `Key entry cancelled.
2063
- `);
2064
- const action = await input.select("Credentials / Recovery", [
2065
- { value: "temporary", label: "Use a temporary key", detail: "Enter a key for this CLI session only" },
2066
- { value: "retry", label: "Retry credential setup" },
2067
- { value: "cancel", label: "Cancel setup" }
2068
- ]);
2069
- if (!action || action === "cancel")
2070
- return false;
2071
- if (action === "temporary") {
2072
- try {
2073
- return await this.configureOnce(provider, input, "temporary");
2074
- } catch (retryError) {
2075
- if (!(retryError instanceof CredentialSetupError) && !(retryError instanceof Error && retryError.name === "AbortError"))
2076
- throw retryError;
2077
- this.write(`Temporary key entry was not completed. Try again or go back.
2078
- `);
2079
- }
2080
- }
2081
- }
2082
- }
2083
- }
2084
- async configureOnce(provider, input, mode) {
2085
- if (provider !== "qwen" && this.environmentKey(provider)) {
2086
- this.write(`An environment key takes precedence. Remove it from the launching shell to use managed credentials.
2087
- `);
2088
- }
2089
- const action = mode ?? await input.select(`Credentials / ${provider}`, [
2090
- { value: "save", label: "Save or replace in system keychain" },
2091
- { value: "temporary", label: "Temporary key for this CLI session" },
2092
- { value: "delete", label: "Remove saved and temporary key" },
2093
- { value: "cancel", label: "Back" }
2094
- ]);
2095
- if (!action || action === "cancel")
2096
- return false;
2097
- if (action === "delete") {
2098
- const confirm = await input.select(`Remove managed key for ${provider}?`, [
2099
- { value: false, label: "Keep key" },
2100
- { value: true, label: "Remove key" }
2101
- ]);
2102
- if (!confirm)
2103
- return false;
2104
- try {
2105
- await (await this.entry(provider)).deleteCredential();
2106
- this.write(`Saved key removed (or already absent).
2107
- `);
2108
- } catch {
2109
- this.write(`Saved key could not be removed: secure storage is unavailable or locked.
2110
- `);
2111
- }
2112
- this.temporary.delete(provider);
2113
- if (provider === "qwen") {
2114
- this.qwenManaged = false;
2115
- this.qwenOverrideAccepted = false;
2116
- }
2117
- this.revision++;
2118
- this.write(`Temporary key cleared. Environment credentials are unchanged.
2119
- `);
2120
- return true;
2121
- }
2122
- const connection = provider === "qwen" ? await selectQwenConnection(input) : undefined;
2123
- if (provider === "qwen" && !connection)
2124
- return false;
2125
- if (connection)
2126
- this.write(`Selected Qwen destination: ${qwenEndpoint(connection)}
2127
- This connection will replace Qwen environment settings for this CLI session.
2128
- `);
2129
- let target;
2130
- if (action === "save") {
2131
- try {
2132
- target = await this.entry(provider);
2133
- } catch {
2134
- throw new CredentialSetupError("Secure storage is unavailable. Unlock it and retry, or choose temporary use.");
2135
- }
2136
- }
2137
- let secret = await input.secret("API key (hidden; Enter submits, Ctrl+C cancels): ");
2138
- if (!secret)
2139
- return false;
2140
- try {
2141
- secret = this.validate(secret);
2142
- } catch {
2143
- throw new CredentialSetupError("Invalid API key. Enter printable characters without whitespace.");
2144
- }
2145
- if (connection)
2146
- secret = JSON.stringify({ version: 1, key: secret, connection });
2147
- if (action === "save") {
2148
- try {
2149
- await target.setPassword(secret);
2150
- } catch {
2151
- throw new CredentialSetupError("API key was not saved: secure storage is unavailable or locked. No plaintext fallback was used.");
2152
- }
2153
- this.temporary.delete(provider);
2154
- this.write(`API key saved in the system keychain.
2155
- `);
2156
- } else {
2157
- this.temporary.set(provider, secret);
2158
- this.write(`API key is available only for this CLI session.
2159
- `);
2160
- }
2161
- if (connection) {
2162
- this.qwenManaged = true;
2163
- this.qwenOverrideAccepted = true;
2164
- }
2165
- this.revision++;
2166
- return true;
2167
- }
2168
- async providerEnvironment(provider, input) {
2169
- if (this.environmentKey(provider) && !(provider === "qwen" && this.qwenManaged)) {
2170
- this.destinations.delete(provider);
2171
- this.sources.set(provider, "environment");
2172
- return { ...this.environment };
2173
- }
2174
- if (provider !== "qwen" && this.endpointOverride(provider)) {
2175
- throw new CredentialSetupError("Managed keys require the provider's default endpoint. Remove endpoint overrides or supply an explicit environment key.");
2176
- }
2177
- let key = this.temporary.get(provider);
2178
- if (!key) {
2179
- try {
2180
- key = await this.saved(provider);
2181
- } catch {
2182
- this.write(`Secure storage is unavailable or locked; temporary use remains available.
2183
- `);
2184
- }
2185
- }
2186
- if (!key) {
2187
- this.write(`No API key available for ${provider}.
2188
- `);
2189
- if (!await this.configure(provider, input))
2190
- throw new CredentialSetupError("Credential setup cancelled. No provider request was sent.");
2191
- key = this.temporary.get(provider) ?? await this.saved(provider);
2192
- }
2193
- if (!key)
2194
- throw new CredentialSetupError("No API key available. No provider request was sent.");
2195
- if (provider === "qwen") {
2196
- const record = decodeQwenCredential(key);
2197
- const endpoint = qwenEndpoint(record.connection);
2198
- if (this.endpointOverride(provider) && !this.qwenOverrideAccepted) {
2199
- const useManaged = await input.select("Qwen / Environment conflicts with saved connection", [
2200
- { value: false, label: "Cancel; keep environment settings" },
2201
- { value: true, label: "Use saved connection for this session", detail: endpoint }
2202
- ]);
2203
- if (!useManaged)
2204
- throw new CredentialSetupError("Qwen connection selection cancelled. No provider request was sent.");
2205
- this.qwenOverrideAccepted = true;
2206
- }
2207
- const env = { ...this.environment };
2208
- for (const name2 of ["QWEN_BASE_URL", "QWEN_REGION", "QWEN_WORKSPACE_ID", "QWEN_API_KEY", "DASHSCOPE_API_KEY"])
2209
- delete env[name2];
2210
- this.destinations.set(provider, endpoint);
2211
- env.QWEN_BASE_URL = endpoint;
2212
- env.DASHSCOPE_API_KEY = record.key;
2213
- this.sources.set(provider, this.temporary.has(provider) ? "temporary" : "keychain");
2214
- return env;
2215
- }
2216
- this.sources.set(provider, this.temporary.has(provider) ? "temporary" : "keychain");
2217
- const name = providerDescriptor(provider).credentialNames[0];
2218
- return { ...this.environment, [name]: key };
2219
- }
2220
- clear() {
2221
- this.destinations.clear();
2222
- this.temporary.clear();
2223
- this.sources.clear();
2224
- this.qwenManaged = false;
2225
- this.qwenOverrideAccepted = false;
2226
- }
2227
- }
2228
- async function credentialModel(config, env, modelFactory) {
2229
- const { createProviderModel: createProviderModel2 } = await import("./config-q32jm1e6.js");
2230
- const { wrapLanguageModel } = await import("@zhivex-ai/core");
2231
- const secrets = providerDescriptor(config.provider).credentialNames.map((name) => env[name]).filter((value) => !!value);
2232
- const safeError = (error) => {
2233
- let message = error instanceof Error ? error.message : "Provider request failed.";
2234
- for (const secret of secrets)
2235
- message = message.split(secret).join("[REDACTED]");
2236
- const safe = new Error(message);
2237
- if (error instanceof Error && error.name === "AbortError")
2238
- safe.name = "AbortError";
2239
- return safe;
2240
- };
2241
- const model = (modelFactory ?? createProviderModel2)(config, env);
2242
- return wrapLanguageModel(model, [{
2243
- name: "cli-credential-errors",
2244
- wrapGenerate: async (_context, next) => {
2245
- try {
2246
- return await next();
2247
- } catch (error) {
2248
- throw safeError(error);
2249
- }
2250
- },
2251
- wrapStream: async (_context, next) => {
2252
- let events;
2253
- try {
2254
- events = await next();
2255
- } catch (error) {
2256
- throw safeError(error);
2257
- }
2258
- return async function* () {
2259
- try {
2260
- for await (const event of events) {
2261
- if (event.type === "error")
2262
- throw safeError(event.error);
2263
- yield event;
2264
- }
2265
- } catch (error) {
2266
- throw safeError(error);
2267
- }
2268
- }();
2269
- }
2270
- }]);
2271
- }
2272
-
2273
391
  // src/cli/doctor.ts
2274
- import { constants as fsConstants2 } from "node:fs";
2275
- import { access, lstat as lstat2, stat } from "node:fs/promises";
2276
- 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";
2277
395
 
2278
396
  // src/cli/errors.ts
2279
397
  var cliRecoveryHint = (error, argv) => {
@@ -2347,7 +465,7 @@ var inspectWorkspace = async (workspace) => {
2347
465
  if (!workspaceStat.isDirectory()) {
2348
466
  return diagnostic("workspace", "fail", "Workspace is not a directory.", { path: workspace });
2349
467
  }
2350
- await access(workspace, fsConstants2.R_OK);
468
+ await access(workspace, fsConstants.R_OK);
2351
469
  return diagnostic("workspace", "pass", "Workspace exists and is readable.", { path: workspace });
2352
470
  } catch (error) {
2353
471
  const code = error.code ?? "UNKNOWN";
@@ -2377,7 +495,7 @@ var inspectGit = async (workspace) => {
2377
495
  }
2378
496
  };
2379
497
  var inspectScripts = async (workspace, allowedChecks) => {
2380
- const packagePath = path2.join(workspace, "package.json");
498
+ const packagePath = path.join(workspace, "package.json");
2381
499
  try {
2382
500
  const packageFile = await readRegularFileNoFollow(packagePath, {
2383
501
  label: "package.json",
@@ -2406,17 +524,17 @@ var inspectScripts = async (workspace, allowedChecks) => {
2406
524
  }
2407
525
  };
2408
526
  var isInsidePath = (root, candidate) => {
2409
- const relative = path2.relative(root, candidate);
2410
- 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);
2411
529
  };
2412
530
  var containsSymlink = async (candidate, trustedPrefix) => {
2413
- const parsed = path2.parse(candidate);
2414
- const parts = trustedPrefix ? path2.relative(trustedPrefix, candidate).split(path2.sep).filter(Boolean) : candidate.slice(parsed.root.length).split(path2.sep).filter(Boolean).slice(1);
2415
- 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] ?? "");
2416
534
  for (const part of parts) {
2417
- current = path2.join(current, part);
535
+ current = path.join(current, part);
2418
536
  try {
2419
- if ((await lstat2(current)).isSymbolicLink()) {
537
+ if ((await lstat(current)).isSymbolicLink()) {
2420
538
  return true;
2421
539
  }
2422
540
  } catch (error) {
@@ -2437,7 +555,7 @@ var nearestExistingPath = async (candidate) => {
2437
555
  if (error.code !== "ENOENT") {
2438
556
  throw error;
2439
557
  }
2440
- const parent = path2.dirname(current);
558
+ const parent = path.dirname(current);
2441
559
  if (parent === current) {
2442
560
  throw error;
2443
561
  }
@@ -2453,8 +571,8 @@ var inspectStateDirectory = async (workspace, stateDirectory) => {
2453
571
  });
2454
572
  }
2455
573
  if (isInsidePath(workspace, stateDirectory)) {
2456
- const segments2 = path2.relative(workspace, stateDirectory).split(path2.sep).filter(Boolean);
2457
- const sensitiveSegment = segments2.find(isSensitiveStateSegment);
574
+ const segments = path.relative(workspace, stateDirectory).split(path.sep).filter(Boolean);
575
+ const sensitiveSegment = segments.find(isSensitiveStateSegment);
2458
576
  if (sensitiveSegment) {
2459
577
  return diagnostic("state-directory", "fail", "State directory is inside a sensitive workspace path.", {
2460
578
  path: stateDirectory,
@@ -2478,7 +596,7 @@ var inspectStateDirectory = async (workspace, stateDirectory) => {
2478
596
  writable: false
2479
597
  });
2480
598
  }
2481
- await access(existing.path, fsConstants2.R_OK | fsConstants2.W_OK);
599
+ await access(existing.path, fsConstants.R_OK | fsConstants.W_OK);
2482
600
  return diagnostic("state-directory", "pass", "State directory is safe and writable.", {
2483
601
  path: stateDirectory,
2484
602
  exists: existing.path === stateDirectory,
@@ -2500,9 +618,9 @@ var inspectOperationsStore = async (stateDirectory, storeBackend) => {
2500
618
  migrationAvailable: true
2501
619
  });
2502
620
  }
2503
- const databasePath = path2.join(stateDirectory, "operations.sqlite");
621
+ const databasePath = path.join(stateDirectory, "operations.sqlite");
2504
622
  try {
2505
- const entry = await lstat2(databasePath);
623
+ const entry = await lstat(databasePath);
2506
624
  if (entry.isSymbolicLink() || !entry.isFile()) {
2507
625
  return diagnostic("operations-store", "fail", "SQLite state path is not a regular file.", {
2508
626
  backend: storeBackend,
@@ -2617,7 +735,7 @@ var inspectProjectContext = async (config) => {
2617
735
  }
2618
736
  try {
2619
737
  const workspace = await Workspace.open(config.workspace);
2620
- 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("/");
2621
739
  const bundle = await loadHarnessProjectContext(workspace, {
2622
740
  manifestPath,
2623
741
  requireManifest: manifestPath !== DEFAULT_HARNESS_CONTEXT_MANIFEST
@@ -2706,7 +824,7 @@ var createDoctorReport = async (options = {}, context = {}) => {
2706
824
  const credential = selected ? context.credentialStatus : undefined;
2707
825
  const configured = credential?.configured ?? provider.configured;
2708
826
  const status = invalidRegion || invalidEndpoint || !configured ? selected ? "fail" : "warn" : provider.support === "provisional" ? "warn" : "pass";
2709
- 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.`;
2710
828
  checks.push(diagnostic(`provider:${provider.id}`, status, message, {
2711
829
  provider: provider.id,
2712
830
  selected,
@@ -2910,16 +1028,16 @@ var formatConsoleDiff = (diff, color) => diff.split(`
2910
1028
  class ConsoleAttachments {
2911
1029
  files = new Map;
2912
1030
  list() {
2913
- 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 }));
2914
1032
  }
2915
1033
  clear() {
2916
1034
  this.files.clear();
2917
1035
  }
2918
- remove(path3) {
2919
- return this.files.delete(path3);
1036
+ remove(path2) {
1037
+ return this.files.delete(path2);
2920
1038
  }
2921
- async add(workspace, path3) {
2922
- const file = await workspace.readFile(path3);
1039
+ async add(workspace, path2) {
1040
+ const file = await workspace.readFile(path2);
2923
1041
  const next = new Map(this.files).set(file.path, file);
2924
1042
  if (next.size > 8 || [...next.values()].reduce((sum, value) => sum + Buffer.byteLength(value.content), 0) > 64 * 1024) {
2925
1043
  throw new Error("Attachments exceed 8 files or 64 KiB; use a smaller selection.");
@@ -2943,7 +1061,7 @@ ${JSON.stringify([...this.files.values()])}`;
2943
1061
  }
2944
1062
 
2945
1063
  // src/cli/console.ts
2946
- import { randomUUID as randomUUID2 } from "node:crypto";
1064
+ import { randomUUID } from "node:crypto";
2947
1065
 
2948
1066
  // src/cli/session-management.ts
2949
1067
  var openSessionStoreForConfig = (config) => openCliSessionStore({
@@ -3059,76 +1177,6 @@ var updateIndexedSessionRun = async (config, runId, status) => {
3059
1177
  }
3060
1178
  };
3061
1179
 
3062
- // src/cli/routing.ts
3063
- var resolvedRouting = (options) => {
3064
- return resolveHarnessModelRoutes(options.routes);
3065
- };
3066
- var assertRoutePricingIsSafe = (routes, costBudget) => {
3067
- if (routes.size > 0 && costBudget !== undefined) {
3068
- throw new CliUsageError("Cost budgets cannot be combined with model routes until pricing is configured per role.");
3069
- }
3070
- };
3071
- var withTemporaryHarnessProfiles = async (profiles, configure, operation) => {
3072
- await configure(profiles);
3073
- try {
3074
- return await operation();
3075
- } finally {
3076
- await configure([]);
3077
- }
3078
- };
3079
-
3080
- // src/cli/configured-harness.ts
3081
- import path3 from "node:path";
3082
- var createConfiguredHarness = async (options, extraProfiles = [], persistedRoutes, credentials) => {
3083
- const routes = persistedRoutes ?? resolvedRouting(options);
3084
- const resolvedConfig = resolveHarnessConfig(options);
3085
- if (options.usageLimitUsd !== undefined && resolvedConfig.costBudget)
3086
- throw new CliUsageError("Choose one monetary policy: --usage-limit-usd or the legacy cost budget.");
3087
- assertRoutePricingIsSafe(routes, resolvedConfig.costBudget);
3088
- const profiles = [...new Set([
3089
- ...options.subagentProfiles ?? [],
3090
- ...extraProfiles,
3091
- ...routes.keys()
3092
- ])];
3093
- const quietTelemetry = options.json || options.jsonl;
3094
- const providerEnv = credentials ? await credentials.store.providerEnvironment(resolvedConfig.provider, credentials.input) : undefined;
3095
- const transportEnv = { ...process.env };
3096
- const bindTransport = (env) => {
3097
- for (const name of ["QWEN_BASE_URL", "QWEN_REGION", "QWEN_WORKSPACE_ID"]) {
3098
- if (env[name] === undefined)
3099
- delete transportEnv[name];
3100
- else
3101
- transportEnv[name] = env[name];
3102
- }
3103
- };
3104
- if (providerEnv && resolvedConfig.provider === "qwen")
3105
- bindTransport(providerEnv);
3106
- const routeModels = {};
3107
- for (const [role, route] of routes) {
3108
- const routeEnv = credentials ? await credentials.store.providerEnvironment(route.provider, credentials.input) : process.env;
3109
- if (route.provider === "qwen")
3110
- bindTransport(routeEnv);
3111
- routeModels[role] = credentials ? await credentialModel(route, routeEnv) : createProviderModel(route, routeEnv);
3112
- }
3113
- const compactionEnv = resolvedConfig.compaction.model && credentials ? await credentials.store.providerEnvironment(resolvedConfig.compaction.model.provider, credentials.input) : undefined;
3114
- if (compactionEnv && resolvedConfig.compaction.model?.provider === "qwen")
3115
- bindTransport(compactionEnv);
3116
- const compactionModelInstance = resolvedConfig.compaction.model && compactionEnv ? await credentialModel(resolvedConfig.compaction.model, compactionEnv) : undefined;
3117
- const harness = await createHarness({
3118
- ...options,
3119
- usageAccounting: {
3120
- ...options.pricingFile ? { pricing: usagePricingSchema.parse(JSON.parse((await readRegularFileNoFollow(path3.resolve(options.pricingFile), { maxBytes: 128 * 1024, label: "Usage pricing" })).contents.toString("utf8"))) } : {},
3121
- ...options.usageLimitUsd !== undefined ? { limitUsd: options.usageLimitUsd } : {}
3122
- },
3123
- subagentProfiles: profiles,
3124
- subagentModels: routeModels,
3125
- ...compactionModelInstance ? { compactionModelInstance } : {},
3126
- ...providerEnv ? { env: transportEnv, modelInstance: await credentialModel(resolvedConfig, providerEnv) } : {},
3127
- onTelemetryEvent: orchestrationObserver(quietTelemetry)
3128
- });
3129
- return { harness, routes };
3130
- };
3131
-
3132
1180
  // src/models/compaction-recommendations.ts
3133
1181
  import { recommendAuxiliaryModel } from "@zhivex-ai/core";
3134
1182
 
@@ -3327,7 +1375,7 @@ async function handleConsoleCompaction(command, deps) {
3327
1375
  const provider = value.slice(0, separator);
3328
1376
  const model = value.slice(separator + 1);
3329
1377
  if (separator < 1 || !PROVIDERS.includes(provider) || !/^[a-zA-Z0-9][a-zA-Z0-9._:/-]{0,159}$/.test(model)) {
3330
- 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.");
3331
1379
  return true;
3332
1380
  }
3333
1381
  await deps.replaceOptions({ ...deps.options, compactionProvider: provider, compactionModel: model });
@@ -3426,7 +1474,7 @@ Stopping the active operation; waiting for durable state and cleanup.
3426
1474
  const consoleApprovals = async (approvals, context) => {
3427
1475
  readline.stopBackground();
3428
1476
  try {
3429
- 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);
3430
1478
  } finally {
3431
1479
  if (activeController && !activeController.signal.aborted)
3432
1480
  readline.startBackground();
@@ -3515,14 +1563,37 @@ Stopping the active operation; waiting for durable state and cleanup.
3515
1563
  session = await sessionStore.updateRun(session.sessionId, state.runId, { status: "running" });
3516
1564
  let result;
3517
1565
  try {
3518
- result = await abortable((abortSignal) => runHarness(harness, {
3519
- state,
3520
- toolExecution: { ...harnessToolExecution },
3521
- abortSignal,
3522
- approvals: approvalResponses(state.pendingApprovals, approve, approve ? "Approved inline in chat." : "Denied inline in chat.")
3523
- }, {
3524
- onEvent,
3525
- 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
+ });
3526
1597
  }));
3527
1598
  } catch {
3528
1599
  flushToolActivity(tracker);
@@ -4083,7 +2154,7 @@ ${sanitizeTerminalText(prompt)}
4083
2154
  }
4084
2155
  readline.rememberPrompt(prompt, literalInput || command === "/paste");
4085
2156
  prompt = await attachments.prompt(harness.workspace, prompt);
4086
- const runId = `run_${randomUUID2()}`;
2157
+ const runId = `run_${randomUUID()}`;
4087
2158
  session = await sessionStore.appendRun(session.sessionId, {
4088
2159
  runId,
4089
2160
  provider: harness.config.provider,
@@ -4096,7 +2167,7 @@ ${sanitizeTerminalText(prompt)}
4096
2167
  let markedRunning = false;
4097
2168
  let result;
4098
2169
  try {
4099
- result = await abortable((abortSignal) => runHarness(harness, messages.length === 0 ? {
2170
+ result = await abortable((abortSignal) => observeCliPolicy({}, tracker, () => runHarness(harness, messages.length === 0 ? {
4100
2171
  runId,
4101
2172
  toolExecution: { ...harnessToolExecution },
4102
2173
  abortSignal: AbortSignal.any([abortSignal, progress.signal]),
@@ -4142,7 +2213,7 @@ ${sanitizeTerminalText(prompt)}
4142
2213
  await onEvent(event);
4143
2214
  },
4144
2215
  resolveApprovals: consoleApprovals
4145
- }));
2216
+ })));
4146
2217
  } catch (error) {
4147
2218
  flushToolActivity(tracker);
4148
2219
  tracker.markdown?.flush();
@@ -4213,7 +2284,7 @@ var runOnce = async (options) => {
4213
2284
  const tracker = { streamedText: false };
4214
2285
  let closeAttempted = false;
4215
2286
  try {
4216
- const result = await runHarness(harness, {
2287
+ const result = await observeCliPolicy(options, tracker, () => runHarness(harness, {
4217
2288
  prompt: options.prompt,
4218
2289
  toolExecution: { ...harnessToolExecution },
4219
2290
  scope: harness.config.scope,
@@ -4221,8 +2292,8 @@ var runOnce = async (options) => {
4221
2292
  ...options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}
4222
2293
  }, {
4223
2294
  onEvent: streamSink(options, tracker),
4224
- resolveApprovals: terminalApprovalResolver(options.approvalMode ?? options.yes)
4225
- });
2295
+ resolveApprovals: terminalApprovalResolver(options.approvalMode ?? options.yes, undefined, undefined, harness)
2296
+ }));
4226
2297
  closeAttempted = true;
4227
2298
  await harness.close();
4228
2299
  printTerminalResult(result, harness, options, tracker);
@@ -4361,13 +2432,21 @@ ${summarizeApproval(approval)}
4361
2432
  const tracker = { streamedText: false };
4362
2433
  let harnessCloseAttempted = false;
4363
2434
  try {
4364
- const result = await runHarness(harness, {
4365
- state,
4366
- toolExecution: { ...harnessToolExecution },
4367
- approvals: approvalResponses(state.pendingApprovals, approve, approve ? "Approved by resume --approve." : "Denied by resume --deny.")
4368
- }, {
4369
- onEvent: streamSink(options, tracker),
4370
- 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
+ });
4371
2450
  });
4372
2451
  await updateIndexedSessionRun(harness.config, result.state.runId, result.status);
4373
2452
  harnessCloseAttempted = true;
@@ -4395,6 +2474,505 @@ ${summarizeApproval(approval)}
4395
2474
  }
4396
2475
  };
4397
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
+
4398
2976
  // src/cli/run-management.ts
4399
2977
  var printRunsDocument = (document, json) => {
4400
2978
  if (json || !document || typeof document !== "object") {
@@ -4447,6 +3025,11 @@ var manageRuns = async (options) => {
4447
3025
  case "inspect":
4448
3026
  document = await inspectHarnessRun(persistence.store, config, options.runId);
4449
3027
  break;
3028
+ case "report": {
3029
+ const report = await governanceReportCli(options, config, persistence.store);
3030
+ process.stdout.write(report);
3031
+ return;
3032
+ }
4450
3033
  case "export": {
4451
3034
  const inspection = await inspectHarnessRun(persistence.store, config, options.runId);
4452
3035
  document = { ...inspection, kind: "run-export" };
@@ -4483,11 +3066,11 @@ var manageRuns = async (options) => {
4483
3066
  };
4484
3067
 
4485
3068
  // src/cli/changes.ts
4486
- import path4 from "node:path";
3069
+ import path3 from "node:path";
4487
3070
  var MAX_CHANGE_ENVELOPE_JSON_BYTES = 4 * 1024 * 1024;
4488
3071
  var MAX_CHANGE_ARTIFACT_BYTES = 64 * 1024 * 1024;
4489
3072
  var readStableCliArtifact = async (filePath, maxBytes) => {
4490
- const absolute = path4.resolve(process.cwd(), filePath);
3073
+ const absolute = path3.resolve(process.cwd(), filePath);
4491
3074
  try {
4492
3075
  return (await readRegularFileNoFollow(absolute, {
4493
3076
  label: `Change artifact ${filePath}`,
@@ -4738,162 +3321,138 @@ async function readBackupTransport(source, legacyMaxBytes) {
4738
3321
  return root;
4739
3322
  }
4740
3323
 
4741
- // src/persistence/workspace-checkpoints.ts
4742
- import { createHash as createHash2, randomUUID as randomUUID3 } from "node:crypto";
4743
- import { z as z3 } from "zod";
4744
- var fileSchema = z3.strictObject({
4745
- path: workspaceFilePathSchema,
4746
- digest: fileDigestSchema,
4747
- content: z3.string().max(65536),
4748
- mode: z3.number().int().min(0).max(511)
4749
- }).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.");
4750
- var workspaceCheckpointSchema = z3.strictObject({
4751
- id: z3.string().uuid(),
4752
- sessionId: z3.string(),
4753
- turnId: z3.string(),
4754
- files: z3.array(fileSchema).min(1).max(20)
4755
- });
4756
- var workspaceRestoreOperationSchema = z3.strictObject({
4757
- id: z3.string().uuid(),
4758
- checkpoint: workspaceCheckpointSchema,
4759
- proposalId: fileDigestSchema,
4760
- expected: z3.record(z3.string(), fileDigestSchema),
4761
- stage: z3.enum(["prepared", "forking", "forked", "applying", "applied", "completed"]),
4762
- forkSessionId: z3.string().optional()
4763
- });
4764
-
4765
3324
  // src/persistence/state-backup.ts
4766
- import { createHash as createHash3, randomUUID as randomUUID4 } from "node:crypto";
4767
- import { constants as fsConstants3 } from "node:fs";
4768
- import { chmod, link, lstat as lstat3, open as open2, realpath, unlink as unlink2 } from "node:fs/promises";
4769
- import path5 from "node:path";
4770
- 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";
4771
3330
  import { normalizeAgentRunState } from "@zhivex-ai/agents";
4772
3331
  import { createSqliteAgentRunStore } from "@zhivex-ai/agents/ops";
4773
3332
  var HARNESS_STATE_BACKUP_SCHEMA_VERSION = 1;
4774
3333
  var HARNESS_STATE_BACKUP_MAX_BYTES = 64 * 1024 * 1024;
4775
3334
  var terminalRunStatuses = new Set(["completed", "failed", "cancelled", "timed_out"]);
4776
3335
  var terminalSessionStatuses = new Set(["completed", "failed", "cancelled", "timed_out"]);
4777
- var digestSchema = z4.string().regex(/^sha256:[a-f0-9]{64}$/);
4778
- var bindingKeySchema = z4.string().regex(/^[a-f0-9]{64}$/);
4779
- var jsonRecordSchema = z4.record(z4.string(), z4.unknown());
4780
- var durableScopeSchema = z4.object({
4781
- tenantId: z4.string().min(1),
4782
- userId: z4.string().min(1).optional(),
4783
- 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()
4784
3343
  }).strict();
4785
- var journalEntrySchema = z4.object({
4786
- runId: z4.string().min(1),
3344
+ var journalEntrySchema = z3.object({
3345
+ runId: z3.string().min(1),
4787
3346
  scope: durableScopeSchema,
4788
- toolCallId: z4.string().min(1),
4789
- toolName: z4.string().min(1),
4790
- status: z4.enum(["pending", "running", "completed", "failed"]),
4791
- idempotencyKey: z4.string().min(1),
4792
- revision: z4.number().int().nonnegative(),
4793
- input: z4.json().optional(),
4794
- output: z4.json().optional(),
4795
- error: z4.object({ message: z4.string() }).strict().optional(),
4796
- startedAt: z4.number().nonnegative().optional(),
4797
- completedAt: z4.number().nonnegative().optional(),
4798
- 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()
4799
3358
  }).passthrough();
4800
- var runRecordSchema = z4.object({
4801
- key: z4.string().min(1).max(1024),
3359
+ var runRecordSchema = z3.object({
3360
+ key: z3.string().min(1).max(1024),
4802
3361
  state: jsonRecordSchema,
4803
- updatedAt: z4.number().int().nonnegative()
3362
+ updatedAt: z3.number().int().nonnegative()
4804
3363
  }).strict();
4805
- var journalRecordSchema = z4.object({
4806
- runKey: z4.string().min(1).max(1024),
4807
- 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),
4808
3367
  entry: journalEntrySchema,
4809
- revision: z4.number().int().nonnegative(),
4810
- updatedAt: z4.number().int().nonnegative()
3368
+ revision: z3.number().int().nonnegative(),
3369
+ updatedAt: z3.number().int().nonnegative()
4811
3370
  }).strict();
4812
- var idempotencyRecordSchema = z4.object({
4813
- key: z4.string().min(1).max(1024),
4814
- runKey: z4.string().min(1).max(1024),
4815
- 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()
4816
3375
  }).strict();
4817
- var parentRecordSchema = z4.object({
4818
- runKey: z4.string().min(1).max(1024),
4819
- parentRunKey: z4.string().min(1).max(1024),
4820
- 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()
4821
3380
  }).strict();
4822
- var memoryRecordSchema = z4.object({
4823
- key: z4.string().min(1).max(1024),
4824
- messages: z4.array(z4.unknown()),
4825
- 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()
4826
3385
  }).strict();
4827
- var sessionRecordSchema = z4.object({
4828
- sessionId: z4.string().min(1).max(260),
3386
+ var sessionRecordSchema = z3.object({
3387
+ sessionId: z3.string().min(1).max(260),
4829
3388
  workspaceKey: bindingKeySchema,
4830
3389
  scopeKey: bindingKeySchema,
4831
- title: z4.string().nullable(),
4832
- parentSessionId: z4.string().nullable(),
4833
- forkedFromTurnId: z4.string().nullable(),
4834
- revision: z4.number().int().nonnegative(),
4835
- activitySequence: z4.number().int().nonnegative(),
4836
- createdAt: z4.number().int().nonnegative(),
4837
- updatedAt: z4.number().int().nonnegative(),
4838
- archivedAt: z4.number().int().nonnegative().nullable(),
4839
- deletedAt: z4.number().int().nonnegative().nullable(),
4840
- 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()
4841
3400
  }).strict();
4842
- var sessionRunRecordSchema = z4.object({
4843
- turnId: z4.string().min(1).max(260),
4844
- sourceTurnId: z4.string().nullable(),
4845
- sessionId: z4.string().min(1).max(260),
4846
- ordinal: z4.number().int().nonnegative(),
4847
- runId: z4.string().min(1).max(260),
4848
- provider: z4.string().min(1).max(260),
4849
- model: z4.string().min(1).max(260),
4850
- role: z4.string().nullable(),
4851
- status: z4.string().min(1),
4852
- createdAt: z4.number().int().nonnegative(),
4853
- updatedAt: z4.number().int().nonnegative(),
4854
- completedAt: z4.number().int().nonnegative().nullable(),
4855
- 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()
4856
3415
  }).strict();
4857
- var checkpointRecordSchema = z4.discriminatedUnion("kind", [
4858
- z4.object({
4859
- id: z4.string().uuid(),
3416
+ var checkpointRecordSchema = z3.discriminatedUnion("kind", [
3417
+ z3.object({
3418
+ id: z3.string().uuid(),
4860
3419
  workspaceKey: bindingKeySchema,
4861
3420
  scopeKey: bindingKeySchema,
4862
- kind: z4.literal("checkpoint"),
3421
+ kind: z3.literal("checkpoint"),
4863
3422
  body: workspaceCheckpointSchema
4864
3423
  }).strict(),
4865
- z4.object({
4866
- id: z4.string().uuid(),
3424
+ z3.object({
3425
+ id: z3.string().uuid(),
4867
3426
  workspaceKey: bindingKeySchema,
4868
3427
  scopeKey: bindingKeySchema,
4869
- kind: z4.literal("restore"),
3428
+ kind: z3.literal("restore"),
4870
3429
  body: workspaceRestoreOperationSchema
4871
3430
  }).strict()
4872
3431
  ]);
4873
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)";
4874
- var stateBackupPayloadSchema = z4.object({
4875
- schemaVersion: z4.literal(HARNESS_STATE_BACKUP_SCHEMA_VERSION),
4876
- kind: z4.literal("state-backup"),
4877
- createdAt: z4.iso.datetime({ precision: 3 }),
4878
- 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({
4879
3438
  workspaceKey: bindingKeySchema,
4880
3439
  scopeKey: bindingKeySchema,
4881
- scopePrefix: z4.string().min(1).max(1024)
3440
+ scopePrefix: z3.string().min(1).max(1024)
4882
3441
  }).strict(),
4883
- schemas: z4.object({
4884
- operations: z4.literal(HARNESS_OPERATIONS_SCHEMA_VERSION),
4885
- 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)
4886
3445
  }).strict(),
4887
- records: z4.object({
4888
- runs: z4.array(runRecordSchema),
4889
- toolJournal: z4.array(journalRecordSchema),
4890
- idempotency: z4.array(idempotencyRecordSchema),
4891
- parents: z4.array(parentRecordSchema),
4892
- memory: z4.array(memoryRecordSchema),
4893
- sessions: z4.array(sessionRecordSchema),
4894
- sessionRuns: z4.array(sessionRunRecordSchema),
4895
- workspaceCheckpoints: z4.array(checkpointRecordSchema).max(100).optional(),
4896
- 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()
4897
3456
  }).strict()
4898
3457
  }).strict();
4899
3458
  var stateBackupBundleSchema = stateBackupPayloadSchema.extend({ checksum: digestSchema }).strict();
@@ -4905,12 +3464,12 @@ var canonicalJson = (value) => {
4905
3464
  return `{${Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`).join(",")}}`;
4906
3465
  };
4907
3466
  var checksumPayload = (payload) => {
4908
- const hash = createHash3("sha256");
3467
+ const hash = createHash4("sha256");
4909
3468
  for (const chunk of canonicalChunks(payload))
4910
3469
  hash.update(chunk);
4911
3470
  return `sha256:${hash.digest("hex")}`;
4912
3471
  };
4913
- 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");
4914
3473
  var scopeValue = (config) => `${config.scope.tenantId}\x00${config.scope.userId ?? ""}\x00${config.scope.namespace ?? ""}`;
4915
3474
  var scopePrefix = (config) => `${encodeURIComponent(config.scope.namespace ?? "default")}:${encodeURIComponent(config.scope.tenantId)}:${encodeURIComponent(config.scope.userId ?? "*")}:`;
4916
3475
  var stateMatchesScope = (config, state) => {
@@ -4949,7 +3508,7 @@ var validateBudgetLedger = (config, run, identities) => {
4949
3508
  return identity;
4950
3509
  };
4951
3510
  var bindingForConfig = async (config) => ({
4952
- workspaceKey: stableKey("workspace", await realpath(path5.resolve(config.workspace))),
3511
+ workspaceKey: stableKey("workspace", await realpath(path4.resolve(config.workspace))),
4953
3512
  scopeKey: stableKey("scope", scopeValue(config)),
4954
3513
  scopePrefix: scopePrefix(config)
4955
3514
  });
@@ -4974,7 +3533,7 @@ var prepareDatabase = async (config) => {
4974
3533
  scope: config.scope
4975
3534
  });
4976
3535
  sessions.close();
4977
- return path5.join(await realpath(config.stateDirectory), HARNESS_SQLITE_FILE);
3536
+ return path4.join(await realpath(config.stateDirectory), HARNESS_SQLITE_FILE);
4978
3537
  };
4979
3538
  var locateExistingDatabase = async (config) => {
4980
3539
  if (config.storeBackend !== "sqlite") {
@@ -4989,9 +3548,9 @@ var locateExistingDatabase = async (config) => {
4989
3548
  return;
4990
3549
  throw error;
4991
3550
  }
4992
- const databasePath = path5.join(canonicalStateDirectory, HARNESS_SQLITE_FILE);
3551
+ const databasePath = path4.join(canonicalStateDirectory, HARNESS_SQLITE_FILE);
4993
3552
  try {
4994
- const entry = await lstat3(databasePath);
3553
+ const entry = await lstat2(databasePath);
4995
3554
  if (entry.isSymbolicLink() || !entry.isFile()) {
4996
3555
  throw new HarnessWorkspaceError(`The SQLite state path must be a real file: ${databasePath}.`);
4997
3556
  }
@@ -5111,7 +3670,7 @@ var readPayload = async (config, recordedWorkspace, accessLease) => {
5111
3670
  const memoryKeys = new Set(runs.map((run) => `${binding.scopePrefix}${String(run.state.runId)}`));
5112
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) => ({
5113
3672
  key: row.memory_key,
5114
- messages: z4.array(z4.unknown()).parse(JSON.parse(row.messages_json)),
3673
+ messages: z3.array(z3.unknown()).parse(JSON.parse(row.messages_json)),
5115
3674
  updatedAt: row.updated_at_ms
5116
3675
  }));
5117
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);
@@ -5189,11 +3748,11 @@ var createHarnessStateBackup = async (config) => {
5189
3748
  return stateBackupBundleSchema.parse({ ...payload, checksum: checksumPayload(payload) });
5190
3749
  };
5191
3750
  var writePrivateBackup = async (target, contents) => {
5192
- const requested = path5.resolve(target);
5193
- const parent = path5.dirname(requested);
3751
+ const requested = path4.resolve(target);
3752
+ const parent = path4.dirname(requested);
5194
3753
  let parentHandle;
5195
3754
  try {
5196
- parentHandle = await open2(parent, fsConstants3.O_RDONLY | fsConstants3.O_NOFOLLOW);
3755
+ parentHandle = await open(parent, fsConstants2.O_RDONLY | fsConstants2.O_NOFOLLOW);
5197
3756
  if (!(await parentHandle.stat()).isDirectory()) {
5198
3757
  throw new Error("Backup parent is not a directory.");
5199
3758
  }
@@ -5203,12 +3762,12 @@ var writePrivateBackup = async (target, contents) => {
5203
3762
  cause: error
5204
3763
  });
5205
3764
  }
5206
- const staged = path5.join(parent, `.zhivex-state-backup-${randomUUID4()}.tmp`);
3765
+ const staged = path4.join(parent, `.zhivex-state-backup-${randomUUID2()}.tmp`);
5207
3766
  let handle;
5208
3767
  let published = false;
5209
3768
  let stagedExists = false;
5210
3769
  try {
5211
- 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);
5212
3771
  stagedExists = true;
5213
3772
  let pending = [];
5214
3773
  let pendingBytes = 0;
@@ -5229,19 +3788,19 @@ var writePrivateBackup = async (target, contents) => {
5229
3788
  handle = undefined;
5230
3789
  await link(staged, requested);
5231
3790
  published = true;
5232
- await unlink2(staged);
3791
+ await unlink(staged);
5233
3792
  stagedExists = false;
5234
3793
  await parentHandle.sync();
5235
3794
  } catch (error) {
5236
3795
  if (published)
5237
- await unlink2(requested).catch(() => {
3796
+ await unlink(requested).catch(() => {
5238
3797
  return;
5239
3798
  });
5240
3799
  throw error;
5241
3800
  } finally {
5242
3801
  await handle?.close();
5243
3802
  if (stagedExists)
5244
- await unlink2(staged).catch(() => {
3803
+ await unlink(staged).catch(() => {
5245
3804
  return;
5246
3805
  });
5247
3806
  await parentHandle.close();
@@ -5259,7 +3818,7 @@ var exportHarnessStateBackup = async (config, target) => {
5259
3818
  return {
5260
3819
  schemaVersion: HARNESS_STATE_BACKUP_SCHEMA_VERSION,
5261
3820
  kind: "state-export",
5262
- path: path5.resolve(target),
3821
+ path: path4.resolve(target),
5263
3822
  checksum: bundle.checksum,
5264
3823
  counts: Object.fromEntries(Object.entries(bundle.records).map(([name, rows]) => [name, rows?.length ?? 0]))
5265
3824
  };
@@ -5267,7 +3826,7 @@ var exportHarnessStateBackup = async (config, target) => {
5267
3826
  var readHarnessStateBackup = async (source) => {
5268
3827
  let input;
5269
3828
  try {
5270
- input = await readBackupTransport(path5.resolve(source), HARNESS_STATE_BACKUP_MAX_BYTES);
3829
+ input = await readBackupTransport(path4.resolve(source), HARNESS_STATE_BACKUP_MAX_BYTES);
5271
3830
  } catch (error) {
5272
3831
  if (error instanceof SyntaxError) {
5273
3832
  throw new HarnessStateConflictError("Harness state backup is not valid JSON.", { cause: error });
@@ -5393,7 +3952,7 @@ var validateBundleBinding = async (config, bundle) => {
5393
3952
  const turn = bundle.records.sessionRuns.find((run) => run.turnId === checkpoint.turnId && run.sessionId === checkpoint.sessionId);
5394
3953
  if (!turn || !terminalSessionStatuses.has(turn.status))
5395
3954
  throw new HarnessStateConflictError("Checkpoint targets a missing terminal turn.");
5396
- 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)) {
5397
3956
  throw new HarnessStateConflictError("Checkpoint content identity is invalid.");
5398
3957
  }
5399
3958
  if (record.kind === "restore") {
@@ -5637,6 +4196,134 @@ var manageState = async (options) => {
5637
4196
  `);
5638
4197
  };
5639
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
+
5640
4327
  // src/cli/task-input.ts
5641
4328
  var MAX_STDIN_TASK_BYTES = 1024 * 1024;
5642
4329
  async function readStdinTask(input = process.stdin) {
@@ -5673,7 +4360,7 @@ var main = async (argv = process.argv.slice(2)) => {
5673
4360
  parsedOptions = { ...parsedOptions, prompt: await readStdinTask() };
5674
4361
  }
5675
4362
  if (parsedOptions.serviceFile) {
5676
- const { runServiceCli } = await import("./service-client-cli-gs78wrxe.js");
4363
+ const { runServiceCli } = await import("./service-client-cli-3365rfav.js");
5677
4364
  await runServiceCli(parsedOptions, annotateCliStreamError);
5678
4365
  return;
5679
4366
  }
@@ -5703,6 +4390,9 @@ var main = async (argv = process.argv.slice(2)) => {
5703
4390
  case "providers":
5704
4391
  listProviders(options.json);
5705
4392
  return;
4393
+ case "policy":
4394
+ await inspectCliPolicy(options);
4395
+ return;
5706
4396
  case "doctor":
5707
4397
  await doctor(options);
5708
4398
  return;
@@ -5718,6 +4408,12 @@ var main = async (argv = process.argv.slice(2)) => {
5718
4408
  case "sessions":
5719
4409
  await manageSessions(options);
5720
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
+ }
5721
4417
  case "changes":
5722
4418
  await manageChanges(options);
5723
4419
  return;