@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.
Files changed (47) hide show
  1. package/README.md +97 -19
  2. package/dist/action-approval-service.d.ts +34 -2
  3. package/dist/action-record.d.ts +13 -0
  4. package/dist/activity-log.d.ts +4 -0
  5. package/dist/agent-loop.d.ts +83 -1
  6. package/dist/assistant-session.d.ts +20 -1
  7. package/dist/assistant-types.d.ts +3 -1
  8. package/dist/assistant.d.ts +32 -0
  9. package/dist/{bin-CjKpxDxl.js → bin-C_hgDdSf.js} +369 -219
  10. package/dist/bin-C_hgDdSf.js.map +1 -0
  11. package/dist/cli-config.d.ts +3 -0
  12. package/dist/cli-session.d.ts +5 -4
  13. package/dist/cli.d.ts +9 -0
  14. package/dist/cli.js +1 -1
  15. package/dist/config.d.ts +3 -1
  16. package/dist/debug-log.d.ts +10 -1
  17. package/dist/email.d.ts +5 -3
  18. package/dist/file-tools-mcp-server.mjs +155 -30
  19. package/dist/file-tools.d.ts +2 -0
  20. package/dist/git-exclude.d.ts +9 -0
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.js +127 -126
  23. package/dist/ink-select-prompt.d.ts +2 -0
  24. package/dist/ink-test-render.d.ts +2 -0
  25. package/dist/memory-service.d.ts +8 -0
  26. package/dist/next-step-proposer.d.ts +7 -0
  27. package/dist/plan-drafting-service.d.ts +3 -0
  28. package/dist/plan-store.d.ts +5 -0
  29. package/dist/{provider-setup-LFsaTkpf.js → provider-setup-wX2IBw2X.js} +2183 -919
  30. package/dist/provider-setup-wX2IBw2X.js.map +1 -0
  31. package/dist/reply-audit.d.ts +67 -0
  32. package/dist/side-call-policy.d.ts +30 -0
  33. package/dist/tool-step.d.ts +6 -0
  34. package/dist/trace-events.d.ts +1 -1
  35. package/dist/trust-tagging.d.ts +2 -0
  36. package/dist/{tui-app-BLR-PRFp.js → tui-app-C0J6HRzv.js} +127 -26
  37. package/dist/tui-app-C0J6HRzv.js.map +1 -0
  38. package/dist/tui-app.d.ts +35 -0
  39. package/dist/tui-mode-flag.d.ts +1 -1
  40. package/dist/turn-abort.d.ts +18 -0
  41. package/dist/turn-intent-classifier.d.ts +8 -0
  42. package/dist/turn-interpreter.d.ts +2 -0
  43. package/dist/version.d.ts +1 -1
  44. package/package.json +3 -3
  45. package/dist/bin-CjKpxDxl.js.map +0 -1
  46. package/dist/provider-setup-LFsaTkpf.js.map +0 -1
  47. 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 { X as buildClaudePrompt, bH as stripJsonCodeFence, bm as recallToolEnabled, aX as lexicalOffEnvValue, a as ALREADY_STAGED_ACTION_TOOL, bG as stagedActionInput, bg as parseClaudeCliOutput, bI as stripMcpToolPrefix, b3 as memoryStatusChecks, bf as normalizePlanNodes, P as PROVIDER_SETUP, a3 as cleanApiKey, Z as checkApiKeyFormat, af as envOverridesFromProcessEnv, bp as resolveConfig, bE as shouldLaunchTuiApp, Q as applyLayerSettings, bC as sanitizeLayerChoices, bL as testApiKey, bO as validateConfig, c as ConfigValidationError, aF as formatSpendCapStatus, aS as isPlanGraphEnabled, z as PersonalAssistant, aP as isGoalGraphSuggestEnabled, aO as isGoalGraphEnabled, bl as planToSnapshot, V as braveSearch, a7 as createSmtpSender, a6 as createResendSender, aU as isQuitCommand, x as LiveSteeringChannel, aC as formatNextSteps, _ as classifyError, b6 as nodeToLayer, L as LAYER_DISPLAY_NAME, b5 as nodeDisplayName, ah as estimateCostUsd, ao as formatConfigListing, aN as isConfigKey, C as CONFIG_KEYS, E as ENV_VAR_FOR_CONFIG_KEY, bh as parseConfigValue, d as ConfigValueParseError, aq as formatDoctorReport, ap as formatCostSummary, as as formatGoalGraphState, aE as formatSearchResults, ax as formatMemoryHistory, aA as formatMemoryStatus, az as formatMemoryPendingOutcome, av as formatMemoryArchive, aI as formatUndoLogListing, aa as defaultExportFilename, aH as formatTranscriptMarkdown, aG as formatStatus, at as formatHelp, q as LAYER_ORDER, au as formatLayerListing, bP as withLayerChoice, w as LayerSettingError, ay as formatMemoryInjection, Y as buildWhyChain, s as LAYER_SHORT_CODE, ab as defaultMemoryExportFilename, aw as formatMemoryExport, b2 as memoryAuditLogEnabled, aB as formatMemorySummary } from "./provider-setup-LFsaTkpf.js";
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.5";
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
- proc.stdout.on("data", (chunk) => {
196
+ const handleLine = (line) => {
134
197
  var _a;
135
- buffer += chunk.toString("utf-8");
136
- let newlineIndex;
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
- continue;
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
- const stats = await stat(filePath);
412
- if (stats.mtimeMs < startTimeMs - 1e3) continue;
413
- const record = JSON.parse(await readFile(filePath, "utf-8"));
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
- (_a = proc.stdout) == null ? void 0 : _a.on("data", (chunk) => {
552
- output += chunk.toString("utf-8");
553
- });
554
- (_b = proc.stderr) == null ? void 0 : _b.on("data", (chunk) => {
555
- output += chunk.toString("utf-8");
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
- return JSON.parse(raw);
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
- async function getJson(fetchFn, url, headers = {}) {
3187
- return fetchFn(url, { headers, signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), redirect: "follow" });
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 fetchSidecarHash(fetchFn, assetUrl) {
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: config.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
- console.log("Aielia is alpha software — expect rough edges and breaking changes. It uses AI models and can make mistakes; verify anything important.\n");
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
- console.error(err.message);
3489
- process.exit(1);
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
- console.error(
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
- console.log("\nExiting.");
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
- console.log(`backend: ${config.llmBackend} (${config.model ?? backendDisplayModel[config.llmBackend]})`);
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
- console.log(`Aielia — your personal assistant on the 11-layer harness, one turn at a time. Ctrl+C to exit.${capabilitySuffix}
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
- console.log("Type /help to see all commands, /config to view settings.\n");
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
- console.log("\nNo active plan for this session.\n");
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
- console.log("\nUsage: /plan sketch <request>\n");
3688
+ printSafe("\nUsage: /plan sketch <request>\n");
3577
3689
  return;
3578
3690
  }
3579
- console.log("\nSketching a plan (read-only grounding only, nothing staged)...\n");
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) console.log(` [ignoring unknown task id(s): ${[...new Set(unknown)].join(", ")}]`);
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
- console.log("\nNo plan is awaiting approval.\n");
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
- console.log("\n[tasks unavailable for editing — /plan approve or /plan decline]\n");
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
- console.log(args[0] ? `
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
- console.log(`
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
- console.log(`
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
- console.log(`
3799
+ printSafe(`
3688
3800
  [plan graph is wider than ${cols} columns] dependency list:
3689
3801
  `);
3690
- for (const n of nodes) console.log(` [${n.status}] ${n.id} — ${n.label}${n.deps.length ? ` (after: ${n.deps.join(", ")})` : ""}`);
3691
- console.log("");
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
- console.log(`
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
- console.log(`
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
- console.log(`
3851
+ printSafe(`
3740
3852
  ${verificationHealthLabel(lastTrace.verificationHealth)}`);
3741
3853
  const chain = buildWhyChain(lastTrace.layerActivity);
3742
3854
  if (chain.length > 0) {
3743
- console.log(" " + chain.map((item) => `${LAYER_SHORT_CODE[item.layer]} (${item.reason})`).join(" > "));
3855
+ printSafe(" " + chain.map((item) => `${LAYER_SHORT_CODE[item.layer]} (${item.reason})`).join(" > "));
3744
3856
  }
3745
3857
  if (lastTrace.batchBudget) {
3746
- console.log(" " + batchBudgetSummaryLine(lastTrace.batchBudget));
3858
+ printSafe(" " + batchBudgetSummaryLine(lastTrace.batchBudget));
3747
3859
  }
3748
- console.log(memoryLine);
3749
- console.log("");
3860
+ printSafe(memoryLine);
3861
+ printSafe("");
3750
3862
  }
3751
3863
  function printLayers() {
3752
3864
  if (!lastTrace) {
3753
- console.log(`
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
- console.log("");
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
- console.log(` [${mark}] ${LAYER_DISPLAY_NAME[layer].padEnd(22)} ${reason}`);
3876
+ printSafe(` [${mark}] ${LAYER_DISPLAY_NAME[layer].padEnd(22)} ${reason}`);
3765
3877
  }
3766
3878
  if (lastTrace.batchBudget) {
3767
- console.log("");
3768
- console.log(` ${batchBudgetSummaryLine(lastTrace.batchBudget)}`);
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
- console.log(` [${STATUS_MARK[outcome.status]}] ${outcome.item.padEnd(30)} ${outcome.status} (${outcome.callsUsed} calls)`);
3887
+ printSafe(` [${STATUS_MARK[outcome.status]}] ${outcome.item.padEnd(30)} ${outcome.status} (${outcome.callsUsed} calls)`);
3776
3888
  }
3777
3889
  }
3778
- console.log("");
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
- console.log("\nNo sources for the last turn (it used no file/web tool calls).\n");
3900
+ printSafe("\nNo sources for the last turn (it used no file/web tool calls).\n");
3789
3901
  return;
3790
3902
  }
3791
- console.log("");
3903
+ printSafe("");
3792
3904
  for (const source of lastSources) {
3793
- console.log(` - ${SOURCE_TOOL_LABEL[source.tool]} ${source.path}`);
3905
+ printSafe(` - ${SOURCE_TOOL_LABEL[source.tool]} ${source.path}`);
3794
3906
  }
3795
- console.log("");
3907
+ printSafe("");
3796
3908
  }
3797
3909
  function printHelp() {
3798
- console.log(`
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
- console.log("\n✓ Started a fresh conversation.\n");
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
- console.log(
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
- console.log("\nNo checkpoint present — the last turn completed normally or there was nothing in progress.\n");
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
- console.log(
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
- console.log(
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
- console.log("\nNothing to export yet.\n");
3965
+ printSafe("\nNothing to export yet.\n");
3854
3966
  return;
3855
3967
  }
3856
- const filename = resolve(process.cwd(), args[0] ?? defaultExportFilename());
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
- console.log(`
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
- console.log(`
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
- console.log("\nNothing to undo.\n");
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
- console.log("\nNothing to undo.\n");
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
- console.log(
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
- console.log(`
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
- console.log(`
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
- console.log(`
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[0] ?? defaultMemoryExportFilename());
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
- console.log(`
4044
+ printSafe(`
3933
4045
  ✓ Exported learned memory to ${filename}
3934
4046
  `);
3935
4047
  } catch (err) {
3936
4048
  const { message } = classifyError(err);
3937
- console.log(`
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
- console.log(`
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
- console.log(`
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
- console.log("\nUsage: /memory forget <n> [erase]\n");
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
- console.log(`
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
- console.log(`
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
- console.log(`
4101
+ printSafe(`
3990
4102
  ${formatMemoryHistory(await assistant.memoryHistory())}
3991
4103
  `);
3992
4104
  return;
3993
4105
  }
3994
4106
  if (args[0] === "status") {
3995
- console.log(`
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
- console.log(`
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
- console.log(`
4122
+ printSafe(`
4011
4123
  ${outcome.message}
4012
4124
  `);
4013
4125
  return;
4014
4126
  }
4015
- console.log(`
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
- console.log(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");
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
- console.log(`
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
- console.log(`
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
- console.log(`
4161
+ printSafe(`
4050
4162
  ${outcome.message}
4051
4163
  `);
4052
4164
  return;
4053
4165
  }
4054
- console.log(`
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
- console.log("\nUsage: /search <query>\n");
4176
+ printSafe("\nUsage: /search <query>\n");
4065
4177
  return;
4066
4178
  }
4067
4179
  const hits = await assistant.searchTranscript(query);
4068
- console.log(`
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
- console.log(`
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
- console.log(`
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
- console.log(`
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
- console.log("");
4151
- if (question.header) console.log(`[${question.header}]`);
4152
- if (total > 1) console.log(`Question ${index + 1} of ${total}`);
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
- console.log(` (${commands2.join(" | ")})`);
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
- console.log(`
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
- console.log("");
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
- console.log(`
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
- console.log(`
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
- console.log("\nThis question does not accept a free-text answer.\n");
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
- console.log("\nEmpty answer ignored.\n");
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
- console.log(`
4362
+ printSafe(`
4245
4363
  No option ${editMatch[1]}.
4246
4364
  `);
4247
4365
  continue;
4248
4366
  }
4249
4367
  if (!draft.selectedLabels.includes(option.label)) {
4250
- console.log(`
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
- console.log(`
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
- console.log(`
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
- console.log(`["don't ask again" active this session for ${kindLabel} — auto-approved]`);
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 !== "n";
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
- console.log(` "${message}"`);
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
- console.log(`["don't ask again" active this session for risk level ${result.riskLevel} — auto-approved]`);
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 !== "n";
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
- console.log("Cancelled.\n");
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
- console.log(`
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) console.log(`
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
- console.log("Decide with /plan approve or /plan decline.\n");
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
- console.log("Left pending — /plan to review, /plan approve|edit|decline to decide.\n");
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.reviewNotice}` : "";
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
- console.log(`
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
- console.log(`
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
- console.log(`
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
- console.log(`
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
- console.log(`
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
- console.log(`
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
- console.log(`
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
- console.log(`
4667
+ printSafe(`
4546
4668
  Usage: /config set ${key} <value>
4547
4669
  `);
4548
4670
  return;
4549
4671
  }
4550
4672
  if (overriddenKeys.has(key)) {
4551
- console.log(`
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
- console.log(`
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
- console.log(`
4693
+ printSafe(`
4572
4694
  ✗ ${err.message}
4573
4695
  `);
4574
4696
  return;
4575
4697
  }
4576
4698
  await configStore.save(patch);
4577
4699
  await reloadAssistant();
4578
- console.log(`
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
- console.log(`
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
- console.log("\nCancelled — nothing was reset.\n");
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
- console.log(`
4734
+ printSafe(`
4602
4735
  ✓ Reset ${key ?? "all settings"} to default
4603
4736
  `);
4604
4737
  return;
4605
4738
  }
4606
- console.log("\nUsage: /config | /config set <key> <value> | /config reset [key]\n");
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
- console.log(`
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
- console.log(`
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
- console.log("\nUsage: /layers settings | /layers on <id> | /layers off <id> | /layers reset [id]\n");
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
- console.log(`
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
- console.log(pinned ? `
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
- console.log(`
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
- console.log(`
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
- console.log("Exiting.");
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
- console.log(` → ${pick.description}`);
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 in commands || isQuitCommand(message);
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
- console.log("\n[queued — the current turn is still running; this will be taken into account once it finishes]\n");
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 ?? "disabled", Boolean(process.stdout.isTTY), Boolean(process.stdin.isTTY))) {
4810
- const { runTuiApp } = await import("./tui-app-BLR-PRFp.js");
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-CjKpxDxl.js.map
4986
+ //# sourceMappingURL=bin-C_hgDdSf.js.map