@buildaharness/aielia 0.3.8 → 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-DETwedpj.js → bin-CCwDFxDe.js} +118 -247
  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-CcxiwZyM.js → tui-app-Dt72u8vZ.js} +3 -3
  33. package/dist/{tui-app-CcxiwZyM.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-DETwedpj.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, 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 { 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.8";
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,81 +3459,45 @@ 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
3503
  const dataDir = options.dataDir ?? defaultDataDir;
@@ -3651,6 +3599,7 @@ Fix it by setting the missing value (in the config file under ${dataDir}, or via
3651
3599
  rl.prompt();
3652
3600
  let lastTrace;
3653
3601
  let lastSources;
3602
+ let lastAnswerClaim;
3654
3603
  let lastPlanStatus;
3655
3604
  let pendingPlanApproval;
3656
3605
  let lastTurnUsage;
@@ -3663,33 +3612,17 @@ Fix it by setting the missing value (in the config file under ${dataDir}, or via
3663
3612
  { key: "a", label: "Yes, don't ask again this session" },
3664
3613
  { key: "n", label: "No" }
3665
3614
  ];
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() {
3615
+ function priceUsage(usage) {
3616
+ return withCostEstimate(usage, { backend: config.llmBackend, model: config.model });
3617
+ }
3618
+ function printPlan(goalGraph) {
3680
3619
  if (!lastPlanStatus) {
3681
- printSafe("\nNo active plan for this session.\n");
3620
+ printSafe(`
3621
+ ${explainPlan({ ...lastTurnExplainInput(), goalGraph }).join("\n")}
3622
+ `);
3682
3623
  return;
3683
3624
  }
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
- ];
3625
+ const lines = [PLAN_LINE_PREFIX, ...explainPlan(lastTurnExplainInput())];
3693
3626
  console.log(stripTerminalControls(`
3694
3627
  ${lines.join("\n")}
3695
3628
  `));
@@ -3704,7 +3637,7 @@ ${lines.join("\n")}
3704
3637
  const result = await assistant.sketchPlan("cli", request);
3705
3638
  console.log(stripTerminalControls(`Aielia> ${result.reply ?? "(no reply)"}
3706
3639
  `));
3707
- if (result.usage) lastTurnUsage = withCostEstimate(result.usage);
3640
+ if (result.usage) lastTurnUsage = priceUsage(result.usage);
3708
3641
  }
3709
3642
  const PLAN_APPROVAL_OPTIONS = [
3710
3643
  { key: "y", label: "Approve" },
@@ -3769,7 +3702,7 @@ ${lines.join("\n")}
3769
3702
  }
3770
3703
  pendingPlanApproval = void 0;
3771
3704
  lastTrace = void 0;
3772
- 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");
3773
3706
  await handleTurn(message, false, void 0, void 0, void 0, id, decision, edits);
3774
3707
  }
3775
3708
  async function loadPlanGraphNodes(live, threadId) {
@@ -3837,86 +3770,38 @@ ${result.lines.join("\n")}
3837
3770
  printPlanApproval(pendingPlanApproval.snapshot);
3838
3771
  return;
3839
3772
  }
3840
- printPlan();
3773
+ printPlan(lastPlanStatus ? void 0 : await assistant.getGoalGraphState("cli"));
3841
3774
  }
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";
3847
- }
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)})`;
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
+ };
3853
3785
  }
3854
3786
  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
3787
  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("");
3788
+ ${explainWhy(lastTurnExplainInput()).join("\n")}
3789
+ `);
3874
3790
  }
3875
3791
  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)."}
3792
+ printSafe(`
3793
+ ${explainLayers(lastTurnExplainInput()).join("\n")}
3879
3794
  `);
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
3795
  }
3904
- const SOURCE_TOOL_LABEL = {
3905
- read_file: "Read",
3906
- list_directory: "Listed",
3907
- web_search: "Searched",
3908
- fetch_url: "Fetched"
3909
- };
3910
3796
  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("");
3797
+ printSafe(`
3798
+ ${explainSources(lastTurnExplainInput()).join("\n")}
3799
+ `);
3800
+ }
3801
+ function printSteps() {
3802
+ printSafe(`
3803
+ ${explainSteps(lastTurnExplainInput()).join("\n")}
3804
+ `);
3920
3805
  }
