@buildaharness/aielia 0.3.8 → 0.3.10

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 (103) hide show
  1. package/README.md +6 -3
  2. package/dist/_memory-core-generated.d.ts +6 -0
  3. package/dist/action-approval-service.d.ts +2 -0
  4. package/dist/ask-draft.d.ts +22 -0
  5. package/dist/assistant-options.d.ts +35 -0
  6. package/dist/assistant-session.d.ts +1 -1
  7. package/dist/assistant.d.ts +11 -0
  8. package/dist/{bin-DETwedpj.js → bin-Cz3FY6Ew.js} +144 -254
  9. package/dist/bin-Cz3FY6Ew.js.map +1 -0
  10. package/dist/claude-cli-llm-client.d.ts +2 -0
  11. package/dist/claude-cli-prompt.d.ts +7 -0
  12. package/dist/cli-config.d.ts +2 -2
  13. package/dist/cli-session.d.ts +2 -1
  14. package/dist/cli.d.ts +2 -0
  15. package/dist/cli.js +1 -1
  16. package/dist/commands.d.ts +32 -0
  17. package/dist/config-keys.d.ts +43 -0
  18. package/dist/doctor-checks.d.ts +1 -2
  19. package/dist/fact-extraction.d.ts +4 -0
  20. package/dist/fact-repository.contract.d.ts +2 -0
  21. package/dist/fact-repository.d.ts +44 -0
  22. package/dist/file-tools-mcp-server.mjs +29 -0
  23. package/dist/goal-graph-store.d.ts +6 -0
  24. package/dist/harness-bridge.d.ts +5 -5
  25. package/dist/html-to-markdown.d.ts +11 -0
  26. package/dist/index.d.ts +98 -3
  27. package/dist/index.js +5003 -150
  28. package/dist/index.js.map +1 -1
  29. package/dist/layer-settings.d.ts +16 -3
  30. package/dist/llm-client-factory.d.ts +13 -0
  31. package/dist/memory-events.d.ts +177 -0
  32. package/dist/memory-service.d.ts +17 -0
  33. package/dist/model-pricing.d.ts +15 -9
  34. package/dist/node-display-names.d.ts +1 -1
  35. package/dist/project-command.d.ts +16 -0
  36. package/dist/{provider-setup-wX2IBw2X.js → provider-setup-DG8iYm_F.js} +14471 -13032
  37. package/dist/provider-setup-DG8iYm_F.js.map +1 -0
  38. package/dist/proxy-health.d.ts +8 -0
  39. package/dist/redact-url.d.ts +2 -0
  40. package/dist/reply-audit.d.ts +2 -0
  41. package/dist/response-service.d.ts +10 -0
  42. package/dist/run-explain.d.ts +58 -0
  43. package/dist/scripted-llm-client.d.ts +1 -1
  44. package/dist/{tui-app-CcxiwZyM.js → tui-app-oWbIUcBW.js} +3 -3
  45. package/dist/{tui-app-CcxiwZyM.js.map → tui-app-oWbIUcBW.js.map} +1 -1
  46. package/dist/turn-abort.d.ts +5 -0
  47. package/dist/turn-cost.d.ts +12 -0
  48. package/dist/turn-presentation.d.ts +86 -0
  49. package/dist/undo-action-command.d.ts +12 -0
  50. package/dist/undo-plan.d.ts +13 -0
  51. package/dist/version.d.ts +1 -1
  52. package/dist/web-fetch-core.d.ts +0 -14
  53. package/dist/writer/article-autosave.d.ts +12 -0
  54. package/dist/writer/article-change-summary.d.ts +28 -0
  55. package/dist/writer/article-claims.d.ts +49 -0
  56. package/dist/writer/article-comments.d.ts +20 -0
  57. package/dist/writer/article-consistency.d.ts +27 -0
  58. package/dist/writer/article-decorations.d.ts +23 -0
  59. package/dist/writer/article-dependencies.d.ts +44 -0
  60. package/dist/writer/article-draft.d.ts +23 -0
  61. package/dist/writer/article-edit-outline.d.ts +16 -0
  62. package/dist/writer/article-edit-suggest.d.ts +30 -0
  63. package/dist/writer/article-folder.d.ts +29 -0
  64. package/dist/writer/article-history.d.ts +73 -0
  65. package/dist/writer/article-ideas.d.ts +34 -0
  66. package/dist/writer/article-issues.d.ts +47 -0
  67. package/dist/writer/article-marks.d.ts +25 -0
  68. package/dist/writer/article-memory-store.d.ts +3 -0
  69. package/dist/writer/article-model.d.ts +1337 -0
  70. package/dist/writer/article-outline.d.ts +43 -0
  71. package/dist/writer/article-resolver.d.ts +55 -0
  72. package/dist/writer/article-revisions.d.ts +30 -0
  73. package/dist/writer/article-rules.d.ts +60 -0
  74. package/dist/writer/article-sections.d.ts +26 -0
  75. package/dist/writer/article-selection.d.ts +26 -0
  76. package/dist/writer/article-service.d.ts +542 -0
  77. package/dist/writer/article-source-links.d.ts +42 -0
  78. package/dist/writer/article-sources.d.ts +82 -0
  79. package/dist/writer/article-style-derive.d.ts +26 -0
  80. package/dist/writer/article-style.d.ts +31 -0
  81. package/dist/writer/article-suggestions.d.ts +33 -0
  82. package/dist/writer/article-support.d.ts +33 -0
  83. package/dist/writer/article-verify.d.ts +42 -0
  84. package/dist/writer/local-folder.d.ts +49 -0
  85. package/dist/writer/model-failure-hint.d.ts +7 -0
  86. package/dist/writer/repo-reader.d.ts +29 -0
  87. package/dist/writer/repo-search.d.ts +33 -0
  88. package/dist/writer/repo-verify-loop.d.ts +61 -0
  89. package/dist/writer/text-tool-calls.d.ts +11 -0
  90. package/dist/writer/work-progress.d.ts +51 -0
  91. package/dist/writer/write-activity.d.ts +61 -0
  92. package/dist/writer/write-models.d.ts +12 -0
  93. package/dist/writer/write-parameters.d.ts +31 -0
  94. package/dist/writer/write-permissions.d.ts +89 -0
  95. package/dist/writer/write-spend.d.ts +16 -0
  96. package/dist/writer/writer-chat-capture.d.ts +25 -0
  97. package/dist/writer/writer-chat-history.d.ts +17 -0
  98. package/dist/writer/writer-chat-state.d.ts +358 -0
  99. package/dist/writer/writer-chat-turn.d.ts +52 -0
  100. package/dist/writer/writer-intent.d.ts +20 -0
  101. package/package.json +3 -3
  102. package/dist/bin-DETwedpj.js.map +0 -1
  103. package/dist/provider-setup-wX2IBw2X.js.map +0 -1
