@buildaharness/aielia 0.3.7 → 0.3.9

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 (41) hide show
  1. package/README.md +6 -3
  2. package/dist/action-approval-service.d.ts +2 -0
  3. package/dist/ask-draft.d.ts +22 -0
  4. package/dist/assistant-options.d.ts +35 -0
  5. package/dist/assistant-session.d.ts +1 -1
  6. package/dist/{bin-C_hgDdSf.js → bin-CCwDFxDe.js} +145 -258
  7. package/dist/bin-CCwDFxDe.js.map +1 -0
  8. package/dist/cli-config.d.ts +2 -2
  9. package/dist/cli-session.d.ts +2 -1
  10. package/dist/cli.d.ts +2 -0
  11. package/dist/cli.js +1 -1
  12. package/dist/commands.d.ts +32 -0
  13. package/dist/config-keys.d.ts +43 -0
  14. package/dist/doctor-checks.d.ts +1 -2
  15. package/dist/fact-extraction.d.ts +4 -0
  16. package/dist/goal-graph-store.d.ts +6 -0
  17. package/dist/index.d.ts +17 -2
  18. package/dist/index.js +199 -148
  19. package/dist/index.js.map +1 -1
  20. package/dist/llm-client-factory.d.ts +13 -0
  21. package/dist/memory-service.d.ts +2 -0
  22. package/dist/model-pricing.d.ts +12 -9
  23. package/dist/node-display-names.d.ts +1 -1
  24. package/dist/project-command.d.ts +16 -0
  25. package/dist/{provider-setup-wX2IBw2X.js → provider-setup-Cy9dwqrd.js} +13504 -13034
  26. package/dist/provider-setup-Cy9dwqrd.js.map +1 -0
  27. package/dist/proxy-health.d.ts +8 -0
  28. package/dist/redact-url.d.ts +2 -0
  29. package/dist/reply-audit.d.ts +2 -0
  30. package/dist/response-service.d.ts +10 -0
  31. package/dist/run-explain.d.ts +58 -0
  32. package/dist/{tui-app-C0J6HRzv.js → tui-app-Dt72u8vZ.js} +11 -3
  33. package/dist/{tui-app-C0J6HRzv.js.map → tui-app-Dt72u8vZ.js.map} +1 -1
  34. package/dist/turn-cost.d.ts +12 -0
  35. package/dist/turn-presentation.d.ts +86 -0
  36. package/dist/undo-action-command.d.ts +12 -0
  37. package/dist/undo-plan.d.ts +13 -0
  38. package/dist/version.d.ts +1 -1
  39. package/package.json +1 -1
  40. package/dist/bin-C_hgDdSf.js.map +0 -1
  41. 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, bM as stripTerminalControls, B as PersonalAssistant, aR as isGoalGraphSuggestEnabled, aQ as isGoalGraphEnabled, bn as planToSnapshot, X as braveSearch, a8 as createSmtpSender, a7 as createResendSender, aW as isQuitCommand, y as LiveSteeringChannel, aE as formatNextSteps, $ as classifyError, b8 as nodeToLayer, L as LAYER_DISPLAY_NAME, b7 as nodeDisplayName, ai as estimateCostUsd, aq as formatConfigListing, aP as isConfigKey, C as CONFIG_KEYS, E as ENV_VAR_FOR_CONFIG_KEY, bj as parseConfigValue, d as ConfigValueParseError, as as formatDoctorReport, ar as formatCostSummary, au as formatGoalGraphState, aG as formatSearchResults, az as formatMemoryHistory, aC as formatMemoryStatus, aB as formatMemoryPendingOutcome, ax as formatMemoryArchive, aK as formatUndoLogListing, ab as defaultExportFilename, aJ as formatTranscriptMarkdown, aI as formatStatus, av as formatHelp, r as LAYER_ORDER, aw as formatLayerListing, bU as withLayerChoice, x as LayerSettingError, aA as formatMemoryInjection, Z as buildWhyChain, t as LAYER_SHORT_CODE, ac as defaultMemoryExportFilename, ay as formatMemoryExport, b4 as memoryAuditLogEnabled, aD as formatMemorySummary } from "./provider-setup-wX2IBw2X.js";
