@zhivex-ai/harness 1.2.0-rc.8 → 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 +43 -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 +1096 -609
- 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 +11 -1
- package/dist/{cli-7nj89dq0.js → cli-3jmy4h5h.js} +385 -217
- 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-hkxakkng.js → cli-entry-c2f58zj2.js} +998 -210
- 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/compaction.d.ts +1 -1
- package/dist/context/semantic-compaction.d.ts +1 -1
- package/dist/context/task-memory.d.ts +2 -2
- package/dist/hostile-repository-demo.js +950 -509
- package/dist/index.js +1803 -1132
- 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/progress-monitor.d.ts +6 -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 +1112 -639
- package/dist/{service-client-cli-s3ee2hrj.js → service-client-cli-yx56n0hc.js} +66 -8
- package/dist/tools/dependency-read.d.ts +51 -4
- package/dist/tools/execution.d.ts +8 -8
- package/dist/tools/workspace.d.ts +27 -16
- package/dist/verify-historical-migrations.js +1122 -638
- package/dist/workspace/file-security.d.ts +6 -0
- package/dist/zhx.js +2 -2
- package/docs/CLI.md +75 -23
- package/docs/CONTEXT_ENGINEERING.md +25 -5
- 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 +55 -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,103 +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 dependency types",
|
|
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: "reads",
|
|
1040
|
-
search_files: "searches",
|
|
1041
|
-
search_many: "searches",
|
|
1042
|
-
list_files: "listings",
|
|
1043
|
-
apply_patch: "edits",
|
|
1044
|
-
apply_reviewed_edits: "edits",
|
|
1045
|
-
apply_reviewed_replacement: "edits",
|
|
1046
|
-
git_diff: "diffs"
|
|
1047
|
-
};
|
|
1048
|
-
|
|
1049
|
-
class ToolActivity {
|
|
1050
|
-
write;
|
|
1051
|
-
tty;
|
|
1052
|
-
columns;
|
|
1053
|
-
active = 0;
|
|
1054
|
-
visible = false;
|
|
1055
|
-
label = "Working";
|
|
1056
|
-
completed = new Map;
|
|
1057
|
-
started = Date.now();
|
|
1058
|
-
timer;
|
|
1059
|
-
constructor(write, tty, columns = () => 80) {
|
|
1060
|
-
this.write = write;
|
|
1061
|
-
this.tty = tty;
|
|
1062
|
-
this.columns = columns;
|
|
1063
|
-
}
|
|
1064
|
-
phase(label, _step) {
|
|
1065
|
-
this.clear();
|
|
1066
|
-
this.label = label;
|
|
1067
|
-
this.begin();
|
|
1068
|
-
}
|
|
1069
|
-
start(name) {
|
|
1070
|
-
this.active++;
|
|
1071
|
-
this.label = labels[name ?? ""] ?? "Running a tool";
|
|
1072
|
-
this.begin();
|
|
1073
|
-
}
|
|
1074
|
-
finish(name, success = true) {
|
|
1075
|
-
this.active = Math.max(0, this.active - 1);
|
|
1076
|
-
if (success && !["run_check", "run_environment_command"].includes(name) && !name.startsWith("verify_and_apply_")) {
|
|
1077
|
-
const label = summaries[name] ?? "other operations";
|
|
1078
|
-
this.completed.set(label, (this.completed.get(label) ?? 0) + 1);
|
|
1079
|
-
}
|
|
1080
|
-
this.label = this.active ? this.label : "Waiting for model response";
|
|
1081
|
-
this.begin();
|
|
1082
|
-
}
|
|
1083
|
-
begin() {
|
|
1084
|
-
this.started = Date.now();
|
|
1085
|
-
this.render();
|
|
1086
|
-
if (this.tty && !this.timer) {
|
|
1087
|
-
this.timer = setInterval(() => this.render(), 250);
|
|
1088
|
-
this.timer.unref();
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
render() {
|
|
1092
|
-
if (!this.tty)
|
|
1093
|
-
return;
|
|
1094
|
-
const elapsed = Math.floor((Date.now() - this.started) / 1000);
|
|
1095
|
-
const done = [...this.completed.values()].reduce((sum, count) => sum + count, 0);
|
|
1096
|
-
const text = `… ${this.label}${elapsed ? ` · ${elapsed}s` : ""}${done ? ` · ${done} tools done` : ""}`;
|
|
1097
|
-
this.write(`\r\x1B[2K${Array.from(text).slice(0, Math.max(1, this.columns() - 1)).join("")}`);
|
|
1098
|
-
this.visible = true;
|
|
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
|
-
|
|
1118
1036
|
// src/cli/terminal/terminal-table.ts
|
|
1119
1037
|
var segments = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
1120
1038
|
var graphemes = (value) => [...segments.segment(value)].map((item) => item.segment);
|
|
@@ -1395,6 +1313,7 @@ var harnessConfigInput = (config) => ({
|
|
|
1395
1313
|
schemaVersion: config.schemaVersion,
|
|
1396
1314
|
provider: config.provider,
|
|
1397
1315
|
model: config.model,
|
|
1316
|
+
...config.reasoningEffort ? { reasoningEffort: config.reasoningEffort } : {},
|
|
1398
1317
|
workspace: config.workspace,
|
|
1399
1318
|
stateDirectory: config.stateDirectory,
|
|
1400
1319
|
storeBackend: config.storeBackend,
|
|
@@ -1549,74 +1468,21 @@ var approvalResponses = (approvals, approve, reason) => approvals.map((approval)
|
|
|
1549
1468
|
reason
|
|
1550
1469
|
}));
|
|
1551
1470
|
var terminalApprovalResolver = (automaticallyApprove, ask, ui) => {
|
|
1552
|
-
const grants = new Set;
|
|
1553
1471
|
return async (approvals) => {
|
|
1554
1472
|
if (automaticallyApprove === "restricted")
|
|
1555
1473
|
return approvalResponses(approvals, false, "Denied by restricted approval mode.");
|
|
1556
|
-
if (
|
|
1557
|
-
return approvalResponses(approvals, true, "Approved by
|
|
1474
|
+
if (automaticallyApprove === true || automaticallyApprove === "auto") {
|
|
1475
|
+
return approvalResponses(approvals, true, "Approved by automatic mode.");
|
|
1558
1476
|
}
|
|
1559
|
-
if (!ask && (!process.stdin.isTTY || !process.stdout.isTTY))
|
|
1477
|
+
if (!ask && (!process.stdin.isTTY || !process.stdout.isTTY))
|
|
1560
1478
|
return;
|
|
1561
|
-
}
|
|
1562
1479
|
const readline = ask ? undefined : createInterface({ input: process.stdin, output: process.stdout });
|
|
1563
|
-
const question = ask ?? ((text) => readline.question(text));
|
|
1564
1480
|
try {
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
const stagedSession = ui?.sessionGrants ? new Set(ui.sessionGrants) : undefined;
|
|
1571
|
-
for (const approval of approvals) {
|
|
1572
|
-
if (approval.name === "read_dependency") {
|
|
1573
|
-
let name;
|
|
1574
|
-
try {
|
|
1575
|
-
const args = typeof approval.arguments === "string" ? JSON.parse(approval.arguments) : approval.arguments;
|
|
1576
|
-
if (args && typeof args.package === "string" && /^(?:@[a-z0-9_-]+\/)?[a-z0-9][a-z0-9._-]*$/.test(args.package))
|
|
1577
|
-
name = args.package;
|
|
1578
|
-
} catch {}
|
|
1579
|
-
const key = name ? JSON.stringify([approval.provider, approval.childAgentId ?? "", name]) : undefined;
|
|
1580
|
-
const sessionKey = key && ui?.workspace ? JSON.stringify(["dependency", ui.workspace, key]) : undefined;
|
|
1581
|
-
if (sessionKey && stagedSession?.has(sessionKey)) {
|
|
1582
|
-
responses.push(...approvalResponses([approval], true, "Dependency metadata/types approved for this CLI session."));
|
|
1583
|
-
continue;
|
|
1584
|
-
}
|
|
1585
|
-
if (key && staged.has(key)) {
|
|
1586
|
-
responses.push(...approvalResponses([approval], true, "Dependency read approved for this task."));
|
|
1587
|
-
continue;
|
|
1588
|
-
}
|
|
1589
|
-
process.stderr.write(formatApproval(approval, { detail: "summary" }) + `
|
|
1590
|
-
`);
|
|
1591
|
-
const answer = ui ? await ui.select(`Dependency metadata/types: ${name ?? "invalid package"}`, [
|
|
1592
|
-
{ value: "n", label: "Reject" },
|
|
1593
|
-
{ value: "y", label: "Allow once" },
|
|
1594
|
-
{ value: "t", label: "Allow this package for this task", detail: "Metadata and type declarations only" },
|
|
1595
|
-
...stagedSession ? [{ value: "s", label: "Allow this package for this session", detail: "Metadata and type declarations only" }] : [],
|
|
1596
|
-
{ value: "q", label: "Leave pending" }
|
|
1597
|
-
]) ?? "q" : (await question(`Read dependency ${name ?? "(invalid package)"} metadata/types? [y] once / [t] this task / [n] deny / [q] leave pending: `)).trim().toLowerCase();
|
|
1598
|
-
if (answer === "q")
|
|
1599
|
-
return;
|
|
1600
|
-
const approved = Boolean(key && (["y", "yes", "t"].includes(answer) || answer === "s" && stagedSession && sessionKey));
|
|
1601
|
-
if (approved && answer === "t")
|
|
1602
|
-
staged.add(key);
|
|
1603
|
-
if (approved && answer === "s" && sessionKey)
|
|
1604
|
-
stagedSession?.add(sessionKey);
|
|
1605
|
-
responses.push(...approvalResponses([approval], approved, approved ? "Approved bounded dependency read." : "Dependency read denied."));
|
|
1606
|
-
} else if (automaticallyApprove === true || automaticallyApprove === "auto") {
|
|
1607
|
-
responses.push(...approvalResponses([approval], true, "Approved by automatic mode."));
|
|
1608
|
-
} else {
|
|
1609
|
-
const result = await resolveTerminalApprovals([approval], { ask: question, write: (text) => process.stderr.write(text), ...ui, ...stagedSession ? { sessionGrants: stagedSession } : {} });
|
|
1610
|
-
if (!result)
|
|
1611
|
-
return;
|
|
1612
|
-
responses.push(...result);
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
1615
|
-
for (const key of staged)
|
|
1616
|
-
grants.add(key);
|
|
1617
|
-
for (const key of stagedSession ?? [])
|
|
1618
|
-
ui?.sessionGrants?.add(key);
|
|
1619
|
-
return responses;
|
|
1481
|
+
return await resolveTerminalApprovals(approvals, {
|
|
1482
|
+
ask: ask ?? ((text) => readline.question(text)),
|
|
1483
|
+
write: (text) => process.stderr.write(text),
|
|
1484
|
+
...ui
|
|
1485
|
+
});
|
|
1620
1486
|
} finally {
|
|
1621
1487
|
readline?.close();
|
|
1622
1488
|
}
|
|
@@ -1668,6 +1534,8 @@ var toolActivities = new WeakMap;
|
|
|
1668
1534
|
var lastTerminalFailures = new WeakMap;
|
|
1669
1535
|
var flushToolActivity = (tracker) => toolActivities.get(tracker)?.flush();
|
|
1670
1536
|
var streamSink = (output, tracker, compact = false) => async (event) => {
|
|
1537
|
+
if (!output.json && !output.jsonl)
|
|
1538
|
+
tracker.activityHistory?.observe(event);
|
|
1671
1539
|
if (output.jsonl) {
|
|
1672
1540
|
tracker.sequence = (tracker.sequence ?? 0) + 1;
|
|
1673
1541
|
process.stdout.write(`${serializeStreamEvent(event, tracker.sequence)}
|
|
@@ -1681,7 +1549,7 @@ var streamSink = (output, tracker, compact = false) => async (event) => {
|
|
|
1681
1549
|
if (!activity) {
|
|
1682
1550
|
activity = new ToolActivity((text) => {
|
|
1683
1551
|
process.stderr.write(text);
|
|
1684
|
-
}, 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);
|
|
1685
1553
|
toolActivities.set(tracker, activity);
|
|
1686
1554
|
}
|
|
1687
1555
|
if (event.type === "agent-compaction") {
|
|
@@ -1721,16 +1589,40 @@ var streamSink = (output, tracker, compact = false) => async (event) => {
|
|
|
1721
1589
|
}
|
|
1722
1590
|
if (event.type === "tool-result") {
|
|
1723
1591
|
activity.finish(event.toolResult.toolName, !event.toolResult.isError);
|
|
1724
|
-
|
|
1592
|
+
const detail = compactToolResult(event.toolResult);
|
|
1593
|
+
const check = ["run_check", "run_environment_command"].includes(event.toolResult.toolName) || event.toolResult.toolName.startsWith("verify_and_apply_");
|
|
1594
|
+
if (detail && !check) {
|
|
1595
|
+
activity.pause();
|
|
1596
|
+
tracker.markdown?.flush();
|
|
1597
|
+
if (proseLineOpen.has(tracker)) {
|
|
1598
|
+
process.stdout.write(`
|
|
1599
|
+
`);
|
|
1600
|
+
proseLineOpen.delete(tracker);
|
|
1601
|
+
}
|
|
1602
|
+
process.stderr.write(`${detail}
|
|
1603
|
+
`);
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
if (!check)
|
|
1725
1607
|
return;
|
|
1608
|
+
activity.pause();
|
|
1609
|
+
} else if (["agent-run-finish", "error", "tool-approval-request", "agent-approval-request"].includes(event.type)) {
|
|
1610
|
+
tracker.markdown?.flush();
|
|
1611
|
+
if (proseLineOpen.has(tracker)) {
|
|
1612
|
+
process.stdout.write(`
|
|
1613
|
+
`);
|
|
1614
|
+
proseLineOpen.delete(tracker);
|
|
1615
|
+
}
|
|
1726
1616
|
activity.flush();
|
|
1727
1617
|
} else if (!["provider-data", "finish", "agent-step-start", "agent-step-finish"].includes(event.type)) {
|
|
1728
|
-
activity.
|
|
1618
|
+
activity.pause();
|
|
1729
1619
|
}
|
|
1730
1620
|
}
|
|
1731
1621
|
if (!output.json && event.type === "text-delta") {
|
|
1732
1622
|
if (compact && !conversationStarted.has(tracker)) {
|
|
1733
|
-
process.stdout.write(`
|
|
1623
|
+
process.stdout.write(terminalSupportsColor(Boolean(process.stdout.isTTY)) ? `
|
|
1624
|
+
\x1B[1mZhivex\x1B[0m
|
|
1625
|
+
` : `
|
|
1734
1626
|
Zhivex
|
|
1735
1627
|
`);
|
|
1736
1628
|
conversationStarted.add(tracker);
|
|
@@ -1757,9 +1649,12 @@ Zhivex
|
|
|
1757
1649
|
return;
|
|
1758
1650
|
tracker.markdown?.flush();
|
|
1759
1651
|
proseLineOpen.delete(tracker);
|
|
1760
|
-
|
|
1652
|
+
let line = formatTerminalEvent(event, {
|
|
1761
1653
|
color: terminalSupportsColor(Boolean(process.stderr.isTTY))
|
|
1762
1654
|
});
|
|
1655
|
+
if (compact && event.type === "tool-result" && event.toolResult.isError && line?.endsWith(" · error")) {
|
|
1656
|
+
line = compactToolResult(event.toolResult);
|
|
1657
|
+
}
|
|
1763
1658
|
if ((event.type === "error" || event.type === "agent-run-finish" && event.status === "failed") && line) {
|
|
1764
1659
|
const cause = terminalRunFailure(event.type === "error" ? event.error : event.state.error);
|
|
1765
1660
|
if (lastTerminalFailures.get(tracker) === cause)
|
|
@@ -1803,7 +1698,10 @@ var initializeCli = async (options, context = {}) => {
|
|
|
1803
1698
|
} catch (error) {
|
|
1804
1699
|
throw new CliUsageError(error instanceof Error ? error.message : String(error));
|
|
1805
1700
|
}
|
|
1701
|
+
let reasoningEffort = options.reasoningEffort ?? existing?.reasoningEffort;
|
|
1806
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;
|
|
1807
1705
|
if (!options.json && process.stdin.isTTY && process.stdout.isTTY) {
|
|
1808
1706
|
const input = new ConsoleInput(process.stdin, process.stdout);
|
|
1809
1707
|
try {
|
|
@@ -1811,7 +1709,7 @@ var initializeCli = async (options, context = {}) => {
|
|
|
1811
1709
|
const providers = [...providerAvailability()].sort((a, b) => Number(b.configured) - Number(a.configured) || Number(b.id === provider) - Number(a.id === provider));
|
|
1812
1710
|
const selected = await navigateConsole(input, {
|
|
1813
1711
|
entry: options.provider ? "model" : "provider",
|
|
1814
|
-
current: { provider, model },
|
|
1712
|
+
current: { provider, model, ...reasoningEffort ? { reasoningEffort } : {} },
|
|
1815
1713
|
providers,
|
|
1816
1714
|
sessions: async () => []
|
|
1817
1715
|
});
|
|
@@ -1822,12 +1720,16 @@ var initializeCli = async (options, context = {}) => {
|
|
|
1822
1720
|
}
|
|
1823
1721
|
provider = parseProvider(selected.provider);
|
|
1824
1722
|
model = options.model ?? selected.model;
|
|
1723
|
+
reasoningEffort = options.reasoningEffort ?? selected.reasoningEffort;
|
|
1825
1724
|
}
|
|
1826
1725
|
} finally {
|
|
1827
1726
|
input.close();
|
|
1828
1727
|
}
|
|
1829
1728
|
}
|
|
1830
|
-
|
|
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 } : {} });
|
|
1831
1733
|
const availability = providerAvailability().find((candidate) => candidate.id === provider);
|
|
1832
1734
|
const doctorCommand = `zhx doctor --profile ${profileName}`;
|
|
1833
1735
|
const runCommand = `zhx --profile ${profileName} "inspect this repository"`;
|
|
@@ -1839,7 +1741,8 @@ var initializeCli = async (options, context = {}) => {
|
|
|
1839
1741
|
path: created.path,
|
|
1840
1742
|
schemaVersion: created.profile.schemaVersion,
|
|
1841
1743
|
provider: created.profile.provider,
|
|
1842
|
-
model: created.profile.model
|
|
1744
|
+
model: created.profile.model,
|
|
1745
|
+
reasoningEffort: created.profile.reasoningEffort ?? "default"
|
|
1843
1746
|
},
|
|
1844
1747
|
credential: {
|
|
1845
1748
|
configured: availability?.configured ?? false,
|
|
@@ -1853,18 +1756,18 @@ var initializeCli = async (options, context = {}) => {
|
|
|
1853
1756
|
return true;
|
|
1854
1757
|
}
|
|
1855
1758
|
if (context.onboarding) {
|
|
1856
|
-
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}.
|
|
1857
1760
|
`);
|
|
1858
1761
|
return true;
|
|
1859
1762
|
}
|
|
1860
1763
|
process.stdout.write([
|
|
1861
1764
|
`${options.updateProfile ? "Updated" : "Created"} personal profile ${profileName} at ${created.path}.`,
|
|
1862
|
-
`Provider: ${created.profile.provider} · Model: ${created.profile.model}`,
|
|
1765
|
+
`Provider: ${created.profile.provider} · Model: ${created.profile.model} · Reasoning: ${created.profile.reasoningEffort ?? "default"}`,
|
|
1863
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.`,
|
|
1864
1767
|
"Next:",
|
|
1865
1768
|
` ${doctorCommand}`,
|
|
1866
1769
|
` ${runCommand}`,
|
|
1867
|
-
"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."
|
|
1868
1771
|
].join(`
|
|
1869
1772
|
`) + `
|
|
1870
1773
|
`);
|
|
@@ -1918,6 +1821,7 @@ Options (automation and advanced configuration):
|
|
|
1918
1821
|
--update Update an existing profile (init only)
|
|
1919
1822
|
--provider <${PROVIDERS.join("|")}> Provider (default: openai)
|
|
1920
1823
|
--model <id> Override the default model
|
|
1824
|
+
--reasoning <level> default, none, minimal, low, medium, high, xhigh, max (model-dependent)
|
|
1921
1825
|
--compaction-model <id> Opt into model-assisted compaction with this model
|
|
1922
1826
|
--compaction-provider <id> Compaction provider (defaults to the primary provider)
|
|
1923
1827
|
--route <role=provider[:model]> Route a subagent role; repeatable
|
|
@@ -2323,7 +2227,7 @@ This connection will replace Qwen environment settings for this CLI session.
|
|
|
2323
2227
|
}
|
|
2324
2228
|
}
|
|
2325
2229
|
async function credentialModel(config, env, modelFactory) {
|
|
2326
|
-
const { createProviderModel: createProviderModel2 } = await import("./config-
|
|
2230
|
+
const { createProviderModel: createProviderModel2 } = await import("./config-1k9t4jhh.js");
|
|
2327
2231
|
const { wrapLanguageModel } = await import("@zhivex-ai/core");
|
|
2328
2232
|
const secrets = providerDescriptor(config.provider).credentialNames.map((name) => env[name]).filter((value) => !!value);
|
|
2329
2233
|
const safeError = (error) => {
|
|
@@ -2936,6 +2840,7 @@ var consoleBudgetOptions = (options, env = process.env) => ({
|
|
|
2936
2840
|
var restoreConsoleOptions = (current, saved) => ({
|
|
2937
2841
|
...current,
|
|
2938
2842
|
...saved,
|
|
2843
|
+
reasoningEffort: saved.reasoningEffort ?? "default",
|
|
2939
2844
|
unlimitedTokens: saved.unlimitedTokens ?? false
|
|
2940
2845
|
});
|
|
2941
2846
|
var formatConsoleBudget = (config, usage) => {
|
|
@@ -3098,8 +3003,8 @@ var manageSessions = async (options) => {
|
|
|
3098
3003
|
let document;
|
|
3099
3004
|
switch (options.sessionsCommand) {
|
|
3100
3005
|
case "list": {
|
|
3101
|
-
const
|
|
3102
|
-
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));
|
|
3103
3008
|
const sessions = [];
|
|
3104
3009
|
for (const selected of indexed)
|
|
3105
3010
|
sessions.push(await refreshDurableStatus(selected));
|
|
@@ -3441,6 +3346,7 @@ var chat = async (options) => {
|
|
|
3441
3346
|
const sessionStore = await openSessionStoreForConfig(baseConfig);
|
|
3442
3347
|
const readline = new ConsoleInput(process.stdin, process.stdout);
|
|
3443
3348
|
const attachments = new ConsoleAttachments;
|
|
3349
|
+
const activityHistory = new ActivityHistory;
|
|
3444
3350
|
const credentials = { store: new CliCredentials, input: readline };
|
|
3445
3351
|
let credentialsRevision = credentials.store.revision;
|
|
3446
3352
|
let activeController;
|
|
@@ -3457,9 +3363,11 @@ Stopping the active operation; waiting for durable state and cleanup.
|
|
|
3457
3363
|
const abortable = async (operation) => {
|
|
3458
3364
|
const controller = new AbortController;
|
|
3459
3365
|
activeController = controller;
|
|
3366
|
+
readline.startBackground();
|
|
3460
3367
|
try {
|
|
3461
3368
|
return await operation(controller.signal);
|
|
3462
3369
|
} finally {
|
|
3370
|
+
readline.stopBackground();
|
|
3463
3371
|
activeController = undefined;
|
|
3464
3372
|
}
|
|
3465
3373
|
};
|
|
@@ -3516,6 +3424,37 @@ Stopping the active operation; waiting for durable state and cleanup.
|
|
|
3516
3424
|
sessionStore.close();
|
|
3517
3425
|
throw error;
|
|
3518
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
|
+
};
|
|
3519
3458
|
const refreshSession = async () => {
|
|
3520
3459
|
const refreshed = await sessionStore.get(session.sessionId);
|
|
3521
3460
|
if (!refreshed)
|
|
@@ -3561,6 +3500,7 @@ Stopping the active operation; waiting for durable state and cleanup.
|
|
|
3561
3500
|
session = selected;
|
|
3562
3501
|
attachments.clear();
|
|
3563
3502
|
readline.clearHistory();
|
|
3503
|
+
activityHistory.clear();
|
|
3564
3504
|
messages = state?.messages ?? [];
|
|
3565
3505
|
retainedTasks = taskSources(state?.metadata);
|
|
3566
3506
|
};
|
|
@@ -3572,7 +3512,7 @@ Stopping the active operation; waiting for durable state and cleanup.
|
|
|
3572
3512
|
`);
|
|
3573
3513
|
return;
|
|
3574
3514
|
}
|
|
3575
|
-
const tracker
|
|
3515
|
+
const { tracker, onEvent } = createTracker();
|
|
3576
3516
|
session = await sessionStore.updateRun(session.sessionId, state.runId, { status: "running" });
|
|
3577
3517
|
let result;
|
|
3578
3518
|
try {
|
|
@@ -3582,8 +3522,8 @@ Stopping the active operation; waiting for durable state and cleanup.
|
|
|
3582
3522
|
abortSignal,
|
|
3583
3523
|
approvals: approvalResponses(state.pendingApprovals, approve, approve ? "Approved inline in chat." : "Denied inline in chat.")
|
|
3584
3524
|
}, {
|
|
3585
|
-
onEvent
|
|
3586
|
-
resolveApprovals:
|
|
3525
|
+
onEvent,
|
|
3526
|
+
resolveApprovals: consoleApprovals
|
|
3587
3527
|
}));
|
|
3588
3528
|
} catch {
|
|
3589
3529
|
flushToolActivity(tracker);
|
|
@@ -3633,9 +3573,11 @@ Last failure: ${terminalRunFailure(failedState.error)} (${failedState.currentSte
|
|
|
3633
3573
|
}, { color: terminalSupportsColor(Boolean(process.stderr.isTTY)), columns: process.stdout.columns ?? 80 }) + `
|
|
3634
3574
|
`);
|
|
3635
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.
|
|
3636
3578
|
`);
|
|
3637
3579
|
process.stderr.write(options.approvalMode === "restricted" ? `Restricted mode: additional approvals are denied.
|
|
3638
|
-
` : options.yes ? `Automatic approvals are enabled
|
|
3580
|
+
` : options.yes ? `Automatic approvals are enabled within workspace and execution policies.
|
|
3639
3581
|
` : `Changes require your approval.
|
|
3640
3582
|
`);
|
|
3641
3583
|
const showSessionState = async () => {
|
|
@@ -3656,16 +3598,17 @@ ${summarizeApproval(approval)}
|
|
|
3656
3598
|
await showSessionState();
|
|
3657
3599
|
for (;; ) {
|
|
3658
3600
|
try {
|
|
3659
|
-
|
|
3601
|
+
const pendingTurn = await hasActiveTurn();
|
|
3602
|
+
readline.setQueueEnabled(!pendingTurn);
|
|
3603
|
+
const submitted = await readline.compose({
|
|
3660
3604
|
model: `${harness.config.provider}/${harness.config.model}`,
|
|
3605
|
+
reasoning: harness.config.reasoningEffort ?? "default",
|
|
3661
3606
|
...session.title ? { title: session.title } : {},
|
|
3662
3607
|
status: session.runs.at(-1)?.status === "waiting_approval" ? "approval pending · /pending" : "ready",
|
|
3663
3608
|
attachments: attachments.list().length,
|
|
3664
3609
|
automaticApprovals: options.yes === true,
|
|
3665
3610
|
...options.approvalMode ? { approvalMode: options.approvalMode } : {}
|
|
3666
|
-
}
|
|
3667
|
-
const submitted = await readline.question(`
|
|
3668
|
-
> `, true);
|
|
3611
|
+
});
|
|
3669
3612
|
const literalInput = readline.lastSubmissionWasPaste || submitted.includes(`
|
|
3670
3613
|
`);
|
|
3671
3614
|
let prompt = literalInput ? submitted : submitted.trim();
|
|
@@ -3676,7 +3619,7 @@ ${summarizeApproval(approval)}
|
|
|
3676
3619
|
if (["/menu", "/provider", "/providers", "/model", "/models"].includes(command)) {
|
|
3677
3620
|
const selection = await navigateConsole(readline, {
|
|
3678
3621
|
entry: command === "/menu" ? "menu" : command.startsWith("/provider") ? "provider" : "model",
|
|
3679
|
-
current: { provider: harness.config.provider, model: harness.config.model },
|
|
3622
|
+
current: { provider: harness.config.provider, model: harness.config.model, reasoningEffort: harness.config.reasoningEffort ?? "default" },
|
|
3680
3623
|
providers: providerAvailability(),
|
|
3681
3624
|
sessions: async () => (await sessionStore.list({ limit: 200 })).map((item) => ({ value: item.sessionId, label: item.title ?? "Untitled conversation", detail: item.sessionId }))
|
|
3682
3625
|
});
|
|
@@ -3693,9 +3636,9 @@ ${summarizeApproval(approval)}
|
|
|
3693
3636
|
continue;
|
|
3694
3637
|
}
|
|
3695
3638
|
const portableMessages = compactHarnessMessages(messages);
|
|
3696
|
-
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);
|
|
3697
3640
|
messages = portableMessages;
|
|
3698
|
-
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.
|
|
3699
3642
|
`);
|
|
3700
3643
|
continue;
|
|
3701
3644
|
}
|
|
@@ -3834,8 +3777,8 @@ ${summarizeApproval(approval)}
|
|
|
3834
3777
|
continue;
|
|
3835
3778
|
}
|
|
3836
3779
|
if (command === "/sessions" || command.startsWith("/sessions ")) {
|
|
3837
|
-
const
|
|
3838
|
-
for (const summary of
|
|
3780
|
+
const summaries = await sessionStore.list({ search: command.slice(9).trim(), limit: 200 });
|
|
3781
|
+
for (const summary of summaries) {
|
|
3839
3782
|
const selected = await sessionStore.get(summary.sessionId);
|
|
3840
3783
|
if (!selected)
|
|
3841
3784
|
continue;
|
|
@@ -3843,7 +3786,7 @@ ${summarizeApproval(approval)}
|
|
|
3843
3786
|
process.stdout.write(sanitizeTerminalText(`${summary.sessionId} · ${summary.title ?? "(untitled)"} · ${summary.runCount} runs · ${durable?.status ?? "empty"}`) + `
|
|
3844
3787
|
`);
|
|
3845
3788
|
}
|
|
3846
|
-
if (!
|
|
3789
|
+
if (!summaries.length)
|
|
3847
3790
|
process.stdout.write(`No sessions match in this workspace and scope.
|
|
3848
3791
|
`);
|
|
3849
3792
|
continue;
|
|
@@ -3884,12 +3827,30 @@ ${summarizeApproval(approval)}
|
|
|
3884
3827
|
retainedTasks = [];
|
|
3885
3828
|
attachments.clear();
|
|
3886
3829
|
readline.clearHistory();
|
|
3830
|
+
activityHistory.clear();
|
|
3887
3831
|
process.stderr.write(`Context cleared.
|
|
3888
3832
|
`);
|
|
3889
3833
|
continue;
|
|
3890
3834
|
}
|
|
3891
3835
|
if (command === "/status") {
|
|
3892
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.
|
|
3893
3854
|
`);
|
|
3894
3855
|
continue;
|
|
3895
3856
|
}
|
|
@@ -3937,7 +3898,7 @@ ${summarizeApproval(approval)}
|
|
|
3937
3898
|
const provider = parseProvider(value);
|
|
3938
3899
|
const model = DEFAULT_PROVIDER_REGISTRY.descriptor(provider).defaultModel;
|
|
3939
3900
|
const portableMessages = compactHarnessMessages(messages);
|
|
3940
|
-
await replaceHarness({ ...runtimeOptions, provider, model }, routes);
|
|
3901
|
+
await replaceHarness({ ...runtimeOptions, provider, model, reasoningEffort: "default" }, routes);
|
|
3941
3902
|
messages = portableMessages;
|
|
3942
3903
|
process.stderr.write(`Next turn: ${provider}/${model}; context was compacted for a safe handoff.
|
|
3943
3904
|
`);
|
|
@@ -3952,9 +3913,28 @@ ${summarizeApproval(approval)}
|
|
|
3952
3913
|
continue;
|
|
3953
3914
|
}
|
|
3954
3915
|
const portableMessages = compactHarnessMessages(messages);
|
|
3955
|
-
await
|
|
3916
|
+
const reasoningEffort = await chooseReasoning(readline, harness.config.provider, value);
|
|
3917
|
+
if (!reasoningEffort)
|
|
3918
|
+
continue;
|
|
3919
|
+
await replaceHarness({ ...runtimeOptions, model: value, reasoningEffort }, routes);
|
|
3956
3920
|
messages = portableMessages;
|
|
3957
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}.
|
|
3958
3938
|
`);
|
|
3959
3939
|
continue;
|
|
3960
3940
|
}
|
|
@@ -4112,7 +4092,7 @@ ${sanitizeTerminalText(prompt)}
|
|
|
4112
4092
|
status: "created"
|
|
4113
4093
|
});
|
|
4114
4094
|
const turn = session.runs.at(-1);
|
|
4115
|
-
const tracker
|
|
4095
|
+
const { tracker, onEvent } = createTracker();
|
|
4116
4096
|
const progress = consoleProgressGuard();
|
|
4117
4097
|
let markedRunning = false;
|
|
4118
4098
|
let result;
|
|
@@ -4160,9 +4140,9 @@ ${sanitizeTerminalText(prompt)}
|
|
|
4160
4140
|
session = await sessionStore.updateRun(session.sessionId, runId, { status: "running" });
|
|
4161
4141
|
markedRunning = true;
|
|
4162
4142
|
}
|
|
4163
|
-
await
|
|
4143
|
+
await onEvent(event);
|
|
4164
4144
|
},
|
|
4165
|
-
resolveApprovals:
|
|
4145
|
+
resolveApprovals: consoleApprovals
|
|
4166
4146
|
}));
|
|
4167
4147
|
} catch (error) {
|
|
4168
4148
|
flushToolActivity(tracker);
|
|
@@ -4590,6 +4570,175 @@ var manageChanges = async (options) => {
|
|
|
4590
4570
|
process.exitCode = CLI_EXIT_CODES.runtimeError;
|
|
4591
4571
|
};
|
|
4592
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
|
+
|
|
4593
4742
|
// src/persistence/workspace-checkpoints.ts
|
|
4594
4743
|
import { createHash as createHash2, randomUUID as randomUUID3 } from "node:crypto";
|
|
4595
4744
|
import { z as z3 } from "zod";
|
|
@@ -4621,6 +4770,7 @@ import { chmod, link, lstat as lstat3, open as open2, realpath, unlink as unlink
|
|
|
4621
4770
|
import path5 from "node:path";
|
|
4622
4771
|
import { z as z4 } from "zod";
|
|
4623
4772
|
import { normalizeAgentRunState } from "@zhivex-ai/agents";
|
|
4773
|
+
import { createSqliteAgentRunStore } from "@zhivex-ai/agents/ops";
|
|
4624
4774
|
var HARNESS_STATE_BACKUP_SCHEMA_VERSION = 1;
|
|
4625
4775
|
var HARNESS_STATE_BACKUP_MAX_BYTES = 64 * 1024 * 1024;
|
|
4626
4776
|
var terminalRunStatuses = new Set(["completed", "failed", "cancelled", "timed_out"]);
|
|
@@ -4755,7 +4905,12 @@ var canonicalJson = (value) => {
|
|
|
4755
4905
|
return `[${value.map(canonicalJson).join(",")}]`;
|
|
4756
4906
|
return `{${Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`).join(",")}}`;
|
|
4757
4907
|
};
|
|
4758
|
-
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
|
+
};
|
|
4759
4914
|
var stableKey = (kind, value) => createHash3("sha256").update(kind).update("\x00").update(value).digest("hex");
|
|
4760
4915
|
var scopeValue = (config) => `${config.scope.tenantId}\x00${config.scope.userId ?? ""}\x00${config.scope.namespace ?? ""}`;
|
|
4761
4916
|
var scopePrefix = (config) => `${encodeURIComponent(config.scope.namespace ?? "default")}:${encodeURIComponent(config.scope.tenantId)}:${encodeURIComponent(config.scope.userId ?? "*")}:`;
|
|
@@ -4787,8 +4942,8 @@ var validateBudgetLedger = (config, run, identities) => {
|
|
|
4787
4942
|
if (!/^sha256:[a-f0-9]{64}$/.test(id) || !value || typeof value !== "object" || Array.isArray(value) || value.status !== "confirmed") {
|
|
4788
4943
|
throw new HarnessStateConflictError("Budget ledger retains unresolved reservations or unknown consumption.");
|
|
4789
4944
|
}
|
|
4790
|
-
const
|
|
4791
|
-
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) {
|
|
4792
4947
|
throw new HarnessStateConflictError("Invalid budget ledger token receipt.");
|
|
4793
4948
|
}
|
|
4794
4949
|
}
|
|
@@ -4890,8 +5045,13 @@ var readPayload = async (config, recordedWorkspace, accessLease) => {
|
|
|
4890
5045
|
if (recordedWorkspace && database.query("SELECT version FROM zhivex_cli_session_schema WHERE singleton=1").get()?.version !== HARNESS_SESSION_SCHEMA_VERSION)
|
|
4891
5046
|
throw new HarnessStateConflictError("Archived session schema is incompatible.");
|
|
4892
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;
|
|
4893
5049
|
const runRows = allRunRows.flatMap((row) => {
|
|
4894
|
-
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}`);
|
|
4895
5055
|
return stateMatchesScope(config, state) ? [{ row, state }] : [];
|
|
4896
5056
|
});
|
|
4897
5057
|
const runs = runRows.map(({ row, state }) => {
|
|
@@ -5051,7 +5211,19 @@ var writePrivateBackup = async (target, contents) => {
|
|
|
5051
5211
|
try {
|
|
5052
5212
|
handle = await open2(staged, fsConstants3.O_WRONLY | fsConstants3.O_CREAT | fsConstants3.O_EXCL | fsConstants3.O_NOFOLLOW, 384);
|
|
5053
5213
|
stagedExists = true;
|
|
5054
|
-
|
|
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");
|
|
5055
5227
|
await handle.sync();
|
|
5056
5228
|
await chmod(staged, 384);
|
|
5057
5229
|
await handle.close();
|
|
@@ -5078,12 +5250,13 @@ var writePrivateBackup = async (target, contents) => {
|
|
|
5078
5250
|
};
|
|
5079
5251
|
var exportHarnessStateBackup = async (config, target) => {
|
|
5080
5252
|
const bundle = await createHarnessStateBackup(config);
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
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;
|
|
5085
5258
|
}
|
|
5086
|
-
await writePrivateBackup(target,
|
|
5259
|
+
await writePrivateBackup(target, bytes > HARNESS_STATE_BACKUP_MAX_BYTES ? segmentedChunks(bundle) : canonicalChunks(bundle));
|
|
5087
5260
|
return {
|
|
5088
5261
|
schemaVersion: HARNESS_STATE_BACKUP_SCHEMA_VERSION,
|
|
5089
5262
|
kind: "state-export",
|
|
@@ -5093,19 +5266,14 @@ var exportHarnessStateBackup = async (config, target) => {
|
|
|
5093
5266
|
};
|
|
5094
5267
|
};
|
|
5095
5268
|
var readHarnessStateBackup = async (source) => {
|
|
5096
|
-
const { contents, stat: stat2 } = await readRegularFileNoFollow(path5.resolve(source), {
|
|
5097
|
-
label: "Harness state backup",
|
|
5098
|
-
maxBytes: HARNESS_STATE_BACKUP_MAX_BYTES,
|
|
5099
|
-
requireSingleLink: true
|
|
5100
|
-
});
|
|
5101
|
-
if ((stat2.mode & 63) !== 0) {
|
|
5102
|
-
throw new HarnessWorkspaceError("Harness state backup permissions must not grant group or other access.");
|
|
5103
|
-
}
|
|
5104
5269
|
let input;
|
|
5105
5270
|
try {
|
|
5106
|
-
input =
|
|
5271
|
+
input = await readBackupTransport(path5.resolve(source), HARNESS_STATE_BACKUP_MAX_BYTES);
|
|
5107
5272
|
} catch (error) {
|
|
5108
|
-
|
|
5273
|
+
if (error instanceof SyntaxError) {
|
|
5274
|
+
throw new HarnessStateConflictError("Harness state backup is not valid JSON.", { cause: error });
|
|
5275
|
+
}
|
|
5276
|
+
throw error;
|
|
5109
5277
|
}
|
|
5110
5278
|
return validateStateBackupBundle(input);
|
|
5111
5279
|
};
|
|
@@ -5506,7 +5674,7 @@ var main = async (argv = process.argv.slice(2)) => {
|
|
|
5506
5674
|
parsedOptions = { ...parsedOptions, prompt: await readStdinTask() };
|
|
5507
5675
|
}
|
|
5508
5676
|
if (parsedOptions.serviceFile) {
|
|
5509
|
-
const { runServiceCli } = await import("./service-client-cli-
|
|
5677
|
+
const { runServiceCli } = await import("./service-client-cli-yx56n0hc.js");
|
|
5510
5678
|
await runServiceCli(parsedOptions, annotateCliStreamError);
|
|
5511
5679
|
return;
|
|
5512
5680
|
}
|