@@ -7,8 +7,8 @@ import { createInterface } from "node:readline";
7
7
  import { rename, stat, realpath, readdir, mkdir, unlink, writeFile, readFile } from "node:fs/promises";
8
8
  import { homedir, tmpdir } from "node:os";
9
9
  import { fileURLToPath, pathToFileURL } from "node:url";
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, B as PersonalAssistant, aR as isGoalGraphSuggestEnabled, bM as stripTerminalControls, 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";
10
+ import { OPENROUTER_DEFAULT_MODEL, OPENAI_DEFAULT_MODEL, ANTHROPIC_DEFAULT_MODEL, InMemoryReminderStore, FileSystemAdapter, FileSystemExperienceStore } from "@buildaharness/runtime";
11
+ import { ap as buildClaudePrompt, cU as stripJsonCodeFence, ct as recallToolEnabled, bT as lexicalOffEnvValue, a as ALREADY_STAGED_ACTION_TOOL, cR as stagedActionInput, az as claudeFailureMessage, ch as parseClaudeCliOutput, cV as stripMcpToolPrefix, c0 as memoryStatusChecks, cd as normalizePlanNodes, X as PROVIDER_SETUP, aA as cleanApiKey, as as checkApiKeyFormat, aV as envOverridesFromProcessEnv, cA as resolveConfig, cP as shouldLaunchTuiApp, t as DEFAULT_TUI_MODE, ag as applyLayerSettings, cN as sanitizeLayerChoices, c_ as testApiKey, d2 as validateConfig, bb as findInvalidNumericSettings, f as ConfigValidationError, by as formatSpendCapStatus, bN as isPlanGraphEnabled, Z as PersonalAssistant, ao as buildAssistantOptions, cW as stripTerminalControls, bJ as isGoalGraphEnabled, cp as planToSnapshot, aE as createLlmClient, aI as createSmtpSender, aH as createResendSender, bg as formatConfigListing, bI as isConfigKey, e as CONFIG_KEYS, x as ENV_VAR_FOR_CONFIG_KEY, ci as parseConfigValue, g as ConfigValueParseError, at as checkProxyHealth, bi as formatDoctorReport, bh as formatCostSummary, ck as parseProjectCommand, bk as formatGoalGraphState, bx as formatSearchResults, bo as formatMemoryEvents, bq as formatMemoryHistory, bt as formatMemoryStatus, bs as formatMemoryPendingOutcome, bn as formatMemoryArchive, cm as parseUndoActionCommand, bB as formatUndoLogListing, cq as planUndo, aL as defaultExportFilename, bA as formatTranscriptMarkdown, au as classifyError, bz as formatStatus, bl as formatHelp, b5 as explainSteps, b4 as explainSources, b2 as explainLayers, bm as formatLayerListing, d5 as withLayerChoice, P as LayerSettingError, bR as layerWarnings, b6 as explainWhy, bP as isQuitCommand, Q as LiveSteeringChannel, aM as defaultMemoryExportFilename, bp as formatMemoryExport, b$ as memoryAuditLogEnabled, bu as formatMemorySummary, ai as approvalLabels, W as NO_TRACE, S as MISSING_PLAN_APPROVAL_ID_REASON, cr as presentTurnResult, bv as formatNextSteps, b3 as explainPlan, c3 as nodeToLayer, L as LAYER_DISPLAY_NAME, c2 as nodeDisplayName, j as DEFAULT_CLARIFICATION_REASON, aS as emptyDraft, aQ as draftToAnswer, cg as orderedOptions, d4 as withCostEstimate, C as CLI_FEATURES, an as braveSearch } from "./provider-setup-DG8iYm_F.js";
12
12
  import { spawn } from "node:child_process";
13
13
  import { createServer, connect } from "node:net";
14
14
  import { createRequire } from "node:module";
@@ -129,7 +129,7 @@ function createNodeFsBackend() {
129
129
  }
130
130
  };
131
131
  }
132
- const CLI_VERSION = "0.3.8";
132
+ const CLI_VERSION = "0.3.10";
133
133
  function seaCacheDir(version, home = homedir()) {
134
134
  return join(home, ".buildaharness", "personal-assistant", "sea-cache", version);
135
135
  }
@@ -166,9 +166,15 @@ function resolveMcpServerPath(seaOverride, home) {
166
166
  return fileURLToPath(new URL(MCP_SERVER_FILE_NAME, import.meta.url));
167
167
  }
168
168
  const EMPTY_MCP_CONFIG = JSON.stringify({ mcpServers: {} });