10
+ import { OPENROUTER_DEFAULT_MODEL, OPENAI_DEFAULT_MODEL, ANTHROPIC_DEFAULT_MODEL, InMemoryReminderStore, FileSystemAdapter, FileSystemExperienceStore } from "@buildaharness/runtime";
11
+ import { af as buildClaudePrompt, co as stripJsonCodeFence, c2 as recallToolEnabled, bw as lexicalOffEnvValue, a as ALREADY_STAGED_ACTION_TOOL, cn as stagedActionInput, bT as parseClaudeCliOutput, cp as stripMcpToolPrefix, bE as memoryStatusChecks, bR as normalizePlanNodes, V as PROVIDER_SETUP, ao as cleanApiKey, ah as checkApiKeyFormat, aG as envOverridesFromProcessEnv, c6 as resolveConfig, cl as shouldLaunchTuiApp, t as DEFAULT_TUI_MODE, a8 as applyLayerSettings, cj as sanitizeLayerChoices, cu as testApiKey, cx as validateConfig, aW as findInvalidNumericSettings, f as ConfigValidationError, be as formatSpendCapStatus, br as isPlanGraphEnabled, X as PersonalAssistant, ae as buildAssistantOptions, cq as stripTerminalControls, bn as isGoalGraphEnabled, b$ as planToSnapshot, ar as createLlmClient, au as createSmtpSender, at as createResendSender, aZ as formatConfigListing, bm as isConfigKey, e as CONFIG_KEYS, x as ENV_VAR_FOR_CONFIG_KEY, bU as parseConfigValue, g as ConfigValueParseError, ai as checkProxyHealth, a$ as formatDoctorReport, a_ as formatCostSummary, bW as parseProjectCommand, b1 as formatGoalGraphState, bd as formatSearchResults, b6 as formatMemoryHistory, b9 as formatMemoryStatus, b8 as formatMemoryPendingOutcome, b4 as formatMemoryArchive, bY as parseUndoActionCommand, bh as formatUndoLogListing, c0 as planUndo, ax as defaultExportFilename, bg as formatTranscriptMarkdown, aj as classifyError, bf as formatStatus, b2 as formatHelp, aS as explainSteps, aR as explainSources, aP as explainLayers, b3 as formatLayerListing, cA as withLayerChoice, O as LayerSettingError, aT as explainWhy, bt as isQuitCommand, P as LiveSteeringChannel, ay as defaultMemoryExportFilename, b5 as formatMemoryExport, bD as memoryAuditLogEnabled, ba as formatMemorySummary, aa as approvalLabels, U as NO_TRACE, R as MISSING_PLAN_APPROVAL_ID_REASON, c1 as presentTurnResult, bb as formatNextSteps, aQ as explainPlan, bH as nodeToLayer, L as LAYER_DISPLAY_NAME, bG as nodeDisplayName, j as DEFAULT_CLARIFICATION_REASON, aE as emptyDraft, aC as draftToAnswer, bS as orderedOptions, cz as withCostEstimate, C as CLI_FEATURES, ad as braveSearch } from "./provider-setup-Cy9dwqrd.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.7";
132
+ const CLI_VERSION = "0.3.9";
133
133
  function seaCacheDir(version, home = homedir()) {
134
134
  return join(home, ".buildaharness", "personal-assistant", "sea-cache", version);
135
135
  }
@@ -699,22 +699,6 @@ class NodeConfigStore {
699
699
  }
700
700
  }
701
701
  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
702
  function checkClaudeCli(claudePath) {
719
703
  const label = `claude binary (${claudePath})`;
720
704
  return new Promise((resolvePromise) => {
@@ -3475,84 +3459,47 @@ function buildEmailSender(config) {
3475
3459
  return void 0;
3476
3460
  }
3477
3461
  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
- }
3462
+ return createLlmClient(config, {
3463
+ claudeCli: () => new ClaudeCliLLMClient({
3464
+ fileTools: { workspaceRoot },
3465
+ remindersFile,
3466
+ shellTools: config.enableShell ? { workspaceRoot } : void 0,
3467
+ webTools: config.enableWeb ? { braveApiKey: config.braveApiKey } : void 0,
3468
+ actionTools: config.enableEmail ? { workspaceRoot } : void 0
3469
+ })
3470
+ });
3501
3471
  }
3502
3472
  async function buildAssistant(config, { backend, dataDir, remindersFile }) {
3503
3473
  const workspaceRoot = config.workspaceRoot ?? process.cwd();
3504
3474
  if (backend === defaultBackend) excludeAieliaStateFromGit(workspaceRoot);
3505
3475
  const search = (query) => braveSearch(query, config.braveApiKey);
3506
3476
  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
- });
3477
+ return PersonalAssistant.create(
3478
+ buildAssistantOptions(config, {
3479
+ llmClient,
3480
+ workspaceRoot,
3481
+ features: CLI_FEATURES,
3482
+ memory: new FileSystemAdapter({ backend, baseDir: dataDir, namespace: "transcripts" }),
3483
+ experienceStore: await FileSystemExperienceStore.create({ backend, baseDir: dataDir, namespace: "experience" }),
3484
+ checkpointStore: new FileSystemAdapter({ backend, baseDir: dataDir, namespace: "checkpoints" }),
3485
+ reminderStore: new InMemoryReminderStore(new FileSystemAdapter({ backend, baseDir: dataDir, namespace: "reminders" })),
3486
+ fileTools: { backend, workspaceRoot },
3487
+ webTools: config.enableWeb ? { search } : void 0,
3488
+ // executeCommand is the real child_process.spawn-based implementation (shell-executor.ts) —
3489
+ // wired in here, not inside assistant.ts, so the browser build never needs node:child_process.
3490
+ shellTools: config.enableShell ? { backend, workspaceRoot, timeoutMs: config.shellTimeoutMs, networkAllowlist: config.shellNetworkAllowlist, executeCommand: runApprovedShellCommand } : void 0,
3491
+ // The Resend/SMTP transport is wired in here, not inside assistant.ts, so the browser build
3492
+ // never needs nodemailer or a mail server — same split as shellTools' executeCommand.
3493
+ actionTools: (() => {
3494
+ const sendEmail = buildEmailSender(config);
3495
+ return sendEmail ? { backend, workspaceRoot, sendEmail } : void 0;
3496
+ })(),
3497
+ // Opt-in durable record of tool calls/results and approvals (see activity-log.ts).
3498
+ onDebugLog: activityLogEnabled(process.env) ? createActivityLogger(join(dataDir, "activity-log.jsonl")) : void 0
3499
+ })
3500
+ );
3553
3501
  }
