@buildaharness/aielia 0.3.5 → 0.3.6
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 +96 -18
- 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-BYRl4xXM.js} +365 -216
- package/dist/bin-BYRl4xXM.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 +2 -0
- 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 +79 -78
- 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-CV3S_TAS.js} +2129 -866
- package/dist/provider-setup-CV3S_TAS.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-CJq6YaiP.js} +127 -26
- package/dist/tui-app-CJq6YaiP.js.map +1 -0
- package/dist/tui-app.d.ts +35 -0
- 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
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 { X as buildClaudePrompt,
|
|
11
|
+
import { X as buildClaudePrompt, bJ as stripJsonCodeFence, bn as recallToolEnabled, aY as lexicalOffEnvValue, a as ALREADY_STAGED_ACTION_TOOL, bI as stagedActionInput, bh as parseClaudeCliOutput, bK as stripMcpToolPrefix, bo as redactUrlCredentials, b4 as memoryStatusChecks, bg as normalizePlanNodes, P as PROVIDER_SETUP, a3 as cleanApiKey, Z as checkApiKeyFormat, af as envOverridesFromProcessEnv, br as resolveConfig, bG as shouldLaunchTuiApp, Q as applyLayerSettings, bE as sanitizeLayerChoices, bP as testApiKey, bS as validateConfig, an as findInvalidNumericSettings, c as ConfigValidationError, aG as formatSpendCapStatus, aT as isPlanGraphEnabled, bL as stripTerminalControls, z as PersonalAssistant, aQ as isGoalGraphSuggestEnabled, aP as isGoalGraphEnabled, bm as planToSnapshot, V as braveSearch, a7 as createSmtpSender, a6 as createResendSender, aV as isQuitCommand, x as LiveSteeringChannel, aD as formatNextSteps, _ as classifyError, b7 as nodeToLayer, L as LAYER_DISPLAY_NAME, b6 as nodeDisplayName, ah as estimateCostUsd, ap as formatConfigListing, aO as isConfigKey, C as CONFIG_KEYS, E as ENV_VAR_FOR_CONFIG_KEY, bi as parseConfigValue, d as ConfigValueParseError, ar as formatDoctorReport, aq as formatCostSummary, at as formatGoalGraphState, aF as formatSearchResults, ay as formatMemoryHistory, aB as formatMemoryStatus, aA as formatMemoryPendingOutcome, aw as formatMemoryArchive, aJ as formatUndoLogListing, aa as defaultExportFilename, aI as formatTranscriptMarkdown, aH as formatStatus, au as formatHelp, q as LAYER_ORDER, av as formatLayerListing, bT as withLayerChoice, w as LayerSettingError, az as formatMemoryInjection, Y as buildWhyChain, s as LAYER_SHORT_CODE, ab as defaultMemoryExportFilename, ax as formatMemoryExport, b3 as memoryAuditLogEnabled, aC as formatMemorySummary } from "./provider-setup-CV3S_TAS.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.6";
|
|
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,6 +3501,7 @@ 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({
|
|
@@ -3424,6 +3530,8 @@ async function buildAssistant(config, { backend, dataDir, remindersFile }) {
|
|
|
3424
3530
|
return sendEmail ? { backend, workspaceRoot, sendEmail } : void 0;
|
|
3425
3531
|
})(),
|
|
3426
3532
|
dangerouslySkipPermissions: config.dangerouslySkipPermissions,
|
|
3533
|
+
// Opt-in durable record of tool calls/results and approvals (see activity-log.ts).
|
|
3534
|
+
onDebugLog: activityLogEnabled(process.env) ? createActivityLogger(join(dataDir, "activity-log.jsonl")) : void 0,
|
|
3427
3535
|
spendCap: config.sessionCostLimitUsd !== void 0 || config.sessionCallLimit !== void 0 ? { sessionCostLimitUsd: config.sessionCostLimitUsd, sessionCallLimit: config.sessionCallLimit } : void 0,
|
|
3428
3536
|
// Resolved through the shared config seam (cli-config.ts's envOverridesFromProcessEnv reads
|
|
3429
3537
|
// ASSISTANT_ONE_LOOP into config.oneLoopMode; /config set oneLoopMode persists it) so the CLI,
|
|
@@ -3443,7 +3551,7 @@ async function buildAssistant(config, { backend, dataDir, remindersFile }) {
|
|
|
3443
3551
|
});
|
|
3444
3552
|
}
|
|
3445
3553
|
async function runCli(options = {}) {
|
|
3446
|
-
|
|
3554
|
+
printSafe("Aielia is alpha software — expect rough edges and breaking changes. It uses AI models and can make mistakes; verify anything important.\n");
|
|
3447
3555
|
const dataDir = options.dataDir ?? defaultDataDir;
|
|
3448
3556
|
const configStore = options.configStore ?? defaultConfigStore;
|
|
3449
3557
|
const backend = options.backend ?? defaultBackend;
|
|
@@ -3483,22 +3591,25 @@ async function runCli(options = {}) {
|
|
|
3483
3591
|
}
|
|
3484
3592
|
try {
|
|
3485
3593
|
validateConfig({}, config);
|
|
3594
|
+
const numericProblems = findInvalidNumericSettings(config);
|
|
3595
|
+
if (numericProblems.length > 0) throw new ConfigValidationError(numericProblems.join(" "));
|
|
3486
3596
|
} catch (err) {
|
|
3487
3597
|
if (!(err instanceof ConfigValidationError)) throw err;
|
|
3488
|
-
|
|
3489
|
-
|
|
3598
|
+
throw new StartupError(
|
|
3599
|
+
`Aielia cannot start with this configuration: ${err.message}
|
|
3600
|
+
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.`
|
|
3601
|
+
);
|
|
3490
3602
|
}
|
|
3491
3603
|
if (nonInteractiveApprovalMode === "require-tty" && !process.stdin.isTTY) {
|
|
3492
|
-
|
|
3604
|
+
throw new StartupError(
|
|
3493
3605
|
`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
3606
|
);
|
|
3495
|
-
process.exit(1);
|
|
3496
3607
|
}
|
|
3497
3608
|
let assistant = options.assistant ?? await buildAssistant(config, { dataDir, backend, remindersFile });
|
|
3498
3609
|
const rl = createInterface({ input: options.input ?? process.stdin, output: options.output ?? process.stdout, prompt: "you> " });
|
|
3499
3610
|
rl.pause();
|
|
3500
3611
|
rl.on("SIGINT", () => {
|
|
3501
|
-
|
|
3612
|
+
printSafe("\nExiting.");
|
|
3502
3613
|
process.exit(0);
|
|
3503
3614
|
});
|
|
3504
3615
|
const backendDisplayModel = {
|
|
@@ -3508,7 +3619,7 @@ async function runCli(options = {}) {
|
|
|
3508
3619
|
openai: OPENAI_DEFAULT_MODEL,
|
|
3509
3620
|
openrouter: OPENROUTER_DEFAULT_MODEL
|
|
3510
3621
|
};
|
|
3511
|
-
|
|
3622
|
+
printSafe(`backend: ${config.llmBackend} (${config.model ?? backendDisplayModel[config.llmBackend]})`);
|
|
3512
3623
|
const enabledCapabilities = [];
|
|
3513
3624
|
if (config.enableWeb) enabledCapabilities.push("web search/fetch (brave)");
|
|
3514
3625
|
if (config.enableShell) enabledCapabilities.push(`shell commands (${config.dangerouslySkipPermissions ? "NOT approval-gated" : "approval-gated"})`);
|
|
@@ -3520,9 +3631,9 @@ async function runCli(options = {}) {
|
|
|
3520
3631
|
const undoBanner = undoableFromBefore > 0 ? `
|
|
3521
3632
|
${undoableFromBefore} action${undoableFromBefore === 1 ? "" : "s"} from earlier sessions ${undoableFromBefore === 1 ? "is" : "are"} still revertible — see /undo-action.
|
|
3522
3633
|
` : "";
|
|
3523
|
-
|
|
3634
|
+
printSafe(`Aielia — your personal assistant on the 11-layer harness, one turn at a time. Ctrl+C to exit.${capabilitySuffix}
|
|
3524
3635
|
${dangerBanner}${nonInteractiveBanner}${undoBanner}`);
|
|
3525
|
-
|
|
3636
|
+
printSafe("Type /help to see all commands, /config to view settings.\n");
|
|
3526
3637
|
void assistant.proposeMemoryConsolidation("cli").catch(() => void 0);
|
|
3527
3638
|
rl.prompt();
|
|
3528
3639
|
let lastTrace;
|
|
@@ -3554,7 +3665,7 @@ ${dangerBanner}${nonInteractiveBanner}${undoBanner}`);
|
|
|
3554
3665
|
};
|
|
3555
3666
|
function printPlan() {
|
|
3556
3667
|
if (!lastPlanStatus) {
|
|
3557
|
-
|
|
3668
|
+
printSafe("\nNo active plan for this session.\n");
|
|
3558
3669
|
return;
|
|
3559
3670
|
}
|
|
3560
3671
|
const lines = [
|
|
@@ -3566,20 +3677,20 @@ ${dangerBanner}${nonInteractiveBanner}${undoBanner}`);
|
|
|
3566
3677
|
]),
|
|
3567
3678
|
`Success criteria: ${lastPlanStatus.successCriteria}`
|
|
3568
3679
|
];
|
|
3569
|
-
console.log(`
|
|
3680
|
+
console.log(stripTerminalControls(`
|
|
3570
3681
|
${lines.join("\n")}
|
|
3571
|
-
`);
|
|
3682
|
+
`));
|
|
3572
3683
|
}
|
|
3573
3684
|
async function handlePlanSketch(args) {
|
|
3574
3685
|
const request = args.join(" ").trim();
|
|
3575
3686
|
if (!request) {
|
|
3576
|
-
|
|
3687
|
+
printSafe("\nUsage: /plan sketch <request>\n");
|
|
3577
3688
|
return;
|
|
3578
3689
|
}
|
|
3579
|
-
|
|
3690
|
+
printSafe("\nSketching a plan (read-only grounding only, nothing staged)...\n");
|
|
3580
3691
|
const result = await assistant.sketchPlan("cli", request);
|
|
3581
|
-
console.log(`Aielia> ${result.reply ?? "(no reply)"}
|
|
3582
|
-
`);
|
|
3692
|
+
console.log(stripTerminalControls(`Aielia> ${result.reply ?? "(no reply)"}
|
|
3693
|
+
`));
|
|
3583
3694
|
if (result.usage) lastTurnUsage = withCostEstimate(result.usage);
|
|
3584
3695
|
}
|
|
3585
3696
|
const PLAN_APPROVAL_OPTIONS = [
|
|
@@ -3598,9 +3709,9 @@ ${lines.join("\n")}
|
|
|
3598
3709
|
...snapshot.tasks.map((task) => ` ${task.riskLevel === "HIGH" ? "!" : task.riskLevel === "MEDIUM" ? "·" : "○"} [${task.riskLevel ?? "LOW"}] ${task.id} — ${task.description}`),
|
|
3599
3710
|
...snapshot.reviewNotes && snapshot.reviewNotes.length > 0 ? ["Review notes:", ...snapshot.reviewNotes.map((note) => ` - ${note}`)] : []
|
|
3600
3711
|
];
|
|
3601
|
-
console.log(`
|
|
3712
|
+
console.log(stripTerminalControls(`
|
|
3602
3713
|
${lines.join("\n")}
|
|
3603
|
-
`);
|
|
3714
|
+
`));
|
|
3604
3715
|
}
|
|
3605
3716
|
async function promptPlanEdits(snapshot) {
|
|
3606
3717
|
const knownIds = new Set(snapshot.tasks.map((task) => task.id));
|
|
@@ -3614,7 +3725,7 @@ ${lines.join("\n")}
|
|
|
3614
3725
|
const validCancels = cancelTaskIds.filter((id) => knownIds.has(id));
|
|
3615
3726
|
const validEdits = parsedEdits.filter((edit) => knownIds.has(edit.id) && edit.description);
|
|
3616
3727
|
const unknown = [...cancelTaskIds.filter((id) => !knownIds.has(id)), ...parsedEdits.filter((e) => !knownIds.has(e.id)).map((e) => e.id)];
|
|
3617
|
-
if (unknown.length > 0)
|
|
3728
|
+
if (unknown.length > 0) printSafe(` [ignoring unknown task id(s): ${[...new Set(unknown)].join(", ")}]`);
|
|
3618
3729
|
return {
|
|
3619
3730
|
...validCancels.length > 0 ? { cancelTaskIds: validCancels } : {},
|
|
3620
3731
|
...validEdits.length > 0 ? { editedTasks: validEdits } : {}
|
|
@@ -3622,7 +3733,7 @@ ${lines.join("\n")}
|
|
|
3622
3733
|
}
|
|
3623
3734
|
async function handlePlanApprovalCommand(sub) {
|
|
3624
3735
|
if (!pendingPlanApproval) {
|
|
3625
|
-
|
|
3736
|
+
printSafe("\nNo plan is awaiting approval.\n");
|
|
3626
3737
|
return;
|
|
3627
3738
|
}
|
|
3628
3739
|
const { id, message, snapshot } = pendingPlanApproval;
|
|
@@ -3634,7 +3745,7 @@ ${lines.join("\n")}
|
|
|
3634
3745
|
decision = "approve_trusted";
|
|
3635
3746
|
} else if (sub === "edit") {
|
|
3636
3747
|
if (!snapshot) {
|
|
3637
|
-
|
|
3748
|
+
printSafe("\n[tasks unavailable for editing — /plan approve or /plan decline]\n");
|
|
3638
3749
|
return;
|
|
3639
3750
|
}
|
|
3640
3751
|
printPlanApproval(snapshot);
|
|
@@ -3658,7 +3769,7 @@ ${lines.join("\n")}
|
|
|
3658
3769
|
async function handlePlanGraph(args) {
|
|
3659
3770
|
const nodes = await loadPlanGraphNodes(void 0, args[0]);
|
|
3660
3771
|
if (!nodes) {
|
|
3661
|
-
|
|
3772
|
+
printSafe(args[0] ? `
|
|
3662
3773
|
No plan graph for thread "${args[0]}".
|
|
3663
3774
|
` : "\nNo active plan for this session.\n");
|
|
3664
3775
|
return;
|
|
@@ -3669,7 +3780,7 @@ No plan graph for thread "${args[0]}".
|
|
|
3669
3780
|
options.openPlanGraph(nodes);
|
|
3670
3781
|
return;
|
|
3671
3782
|
}
|
|
3672
|
-
|
|
3783
|
+
printSafe(`
|
|
3673
3784
|
[plan graph unavailable: ${probe.message}] showing the checklist instead.`);
|
|
3674
3785
|
printPlan();
|
|
3675
3786
|
return;
|
|
@@ -3678,20 +3789,20 @@ No plan graph for thread "${args[0]}".
|
|
|
3678
3789
|
const color = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
|
|
3679
3790
|
const result = renderPlan(nodes, { maxCols: cols, color });
|
|
3680
3791
|
if (!result.ok) {
|
|
3681
|
-
|
|
3792
|
+
printSafe(`
|
|
3682
3793
|
[plan graph unavailable: ${result.message}] showing the checklist instead.`);
|
|
3683
3794
|
printPlan();
|
|
3684
3795
|
return;
|
|
3685
3796
|
}
|
|
3686
3797
|
if (!result.fits) {
|
|
3687
|
-
|
|
3798
|
+
printSafe(`
|
|
3688
3799
|
[plan graph is wider than ${cols} columns] dependency list:
|
|
3689
3800
|
`);
|
|
3690
|
-
for (const n of nodes)
|
|
3691
|
-
|
|
3801
|
+
for (const n of nodes) printSafe(` [${n.status}] ${n.id} — ${n.label}${n.deps.length ? ` (after: ${n.deps.join(", ")})` : ""}`);
|
|
3802
|
+
printSafe("");
|
|
3692
3803
|
return;
|
|
3693
3804
|
}
|
|
3694
|
-
|
|
3805
|
+
printSafe(`
|
|
3695
3806
|
${result.lines.join("\n")}
|
|
3696
3807
|
`);
|
|
3697
3808
|
}
|
|
@@ -3730,52 +3841,52 @@ ${result.lines.join("\n")}
|
|
|
3730
3841
|
function printWhy() {
|
|
3731
3842
|
const memoryLine = formatMemoryInjection(assistant.getLastMemoryInjection());
|
|
3732
3843
|
if (!lastTrace) {
|
|
3733
|
-
|
|
3844
|
+
printSafe(`
|
|
3734
3845
|
${lastNoTraceReason ?? "No harness trace for the last turn (nothing to explain yet, or it took the fast path)."}
|
|
3735
3846
|
${memoryLine}
|
|
3736
3847
|
`);
|
|
3737
3848
|
return;
|
|
3738
3849
|
}
|
|
3739
|
-
|
|
3850
|
+
printSafe(`
|
|
3740
3851
|
${verificationHealthLabel(lastTrace.verificationHealth)}`);
|
|
3741
3852
|
const chain = buildWhyChain(lastTrace.layerActivity);
|
|
3742
3853
|
if (chain.length > 0) {
|
|
3743
|
-
|
|
3854
|
+
printSafe(" " + chain.map((item) => `${LAYER_SHORT_CODE[item.layer]} (${item.reason})`).join(" > "));
|
|
3744
3855
|
}
|
|
3745
3856
|
if (lastTrace.batchBudget) {
|
|
3746
|
-
|
|
3857
|
+
printSafe(" " + batchBudgetSummaryLine(lastTrace.batchBudget));
|
|
3747
3858
|
}
|
|
3748
|
-
|
|
3749
|
-
|
|
3859
|
+
printSafe(memoryLine);
|
|
3860
|
+
printSafe("");
|
|
3750
3861
|
}
|
|
3751
3862
|
function printLayers() {
|
|
3752
3863
|
if (!lastTrace) {
|
|
3753
|
-
|
|
3864
|
+
printSafe(`
|
|
3754
3865
|
${lastNoTraceReason ?? "No harness trace for the last turn (nothing to explain yet, or it took the fast path)."}
|
|
3755
3866
|
`);
|
|
3756
3867
|
return;
|
|
3757
3868
|
}
|
|
3758
|
-
|
|
3869
|
+
printSafe("");
|
|
3759
3870
|
const byLayer = new Map(lastTrace.layerActivity.map((e) => [e.layer, e]));
|
|
3760
3871
|
for (const layer of LAYER_ORDER) {
|
|
3761
3872
|
const e = byLayer.get(layer);
|
|
3762
3873
|
const mark = (e == null ? void 0 : e.fired) ? "✓" : "·";
|
|
3763
3874
|
const reason = (e == null ? void 0 : e.reason) ?? "not evaluated this turn";
|
|
3764
|
-
|
|
3875
|
+
printSafe(` [${mark}] ${LAYER_DISPLAY_NAME[layer].padEnd(22)} ${reason}`);
|
|
3765
3876
|
}
|
|
3766
3877
|
if (lastTrace.batchBudget) {
|
|
3767
|
-
|
|
3768
|
-
|
|
3878
|
+
printSafe("");
|
|
3879
|
+
printSafe(` ${batchBudgetSummaryLine(lastTrace.batchBudget)}`);
|
|
3769
3880
|
const STATUS_MARK = {
|
|
3770
3881
|
found: "✓",
|
|
3771
3882
|
not_found: "✗",
|
|
3772
3883
|
truncated_while_productive: "~"
|
|
3773
3884
|
};
|
|
3774
3885
|
for (const outcome of lastTrace.batchBudget.perItemOutcomes) {
|
|
3775
|
-
|
|
3886
|
+
printSafe(` [${STATUS_MARK[outcome.status]}] ${outcome.item.padEnd(30)} ${outcome.status} (${outcome.callsUsed} calls)`);
|
|
3776
3887
|
}
|
|
3777
3888
|
}
|
|
3778
|
-
|
|
3889
|
+
printSafe("");
|
|
3779
3890
|
}
|
|
3780
3891
|
const SOURCE_TOOL_LABEL = {
|
|
3781
3892
|
read_file: "Read",
|
|
@@ -3785,17 +3896,17 @@ ${lastNoTraceReason ?? "No harness trace for the last turn (nothing to explain y
|
|
|
3785
3896
|
};
|
|
3786
3897
|
function printSources() {
|
|
3787
3898
|
if (!lastSources || lastSources.length === 0) {
|
|
3788
|
-
|
|
3899
|
+
printSafe("\nNo sources for the last turn (it used no file/web tool calls).\n");
|
|
3789
3900
|
return;
|
|
3790
3901
|
}
|
|
3791
|
-
|
|
3902
|
+
printSafe("");
|
|
3792
3903
|
for (const source of lastSources) {
|
|
3793
|
-
|
|
3904
|
+
printSafe(` - ${SOURCE_TOOL_LABEL[source.tool]} ${source.path}`);
|
|
3794
3905
|
}
|
|
3795
|
-
|
|
3906
|
+
printSafe("");
|
|
3796
3907
|
}
|
|
3797
3908
|
function printHelp() {
|
|
3798
|
-
|
|
3909
|
+
printSafe(`
|
|
3799
3910
|
${formatHelp()}
|
|
3800
3911
|
`);
|
|
3801
3912
|
}
|
|
@@ -3807,12 +3918,12 @@ ${formatHelp()}
|
|
|
3807
3918
|
pendingPlanApproval = void 0;
|
|
3808
3919
|
lastTurnUsage = void 0;
|
|
3809
3920
|
lastNoTraceReason = void 0;
|
|
3810
|
-
|
|
3921
|
+
printSafe("\n✓ Started a fresh conversation.\n");
|
|
3811
3922
|
}
|
|
3812
3923
|
async function handleCheckpoint(args) {
|
|
3813
3924
|
if (args[0] === "clear") {
|
|
3814
3925
|
const result = await assistant.clearCheckpoint("cli");
|
|
3815
|
-
|
|
3926
|
+
printSafe(
|
|
3816
3927
|
result.cleared ? `
|
|
3817
3928
|
✓ 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
3929
|
` : "\nNo checkpoint to clear — the last turn either completed normally or there was nothing in progress.\n"
|
|
@@ -3821,11 +3932,11 @@ ${formatHelp()}
|
|
|
3821
3932
|
}
|
|
3822
3933
|
const status = await assistant.getCheckpointStatus("cli");
|
|
3823
3934
|
if (!status.present) {
|
|
3824
|
-
|
|
3935
|
+
printSafe("\nNo checkpoint present — the last turn completed normally or there was nothing in progress.\n");
|
|
3825
3936
|
return;
|
|
3826
3937
|
}
|
|
3827
3938
|
const attemptsNote = status.failedResumeAttempts > 0 ? ` — failed to resume ${status.failedResumeAttempts} time${status.failedResumeAttempts === 1 ? "" : "s"} in a row so far` : "";
|
|
3828
|
-
|
|
3939
|
+
printSafe(
|
|
3829
3940
|
`
|
|
3830
3941
|
A checkpoint is present: step ${status.stepsUsed}, last node "${status.currentNode}"${attemptsNote}.
|
|
3831
3942
|
Your next message will try to resume it automatically. Run /checkpoint clear to discard it and start fresh instead.
|
|
@@ -3836,7 +3947,7 @@ Your next message will try to resume it automatically. Run /checkpoint clear to
|
|
|
3836
3947
|
const transcript = await assistant.getTranscript("cli");
|
|
3837
3948
|
const undoLogEntries = await assistant.listUndoLogEntries();
|
|
3838
3949
|
const spendCapLine = await spendCapStatusLine();
|
|
3839
|
-
|
|
3950
|
+
printSafe(
|
|
3840
3951
|
`
|
|
3841
3952
|
${formatStatus({ config, overriddenKeys, transcriptLength: transcript.length, planActive: lastPlanStatus !== void 0, undoLogEntries, spendCapLine })}
|
|
3842
3953
|
`
|
|
@@ -3850,18 +3961,18 @@ ${formatStatus({ config, overriddenKeys, transcriptLength: transcript.length, pl
|
|
|
3850
3961
|
async function handleExport(args) {
|
|
3851
3962
|
const transcript = await assistant.getTranscript("cli");
|
|
3852
3963
|
if (transcript.length === 0) {
|
|
3853
|
-
|
|
3964
|
+
printSafe("\nNothing to export yet.\n");
|
|
3854
3965
|
return;
|
|
3855
3966
|
}
|
|
3856
|
-
const filename = resolve(process.cwd(), args
|
|
3967
|
+
const filename = resolve(process.cwd(), args.length > 0 ? args.join(" ") : defaultExportFilename());
|
|
3857
3968
|
try {
|
|
3858
3969
|
await writeFile(filename, formatTranscriptMarkdown(transcript), "utf-8");
|
|
3859
|
-
|
|
3970
|
+
printSafe(`
|
|
3860
3971
|
✓ Exported ${transcript.length} message${transcript.length === 1 ? "" : "s"} to ${filename}
|
|
3861
3972
|
`);
|
|
3862
3973
|
} catch (err) {
|
|
3863
3974
|
const { message } = classifyError(err);
|
|
3864
|
-
|
|
3975
|
+
printSafe(`
|
|
3865
3976
|
[error] ${message}
|
|
3866
3977
|
`);
|
|
3867
3978
|
}
|
|
@@ -3869,14 +3980,14 @@ ${formatStatus({ config, overriddenKeys, transcriptLength: transcript.length, pl
|
|
|
3869
3980
|
async function handleUndo() {
|
|
3870
3981
|
const transcriptBefore = await assistant.getTranscript("cli");
|
|
3871
3982
|
if (transcriptBefore.length === 0) {
|
|
3872
|
-
|
|
3983
|
+
printSafe("\nNothing to undo.\n");
|
|
3873
3984
|
return;
|
|
3874
3985
|
}
|
|
3875
3986
|
const wasPendingApproval = transcriptBefore[transcriptBefore.length - 1].role !== "assistant";
|
|
3876
3987
|
const undoneReminders = lastTurnToolSteps.filter((s) => s.tool === "create_reminder");
|
|
3877
3988
|
const result = await assistant.undoLastTurn("cli");
|
|
3878
3989
|
if (!result.undone) {
|
|
3879
|
-
|
|
3990
|
+
printSafe("\nNothing to undo.\n");
|
|
3880
3991
|
return;
|
|
3881
3992
|
}
|
|
3882
3993
|
lastTrace = void 0;
|
|
@@ -3884,7 +3995,7 @@ ${formatStatus({ config, overriddenKeys, transcriptLength: transcript.length, pl
|
|
|
3884
3995
|
lastPlanStatus = void 0;
|
|
3885
3996
|
lastTurnToolSteps = [];
|
|
3886
3997
|
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
|
-
|
|
3998
|
+
printSafe(
|
|
3888
3999
|
`
|
|
3889
4000
|
✓ Removed ${wasPendingApproval ? "the pending message awaiting approval" : "the last exchange (1 user message, 1 assistant reply)"}.${caveat}
|
|
3890
4001
|
`
|
|
@@ -3893,20 +4004,20 @@ ${formatStatus({ config, overriddenKeys, transcriptLength: transcript.length, pl
|
|
|
3893
4004
|
async function handleUndoAction(args) {
|
|
3894
4005
|
if (args.length === 0) {
|
|
3895
4006
|
const entries = await assistant.listUndoLogEntries();
|
|
3896
|
-
|
|
4007
|
+
printSafe(`
|
|
3897
4008
|
${formatUndoLogListing(entries)}
|
|
3898
4009
|
`);
|
|
3899
4010
|
return;
|
|
3900
4011
|
}
|
|
3901
4012
|
const staged = await assistant.stageUndoAction(args[0]);
|
|
3902
4013
|
if (staged.status === "error") {
|
|
3903
|
-
|
|
4014
|
+
printSafe(`
|
|
3904
4015
|
✗ ${staged.message}
|
|
3905
4016
|
`);
|
|
3906
4017
|
return;
|
|
3907
4018
|
}
|
|
3908
|
-
console.log(`
|
|
3909
|
-
[needs approval — revert] ${staged.reason}`);
|
|
4019
|
+
console.log(stripTerminalControls(`
|
|
4020
|
+
[needs approval — revert] ${staged.reason}`));
|
|
3910
4021
|
const confirmed = await askYesNo("Apply this revert? (y/N) ");
|
|
3911
4022
|
lastTrace = void 0;
|
|
3912
4023
|
lastNoTraceReason = `No harness trace — the last turn was a staged revert that was ${confirmed ? "approved" : "declined"} before the harness ran.`;
|
|
@@ -3918,7 +4029,7 @@ ${formatUndoLogListing(entries)}
|
|
|
3918
4029
|
const digestLine = digests.length > 0 ? `
|
|
3919
4030
|
Session digests stored: ${digests.length} (/memory forget digest [id] erases them; /memory export includes them).
|
|
3920
4031
|
` : "";
|
|
3921
|
-
|
|
4032
|
+
printSafe(`
|
|
3922
4033
|
${formatMemoryStatus(await assistant.getMemoryStatus("cli"))}
|
|
3923
4034
|
|
|
3924
4035
|
${formatMemorySummary(summary)}
|
|
@@ -3926,15 +4037,15 @@ ${digestLine}`);
|
|
|
3926
4037
|
}
|
|
3927
4038
|
async function handleMemoryExport(args) {
|
|
3928
4039
|
const data = await assistant.exportMemory("cli");
|
|
3929
|
-
const filename = resolve(process.cwd(), args
|
|
4040
|
+
const filename = resolve(process.cwd(), args.length > 0 ? args.join(" ") : defaultMemoryExportFilename());
|
|
3930
4041
|
try {
|
|
3931
4042
|
await writeFile(filename, formatMemoryExport(data), "utf-8");
|
|
3932
|
-
|
|
4043
|
+
printSafe(`
|
|
3933
4044
|
✓ Exported learned memory to ${filename}
|
|
3934
4045
|
`);
|
|
3935
4046
|
} catch (err) {
|
|
3936
4047
|
const { message } = classifyError(err);
|
|
3937
|
-
|
|
4048
|
+
printSafe(`
|
|
3938
4049
|
[error] ${message}
|
|
3939
4050
|
`);
|
|
3940
4051
|
}
|
|
@@ -3942,26 +4053,26 @@ ${digestLine}`);
|
|
|
3942
4053
|
async function handleMemoryPending(action, args) {
|
|
3943
4054
|
const selector = args[0];
|
|
3944
4055
|
if (!selector) {
|
|
3945
|
-
|
|
4056
|
+
printSafe(`
|
|
3946
4057
|
Usage: /memory ${action} <n> or /memory ${action} <category>
|
|
3947
4058
|
`);
|
|
3948
4059
|
return;
|
|
3949
4060
|
}
|
|
3950
4061
|
const outcome = action === "confirm" ? await assistant.confirmPendingFact(selector) : await assistant.rejectPendingFact(selector);
|
|
3951
|
-
|
|
4062
|
+
printSafe(`
|
|
3952
4063
|
${formatMemoryPendingOutcome(action === "confirm" ? "confirmed" : "rejected", outcome)}
|
|
3953
4064
|
`);
|
|
3954
4065
|
}
|
|
3955
4066
|
async function handleMemoryForget(args) {
|
|
3956
4067
|
const selector = args[0];
|
|
3957
4068
|
if (!selector) {
|
|
3958
|
-
|
|
4069
|
+
printSafe("\nUsage: /memory forget <n> [erase]\n");
|
|
3959
4070
|
return;
|
|
3960
4071
|
}
|
|
3961
4072
|
const erase = args[1] === "erase";
|
|
3962
4073
|
const outcome = await assistant.forgetFact(selector, "cli", erase);
|
|
3963
4074
|
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
|
-
|
|
4075
|
+
printSafe(`
|
|
3965
4076
|
${formatMemoryPendingOutcome("forgotten", outcome)}${note}
|
|
3966
4077
|
`);
|
|
3967
4078
|
}
|
|
@@ -3976,7 +4087,7 @@ ${formatMemoryPendingOutcome("forgotten", outcome)}${note}
|
|
|
3976
4087
|
}
|
|
3977
4088
|
if (args[0] === "forget" && (args[1] === "digest" || args[1] === "digests")) {
|
|
3978
4089
|
const removed = await assistant.forgetDigests(args[2]);
|
|
3979
|
-
|
|
4090
|
+
printSafe(`
|
|
3980
4091
|
Forgot ${removed} session digest${removed === 1 ? "" : "s"}.
|
|
3981
4092
|
`);
|
|
3982
4093
|
return;
|
|
@@ -3986,13 +4097,13 @@ Forgot ${removed} session digest${removed === 1 ? "" : "s"}.
|
|
|
3986
4097
|
return;
|
|
3987
4098
|
}
|
|
3988
4099
|
if (args[0] === "history") {
|
|
3989
|
-
|
|
4100
|
+
printSafe(`
|
|
3990
4101
|
${formatMemoryHistory(await assistant.memoryHistory())}
|
|
3991
4102
|
`);
|
|
3992
4103
|
return;
|
|
3993
4104
|
}
|
|
3994
4105
|
if (args[0] === "status") {
|
|
3995
|
-
|
|
4106
|
+
printSafe(`
|
|
3996
4107
|
${formatMemoryStatus(await assistant.getMemoryStatus("cli"))}
|
|
3997
4108
|
`);
|
|
3998
4109
|
return;
|
|
@@ -4000,31 +4111,31 @@ ${formatMemoryStatus(await assistant.getMemoryStatus("cli"))}
|
|
|
4000
4111
|
if (args[0] === "archive") {
|
|
4001
4112
|
if (args[1] === "forget") {
|
|
4002
4113
|
const outcome = await assistant.forgetArchivedFact(args[2] ?? "");
|
|
4003
|
-
|
|
4114
|
+
printSafe(`
|
|
4004
4115
|
${formatMemoryPendingOutcome("forgotten", outcome)}
|
|
4005
4116
|
`);
|
|
4006
4117
|
return;
|
|
4007
4118
|
}
|
|
4008
4119
|
if (args[1] === "restore") {
|
|
4009
4120
|
const outcome = await assistant.restoreArchivedMemory(args[2] ?? "", "cli");
|
|
4010
|
-
|
|
4121
|
+
printSafe(`
|
|
4011
4122
|
${outcome.message}
|
|
4012
4123
|
`);
|
|
4013
4124
|
return;
|
|
4014
4125
|
}
|
|
4015
|
-
|
|
4126
|
+
printSafe(`
|
|
4016
4127
|
${formatMemoryArchive(await assistant.listArchivedFacts(), await assistant.restorableArchiveCount())}
|
|
4017
4128
|
`);
|
|
4018
4129
|
return;
|
|
4019
4130
|
}
|
|
4020
4131
|
if (args[0] === "off" || args[0] === "on") {
|
|
4021
4132
|
await assistant.setMemoryEnabled(args[0] === "on");
|
|
4022
|
-
|
|
4133
|
+
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
4134
|
return;
|
|
4024
4135
|
}
|
|
4025
4136
|
if (args[0] === "undo") {
|
|
4026
4137
|
const outcome = await assistant.undoMemoryChange(args[1] ?? "", "cli");
|
|
4027
|
-
|
|
4138
|
+
printSafe(`
|
|
4028
4139
|
${outcome.message}
|
|
4029
4140
|
`);
|
|
4030
4141
|
return;
|
|
@@ -4038,7 +4149,7 @@ ${outcome.message}
|
|
|
4038
4149
|
async function handleMemoryConsolidate(args) {
|
|
4039
4150
|
if (args[0] === "accept" || args[0] === "dismiss") {
|
|
4040
4151
|
const outcome2 = args[0] === "accept" ? await assistant.acceptMemoryProposal(args[1] ?? "", "cli") : await assistant.dismissMemoryProposal(args[1] ?? "");
|
|
4041
|
-
|
|
4152
|
+
printSafe(`
|
|
4042
4153
|
${outcome2.message}
|
|
4043
4154
|
`);
|
|
4044
4155
|
return;
|
|
@@ -4046,12 +4157,12 @@ ${outcome2.message}
|
|
|
4046
4157
|
const outcome = await assistant.consolidateMemory("cli");
|
|
4047
4158
|
const proposals = await assistant.memoryProposals();
|
|
4048
4159
|
if (proposals.length === 0) {
|
|
4049
|
-
|
|
4160
|
+
printSafe(`
|
|
4050
4161
|
${outcome.message}
|
|
4051
4162
|
`);
|
|
4052
4163
|
return;
|
|
4053
4164
|
}
|
|
4054
|
-
|
|
4165
|
+
printSafe(`
|
|
4055
4166
|
${outcome.status === "done" ? "" : `${outcome.message}
|
|
4056
4167
|
`}Proposed changes (nothing applied yet):
|
|
4057
4168
|
${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 +4172,17 @@ ${proposals.map((p, i) => ` ${i + 1}. [${p.kind}]${p.touchesUserAsserted ? " (t
|
|
|
4061
4172
|
async function handleSearch(args) {
|
|
4062
4173
|
const query = args.join(" ");
|
|
4063
4174
|
if (!query.trim()) {
|
|
4064
|
-
|
|
4175
|
+
printSafe("\nUsage: /search <query>\n");
|
|
4065
4176
|
return;
|
|
4066
4177
|
}
|
|
4067
4178
|
const hits = await assistant.searchTranscript(query);
|
|
4068
|
-
|
|
4179
|
+
printSafe(`
|
|
4069
4180
|
${formatSearchResults(hits, query)}
|
|
4070
4181
|
`);
|
|
4071
4182
|
}
|
|
4072
4183
|
async function handleGoals() {
|
|
4073
4184
|
const state = await assistant.getGoalGraphState("cli");
|
|
4074
|
-
|
|
4185
|
+
printSafe(`
|
|
4075
4186
|
${formatGoalGraphState(state)}
|
|
4076
4187
|
`);
|
|
4077
4188
|
}
|
|
@@ -4079,7 +4190,7 @@ ${formatGoalGraphState(state)}
|
|
|
4079
4190
|
const spendCapLine = await spendCapStatusLine();
|
|
4080
4191
|
const spendState = await assistant.getSpendState("cli");
|
|
4081
4192
|
const session = { inputTokens: spendState.cumulativeInputTokens, outputTokens: spendState.cumulativeOutputTokens, costUsd: spendState.cumulativeCostUsd };
|
|
4082
|
-
|
|
4193
|
+
printSafe(`
|
|
4083
4194
|
${formatCostSummary({ lastTurn: lastTurnUsage, session, backend: config.llmBackend, spendCapLine })}
|
|
4084
4195
|
`);
|
|
4085
4196
|
}
|
|
@@ -4101,7 +4212,7 @@ ${formatCostSummary({ lastTurn: lastTurnUsage, session, backend: config.llmBacke
|
|
|
4101
4212
|
// that explicitly here rather than leaving it unstated (no keychain integration exists).
|
|
4102
4213
|
{ label: "provider keys stored: plaintext in config.json (no OS keychain integration)", ok: true }
|
|
4103
4214
|
]);
|
|
4104
|
-
|
|
4215
|
+
printSafe(`
|
|
4105
4216
|
${formatDoctorReport(checks)}
|
|
4106
4217
|
`);
|
|
4107
4218
|
}
|
|
@@ -4110,7 +4221,7 @@ ${formatDoctorReport(checks)}
|
|
|
4110
4221
|
const layer = nodeToLayer(progress.currentNode);
|
|
4111
4222
|
const label = layer ? LAYER_DISPLAY_NAME[layer] : nodeDisplayName(progress.currentNode);
|
|
4112
4223
|
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}…` : ""}
|
|
4224
|
+
const line = stripTerminalControls(`${prefix}${label ? ` ${label}…` : ""}`);
|
|
4114
4225
|
process.stdout.write(`\r${line.padEnd(lastProgressLineLength)}`);
|
|
4115
4226
|
lastProgressLineLength = line.length;
|
|
4116
4227
|
}
|
|
@@ -4122,9 +4233,9 @@ ${formatDoctorReport(checks)}
|
|
|
4122
4233
|
function writeToolStep(step) {
|
|
4123
4234
|
clearProgress();
|
|
4124
4235
|
if (step.deniedReason) {
|
|
4125
|
-
console.log(` ${ICONS.deniedStep} Denied: ${step.summary} — ${step.deniedReason}`);
|
|
4236
|
+
console.log(stripTerminalControls(` ${ICONS.deniedStep} Denied: ${step.summary} — ${step.deniedReason}`));
|
|
4126
4237
|
} else {
|
|
4127
|
-
console.log(` ${toolStepIcon(step.tool)} ${step.summary}`);
|
|
4238
|
+
console.log(stripTerminalControls(` ${toolStepIcon(step.tool)} ${step.summary}`));
|
|
4128
4239
|
}
|
|
4129
4240
|
lastTurnToolSteps.push(step);
|
|
4130
4241
|
}
|
|
@@ -4147,20 +4258,20 @@ ${formatDoctorReport(checks)}
|
|
|
4147
4258
|
function printClarificationQuestion(question, draft, index, total) {
|
|
4148
4259
|
const options2 = orderedAskOptions(question);
|
|
4149
4260
|
const allowFreeText = question.allowFreeText !== false;
|
|
4150
|
-
|
|
4151
|
-
if (question.header) console.log(`[${question.header}]`);
|
|
4152
|
-
if (total > 1)
|
|
4153
|
-
console.log(question.question);
|
|
4261
|
+
printSafe("");
|
|
4262
|
+
if (question.header) console.log(stripTerminalControls(`[${question.header}]`));
|
|
4263
|
+
if (total > 1) printSafe(`Question ${index + 1} of ${total}`);
|
|
4264
|
+
console.log(stripTerminalControls(question.question));
|
|
4154
4265
|
options2.forEach((option, i) => {
|
|
4155
4266
|
const n = i + 1;
|
|
4156
4267
|
const picked = draft.selectedLabels.includes(option.label) ? "✓" : " ";
|
|
4157
4268
|
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}`);
|
|
4269
|
+
console.log(stripTerminalControls(` [${picked}] ${n}) ${option.label}${recommended}`));
|
|
4270
|
+
if (option.description) console.log(stripTerminalControls(` ${option.description}`));
|
|
4271
|
+
if (option.preview) console.log(stripTerminalControls(` preview: ${option.preview}`));
|
|
4161
4272
|
});
|
|
4162
|
-
if (draft.editText) console.log(` note: ${draft.editText}`);
|
|
4163
|
-
if (draft.freeText) console.log(` other: ${draft.freeText}`);
|
|
4273
|
+
if (draft.editText) console.log(stripTerminalControls(` note: ${draft.editText}`));
|
|
4274
|
+
if (draft.freeText) console.log(stripTerminalControls(` other: ${draft.freeText}`));
|
|
4164
4275
|
const commands2 = [
|
|
4165
4276
|
options2.length > 0 ? question.allowMultiple ? "<n>/t<n> toggle option" : "<n> select option" : void 0,
|
|
4166
4277
|
options2.length > 0 ? "e<n> add a note to a selected option" : void 0,
|
|
@@ -4168,7 +4279,7 @@ ${formatDoctorReport(checks)}
|
|
|
4168
4279
|
total > 1 ? "b back, n next" : void 0,
|
|
4169
4280
|
"s submit"
|
|
4170
4281
|
].filter((c) => c !== void 0);
|
|
4171
|
-
|
|
4282
|
+
printSafe(` (${commands2.join(" | ")})`);
|
|
4172
4283
|
}
|
|
4173
4284
|
async function handleClarification(result) {
|
|
4174
4285
|
const questions = result.questions ?? [];
|
|
@@ -4176,23 +4287,23 @@ ${formatDoctorReport(checks)}
|
|
|
4176
4287
|
if (questions.length === 0 || !pendingClarificationId) {
|
|
4177
4288
|
lastTrace = void 0;
|
|
4178
4289
|
lastNoTraceReason = `No harness trace — the last turn needed clarification (${result.reason ?? "no further detail"}) but had no answerable questions.`;
|
|
4179
|
-
console.log(`
|
|
4290
|
+
console.log(stripTerminalControls(`
|
|
4180
4291
|
[needs clarification] ${result.reason ?? "This request needs clarification."}
|
|
4181
|
-
`);
|
|
4292
|
+
`));
|
|
4182
4293
|
return;
|
|
4183
4294
|
}
|
|
4184
4295
|
if (nonInteractiveApprovalMode === "decline") {
|
|
4185
|
-
|
|
4296
|
+
printSafe(`
|
|
4186
4297
|
[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
|
-
|
|
4298
|
+
console.log(stripTerminalControls(`
|
|
4299
|
+
[needs clarification] ${result.reason ?? "This request needs clarification."}`));
|
|
4300
|
+
for (const q2 of questions) console.log(stripTerminalControls(` - ${q2.question}`));
|
|
4301
|
+
printSafe("");
|
|
4191
4302
|
lastTrace = void 0;
|
|
4192
4303
|
lastNoTraceReason = "No harness trace — the last turn needed clarification and was auto-declined (non-interactive mode) before the harness resumed.";
|
|
4193
4304
|
return;
|
|
4194
4305
|
}
|
|
4195
|
-
|
|
4306
|
+
printSafe(`
|
|
4196
4307
|
[needs clarification] ${questions.length} question${questions.length === 1 ? "" : "s"} to answer.`);
|
|
4197
4308
|
const drafts = new Map(questions.map((q2) => [q2.id, emptyClarificationDraft()]));
|
|
4198
4309
|
let index = 0;
|
|
@@ -4202,11 +4313,17 @@ ${formatDoctorReport(checks)}
|
|
|
4202
4313
|
printClarificationQuestion(question, draft, index, questions.length);
|
|
4203
4314
|
const token = (await askLine("clarify> ")).trim();
|
|
4204
4315
|
const lower = token.toLowerCase();
|
|
4316
|
+
if (inputUnavailable) {
|
|
4317
|
+
printSafe("\n[no input available — leaving the clarification unanswered]\n");
|
|
4318
|
+
lastTrace = void 0;
|
|
4319
|
+
lastNoTraceReason = "No harness trace — the last turn needed clarification and input closed before it was answered.";
|
|
4320
|
+
return;
|
|
4321
|
+
}
|
|
4205
4322
|
if (lower === "s" || lower === "submit") {
|
|
4206
4323
|
const answers = questions.map((q2) => clarificationDraftToAnswer(q2.id, drafts.get(q2.id)));
|
|
4207
4324
|
const missing = questions.filter((q2, i) => answers[i] === null);
|
|
4208
4325
|
if (missing.length > 0) {
|
|
4209
|
-
|
|
4326
|
+
printSafe(`
|
|
4210
4327
|
Still need an answer for: ${missing.map((q2) => q2.question).join("; ")}
|
|
4211
4328
|
`);
|
|
4212
4329
|
continue;
|
|
@@ -4225,12 +4342,12 @@ Still need an answer for: ${missing.map((q2) => q2.question).join("; ")}
|
|
|
4225
4342
|
}
|
|
4226
4343
|
if (lower === "o") {
|
|
4227
4344
|
if (question.allowFreeText === false) {
|
|
4228
|
-
|
|
4345
|
+
printSafe("\nThis question does not accept a free-text answer.\n");
|
|
4229
4346
|
continue;
|
|
4230
4347
|
}
|
|
4231
4348
|
const text = await askLine("Other — type your own answer: ");
|
|
4232
4349
|
if (!text.trim()) {
|
|
4233
|
-
|
|
4350
|
+
printSafe("\nEmpty answer ignored.\n");
|
|
4234
4351
|
continue;
|
|
4235
4352
|
}
|
|
4236
4353
|
drafts.set(question.id, { selectedLabels: [], editText: "", freeText: text.trim() });
|
|
@@ -4241,13 +4358,13 @@ Still need an answer for: ${missing.map((q2) => q2.question).join("; ")}
|
|
|
4241
4358
|
const options2 = orderedAskOptions(question);
|
|
4242
4359
|
const option = options2[Number(editMatch[1]) - 1];
|
|
4243
4360
|
if (!option) {
|
|
4244
|
-
|
|
4361
|
+
printSafe(`
|
|
4245
4362
|
No option ${editMatch[1]}.
|
|
4246
4363
|
`);
|
|
4247
4364
|
continue;
|
|
4248
4365
|
}
|
|
4249
4366
|
if (!draft.selectedLabels.includes(option.label)) {
|
|
4250
|
-
|
|
4367
|
+
printSafe(`
|
|
4251
4368
|
Select option ${editMatch[1]} first, then add a note with e${editMatch[1]}.
|
|
4252
4369
|
`);
|
|
4253
4370
|
continue;
|
|
@@ -4261,7 +4378,7 @@ Select option ${editMatch[1]} first, then add a note with e${editMatch[1]}.
|
|
|
4261
4378
|
const options2 = orderedAskOptions(question);
|
|
4262
4379
|
const option = options2[Number(toggleMatch[1]) - 1];
|
|
4263
4380
|
if (!option) {
|
|
4264
|
-
|
|
4381
|
+
printSafe(`
|
|
4265
4382
|
No option ${toggleMatch[1]}.
|
|
4266
4383
|
`);
|
|
4267
4384
|
continue;
|
|
@@ -4278,7 +4395,7 @@ No option ${toggleMatch[1]}.
|
|
|
4278
4395
|
}
|
|
4279
4396
|
continue;
|
|
4280
4397
|
}
|
|
4281
|
-
|
|
4398
|
+
printSafe(`
|
|
4282
4399
|
Unrecognized input "${token}".
|
|
4283
4400
|
`);
|
|
4284
4401
|
}
|
|
@@ -4291,7 +4408,7 @@ Unrecognized input "${token}".
|
|
|
4291
4408
|
process.stdout.write("\nAielia> ");
|
|
4292
4409
|
streamedAnyTokens = true;
|
|
4293
4410
|
}
|
|
4294
|
-
process.stdout.write(token);
|
|
4411
|
+
process.stdout.write(stripTerminalControls(token));
|
|
4295
4412
|
}
|
|
4296
4413
|
try {
|
|
4297
4414
|
const result = await assistant.turn(message, {
|
|
@@ -4331,15 +4448,15 @@ Unrecognized input "${token}".
|
|
|
4331
4448
|
if (result.status === "needs_approval" && result.pendingActionId) {
|
|
4332
4449
|
const kindLabel = result.pendingActionKind === "shell" ? "shell command" : result.pendingActionKind === "email" ? "send email" : result.pendingActionKind === "batch" ? "batch research" : "write";
|
|
4333
4450
|
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}`);
|
|
4451
|
+
console.log(stripTerminalControls(`
|
|
4452
|
+
[needs approval — ${kindLabel}] ${result.reason}`));
|
|
4336
4453
|
let confirmed;
|
|
4337
4454
|
if (result.pendingActionKind && rememberedActionKinds.has(result.pendingActionKind)) {
|
|
4338
|
-
|
|
4455
|
+
printSafe(`["don't ask again" active this session for ${kindLabel} — auto-approved]`);
|
|
4339
4456
|
confirmed = true;
|
|
4340
4457
|
} else {
|
|
4341
4458
|
const decision = await askSelect(promptText, APPROVAL_OPTIONS);
|
|
4342
|
-
confirmed = decision
|
|
4459
|
+
confirmed = decision === "y" || decision === "a";
|
|
4343
4460
|
if (decision === "a" && result.pendingActionKind) rememberedActionKinds.add(result.pendingActionKind);
|
|
4344
4461
|
}
|
|
4345
4462
|
lastTrace = void 0;
|
|
@@ -4348,16 +4465,16 @@ Unrecognized input "${token}".
|
|
|
4348
4465
|
return;
|
|
4349
4466
|
}
|
|
4350
4467
|
if (result.status === "needs_approval") {
|
|
4351
|
-
console.log(`
|
|
4352
|
-
[needs approval — ${result.riskLevel}] ${result.reason}`);
|
|
4353
|
-
|
|
4468
|
+
console.log(stripTerminalControls(`
|
|
4469
|
+
[needs approval — ${result.riskLevel}] ${result.reason}`));
|
|
4470
|
+
printSafe(` "${message}"`);
|
|
4354
4471
|
let confirmed;
|
|
4355
4472
|
if (result.riskLevel && rememberedRiskLevels.has(result.riskLevel)) {
|
|
4356
|
-
|
|
4473
|
+
printSafe(`["don't ask again" active this session for risk level ${result.riskLevel} — auto-approved]`);
|
|
4357
4474
|
confirmed = true;
|
|
4358
4475
|
} else {
|
|
4359
4476
|
const decision = await askSelect("Proceed?", APPROVAL_OPTIONS);
|
|
4360
|
-
confirmed = decision
|
|
4477
|
+
confirmed = decision === "y" || decision === "a";
|
|
4361
4478
|
if (decision === "a" && result.riskLevel) rememberedRiskLevels.add(result.riskLevel);
|
|
4362
4479
|
}
|
|
4363
4480
|
if (confirmed) {
|
|
@@ -4366,7 +4483,7 @@ Unrecognized input "${token}".
|
|
|
4366
4483
|
lastTrace = void 0;
|
|
4367
4484
|
lastNoTraceReason = "No harness trace — the last turn was blocked on an approval gate and declined before the harness ran.";
|
|
4368
4485
|
await assistant.recordDeclinedRequest("cli", message, result.reason ?? "This request needed approval.");
|
|
4369
|
-
|
|
4486
|
+
printSafe("Cancelled.\n");
|
|
4370
4487
|
}
|
|
4371
4488
|
return;
|
|
4372
4489
|
}
|
|
@@ -4376,26 +4493,26 @@ Unrecognized input "${token}".
|
|
|
4376
4493
|
}
|
|
4377
4494
|
if (result.status === "needs_plan_approval") {
|
|
4378
4495
|
if (!result.planApprovalId) {
|
|
4379
|
-
|
|
4496
|
+
printSafe(`
|
|
4380
4497
|
[needs plan approval] ${result.reason ?? "A plan is awaiting approval, but its approval id is missing."}
|
|
4381
4498
|
`);
|
|
4382
4499
|
return;
|
|
4383
4500
|
}
|
|
4384
4501
|
const id = result.planApprovalId;
|
|
4385
4502
|
const snapshot = result.planApproval;
|
|
4386
|
-
if (result.reason)
|
|
4503
|
+
if (result.reason) printSafe(`
|
|
4387
4504
|
${result.reason}`);
|
|
4388
4505
|
if (snapshot) printPlanApproval(snapshot);
|
|
4389
4506
|
pendingPlanApproval = { id, message, snapshot };
|
|
4390
4507
|
lastTrace = void 0;
|
|
4391
4508
|
lastNoTraceReason = "No harness trace — the last turn staged a plan for approval (pending /plan approve|decline|edit).";
|
|
4392
4509
|
if (!snapshot) {
|
|
4393
|
-
|
|
4510
|
+
printSafe("Decide with /plan approve or /plan decline.\n");
|
|
4394
4511
|
return;
|
|
4395
4512
|
}
|
|
4396
4513
|
const choice = await askSelect("Approve this plan?", PLAN_APPROVAL_OPTIONS);
|
|
4397
4514
|
if (choice === "d") {
|
|
4398
|
-
|
|
4515
|
+
printSafe("Left pending — /plan to review, /plan approve|edit|decline to decide.\n");
|
|
4399
4516
|
return;
|
|
4400
4517
|
}
|
|
4401
4518
|
pendingPlanApproval = void 0;
|
|
@@ -4418,9 +4535,9 @@ ${result.reason}`);
|
|
|
4418
4535
|
if (result.status === "escalated") {
|
|
4419
4536
|
lastTrace = void 0;
|
|
4420
4537
|
lastNoTraceReason = `No harness trace — the last turn escalated (${result.reason}) before completing.`;
|
|
4421
|
-
console.log(`
|
|
4538
|
+
console.log(stripTerminalControls(`
|
|
4422
4539
|
[escalated] ${result.reason}
|
|
4423
|
-
`);
|
|
4540
|
+
`));
|
|
4424
4541
|
return;
|
|
4425
4542
|
}
|
|
4426
4543
|
if (!pendingActionId) {
|
|
@@ -4436,9 +4553,11 @@ ${result.reason}`);
|
|
|
4436
4553
|
const contradictionNotice = result.contradictionNotice ? `
|
|
4437
4554
|
|
|
4438
4555
|
${result.contradictionNotice}` : "";
|
|
4439
|
-
const reviewNotice = result.reviewNotice ? `
|
|
4556
|
+
const reviewNotice = (result.reviewNotice ? `
|
|
4557
|
+
|
|
4558
|
+
${result.reviewNotice}` : "") + (result.auditNotice ? `
|
|
4440
4559
|
|
|
4441
|
-
${result.
|
|
4560
|
+
${result.auditNotice}` : "");
|
|
4442
4561
|
lastNextSteps = result.nextSteps && result.nextSteps.length > 0 ? result.nextSteps : void 0;
|
|
4443
4562
|
const nextStepsBlock = lastNextSteps ? `
|
|
4444
4563
|
|
|
@@ -4447,26 +4566,26 @@ ${formatNextSteps(lastNextSteps)}` : "";
|
|
|
4447
4566
|
const pausedNoteText = result.pausedNote ? `
|
|
4448
4567
|
|
|
4449
4568
|
${result.pausedNote}` : "";
|
|
4450
|
-
process.stdout.write(`${pausedNoteText}${riskSuffix}${sourcesHint}${planHint}${contradictionNotice}${reviewNotice}${nextStepsBlock}
|
|
4569
|
+
process.stdout.write(stripTerminalControls(`${pausedNoteText}${riskSuffix}${sourcesHint}${planHint}${contradictionNotice}${reviewNotice}${nextStepsBlock}
|
|
4451
4570
|
|
|
4452
|
-
`);
|
|
4571
|
+
`));
|
|
4453
4572
|
} else {
|
|
4454
|
-
console.log(`
|
|
4573
|
+
console.log(stripTerminalControls(`
|
|
4455
4574
|
Aielia>${riskSuffix} ${result.reply}${sourcesHint}${planHint}${contradictionNotice}${reviewNotice}${nextStepsBlock}
|
|
4456
|
-
`);
|
|
4575
|
+
`));
|
|
4457
4576
|
}
|
|
4458
4577
|
} catch (err) {
|
|
4459
4578
|
clearProgress();
|
|
4460
4579
|
const { message: errorMessage, retryable } = classifyError(err, config.llmBackend);
|
|
4461
|
-
console.log(`
|
|
4580
|
+
console.log(stripTerminalControls(`
|
|
4462
4581
|
[error] ${errorMessage}${retryable ? " Type the message again to retry." : ""}
|
|
4463
|
-
`);
|
|
4582
|
+
`));
|
|
4464
4583
|
}
|
|
4465
4584
|
}
|
|
4466
4585
|
function askYesNo(question) {
|
|
4467
4586
|
if (options.askYesNo) return options.askYesNo(question);
|
|
4468
4587
|
if (nonInteractiveApprovalMode === "decline") {
|
|
4469
|
-
|
|
4588
|
+
printSafe(`
|
|
4470
4589
|
[non-interactive mode: auto-declining — ASSISTANT_NON_INTERACTIVE_APPROVAL=decline]`);
|
|
4471
4590
|
return Promise.resolve(false);
|
|
4472
4591
|
}
|
|
@@ -4474,19 +4593,21 @@ Aielia>${riskSuffix} ${result.reply}${sourcesHint}${planHint}${contradictionNoti
|
|
|
4474
4593
|
try {
|
|
4475
4594
|
rl.question(question, (answer) => resolve2(answer.trim().toLowerCase().startsWith("y")));
|
|
4476
4595
|
} catch {
|
|
4477
|
-
|
|
4596
|
+
printSafe(`
|
|
4478
4597
|
[could not read a response — treating as declined]`);
|
|
4479
4598
|
resolve2(false);
|
|
4480
4599
|
}
|
|
4481
4600
|
});
|
|
4482
4601
|
}
|
|
4602
|
+
let inputUnavailable = false;
|
|
4483
4603
|
function askLine(question) {
|
|
4484
4604
|
if (options.askLine) return options.askLine(question);
|
|
4485
4605
|
return new Promise((resolve2) => {
|
|
4486
4606
|
try {
|
|
4487
4607
|
rl.question(question, (answer) => resolve2(answer.trim()));
|
|
4488
4608
|
} catch {
|
|
4489
|
-
|
|
4609
|
+
inputUnavailable = true;
|
|
4610
|
+
printSafe(`
|
|
4490
4611
|
[could not read a response — treating as blank]`);
|
|
4491
4612
|
resolve2("");
|
|
4492
4613
|
}
|
|
@@ -4496,7 +4617,7 @@ Aielia>${riskSuffix} ${result.reply}${sourcesHint}${planHint}${contradictionNoti
|
|
|
4496
4617
|
if (options.askSelect) return options.askSelect(question, selectOptions);
|
|
4497
4618
|
const fallbackKey = selectOptions[selectOptions.length - 1].key;
|
|
4498
4619
|
if (nonInteractiveApprovalMode === "decline") {
|
|
4499
|
-
|
|
4620
|
+
printSafe(`
|
|
4500
4621
|
[non-interactive mode: auto-declining — ASSISTANT_NON_INTERACTIVE_APPROVAL=decline]`);
|
|
4501
4622
|
return Promise.resolve(fallbackKey);
|
|
4502
4623
|
}
|
|
@@ -4513,7 +4634,7 @@ ${optionLines}
|
|
|
4513
4634
|
resolve2(((_a = byKey ?? byIndex) == null ? void 0 : _a.key) ?? fallbackKey);
|
|
4514
4635
|
});
|
|
4515
4636
|
} catch {
|
|
4516
|
-
|
|
4637
|
+
printSafe(`
|
|
4517
4638
|
[could not read a response — treating as declined]`);
|
|
4518
4639
|
resolve2(fallbackKey);
|
|
4519
4640
|
}
|
|
@@ -4527,7 +4648,7 @@ ${optionLines}
|
|
|
4527
4648
|
}
|
|
4528
4649
|
async function handleConfigCommand(args) {
|
|
4529
4650
|
if (args.length === 0) {
|
|
4530
|
-
|
|
4651
|
+
printSafe(`
|
|
4531
4652
|
${formatConfigListing(config, overriddenKeys)}
|
|
4532
4653
|
`);
|
|
4533
4654
|
return;
|
|
@@ -4536,19 +4657,19 @@ ${formatConfigListing(config, overriddenKeys)}
|
|
|
4536
4657
|
const key = args[1];
|
|
4537
4658
|
const raw = args.slice(2).join(" ");
|
|
4538
4659
|
if (!key || !isConfigKey(key)) {
|
|
4539
|
-
|
|
4660
|
+
printSafe(`
|
|
4540
4661
|
✗ Unknown config key "${key ?? ""}". Known keys: ${CONFIG_KEYS.join(", ")}
|
|
4541
4662
|
`);
|
|
4542
4663
|
return;
|
|
4543
4664
|
}
|
|
4544
4665
|
if (!raw) {
|
|
4545
|
-
|
|
4666
|
+
printSafe(`
|
|
4546
4667
|
Usage: /config set ${key} <value>
|
|
4547
4668
|
`);
|
|
4548
4669
|
return;
|
|
4549
4670
|
}
|
|
4550
4671
|
if (overriddenKeys.has(key)) {
|
|
4551
|
-
|
|
4672
|
+
printSafe(`
|
|
4552
4673
|
✗ "${key}" is pinned by ${ENV_VAR_FOR_CONFIG_KEY[key]} — unset that env var to change it here.
|
|
4553
4674
|
`);
|
|
4554
4675
|
return;
|
|
@@ -4558,7 +4679,7 @@ Usage: /config set ${key} <value>
|
|
|
4558
4679
|
value = parseConfigValue(key, raw);
|
|
4559
4680
|
} catch (err) {
|
|
4560
4681
|
if (!(err instanceof ConfigValueParseError)) throw err;
|
|
4561
|
-
|
|
4682
|
+
printSafe(`
|
|
4562
4683
|
✗ ${err.message}
|
|
4563
4684
|
`);
|
|
4564
4685
|
return;
|
|
@@ -4568,14 +4689,14 @@ Usage: /config set ${key} <value>
|
|
|
4568
4689
|
validateConfig(patch, config);
|
|
4569
4690
|
} catch (err) {
|
|
4570
4691
|
if (!(err instanceof ConfigValidationError)) throw err;
|
|
4571
|
-
|
|
4692
|
+
printSafe(`
|
|
4572
4693
|
✗ ${err.message}
|
|
4573
4694
|
`);
|
|
4574
4695
|
return;
|
|
4575
4696
|
}
|
|
4576
4697
|
await configStore.save(patch);
|
|
4577
4698
|
await reloadAssistant();
|
|
4578
|
-
|
|
4699
|
+
printSafe(`
|
|
4579
4700
|
✓ ${key} updated (took effect immediately, no restart needed)
|
|
4580
4701
|
`);
|
|
4581
4702
|
return;
|
|
@@ -4583,8 +4704,19 @@ Usage: /config set ${key} <value>
|
|
|
4583
4704
|
if (args[0] === "reset") {
|
|
4584
4705
|
const key = args[1];
|
|
4585
4706
|
if (key && !isConfigKey(key)) {
|
|
4586
|
-
|
|
4707
|
+
printSafe(`
|
|
4587
4708
|
✗ Unknown config key "${key}". Known keys: ${CONFIG_KEYS.join(", ")}
|
|
4709
|
+
`);
|
|
4710
|
+
return;
|
|
4711
|
+
}
|
|
4712
|
+
const remaining = { ...await configStore.load() };
|
|
4713
|
+
for (const k of key ? [key] : CONFIG_KEYS) delete remaining[k];
|
|
4714
|
+
try {
|
|
4715
|
+
validateConfig({}, resolveConfig(remaining, envOverrides).config);
|
|
4716
|
+
} catch (err) {
|
|
4717
|
+
if (!(err instanceof ConfigValidationError)) throw err;
|
|
4718
|
+
printSafe(`
|
|
4719
|
+
✗ Not reset: ${err.message}
|
|
4588
4720
|
`);
|
|
4589
4721
|
return;
|
|
4590
4722
|
}
|
|
@@ -4592,23 +4724,23 @@ Usage: /config set ${key} <value>
|
|
|
4592
4724
|
const confirmed = await askYesNo(`
|
|
4593
4725
|
Reset ${target} to default? This takes effect immediately. (y/N) `);
|
|
4594
4726
|
if (!confirmed) {
|
|
4595
|
-
|
|
4727
|
+
printSafe("\nCancelled — nothing was reset.\n");
|
|
4596
4728
|
return;
|
|
4597
4729
|
}
|
|
4598
4730
|
const clearPatch = key ? { [key]: void 0 } : Object.fromEntries(CONFIG_KEYS.map((k) => [k, void 0]));
|
|
4599
4731
|
await configStore.save(clearPatch);
|
|
4600
4732
|
await reloadAssistant();
|
|
4601
|
-
|
|
4733
|
+
printSafe(`
|
|
4602
4734
|
✓ Reset ${key ?? "all settings"} to default
|
|
4603
4735
|
`);
|
|
4604
4736
|
return;
|
|
4605
4737
|
}
|
|
4606
|
-
|
|
4738
|
+
printSafe("\nUsage: /config | /config set <key> <value> | /config reset [key]\n");
|
|
4607
4739
|
}
|
|
4608
4740
|
async function handleLayers(args) {
|
|
4609
4741
|
const [sub, id] = args;
|
|
4610
4742
|
if (sub === "settings" || sub === "list") {
|
|
4611
|
-
|
|
4743
|
+
printSafe(`
|
|
4612
4744
|
${formatLayerListing(sanitizeLayerChoices(config.layers), layerPins, process.env)}
|
|
4613
4745
|
`);
|
|
4614
4746
|
return;
|
|
@@ -4616,7 +4748,7 @@ ${formatLayerListing(sanitizeLayerChoices(config.layers), layerPins, process.env
|
|
|
4616
4748
|
try {
|
|
4617
4749
|
if (sub === "on" || sub === "off") {
|
|
4618
4750
|
if (!id) {
|
|
4619
|
-
|
|
4751
|
+
printSafe(`
|
|
4620
4752
|
Usage: /layers ${sub} <id>
|
|
4621
4753
|
`);
|
|
4622
4754
|
return;
|
|
@@ -4627,19 +4759,19 @@ Usage: /layers ${sub} <id>
|
|
|
4627
4759
|
const layers = id ? withLayerChoice(sanitizeLayerChoices(config.layers), id, void 0) : {};
|
|
4628
4760
|
await configStore.save({ layers });
|
|
4629
4761
|
} else {
|
|
4630
|
-
|
|
4762
|
+
printSafe("\nUsage: /layers settings | /layers on <id> | /layers off <id> | /layers reset [id]\n");
|
|
4631
4763
|
return;
|
|
4632
4764
|
}
|
|
4633
4765
|
} catch (err) {
|
|
4634
4766
|
if (!(err instanceof LayerSettingError)) throw err;
|
|
4635
|
-
|
|
4767
|
+
printSafe(`
|
|
4636
4768
|
✗ ${err.message}
|
|
4637
4769
|
`);
|
|
4638
4770
|
return;
|
|
4639
4771
|
}
|
|
4640
4772
|
await reloadAssistant();
|
|
4641
4773
|
const pinned = id !== void 0 && layerPins.has(id);
|
|
4642
|
-
|
|
4774
|
+
printSafe(pinned ? `
|
|
4643
4775
|
✗ Saved, but "${id}" is pinned by its AUDIT_* env flag, so the saved choice has no effect until that is unset.
|
|
4644
4776
|
` : `
|
|
4645
4777
|
✓ Layers updated (took effect immediately, no restart needed)
|
|
@@ -4647,7 +4779,7 @@ Usage: /layers ${sub} <id>
|
|
|
4647
4779
|
}
|
|
4648
4780
|
async function handleModel(args) {
|
|
4649
4781
|
if (args.length === 0) {
|
|
4650
|
-
|
|
4782
|
+
printSafe(`
|
|
4651
4783
|
${config.model ?? "(using each backend's default)"}
|
|
4652
4784
|
`);
|
|
4653
4785
|
return;
|
|
@@ -4656,7 +4788,7 @@ ${config.model ?? "(using each backend's default)"}
|
|
|
4656
4788
|
}
|
|
4657
4789
|
async function handleProject(args) {
|
|
4658
4790
|
if (args.length === 0) {
|
|
4659
|
-
|
|
4791
|
+
printSafe(`
|
|
4660
4792
|
${assistant.getActiveProject() || "(none)"}
|
|
4661
4793
|
`);
|
|
4662
4794
|
return;
|
|
@@ -4694,6 +4826,7 @@ ${assistant.getActiveProject() || "(none)"}
|
|
|
4694
4826
|
"/checkpoint": (args) => handleCheckpoint(args)
|
|
4695
4827
|
};
|
|
4696
4828
|
async function dispatchOne(message) {
|
|
4829
|
+
var _a, _b;
|
|
4697
4830
|
if (quitting) return;
|
|
4698
4831
|
if (isQuitCommand(message)) {
|
|
4699
4832
|
quitting = true;
|
|
@@ -4701,27 +4834,29 @@ ${assistant.getActiveProject() || "(none)"}
|
|
|
4701
4834
|
await assistant.endSession("cli");
|
|
4702
4835
|
} catch {
|
|
4703
4836
|
}
|
|
4704
|
-
|
|
4837
|
+
printSafe("Exiting.");
|
|
4705
4838
|
rl.close();
|
|
4706
4839
|
return;
|
|
4707
4840
|
}
|
|
4708
4841
|
const pick = /^[1-3]$/.test(message) && lastNextSteps ? lastNextSteps[Number(message) - 1] : void 0;
|
|
4709
4842
|
lastNextSteps = void 0;
|
|
4710
4843
|
if (pick) {
|
|
4711
|
-
|
|
4844
|
+
printSafe(` → ${pick.description}`);
|
|
4712
4845
|
message = pick.description;
|
|
4713
4846
|
}
|
|
4714
4847
|
const [token, ...args] = message.split(/\s+/);
|
|
4715
|
-
const handler = commands[token];
|
|
4848
|
+
const handler = !pick && Object.hasOwn(commands, token) ? commands[token] : void 0;
|
|
4716
4849
|
if (handler) {
|
|
4717
4850
|
await handler(args);
|
|
4718
4851
|
return;
|
|
4719
4852
|
}
|
|
4720
4853
|
turnInProgress = true;
|
|
4854
|
+
(_a = options.onTurnBusyChange) == null ? void 0 : _a.call(options, true);
|
|
4721
4855
|
try {
|
|
4722
4856
|
await handleTurn(message);
|
|
4723
4857
|
} finally {
|
|
4724
4858
|
turnInProgress = false;
|
|
4859
|
+
(_b = options.onTurnBusyChange) == null ? void 0 : _b.call(options, false);
|
|
4725
4860
|
if (isGoalGraphEnabled(config.goalGraphMode)) {
|
|
4726
4861
|
for (const event of steeringChannel.poll()) {
|
|
4727
4862
|
void enqueue(event.message);
|
|
@@ -4740,12 +4875,13 @@ ${assistant.getActiveProject() || "(none)"}
|
|
|
4740
4875
|
}
|
|
4741
4876
|
function isKnownCommand(message) {
|
|
4742
4877
|
const [token] = message.split(/\s+/);
|
|
4743
|
-
return token
|
|
4878
|
+
return Object.hasOwn(commands, token) || isQuitCommand(message);
|
|
4744
4879
|
}
|
|
4745
4880
|
function routeMessage(message) {
|
|
4746
4881
|
if (isGoalGraphEnabled(config.goalGraphMode) && turnInProgress && !isKnownCommand(message)) {
|
|
4747
4882
|
steeringChannel.enqueue(message);
|
|
4748
|
-
|
|
4883
|
+
assistant.logQueuedMessage("cli", message);
|
|
4884
|
+
printSafe("\n[queued — the current turn is still running; this will be taken into account once it finishes]\n");
|
|
4749
4885
|
return Promise.resolve();
|
|
4750
4886
|
}
|
|
4751
4887
|
return enqueue(message);
|
|
@@ -4783,7 +4919,19 @@ ${assistant.getActiveProject() || "(none)"}
|
|
|
4783
4919
|
}
|
|
4784
4920
|
};
|
|
4785
4921
|
}
|
|
4922
|
+
class StartupError extends Error {
|
|
4923
|
+
}
|
|
4786
4924
|
async function main(argv = process.argv.slice(2)) {
|
|
4925
|
+
try {
|
|
4926
|
+
await mainInner(argv);
|
|
4927
|
+
} catch (err) {
|
|
4928
|
+
if (!(err instanceof StartupError)) throw err;
|
|
4929
|
+
process.stderr.write(`${err.message}
|
|
4930
|
+
`);
|
|
4931
|
+
process.exitCode = 1;
|
|
4932
|
+
}
|
|
4933
|
+
}
|
|
4934
|
+
async function mainInner(argv) {
|
|
4787
4935
|
const parsed = parseCliArgs(argv);
|
|
4788
4936
|
if (parsed.command === "version") {
|
|
4789
4937
|
console.log(CLI_VERSION);
|
|
@@ -4807,7 +4955,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
4807
4955
|
});
|
|
4808
4956
|
}
|
|
4809
4957
|
if (shouldLaunchTuiApp(config.tuiMode ?? "disabled", Boolean(process.stdout.isTTY), Boolean(process.stdin.isTTY))) {
|
|
4810
|
-
const { runTuiApp } = await import("./tui-app-
|
|
4958
|
+
const { runTuiApp } = await import("./tui-app-CJq6YaiP.js");
|
|
4811
4959
|
await runTuiApp();
|
|
4812
4960
|
return;
|
|
4813
4961
|
}
|
|
@@ -4831,6 +4979,7 @@ export {
|
|
|
4831
4979
|
PLAN_LINE_PREFIX as P,
|
|
4832
4980
|
runCli as a,
|
|
4833
4981
|
renderPlan as r,
|
|
4982
|
+
sanitizeLabel as s,
|
|
4834
4983
|
toLines as t
|
|
4835
4984
|
};
|
|
4836
|
-
//# sourceMappingURL=bin-
|
|
4985
|
+
//# sourceMappingURL=bin-BYRl4xXM.js.map
|