3921
3806
  function printHelp() {
3922
3807
  printSafe(`
@@ -3991,12 +3876,12 @@ ${formatStatus({ config, overriddenKeys, transcriptLength: transcript.length, pl
3991
3876
  }
3992
3877
  }
3993
3878
  async function handleUndo() {
3994
- const transcriptBefore = await assistant.getTranscript("cli");
3995
- if (transcriptBefore.length === 0) {
3879
+ const plan = planUndo(await assistant.getTranscript("cli"));
3880
+ if (plan.nothingToUndo) {
3996
3881
  printSafe("\nNothing to undo.\n");
3997
3882
  return;
3998
3883
  }
3999
- const wasPendingApproval = transcriptBefore[transcriptBefore.length - 1].role !== "assistant";
3884
+ const { wasPendingApproval } = plan;
4000
3885
  const undoneReminders = lastTurnToolSteps.filter((s) => s.tool === "create_reminder");
4001
3886
  const result = await assistant.undoLastTurn("cli");
4002
3887
  if (!result.undone) {
@@ -4015,14 +3900,15 @@ ${formatStatus({ config, overriddenKeys, transcriptLength: transcript.length, pl
4015
3900
  );
4016
3901
  }
4017
3902
  async function handleUndoAction(args) {
4018
- if (args.length === 0) {
3903
+ const command = parseUndoActionCommand(args);
3904
+ if (command.kind === "list") {
4019
3905
  const entries = await assistant.listUndoLogEntries();
4020
3906
  printSafe(`
4021
3907
  ${formatUndoLogListing(entries)}
4022
3908
  `);
4023
3909
  return;
4024
3910
  }
4025
- const staged = await assistant.stageUndoAction(args[0]);
3911
+ const staged = await assistant.stageUndoAction(command.id);
4026
3912
  if (staged.status === "error") {
4027
3913
  printSafe(`
4028
3914
  ✗ ${staged.message}
@@ -4033,7 +3919,7 @@ ${formatUndoLogListing(entries)}
4033
3919
  [needs approval — revert] ${staged.reason}`));
4034
3920
  const confirmed = await askYesNo("Apply this revert? (y/N) ");
4035
3921
  lastTrace = void 0;
4036
- 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");
4037
3923
  await handleTurn("", confirmed, staged.pendingActionId);
4038
3924
  }
4039
3925
  async function printMemory() {
@@ -4252,24 +4138,8 @@ ${formatDoctorReport(checks)}
4252
4138
  }
4253
4139
  lastTurnToolSteps.push(step);
4254
4140
  }
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
4141
  function printClarificationQuestion(question, draft, index, total) {
4272
- const options2 = orderedAskOptions(question);
4142
+ const options2 = orderedOptions(question);
4273
4143
  const allowFreeText = question.allowFreeText !== false;
4274
4144
  printSafe("");
4275
4145
  if (question.header) console.log(stripTerminalControls(`[${question.header}]`));
@@ -4299,9 +4169,10 @@ ${formatDoctorReport(checks)}
4299
4169
  const pendingClarificationId = result.pendingClarificationId;
4300
4170
  if (questions.length === 0 || !pendingClarificationId) {
4301
4171
  lastTrace = void 0;
4302
- 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;
4303
4174
  console.log(stripTerminalControls(`
4304
- [needs clarification] ${result.reason ?? "This request needs clarification."}
4175
+ [needs clarification] ${unanswerable.kind === "escalation" ? unanswerable.reason : DEFAULT_CLARIFICATION_REASON}
4305
4176
  `));
4306
4177
  return;
4307
4178
  }
@@ -4318,7 +4189,7 @@ ${formatDoctorReport(checks)}
4318
4189
  }
4319
4190
  printSafe(`
4320
4191
  [needs clarification] ${questions.length} question${questions.length === 1 ? "" : "s"} to answer.`);
4321
- const drafts = new Map(questions.map((q2) => [q2.id, emptyClarificationDraft()]));
4192
+ const drafts = new Map(questions.map((q2) => [q2.id, emptyDraft()]));
4322
4193
  let index = 0;
4323
4194
  while (true) {
4324
4195
  const question = questions[index];
@@ -4333,7 +4204,7 @@ ${formatDoctorReport(checks)}
4333
4204
  return;
4334
4205
  }
4335
4206
  if (lower === "s" || lower === "submit") {
4336
- 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)));
4337
4208
  const missing = questions.filter((q2, i) => answers[i] === null);
4338
4209
  if (missing.length > 0) {
4339
4210
  printSafe(`
@@ -4368,7 +4239,7 @@ Still need an answer for: ${missing.map((q2) => q2.question).join("; ")}
4368
4239
  }
4369
4240
  const editMatch = /^e(\d+)$/i.exec(token);
4370
4241
  if (editMatch) {
4371
- const options2 = orderedAskOptions(question);
4242
+ const options2 = orderedOptions(question);
4372
4243
  const option = options2[Number(editMatch[1]) - 1];
4373
4244
  if (!option) {
4374
4245
  printSafe(`
@@ -4388,7 +4259,7 @@ Select option ${editMatch[1]} first, then add a note with e${editMatch[1]}.
4388
4259
  }
4389
4260
  const toggleMatch = /^t(\d+)$/i.exec(token) ?? /^(\d+)$/.exec(token);
4390
4261
  if (toggleMatch) {
4391
- const options2 = orderedAskOptions(question);
4262
+ const options2 = orderedOptions(question);
4392
4263
  const option = options2[Number(toggleMatch[1]) - 1];
4393
4264
  if (!option) {
4394
4265
  printSafe(`
@@ -4459,8 +4330,7 @@ Unrecognized input "${token}".
4459
4330
  });
4460
4331
  if (!streamedAnyTokens) clearProgress();
4461
4332
  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?";
4333
+ const { kindLabel, promptText } = approvalLabels(result.pendingActionKind);
4464
4334
  console.log(stripTerminalControls(`
4465
4335
  [needs approval — ${kindLabel}] ${result.reason}`));
4466
4336
  let confirmed;
@@ -4473,7 +4343,7 @@ Unrecognized input "${token}".
4473
4343
  if (decision === "a" && result.pendingActionKind) rememberedActionKinds.add(result.pendingActionKind);
4474
4344
  }
4475
4345
  lastTrace = void 0;
4476
- 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");
4477
4347
  await handleTurn(message, confirmed, result.pendingActionId);
4478
4348
  return;
4479
4349
  }