3554
3502
  async function runCli(options = {}) {
3555
- printSafe("Aielia is alpha software — expect rough edges and breaking changes. It uses AI models and can make mistakes; verify anything important.\n");
3556
3503
  const dataDir = options.dataDir ?? defaultDataDir;
3557
3504
  const configStore = options.configStore ?? defaultConfigStore;
3558
3505
  const backend = options.backend ?? defaultBackend;
@@ -3620,25 +3567,39 @@ Fix it by setting the missing value (in the config file under ${dataDir}, or via
3620
3567
  openai: OPENAI_DEFAULT_MODEL,
3621
3568
  openrouter: OPENROUTER_DEFAULT_MODEL
3622
3569
  };
3623
- printSafe(`backend: ${config.llmBackend} (${config.model ?? backendDisplayModel[config.llmBackend]})`);
3570
+ const backendLine = `${config.llmBackend} · ${config.model ?? backendDisplayModel[config.llmBackend]}`;
3624
3571
  const enabledCapabilities = [];
3625
3572
  if (config.enableWeb) enabledCapabilities.push("web search/fetch (brave)");
3626
3573
  if (config.enableShell) enabledCapabilities.push(`shell commands (${config.dangerouslySkipPermissions ? "NOT approval-gated" : "approval-gated"})`);
3627
- const capabilitySuffix = enabledCapabilities.length > 0 ? ` — enabled: ${enabledCapabilities.join(", ")}` : "";
3628
- const dangerBanner = config.dangerouslySkipPermissions ? "\n⚠ dangerouslySkipPermissions is ON — every approval prompt (risky messages, file writes, shell commands) is skipped automatically.\n" : "";
3629
- const nonInteractiveBanner = nonInteractiveApprovalMode === "decline" ? "\nASSISTANT_NON_INTERACTIVE_APPROVAL=decline is set — every approval prompt auto-declines.\n" : "";
3574
+ const capabilityRow = enabledCapabilities.length > 0 ? `
3575
+ tools ${enabledCapabilities.join(", ")}` : "";
3576
+ const dangerBanner = config.dangerouslySkipPermissions ? "\n ⚠ dangerouslySkipPermissions is ON — every approval prompt is skipped automatically." : "";
3577
+ const nonInteractiveBanner = nonInteractiveApprovalMode === "decline" ? "\n ⚠ ASSISTANT_NON_INTERACTIVE_APPROVAL=decline — every approval prompt auto-declines." : "";
3630
3578
  const startupUndoLogEntries = await assistant.listUndoLogEntries();
3631
3579
  const undoableFromBefore = startupUndoLogEntries.filter((e) => e.undoable).length;
3632
3580
  const undoBanner = undoableFromBefore > 0 ? `
3633
- ${undoableFromBefore} action${undoableFromBefore === 1 ? "" : "s"} from earlier sessions ${undoableFromBefore === 1 ? "is" : "are"} still revertible — see /undo-action.
3634
- ` : "";
3635
- printSafe(`Aielia — your personal assistant on the 11-layer harness, one turn at a time. Ctrl+C to exit.${capabilitySuffix}
3636
- ${dangerBanner}${nonInteractiveBanner}${undoBanner}`);
3637
- printSafe("Type /help to see all commands, /config to view settings.\n");
3581
+ undo ${undoableFromBefore} earlier action${undoableFromBefore === 1 ? "" : "s"} can be reverted with /undo-action` : "";
3582
+ const startupSpend = await assistant.getSpendState("cli");
3583
+ const fmtUsage = (input, output, cost) => `↑${input.toLocaleString()} ↓${output.toLocaleString()} tokens (~$${cost.toFixed(4)})`;
3584
+ const totalUsageRow = `
3585
+ total ${fmtUsage(startupSpend.cumulativeInputTokens, startupSpend.cumulativeOutputTokens, startupSpend.cumulativeCostUsd)} across all sessions`;
3586
+ printSafe(
3587
+ [
3588
+ `Aielia v${CLI_VERSION}`,
3589
+ "Your personal assistant, one turn at a time.",
3590
+ " ",
3591
+ ` backend ${backendLine}${capabilityRow}${undoBanner}${totalUsageRow}${dangerBanner}${nonInteractiveBanner}`,
3592
+ " ",
3593
+ " /help commands /config settings Ctrl+C exit",
3594
+ " Alpha software: it can make mistakes, so verify anything important.",
3595
+ " "
3596
+ ].join("\n")
3597
+ );
3638
3598
  void assistant.proposeMemoryConsolidation("cli").catch(() => void 0);
3639
3599
  rl.prompt();
3640
3600
  let lastTrace;
3641
3601
  let lastSources;
3602
+ let lastAnswerClaim;
3642
3603
  let lastPlanStatus;
3643
3604
  let pendingPlanApproval;
3644
3605
  let lastTurnUsage;
@@ -3651,33 +3612,17 @@ ${dangerBanner}${nonInteractiveBanner}${undoBanner}`);
3651
3612
  { key: "a", label: "Yes, don't ask again this session" },
3652
3613
  { key: "n", label: "No" }
3653
3614
  ];