169
- function invokeClaude(claudePath, args) {
169
+ function killOnAbort(proc, signal) {
170
+ if (!signal) return;
171
+ if (signal.aborted) proc.kill();
172
+ else signal.addEventListener("abort", () => proc.kill(), { once: true });
173
+ }
174
+ function invokeClaude(claudePath, args, signal) {
170
175
  return new Promise((resolvePromise, reject) => {
171
176
  const proc = spawn(claudePath, args, { cwd: tmpdir(), stdio: ["ignore", "pipe", "pipe"] });
177
+ killOnAbort(proc, signal);
172
178
  let stdout = "";
173
179
  let stderr = "";
174
180
  proc.stdout.on("data", (chunk) => {
@@ -180,16 +186,17 @@ function invokeClaude(claudePath, args) {
180
186
  proc.on("error", reject);
181
187
  proc.on("close", (code) => {
182
188
  if (code !== 0) {
183
- reject(new Error(stderr.trim() || `claude exited with code ${code}`));
189
+ reject(new Error(claudeFailureMessage(stdout, stderr, code)));
184
190
  return;
185
191
  }
186
192
  resolvePromise(parseClaudeCliOutput(stdout));
187
193
  });
188
194
  });
189
195
  }
190
- function invokeClaudeStreaming(claudePath, args, onToolStep) {
196
+ function invokeClaudeStreaming(claudePath, args, onToolStep, signal) {
191
197
  return new Promise((resolvePromise, reject) => {
192
198
  const proc = spawn(claudePath, args, { cwd: tmpdir(), stdio: ["ignore", "pipe", "pipe"] });
199
+ killOnAbort(proc, signal);
193
200
  let buffer = "";
194
201
  let stderr = "";
195
202
  let finalResultLine;
@@ -229,7 +236,7 @@ function invokeClaudeStreaming(claudePath, args, onToolStep) {
229
236
  proc.on("error", reject);
230
237
  proc.on("close", (code) => {
231
238
  if (code !== 0) {
232
- reject(new Error(stderr.trim() || `claude exited with code ${code}`));
239
+ reject(new Error(claudeFailureMessage(finalResultLine ?? "", stderr, code)));
233
240
  return;
234
241
  }
235
242
  handleLine(buffer);
@@ -312,6 +319,8 @@ function startToolGateServer(onToolProposal, onToolResult, onToolExecute) {
312
319
  }
313
320
  class ClaudeCliLLMClient {
314
321
  constructor(options = {}) {
322
+ /** Runs its own tool loop inside `claude -p`; it cannot return tool calls to a caller's loop, so those callers use text tool calls (see writer/text-tool-calls.ts). */
323
+ __publicField(this, "nativeToolCalls", false);
315
324
  __publicField(this, "claudePath");
316
325
  __publicField(this, "fileTools");
317
326
  __publicField(this, "remindersFile");
@@ -356,7 +365,7 @@ class ClaudeCliLLMClient {
356
365
  const model = options.model ?? this.model;
357
366
  if (model) args.push("--model", model);
358
367
  args.push("--", prompt);
359
- const { reply, usage, model: resolved } = await invokeClaude(this.claudePath, args);
368
+ const { reply, usage, model: resolved } = await invokeClaude(this.claudePath, args, options.signal);
360
369
  if (resolved) this.resolvedModelId = resolved;
361
370
  if (usage) (_a = options.onUsage) == null ? void 0 : _a.call(options, usage);
362
371
  return reply;
@@ -414,7 +423,9 @@ class ClaudeCliLLMClient {
414
423
  // M3: recall_memory is registered (gated, then served by the parent via onToolExecute) only under AUDIT_RECALL_TOOL.
415
424
  ...recallToolEnabled() ? { ENABLE_RECALL_TOOL: "1" } : {},
416
425
  // Always passed (even empty): the server reads an unset value as the default, every family off.
417
- ASSISTANT_LEXICAL_RESOLVED_OFF: lexicalOffEnvValue()
426
+ ASSISTANT_LEXICAL_RESOLVED_OFF: lexicalOffEnvValue(),
427
+ // Eval harness only: absent in the product, so the env block is unchanged.
428
+ ...process.env.BENCHMARK_TOOL_FAILURE ? { BENCHMARK_TOOL_FAILURE: process.env.BENCHMARK_TOOL_FAILURE } : {}
418
429
  }
419
430
  }
420
431
  }
@@ -443,7 +454,7 @@ class ClaudeCliLLMClient {
443
454
  if (model) args.push("--model", model);
444
455
  args.push("--", prompt);
445
456
  const callStartedAt = Date.now();
446
- const { reply, usage, model: resolved } = await invokeClaudeStreaming(this.claudePath, args, options.onToolStep);
457
+ const { reply, usage, model: resolved } = await invokeClaudeStreaming(this.claudePath, args, options.onToolStep, options.signal);
447
458
  if (resolved) this.resolvedModelId = resolved;
448
459
  if (usage) (_f = options.onUsage) == null ? void 0 : _f.call(options, usage);
449
460
  const staged = await this.findPendingActionStagedSince(workspaceRoot, callStartedAt);
@@ -699,22 +710,6 @@ class NodeConfigStore {
699
710
  }
700
711
  }
701
712
  const DOCTOR_CHECK_TIMEOUT_MS = 3e3;
702
- async function checkProxyHealth(proxyUrl) {
703
- const label = `proxy reachable (${redactUrlCredentials(proxyUrl)}/health)`;
704
- const controller = new AbortController();
705
- const timeout = setTimeout(() => controller.abort(), DOCTOR_CHECK_TIMEOUT_MS);
706
- try {
707
- const res = await fetch(`${proxyUrl}/health`, { signal: controller.signal });
708
- if (!res.ok) return { label, ok: false, detail: `HTTP ${res.status}` };
709
- const body = await res.json().catch(() => void 0);
710
- return (body == null ? void 0 : body.status) === "ok" ? { label, ok: true } : { label, ok: false, detail: "unexpected response body" };
711
- } catch (err) {
712
- const timedOut = err instanceof Error && err.name === "AbortError";
713
- return { label, ok: false, detail: timedOut ? "timed out" : err instanceof Error ? err.message : String(err) };
714
- } finally {
715
- clearTimeout(timeout);
716
- }
717
- }
718
713
  function checkClaudeCli(claudePath) {
719
714
  const label = `claude binary (${claudePath})`;
720
715
  return new Promise((resolvePromise) => {
@@ -3475,81 +3470,45 @@ function buildEmailSender(config) {
3475
3470
  return void 0;
3476
3471
  }
3477
3472
  function buildLlmClient(config, workspaceRoot, remindersFile) {
3478
- switch (config.llmBackend) {
3479
- case "claude-cli":
3480
- return new ClaudeCliLLMClient({
3481
- fileTools: { workspaceRoot },
3482
- remindersFile,
3483
- shellTools: config.enableShell ? { workspaceRoot } : void 0,
3484
- webTools: config.enableWeb ? { braveApiKey: config.braveApiKey } : void 0,
3485
- actionTools: config.enableEmail ? { workspaceRoot } : void 0
3486
- });
3487
- case "anthropic":
3488
- return new AnthropicLLMClient({ apiKey: config.apiKey ?? "" });
3489
- case "openai":
3490
- return new OpenAICompatibleLLMClient({ apiKey: config.apiKey ?? "", baseUrl: OPENAI_BASE_URL, defaultModel: OPENAI_DEFAULT_MODEL });
3491
- case "openrouter":
3492
- return new OpenAICompatibleLLMClient({
3493
- apiKey: config.apiKey ?? "",
3494
- baseUrl: OPENROUTER_BASE_URL,
3495
- defaultModel: OPENROUTER_DEFAULT_MODEL,
3496
- extraHeaders: OPENROUTER_EXTRA_HEADERS
3497
- });
3498
- case "proxy":
3499
- return new LLMClient({ proxyUrl: config.proxyUrl, authToken: config.authToken });
3500
- }
3473
+ return createLlmClient(config, {
3474
+ claudeCli: () => new ClaudeCliLLMClient({
3475
+ fileTools: { workspaceRoot },
3476
+ remindersFile,
3477
+ shellTools: config.enableShell ? { workspaceRoot } : void 0,
3478
+ webTools: config.enableWeb ? { braveApiKey: config.braveApiKey } : void 0,
3479
+ actionTools: config.enableEmail ? { workspaceRoot } : void 0
3480
+ })
3481
+ });
3501
3482
  }
3502
3483
  async function buildAssistant(config, { backend, dataDir, remindersFile }) {
3503
3484
  const workspaceRoot = config.workspaceRoot ?? process.cwd();
3504
3485
  if (backend === defaultBackend) excludeAieliaStateFromGit(workspaceRoot);
3505
3486
  const search = (query) => braveSearch(query, config.braveApiKey);
3506
3487
  const llmClient = buildLlmClient(config, workspaceRoot, remindersFile);
3507
- return PersonalAssistant.create({
3508
- llmClient,
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),
3511
- // config.activeProject (set via /project <name>) overrides the default of "the workspace
3512
- // I'm running in" — see PersonalAssistantOptions.activeProject's doc comment.
3513
- activeProject: config.activeProject || workspaceRoot,
3514
- memoryBudgetChars: config.memoryBudgetChars,
3515
- memoryWriteMode: config.memoryWriteMode,
3516
- // M5: consolidation proposals are staged for /memory consolidate; the CLI has the controls, so it may raise them on /new.
3517
- consolidateOnNewSession: true,
3518
- memory: new FileSystemAdapter({ backend, baseDir: dataDir, namespace: "transcripts" }),
3519
- experienceStore: await FileSystemExperienceStore.create({ backend, baseDir: dataDir, namespace: "experience" }),
3520
- checkpointStore: new FileSystemAdapter({ backend, baseDir: dataDir, namespace: "checkpoints" }),
3521
- reminderStore: new InMemoryReminderStore(new FileSystemAdapter({ backend, baseDir: dataDir, namespace: "reminders" })),
3522
- fileTools: { backend, workspaceRoot },
3523
- webTools: config.enableWeb ? { search } : void 0,
3524
- // executeCommand is the real child_process.spawn-based implementation (shell-executor.ts) —
3525
- // wired in here, not inside assistant.ts, so the browser build never needs node:child_process.
3526
- shellTools: config.enableShell ? { backend, workspaceRoot, timeoutMs: config.shellTimeoutMs, networkAllowlist: config.shellNetworkAllowlist, executeCommand: runApprovedShellCommand } : void 0,
3527
- // The Resend/SMTP transport is wired in here, not inside assistant.ts, so the browser build
3528
- // never needs nodemailer or a mail server — same split as shellTools' executeCommand.
3529
- actionTools: (() => {
3530
- const sendEmail = buildEmailSender(config);
3531
- return sendEmail ? { backend, workspaceRoot, sendEmail } : void 0;
3532
- })(),
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,
3536
- spendCap: config.sessionCostLimitUsd !== void 0 || config.sessionCallLimit !== void 0 ? { sessionCostLimitUsd: config.sessionCostLimitUsd, sessionCallLimit: config.sessionCallLimit } : void 0,
3537
- // Resolved through the shared config seam (cli-config.ts's envOverridesFromProcessEnv reads
3538
- // ASSISTANT_ONE_LOOP into config.oneLoopMode; /config set oneLoopMode persists it) so the CLI,
3539
- // chat-ui, and the Tauri desktop build all decide this the same way. Undefined here means
3540
- // PersonalAssistant falls back to DEFAULT_ONE_LOOP_MODE.
3541
- oneLoopMode: config.oneLoopMode,
3542
- layerPolicyMode: config.layerPolicyMode,
3543
- // R7: turn-end next-step options and DONE-thread suggestions. Undefined config means the
3544
- // package default (enabled); PersonalAssistant itself defaults to disabled, so this is where
3545
- // the front end opts in.
3546
- goalGraphSuggestMode: isGoalGraphSuggestEnabled(config.goalGraphSuggestMode) ? "enabled" : "disabled",
3547
- // P11 of the internal plan — resolved through the same shared config
3548
- // seam as oneLoopMode above (ASSISTANT_PLAN_MODE / `/config set planMode`). Undefined here
3549
- // means PersonalAssistant falls back to DEFAULT_PLAN_MODE ('legacy').
3550
- planMode: config.planMode,
3551
- ambiguityGuardMode: config.ambiguityGuardMode
3552
- });
3488
+ return PersonalAssistant.create(
3489
+ buildAssistantOptions(config, {
3490
+ llmClient,
3491
+ workspaceRoot,
3492
+ features: CLI_FEATURES,
3493
+ memory: new FileSystemAdapter({ backend, baseDir: dataDir, namespace: "transcripts" }),
3494
+ experienceStore: await FileSystemExperienceStore.create({ backend, baseDir: dataDir, namespace: "experience" }),
3495
+ checkpointStore: new FileSystemAdapter({ backend, baseDir: dataDir, namespace: "checkpoints" }),
3496
+ reminderStore: new InMemoryReminderStore(new FileSystemAdapter({ backend, baseDir: dataDir, namespace: "reminders" })),
3497
+ fileTools: { backend, workspaceRoot },
3498
+ webTools: config.enableWeb ? { search } : void 0,
3499
+ // executeCommand is the real child_process.spawn-based implementation (shell-executor.ts) —
3500
+ // wired in here, not inside assistant.ts, so the browser build never needs node:child_process.
3501
+ shellTools: config.enableShell ? { backend, workspaceRoot, timeoutMs: config.shellTimeoutMs, networkAllowlist: config.shellNetworkAllowlist, executeCommand: runApprovedShellCommand } : void 0,
3502
+ // The Resend/SMTP transport is wired in here, not inside assistant.ts, so the browser build
3503
+ // never needs nodemailer or a mail server — same split as shellTools' executeCommand.
3504
+ actionTools: (() => {
3505
+ const sendEmail = buildEmailSender(config);
3506
+ return sendEmail ? { backend, workspaceRoot, sendEmail } : void 0;
3507
+ })(),
3508
+ // Opt-in durable record of tool calls/results and approvals (see activity-log.ts).
3509
+ onDebugLog: activityLogEnabled(process.env) ? createActivityLogger(join(dataDir, "activity-log.jsonl")) : void 0
3510
+ })
3511
+ );
3553
3512
  }
3554
3513
  async function runCli(options = {}) {
3555
3514
  const dataDir = options.dataDir ?? defaultDataDir;
@@ -3651,6 +3610,7 @@ Fix it by setting the missing value (in the config file under ${dataDir}, or via
3651
3610
  rl.prompt();
3652
3611
  let lastTrace;
3653
3612
  let lastSources;
3613
+ let lastAnswerClaim;
3654
3614
  let lastPlanStatus;
3655
3615
  let pendingPlanApproval;
3656
3616
  let lastTurnUsage;
@@ -3663,33 +3623,17 @@ Fix it by setting the missing value (in the config file under ${dataDir}, or via
3663
3623
  { key: "a", label: "Yes, don't ask again this session" },
3664
3624
  { key: "n", label: "No" }
3665
3625
  ];
3666
- function withCostEstimate(usage) {
3667
- if (config.llmBackend === "claude-cli" || usage.costUsd !== void 0) return usage;
3668
- const estimated = estimateCostUsd(config.model ?? ANTHROPIC_DEFAULT_MODEL, usage);
3669
- return estimated !== void 0 ? { ...usage, costUsd: estimated } : usage;
3670
- }
3671
- const PLAN_TASK_STATUS_ICON = {
3672
- PENDING: "○",
3673
- RUNNING: "▶",
3674
- COMPLETE: "✓",
3675
- FAILED: "✗",
3676
- BLOCKED: "✗",
3677
- HUMAN_REQUIRED: "~"
3678
- };
3679
- function printPlan() {
3626
+ function priceUsage(usage) {
3627
+ return withCostEstimate(usage, { backend: config.llmBackend, model: config.model });
3628
+ }
3629
+ function printPlan(goalGraph) {
3680
3630
  if (!lastPlanStatus) {
3681
- printSafe("\nNo active plan for this session.\n");
3631
+ printSafe(`
3632
+ ${explainPlan({ ...lastTurnExplainInput(), goalGraph }).join("\n")}
3633
+ `);
3682
3634
  return;
3683
3635
  }
3684
- const lines = [
3685
- PLAN_LINE_PREFIX,
3686
- `Plan: ${lastPlanStatus.templateName ?? "custom plan"} (${lastPlanStatus.completionPct.toFixed(1)}% complete)`,
3687
- ...lastPlanStatus.tasks.flatMap((task) => [
3688
- ` ${PLAN_TASK_STATUS_ICON[task.status] ?? "?"} [${task.status}] ${task.id} — ${task.description}`,
3689
- ...task.note ? [` not accepted as done: ${task.note}`] : []
3690
- ]),
3691
- `Success criteria: ${lastPlanStatus.successCriteria}`
3692
- ];
3636
+ const lines = [PLAN_LINE_PREFIX, ...explainPlan(lastTurnExplainInput())];
3693
3637
  console.log(stripTerminalControls(`
3694
3638
  ${lines.join("\n")}
3695
3639
  `));
@@ -3704,7 +3648,7 @@ ${lines.join("\n")}
3704
3648
  const result = await assistant.sketchPlan("cli", request);
3705
3649
  console.log(stripTerminalControls(`Aielia> ${result.reply ?? "(no reply)"}
3706
3650
  `));
3707
- if (result.usage) lastTurnUsage = withCostEstimate(result.usage);
3651
+ if (result.usage) lastTurnUsage = priceUsage(result.usage);
3708
3652
  }
3709
3653
  const PLAN_APPROVAL_OPTIONS = [
3710
3654
  { key: "y", label: "Approve" },
@@ -3769,7 +3713,7 @@ ${lines.join("\n")}
3769
3713
  }
3770
3714
  pendingPlanApproval = void 0;
3771
3715
  lastTrace = void 0;
3772
- lastNoTraceReason = `No harness trace — the last turn staged a plan that was ${decision === "decline" ? "declined" : "approved"} before the harness ran.`;
3716
+ lastNoTraceReason = NO_TRACE.planApproval(decision === "decline" ? "declined" : "approved");
3773
3717
  await handleTurn(message, false, void 0, void 0, void 0, id, decision, edits);
3774
3718
  }
3775
3719
  async function loadPlanGraphNodes(live, threadId) {
@@ -3837,86 +3781,38 @@ ${result.lines.join("\n")}
3837
3781
  printPlanApproval(pendingPlanApproval.snapshot);
3838
3782
  return;
3839
3783
  }
3840
- printPlan();
3841
- }
3842
- function verificationHealthLabel({ strength, feasibility }) {
3843
- const confidence = Math.min(strength, feasibility);
3844
- if (confidence >= 0.7) return "High confidence";
3845
- if (confidence >= 0.4) return "Reasonably confident";
3846
- return "Worth double-checking";
3784
+ printPlan(lastPlanStatus ? void 0 : await assistant.getGoalGraphState("cli"));
3847
3785
  }
3848
- function batchBudgetSummaryLine(batchBudget) {
3849
- const found = batchBudget.perItemOutcomes.filter((o) => o.status === "found").length;
3850
- const notFound = batchBudget.perItemOutcomes.filter((o) => o.status === "not_found").length;
3851
- const truncated = batchBudget.perItemOutcomes.filter((o) => o.status === "truncated_while_productive").length;
3852
- return `Batch: ${batchBudget.itemCount} items — ${found} found, ${notFound} not found, ${truncated} truncated (${batchBudget.totalCallsUsed} calls used, projected ~${Math.ceil(batchBudget.projectedTotal)})`;
3786
+ function lastTurnExplainInput() {
3787
+ return {
3788
+ trace: lastTrace,
3789
+ noTraceReason: lastNoTraceReason,
3790
+ answerClaim: lastAnswerClaim,
3791
+ memoryInjection: assistant.getLastMemoryInjection(),
3792
+ sources: lastSources,
3793
+ toolSteps: lastTurnToolSteps,
3794
+ planStatus: lastPlanStatus
3795
+ };
3853
3796
  }
3854
3797
  function printWhy() {
3855
- const memoryLine = formatMemoryInjection(assistant.getLastMemoryInjection());
3856
- if (!lastTrace) {
3857
- printSafe(`
3858
- ${lastNoTraceReason ?? "No harness trace for the last turn (nothing to explain yet, or it took the fast path)."}
3859
- ${memoryLine}
3860
- `);
3861
- return;
3862
- }
3863
3798
  printSafe(`
3864
- ${verificationHealthLabel(lastTrace.verificationHealth)}`);
3865
- const chain = buildWhyChain(lastTrace.layerActivity);
3866
- if (chain.length > 0) {
3867
- printSafe(" " + chain.map((item) => `${LAYER_SHORT_CODE[item.layer]} (${item.reason})`).join(" > "));
3868
- }
3869
- if (lastTrace.batchBudget) {
3870
- printSafe(" " + batchBudgetSummaryLine(lastTrace.batchBudget));
3871
- }
3872
- printSafe(memoryLine);
3873
- printSafe("");
3799
+ ${explainWhy(lastTurnExplainInput()).join("\n")}
3800
+ `);
3874
3801
  }
3875
3802
  function printLayers() {
3876
- if (!lastTrace) {
3877
- printSafe(`
3878
- ${lastNoTraceReason ?? "No harness trace for the last turn (nothing to explain yet, or it took the fast path)."}
3803
+ printSafe(`
3804
+ ${explainLayers(lastTurnExplainInput()).join("\n")}
3879
3805
  `);
3880
- return;
3881
- }
3882
- printSafe("");
3883
- const byLayer = new Map(lastTrace.layerActivity.map((e) => [e.layer, e]));
3884
- for (const layer of LAYER_ORDER) {
3885
- const e = byLayer.get(layer);
3886
- const mark = (e == null ? void 0 : e.fired) ? "✓" : "·";
3887
- const reason = (e == null ? void 0 : e.reason) ?? "not evaluated this turn";
3888
- printSafe(` [${mark}] ${LAYER_DISPLAY_NAME[layer].padEnd(22)} ${reason}`);
3889
- }
3890
- if (lastTrace.batchBudget) {
3891
- printSafe("");
3892
- printSafe(` ${batchBudgetSummaryLine(lastTrace.batchBudget)}`);
3893
- const STATUS_MARK = {
3894
- found: "✓",
3895
- not_found: "✗",
3896
- truncated_while_productive: "~"
3897
- };
3898
- for (const outcome of lastTrace.batchBudget.perItemOutcomes) {
3899
- printSafe(` [${STATUS_MARK[outcome.status]}] ${outcome.item.padEnd(30)} ${outcome.status} (${outcome.callsUsed} calls)`);
3900
- }
3901
- }
3902
- printSafe("");
3903
3806
  }
3904
- const SOURCE_TOOL_LABEL = {
3905
- read_file: "Read",
3906
- list_directory: "Listed",
3907
- web_search: "Searched",
3908
- fetch_url: "Fetched"
3909
- };
3910
3807
  function printSources() {
3911
- if (!lastSources || lastSources.length === 0) {
3912
- printSafe("\nNo sources for the last turn (it used no file/web tool calls).\n");
3913
- return;
3914
- }
3915
- printSafe("");
3916
- for (const source of lastSources) {
3917
- printSafe(` - ${SOURCE_TOOL_LABEL[source.tool]} ${source.path}`);
3918
- }
3919
- printSafe("");
3808
+ printSafe(`
3809
+ ${explainSources(lastTurnExplainInput()).join("\n")}
3810
+ `);
3811
+ }
3812
+ function printSteps() {
3813
+ printSafe(`
3814
+ ${explainSteps(lastTurnExplainInput()).join("\n")}
3815
+ `);
3920
3816
  }
3921
3817
  function printHelp() {
3922
3818
  printSafe(`
@@ -3991,12 +3887,12 @@ ${formatStatus({ config, overriddenKeys, transcriptLength: transcript.length, pl
3991
3887
  }
3992
3888
  }
3993
3889
  async function handleUndo() {
3994
- const transcriptBefore = await assistant.getTranscript("cli");
3995
- if (transcriptBefore.length === 0) {
3890
+ const plan = planUndo(await assistant.getTranscript("cli"));
3891
+ if (plan.nothingToUndo) {
3996
3892
  printSafe("\nNothing to undo.\n");
3997
3893
  return;
3998
3894
  }
3999
- const wasPendingApproval = transcriptBefore[transcriptBefore.length - 1].role !== "assistant";
3895
+ const { wasPendingApproval } = plan;
4000
3896
  const undoneReminders = lastTurnToolSteps.filter((s) => s.tool === "create_reminder");
4001
3897
  const result = await assistant.undoLastTurn("cli");
4002
3898
  if (!result.undone) {
@@ -4015,14 +3911,15 @@ ${formatStatus({ config, overriddenKeys, transcriptLength: transcript.length, pl
4015
3911
  );
4016
3912
  }
4017
3913
  async function handleUndoAction(args) {
4018
- if (args.length === 0) {
3914
+ const command = parseUndoActionCommand(args);
3915
+ if (command.kind === "list") {
4019
3916
  const entries = await assistant.listUndoLogEntries();
4020
3917
  printSafe(`
4021
3918
  ${formatUndoLogListing(entries)}
4022
3919
  `);
4023
3920
  return;
4024
3921
  }
4025
- const staged = await assistant.stageUndoAction(args[0]);
3922
+ const staged = await assistant.stageUndoAction(command.id);
4026
3923
  if (staged.status === "error") {
4027
3924
  printSafe(`
4028
3925
  ✗ ${staged.message}
@@ -4033,7 +3930,7 @@ ${formatUndoLogListing(entries)}
4033
3930
  [needs approval — revert] ${staged.reason}`));
4034
3931
  const confirmed = await askYesNo("Apply this revert? (y/N) ");
4035
3932
  lastTrace = void 0;
4036
- lastNoTraceReason = `No harness trace — the last turn was a staged revert that was ${confirmed ? "approved" : "declined"} before the harness ran.`;
3933
+ lastNoTraceReason = NO_TRACE.stagedAction("revert", confirmed ? "approved" : "declined");
4037
3934
  await handleTurn("", confirmed, staged.pendingActionId);
4038
3935
  }
4039
3936
  async function printMemory() {
@@ -4109,6 +4006,12 @@ Forgot ${removed} session digest${removed === 1 ? "" : "s"}.
4109
4006
  await handleMemoryForget(args.slice(1));
4110
4007
  return;
4111
4008
  }
4009
+ if (args[0] === "history" && args[1] === "events") {
4010
+ printSafe(`
4011
+ ${formatMemoryEvents(await assistant.memoryEvents(50, args[2] ? { fact: args[2] } : {}))}
4012
+ `);
4013
+ return;
4014
+ }
4112
4015
  if (args[0] === "history") {
4113
4016
  printSafe(`
4114
4017
  ${formatMemoryHistory(await assistant.memoryHistory())}
@@ -4252,24 +4155,8 @@ ${formatDoctorReport(checks)}
4252
4155
  }
4253
4156
  lastTurnToolSteps.push(step);
4254
4157
  }
4255
- function emptyClarificationDraft() {
4256
- return { selectedLabels: [], editText: "", freeText: "" };
4257
- }
4258
- function orderedAskOptions(question) {
4259
- const options2 = question.options ?? [];
4260
- return options2.map((option, index) => ({ option, index })).sort((a, b) => Number(b.option.recommended ?? false) - Number(a.option.recommended ?? false) || a.index - b.index).map(({ option }) => option);
4261
- }
4262
- function clarificationDraftToAnswer(questionId, draft) {
4263
- if (draft.selectedLabels.length > 0) {
4264
- return draft.editText.trim() ? { questionId, kind: "selected_with_edit", selectedLabels: draft.selectedLabels, editText: draft.editText.trim() } : { questionId, kind: "selected", selectedLabels: draft.selectedLabels };
4265
- }
4266
- if (draft.freeText.trim()) {
4267
- return { questionId, kind: "free_text", freeText: draft.freeText.trim() };
4268
- }
4269
- return null;
4270
- }
4271
4158
  function printClarificationQuestion(question, draft, index, total) {
4272
- const options2 = orderedAskOptions(question);
4159
+ const options2 = orderedOptions(question);
4273
4160
  const allowFreeText = question.allowFreeText !== false;
4274
4161
  printSafe("");
4275
4162
  if (question.header) console.log(stripTerminalControls(`[${question.header}]`));
@@ -4299,9 +4186,10 @@ ${formatDoctorReport(checks)}
4299
4186
  const pendingClarificationId = result.pendingClarificationId;
4300
4187
  if (questions.length === 0 || !pendingClarificationId) {
4301
4188
  lastTrace = void 0;
4302
- lastNoTraceReason = `No harness trace — the last turn needed clarification (${result.reason ?? "no further detail"}) but had no answerable questions.`;
4189
+ const unanswerable = presentTurnResult(result);
4190
+ lastNoTraceReason = unanswerable.noTraceReason;
4303
4191
  console.log(stripTerminalControls(`
4304
- [needs clarification] ${result.reason ?? "This request needs clarification."}
4192
+ [needs clarification] ${unanswerable.kind === "escalation" ? unanswerable.reason : DEFAULT_CLARIFICATION_REASON}
4305
4193
  `));
4306
4194
  return;
4307
4195
  }
@@ -4318,7 +4206,7 @@ ${formatDoctorReport(checks)}
4318
4206
  }
4319
4207
  printSafe(`
4320
4208
  [needs clarification] ${questions.length} question${questions.length === 1 ? "" : "s"} to answer.`);
4321
- const drafts = new Map(questions.map((q2) => [q2.id, emptyClarificationDraft()]));
4209
+ const drafts = new Map(questions.map((q2) => [q2.id, emptyDraft()]));
4322
4210
  let index = 0;
4323
4211
  while (true) {
4324
4212
  const question = questions[index];
@@ -4333,7 +4221,7 @@ ${formatDoctorReport(checks)}
4333
4221
  return;
4334
4222
  }
4335
4223
  if (lower === "s" || lower === "submit") {
4336
- const answers = questions.map((q2) => clarificationDraftToAnswer(q2.id, drafts.get(q2.id)));
4224
+ const answers = questions.map((q2) => draftToAnswer(q2.id, drafts.get(q2.id)));
4337
4225
  const missing = questions.filter((q2, i) => answers[i] === null);
4338
4226
  if (missing.length > 0) {
4339
4227
  printSafe(`
@@ -4368,7 +4256,7 @@ Still need an answer for: ${missing.map((q2) => q2.question).join("; ")}
4368
4256
  }
4369
4257
  const editMatch = /^e(\d+)$/i.exec(token);
4370
4258
  if (editMatch) {
4371
- const options2 = orderedAskOptions(question);
4259
+ const options2 = orderedOptions(question);
4372
4260
  const option = options2[Number(editMatch[1]) - 1];
4373
4261
  if (!option) {
4374
4262
  printSafe(`
@@ -4388,7 +4276,7 @@ Select option ${editMatch[1]} first, then add a note with e${editMatch[1]}.
4388
4276
  }
4389
4277
  const toggleMatch = /^t(\d+)$/i.exec(token) ?? /^(\d+)$/.exec(token);
4390
4278
  if (toggleMatch) {
4391
- const options2 = orderedAskOptions(question);
4279
+ const options2 = orderedOptions(question);
4392
4280
  const option = options2[Number(toggleMatch[1]) - 1];
4393
4281
  if (!option) {
4394
4282
  printSafe(`
@@ -4459,8 +4347,7 @@ Unrecognized input "${token}".
4459
4347
  });
4460
4348
  if (!streamedAnyTokens) clearProgress();
4461
4349
  if (result.status === "needs_approval" && result.pendingActionId) {
4462
- const kindLabel = result.pendingActionKind === "shell" ? "shell command" : result.pendingActionKind === "email" ? "send email" : result.pendingActionKind === "batch" ? "batch research" : "write";
4463
- const promptText = result.pendingActionKind === "shell" ? "Run this command?" : result.pendingActionKind === "email" ? "Send this email?" : result.pendingActionKind === "batch" ? "Continue?" : "Apply this write?";
4350
+ const { kindLabel, promptText } = approvalLabels(result.pendingActionKind);
4464
4351
  console.log(stripTerminalControls(`
4465
4352
  [needs approval — ${kindLabel}] ${result.reason}`));
4466
4353
  let confirmed;
@@ -4473,7 +4360,7 @@ Unrecognized input "${token}".
4473
4360
  if (decision === "a" && result.pendingActionKind) rememberedActionKinds.add(result.pendingActionKind);
4474
4361
  }
4475
4362
  lastTrace = void 0;
4476
- lastNoTraceReason = `No harness trace — the last turn was a staged ${kindLabel} that was ${confirmed ? "approved" : "declined"} before the harness ran.`;
4363
+ lastNoTraceReason = NO_TRACE.stagedAction(kindLabel, confirmed ? "approved" : "declined");
4477
4364
  await handleTurn(message, confirmed, result.pendingActionId);
4478
4365
  return;
4479
4366
  }
@@ -4494,7 +4381,7 @@ Unrecognized input "${token}".
4494
4381
  await handleTurn(message, true);
4495
4382
  } else {
4496
4383
  lastTrace = void 0;
4497
- lastNoTraceReason = "No harness trace — the last turn was blocked on an approval gate and declined before the harness ran.";
4384
+ lastNoTraceReason = NO_TRACE.messageApproval("declined");
4498
4385
  await assistant.recordDeclinedRequest("cli", message, result.reason ?? "This request needed approval.");
4499
4386
  printSafe("Cancelled.\n");
4500
4387
  }
@@ -4507,7 +4394,7 @@ Unrecognized input "${token}".
4507
4394
  if (result.status === "needs_plan_approval") {
4508
4395
  if (!result.planApprovalId) {
4509
4396
  printSafe(`
4510
- [needs plan approval] ${result.reason ?? "A plan is awaiting approval, but its approval id is missing."}
4397
+ [needs plan approval] ${result.reason ?? MISSING_PLAN_APPROVAL_ID_REASON}
4511
4398
  `);
4512
4399
  return;
4513
4400
  }
@@ -4518,7 +4405,7 @@ ${result.reason}`);
4518
4405
  if (snapshot) printPlanApproval(snapshot);
4519
4406
  pendingPlanApproval = { id, message, snapshot };
4520
4407
  lastTrace = void 0;
4521
- lastNoTraceReason = "No harness trace — the last turn staged a plan for approval (pending /plan approve|decline|edit).";
4408
+ lastNoTraceReason = NO_TRACE.planApproval(void 0, " (pending /plan approve|decline|edit)");
4522
4409
  if (!snapshot) {
4523
4410
  printSafe("Decide with /plan approve or /plan decline.\n");
4524
4411
  return;
@@ -4541,44 +4428,48 @@ ${result.reason}`);
4541
4428
  } else {
4542
4429
  decision = "decline";
4543
4430
  }
4544
- lastNoTraceReason = `No harness trace — the last turn staged a plan that was ${decision === "decline" ? "declined" : "approved"} before the harness ran.`;
4431
+ lastNoTraceReason = NO_TRACE.planApproval(decision === "decline" ? "declined" : "approved");
4545
4432
  await handleTurn(message, false, void 0, void 0, void 0, id, decision, edits);
4546
4433
  return;
4547
4434
  }
4548
4435
  if (result.status === "escalated") {
4549
4436
  lastTrace = void 0;
4550
- lastNoTraceReason = `No harness trace — the last turn escalated (${result.reason}) before completing.`;
4437
+ const escalation = presentTurnResult(result);
4438
+ lastNoTraceReason = escalation.noTraceReason;
4551
4439
  console.log(stripTerminalControls(`
4552
- [escalated] ${result.reason}
4440
+ [escalated] ${escalation.kind === "escalation" ? escalation.reason : result.reason}
4553
4441
  `));
4554
4442
  return;
4555
4443
  }
4556
4444
  if (!pendingActionId) {
4557
4445
  lastTrace = result.harnessSkipped ? void 0 : result.trace;
4558
- lastNoTraceReason = result.harnessSkipped ? "No harness trace — the last turn was a simple, self-contained question answered directly without activating the harness (fast path)." : void 0;
4446
+ lastAnswerClaim = result.answerClaim;
4447
+ lastNoTraceReason = result.harnessSkipped ? NO_TRACE.fastPath : void 0;
4559
4448
  }
4560
4449
  lastSources = result.sources;
4561
4450
  if (!pendingActionId && (!result.harnessSkipped || result.planStatus)) lastPlanStatus = result.planStatus;
4562
- lastTurnUsage = result.usage ? withCostEstimate(result.usage) : void 0;
4451
+ lastTurnUsage = result.usage ? priceUsage(result.usage) : void 0;
4452
+ const shown = presentTurnResult(result);
4453
+ const hints = shown.kind === "reply" ? shown.hints : {};
4563
4454
  const riskSuffix = result.riskLevel && result.riskLevel !== "LOW" ? ` [risk: ${result.riskLevel}]` : "";
4564
- const sourcesHint = result.sources && result.sources.length > 0 ? ` (${result.sources.length} source${result.sources.length > 1 ? "s" : ""} — /sources)` : "";
4565
- const planHint = result.planStatus ? ` (plan: ${result.planStatus.completionPct.toFixed(0)}% — /plan)` : "";
4566
- const contradictionNotice = result.contradictionNotice ? `
4455
+ const sourcesHint = hints.sourcesCount ? ` (${hints.sourcesCount} source${hints.sourcesCount > 1 ? "s" : ""} — /sources)` : "";
4456
+ const planHint = hints.planCompletionPct !== void 0 ? ` (plan: ${hints.planCompletionPct.toFixed(0)}% — /plan)` : "";
4457
+ const contradictionNotice = hints.contradictionNotice ? `
4567
4458
 
4568
- ${result.contradictionNotice}` : "";
4569
- const reviewNotice = (result.reviewNotice ? `
4459
+ ${hints.contradictionNotice}` : "";
4460
+ const reviewNotice = (hints.reviewNotice ? `
4570
4461
 
4571
- ${result.reviewNotice}` : "") + (result.auditNotice ? `
4462
+ ${hints.reviewNotice}` : "") + (hints.auditNotice ? `
4572
4463
 
4573
- ${result.auditNotice}` : "");
4574
- lastNextSteps = result.nextSteps && result.nextSteps.length > 0 ? result.nextSteps : void 0;
4464
+ ${hints.auditNotice}` : "");
4465
+ lastNextSteps = hints.nextSteps;
4575
4466
  const nextStepsBlock = lastNextSteps ? `
4576
4467
 
4577
4468
  ${formatNextSteps(lastNextSteps)}` : "";
4578
4469
  if (streamedAnyTokens) {
4579
- const pausedNoteText = result.pausedNote ? `
4470
+ const pausedNoteText = hints.pausedNote ? `
4580
4471
 
4581
- ${result.pausedNote}` : "";
4472
+ ${hints.pausedNote}` : "";
4582
4473
  process.stdout.write(stripTerminalControls(`${pausedNoteText}${riskSuffix}${sourcesHint}${planHint}${contradictionNotice}${reviewNotice}${nextStepsBlock}
4583
4474
 
4584
4475
  `));
@@ -4788,6 +4679,8 @@ Usage: /layers ${sub} <id>
4788
4679
  ✗ Saved, but "${id}" is pinned by its AUDIT_* env flag, so the saved choice has no effect until that is unset.
4789
4680
  ` : `
4790
4681
  ✓ Layers updated (took effect immediately, no restart needed)
4682
+ `);
4683
+ for (const warning of layerWarnings(sanitizeLayerChoices(config.layers), layerPins, process.env)) printSafe(`⚠ ${warning}
4791
4684
  `);
4792
4685
  }
4793
4686
  async function handleModel(args) {
@@ -4800,17 +4693,12 @@ ${config.model ?? "(using each backend's default)"}
4800
4693
  await handleConfigCommand(["set", "model", args.join(" ")]);
4801
4694
  }
4802
4695
  async function handleProject(args) {
4803
- if (args.length === 0) {
4804
- printSafe(`
4805
- ${assistant.getActiveProject() || "(none)"}
4696
+ const command = parseProjectCommand(args, assistant.getActiveProject());
4697
+ if (command.kind === "show") printSafe(`
4698
+ ${command.message}
4806
4699
  `);
4807
- return;
4808
- }
4809
- if (args[0] === "clear") {
4810
- await handleConfigCommand(["reset", "activeProject"]);
4811
- return;
4812
- }
4813
- await handleConfigCommand(["set", "activeProject", args.join(" ")]);
4700
+ else if (command.kind === "clear") await handleConfigCommand(["reset", "activeProject"]);
4701
+ else await handleConfigCommand(["set", "activeProject", command.project]);
4814
4702
  }
4815
4703
  let turnInProgress = false;
4816
4704
  let lastNextSteps;
@@ -4820,6 +4708,7 @@ ${assistant.getActiveProject() || "(none)"}
4820
4708
  // Bare /layers is the last-turn fired/skipped view; its subcommands (settings/on/off/reset) edit the on/off choices.
4821
4709
  "/layers": (args) => args.length === 0 ? printLayers() : handleLayers(args),
4822
4710
  "/sources": () => printSources(),
4711
+ "/steps": () => printSteps(),
4823
4712
  "/plan": (args) => handlePlan(args),
4824
4713
  "/help": () => printHelp(),
4825
4714
  "/clear": () => handleClear(),
@@ -4909,6 +4798,7 @@ ${assistant.getActiveProject() || "(none)"}
4909
4798
  });
4910
4799
  rl.resume();
4911
4800
  return {
4801
+ commandNames: Object.keys(commands),
4912
4802
  dispatchLine: async (line) => {
4913
4803
  const message = line.trim();
4914
4804
  if (!message) return;
@@ -4972,7 +4862,7 @@ async function mainInner(argv) {
4972
4862
  });
4973
4863
  }
4974
4864
  if (shouldLaunchTuiApp(config.tuiMode ?? DEFAULT_TUI_MODE, Boolean(process.stdout.isTTY), Boolean(process.stdin.isTTY))) {
4975
- const { runTuiApp } = await import("./tui-app-CcxiwZyM.js");
4865
+ const { runTuiApp } = await import("./tui-app-oWbIUcBW.js");
4976
4866
  await runTuiApp();
4977
4867
  return;
4978
4868
  }
@@ -4999,4 +4889,4 @@ export {
4999
4889
  sanitizeLabel as s,
5000
4890
  toLines as t
5001
4891
  };
5002
- //# sourceMappingURL=bin-DETwedpj.js.map
4892
+ //# sourceMappingURL=bin-Cz3FY6Ew.js.map