@@ -4494,7 +4364,7 @@ Unrecognized input "${token}".
4494
4364
  await handleTurn(message, true);
4495
4365
  } else {
4496
4366
  lastTrace = void 0;
4497
- 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");
4498
4368
  await assistant.recordDeclinedRequest("cli", message, result.reason ?? "This request needed approval.");
4499
4369
  printSafe("Cancelled.\n");
4500
4370
  }
@@ -4507,7 +4377,7 @@ Unrecognized input "${token}".
4507
4377
  if (result.status === "needs_plan_approval") {
4508
4378
  if (!result.planApprovalId) {
4509
4379
  printSafe(`
4510
- [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}
4511
4381
  `);
4512
4382
  return;
4513
4383
  }
@@ -4518,7 +4388,7 @@ ${result.reason}`);
4518
4388
  if (snapshot) printPlanApproval(snapshot);
4519
4389
  pendingPlanApproval = { id, message, snapshot };
4520
4390
  lastTrace = void 0;
4521
- 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)");
4522
4392
  if (!snapshot) {
4523
4393
  printSafe("Decide with /plan approve or /plan decline.\n");
4524
4394
  return;
@@ -4541,44 +4411,48 @@ ${result.reason}`);
4541
4411
  } else {
4542
4412
  decision = "decline";
4543
4413
  }
4544
- 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");
4545
4415
  await handleTurn(message, false, void 0, void 0, void 0, id, decision, edits);
4546
4416
  return;
4547
4417
  }
4548
4418
  if (result.status === "escalated") {
4549
4419
  lastTrace = void 0;
4550
- lastNoTraceReason = `No harness trace — the last turn escalated (${result.reason}) before completing.`;
4420
+ const escalation = presentTurnResult(result);
4421
+ lastNoTraceReason = escalation.noTraceReason;
4551
4422
  console.log(stripTerminalControls(`
4552
- [escalated] ${result.reason}
4423
+ [escalated] ${escalation.kind === "escalation" ? escalation.reason : result.reason}
4553
4424
  `));
4554
4425
  return;
4555
4426
  }
4556
4427
  if (!pendingActionId) {
4557
4428
  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;
4429
+ lastAnswerClaim = result.answerClaim;
4430
+ lastNoTraceReason = result.harnessSkipped ? NO_TRACE.fastPath : void 0;
4559
4431
  }
4560
4432
  lastSources = result.sources;
4561
4433
  if (!pendingActionId && (!result.harnessSkipped || result.planStatus)) lastPlanStatus = result.planStatus;
4562
- 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 : {};
4563
4437
  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 ? `
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 ? `
4567
4441
 