3654
- function withCostEstimate(usage) {
3655
- if (config.llmBackend === "claude-cli" || usage.costUsd !== void 0) return usage;
3656
- const estimated = estimateCostUsd(config.model ?? ANTHROPIC_DEFAULT_MODEL, usage);
3657
- return estimated !== void 0 ? { ...usage, costUsd: estimated } : usage;
3658
- }
3659
- const PLAN_TASK_STATUS_ICON = {
3660
- PENDING: "○",
3661
- RUNNING: "▶",
3662
- COMPLETE: "✓",
3663
- FAILED: "✗",
3664
- BLOCKED: "✗",
3665
- HUMAN_REQUIRED: "~"
3666
- };
3667
- function printPlan() {
3615
+ function priceUsage(usage) {
3616
+ return withCostEstimate(usage, { backend: config.llmBackend, model: config.model });
3617
+ }
3618
+ function printPlan(goalGraph) {
3668
3619
  if (!lastPlanStatus) {
3669
- printSafe("\nNo active plan for this session.\n");
3620
+ printSafe(`
3621
+ ${explainPlan({ ...lastTurnExplainInput(), goalGraph }).join("\n")}
3622
+ `);
3670
3623
  return;
3671
3624
  }
3672
- const lines = [
3673
- PLAN_LINE_PREFIX,
3674
- `Plan: ${lastPlanStatus.templateName ?? "custom plan"} (${lastPlanStatus.completionPct.toFixed(1)}% complete)`,
3675
- ...lastPlanStatus.tasks.flatMap((task) => [
3676
- ` ${PLAN_TASK_STATUS_ICON[task.status] ?? "?"} [${task.status}] ${task.id} — ${task.description}`,
3677
- ...task.note ? [` not accepted as done: ${task.note}`] : []
3678
- ]),
3679
- `Success criteria: ${lastPlanStatus.successCriteria}`
3680
- ];
3625
+ const lines = [PLAN_LINE_PREFIX, ...explainPlan(lastTurnExplainInput())];
3681
3626
  console.log(stripTerminalControls(`
3682
3627
  ${lines.join("\n")}
3683
3628
  `));
@@ -3692,7 +3637,7 @@ ${lines.join("\n")}
3692
3637
  const result = await assistant.sketchPlan("cli", request);
3693
3638
  console.log(stripTerminalControls(`Aielia> ${result.reply ?? "(no reply)"}
3694
3639
  `));
3695
- if (result.usage) lastTurnUsage = withCostEstimate(result.usage);
3640
+ if (result.usage) lastTurnUsage = priceUsage(result.usage);
3696
3641
  }
3697
3642
  const PLAN_APPROVAL_OPTIONS = [
3698
3643
  { key: "y", label: "Approve" },
@@ -3757,7 +3702,7 @@ ${lines.join("\n")}
3757
3702
  }
3758
3703
  pendingPlanApproval = void 0;
3759
3704
  lastTrace = void 0;
3760
- lastNoTraceReason = `No harness trace — the last turn staged a plan that was ${decision === "decline" ? "declined" : "approved"} before the harness ran.`;
3705
+ lastNoTraceReason = NO_TRACE.planApproval(decision === "decline" ? "declined" : "approved");
3761
3706
  await handleTurn(message, false, void 0, void 0, void 0, id, decision, edits);
3762
3707
  }
3763
3708
  async function loadPlanGraphNodes(live, threadId) {
@@ -3825,86 +3770,38 @@ ${result.lines.join("\n")}
3825
3770
  printPlanApproval(pendingPlanApproval.snapshot);
3826
3771
  return;
3827
3772
  }
3828
- printPlan();
3829
- }
3830
- function verificationHealthLabel({ strength, feasibility }) {
3831
- const confidence = Math.min(strength, feasibility);
3832
- if (confidence >= 0.7) return "High confidence";
3833
- if (confidence >= 0.4) return "Reasonably confident";
3834
- return "Worth double-checking";
3773
+ printPlan(lastPlanStatus ? void 0 : await assistant.getGoalGraphState("cli"));
3835
3774
  }
3836
- function batchBudgetSummaryLine(batchBudget) {
3837
- const found = batchBudget.perItemOutcomes.filter((o) => o.status === "found").length;
3838
- const notFound = batchBudget.perItemOutcomes.filter((o) => o.status === "not_found").length;
3839
- const truncated = batchBudget.perItemOutcomes.filter((o) => o.status === "truncated_while_productive").length;
3840
- return `Batch: ${batchBudget.itemCount} items — ${found} found, ${notFound} not found, ${truncated} truncated (${batchBudget.totalCallsUsed} calls used, projected ~${Math.ceil(batchBudget.projectedTotal)})`;
3775
+ function lastTurnExplainInput() {
3776
+ return {
3777
+ trace: lastTrace,
3778
+ noTraceReason: lastNoTraceReason,
3779
+ answerClaim: lastAnswerClaim,
3780
+ memoryInjection: assistant.getLastMemoryInjection(),
3781
+ sources: lastSources,
3782
+ toolSteps: lastTurnToolSteps,
3783
+ planStatus: lastPlanStatus
3784
+ };
3841
3785
  }
3842
3786
  function printWhy() {
3843
- const memoryLine = formatMemoryInjection(assistant.getLastMemoryInjection());
3844
- if (!lastTrace) {
3845
- printSafe(`
3846
- ${lastNoTraceReason ?? "No harness trace for the last turn (nothing to explain yet, or it took the fast path)."}
3847
- ${memoryLine}
3848
- `);
3849
- return;
3850
- }
3851
3787
  printSafe(`
3852
- ${verificationHealthLabel(lastTrace.verificationHealth)}`);
3853
- const chain = buildWhyChain(lastTrace.layerActivity);
3854
- if (chain.length > 0) {
3855
- printSafe(" " + chain.map((item) => `${LAYER_SHORT_CODE[item.layer]} (${item.reason})`).join(" > "));
3856
- }
3857
- if (lastTrace.batchBudget) {
3858
- printSafe(" " + batchBudgetSummaryLine(lastTrace.batchBudget));
3859
- }
3860
- printSafe(memoryLine);
3861
- printSafe("");
3788
+ ${explainWhy(lastTurnExplainInput()).join("\n")}
3789
+ `);
3862
3790
  }
3863
3791
  function printLayers() {
3864
- if (!lastTrace) {
3865
- printSafe(`
3866
- ${lastNoTraceReason ?? "No harness trace for the last turn (nothing to explain yet, or it took the fast path)."}
3792
+ printSafe(`
3793
+ ${explainLayers(lastTurnExplainInput()).join("\n")}
3867
3794
  `);
3868
- return;
3869
- }
3870
- printSafe("");
3871
- const byLayer = new Map(lastTrace.layerActivity.map((e) => [e.layer, e]));
3872
- for (const layer of LAYER_ORDER) {
3873
- const e = byLayer.get(layer);
3874
- const mark = (e == null ? void 0 : e.fired) ? "✓" : "·";
3875
- const reason = (e == null ? void 0 : e.reason) ?? "not evaluated this turn";
3876
- printSafe(` [${mark}] ${LAYER_DISPLAY_NAME[layer].padEnd(22)} ${reason}`);
3877
- }
3878
- if (lastTrace.batchBudget) {
3879
- printSafe("");
3880
- printSafe(` ${batchBudgetSummaryLine(lastTrace.batchBudget)}`);
3881
- const STATUS_MARK = {
3882
- found: "✓",
3883
- not_found: "✗",
3884
- truncated_while_productive: "~"
3885
- };
3886
- for (const outcome of lastTrace.batchBudget.perItemOutcomes) {
3887
- printSafe(` [${STATUS_MARK[outcome.status]}] ${outcome.item.padEnd(30)} ${outcome.status} (${outcome.callsUsed} calls)`);
3888
- }
3889
- }
3890
- printSafe("");
3891
3795
  }
