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