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