3892
- const SOURCE_TOOL_LABEL = {
3893
- read_file: "Read",
3894
- list_directory: "Listed",
3895
- web_search: "Searched",
3896
- fetch_url: "Fetched"
3897
- };
3898
3796
  function printSources() {
3899
- if (!lastSources || lastSources.length === 0) {
3900
- printSafe("\nNo sources for the last turn (it used no file/web tool calls).\n");
3901
- return;
3902
- }
3903
- printSafe("");
3904
- for (const source of lastSources) {
3905
- printSafe(` - ${SOURCE_TOOL_LABEL[source.tool]} ${source.path}`);
3906
- }
3907
- printSafe("");
3797
+ printSafe(`
3798
+ ${explainSources(lastTurnExplainInput()).join("\n")}
3799
+ `);
3800
+ }
3801
+ function printSteps() {
3802
+ printSafe(`
3803
+ ${explainSteps(lastTurnExplainInput()).join("\n")}
3804
+ `);
3908
3805
  }
3909
3806
  function printHelp() {
3910
3807
  printSafe(`
@@ -3979,12 +3876,12 @@ ${formatStatus({ config, overriddenKeys, transcriptLength: transcript.length, pl
3979
3876
  }
3980
3877
  }
3981
3878
  async function handleUndo() {
3982
- const transcriptBefore = await assistant.getTranscript("cli");
3983
- if (transcriptBefore.length === 0) {
3879
+ const plan = planUndo(await assistant.getTranscript("cli"));
3880
+ if (plan.nothingToUndo) {
3984
3881
  printSafe("\nNothing to undo.\n");
3985
3882
  return;
3986
3883
  }
3987
- const wasPendingApproval = transcriptBefore[transcriptBefore.length - 1].role !== "assistant";
3884
+ const { wasPendingApproval } = plan;
3988
3885
  const undoneReminders = lastTurnToolSteps.filter((s) => s.tool === "create_reminder");
3989
3886
  const result = await assistant.undoLastTurn("cli");
3990
3887
  if (!result.undone) {
@@ -4003,14 +3900,15 @@ ${formatStatus({ config, overriddenKeys, transcriptLength: transcript.length, pl
4003
3900
  );
4004
3901
  }
4005
3902
  async function handleUndoAction(args) {
4006
- if (args.length === 0) {
3903
+ const command = parseUndoActionCommand(args);
3904
+ if (command.kind === "list") {
4007
3905
  const entries = await assistant.listUndoLogEntries();
4008
3906
  printSafe(`
4009
3907
  ${formatUndoLogListing(entries)}
4010
3908
  `);
4011
3909
  return;
4012
3910
  }
4013
- const staged = await assistant.stageUndoAction(args[0]);
3911
+ const staged = await assistant.stageUndoAction(command.id);
4014
3912
  if (staged.status === "error") {
4015
3913
  printSafe(`
4016
3914
  ✗ ${staged.message}
@@ -4021,7 +3919,7 @@ ${formatUndoLogListing(entries)}
4021
3919
  [needs approval — revert] ${staged.reason}`));
4022
3920
  const confirmed = await askYesNo("Apply this revert? (y/N) ");
4023
3921
  lastTrace = void 0;
4024
- lastNoTraceReason = `No harness trace — the last turn was a staged revert that was ${confirmed ? "approved" : "declined"} before the harness ran.`;
3922
+ lastNoTraceReason = NO_TRACE.stagedAction("revert", confirmed ? "approved" : "declined");
4025
3923
  await handleTurn("", confirmed, staged.pendingActionId);
4026
3924
  }
4027
3925
  async function printMemory() {
@@ -4240,24 +4138,8 @@ ${formatDoctorReport(checks)}
4240
4138
  }
4241
4139
  lastTurnToolSteps.push(step);
4242
4140
  }
4243
- function emptyClarificationDraft() {
4244
- return { selectedLabels: [], editText: "", freeText: "" };
4245
- }
4246
- function orderedAskOptions(question) {
4247
- const options2 = question.options ?? [];
4248
- 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);
4249
- }
4250
- function clarificationDraftToAnswer(questionId, draft) {
4251
- if (draft.selectedLabels.length > 0) {
4252
- return draft.editText.trim() ? { questionId, kind: "selected_with_edit", selectedLabels: draft.selectedLabels, editText: draft.editText.trim() } : { questionId, kind: "selected", selectedLabels: draft.selectedLabels };
4253
- }
4254
- if (draft.freeText.trim()) {
4255
- return { questionId, kind: "free_text", freeText: draft.freeText.trim() };
4256
- }
4257
- return null;
4258
- }
4259
4141
  function printClarificationQuestion(question, draft, index, total) {
4260
- const options2 = orderedAskOptions(question);
4142
+ const options2 = orderedOptions(question);
4261
4143
  const allowFreeText = question.allowFreeText !== false;
4262
4144
  printSafe("");
4263
4145
  if (question.header) console.log(stripTerminalControls(`[${question.header}]`));
@@ -4287,9 +4169,10 @@ ${formatDoctorReport(checks)}
4287
4169
  const pendingClarificationId = result.pendingClarificationId;
4288
4170
  if (questions.length === 0 || !pendingClarificationId) {
4289
4171
  lastTrace = void 0;
4290
- lastNoTraceReason = `No harness trace — the last turn needed clarification (${result.reason ?? "no further detail"}) but had no answerable questions.`;
4172
+ const unanswerable = presentTurnResult(result);
4173
+ lastNoTraceReason = unanswerable.noTraceReason;
4291
4174
  console.log(stripTerminalControls(`
4292
- [needs clarification] ${result.reason ?? "This request needs clarification."}
4175
+ [needs clarification] ${unanswerable.kind === "escalation" ? unanswerable.reason : DEFAULT_CLARIFICATION_REASON}
4293
4176
  `));
4294
4177
  return;
4295
4178
  }
@@ -4306,7 +4189,7 @@ ${formatDoctorReport(checks)}
4306
4189
  }
4307
4190
  printSafe(`
4308
4191
  [needs clarification] ${questions.length} question${questions.length === 1 ? "" : "s"} to answer.`);
4309
- const drafts = new Map(questions.map((q2) => [q2.id, emptyClarificationDraft()]));
4192
+ const drafts = new Map(questions.map((q2) => [q2.id, emptyDraft()]));
4310
4193
  let index = 0;
4311
4194
  while (true) {
4312
4195
  const question = questions[index];
@@ -4321,7 +4204,7 @@ ${formatDoctorReport(checks)}
4321
4204
  return;
4322
4205
  }
4323
4206
  if (lower === "s" || lower === "submit") {
4324
- const answers = questions.map((q2) => clarificationDraftToAnswer(q2.id, drafts.get(q2.id)));
4207
+ const answers = questions.map((q2) => draftToAnswer(q2.id, drafts.get(q2.id)));
4325
4208
  const missing = questions.filter((q2, i) => answers[i] === null);
4326
4209
  if (missing.length > 0) {
4327
4210
  printSafe(`
@@ -4356,7 +4239,7 @@ Still need an answer for: ${missing.map((q2) => q2.question).join("; ")}
4356
4239
  }
4357
4240
  const editMatch = /^e(\d+)$/i.exec(token);
4358
4241
  if (editMatch) {
4359
- const options2 = orderedAskOptions(question);
4242
+ const options2 = orderedOptions(question);
4360
4243
  const option = options2[Number(editMatch[1]) - 1];
4361
4244
  if (!option) {
4362
4245
  printSafe(`
@@ -4376,7 +4259,7 @@ Select option ${editMatch[1]} first, then add a note with e${editMatch[1]}.
4376
4259
  }
4377
4260
  const toggleMatch = /^t(\d+)$/i.exec(token) ?? /^(\d+)$/.exec(token);
4378
4261
  if (toggleMatch) {
4379
- const options2 = orderedAskOptions(question);
4262
+ const options2 = orderedOptions(question);
4380
4263
  const option = options2[Number(toggleMatch[1]) - 1];
4381
4264
  if (!option) {
4382
4265
  printSafe(`
@@ -4447,8 +4330,7 @@ Unrecognized input "${token}".
4447
4330
  });
4448
4331
  if (!streamedAnyTokens) clearProgress();
4449
4332
  if (result.status === "needs_approval" && result.pendingActionId) {
4450
- const kindLabel = result.pendingActionKind === "shell" ? "shell command" : result.pendingActionKind === "email" ? "send email" : result.pendingActionKind === "batch" ? "batch research" : "write";
4451
- const promptText = result.pendingActionKind === "shell" ? "Run this command?" : result.pendingActionKind === "email" ? "Send this email?" : result.pendingActionKind === "batch" ? "Continue?" : "Apply this write?";
4333
+ const { kindLabel, promptText } = approvalLabels(result.pendingActionKind);
4452
4334
  console.log(stripTerminalControls(`
4453
4335
  [needs approval — ${kindLabel}] ${result.reason}`));
4454
4336
  let confirmed;
@@ -4461,7 +4343,7 @@ Unrecognized input "${token}".
4461
4343
  if (decision === "a" && result.pendingActionKind) rememberedActionKinds.add(result.pendingActionKind);
4462
4344
  }
4463
4345
  lastTrace = void 0;
4464
- lastNoTraceReason = `No harness trace — the last turn was a staged ${kindLabel} that was ${confirmed ? "approved" : "declined"} before the harness ran.`;
4346
+ lastNoTraceReason = NO_TRACE.stagedAction(kindLabel, confirmed ? "approved" : "declined");
4465
4347
  await handleTurn(message, confirmed, result.pendingActionId);
4466
4348
  return;
4467
4349
  }
@@ -4482,7 +4364,7 @@ Unrecognized input "${token}".
4482
4364
  await handleTurn(message, true);
4483
4365
  } else {
4484
4366
  lastTrace = void 0;
4485
- lastNoTraceReason = "No harness trace — the last turn was blocked on an approval gate and declined before the harness ran.";
4367
+ lastNoTraceReason = NO_TRACE.messageApproval("declined");
4486
4368
  await assistant.recordDeclinedRequest("cli", message, result.reason ?? "This request needed approval.");
4487
4369
  printSafe("Cancelled.\n");
4488
4370
  }
