@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.
Files changed (46) hide show
  1. package/README.md +96 -18
  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-BYRl4xXM.js} +365 -216
  10. package/dist/bin-BYRl4xXM.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 +2 -0
  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 +79 -78
  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-CV3S_TAS.js} +2129 -866
  30. package/dist/provider-setup-CV3S_TAS.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-CJq6YaiP.js} +127 -26
  37. package/dist/tui-app-CJq6YaiP.js.map +1 -0
  38. package/dist/tui-app.d.ts +35 -0
  39. package/dist/turn-abort.d.ts +18 -0
  40. package/dist/turn-intent-classifier.d.ts +8 -0
  41. package/dist/turn-interpreter.d.ts +2 -0
  42. package/dist/version.d.ts +1 -1
  43. package/package.json +3 -3
  44. package/dist/bin-CjKpxDxl.js.map +0 -1
  45. package/dist/provider-setup-LFsaTkpf.js.map +0 -1
  46. 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, 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";
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.5";
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
- 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,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
- console.log("Aielia is alpha software — expect rough edges and breaking changes. It uses AI models and can make mistakes; verify anything important.\n");
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
- console.error(err.message);
3489
- process.exit(1);
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
- console.error(
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
- console.log("\nExiting.");
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
- console.log(`backend: ${config.llmBackend} (${config.model ?? backendDisplayModel[config.llmBackend]})`);
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
- console.log(`Aielia — your personal assistant on the 11-layer harness, one turn at a time. Ctrl+C to exit.${capabilitySuffix}
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
- console.log("Type /help to see all commands, /config to view settings.\n");
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
- console.log("\nNo active plan for this session.\n");
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
- console.log("\nUsage: /plan sketch <request>\n");
3687
+ printSafe("\nUsage: /plan sketch <request>\n");
3577
3688
  return;
3578
3689
  }
3579
- console.log("\nSketching a plan (read-only grounding only, nothing staged)...\n");
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) console.log(` [ignoring unknown task id(s): ${[...new Set(unknown)].join(", ")}]`);
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
- console.log("\nNo plan is awaiting approval.\n");
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
- console.log("\n[tasks unavailable for editing — /plan approve or /plan decline]\n");
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
- console.log(args[0] ? `
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
- console.log(`
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
- console.log(`
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
- console.log(`
3798
+ printSafe(`
3688
3799
  [plan graph is wider than ${cols} columns] dependency list:
3689
3800
  `);
3690
- for (const n of nodes) console.log(` [${n.status}] ${n.id} — ${n.label}${n.deps.length ? ` (after: ${n.deps.join(", ")})` : ""}`);
3691
- console.log("");
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
- console.log(`
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
- console.log(`
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
- console.log(`
3850
+ printSafe(`
3740
3851
  ${verificationHealthLabel(lastTrace.verificationHealth)}`);
3741
3852
  const chain = buildWhyChain(lastTrace.layerActivity);
3742
3853
  if (chain.length > 0) {
3743
- console.log(" " + chain.map((item) => `${LAYER_SHORT_CODE[item.layer]} (${item.reason})`).join(" > "));
3854
+ printSafe(" " + chain.map((item) => `${LAYER_SHORT_CODE[item.layer]} (${item.reason})`).join(" > "));
3744
3855
  }
3745
3856
  if (lastTrace.batchBudget) {
3746
- console.log(" " + batchBudgetSummaryLine(lastTrace.batchBudget));
3857
+ printSafe(" " + batchBudgetSummaryLine(lastTrace.batchBudget));
3747
3858
  }
3748
- console.log(memoryLine);
3749
- console.log("");
3859
+ printSafe(memoryLine);
3860
+ printSafe("");
3750
3861
  }
3751
3862
  function printLayers() {
3752
3863
  if (!lastTrace) {
3753
- console.log(`
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
- console.log("");
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
- console.log(` [${mark}] ${LAYER_DISPLAY_NAME[layer].padEnd(22)} ${reason}`);
3875
+ printSafe(` [${mark}] ${LAYER_DISPLAY_NAME[layer].padEnd(22)} ${reason}`);
3765
3876
  }
3766
3877
  if (lastTrace.batchBudget) {
3767
- console.log("");
3768
- console.log(` ${batchBudgetSummaryLine(lastTrace.batchBudget)}`);
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
- console.log(` [${STATUS_MARK[outcome.status]}] ${outcome.item.padEnd(30)} ${outcome.status} (${outcome.callsUsed} calls)`);
3886
+ printSafe(` [${STATUS_MARK[outcome.status]}] ${outcome.item.padEnd(30)} ${outcome.status} (${outcome.callsUsed} calls)`);
3776
3887
  }
3777
3888
  }
3778
- console.log("");
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
- console.log("\nNo sources for the last turn (it used no file/web tool calls).\n");
3899
+ printSafe("\nNo sources for the last turn (it used no file/web tool calls).\n");
3789
3900
  return;
3790
3901
  }
3791
- console.log("");
3902
+ printSafe("");
3792
3903
  for (const source of lastSources) {
3793
- console.log(` - ${SOURCE_TOOL_LABEL[source.tool]} ${source.path}`);
3904
+ printSafe(` - ${SOURCE_TOOL_LABEL[source.tool]} ${source.path}`);
3794
3905
  }
3795
- console.log("");
3906
+ printSafe("");
3796
3907
  }
3797
3908
  function printHelp() {
3798
- console.log(`
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
- console.log("\n✓ Started a fresh conversation.\n");
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
- console.log(
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
- console.log("\nNo checkpoint present — the last turn completed normally or there was nothing in progress.\n");
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
- console.log(
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
- console.log(
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
- console.log("\nNothing to export yet.\n");
3964
+ printSafe("\nNothing to export yet.\n");
3854
3965
  return;
3855
3966
  }
3856
- const filename = resolve(process.cwd(), args[0] ?? defaultExportFilename());
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
- console.log(`
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
- console.log(`
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
- console.log("\nNothing to undo.\n");
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
- console.log("\nNothing to undo.\n");
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
- console.log(
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
- console.log(`
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
- console.log(`
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
- console.log(`
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[0] ?? defaultMemoryExportFilename());
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
- console.log(`
4043
+ printSafe(`
3933
4044
  ✓ Exported learned memory to ${filename}
3934
4045
  `);
3935
4046
  } catch (err) {
3936
4047
  const { message } = classifyError(err);
3937
- console.log(`
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
- console.log(`
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
- console.log(`
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
- console.log("\nUsage: /memory forget <n> [erase]\n");
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
- console.log(`
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
- console.log(`
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
- console.log(`
4100
+ printSafe(`
3990
4101
  ${formatMemoryHistory(await assistant.memoryHistory())}
3991
4102
  `);
3992
4103
  return;
3993
4104
  }
3994
4105
  if (args[0] === "status") {
3995
- console.log(`
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
- console.log(`
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
- console.log(`
4121
+ printSafe(`
4011
4122
  ${outcome.message}
4012
4123
  `);
4013
4124
  return;
4014
4125
  }
4015
- console.log(`
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
- 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");
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
- console.log(`
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
- console.log(`
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
- console.log(`
4160
+ printSafe(`
4050
4161
  ${outcome.message}
4051
4162
  `);
4052
4163
  return;
4053
4164
  }
4054
- console.log(`
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
- console.log("\nUsage: /search <query>\n");
4175
+ printSafe("\nUsage: /search <query>\n");
4065
4176
  return;
4066
4177
  }
4067
4178
  const hits = await assistant.searchTranscript(query);
4068
- console.log(`
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
- console.log(`
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
- console.log(`
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
- console.log(`
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
- 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);
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
- console.log(` (${commands2.join(" | ")})`);
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
- console.log(`
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
- console.log("");
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
- console.log(`
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
- console.log(`
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
- console.log("\nThis question does not accept a free-text answer.\n");
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
- console.log("\nEmpty answer ignored.\n");
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
- console.log(`
4361
+ printSafe(`
4245
4362
  No option ${editMatch[1]}.
4246
4363
  `);
4247
4364
  continue;
4248
4365
  }
4249
4366
  if (!draft.selectedLabels.includes(option.label)) {
4250
- console.log(`
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
- console.log(`
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
- console.log(`
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
- console.log(`["don't ask again" active this session for ${kindLabel} — auto-approved]`);
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 !== "n";
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
- console.log(` "${message}"`);
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
- console.log(`["don't ask again" active this session for risk level ${result.riskLevel} — auto-approved]`);
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 !== "n";
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
- console.log("Cancelled.\n");
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
- console.log(`
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) console.log(`
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
- console.log("Decide with /plan approve or /plan decline.\n");
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
- console.log("Left pending — /plan to review, /plan approve|edit|decline to decide.\n");
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.reviewNotice}` : "";
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
- console.log(`
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
- console.log(`
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
- console.log(`
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
- console.log(`
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
- console.log(`
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
- console.log(`
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
- console.log(`
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
- console.log(`
4666
+ printSafe(`
4546
4667
  Usage: /config set ${key} <value>
4547
4668
  `);
4548
4669
  return;
4549
4670
  }
4550
4671
  if (overriddenKeys.has(key)) {
4551
- console.log(`
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
- console.log(`
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
- console.log(`
4692
+ printSafe(`
4572
4693
  ✗ ${err.message}
4573
4694
  `);
4574
4695
  return;
4575
4696
  }
4576
4697
  await configStore.save(patch);
4577
4698
  await reloadAssistant();
4578
- console.log(`
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
- console.log(`
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
- console.log("\nCancelled — nothing was reset.\n");
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
- console.log(`
4733
+ printSafe(`
4602
4734
  ✓ Reset ${key ?? "all settings"} to default
4603
4735
  `);
4604
4736
  return;
4605
4737
  }
4606
- console.log("\nUsage: /config | /config set <key> <value> | /config reset [key]\n");
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
- console.log(`
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
- console.log(`
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
- console.log("\nUsage: /layers settings | /layers on <id> | /layers off <id> | /layers reset [id]\n");
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
- console.log(`
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
- console.log(pinned ? `
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
- console.log(`
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
- console.log(`
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
- console.log("Exiting.");
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
- console.log(` → ${pick.description}`);
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 in commands || isQuitCommand(message);
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
- console.log("\n[queued — the current turn is still running; this will be taken into account once it finishes]\n");
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-BLR-PRFp.js");
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-CjKpxDxl.js.map
4985
+ //# sourceMappingURL=bin-BYRl4xXM.js.map