@buildaharness/aielia 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -19
- package/dist/action-approval-service.d.ts +34 -2
- package/dist/action-record.d.ts +13 -0
- package/dist/activity-log.d.ts +4 -0
- package/dist/agent-loop.d.ts +83 -1
- package/dist/assistant-session.d.ts +20 -1
- package/dist/assistant-types.d.ts +3 -1
- package/dist/assistant.d.ts +32 -0
- package/dist/{bin-CjKpxDxl.js → bin-C_hgDdSf.js} +369 -219
- package/dist/bin-C_hgDdSf.js.map +1 -0
- package/dist/cli-config.d.ts +3 -0
- package/dist/cli-session.d.ts +5 -4
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +1 -1
- package/dist/config.d.ts +3 -1
- package/dist/debug-log.d.ts +10 -1
- package/dist/email.d.ts +5 -3
- package/dist/file-tools-mcp-server.mjs +155 -30
- package/dist/file-tools.d.ts +2 -0
- package/dist/git-exclude.d.ts +9 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +127 -126
- package/dist/ink-select-prompt.d.ts +2 -0
- package/dist/ink-test-render.d.ts +2 -0
- package/dist/memory-service.d.ts +8 -0
- package/dist/next-step-proposer.d.ts +7 -0
- package/dist/plan-drafting-service.d.ts +3 -0
- package/dist/plan-store.d.ts +5 -0
- package/dist/{provider-setup-LFsaTkpf.js → provider-setup-wX2IBw2X.js} +2183 -919
- package/dist/provider-setup-wX2IBw2X.js.map +1 -0
- package/dist/reply-audit.d.ts +67 -0
- package/dist/side-call-policy.d.ts +30 -0
- package/dist/tool-step.d.ts +6 -0
- package/dist/trace-events.d.ts +1 -1
- package/dist/trust-tagging.d.ts +2 -0
- package/dist/{tui-app-BLR-PRFp.js → tui-app-C0J6HRzv.js} +127 -26
- package/dist/tui-app-C0J6HRzv.js.map +1 -0
- package/dist/tui-app.d.ts +35 -0
- package/dist/tui-mode-flag.d.ts +1 -1
- package/dist/turn-abort.d.ts +18 -0
- package/dist/turn-intent-classifier.d.ts +8 -0
- package/dist/turn-interpreter.d.ts +2 -0
- package/dist/version.d.ts +1 -1
- package/package.json +3 -3
- package/dist/bin-CjKpxDxl.js.map +0 -1
- package/dist/provider-setup-LFsaTkpf.js.map +0 -1
- package/dist/tui-app-BLR-PRFp.js.map +0 -1
|
@@ -1,20 +1,83 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
import { existsSync, readFileSync, mkdirSync, appendFileSync, statSync, chmodSync, writeFileSync, renameSync, rmSync, createWriteStream, createReadStream, realpathSync } from "node:fs";
|
|
5
|
+
import { join, dirname, resolve, isAbsolute } from "node:path";
|
|
4
6
|
import { createInterface } from "node:readline";
|
|
5
7
|
import { rename, stat, realpath, readdir, mkdir, unlink, writeFile, readFile } from "node:fs/promises";
|
|
6
|
-
import { existsSync, mkdirSync, writeFileSync, renameSync, rmSync, createWriteStream, chmodSync, readFileSync, realpathSync } from "node:fs";
|
|
7
8
|
import { homedir, tmpdir } from "node:os";
|
|
8
|
-
import { join, dirname, resolve } from "node:path";
|
|
9
9
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
10
|
-
import { OPENROUTER_DEFAULT_MODEL, OPENAI_DEFAULT_MODEL, ANTHROPIC_DEFAULT_MODEL, InMemoryReminderStore, FileSystemAdapter, FileSystemExperienceStore, LLMClient, OpenAICompatibleLLMClient, OPENROUTER_EXTRA_HEADERS, OPENROUTER_BASE_URL, OPENAI_BASE_URL, AnthropicLLMClient } from "@buildaharness/runtime";
|
|
11
|
-
import {
|
|
10
|
+
import { OPENROUTER_DEFAULT_MODEL, OPENAI_DEFAULT_MODEL, ANTHROPIC_DEFAULT_MODEL, InMemoryReminderStore, FileSystemAdapter, FileSystemExperienceStore, defaultModelForBackend, LLMClient, OpenAICompatibleLLMClient, OPENROUTER_EXTRA_HEADERS, OPENROUTER_BASE_URL, OPENAI_BASE_URL, AnthropicLLMClient } from "@buildaharness/runtime";
|
|
11
|
+
import { Y as buildClaudePrompt, bK as stripJsonCodeFence, bo as recallToolEnabled, aZ as lexicalOffEnvValue, a as ALREADY_STAGED_ACTION_TOOL, bJ as stagedActionInput, bi as parseClaudeCliOutput, bL as stripMcpToolPrefix, bp as redactUrlCredentials, b5 as memoryStatusChecks, bh as normalizePlanNodes, P as PROVIDER_SETUP, a4 as cleanApiKey, _ as checkApiKeyFormat, ag as envOverridesFromProcessEnv, bs as resolveConfig, bH as shouldLaunchTuiApp, n as DEFAULT_TUI_MODE, T as applyLayerSettings, bF as sanitizeLayerChoices, bQ as testApiKey, bT as validateConfig, ao as findInvalidNumericSettings, c as ConfigValidationError, aH as formatSpendCapStatus, aU as isPlanGraphEnabled, bM as stripTerminalControls, B as PersonalAssistant, aR as isGoalGraphSuggestEnabled, aQ as isGoalGraphEnabled, bn as planToSnapshot, X as braveSearch, a8 as createSmtpSender, a7 as createResendSender, aW as isQuitCommand, y as LiveSteeringChannel, aE as formatNextSteps, $ as classifyError, b8 as nodeToLayer, L as LAYER_DISPLAY_NAME, b7 as nodeDisplayName, ai as estimateCostUsd, aq as formatConfigListing, aP as isConfigKey, C as CONFIG_KEYS, E as ENV_VAR_FOR_CONFIG_KEY, bj as parseConfigValue, d as ConfigValueParseError, as as formatDoctorReport, ar as formatCostSummary, au as formatGoalGraphState, aG as formatSearchResults, az as formatMemoryHistory, aC as formatMemoryStatus, aB as formatMemoryPendingOutcome, ax as formatMemoryArchive, aK as formatUndoLogListing, ab as defaultExportFilename, aJ as formatTranscriptMarkdown, aI as formatStatus, av as formatHelp, r as LAYER_ORDER, aw as formatLayerListing, bU as withLayerChoice, x as LayerSettingError, aA as formatMemoryInjection, Z as buildWhyChain, t as LAYER_SHORT_CODE, ac as defaultMemoryExportFilename, ay as formatMemoryExport, b4 as memoryAuditLogEnabled, aD as formatMemorySummary } from "./provider-setup-wX2IBw2X.js";
|
|
12
12
|
import { spawn } from "node:child_process";
|
|
13
13
|
import { createServer, connect } from "node:net";
|
|
14
14
|
import { createRequire } from "node:module";
|
|
15
15
|
import { randomBytes, createHash } from "node:crypto";
|
|
16
16
|
import { Readable } from "node:stream";
|
|
17
17
|
import { pipeline } from "node:stream/promises";
|
|
18
|
+
const AIELIA_STATE_DIRS = [".pending-actions", ".undo-log", ".buildaharness"];
|
|
19
|
+
function findGitDir(start) {
|
|
20
|
+
let dir = resolve(start);
|
|
21
|
+
for (; ; ) {
|
|
22
|
+
const dotGit = join(dir, ".git");
|
|
23
|
+
if (existsSync(dotGit)) {
|
|
24
|
+
const stat2 = statSync(dotGit);
|
|
25
|
+
if (stat2.isDirectory()) return dotGit;
|
|
26
|
+
const line = readFileSync(dotGit, "utf8").split("\n").find((l) => l.startsWith("gitdir:"));
|
|
27
|
+
if (!line) return void 0;
|
|
28
|
+
const target = line.slice("gitdir:".length).trim();
|
|
29
|
+
const gitDir = isAbsolute(target) ? target : resolve(dir, target);
|
|
30
|
+
const commonFile = join(gitDir, "commondir");
|
|
31
|
+
if (existsSync(commonFile)) return resolve(gitDir, readFileSync(commonFile, "utf8").trim());
|
|
32
|
+
return gitDir;
|
|
33
|
+
}
|
|
34
|
+
const parent = dirname(dir);
|
|
35
|
+
if (parent === dir) return void 0;
|
|
36
|
+
dir = parent;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function excludeAieliaStateFromGit(workspaceRoot) {
|
|
40
|
+
try {
|
|
41
|
+
const gitDir = findGitDir(workspaceRoot);
|
|
42
|
+
if (!gitDir) return [];
|
|
43
|
+
const excludeFile = join(gitDir, "info", "exclude");
|
|
44
|
+
const existing = existsSync(excludeFile) ? readFileSync(excludeFile, "utf8") : "";
|
|
45
|
+
const present = new Set(existing.split("\n").map((l) => l.trim()));
|
|
46
|
+
const missing = AIELIA_STATE_DIRS.map((d) => `${d}/`).filter((entry) => !present.has(entry) && !present.has(entry.slice(0, -1)));
|
|
47
|
+
if (missing.length === 0) return [];
|
|
48
|
+
mkdirSync(dirname(excludeFile), { recursive: true });
|
|
49
|
+
const prefix = existing === "" || existing.endsWith("\n") ? "" : "\n";
|
|
50
|
+
appendFileSync(excludeFile, `${prefix}# Aielia state (added automatically)
|
|
51
|
+
${missing.join("\n")}
|
|
52
|
+
`);
|
|
53
|
+
return missing;
|
|
54
|
+
} catch {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function activityLogEnabled(env) {
|
|
59
|
+
return /^(1|true|on|yes|enabled)$/i.test(env.ASSISTANT_ACTIVITY_LOG ?? "");
|
|
60
|
+
}
|
|
61
|
+
function createActivityLogger(filePath, now = () => /* @__PURE__ */ new Date()) {
|
|
62
|
+
let ready = false;
|
|
63
|
+
return (entry) => {
|
|
64
|
+
try {
|
|
65
|
+
if (!ready) {
|
|
66
|
+
mkdirSync(dirname(filePath), { recursive: true, mode: 448 });
|
|
67
|
+
}
|
|
68
|
+
appendFileSync(filePath, JSON.stringify({ at: now().toISOString(), sessionId: entry.sessionId, kind: entry.kind, content: entry.content }) + "\n", { mode: 384 });
|
|
69
|
+
if (!ready) {
|
|
70
|
+
try {
|
|
71
|
+
chmodSync(filePath, 384);
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
ready = true;
|
|
75
|
+
}
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.error("[activity-log] could not write an entry:", err);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
18
81
|
function isEnoent$1(err) {
|
|
19
82
|
return (err == null ? void 0 : err.code) === "ENOENT";
|
|
20
83
|
}
|
|
@@ -66,7 +129,7 @@ function createNodeFsBackend() {
|
|
|
66
129
|
}
|
|
67
130
|
};
|
|
68
131
|
}
|
|
69
|
-
const CLI_VERSION = "0.3.
|
|
132
|
+
const CLI_VERSION = "0.3.7";
|
|
70
133
|
function seaCacheDir(version, home = homedir()) {
|
|
71
134
|
return join(home, ".buildaharness", "personal-assistant", "sea-cache", version);
|
|
72
135
|
}
|
|
@@ -130,19 +193,15 @@ function invokeClaudeStreaming(claudePath, args, onToolStep) {
|
|
|
130
193
|
let buffer = "";
|
|
131
194
|
let stderr = "";
|
|
132
195
|
let finalResultLine;
|
|
133
|
-
|
|
196
|
+
const handleLine = (line) => {
|
|
134
197
|
var _a;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
while ((newlineIndex = buffer.indexOf("\n")) !== -1) {
|
|
138
|
-
const line = buffer.slice(0, newlineIndex);
|
|
139
|
-
buffer = buffer.slice(newlineIndex + 1);
|
|
140
|
-
if (!line.trim()) continue;
|
|
198
|
+
{
|
|
199
|
+
if (!line.trim()) return;
|
|
141
200
|
let event;
|
|
142
201
|
try {
|
|
143
202
|
event = JSON.parse(line);
|
|
144
203
|
} catch {
|
|
145
|
-
|
|
204
|
+
return;
|
|
146
205
|
}
|
|
147
206
|
if (event.type === "assistant" && onToolStep) {
|
|
148
207
|
for (const block of ((_a = event.message) == null ? void 0 : _a.content) ?? []) {
|
|
@@ -154,6 +213,15 @@ function invokeClaudeStreaming(claudePath, args, onToolStep) {
|
|
|
154
213
|
finalResultLine = line;
|
|
155
214
|
}
|
|
156
215
|
}
|
|
216
|
+
};
|
|
217
|
+
proc.stdout.on("data", (chunk) => {
|
|
218
|
+
buffer += chunk.toString("utf-8");
|
|
219
|
+
let newlineIndex;
|
|
220
|
+
while ((newlineIndex = buffer.indexOf("\n")) !== -1) {
|
|
221
|
+
const line = buffer.slice(0, newlineIndex);
|
|
222
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
223
|
+
handleLine(line);
|
|
224
|
+
}
|
|
157
225
|
});
|
|
158
226
|
proc.stderr.on("data", (chunk) => {
|
|
159
227
|
stderr += chunk;
|
|
@@ -164,6 +232,8 @@ function invokeClaudeStreaming(claudePath, args, onToolStep) {
|
|
|
164
232
|
reject(new Error(stderr.trim() || `claude exited with code ${code}`));
|
|
165
233
|
return;
|
|
166
234
|
}
|
|
235
|
+
handleLine(buffer);
|
|
236
|
+
buffer = "";
|
|
167
237
|
resolvePromise(parseClaudeCliOutput(finalResultLine ?? ""));
|
|
168
238
|
});
|
|
169
239
|
});
|
|
@@ -171,6 +241,8 @@ function invokeClaudeStreaming(claudePath, args, onToolStep) {
|
|
|
171
241
|
function startToolGateServer(onToolProposal, onToolResult, onToolExecute) {
|
|
172
242
|
return new Promise((resolvePromise, reject) => {
|
|
173
243
|
const server = createServer((socket) => {
|
|
244
|
+
socket.on("error", () => {
|
|
245
|
+
});
|
|
174
246
|
let buffer = "";
|
|
175
247
|
socket.on("data", (chunk) => {
|
|
176
248
|
buffer += chunk.toString("utf-8");
|
|
@@ -283,7 +355,7 @@ class ClaudeCliLLMClient {
|
|
|
283
355
|
];
|
|
284
356
|
const model = options.model ?? this.model;
|
|
285
357
|
if (model) args.push("--model", model);
|
|
286
|
-
args.push(prompt);
|
|
358
|
+
args.push("--", prompt);
|
|
287
359
|
const { reply, usage, model: resolved } = await invokeClaude(this.claudePath, args);
|
|
288
360
|
if (resolved) this.resolvedModelId = resolved;
|
|
289
361
|
if (usage) (_a = options.onUsage) == null ? void 0 : _a.call(options, usage);
|
|
@@ -369,7 +441,7 @@ class ClaudeCliLLMClient {
|
|
|
369
441
|
];
|
|
370
442
|
const model = options.model ?? this.model;
|
|
371
443
|
if (model) args.push("--model", model);
|
|
372
|
-
args.push(prompt);
|
|
444
|
+
args.push("--", prompt);
|
|
373
445
|
const callStartedAt = Date.now();
|
|
374
446
|
const { reply, usage, model: resolved } = await invokeClaudeStreaming(this.claudePath, args, options.onToolStep);
|
|
375
447
|
if (resolved) this.resolvedModelId = resolved;
|
|
@@ -408,9 +480,14 @@ class ClaudeCliLLMClient {
|
|
|
408
480
|
for (const name of names) {
|
|
409
481
|
if (!name.endsWith(".json")) continue;
|
|
410
482
|
const filePath = `${dir}/${name}`;
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
483
|
+
let record;
|
|
484
|
+
try {
|
|
485
|
+
const stats = await stat(filePath);
|
|
486
|
+
if (stats.mtimeMs < startTimeMs - 1e3) continue;
|
|
487
|
+
record = JSON.parse(await readFile(filePath, "utf-8"));
|
|
488
|
+
} catch {
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
414
491
|
if (!earliest || record.stagedAt < earliest.stagedAt) earliest = record;
|
|
415
492
|
}
|
|
416
493
|
return earliest;
|
|
@@ -444,6 +521,7 @@ function handleConnection(clientSocket, allowlist) {
|
|
|
444
521
|
return;
|
|
445
522
|
}
|
|
446
523
|
clientSocket.removeListener("data", onData);
|
|
524
|
+
clientSocket.pause();
|
|
447
525
|
const headerText = buffered.slice(0, headerEnd).toString("utf-8");
|
|
448
526
|
const requestLine = headerText.split("\r\n")[0] ?? "";
|
|
449
527
|
const isConnect = /^CONNECT\s/i.test(requestLine);
|
|
@@ -464,6 +542,7 @@ function handleConnection(clientSocket, allowlist) {
|
|
|
464
542
|
upstream.pipe(clientSocket);
|
|
465
543
|
});
|
|
466
544
|
upstream.on("error", () => clientSocket.destroy());
|
|
545
|
+
clientSocket.on("close", () => upstream.destroy());
|
|
467
546
|
};
|
|
468
547
|
clientSocket.on("data", onData);
|
|
469
548
|
clientSocket.on("error", () => clientSocket.destroy());
|
|
@@ -539,7 +618,18 @@ const runApprovedShellCommand = async (command, cwd, options = {}) => {
|
|
|
539
618
|
let output = "";
|
|
540
619
|
let timedOut = false;
|
|
541
620
|
let settled = false;
|
|
621
|
+
const finish = (exitCode) => {
|
|
622
|
+
if (settled) return;
|
|
623
|
+
settled = true;
|
|
624
|
+
clearTimeout(timer);
|
|
625
|
+
resolvePromise({
|
|
626
|
+
output: truncateOutput(output, maxOutputBytes),
|
|
627
|
+
exitCode: timedOut ? null : exitCode,
|
|
628
|
+
timedOut
|
|
629
|
+
});
|
|
630
|
+
};
|
|
542
631
|
const timer = setTimeout(() => {
|
|
632
|
+
var _a2, _b2;
|
|
543
633
|
timedOut = true;
|
|
544
634
|
try {
|
|
545
635
|
if (proc.pid) process.kill(-proc.pid, "SIGKILL");
|
|
@@ -547,29 +637,25 @@ const runApprovedShellCommand = async (command, cwd, options = {}) => {
|
|
|
547
637
|
} catch {
|
|
548
638
|
proc.kill("SIGKILL");
|
|
549
639
|
}
|
|
640
|
+
(_b2 = (_a2 = setTimeout(() => {
|
|
641
|
+
var _a3, _b3;
|
|
642
|
+
(_a3 = proc.stdout) == null ? void 0 : _a3.destroy();
|
|
643
|
+
(_b3 = proc.stderr) == null ? void 0 : _b3.destroy();
|
|
644
|
+
finish(null);
|
|
645
|
+
}, 2e3)).unref) == null ? void 0 : _b2.call(_a2);
|
|
550
646
|
}, timeoutMs);
|
|
551
|
-
|
|
552
|
-
output += chunk.toString("utf-8");
|
|
553
|
-
}
|
|
554
|
-
(
|
|
555
|
-
|
|
556
|
-
});
|
|
647
|
+
const append = (chunk) => {
|
|
648
|
+
if (output.length <= maxOutputBytes * 2) output += chunk.toString("utf-8");
|
|
649
|
+
};
|
|
650
|
+
(_a = proc.stdout) == null ? void 0 : _a.on("data", append);
|
|
651
|
+
(_b = proc.stderr) == null ? void 0 : _b.on("data", append);
|
|
557
652
|
proc.on("error", (err) => {
|
|
558
653
|
if (settled) return;
|
|
559
654
|
settled = true;
|
|
560
655
|
clearTimeout(timer);
|
|
561
656
|
reject(err);
|
|
562
657
|
});
|
|
563
|
-
proc.on("close", (exitCode) =>
|
|
564
|
-
if (settled) return;
|
|
565
|
-
settled = true;
|
|
566
|
-
clearTimeout(timer);
|
|
567
|
-
resolvePromise({
|
|
568
|
-
output: truncateOutput(output, maxOutputBytes),
|
|
569
|
-
exitCode: timedOut ? null : exitCode,
|
|
570
|
-
timedOut
|
|
571
|
-
});
|
|
572
|
-
});
|
|
658
|
+
proc.on("close", (exitCode) => finish(exitCode));
|
|
573
659
|
});
|
|
574
660
|
};
|
|
575
661
|
function isEnoent(err) {
|
|
@@ -589,7 +675,9 @@ class NodeConfigStore {
|
|
|
589
675
|
throw err;
|
|
590
676
|
}
|
|
591
677
|
try {
|
|
592
|
-
|
|
678
|
+
const parsed = JSON.parse(raw);
|
|
679
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("not an object");
|
|
680
|
+
return parsed;
|
|
593
681
|
} catch {
|
|
594
682
|
console.error(`Warning: ${this.path} is not valid JSON — ignoring it and falling back to defaults.`);
|
|
595
683
|
return {};
|
|
@@ -604,15 +692,15 @@ class NodeConfigStore {
|
|
|
604
692
|
async writePatch(patch) {
|
|
605
693
|
const existing = await this.load();
|
|
606
694
|
const merged = { ...existing, ...patch };
|
|
607
|
-
await mkdir(dirname(this.path), { recursive: true });
|
|
695
|
+
await mkdir(dirname(this.path), { recursive: true, mode: 448 });
|
|
608
696
|
const tempPath = `${this.path}.tmp-${process.pid}-${randomBytes(6).toString("hex")}`;
|
|
609
|
-
await writeFile(tempPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
697
|
+
await writeFile(tempPath, JSON.stringify(merged, null, 2), { encoding: "utf-8", mode: 384 });
|
|
610
698
|
await rename(tempPath, this.path);
|
|
611
699
|
}
|
|
612
700
|
}
|
|
613
701
|
const DOCTOR_CHECK_TIMEOUT_MS = 3e3;
|
|
614
702
|
async function checkProxyHealth(proxyUrl) {
|
|
615
|
-
const label = `proxy reachable (${proxyUrl}/health)`;
|
|
703
|
+
const label = `proxy reachable (${redactUrlCredentials(proxyUrl)}/health)`;
|
|
616
704
|
const controller = new AbortController();
|
|
617
705
|
const timeout = setTimeout(() => controller.abort(), DOCTOR_CHECK_TIMEOUT_MS);
|
|
618
706
|
try {
|
|
@@ -3183,8 +3271,13 @@ function parseGithubReleases(releases, allowInsecureHttp) {
|
|
|
3183
3271
|
}
|
|
3184
3272
|
return best;
|
|
3185
3273
|
}
|
|
3186
|
-
|
|
3187
|
-
|
|
3274
|
+
function assertFinalUrlHttps(res, allowInsecureHttp) {
|
|
3275
|
+
if (res.url) assertHttps(res.url, allowInsecureHttp);
|
|
3276
|
+
}
|
|
3277
|
+
async function getJson(fetchFn, url, headers = {}, allowInsecureHttp = false) {
|
|
3278
|
+
const res = await fetchFn(url, { headers, signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), redirect: "follow" });
|
|
3279
|
+
assertFinalUrlHttps(res, allowInsecureHttp);
|
|
3280
|
+
return res;
|
|
3188
3281
|
}
|
|
3189
3282
|
async function fetchLatestRelease(options, etag) {
|
|
3190
3283
|
const fetchFn = options.fetchFn ?? fetch;
|
|
@@ -3195,7 +3288,7 @@ async function fetchLatestRelease(options, etag) {
|
|
|
3195
3288
|
assertHttps(apiUrl, insecure);
|
|
3196
3289
|
let manifestError;
|
|
3197
3290
|
try {
|
|
3198
|
-
const res2 = await getJson(fetchFn, manifestUrl);
|
|
3291
|
+
const res2 = await getJson(fetchFn, manifestUrl, {}, insecure);
|
|
3199
3292
|
if (res2.ok) return { release: parseManifest(await res2.json(), insecure) };
|
|
3200
3293
|
manifestError = new Error(`manifest HTTP ${res2.status}`);
|
|
3201
3294
|
} catch (err) {
|
|
@@ -3203,7 +3296,7 @@ async function fetchLatestRelease(options, etag) {
|
|
|
3203
3296
|
}
|
|
3204
3297
|
const headers = { Accept: "application/vnd.github+json" };
|
|
3205
3298
|
if (etag) headers["If-None-Match"] = etag;
|
|
3206
|
-
const res = await getJson(fetchFn, apiUrl, headers);
|
|
3299
|
+
const res = await getJson(fetchFn, apiUrl, headers, insecure);
|
|
3207
3300
|
if (res.status === 304 && etag) return { notModified: true };
|
|
3208
3301
|
if (!res.ok) {
|
|
3209
3302
|
throw new Error(`Could not check for updates (manifest: ${(manifestError == null ? void 0 : manifestError.message) ?? manifestError}; GitHub: HTTP ${res.status})`);
|
|
@@ -3264,10 +3357,12 @@ function replaceBinary(execPath, newFile, platform = process.platform) {
|
|
|
3264
3357
|
chmodSync(newFile, 493);
|
|
3265
3358
|
renameSync(newFile, execPath);
|
|
3266
3359
|
}
|
|
3267
|
-
async function downloadTo(fetchFn, url, dest) {
|
|
3360
|
+
async function downloadTo(fetchFn, url, dest, allowInsecureHttp = false) {
|
|
3268
3361
|
const res = await fetchFn(url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS), redirect: "follow" });
|
|
3269
3362
|
if (!res.ok || !res.body) throw new Error(`Download failed: HTTP ${res.status} for ${url}`);
|
|
3363
|
+
if (res.url) assertHttps(res.url, allowInsecureHttp);
|
|
3270
3364
|
const hash = createHash("sha256");
|
|
3365
|
+
rmSync(dest, { force: true });
|
|
3271
3366
|
await pipeline(
|
|
3272
3367
|
Readable.fromWeb(res.body),
|
|
3273
3368
|
async function* (source) {
|
|
@@ -3276,13 +3371,18 @@ async function downloadTo(fetchFn, url, dest) {
|
|
|
3276
3371
|
yield chunk;
|
|
3277
3372
|
}
|
|
3278
3373
|
},
|
|
3279
|
-
createWriteStream(dest)
|
|
3374
|
+
createWriteStream(dest, { flags: "wx" })
|
|
3280
3375
|
);
|
|
3281
3376
|
return hash.digest("hex");
|
|
3282
3377
|
}
|
|
3283
|
-
async function
|
|
3378
|
+
async function sha256OfFile(path) {
|
|
3379
|
+
const hash = createHash("sha256");
|
|
3380
|
+
for await (const chunk of createReadStream(path)) hash.update(chunk);
|
|
3381
|
+
return hash.digest("hex");
|
|
3382
|
+
}
|
|
3383
|
+
async function fetchSidecarHash(fetchFn, assetUrl, allowInsecureHttp = false) {
|
|
3284
3384
|
try {
|
|
3285
|
-
const res = await getJson(fetchFn, `${assetUrl}.sha256
|
|
3385
|
+
const res = await getJson(fetchFn, `${assetUrl}.sha256`, {}, allowInsecureHttp);
|
|
3286
3386
|
if (!res.ok) return null;
|
|
3287
3387
|
const m = /[0-9a-fA-F]{64}/.exec(await res.text());
|
|
3288
3388
|
return m ? m[0].toLowerCase() : null;
|
|
@@ -3330,10 +3430,12 @@ async function runUpdateCommand(input = {}) {
|
|
|
3330
3430
|
try {
|
|
3331
3431
|
assertHttps(asset.url, insecure);
|
|
3332
3432
|
log(`Downloading aielia ${release.version} (${key})…`);
|
|
3333
|
-
const actual = await downloadTo(fetchFn, asset.url, tmp);
|
|
3334
|
-
const sidecar = await fetchSidecarHash(fetchFn, asset.url);
|
|
3433
|
+
const actual = await downloadTo(fetchFn, asset.url, tmp, insecure);
|
|
3434
|
+
const sidecar = await fetchSidecarHash(fetchFn, asset.url, insecure);
|
|
3335
3435
|
const expected = asset.sha256 ?? sidecar;
|
|
3336
3436
|
if (!expected) throw new Error("No checksum available for this release — refusing to install an unverified binary");
|
|
3437
|
+
const onDisk = await sha256OfFile(tmp);
|
|
3438
|
+
if (onDisk !== actual) throw new Error("Downloaded file changed on disk during verification — download discarded");
|
|
3337
3439
|
if (actual !== expected) throw new Error(`Checksum mismatch (expected ${expected}, got ${actual}) — download discarded`);
|
|
3338
3440
|
if (asset.sha256 && sidecar && sidecar !== asset.sha256) {
|
|
3339
3441
|
throw new Error("Checksum sidecar disagrees with the release manifest — download discarded");
|
|
@@ -3348,6 +3450,9 @@ async function runUpdateCommand(input = {}) {
|
|
|
3348
3450
|
return 1;
|
|
3349
3451
|
}
|
|
3350
3452
|
}
|
|
3453
|
+
function printSafe(...args) {
|
|
3454
|
+
console.log(...args.map((a) => typeof a === "string" ? stripTerminalControls(a) : a));
|
|
3455
|
+
}
|
|
3351
3456
|
const defaultDataDir = join(homedir(), ".buildaharness", "personal-assistant");
|
|
3352
3457
|
const defaultConfigStore = new NodeConfigStore(join(defaultDataDir, "config.json"));
|
|
3353
3458
|
const defaultRemindersFile = join(defaultDataDir, "reminders", "reminders.json");
|
|
@@ -3396,11 +3501,13 @@ function buildLlmClient(config, workspaceRoot, remindersFile) {
|
|
|
3396
3501
|
}
|
|
3397
3502
|
async function buildAssistant(config, { backend, dataDir, remindersFile }) {
|
|
3398
3503
|
const workspaceRoot = config.workspaceRoot ?? process.cwd();
|
|
3504
|
+
if (backend === defaultBackend) excludeAieliaStateFromGit(workspaceRoot);
|
|
3399
3505
|
const search = (query) => braveSearch(query, config.braveApiKey);
|
|
3400
3506
|
const llmClient = buildLlmClient(config, workspaceRoot, remindersFile);
|
|
3401
3507
|
return PersonalAssistant.create({
|
|
3402
3508
|
llmClient,
|
|
3403
|
-
model
|
|
3509
|
+
// Name the backend's real default when none is set, so spend tracking prices the model that actually runs (not the Anthropic default).
|
|
3510
|
+
model: config.model ?? defaultModelForBackend(config.llmBackend),
|
|
3404
3511
|
// config.activeProject (set via /project <name>) overrides the default of "the workspace
|
|
3405
3512
|
// I'm running in" — see PersonalAssistantOptions.activeProject's doc comment.
|
|
3406
3513
|
activeProject: config.activeProject || workspaceRoot,
|
|
@@ -3424,6 +3531,8 @@ async function buildAssistant(config, { backend, dataDir, remindersFile }) {
|
|
|
3424
3531
|
return sendEmail ? { backend, workspaceRoot, sendEmail } : void 0;
|
|
3425
3532
|
})(),
|
|
3426
3533
|
dangerouslySkipPermissions: config.dangerouslySkipPermissions,
|
|
3534
|
+
// Opt-in durable record of tool calls/results and approvals (see activity-log.ts).
|
|
3535
|
+
onDebugLog: activityLogEnabled(process.env) ? createActivityLogger(join(dataDir, "activity-log.jsonl")) : void 0,
|
|
3427
3536
|
spendCap: config.sessionCostLimitUsd !== void 0 || config.sessionCallLimit !== void 0 ? { sessionCostLimitUsd: config.sessionCostLimitUsd, sessionCallLimit: config.sessionCallLimit } : void 0,
|
|
3428
3537
|
// Resolved through the shared config seam (cli-config.ts's envOverridesFromProcessEnv reads
|
|
3429
3538
|
// ASSISTANT_ONE_LOOP into config.oneLoopMode; /config set oneLoopMode persists it) so the CLI,
|
|
@@ -3443,7 +3552,7 @@ async function buildAssistant(config, { backend, dataDir, remindersFile }) {
|
|
|
3443
3552
|
});
|
|
3444
3553
|
}
|
|
3445
3554
|
async function runCli(options = {}) {
|
|
3446
|
-
|
|
3555
|
+
printSafe("Aielia is alpha software — expect rough edges and breaking changes. It uses AI models and can make mistakes; verify anything important.\n");
|
|
3447
3556
|
const dataDir = options.dataDir ?? defaultDataDir;
|
|
3448
3557
|
const configStore = options.configStore ?? defaultConfigStore;
|
|
3449
3558
|
const backend = options.backend ?? defaultBackend;
|
|
@@ -3483,22 +3592,25 @@ async function runCli(options = {}) {
|
|
|
3483
3592
|
}
|
|
3484
3593
|
try {
|
|
3485
3594
|
validateConfig({}, config);
|
|
3595
|
+
const numericProblems = findInvalidNumericSettings(config);
|
|
3596
|
+
if (numericProblems.length > 0) throw new ConfigValidationError(numericProblems.join(" "));
|
|
3486
3597
|
} catch (err) {
|
|
3487
3598
|
if (!(err instanceof ConfigValidationError)) throw err;
|
|
3488
|
-
|
|
3489
|
-
|
|
3599
|
+
throw new StartupError(
|
|
3600
|
+
`Aielia cannot start with this configuration: ${err.message}
|
|
3601
|
+
Fix it by setting the missing value (in the config file under ${dataDir}, or via the matching ASSISTANT_* environment variable), or by turning that feature off, then start again.`
|
|
3602
|
+
);
|
|
3490
3603
|
}
|
|
3491
3604
|
if (nonInteractiveApprovalMode === "require-tty" && !process.stdin.isTTY) {
|
|
3492
|
-
|
|
3605
|
+
throw new StartupError(
|
|
3493
3606
|
`ASSISTANT_NON_INTERACTIVE_APPROVAL=require-tty is set, but stdin is not a real TTY (piped/scripted input). Refusing to start rather than fail confusingly at the first approval prompt — see README.md's "Non-interactive / scripted use" section for the alternative (ASSISTANT_NON_INTERACTIVE_APPROVAL=decline).`
|
|
3494
3607
|
);
|
|
3495
|
-
process.exit(1);
|
|
3496
3608
|
}
|
|
3497
3609
|
let assistant = options.assistant ?? await buildAssistant(config, { dataDir, backend, remindersFile });
|
|
3498
3610
|
const rl = createInterface({ input: options.input ?? process.stdin, output: options.output ?? process.stdout, prompt: "you> " });
|
|
3499
3611
|
rl.pause();
|
|
3500
3612
|
rl.on("SIGINT", () => {
|
|
3501
|
-
|
|
3613
|
+
printSafe("\nExiting.");
|
|
3502
3614
|
process.exit(0);
|
|
3503
3615
|
});
|
|
3504
3616
|
const backendDisplayModel = {
|
|
@@ -3508,7 +3620,7 @@ async function runCli(options = {}) {
|
|
|
3508
3620
|
openai: OPENAI_DEFAULT_MODEL,
|
|
3509
3621
|
openrouter: OPENROUTER_DEFAULT_MODEL
|
|
3510
3622
|
};
|
|
3511
|
-
|
|
3623
|
+
printSafe(`backend: ${config.llmBackend} (${config.model ?? backendDisplayModel[config.llmBackend]})`);
|
|
3512
3624
|
const enabledCapabilities = [];
|
|
3513
3625
|
if (config.enableWeb) enabledCapabilities.push("web search/fetch (brave)");
|
|
3514
3626
|
if (config.enableShell) enabledCapabilities.push(`shell commands (${config.dangerouslySkipPermissions ? "NOT approval-gated" : "approval-gated"})`);
|
|
@@ -3520,9 +3632,9 @@ async function runCli(options = {}) {
|
|
|
3520
3632
|
const undoBanner = undoableFromBefore > 0 ? `
|
|
3521
3633
|
${undoableFromBefore} action${undoableFromBefore === 1 ? "" : "s"} from earlier sessions ${undoableFromBefore === 1 ? "is" : "are"} still revertible — see /undo-action.
|
|
3522
3634
|
` : "";
|
|
3523
|
-
|
|
3635
|
+
printSafe(`Aielia — your personal assistant on the 11-layer harness, one turn at a time. Ctrl+C to exit.${capabilitySuffix}
|
|
3524
3636
|
${dangerBanner}${nonInteractiveBanner}${undoBanner}`);
|
|
3525
|
-
|
|
3637
|
+
printSafe("Type /help to see all commands, /config to view settings.\n");
|
|
3526
3638
|
void assistant.proposeMemoryConsolidation("cli").catch(() => void 0);
|
|
3527
3639
|
rl.prompt();
|
|
3528
3640
|
let lastTrace;
|
|
@@ -3554,7 +3666,7 @@ ${dangerBanner}${nonInteractiveBanner}${undoBanner}`);
|
|
|
3554
3666
|
};
|
|
3555
3667
|
function printPlan() {
|
|
3556
3668
|
if (!lastPlanStatus) {
|
|
3557
|
-
|
|
3669
|
+
printSafe("\nNo active plan for this session.\n");
|
|
3558
3670
|
return;
|
|
3559
3671
|
}
|
|
3560
3672
|
const lines = [
|
|
@@ -3566,20 +3678,20 @@ ${dangerBanner}${nonInteractiveBanner}${undoBanner}`);
|
|
|
3566
3678
|
]),
|
|
3567
3679
|
`Success criteria: ${lastPlanStatus.successCriteria}`
|
|
3568
3680
|
];
|
|
3569
|
-
console.log(`
|
|
3681
|
+
console.log(stripTerminalControls(`
|
|
3570
3682
|
${lines.join("\n")}
|
|
3571
|
-
`);
|
|
3683
|
+
`));
|
|
3572
3684
|
}
|
|
3573
3685
|
async function handlePlanSketch(args) {
|
|
3574
3686
|
const request = args.join(" ").trim();
|
|
3575
3687
|
if (!request) {
|
|
3576
|
-
|
|
3688
|
+
printSafe("\nUsage: /plan sketch <request>\n");
|
|
3577
3689
|
return;
|
|
3578
3690
|
}
|
|
3579
|
-
|
|
3691
|
+
printSafe("\nSketching a plan (read-only grounding only, nothing staged)...\n");
|
|
3580
3692
|
const result = await assistant.sketchPlan("cli", request);
|
|
3581
|
-
console.log(`Aielia> ${result.reply ?? "(no reply)"}
|
|
3582
|
-
`);
|
|
3693
|
+
console.log(stripTerminalControls(`Aielia> ${result.reply ?? "(no reply)"}
|
|
3694
|
+
`));
|
|
3583
3695
|
if (result.usage) lastTurnUsage = withCostEstimate(result.usage);
|
|
3584
3696
|
}
|
|
3585
3697
|
const PLAN_APPROVAL_OPTIONS = [
|
|
@@ -3598,9 +3710,9 @@ ${lines.join("\n")}
|
|
|
3598
3710
|
...snapshot.tasks.map((task) => ` ${task.riskLevel === "HIGH" ? "!" : task.riskLevel === "MEDIUM" ? "·" : "○"} [${task.riskLevel ?? "LOW"}] ${task.id} — ${task.description}`),
|
|
3599
3711
|
...snapshot.reviewNotes && snapshot.reviewNotes.length > 0 ? ["Review notes:", ...snapshot.reviewNotes.map((note) => ` - ${note}`)] : []
|
|
3600
3712
|
];
|
|
3601
|
-
console.log(`
|
|
3713
|
+
console.log(stripTerminalControls(`
|
|
3602
3714
|
${lines.join("\n")}
|
|
3603
|
-
`);
|
|
3715
|
+
`));
|
|
3604
3716
|
}
|
|
3605
3717
|
async function promptPlanEdits(snapshot) {
|
|
3606
3718
|
const knownIds = new Set(snapshot.tasks.map((task) => task.id));
|
|
@@ -3614,7 +3726,7 @@ ${lines.join("\n")}
|
|
|
3614
3726
|
const validCancels = cancelTaskIds.filter((id) => knownIds.has(id));
|
|
3615
3727
|
const validEdits = parsedEdits.filter((edit) => knownIds.has(edit.id) && edit.description);
|
|
3616
3728
|
const unknown = [...cancelTaskIds.filter((id) => !knownIds.has(id)), ...parsedEdits.filter((e) => !knownIds.has(e.id)).map((e) => e.id)];
|
|
3617
|
-
if (unknown.length > 0)
|
|
3729
|
+
if (unknown.length > 0) printSafe(` [ignoring unknown task id(s): ${[...new Set(unknown)].join(", ")}]`);
|
|
3618
3730
|
return {
|
|
3619
3731
|
...validCancels.length > 0 ? { cancelTaskIds: validCancels } : {},
|
|
3620
3732
|
...validEdits.length > 0 ? { editedTasks: validEdits } : {}
|
|
@@ -3622,7 +3734,7 @@ ${lines.join("\n")}
|
|
|
3622
3734
|
}
|
|
3623
3735
|
async function handlePlanApprovalCommand(sub) {
|
|
3624
3736
|
if (!pendingPlanApproval) {
|
|
3625
|
-
|
|
3737
|
+
printSafe("\nNo plan is awaiting approval.\n");
|
|
3626
3738
|
return;
|
|
3627
3739
|
}
|
|
3628
3740
|
const { id, message, snapshot } = pendingPlanApproval;
|
|
@@ -3634,7 +3746,7 @@ ${lines.join("\n")}
|
|
|
3634
3746
|
decision = "approve_trusted";
|
|
3635
3747
|
} else if (sub === "edit") {
|
|
3636
3748
|
if (!snapshot) {
|
|
3637
|
-
|
|
3749
|
+
printSafe("\n[tasks unavailable for editing — /plan approve or /plan decline]\n");
|
|
3638
3750
|
return;
|
|
3639
3751
|
}
|
|
3640
3752
|
printPlanApproval(snapshot);
|
|
@@ -3658,7 +3770,7 @@ ${lines.join("\n")}
|
|
|
3658
3770
|
async function handlePlanGraph(args) {
|
|
3659
3771
|
const nodes = await loadPlanGraphNodes(void 0, args[0]);
|
|
3660
3772
|
if (!nodes) {
|
|
3661
|
-
|
|
3773
|
+
printSafe(args[0] ? `
|
|
3662
3774
|
No plan graph for thread "${args[0]}".
|
|
3663
3775
|
` : "\nNo active plan for this session.\n");
|
|
3664
3776
|
return;
|
|
@@ -3669,7 +3781,7 @@ No plan graph for thread "${args[0]}".
|
|
|
3669
3781
|
options.openPlanGraph(nodes);
|
|
3670
3782
|
return;
|
|
3671
3783
|
}
|
|
3672
|
-
|
|
3784
|
+
printSafe(`
|
|
3673
3785
|
[plan graph unavailable: ${probe.message}] showing the checklist instead.`);
|
|
3674
3786
|
printPlan();
|
|
3675
3787
|
return;
|
|
@@ -3678,20 +3790,20 @@ No plan graph for thread "${args[0]}".
|
|
|
3678
3790
|
const color = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
|
|
3679
3791
|
const result = renderPlan(nodes, { maxCols: cols, color });
|
|
3680
3792
|
if (!result.ok) {
|
|
3681
|
-
|
|
3793
|
+
printSafe(`
|
|
3682
3794
|
[plan graph unavailable: ${result.message}] showing the checklist instead.`);
|
|
3683
3795
|
printPlan();
|
|
3684
3796
|
return;
|
|
3685
3797
|
}
|
|
3686
3798
|
if (!result.fits) {
|
|
3687
|
-
|
|
3799
|
+
printSafe(`
|
|
3688
3800
|
[plan graph is wider than ${cols} columns] dependency list:
|
|
3689
3801
|
`);
|
|
3690
|
-
for (const n of nodes)
|
|
3691
|
-
|
|
3802
|
+
for (const n of nodes) printSafe(` [${n.status}] ${n.id} — ${n.label}${n.deps.length ? ` (after: ${n.deps.join(", ")})` : ""}`);
|
|
3803
|
+
printSafe("");
|
|
3692
3804
|
return;
|
|
3693
3805
|
}
|
|
3694
|
-
|
|
3806
|
+
printSafe(`
|
|
3695
3807
|
${result.lines.join("\n")}
|
|
3696
3808
|
`);
|
|
3697
3809
|
}
|
|
@@ -3730,52 +3842,52 @@ ${result.lines.join("\n")}
|
|
|
3730
3842
|
function printWhy() {
|
|
3731
3843
|
const memoryLine = formatMemoryInjection(assistant.getLastMemoryInjection());
|
|
3732
3844
|
if (!lastTrace) {
|
|
3733
|
-
|
|
3845
|
+
printSafe(`
|
|
3734
3846
|
${lastNoTraceReason ?? "No harness trace for the last turn (nothing to explain yet, or it took the fast path)."}
|
|
3735
3847
|
${memoryLine}
|
|
3736
3848
|
`);
|
|
3737
3849
|
return;
|
|
3738
3850
|
}
|
|
3739
|
-
|
|
3851
|
+
printSafe(`
|
|
3740
3852
|
${verificationHealthLabel(lastTrace.verificationHealth)}`);
|
|
3741
3853
|
const chain = buildWhyChain(lastTrace.layerActivity);
|
|
3742
3854
|
if (chain.length > 0) {
|
|
3743
|
-
|
|
3855
|
+
printSafe(" " + chain.map((item) => `${LAYER_SHORT_CODE[item.layer]} (${item.reason})`).join(" > "));
|
|
3744
3856
|
}
|
|
3745
3857
|
if (lastTrace.batchBudget) {
|
|
3746
|
-
|
|
3858
|
+
printSafe(" " + batchBudgetSummaryLine(lastTrace.batchBudget));
|
|
3747
3859
|
}
|
|
3748
|
-
|
|
3749
|
-
|
|
3860
|
+
printSafe(memoryLine);
|
|
3861
|
+
printSafe("");
|
|
3750
3862
|
}
|
|
3751
3863
|
function printLayers() {
|
|
3752
3864
|
if (!lastTrace) {
|
|
3753
|
-
|
|
3865
|
+
printSafe(`
|
|
3754
3866
|
${lastNoTraceReason ?? "No harness trace for the last turn (nothing to explain yet, or it took the fast path)."}
|
|
3755
3867
|
`);
|
|
3756
3868
|
return;
|
|
3757
3869
|
}
|
|
3758
|
-
|
|
3870
|
+
printSafe("");
|
|
3759
3871
|
const byLayer = new Map(lastTrace.layerActivity.map((e) => [e.layer, e]));
|
|
3760
3872
|
for (const layer of LAYER_ORDER) {
|
|
3761
3873
|
const e = byLayer.get(layer);
|
|
3762
3874
|
const mark = (e == null ? void 0 : e.fired) ? "✓" : "·";
|
|
3763
3875
|
const reason = (e == null ? void 0 : e.reason) ?? "not evaluated this turn";
|
|
3764
|
-
|
|
3876
|
+
printSafe(` [${mark}] ${LAYER_DISPLAY_NAME[layer].padEnd(22)} ${reason}`);
|
|
3765
3877
|
}
|
|
3766
3878
|
if (lastTrace.batchBudget) {
|
|
3767
|
-
|
|
3768
|
-
|
|
3879
|
+
printSafe("");
|
|
3880
|
+
printSafe(` ${batchBudgetSummaryLine(lastTrace.batchBudget)}`);
|
|
3769
3881
|
const STATUS_MARK = {
|
|
3770
3882
|
found: "✓",
|
|
3771
3883
|
not_found: "✗",
|
|
3772
3884
|
truncated_while_productive: "~"
|
|
3773
3885
|
};
|
|
3774
3886
|
for (const outcome of lastTrace.batchBudget.perItemOutcomes) {
|
|
3775
|
-
|
|
3887
|
+
printSafe(` [${STATUS_MARK[outcome.status]}] ${outcome.item.padEnd(30)} ${outcome.status} (${outcome.callsUsed} calls)`);
|
|
3776
3888
|
}
|
|
3777
3889
|
}
|
|
3778
|
-
|
|
3890
|
+
printSafe("");
|
|
3779
3891
|
}
|
|
3780
3892
|
const SOURCE_TOOL_LABEL = {
|
|
3781
3893
|
read_file: "Read",
|
|
@@ -3785,17 +3897,17 @@ ${lastNoTraceReason ?? "No harness trace for the last turn (nothing to explain y
|
|
|
3785
3897
|
};
|
|
3786
3898
|
function printSources() {
|
|
3787
3899
|
if (!lastSources || lastSources.length === 0) {
|
|
3788
|
-
|
|
3900
|
+
printSafe("\nNo sources for the last turn (it used no file/web tool calls).\n");
|
|
3789
3901
|
return;
|
|
3790
3902
|
}
|
|
3791
|
-
|
|
3903
|
+
printSafe("");
|
|
3792
3904
|
for (const source of lastSources) {
|
|
3793
|
-
|
|
3905
|
+
printSafe(` - ${SOURCE_TOOL_LABEL[source.tool]} ${source.path}`);
|
|
3794
3906
|
}
|
|
3795
|
-
|
|
3907
|
+
printSafe("");
|
|
3796
3908
|
}
|
|
3797
3909
|
function printHelp() {
|
|
3798
|
-
|
|
3910
|
+
printSafe(`
|
|
3799
3911
|
${formatHelp()}
|
|
3800
3912
|
`);
|
|
3801
3913
|
}
|
|
@@ -3807,12 +3919,12 @@ ${formatHelp()}
|
|
|
3807
3919
|
pendingPlanApproval = void 0;
|
|
3808
3920
|
lastTurnUsage = void 0;
|
|
3809
3921
|
lastNoTraceReason = void 0;
|
|
3810
|
-
|
|
3922
|
+
printSafe("\n✓ Started a fresh conversation.\n");
|
|
3811
3923
|
}
|
|
3812
3924
|
async function handleCheckpoint(args) {
|
|
3813
3925
|
if (args[0] === "clear") {
|
|
3814
3926
|
const result = await assistant.clearCheckpoint("cli");
|
|
3815
|
-
|
|
3927
|
+
printSafe(
|
|
3816
3928
|
result.cleared ? `
|
|
3817
3929
|
✓ Cleared the stuck checkpoint (was at step ${result.stepsUsed}, node "${result.currentNode}"). Conversation history is untouched — your next message starts a fresh harness run.
|
|
3818
3930
|
` : "\nNo checkpoint to clear — the last turn either completed normally or there was nothing in progress.\n"
|
|
@@ -3821,11 +3933,11 @@ ${formatHelp()}
|
|
|
3821
3933
|
}
|
|
3822
3934
|
const status = await assistant.getCheckpointStatus("cli");
|
|
3823
3935
|
if (!status.present) {
|
|
3824
|
-
|
|
3936
|
+
printSafe("\nNo checkpoint present — the last turn completed normally or there was nothing in progress.\n");
|
|
3825
3937
|
return;
|
|
3826
3938
|
}
|
|
3827
3939
|
const attemptsNote = status.failedResumeAttempts > 0 ? ` — failed to resume ${status.failedResumeAttempts} time${status.failedResumeAttempts === 1 ? "" : "s"} in a row so far` : "";
|
|
3828
|
-
|
|
3940
|
+
printSafe(
|
|
3829
3941
|
`
|
|
3830
3942
|
A checkpoint is present: step ${status.stepsUsed}, last node "${status.currentNode}"${attemptsNote}.
|
|
3831
3943
|
Your next message will try to resume it automatically. Run /checkpoint clear to discard it and start fresh instead.
|
|
@@ -3836,7 +3948,7 @@ Your next message will try to resume it automatically. Run /checkpoint clear to
|
|
|
3836
3948
|
const transcript = await assistant.getTranscript("cli");
|
|
3837
3949
|
const undoLogEntries = await assistant.listUndoLogEntries();
|
|
3838
3950
|
const spendCapLine = await spendCapStatusLine();
|
|
3839
|
-
|
|
3951
|
+
printSafe(
|
|
3840
3952
|
`
|
|
3841
3953
|
${formatStatus({ config, overriddenKeys, transcriptLength: transcript.length, planActive: lastPlanStatus !== void 0, undoLogEntries, spendCapLine })}
|
|
3842
3954
|
`
|
|
@@ -3850,18 +3962,18 @@ ${formatStatus({ config, overriddenKeys, transcriptLength: transcript.length, pl
|
|
|
3850
3962
|
async function handleExport(args) {
|
|
3851
3963
|
const transcript = await assistant.getTranscript("cli");
|
|
3852
3964
|
if (transcript.length === 0) {
|
|
3853
|
-
|
|
3965
|
+
printSafe("\nNothing to export yet.\n");
|
|
3854
3966
|
return;
|
|
3855
3967
|
}
|
|
3856
|
-
const filename = resolve(process.cwd(), args
|
|
3968
|
+
const filename = resolve(process.cwd(), args.length > 0 ? args.join(" ") : defaultExportFilename());
|
|
3857
3969
|
try {
|
|
3858
3970
|
await writeFile(filename, formatTranscriptMarkdown(transcript), "utf-8");
|
|
3859
|
-
|
|
3971
|
+
printSafe(`
|
|
3860
3972
|
✓ Exported ${transcript.length} message${transcript.length === 1 ? "" : "s"} to ${filename}
|
|
3861
3973
|
`);
|
|
3862
3974
|
} catch (err) {
|
|
3863
3975
|
const { message } = classifyError(err);
|
|
3864
|
-
|
|
3976
|
+
printSafe(`
|
|
3865
3977
|
[error] ${message}
|
|
3866
3978
|
`);
|
|
3867
3979
|
}
|
|
@@ -3869,14 +3981,14 @@ ${formatStatus({ config, overriddenKeys, transcriptLength: transcript.length, pl
|
|
|
3869
3981
|
async function handleUndo() {
|
|
3870
3982
|
const transcriptBefore = await assistant.getTranscript("cli");
|
|
3871
3983
|
if (transcriptBefore.length === 0) {
|
|
3872
|
-
|
|
3984
|
+
printSafe("\nNothing to undo.\n");
|
|
3873
3985
|
return;
|
|
3874
3986
|
}
|
|
3875
3987
|
const wasPendingApproval = transcriptBefore[transcriptBefore.length - 1].role !== "assistant";
|
|
3876
3988
|
const undoneReminders = lastTurnToolSteps.filter((s) => s.tool === "create_reminder");
|
|
3877
3989
|
const result = await assistant.undoLastTurn("cli");
|
|
3878
3990
|
if (!result.undone) {
|
|
3879
|
-
|
|
3991
|
+
printSafe("\nNothing to undo.\n");
|
|
3880
3992
|
return;
|
|
3881
3993
|
}
|
|
3882
3994
|
lastTrace = void 0;
|
|
@@ -3884,7 +3996,7 @@ ${formatStatus({ config, overriddenKeys, transcriptLength: transcript.length, pl
|
|
|
3884
3996
|
lastPlanStatus = void 0;
|
|
3885
3997
|
lastTurnToolSteps = [];
|
|
3886
3998
|
const caveat = undoneReminders.length > 0 ? ` Note: ${undoneReminders.length === 1 ? "a reminder" : `${undoneReminders.length} reminders`} created in that exchange (${undoneReminders.map((s) => `"${s.input.text}"`).join(", ")}) ${undoneReminders.length === 1 ? "is" : "are"} still active — /undo only removes chat history, not that side effect.` : "";
|
|
3887
|
-
|
|
3999
|
+
printSafe(
|
|
3888
4000
|
`
|
|
3889
4001
|
✓ Removed ${wasPendingApproval ? "the pending message awaiting approval" : "the last exchange (1 user message, 1 assistant reply)"}.${caveat}
|
|
3890
4002
|
`
|
|
@@ -3893,20 +4005,20 @@ ${formatStatus({ config, overriddenKeys, transcriptLength: transcript.length, pl
|
|
|
3893
4005
|
async function handleUndoAction(args) {
|
|
3894
4006
|
if (args.length === 0) {
|
|
3895
4007
|
const entries = await assistant.listUndoLogEntries();
|
|
3896
|
-
|
|
4008
|
+
printSafe(`
|
|
3897
4009
|
${formatUndoLogListing(entries)}
|
|
3898
4010
|
`);
|
|
3899
4011
|
return;
|
|
3900
4012
|
}
|
|
3901
4013
|
const staged = await assistant.stageUndoAction(args[0]);
|
|
3902
4014
|
if (staged.status === "error") {
|
|
3903
|
-
|
|
4015
|
+
printSafe(`
|
|
3904
4016
|
✗ ${staged.message}
|
|
3905
4017
|
`);
|
|
3906
4018
|
return;
|
|
3907
4019
|
}
|
|
3908
|
-
console.log(`
|
|
3909
|
-
[needs approval — revert] ${staged.reason}`);
|
|
4020
|
+
console.log(stripTerminalControls(`
|
|
4021
|
+
[needs approval — revert] ${staged.reason}`));
|
|
3910
4022
|
const confirmed = await askYesNo("Apply this revert? (y/N) ");
|
|
3911
4023
|
lastTrace = void 0;
|
|
3912
4024
|
lastNoTraceReason = `No harness trace — the last turn was a staged revert that was ${confirmed ? "approved" : "declined"} before the harness ran.`;
|
|
@@ -3918,7 +4030,7 @@ ${formatUndoLogListing(entries)}
|
|
|
3918
4030
|
const digestLine = digests.length > 0 ? `
|
|
3919
4031
|
Session digests stored: ${digests.length} (/memory forget digest [id] erases them; /memory export includes them).
|
|
3920
4032
|
` : "";
|
|
3921
|
-
|
|
4033
|
+
printSafe(`
|
|
3922
4034
|
${formatMemoryStatus(await assistant.getMemoryStatus("cli"))}
|
|
3923
4035
|
|
|
3924
4036
|
${formatMemorySummary(summary)}
|
|
@@ -3926,15 +4038,15 @@ ${digestLine}`);
|
|
|
3926
4038
|
}
|
|
3927
4039
|
async function handleMemoryExport(args) {
|
|
3928
4040
|
const data = await assistant.exportMemory("cli");
|
|
3929
|
-
const filename = resolve(process.cwd(), args
|
|
4041
|
+
const filename = resolve(process.cwd(), args.length > 0 ? args.join(" ") : defaultMemoryExportFilename());
|
|
3930
4042
|
try {
|
|
3931
4043
|
await writeFile(filename, formatMemoryExport(data), "utf-8");
|
|
3932
|
-
|
|
4044
|
+
printSafe(`
|
|
3933
4045
|
✓ Exported learned memory to ${filename}
|
|
3934
4046
|
`);
|
|
3935
4047
|
} catch (err) {
|
|
3936
4048
|
const { message } = classifyError(err);
|
|
3937
|
-
|
|
4049
|
+
printSafe(`
|
|
3938
4050
|
[error] ${message}
|
|
3939
4051
|
`);
|
|
3940
4052
|
}
|
|
@@ -3942,26 +4054,26 @@ ${digestLine}`);
|
|
|
3942
4054
|
async function handleMemoryPending(action, args) {
|
|
3943
4055
|
const selector = args[0];
|
|
3944
4056
|
if (!selector) {
|
|
3945
|
-
|
|
4057
|
+
printSafe(`
|
|
3946
4058
|
Usage: /memory ${action} <n> or /memory ${action} <category>
|
|
3947
4059
|
`);
|
|
3948
4060
|
return;
|
|
3949
4061
|
}
|
|
3950
4062
|
const outcome = action === "confirm" ? await assistant.confirmPendingFact(selector) : await assistant.rejectPendingFact(selector);
|
|
3951
|
-
|
|
4063
|
+
printSafe(`
|
|
3952
4064
|
${formatMemoryPendingOutcome(action === "confirm" ? "confirmed" : "rejected", outcome)}
|
|
3953
4065
|
`);
|
|
3954
4066
|
}
|
|
3955
4067
|
async function handleMemoryForget(args) {
|
|
3956
4068
|
const selector = args[0];
|
|
3957
4069
|
if (!selector) {
|
|
3958
|
-
|
|
4070
|
+
printSafe("\nUsage: /memory forget <n> [erase]\n");
|
|
3959
4071
|
return;
|
|
3960
4072
|
}
|
|
3961
4073
|
const erase = args[1] === "erase";
|
|
3962
4074
|
const outcome = await assistant.forgetFact(selector, "cli", erase);
|
|
3963
4075
|
const note = outcome.ok && !erase && memoryAuditLogEnabled() ? " The audit log still holds its text (see /memory history); use `/memory forget <n> erase` to remove that too." : "";
|
|
3964
|
-
|
|
4076
|
+
printSafe(`
|
|
3965
4077
|
${formatMemoryPendingOutcome("forgotten", outcome)}${note}
|
|
3966
4078
|
`);
|
|
3967
4079
|
}
|
|
@@ -3976,7 +4088,7 @@ ${formatMemoryPendingOutcome("forgotten", outcome)}${note}
|
|
|
3976
4088
|
}
|
|
3977
4089
|
if (args[0] === "forget" && (args[1] === "digest" || args[1] === "digests")) {
|
|
3978
4090
|
const removed = await assistant.forgetDigests(args[2]);
|
|
3979
|
-
|
|
4091
|
+
printSafe(`
|
|
3980
4092
|
Forgot ${removed} session digest${removed === 1 ? "" : "s"}.
|
|
3981
4093
|
`);
|
|
3982
4094
|
return;
|
|
@@ -3986,13 +4098,13 @@ Forgot ${removed} session digest${removed === 1 ? "" : "s"}.
|
|
|
3986
4098
|
return;
|
|
3987
4099
|
}
|
|
3988
4100
|
if (args[0] === "history") {
|
|
3989
|
-
|
|
4101
|
+
printSafe(`
|
|
3990
4102
|
${formatMemoryHistory(await assistant.memoryHistory())}
|
|
3991
4103
|
`);
|
|
3992
4104
|
return;
|
|
3993
4105
|
}
|
|
3994
4106
|
if (args[0] === "status") {
|
|
3995
|
-
|
|
4107
|
+
printSafe(`
|
|
3996
4108
|
${formatMemoryStatus(await assistant.getMemoryStatus("cli"))}
|
|
3997
4109
|
`);
|
|
3998
4110
|
return;
|
|
@@ -4000,31 +4112,31 @@ ${formatMemoryStatus(await assistant.getMemoryStatus("cli"))}
|
|
|
4000
4112
|
if (args[0] === "archive") {
|
|
4001
4113
|
if (args[1] === "forget") {
|
|
4002
4114
|
const outcome = await assistant.forgetArchivedFact(args[2] ?? "");
|
|
4003
|
-
|
|
4115
|
+
printSafe(`
|
|
4004
4116
|
${formatMemoryPendingOutcome("forgotten", outcome)}
|
|
4005
4117
|
`);
|
|
4006
4118
|
return;
|
|
4007
4119
|
}
|
|
4008
4120
|
if (args[1] === "restore") {
|
|
4009
4121
|
const outcome = await assistant.restoreArchivedMemory(args[2] ?? "", "cli");
|
|
4010
|
-
|
|
4122
|
+
printSafe(`
|
|
4011
4123
|
${outcome.message}
|
|
4012
4124
|
`);
|
|
4013
4125
|
return;
|
|
4014
4126
|
}
|
|
4015
|
-
|
|
4127
|
+
printSafe(`
|
|
4016
4128
|
${formatMemoryArchive(await assistant.listArchivedFacts(), await assistant.restorableArchiveCount())}
|
|
4017
4129
|
`);
|
|
4018
4130
|
return;
|
|
4019
4131
|
}
|
|
4020
4132
|
if (args[0] === "off" || args[0] === "on") {
|
|
4021
4133
|
await assistant.setMemoryEnabled(args[0] === "on");
|
|
4022
|
-
|
|
4134
|
+
printSafe(args[0] === "off" ? "\nMemory writes are OFF for this install: nothing new will be saved (facts, pending guesses, digests). What is already stored stays readable; /memory forget and /memory reject still work. /memory on resumes.\n" : "\nMemory writes are on.\n");
|
|
4023
4135
|
return;
|
|
4024
4136
|
}
|
|
4025
4137
|
if (args[0] === "undo") {
|
|
4026
4138
|
const outcome = await assistant.undoMemoryChange(args[1] ?? "", "cli");
|
|
4027
|
-
|
|
4139
|
+
printSafe(`
|
|
4028
4140
|
${outcome.message}
|
|
4029
4141
|
`);
|
|
4030
4142
|
return;
|
|
@@ -4038,7 +4150,7 @@ ${outcome.message}
|
|
|
4038
4150
|
async function handleMemoryConsolidate(args) {
|
|
4039
4151
|
if (args[0] === "accept" || args[0] === "dismiss") {
|
|
4040
4152
|
const outcome2 = args[0] === "accept" ? await assistant.acceptMemoryProposal(args[1] ?? "", "cli") : await assistant.dismissMemoryProposal(args[1] ?? "");
|
|
4041
|
-
|
|
4153
|
+
printSafe(`
|
|
4042
4154
|
${outcome2.message}
|
|
4043
4155
|
`);
|
|
4044
4156
|
return;
|
|
@@ -4046,12 +4158,12 @@ ${outcome2.message}
|
|
|
4046
4158
|
const outcome = await assistant.consolidateMemory("cli");
|
|
4047
4159
|
const proposals = await assistant.memoryProposals();
|
|
4048
4160
|
if (proposals.length === 0) {
|
|
4049
|
-
|
|
4161
|
+
printSafe(`
|
|
4050
4162
|
${outcome.message}
|
|
4051
4163
|
`);
|
|
4052
4164
|
return;
|
|
4053
4165
|
}
|
|
4054
|
-
|
|
4166
|
+
printSafe(`
|
|
4055
4167
|
${outcome.status === "done" ? "" : `${outcome.message}
|
|
4056
4168
|
`}Proposed changes (nothing applied yet):
|
|
4057
4169
|
${proposals.map((p, i) => ` ${i + 1}. [${p.kind}]${p.touchesUserAsserted ? " (touches something you said yourself)" : ""} ${p.text ? `-> "${p.text}" ` : ""}${p.reason}`).join("\n")}
|
|
@@ -4061,17 +4173,17 @@ ${proposals.map((p, i) => ` ${i + 1}. [${p.kind}]${p.touchesUserAsserted ? " (t
|
|
|
4061
4173
|
async function handleSearch(args) {
|
|
4062
4174
|
const query = args.join(" ");
|
|
4063
4175
|
if (!query.trim()) {
|
|
4064
|
-
|
|
4176
|
+
printSafe("\nUsage: /search <query>\n");
|
|
4065
4177
|
return;
|
|
4066
4178
|
}
|
|
4067
4179
|
const hits = await assistant.searchTranscript(query);
|
|
4068
|
-
|
|
4180
|
+
printSafe(`
|
|
4069
4181
|
${formatSearchResults(hits, query)}
|
|
4070
4182
|
`);
|
|
4071
4183
|
}
|
|
4072
4184
|
async function handleGoals() {
|
|
4073
4185
|
const state = await assistant.getGoalGraphState("cli");
|
|
4074
|
-
|
|
4186
|
+
printSafe(`
|
|
4075
4187
|
${formatGoalGraphState(state)}
|
|
4076
4188
|
`);
|
|
4077
4189
|
}
|
|
@@ -4079,7 +4191,7 @@ ${formatGoalGraphState(state)}
|
|
|
4079
4191
|
const spendCapLine = await spendCapStatusLine();
|
|
4080
4192
|
const spendState = await assistant.getSpendState("cli");
|
|
4081
4193
|
const session = { inputTokens: spendState.cumulativeInputTokens, outputTokens: spendState.cumulativeOutputTokens, costUsd: spendState.cumulativeCostUsd };
|
|
4082
|
-
|
|
4194
|
+
printSafe(`
|
|
4083
4195
|
${formatCostSummary({ lastTurn: lastTurnUsage, session, backend: config.llmBackend, spendCapLine })}
|
|
4084
4196
|
`);
|
|
4085
4197
|
}
|
|
@@ -4101,7 +4213,7 @@ ${formatCostSummary({ lastTurn: lastTurnUsage, session, backend: config.llmBacke
|
|
|
4101
4213
|
// that explicitly here rather than leaving it unstated (no keychain integration exists).
|
|
4102
4214
|
{ label: "provider keys stored: plaintext in config.json (no OS keychain integration)", ok: true }
|
|
4103
4215
|
]);
|
|
4104
|
-
|
|
4216
|
+
printSafe(`
|
|
4105
4217
|
${formatDoctorReport(checks)}
|
|
4106
4218
|
`);
|
|
4107
4219
|
}
|
|
@@ -4110,7 +4222,7 @@ ${formatDoctorReport(checks)}
|
|
|
4110
4222
|
const layer = nodeToLayer(progress.currentNode);
|
|
4111
4223
|
const label = layer ? LAYER_DISPLAY_NAME[layer] : nodeDisplayName(progress.currentNode);
|
|
4112
4224
|
const prefix = progress.planPosition ? `[${progress.planPosition.templateName ?? "custom plan"} — step ${progress.planPosition.stepIndex}/${progress.planPosition.stepCount} (${progress.planPosition.completionPct.toFixed(0)}%)]` : `[step ${progress.stepsUsed}/${progress.maxSteps}]`;
|
|
4113
|
-
const line = `${prefix}${label ? ` ${label}…` : ""}
|
|
4225
|
+
const line = stripTerminalControls(`${prefix}${label ? ` ${label}…` : ""}`);
|
|
4114
4226
|
process.stdout.write(`\r${line.padEnd(lastProgressLineLength)}`);
|
|
4115
4227
|
lastProgressLineLength = line.length;
|
|
4116
4228
|
}
|
|
@@ -4122,9 +4234,9 @@ ${formatDoctorReport(checks)}
|
|
|
4122
4234
|
function writeToolStep(step) {
|
|
4123
4235
|
clearProgress();
|
|
4124
4236
|
if (step.deniedReason) {
|
|
4125
|
-
console.log(` ${ICONS.deniedStep} Denied: ${step.summary} — ${step.deniedReason}`);
|
|
4237
|
+
console.log(stripTerminalControls(` ${ICONS.deniedStep} Denied: ${step.summary} — ${step.deniedReason}`));
|
|
4126
4238
|
} else {
|
|
4127
|
-
console.log(` ${toolStepIcon(step.tool)} ${step.summary}`);
|
|
4239
|
+
console.log(stripTerminalControls(` ${toolStepIcon(step.tool)} ${step.summary}`));
|
|
4128
4240
|
}
|
|
4129
4241
|
lastTurnToolSteps.push(step);
|
|
4130
4242
|
}
|
|
@@ -4147,20 +4259,20 @@ ${formatDoctorReport(checks)}
|
|
|
4147
4259
|
function printClarificationQuestion(question, draft, index, total) {
|
|
4148
4260
|
const options2 = orderedAskOptions(question);
|
|
4149
4261
|
const allowFreeText = question.allowFreeText !== false;
|
|
4150
|
-
|
|
4151
|
-
if (question.header) console.log(`[${question.header}]`);
|
|
4152
|
-
if (total > 1)
|
|
4153
|
-
console.log(question.question);
|
|
4262
|
+
printSafe("");
|
|
4263
|
+
if (question.header) console.log(stripTerminalControls(`[${question.header}]`));
|
|
4264
|
+
if (total > 1) printSafe(`Question ${index + 1} of ${total}`);
|
|
4265
|
+
console.log(stripTerminalControls(question.question));
|
|
4154
4266
|
options2.forEach((option, i) => {
|
|
4155
4267
|
const n = i + 1;
|
|
4156
4268
|
const picked = draft.selectedLabels.includes(option.label) ? "✓" : " ";
|
|
4157
4269
|
const recommended = option.recommended ? " (recommended)" : "";
|
|
4158
|
-
console.log(` [${picked}] ${n}) ${option.label}${recommended}`);
|
|
4159
|
-
if (option.description) console.log(` ${option.description}`);
|
|
4160
|
-
if (option.preview) console.log(` preview: ${option.preview}`);
|
|
4270
|
+
console.log(stripTerminalControls(` [${picked}] ${n}) ${option.label}${recommended}`));
|
|
4271
|
+
if (option.description) console.log(stripTerminalControls(` ${option.description}`));
|
|
4272
|
+
if (option.preview) console.log(stripTerminalControls(` preview: ${option.preview}`));
|
|
4161
4273
|
});
|
|
4162
|
-
if (draft.editText) console.log(` note: ${draft.editText}`);
|
|
4163
|
-
if (draft.freeText) console.log(` other: ${draft.freeText}`);
|
|
4274
|
+
if (draft.editText) console.log(stripTerminalControls(` note: ${draft.editText}`));
|
|
4275
|
+
if (draft.freeText) console.log(stripTerminalControls(` other: ${draft.freeText}`));
|
|
4164
4276
|
const commands2 = [
|
|
4165
4277
|
options2.length > 0 ? question.allowMultiple ? "<n>/t<n> toggle option" : "<n> select option" : void 0,
|
|
4166
4278
|
options2.length > 0 ? "e<n> add a note to a selected option" : void 0,
|
|
@@ -4168,7 +4280,7 @@ ${formatDoctorReport(checks)}
|
|
|
4168
4280
|
total > 1 ? "b back, n next" : void 0,
|
|
4169
4281
|
"s submit"
|
|
4170
4282
|
].filter((c) => c !== void 0);
|
|
4171
|
-
|
|
4283
|
+
printSafe(` (${commands2.join(" | ")})`);
|
|
4172
4284
|
}
|
|
4173
4285
|
async function handleClarification(result) {
|
|
4174
4286
|
const questions = result.questions ?? [];
|
|
@@ -4176,23 +4288,23 @@ ${formatDoctorReport(checks)}
|
|
|
4176
4288
|
if (questions.length === 0 || !pendingClarificationId) {
|
|
4177
4289
|
lastTrace = void 0;
|
|
4178
4290
|
lastNoTraceReason = `No harness trace — the last turn needed clarification (${result.reason ?? "no further detail"}) but had no answerable questions.`;
|
|
4179
|
-
console.log(`
|
|
4291
|
+
console.log(stripTerminalControls(`
|
|
4180
4292
|
[needs clarification] ${result.reason ?? "This request needs clarification."}
|
|
4181
|
-
`);
|
|
4293
|
+
`));
|
|
4182
4294
|
return;
|
|
4183
4295
|
}
|
|
4184
4296
|
if (nonInteractiveApprovalMode === "decline") {
|
|
4185
|
-
|
|
4297
|
+
printSafe(`
|
|
4186
4298
|
[non-interactive mode: auto-declining — ASSISTANT_NON_INTERACTIVE_APPROVAL=decline]`);
|
|
4187
|
-
console.log(`
|
|
4188
|
-
[needs clarification] ${result.reason ?? "This request needs clarification."}`);
|
|
4189
|
-
for (const q2 of questions) console.log(` - ${q2.question}`);
|
|
4190
|
-
|
|
4299
|
+
console.log(stripTerminalControls(`
|
|
4300
|
+
[needs clarification] ${result.reason ?? "This request needs clarification."}`));
|
|
4301
|
+
for (const q2 of questions) console.log(stripTerminalControls(` - ${q2.question}`));
|
|
4302
|
+
printSafe("");
|
|
4191
4303
|
lastTrace = void 0;
|
|
4192
4304
|
lastNoTraceReason = "No harness trace — the last turn needed clarification and was auto-declined (non-interactive mode) before the harness resumed.";
|
|
4193
4305
|
return;
|
|
4194
4306
|
}
|
|
4195
|
-
|
|
4307
|
+
printSafe(`
|
|
4196
4308
|
[needs clarification] ${questions.length} question${questions.length === 1 ? "" : "s"} to answer.`);
|
|
4197
4309
|
const drafts = new Map(questions.map((q2) => [q2.id, emptyClarificationDraft()]));
|
|
4198
4310
|
let index = 0;
|
|
@@ -4202,11 +4314,17 @@ ${formatDoctorReport(checks)}
|
|
|
4202
4314
|
printClarificationQuestion(question, draft, index, questions.length);
|
|
4203
4315
|
const token = (await askLine("clarify> ")).trim();
|
|
4204
4316
|
const lower = token.toLowerCase();
|
|
4317
|
+
if (inputUnavailable) {
|
|
4318
|
+
printSafe("\n[no input available — leaving the clarification unanswered]\n");
|
|
4319
|
+
lastTrace = void 0;
|
|
4320
|
+
lastNoTraceReason = "No harness trace — the last turn needed clarification and input closed before it was answered.";
|
|
4321
|
+
return;
|
|
4322
|
+
}
|
|
4205
4323
|
if (lower === "s" || lower === "submit") {
|
|
4206
4324
|
const answers = questions.map((q2) => clarificationDraftToAnswer(q2.id, drafts.get(q2.id)));
|
|
4207
4325
|
const missing = questions.filter((q2, i) => answers[i] === null);
|
|
4208
4326
|
if (missing.length > 0) {
|
|
4209
|
-
|
|
4327
|
+
printSafe(`
|
|
4210
4328
|
Still need an answer for: ${missing.map((q2) => q2.question).join("; ")}
|
|
4211
4329
|
`);
|
|
4212
4330
|
continue;
|
|
@@ -4225,12 +4343,12 @@ Still need an answer for: ${missing.map((q2) => q2.question).join("; ")}
|
|
|
4225
4343
|
}
|
|
4226
4344
|
if (lower === "o") {
|
|
4227
4345
|
if (question.allowFreeText === false) {
|
|
4228
|
-
|
|
4346
|
+
printSafe("\nThis question does not accept a free-text answer.\n");
|
|
4229
4347
|
continue;
|
|
4230
4348
|
}
|
|
4231
4349
|
const text = await askLine("Other — type your own answer: ");
|
|
4232
4350
|
if (!text.trim()) {
|
|
4233
|
-
|
|
4351
|
+
printSafe("\nEmpty answer ignored.\n");
|
|
4234
4352
|
continue;
|
|
4235
4353
|
}
|
|
4236
4354
|
drafts.set(question.id, { selectedLabels: [], editText: "", freeText: text.trim() });
|
|
@@ -4241,13 +4359,13 @@ Still need an answer for: ${missing.map((q2) => q2.question).join("; ")}
|
|
|
4241
4359
|
const options2 = orderedAskOptions(question);
|
|
4242
4360
|
const option = options2[Number(editMatch[1]) - 1];
|
|
4243
4361
|
if (!option) {
|
|
4244
|
-
|
|
4362
|
+
printSafe(`
|
|
4245
4363
|
No option ${editMatch[1]}.
|
|
4246
4364
|
`);
|
|
4247
4365
|
continue;
|
|
4248
4366
|
}
|
|
4249
4367
|
if (!draft.selectedLabels.includes(option.label)) {
|
|
4250
|
-
|
|
4368
|
+
printSafe(`
|
|
4251
4369
|
Select option ${editMatch[1]} first, then add a note with e${editMatch[1]}.
|
|
4252
4370
|
`);
|
|
4253
4371
|
continue;
|
|
@@ -4261,7 +4379,7 @@ Select option ${editMatch[1]} first, then add a note with e${editMatch[1]}.
|
|
|
4261
4379
|
const options2 = orderedAskOptions(question);
|
|
4262
4380
|
const option = options2[Number(toggleMatch[1]) - 1];
|
|
4263
4381
|
if (!option) {
|
|
4264
|
-
|
|
4382
|
+
printSafe(`
|
|
4265
4383
|
No option ${toggleMatch[1]}.
|
|
4266
4384
|
`);
|
|
4267
4385
|
continue;
|
|
@@ -4278,7 +4396,7 @@ No option ${toggleMatch[1]}.
|
|
|
4278
4396
|
}
|
|
4279
4397
|
continue;
|
|
4280
4398
|
}
|
|
4281
|
-
|
|
4399
|
+
printSafe(`
|
|
4282
4400
|
Unrecognized input "${token}".
|
|
4283
4401
|
`);
|
|
4284
4402
|
}
|
|
@@ -4291,7 +4409,7 @@ Unrecognized input "${token}".
|
|
|
4291
4409
|
process.stdout.write("\nAielia> ");
|
|
4292
4410
|
streamedAnyTokens = true;
|
|
4293
4411
|
}
|
|
4294
|
-
process.stdout.write(token);
|
|
4412
|
+
process.stdout.write(stripTerminalControls(token));
|
|
4295
4413
|
}
|
|
4296
4414
|
try {
|
|
4297
4415
|
const result = await assistant.turn(message, {
|
|
@@ -4331,15 +4449,15 @@ Unrecognized input "${token}".
|
|
|
4331
4449
|
if (result.status === "needs_approval" && result.pendingActionId) {
|
|
4332
4450
|
const kindLabel = result.pendingActionKind === "shell" ? "shell command" : result.pendingActionKind === "email" ? "send email" : result.pendingActionKind === "batch" ? "batch research" : "write";
|
|
4333
4451
|
const promptText = result.pendingActionKind === "shell" ? "Run this command?" : result.pendingActionKind === "email" ? "Send this email?" : result.pendingActionKind === "batch" ? "Continue?" : "Apply this write?";
|
|
4334
|
-
console.log(`
|
|
4335
|
-
[needs approval — ${kindLabel}] ${result.reason}`);
|
|
4452
|
+
console.log(stripTerminalControls(`
|
|
4453
|
+
[needs approval — ${kindLabel}] ${result.reason}`));
|
|
4336
4454
|
let confirmed;
|
|
4337
4455
|
if (result.pendingActionKind && rememberedActionKinds.has(result.pendingActionKind)) {
|
|
4338
|
-
|
|
4456
|
+
printSafe(`["don't ask again" active this session for ${kindLabel} — auto-approved]`);
|
|
4339
4457
|
confirmed = true;
|
|
4340
4458
|
} else {
|
|
4341
4459
|
const decision = await askSelect(promptText, APPROVAL_OPTIONS);
|
|
4342
|
-
confirmed = decision
|
|
4460
|
+
confirmed = decision === "y" || decision === "a";
|
|
4343
4461
|
if (decision === "a" && result.pendingActionKind) rememberedActionKinds.add(result.pendingActionKind);
|
|
4344
4462
|
}
|
|
4345
4463
|
lastTrace = void 0;
|
|
@@ -4348,16 +4466,16 @@ Unrecognized input "${token}".
|
|
|
4348
4466
|
return;
|
|
4349
4467
|
}
|
|
4350
4468
|
if (result.status === "needs_approval") {
|
|
4351
|
-
console.log(`
|
|
4352
|
-
[needs approval — ${result.riskLevel}] ${result.reason}`);
|
|
4353
|
-
|
|
4469
|
+
console.log(stripTerminalControls(`
|
|
4470
|
+
[needs approval — ${result.riskLevel}] ${result.reason}`));
|
|
4471
|
+
printSafe(` "${message}"`);
|
|
4354
4472
|
let confirmed;
|
|
4355
4473
|
if (result.riskLevel && rememberedRiskLevels.has(result.riskLevel)) {
|
|
4356
|
-
|
|
4474
|
+
printSafe(`["don't ask again" active this session for risk level ${result.riskLevel} — auto-approved]`);
|
|
4357
4475
|
confirmed = true;
|
|
4358
4476
|
} else {
|
|
4359
4477
|
const decision = await askSelect("Proceed?", APPROVAL_OPTIONS);
|
|
4360
|
-
confirmed = decision
|
|
4478
|
+
confirmed = decision === "y" || decision === "a";
|
|
4361
4479
|
if (decision === "a" && result.riskLevel) rememberedRiskLevels.add(result.riskLevel);
|
|
4362
4480
|
}
|
|
4363
4481
|
if (confirmed) {
|
|
@@ -4366,7 +4484,7 @@ Unrecognized input "${token}".
|
|
|
4366
4484
|
lastTrace = void 0;
|
|
4367
4485
|
lastNoTraceReason = "No harness trace — the last turn was blocked on an approval gate and declined before the harness ran.";
|
|
4368
4486
|
await assistant.recordDeclinedRequest("cli", message, result.reason ?? "This request needed approval.");
|
|
4369
|
-
|
|
4487
|
+
printSafe("Cancelled.\n");
|
|
4370
4488
|
}
|
|
4371
4489
|
return;
|
|
4372
4490
|
}
|
|
@@ -4376,26 +4494,26 @@ Unrecognized input "${token}".
|
|
|
4376
4494
|
}
|
|
4377
4495
|
if (result.status === "needs_plan_approval") {
|
|
4378
4496
|
if (!result.planApprovalId) {
|
|
4379
|
-
|
|
4497
|
+
printSafe(`
|
|
4380
4498
|
[needs plan approval] ${result.reason ?? "A plan is awaiting approval, but its approval id is missing."}
|
|
4381
4499
|
`);
|
|
4382
4500
|
return;
|
|
4383
4501
|
}
|
|
4384
4502
|
const id = result.planApprovalId;
|
|
4385
4503
|
const snapshot = result.planApproval;
|
|
4386
|
-
if (result.reason)
|
|
4504
|
+
if (result.reason) printSafe(`
|
|
4387
4505
|
${result.reason}`);
|
|
4388
4506
|
if (snapshot) printPlanApproval(snapshot);
|
|
4389
4507
|
pendingPlanApproval = { id, message, snapshot };
|
|
4390
4508
|
lastTrace = void 0;
|
|
4391
4509
|
lastNoTraceReason = "No harness trace — the last turn staged a plan for approval (pending /plan approve|decline|edit).";
|
|
4392
4510
|
if (!snapshot) {
|
|
4393
|
-
|
|
4511
|
+
printSafe("Decide with /plan approve or /plan decline.\n");
|
|
4394
4512
|
return;
|
|
4395
4513
|
}
|
|
4396
4514
|
const choice = await askSelect("Approve this plan?", PLAN_APPROVAL_OPTIONS);
|
|
4397
4515
|
if (choice === "d") {
|
|
4398
|
-
|
|
4516
|
+
printSafe("Left pending — /plan to review, /plan approve|edit|decline to decide.\n");
|
|
4399
4517
|
return;
|
|
4400
4518
|
}
|
|
4401
4519
|
pendingPlanApproval = void 0;
|
|
@@ -4418,9 +4536,9 @@ ${result.reason}`);
|
|
|
4418
4536
|
if (result.status === "escalated") {
|
|
4419
4537
|
lastTrace = void 0;
|
|
4420
4538
|
lastNoTraceReason = `No harness trace — the last turn escalated (${result.reason}) before completing.`;
|
|
4421
|
-
console.log(`
|
|
4539
|
+
console.log(stripTerminalControls(`
|
|
4422
4540
|
[escalated] ${result.reason}
|
|
4423
|
-
`);
|
|
4541
|
+
`));
|
|
4424
4542
|
return;
|
|
4425
4543
|
}
|
|
4426
4544
|
if (!pendingActionId) {
|
|
@@ -4436,9 +4554,11 @@ ${result.reason}`);
|
|
|
4436
4554
|
const contradictionNotice = result.contradictionNotice ? `
|
|
4437
4555
|
|
|
4438
4556
|
${result.contradictionNotice}` : "";
|
|
4439
|
-
const reviewNotice = result.reviewNotice ? `
|
|
4557
|
+
const reviewNotice = (result.reviewNotice ? `
|
|
4558
|
+
|
|
4559
|
+
${result.reviewNotice}` : "") + (result.auditNotice ? `
|
|
4440
4560
|
|
|
4441
|
-
${result.
|
|
4561
|
+
${result.auditNotice}` : "");
|
|
4442
4562
|
lastNextSteps = result.nextSteps && result.nextSteps.length > 0 ? result.nextSteps : void 0;
|
|
4443
4563
|
const nextStepsBlock = lastNextSteps ? `
|
|
4444
4564
|
|
|
@@ -4447,26 +4567,26 @@ ${formatNextSteps(lastNextSteps)}` : "";
|
|
|
4447
4567
|
const pausedNoteText = result.pausedNote ? `
|
|
4448
4568
|
|
|
4449
4569
|
${result.pausedNote}` : "";
|
|
4450
|
-
process.stdout.write(`${pausedNoteText}${riskSuffix}${sourcesHint}${planHint}${contradictionNotice}${reviewNotice}${nextStepsBlock}
|
|
4570
|
+
process.stdout.write(stripTerminalControls(`${pausedNoteText}${riskSuffix}${sourcesHint}${planHint}${contradictionNotice}${reviewNotice}${nextStepsBlock}
|
|
4451
4571
|
|
|
4452
|
-
`);
|
|
4572
|
+
`));
|
|
4453
4573
|
} else {
|
|
4454
|
-
console.log(`
|
|
4574
|
+
console.log(stripTerminalControls(`
|
|
4455
4575
|
Aielia>${riskSuffix} ${result.reply}${sourcesHint}${planHint}${contradictionNotice}${reviewNotice}${nextStepsBlock}
|
|
4456
|
-
`);
|
|
4576
|
+
`));
|
|
4457
4577
|
}
|
|
4458
4578
|
} catch (err) {
|
|
4459
4579
|
clearProgress();
|
|
4460
4580
|
const { message: errorMessage, retryable } = classifyError(err, config.llmBackend);
|
|
4461
|
-
console.log(`
|
|
4581
|
+
console.log(stripTerminalControls(`
|
|
4462
4582
|
[error] ${errorMessage}${retryable ? " Type the message again to retry." : ""}
|
|
4463
|
-
`);
|
|
4583
|
+
`));
|
|
4464
4584
|
}
|
|
4465
4585
|
}
|
|
4466
4586
|
function askYesNo(question) {
|
|
4467
4587
|
if (options.askYesNo) return options.askYesNo(question);
|
|
4468
4588
|
if (nonInteractiveApprovalMode === "decline") {
|
|
4469
|
-
|
|
4589
|
+
printSafe(`
|
|
4470
4590
|
[non-interactive mode: auto-declining — ASSISTANT_NON_INTERACTIVE_APPROVAL=decline]`);
|
|
4471
4591
|
return Promise.resolve(false);
|
|
4472
4592
|
}
|
|
@@ -4474,19 +4594,21 @@ Aielia>${riskSuffix} ${result.reply}${sourcesHint}${planHint}${contradictionNoti
|
|
|
4474
4594
|
try {
|
|
4475
4595
|
rl.question(question, (answer) => resolve2(answer.trim().toLowerCase().startsWith("y")));
|
|
4476
4596
|
} catch {
|
|
4477
|
-
|
|
4597
|
+
printSafe(`
|
|
4478
4598
|
[could not read a response — treating as declined]`);
|
|
4479
4599
|
resolve2(false);
|
|
4480
4600
|
}
|
|
4481
4601
|
});
|
|
4482
4602
|
}
|
|
4603
|
+
let inputUnavailable = false;
|
|
4483
4604
|
function askLine(question) {
|
|
4484
4605
|
if (options.askLine) return options.askLine(question);
|
|
4485
4606
|
return new Promise((resolve2) => {
|
|
4486
4607
|
try {
|
|
4487
4608
|
rl.question(question, (answer) => resolve2(answer.trim()));
|
|
4488
4609
|
} catch {
|
|
4489
|
-
|
|
4610
|
+
inputUnavailable = true;
|
|
4611
|
+
printSafe(`
|
|
4490
4612
|
[could not read a response — treating as blank]`);
|
|
4491
4613
|
resolve2("");
|
|
4492
4614
|
}
|
|
@@ -4496,7 +4618,7 @@ Aielia>${riskSuffix} ${result.reply}${sourcesHint}${planHint}${contradictionNoti
|
|
|
4496
4618
|
if (options.askSelect) return options.askSelect(question, selectOptions);
|
|
4497
4619
|
const fallbackKey = selectOptions[selectOptions.length - 1].key;
|
|
4498
4620
|
if (nonInteractiveApprovalMode === "decline") {
|
|
4499
|
-
|
|
4621
|
+
printSafe(`
|
|
4500
4622
|
[non-interactive mode: auto-declining — ASSISTANT_NON_INTERACTIVE_APPROVAL=decline]`);
|
|
4501
4623
|
return Promise.resolve(fallbackKey);
|
|
4502
4624
|
}
|
|
@@ -4513,7 +4635,7 @@ ${optionLines}
|
|
|
4513
4635
|
resolve2(((_a = byKey ?? byIndex) == null ? void 0 : _a.key) ?? fallbackKey);
|
|
4514
4636
|
});
|
|
4515
4637
|
} catch {
|
|
4516
|
-
|
|
4638
|
+
printSafe(`
|
|
4517
4639
|
[could not read a response — treating as declined]`);
|
|
4518
4640
|
resolve2(fallbackKey);
|
|
4519
4641
|
}
|
|
@@ -4527,7 +4649,7 @@ ${optionLines}
|
|
|
4527
4649
|
}
|
|
4528
4650
|
async function handleConfigCommand(args) {
|
|
4529
4651
|
if (args.length === 0) {
|
|
4530
|
-
|
|
4652
|
+
printSafe(`
|
|
4531
4653
|
${formatConfigListing(config, overriddenKeys)}
|
|
4532
4654
|
`);
|
|
4533
4655
|
return;
|
|
@@ -4536,19 +4658,19 @@ ${formatConfigListing(config, overriddenKeys)}
|
|
|
4536
4658
|
const key = args[1];
|
|
4537
4659
|
const raw = args.slice(2).join(" ");
|
|
4538
4660
|
if (!key || !isConfigKey(key)) {
|
|
4539
|
-
|
|
4661
|
+
printSafe(`
|
|
4540
4662
|
✗ Unknown config key "${key ?? ""}". Known keys: ${CONFIG_KEYS.join(", ")}
|
|
4541
4663
|
`);
|
|
4542
4664
|
return;
|
|
4543
4665
|
}
|
|
4544
4666
|
if (!raw) {
|
|
4545
|
-
|
|
4667
|
+
printSafe(`
|
|
4546
4668
|
Usage: /config set ${key} <value>
|
|
4547
4669
|
`);
|
|
4548
4670
|
return;
|
|
4549
4671
|
}
|
|
4550
4672
|
if (overriddenKeys.has(key)) {
|
|
4551
|
-
|
|
4673
|
+
printSafe(`
|
|
4552
4674
|
✗ "${key}" is pinned by ${ENV_VAR_FOR_CONFIG_KEY[key]} — unset that env var to change it here.
|
|
4553
4675
|
`);
|
|
4554
4676
|
return;
|
|
@@ -4558,7 +4680,7 @@ Usage: /config set ${key} <value>
|
|
|
4558
4680
|
value = parseConfigValue(key, raw);
|
|
4559
4681
|
} catch (err) {
|
|
4560
4682
|
if (!(err instanceof ConfigValueParseError)) throw err;
|
|
4561
|
-
|
|
4683
|
+
printSafe(`
|
|
4562
4684
|
✗ ${err.message}
|
|
4563
4685
|
`);
|
|
4564
4686
|
return;
|
|
@@ -4568,14 +4690,14 @@ Usage: /config set ${key} <value>
|
|
|
4568
4690
|
validateConfig(patch, config);
|
|
4569
4691
|
} catch (err) {
|
|
4570
4692
|
if (!(err instanceof ConfigValidationError)) throw err;
|
|
4571
|
-
|
|
4693
|
+
printSafe(`
|
|
4572
4694
|
✗ ${err.message}
|
|
4573
4695
|
`);
|
|
4574
4696
|
return;
|
|
4575
4697
|
}
|
|
4576
4698
|
await configStore.save(patch);
|
|
4577
4699
|
await reloadAssistant();
|
|
4578
|
-
|
|
4700
|
+
printSafe(`
|
|
4579
4701
|
✓ ${key} updated (took effect immediately, no restart needed)
|
|
4580
4702
|
`);
|
|
4581
4703
|
return;
|
|
@@ -4583,8 +4705,19 @@ Usage: /config set ${key} <value>
|
|
|
4583
4705
|
if (args[0] === "reset") {
|
|
4584
4706
|
const key = args[1];
|
|
4585
4707
|
if (key && !isConfigKey(key)) {
|
|
4586
|
-
|
|
4708
|
+
printSafe(`
|
|
4587
4709
|
✗ Unknown config key "${key}". Known keys: ${CONFIG_KEYS.join(", ")}
|
|
4710
|
+
`);
|
|
4711
|
+
return;
|
|
4712
|
+
}
|
|
4713
|
+
const remaining = { ...await configStore.load() };
|
|
4714
|
+
for (const k of key ? [key] : CONFIG_KEYS) delete remaining[k];
|
|
4715
|
+
try {
|
|
4716
|
+
validateConfig({}, resolveConfig(remaining, envOverrides).config);
|
|
4717
|
+
} catch (err) {
|
|
4718
|
+
if (!(err instanceof ConfigValidationError)) throw err;
|
|
4719
|
+
printSafe(`
|
|
4720
|
+
✗ Not reset: ${err.message}
|
|
4588
4721
|
`);
|
|
4589
4722
|
return;
|
|
4590
4723
|
}
|
|
@@ -4592,23 +4725,23 @@ Usage: /config set ${key} <value>
|
|
|
4592
4725
|
const confirmed = await askYesNo(`
|
|
4593
4726
|
Reset ${target} to default? This takes effect immediately. (y/N) `);
|
|
4594
4727
|
if (!confirmed) {
|
|
4595
|
-
|
|
4728
|
+
printSafe("\nCancelled — nothing was reset.\n");
|
|
4596
4729
|
return;
|
|
4597
4730
|
}
|
|
4598
4731
|
const clearPatch = key ? { [key]: void 0 } : Object.fromEntries(CONFIG_KEYS.map((k) => [k, void 0]));
|
|
4599
4732
|
await configStore.save(clearPatch);
|
|
4600
4733
|
await reloadAssistant();
|
|
4601
|
-
|
|
4734
|
+
printSafe(`
|
|
4602
4735
|
✓ Reset ${key ?? "all settings"} to default
|
|
4603
4736
|
`);
|
|
4604
4737
|
return;
|
|
4605
4738
|
}
|
|
4606
|
-
|
|
4739
|
+
printSafe("\nUsage: /config | /config set <key> <value> | /config reset [key]\n");
|
|
4607
4740
|
}
|
|
4608
4741
|
async function handleLayers(args) {
|
|
4609
4742
|
const [sub, id] = args;
|
|
4610
4743
|
if (sub === "settings" || sub === "list") {
|
|
4611
|
-
|
|
4744
|
+
printSafe(`
|
|
4612
4745
|
${formatLayerListing(sanitizeLayerChoices(config.layers), layerPins, process.env)}
|
|
4613
4746
|
`);
|
|
4614
4747
|
return;
|
|
@@ -4616,7 +4749,7 @@ ${formatLayerListing(sanitizeLayerChoices(config.layers), layerPins, process.env
|
|
|
4616
4749
|
try {
|
|
4617
4750
|
if (sub === "on" || sub === "off") {
|
|
4618
4751
|
if (!id) {
|
|
4619
|
-
|
|
4752
|
+
printSafe(`
|
|
4620
4753
|
Usage: /layers ${sub} <id>
|
|
4621
4754
|
`);
|
|
4622
4755
|
return;
|
|
@@ -4627,19 +4760,19 @@ Usage: /layers ${sub} <id>
|
|
|
4627
4760
|
const layers = id ? withLayerChoice(sanitizeLayerChoices(config.layers), id, void 0) : {};
|
|
4628
4761
|
await configStore.save({ layers });
|
|
4629
4762
|
} else {
|
|
4630
|
-
|
|
4763
|
+
printSafe("\nUsage: /layers settings | /layers on <id> | /layers off <id> | /layers reset [id]\n");
|
|
4631
4764
|
return;
|
|
4632
4765
|
}
|
|
4633
4766
|
} catch (err) {
|
|
4634
4767
|
if (!(err instanceof LayerSettingError)) throw err;
|
|
4635
|
-
|
|
4768
|
+
printSafe(`
|
|
4636
4769
|
✗ ${err.message}
|
|
4637
4770
|
`);
|
|
4638
4771
|
return;
|
|
4639
4772
|
}
|
|
4640
4773
|
await reloadAssistant();
|
|
4641
4774
|
const pinned = id !== void 0 && layerPins.has(id);
|
|
4642
|
-
|
|
4775
|
+
printSafe(pinned ? `
|
|
4643
4776
|
✗ Saved, but "${id}" is pinned by its AUDIT_* env flag, so the saved choice has no effect until that is unset.
|
|
4644
4777
|
` : `
|
|
4645
4778
|
✓ Layers updated (took effect immediately, no restart needed)
|
|
@@ -4647,7 +4780,7 @@ Usage: /layers ${sub} <id>
|
|
|
4647
4780
|
}
|
|
4648
4781
|
async function handleModel(args) {
|
|
4649
4782
|
if (args.length === 0) {
|
|
4650
|
-
|
|
4783
|
+
printSafe(`
|
|
4651
4784
|
${config.model ?? "(using each backend's default)"}
|
|
4652
4785
|
`);
|
|
4653
4786
|
return;
|
|
@@ -4656,7 +4789,7 @@ ${config.model ?? "(using each backend's default)"}
|
|
|
4656
4789
|
}
|
|
4657
4790
|
async function handleProject(args) {
|
|
4658
4791
|
if (args.length === 0) {
|
|
4659
|
-
|
|
4792
|
+
printSafe(`
|
|
4660
4793
|
${assistant.getActiveProject() || "(none)"}
|
|
4661
4794
|
`);
|
|
4662
4795
|
return;
|
|
@@ -4694,6 +4827,7 @@ ${assistant.getActiveProject() || "(none)"}
|
|
|
4694
4827
|
"/checkpoint": (args) => handleCheckpoint(args)
|
|
4695
4828
|
};
|
|
4696
4829
|
async function dispatchOne(message) {
|
|
4830
|
+
var _a, _b;
|
|
4697
4831
|
if (quitting) return;
|
|
4698
4832
|
if (isQuitCommand(message)) {
|
|
4699
4833
|
quitting = true;
|
|
@@ -4701,27 +4835,29 @@ ${assistant.getActiveProject() || "(none)"}
|
|
|
4701
4835
|
await assistant.endSession("cli");
|
|
4702
4836
|
} catch {
|
|
4703
4837
|
}
|
|
4704
|
-
|
|
4838
|
+
printSafe("Exiting.");
|
|
4705
4839
|
rl.close();
|
|
4706
4840
|
return;
|
|
4707
4841
|
}
|
|
4708
4842
|
const pick = /^[1-3]$/.test(message) && lastNextSteps ? lastNextSteps[Number(message) - 1] : void 0;
|
|
4709
4843
|
lastNextSteps = void 0;
|
|
4710
4844
|
if (pick) {
|
|
4711
|
-
|
|
4845
|
+
printSafe(` → ${pick.description}`);
|
|
4712
4846
|
message = pick.description;
|
|
4713
4847
|
}
|
|
4714
4848
|
const [token, ...args] = message.split(/\s+/);
|
|
4715
|
-
const handler = commands[token];
|
|
4849
|
+
const handler = !pick && Object.hasOwn(commands, token) ? commands[token] : void 0;
|
|
4716
4850
|
if (handler) {
|
|
4717
4851
|
await handler(args);
|
|
4718
4852
|
return;
|
|
4719
4853
|
}
|
|
4720
4854
|
turnInProgress = true;
|
|
4855
|
+
(_a = options.onTurnBusyChange) == null ? void 0 : _a.call(options, true);
|
|
4721
4856
|
try {
|
|
4722
4857
|
await handleTurn(message);
|
|
4723
4858
|
} finally {
|
|
4724
4859
|
turnInProgress = false;
|
|
4860
|
+
(_b = options.onTurnBusyChange) == null ? void 0 : _b.call(options, false);
|
|
4725
4861
|
if (isGoalGraphEnabled(config.goalGraphMode)) {
|
|
4726
4862
|
for (const event of steeringChannel.poll()) {
|
|
4727
4863
|
void enqueue(event.message);
|
|
@@ -4740,12 +4876,13 @@ ${assistant.getActiveProject() || "(none)"}
|
|
|
4740
4876
|
}
|
|
4741
4877
|
function isKnownCommand(message) {
|
|
4742
4878
|
const [token] = message.split(/\s+/);
|
|
4743
|
-
return token
|
|
4879
|
+
return Object.hasOwn(commands, token) || isQuitCommand(message);
|
|
4744
4880
|
}
|
|
4745
4881
|
function routeMessage(message) {
|
|
4746
4882
|
if (isGoalGraphEnabled(config.goalGraphMode) && turnInProgress && !isKnownCommand(message)) {
|
|
4747
4883
|
steeringChannel.enqueue(message);
|
|
4748
|
-
|
|
4884
|
+
assistant.logQueuedMessage("cli", message);
|
|
4885
|
+
printSafe("\n[queued — the current turn is still running; this will be taken into account once it finishes]\n");
|
|
4749
4886
|
return Promise.resolve();
|
|
4750
4887
|
}
|
|
4751
4888
|
return enqueue(message);
|
|
@@ -4783,7 +4920,19 @@ ${assistant.getActiveProject() || "(none)"}
|
|
|
4783
4920
|
}
|
|
4784
4921
|
};
|
|
4785
4922
|
}
|
|
4923
|
+
class StartupError extends Error {
|
|
4924
|
+
}
|
|
4786
4925
|
async function main(argv = process.argv.slice(2)) {
|
|
4926
|
+
try {
|
|
4927
|
+
await mainInner(argv);
|
|
4928
|
+
} catch (err) {
|
|
4929
|
+
if (!(err instanceof StartupError)) throw err;
|
|
4930
|
+
process.stderr.write(`${err.message}
|
|
4931
|
+
`);
|
|
4932
|
+
process.exitCode = 1;
|
|
4933
|
+
}
|
|
4934
|
+
}
|
|
4935
|
+
async function mainInner(argv) {
|
|
4787
4936
|
const parsed = parseCliArgs(argv);
|
|
4788
4937
|
if (parsed.command === "version") {
|
|
4789
4938
|
console.log(CLI_VERSION);
|
|
@@ -4806,8 +4955,8 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
4806
4955
|
if (result == null ? void 0 : result.updateAvailable) console.log(updateAvailableNotice(result.latestVersion, CLI_VERSION, isSea));
|
|
4807
4956
|
});
|
|
4808
4957
|
}
|
|
4809
|
-
if (shouldLaunchTuiApp(config.tuiMode ??
|
|
4810
|
-
const { runTuiApp } = await import("./tui-app-
|
|
4958
|
+
if (shouldLaunchTuiApp(config.tuiMode ?? DEFAULT_TUI_MODE, Boolean(process.stdout.isTTY), Boolean(process.stdin.isTTY))) {
|
|
4959
|
+
const { runTuiApp } = await import("./tui-app-C0J6HRzv.js");
|
|
4811
4960
|
await runTuiApp();
|
|
4812
4961
|
return;
|
|
4813
4962
|
}
|
|
@@ -4831,6 +4980,7 @@ export {
|
|
|
4831
4980
|
PLAN_LINE_PREFIX as P,
|
|
4832
4981
|
runCli as a,
|
|
4833
4982
|
renderPlan as r,
|
|
4983
|
+
sanitizeLabel as s,
|
|
4834
4984
|
toLines as t
|
|
4835
4985
|
};
|
|
4836
|
-
//# sourceMappingURL=bin-
|
|
4986
|
+
//# sourceMappingURL=bin-C_hgDdSf.js.map
|