@@ -4495,7 +4377,7 @@ Unrecognized input "${token}".
4495
4377
  if (result.status === "needs_plan_approval") {
4496
4378
  if (!result.planApprovalId) {
4497
4379
  printSafe(`
4498
- [needs plan approval] ${result.reason ?? "A plan is awaiting approval, but its approval id is missing."}
4380
+ [needs plan approval] ${result.reason ?? MISSING_PLAN_APPROVAL_ID_REASON}
4499
4381
  `);
4500
4382
  return;
4501
4383
  }
@@ -4506,7 +4388,7 @@ ${result.reason}`);
4506
4388
  if (snapshot) printPlanApproval(snapshot);
4507
4389
  pendingPlanApproval = { id, message, snapshot };
4508
4390
  lastTrace = void 0;
4509
- lastNoTraceReason = "No harness trace — the last turn staged a plan for approval (pending /plan approve|decline|edit).";
4391
+ lastNoTraceReason = NO_TRACE.planApproval(void 0, " (pending /plan approve|decline|edit)");
4510
4392
  if (!snapshot) {
4511
4393
  printSafe("Decide with /plan approve or /plan decline.\n");
4512
4394
  return;
@@ -4529,44 +4411,48 @@ ${result.reason}`);
4529
4411
  } else {
4530
4412
  decision = "decline";
4531
4413
  }
