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