4568
- ${result.contradictionNotice}` : "";
4569
- const reviewNotice = (result.reviewNotice ? `
4442
+ ${hints.contradictionNotice}` : "";
4443
+ const reviewNotice = (hints.reviewNotice ? `
4570
4444
 
4571
- ${result.reviewNotice}` : "") + (result.auditNotice ? `
4445
+ ${hints.reviewNotice}` : "") + (hints.auditNotice ? `
4572
4446
 
4573
- ${result.auditNotice}` : "");
4574
- lastNextSteps = result.nextSteps && result.nextSteps.length > 0 ? result.nextSteps : void 0;
4447
+ ${hints.auditNotice}` : "");
4448
+ lastNextSteps = hints.nextSteps;
4575
4449
  const nextStepsBlock = lastNextSteps ? `
4576
4450
 
4577
4451
  ${formatNextSteps(lastNextSteps)}` : "";
4578
4452
  if (streamedAnyTokens) {
4579
- const pausedNoteText = result.pausedNote ? `
4453
+ const pausedNoteText = hints.pausedNote ? `
4580
4454
 
4581
- ${result.pausedNote}` : "";
4455
+ ${hints.pausedNote}` : "";
4582
4456
  process.stdout.write(stripTerminalControls(`${pausedNoteText}${riskSuffix}${sourcesHint}${planHint}${contradictionNotice}${reviewNotice}${nextStepsBlock}
4583
4457
 
4584
4458
  `));
@@ -4800,17 +4674,12 @@ ${config.model ?? "(using each backend's default)"}
4800
4674
  await handleConfigCommand(["set", "model", args.join(" ")]);
4801
4675
  }
4802
4676
  async function handleProject(args) {
4803
- if (args.length === 0) {
4804
- printSafe(`
4805
- ${assistant.getActiveProject() || "(none)"}
4677
+ const command = parseProjectCommand(args, assistant.getActiveProject());
4678
+ if (command.kind === "show") printSafe(`
4679
+ ${command.message}
4806
4680
  `);
4807
- return;
4808
- }
4809
- if (args[0] === "clear") {
4810
- await handleConfigCommand(["reset", "activeProject"]);
4811
- return;
4812
- }
4813
- await handleConfigCommand(["set", "activeProject", args.join(" ")]);
4681
+ else if (command.kind === "clear") await handleConfigCommand(["reset", "activeProject"]);
4682
+ else await handleConfigCommand(["set", "activeProject", command.project]);
4814
4683
  }
4815
4684
  let turnInProgress = false;
4816
4685
  let lastNextSteps;
@@ -4820,6 +4689,7 @@ ${assistant.getActiveProject() || "(none)"}
4820
4689
  // Bare /layers is the last-turn fired/skipped view; its subcommands (settings/on/off/reset) edit the on/off choices.
4821
4690
  "/layers": (args) => args.length === 0 ? printLayers() : handleLayers(args),
4822
4691
  "/sources": () => printSources(),
4692
+ "/steps": () => printSteps(),
4823
4693
  "/plan": (args) => handlePlan(args),
4824
4694
  "/help": () => printHelp(),
4825
4695
  "/clear": () => handleClear(),
@@ -4909,6 +4779,7 @@ ${assistant.getActiveProject() || "(none)"}
4909
4779
  });
4910
4780
  rl.resume();
4911
4781
  return {
4782
+ commandNames: Object.keys(commands),
4912
4783
  dispatchLine: async (line) => {
4913
4784
  const message = line.trim();
4914
4785
  if (!message) return;
@@ -4972,7 +4843,7 @@ async function mainInner(argv) {
4972
4843
  });
4973
4844
  }
4974
4845
  if (shouldLaunchTuiApp(config.tuiMode ?? DEFAULT_TUI_MODE, Boolean(process.stdout.isTTY), Boolean(process.stdin.isTTY))) {
4975
- const { runTuiApp } = await import("./tui-app-CcxiwZyM.js");
4846
+ const { runTuiApp } = await import("./tui-app-Dt72u8vZ.js");
4976
4847
  await runTuiApp();
4977
4848
  return;
4978
4849
  }
@@ -4999,4 +4870,4 @@ export {
4999
4870
  sanitizeLabel as s,
5000
4871
  toLines as t
5001
4872
  };
5002
- //# sourceMappingURL=bin-DETwedpj.js.map
4873
+ //# sourceMappingURL=bin-CCwDFxDe.js.map