4532
- lastNoTraceReason = `No harness trace — the last turn staged a plan that was ${decision === "decline" ? "declined" : "approved"} before the harness ran.`;
4414
+ lastNoTraceReason = NO_TRACE.planApproval(decision === "decline" ? "declined" : "approved");
4533
4415
  await handleTurn(message, false, void 0, void 0, void 0, id, decision, edits);
4534
4416
  return;
4535
4417
  }
4536
4418
  if (result.status === "escalated") {
4537
4419
  lastTrace = void 0;
4538
- lastNoTraceReason = `No harness trace — the last turn escalated (${result.reason}) before completing.`;
4420
+ const escalation = presentTurnResult(result);
4421
+ lastNoTraceReason = escalation.noTraceReason;
4539
4422
  console.log(stripTerminalControls(`
4540
- [escalated] ${result.reason}
4423
+ [escalated] ${escalation.kind === "escalation" ? escalation.reason : result.reason}
4541
4424
  `));
4542
4425
  return;
4543
4426
  }
4544
4427
  if (!pendingActionId) {
4545
4428
  lastTrace = result.harnessSkipped ? void 0 : result.trace;
4546
- 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;
4429
+ lastAnswerClaim = result.answerClaim;
4430
+ lastNoTraceReason = result.harnessSkipped ? NO_TRACE.fastPath : void 0;
4547
4431
  }
4548
4432
  lastSources = result.sources;
4549
4433
  if (!pendingActionId && (!result.harnessSkipped || result.planStatus)) lastPlanStatus = result.planStatus;
4550
- lastTurnUsage = result.usage ? withCostEstimate(result.usage) : void 0;
4434
+ lastTurnUsage = result.usage ? priceUsage(result.usage) : void 0;
4435
+ const shown = presentTurnResult(result);
4436
+ const hints = shown.kind === "reply" ? shown.hints : {};
4551
4437
  const riskSuffix = result.riskLevel && result.riskLevel !== "LOW" ? ` [risk: ${result.riskLevel}]` : "";
4552
- const sourcesHint = result.sources && result.sources.length > 0 ? ` (${result.sources.length} source${result.sources.length > 1 ? "s" : ""} — /sources)` : "";
4553
- const planHint = result.planStatus ? ` (plan: ${result.planStatus.completionPct.toFixed(0)}% — /plan)` : "";
4554
- const contradictionNotice = result.contradictionNotice ? `
4438
+ const sourcesHint = hints.sourcesCount ? ` (${hints.sourcesCount} source${hints.sourcesCount > 1 ? "s" : ""} — /sources)` : "";
4439
+ const planHint = hints.planCompletionPct !== void 0 ? ` (plan: ${hints.planCompletionPct.toFixed(0)}% — /plan)` : "";
4440
+ const contradictionNotice = hints.contradictionNotice ? `
4555
4441
 
4556
- ${result.contradictionNotice}` : "";
4557
- const reviewNotice = (result.reviewNotice ? `
4442
+ ${hints.contradictionNotice}` : "";
4443
+ const reviewNotice = (hints.reviewNotice ? `
4558
4444
 
4559
- ${result.reviewNotice}` : "") + (result.auditNotice ? `
4445
+ ${hints.reviewNotice}` : "") + (hints.auditNotice ? `
4560
4446
 
4561
- ${result.auditNotice}` : "");
4562
- lastNextSteps = result.nextSteps && result.nextSteps.length > 0 ? result.nextSteps : void 0;
4447
+ ${hints.auditNotice}` : "");
4448
+ lastNextSteps = hints.nextSteps;
4563
4449
  const nextStepsBlock = lastNextSteps ? `
4564
4450
 
4565
4451
  ${formatNextSteps(lastNextSteps)}` : "";
4566
4452
  if (streamedAnyTokens) {
4567
- const pausedNoteText = result.pausedNote ? `
4453
+ const pausedNoteText = hints.pausedNote ? `
4568
4454
 
4569
- ${result.pausedNote}` : "";
4455
+ ${hints.pausedNote}` : "";
4570
4456
  process.stdout.write(stripTerminalControls(`${pausedNoteText}${riskSuffix}${sourcesHint}${planHint}${contradictionNotice}${reviewNotice}${nextStepsBlock}
4571
4457
 
4572
4458
  `));
@@ -4788,17 +4674,12 @@ ${config.model ?? "(using each backend's default)"}
4788
4674
  await handleConfigCommand(["set", "model", args.join(" ")]);
4789
4675
  }
4790
4676
  async function handleProject(args) {
4791
- if (args.length === 0) {
4792
- printSafe(`
4793
- ${assistant.getActiveProject() || "(none)"}
4677
+ const command = parseProjectCommand(args, assistant.getActiveProject());
4678
+ if (command.kind === "show") printSafe(`
4679
+ ${command.message}
4794
4680
  `);
4795
- return;
4796
- }
4797
- if (args[0] === "clear") {
4798
- await handleConfigCommand(["reset", "activeProject"]);
4799
- return;
4800
- }
4801
- await handleConfigCommand(["set", "activeProject", args.join(" ")]);
4681
+ else if (command.kind === "clear") await handleConfigCommand(["reset", "activeProject"]);
4682
+ else await handleConfigCommand(["set", "activeProject", command.project]);
4802
4683
  }
4803
4684
  let turnInProgress = false;
4804
4685
  let lastNextSteps;
@@ -4808,6 +4689,7 @@ ${assistant.getActiveProject() || "(none)"}
4808
4689
  // Bare /layers is the last-turn fired/skipped view; its subcommands (settings/on/off/reset) edit the on/off choices.
4809
4690
  "/layers": (args) => args.length === 0 ? printLayers() : handleLayers(args),
4810
4691
  "/sources": () => printSources(),
4692
+ "/steps": () => printSteps(),
4811
4693
  "/plan": (args) => handlePlan(args),
4812
4694
  "/help": () => printHelp(),
4813
4695
  "/clear": () => handleClear(),
@@ -4897,6 +4779,7 @@ ${assistant.getActiveProject() || "(none)"}
4897
4779
  });
4898
4780
  rl.resume();
4899
4781
  return {
4782
+ commandNames: Object.keys(commands),
4900
4783
  dispatchLine: async (line) => {
4901
4784
  const message = line.trim();
4902
4785
  if (!message) return;
@@ -4912,7 +4795,11 @@ ${assistant.getActiveProject() || "(none)"}
4912
4795
  indicators.push(`Model: ${config.model ?? backendDisplayModel[config.llmBackend]}`);
4913
4796
  const spendState = await assistant.getSpendState("cli");
4914
4797
  indicators.push(
4915
- `↑${spendState.cumulativeInputTokens.toLocaleString()} ↓${spendState.cumulativeOutputTokens.toLocaleString()} tokens (~$${spendState.cumulativeCostUsd.toFixed(4)})`
4798
+ `This session: ${fmtUsage(
4799
+ spendState.cumulativeInputTokens - startupSpend.cumulativeInputTokens,
4800
+ spendState.cumulativeOutputTokens - startupSpend.cumulativeOutputTokens,
4801
+ spendState.cumulativeCostUsd - startupSpend.cumulativeCostUsd
4802
+ )}`
4916
4803
  );
4917
4804
  const spendCapLine = formatSpendCapStatus(spendState, { sessionCostLimitUsd: config.sessionCostLimitUsd, sessionCallLimit: config.sessionCallLimit });
4918
4805
  if (spendCapLine) indicators.push(spendCapLine);
@@ -4956,7 +4843,7 @@ async function mainInner(argv) {
4956
4843
  });
4957
4844
  }
4958
4845
  if (shouldLaunchTuiApp(config.tuiMode ?? DEFAULT_TUI_MODE, Boolean(process.stdout.isTTY), Boolean(process.stdin.isTTY))) {
4959
- const { runTuiApp } = await import("./tui-app-C0J6HRzv.js");
4846
+ const { runTuiApp } = await import("./tui-app-Dt72u8vZ.js");
4960
4847
  await runTuiApp();
4961
4848
  return;
4962
4849
  }
@@ -4983,4 +4870,4 @@ export {
4983
4870
  sanitizeLabel as s,
4984
4871
  toLines as t
4985
4872
  };
4986
- //# sourceMappingURL=bin-C_hgDdSf.js.map
4873
+ //# sourceMappingURL=bin-CCwDFxDe.js.map