@buildaharness/aielia 0.3.0
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.
- package/README.md +832 -0
- package/dist/action-approval-service.d.ts +52 -0
- package/dist/action-snapshot.d.ts +173 -0
- package/dist/action-tools.d.ts +38 -0
- package/dist/agent-loop.d.ts +372 -0
- package/dist/answer-claim.d.ts +51 -0
- package/dist/ask-clarification-service.d.ts +70 -0
- package/dist/ask-mode-flag.d.ts +25 -0
- package/dist/ask-response-format.d.ts +8 -0
- package/dist/assistant-session.d.ts +267 -0
- package/dist/assistant-source.d.ts +14 -0
- package/dist/assistant-types.d.ts +158 -0
- package/dist/assistant.d.ts +419 -0
- package/dist/batch-list-detector.d.ts +15 -0
- package/dist/batch-research-fixtures.d.ts +50 -0
- package/dist/benchmark-injected-failure.d.ts +27 -0
- package/dist/claude-cli-llm-client.d.ts +139 -0
- package/dist/claude-cli-prompt.d.ts +86 -0
- package/dist/cli-config.d.ts +27 -0
- package/dist/cli-session-D0uLdT2A.js +7863 -0
- package/dist/cli-session-D0uLdT2A.js.map +1 -0
- package/dist/cli-session.d.ts +63 -0
- package/dist/cli.d.ts +65 -0
- package/dist/cli.js +1693 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +175 -0
- package/dist/contradiction-checker.d.ts +27 -0
- package/dist/debug-log.d.ts +16 -0
- package/dist/decomposition-classifier.d.ts +38 -0
- package/dist/doctor-checks.d.ts +8 -0
- package/dist/email-smtp.d.ts +19 -0
- package/dist/email.d.ts +60 -0
- package/dist/error-classifier.d.ts +13 -0
- package/dist/execution-mode.d.ts +85 -0
- package/dist/fact-extraction.d.ts +143 -0
- package/dist/failure-mode-matcher.d.ts +32 -0
- package/dist/file-tools-mcp-server.mjs +1027 -0
- package/dist/file-tools.d.ts +179 -0
- package/dist/first-run.d.ts +22 -0
- package/dist/harness-bridge.d.ts +103 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.js +212 -0
- package/dist/index.js.map +1 -0
- package/dist/ink-test-render.d.ts +24 -0
- package/dist/lexical/patterns/batch-list-markers.json +8 -0
- package/dist/lexical/patterns/batch-list-markers.json.d.ts +11 -0
- package/dist/lexical/patterns/coding-fact-markers.json +8 -0
- package/dist/lexical/patterns/coding-fact-markers.json.d.ts +11 -0
- package/dist/lexical/patterns/enumeration-markers.json +20 -0
- package/dist/lexical/patterns/enumeration-markers.json.d.ts +23 -0
- package/dist/lexical/patterns/fact-markers.json +16 -0
- package/dist/lexical/patterns/fact-markers.json.d.ts +19 -0
- package/dist/lexical/patterns/injection-patterns.json +20 -0
- package/dist/lexical/patterns/injection-patterns.json.d.ts +23 -0
- package/dist/lexical/patterns/plan-mode-markers.json +10 -0
- package/dist/lexical/patterns/plan-mode-markers.json.d.ts +13 -0
- package/dist/lexical/patterns/risk-patterns.json +122 -0
- package/dist/lexical/patterns/risk-patterns.json.d.ts +125 -0
- package/dist/lexical/patterns/task-cancel-markers.json +22 -0
- package/dist/lexical/patterns/task-cancel-markers.json.d.ts +25 -0
- package/dist/lexical/patterns/template-keywords.json +20 -0
- package/dist/lexical/patterns/template-keywords.json.d.ts +23 -0
- package/dist/lexical/patterns/tool-yield-markers.json +22 -0
- package/dist/lexical/patterns/tool-yield-markers.json.d.ts +25 -0
- package/dist/lexical/patterns.d.ts +64 -0
- package/dist/memory-service.d.ts +219 -0
- package/dist/model-pricing.d.ts +19 -0
- package/dist/network-containment.d.ts +21 -0
- package/dist/node-config-store.d.ts +29 -0
- package/dist/node-display-names.d.ts +26 -0
- package/dist/node-fs-backend.d.ts +8 -0
- package/dist/non-interactive-mode.d.ts +22 -0
- package/dist/one-loop-flag.d.ts +38 -0
- package/dist/plan-approval-service.d.ts +62 -0
- package/dist/plan-builder.d.ts +32 -0
- package/dist/plan-drafting-service.d.ts +87 -0
- package/dist/plan-drafting.d.ts +58 -0
- package/dist/plan-mode-flag.d.ts +34 -0
- package/dist/plan-service.d.ts +61 -0
- package/dist/plan-sketch-service.d.ts +24 -0
- package/dist/plan-sketch.d.ts +20 -0
- package/dist/plan-store.d.ts +232 -0
- package/dist/plan-templates/data/content_creation.json.d.ts +75 -0
- package/dist/plan-templates/data/decision_making.json.d.ts +66 -0
- package/dist/plan-templates/data/problem_solving.json.d.ts +75 -0
- package/dist/plan-templates/data/process_improvement.json.d.ts +57 -0
- package/dist/plan-templates/data/project_planning.json.d.ts +66 -0
- package/dist/plan-templates/data/research_analysis.json.d.ts +66 -0
- package/dist/plan-templates/data/trip_planning.json.d.ts +66 -0
- package/dist/plan-templates/index.d.ts +33 -0
- package/dist/plan-verification.d.ts +31 -0
- package/dist/reminder-tools.d.ts +13 -0
- package/dist/response-service.d.ts +73 -0
- package/dist/review-checker.d.ts +22 -0
- package/dist/risk-classifier.d.ts +7 -0
- package/dist/scripted-llm-client.d.ts +37 -0
- package/dist/semantic-criterion-coverage.d.ts +30 -0
- package/dist/shell-executor.d.ts +14 -0
- package/dist/shell-tools.d.ts +84 -0
- package/dist/spend-cap.d.ts +41 -0
- package/dist/spike.d.ts +1 -0
- package/dist/spike.js +18 -0
- package/dist/spike.js.map +1 -0
- package/dist/supervisor-decider.d.ts +3 -0
- package/dist/system-prompt.d.ts +8 -0
- package/dist/task-mapping.d.ts +42 -0
- package/dist/tool-control-plane.d.ts +67 -0
- package/dist/tool-policy.d.ts +45 -0
- package/dist/tool-step.d.ts +23 -0
- package/dist/tool-yield-classifier.d.ts +7 -0
- package/dist/trace-events.d.ts +126 -0
- package/dist/transcript-compaction.d.ts +13 -0
- package/dist/trust-tagging.d.ts +38 -0
- package/dist/tui-app-DxLPSGh2.js +481 -0
- package/dist/tui-app-DxLPSGh2.js.map +1 -0
- package/dist/tui-app.d.ts +125 -0
- package/dist/tui-input.d.ts +51 -0
- package/dist/tui-mode-flag.d.ts +36 -0
- package/dist/tui-output-capture.d.ts +30 -0
- package/dist/tui-spike.d.ts +1 -0
- package/dist/turn-intent-classifier.d.ts +96 -0
- package/dist/turn-interpreter.d.ts +71 -0
- package/dist/turn-policy.d.ts +45 -0
- package/dist/web-fetch-core.d.ts +65 -0
- package/dist/web-search-provider.d.ts +12 -0
- package/dist/web-tools.d.ts +26 -0
- package/package.json +69 -0
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sources":["../src/node-fs-backend.ts","../src/claude-cli-llm-client.ts","../src/network-containment.ts","../src/shell-executor.ts","../src/node-config-store.ts","../src/doctor-checks.ts","../src/non-interactive-mode.ts","../src/first-run.ts","../src/cli.ts"],"sourcesContent":["import { mkdir, readFile, writeFile, readdir, unlink, realpath, stat, rename } from 'node:fs/promises'\nimport type { FsBackend } from '@buildaharness/runtime'\n\nfunction isEnoent(err: unknown): boolean {\n return (err as NodeJS.ErrnoException)?.code === 'ENOENT'\n}\n\n/**\n * node:fs/promises-backed FsBackend for the CLI. Deliberately not exported from\n * this package's index — only cli.ts imports it, so a browser build (chat-ui)\n * that only ever imports the package root never pulls a Node builtin into its\n * bundle.\n */\nexport function createNodeFsBackend(): FsBackend {\n return {\n async readTextFile(path) {\n try {\n return await readFile(path, 'utf-8')\n } catch (err) {\n if (isEnoent(err)) return undefined\n throw err\n }\n },\n async writeTextFile(path, contents) {\n await writeFile(path, contents, 'utf-8')\n },\n async removeFile(path) {\n try {\n await unlink(path)\n } catch (err) {\n if (!isEnoent(err)) throw err\n }\n },\n async mkdir(path) {\n await mkdir(path, { recursive: true })\n },\n async readDir(path) {\n try {\n return await readdir(path)\n } catch (err) {\n if (isEnoent(err)) return []\n throw err\n }\n },\n async realpath(path) {\n return realpath(path)\n },\n async stat(path) {\n try {\n const info = await stat(path)\n return { isDirectory: info.isDirectory(), size: info.size }\n } catch (err) {\n if (isEnoent(err)) return undefined\n throw err\n }\n },\n async rename(from, to) {\n await rename(from, to)\n },\n }\n}\n","import { spawn } from 'node:child_process'\nimport { readdir, readFile, stat } from 'node:fs/promises'\nimport { fileURLToPath } from 'node:url'\nimport { tmpdir } from 'node:os'\nimport { createServer, type Server } from 'node:net'\nimport type { ILLMClient, ChatMessage, ChatOptions, ToolDefinition, LLMStructuredResponse, ToolStepEvent, ToolProposalDecision } from '@buildaharness/runtime'\nimport type { PendingActionRecord } from './file-tools.js'\nimport { buildClaudePrompt, parseClaudeCliOutput, stripJsonCodeFence, ALREADY_STAGED_ACTION_TOOL, stagedActionInput, type ParsedClaudeCliOutput } from './claude-cli-prompt.js'\nimport { stripMcpToolPrefix } from './tool-step.js'\n\n/**\n * `--tools \"\"` only disables Claude Code's own built-in tools (Read/Write/Bash/etc.) — it\n * has no effect on MCP servers, which are controlled entirely by --mcp-config/\n * --strict-mcp-config. Without this, a plain callChatSync call silently inherits whatever\n * MCP servers happen to be configured ambiently (a project .mcp.json, or the user's own\n * global Claude Code config) — an ordinary PersonalAssistant chat turn must not pick up\n * unrelated tools the user happens to have configured for their own interactive sessions.\n * An empty --mcp-config plus --strict-mcp-config guarantees zero MCP servers, the same way\n * --tools \"\" guarantees zero built-ins.\n */\nconst EMPTY_MCP_CONFIG = JSON.stringify({ mcpServers: {} })\n\nexport interface ClaudeCliLLMClientOptions {\n /** Path to the claude binary. Defaults to CLAUDE_PATH env var, then 'claude' on PATH. */\n claudePath?: string\n /**\n * When set, callChatStructured wires in the file-tools MCP server (read_file/\n * list_directory/write_file, scoped to workspaceRoot) via --mcp-config instead\n * of throwing when tools are supplied. Absent by default — an ordinary chat\n * turn through this backend stays fully tool-free, exactly as before.\n */\n fileTools?: { workspaceRoot: string }\n /**\n * When set (and fileTools is also set — the MCP server only starts at all\n * when fileTools is configured), also registers create_reminder/list_reminders\n * on the same MCP server, backed by this file. Must point at the exact file a\n * `FileSystemAdapter` (namespace \"reminders\") would use for the key\n * \"reminders\" — see file-tools-mcp-server.mjs's doc comment — so this\n * subprocess and the parent process's own ReminderStore share one reminder\n * list instead of drifting into two disconnected ones. Absent by default —\n * those two tools stay unregistered, same as before this option existed.\n * fetch_url needs no such option: it's registered unconditionally whenever\n * fileTools is set, since it has no shared-state dependency to configure.\n */\n remindersFile?: string\n /**\n * When set, also registers run_shell_command on the same MCP server (started\n * whenever fileTools or shellTools is configured) — gated behind an env var\n * the same way reminders are gated behind `remindersFile`. Bash itself is\n * never added to --tools regardless of this setting — see the class doc\n * comment. Kept as a separate option from fileTools so a caller can enable\n * shell without file access or vice versa, even though `workspaceRoot` is\n * typically the same value for both on this backend.\n */\n shellTools?: { workspaceRoot: string }\n /**\n * When set, also registers `web_search` on the same MCP server (started\n * whenever fileTools, shellTools, or webTools is configured), backed by the\n * Brave Search API (the only backend — see web-search-provider.ts). Gated\n * behind the `BRAVE_SEARCH_API_KEY` env var passed to the server, the same\n * way shell is gated behind `ENABLE_SHELL_TOOLS`. `fetch_url` is registered\n * independently whenever the server runs at all — this option only adds the\n * search half.\n */\n webTools?: { braveApiKey?: string }\n /**\n * When set, also registers send_email on the same MCP server (started whenever any of\n * fileTools/shellTools/webTools/actionTools is configured), gated behind an env var the same\n * way run_shell_command is. The MCP server only ever *stages* the message — the parent process\n * delivers it, after approval, through its own configured SendEmail transport (email.ts /\n * email-smtp.ts). Kept separate from fileTools so email can be enabled without file access.\n */\n actionTools?: { workspaceRoot: string }\n /**\n * Default model passed as `--model` to every `claude -p` call when the per-call\n * `ChatOptions.model` doesn't already set one. The benchmark (Plan A1) uses this to pin every\n * audit run to a stated model instead of silently inheriting the CLI default. `undefined` keeps\n * the pre-A1 behaviour (no `--model` flag → CLI default).\n */\n model?: string\n}\n\n/**\n * Runs `claude` with cwd pinned to the OS temp directory, never the caller's actual\n * working directory — `claude` has no flag to suppress project-context loading, it\n * infers it entirely from the process's cwd (project CLAUDE.md, .mcp.json, skills, all\n * auto-loaded regardless of --system-prompt/--tools). Since PersonalAssistant's whole\n * point is a plain personal-assistant persona, not a coding agent scoped to whatever\n * repo happens to be the launch directory, every invocation runs from a directory that's\n * essentially guaranteed to have none of that project-level config to auto-load.\n */\nfunction invokeClaude(claudePath: string, args: string[]): Promise<ParsedClaudeCliOutput> {\n return new Promise((resolvePromise, reject) => {\n const proc = spawn(claudePath, args, { cwd: tmpdir(), stdio: ['ignore', 'pipe', 'pipe'] })\n let stdout = ''\n let stderr = ''\n proc.stdout.on('data', (chunk) => { stdout += chunk })\n proc.stderr.on('data', (chunk) => { stderr += chunk })\n proc.on('error', reject)\n proc.on('close', (code) => {\n if (code !== 0) {\n reject(new Error(stderr.trim() || `claude exited with code ${code}`))\n return\n }\n resolvePromise(parseClaudeCliOutput(stdout))\n })\n })\n}\n\n/**\n * Same subprocess-invocation contract as invokeClaude, but for a `--output-format\n * stream-json` call (requires `--verbose` — enforced by whichever caller builds `args`):\n * parses newline-delimited JSON events as they arrive so tool_use blocks can be reported\n * live via onToolStep — otherwise invisible, since Claude Code's own agentic loop resolves\n * every tool call inside this one subprocess call before returning. The final answer still\n * comes from the last `result`-type event, in the exact same shape/field names\n * --output-format json's single object uses, so parseClaudeCliOutput's existing\n * result/content/fallback logic covers it unchanged — just handed one line instead of the\n * whole stdout.\n */\nfunction invokeClaudeStreaming(claudePath: string, args: string[], onToolStep?: (event: ToolStepEvent) => void): Promise<ParsedClaudeCliOutput> {\n return new Promise((resolvePromise, reject) => {\n const proc = spawn(claudePath, args, { cwd: tmpdir(), stdio: ['ignore', 'pipe', 'pipe'] })\n let buffer = ''\n let stderr = ''\n let finalResultLine: string | undefined\n\n proc.stdout.on('data', (chunk: Buffer) => {\n buffer += chunk.toString('utf-8')\n let newlineIndex: number\n while ((newlineIndex = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, newlineIndex)\n buffer = buffer.slice(newlineIndex + 1)\n if (!line.trim()) continue\n\n let event: { type?: string; message?: { content?: unknown[] } }\n try {\n event = JSON.parse(line)\n } catch {\n continue // stream-json is one complete JSON object per line — an unparseable line is never expected, but must never crash the stream\n }\n\n if (event.type === 'assistant' && onToolStep) {\n for (const block of event.message?.content ?? []) {\n if ((block as { type?: string }).type !== 'tool_use') continue\n const toolUse = block as { name: string; input?: Record<string, unknown> }\n onToolStep({ tool: stripMcpToolPrefix(toolUse.name), input: toolUse.input ?? {} })\n }\n } else if (event.type === 'result') {\n finalResultLine = line\n }\n }\n })\n proc.stderr.on('data', (chunk) => { stderr += chunk })\n proc.on('error', reject)\n proc.on('close', (code) => {\n if (code !== 0) {\n reject(new Error(stderr.trim() || `claude exited with code ${code}`))\n return\n }\n resolvePromise(parseClaudeCliOutput(finalResultLine ?? ''))\n })\n })\n}\n\n/**\n * Phase D0 (harness_consolidation_and_control_plane_plan.html): a synchronous, per-call gate the\n * file-tools MCP server (spawned as this `claude` subprocess's own child — see\n * file-tools-mcp-server.mjs) blocks on before executing a read-only tool (read_file/\n * list_directory/fetch_url/web_search/create_reminder/list_reminders). write_file/\n * run_shell_command/send_email are unaffected — they already stage unconditionally and never\n * call this gate.\n *\n * Without this, Claude Code's own agentic loop resolves those read-only calls entirely inside\n * this one subprocess call: the deterministic, harness-informed check the proxy backend's manual\n * tool loop already runs before every call (agent-loop.ts's checkToolPolicy) never ran for this\n * backend at all — onToolStep only reports the call after Claude Code has already decided to\n * make it, too late to gate.\n *\n * Listens on an ephemeral loopback TCP port for the lifetime of one callChatStructured call;\n * newline-delimited JSON request/response, one request per tool call (`{tool, input}` in,\n * `{decision, reason?}` out — see ChatOptions.onToolProposal). Absent `onToolProposal` (a caller\n * that hasn't wired the gate), or an unparseable request, every proposal is allowed — the\n * pre-D0 behavior, unchanged.\n */\nfunction startToolGateServer(\n onToolProposal?: (tool: string, input: Record<string, unknown>) => Promise<ToolProposalDecision>,\n): Promise<{ server: Server; port: number }> {\n return new Promise((resolvePromise, reject) => {\n const server = createServer((socket) => {\n let buffer = ''\n socket.on('data', (chunk: Buffer) => {\n buffer += chunk.toString('utf-8')\n let newlineIndex: number\n while ((newlineIndex = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, newlineIndex)\n buffer = buffer.slice(newlineIndex + 1)\n if (!line.trim()) continue\n void (async (requestLine: string) => {\n let request: { tool?: unknown; input?: unknown }\n try {\n request = JSON.parse(requestLine)\n } catch {\n socket.write(`${JSON.stringify({ decision: 'allow' })}\\n`)\n return\n }\n let decision: ToolProposalDecision\n try {\n decision =\n onToolProposal && typeof request.tool === 'string'\n ? await onToolProposal(request.tool, (request.input as Record<string, unknown>) ?? {})\n : { decision: 'allow' }\n } catch (err) {\n // onToolProposal (agent-loop.ts's wiring) can throw — e.g. a tracing hook\n // (onTrace) called from inside checkToolPolicy. Left uncaught, this rejection\n // is unhandled inside the fire-and-forget IIFE below and crashes the whole\n // process under Node's default throw-on-unhandledRejection. Fail open here,\n // the same posture file-tools-mcp-server.mjs's requestToolGate already takes\n // on its own transport errors (ADR-003 decision 10's injection-detector\n // fail-open) — this is defense-in-depth on top of the real sandboxing/staging\n // boundaries, not itself the security control.\n console.error('claude-cli tool gate: onToolProposal threw — failing open:', err)\n decision = { decision: 'allow' }\n }\n socket.write(`${JSON.stringify(decision)}\\n`)\n })(line)\n }\n })\n })\n server.on('error', reject)\n server.listen(0, '127.0.0.1', () => {\n const address = server.address()\n if (address === null || typeof address === 'string') {\n reject(new Error('tool gate server failed to bind to a loopback TCP port'))\n return\n }\n resolvePromise({ server, port: address.port })\n })\n })\n}\n\n/**\n * ILLMClient backed by a local `claude -p` subprocess instead of a hosted API key —\n * runs PersonalAssistant against an already-authenticated Claude Code CLI session.\n * Always passes --system-prompt (so the CLI's own default prompt/CLAUDE.md/skills\n * never leak into an ordinary chat turn) and --tools \"\" (PersonalAssistant only\n * ever calls callChatSync — there's nothing here that expects tool use). **Bash is\n * never added to --tools, under any configuration** — run_shell_command instead goes\n * through the same MCP server as the file tools, which only stages (never executes),\n * exactly like write_file: once a built-in like Bash is active, Claude Code's own\n * agentic loop would execute it autonomously within the single `claude -p` call, with\n * no point at which our code sees the command before it runs.\n *\n * **onUsage's costUsd caveat:** token counts (`inputTokens`/`outputTokens`) are always real —\n * they come straight from `claude --output-format json`'s `usage` field. `costUsd` comes from\n * that same response's `total_cost_usd`, which reflects real API billing when the underlying\n * `claude` session is authenticated with an API key, but may read `0` when it's authenticated\n * against a Pro/Max subscription instead (no per-call dollar cost to report). A caller\n * presenting this to a user (see personal-assistant's /cost) must not read a `0` here as \"this\n * turn was free\" without that context.\n */\nexport class ClaudeCliLLMClient implements ILLMClient {\n private readonly claudePath: string\n private readonly fileTools?: { workspaceRoot: string }\n private readonly remindersFile?: string\n private readonly shellTools?: { workspaceRoot: string }\n private readonly webTools?: { braveApiKey?: string }\n private readonly actionTools?: { workspaceRoot: string }\n private readonly model?: string\n /**\n * The concrete model id the last `claude -p` call actually reported running against (read from\n * the response's own `model` field, not the CLI alias). `undefined` until a call resolves one.\n * Plan A1 records this into the benchmark report so a published table can name the model.\n */\n resolvedModelId?: string\n\n constructor(options: ClaudeCliLLMClientOptions = {}) {\n this.claudePath = options.claudePath ?? process.env.CLAUDE_PATH ?? 'claude'\n this.fileTools = options.fileTools\n this.remindersFile = options.remindersFile\n this.shellTools = options.shellTools\n this.webTools = options.webTools\n this.actionTools = options.actionTools\n this.model = options.model\n }\n\n async *callChat(messages: ChatMessage[], options: ChatOptions = {}): AsyncIterable<string> {\n yield await this.callChatSync(messages, options)\n }\n\n async callChatSync(messages: ChatMessage[], options: ChatOptions = {}): Promise<string> {\n const { systemPrompt, prompt } = buildClaudePrompt(messages)\n const args = [\n '--print',\n '--output-format', 'json',\n '--tools', '',\n '--no-session-persistence',\n '--system-prompt', systemPrompt,\n '--mcp-config', EMPTY_MCP_CONFIG,\n '--strict-mcp-config', // ignore any ambient project/user MCP config — see EMPTY_MCP_CONFIG's doc comment\n ]\n const model = options.model ?? this.model\n if (model) args.push('--model', model)\n args.push(prompt)\n const { reply, usage, model: resolved } = await invokeClaude(this.claudePath, args)\n if (resolved) this.resolvedModelId = resolved\n if (usage) options.onUsage?.(usage)\n return reply\n }\n\n /**\n * When `tools` is non-empty and `fileTools`/`shellTools` was configured, wires the\n * file-tools MCP server into a single `claude -p` call and lets Claude Code's own\n * agentic loop call read_file/list_directory/write_file/fetch_url/create_reminder/\n * list_reminders/run_shell_command autonomously — we don't get to intercept each\n * call the way the proxy backend's manual tool loop does (see\n * plans/personal_assistant_file_tools_plan.html, T6). fetch_url is always\n * registered on that server; create_reminder/list_reminders only when\n * `remindersFile` is set; run_shell_command only when `shellTools` is set;\n * web_search only when `webTools` is set (Brave Search — see the `webTools`\n * option). Three\n * possible outcomes: a final text reply (no tool call this backend needs to\n * surface), a write or shell command staged by the MCP server mid-call (surfaced\n * as a synthetic `__staged_action` tool call so assistant.ts's tool loop treats it\n * the same as a manually staged action without staging it a second time), or —\n * for fetch_url/create_reminder/list_reminders — the tool's result text folded\n * directly into Claude Code's own reply, since (unlike the proxy backend's\n * `executeToolCall`) there's no outer loop here to intercept each call and\n * re-apply trust-tagging itself; the MCP server tags fetch_url's result before\n * Claude Code ever sees it instead (see file-tools-mcp-server.mjs).\n */\n async callChatStructured(messages: ChatMessage[], tools?: ToolDefinition[], options: ChatOptions = {}): Promise<LLMStructuredResponse> {\n if (!tools || tools.length === 0) {\n // See stripJsonCodeFence's doc comment: this backend has no real schema-constrained\n // output mode, so a caller expecting bare JSON in `content` (per options.structuredOutput)\n // needs any markdown fence the model wrapped it in removed here, once, rather than in\n // every individual JSON.parse(response.content) call site.\n const content = await this.callChatSync(messages, options)\n return { content: options.structuredOutput ? stripJsonCodeFence(content) : content }\n }\n if (!this.fileTools && !this.shellTools && !this.webTools && !this.actionTools) {\n throw new Error(\n 'ClaudeCliLLMClient does not support tool calls unless constructed with fileTools, shellTools, webTools, or actionTools configured',\n )\n }\n\n const { systemPrompt, prompt } = buildClaudePrompt(messages)\n // web_search / send_email need no workspace; fall back to the temp dir when one of those is\n // the only capability configured (the CLI always sets fileTools for this backend, so in\n // practice this is the real workspace root).\n const workspaceRoot =\n this.fileTools?.workspaceRoot ?? this.shellTools?.workspaceRoot ?? this.actionTools?.workspaceRoot ?? tmpdir()\n // The current turn's raw user message, before Claude Code's own agentic loop (and any\n // rewording it does before calling a tool) ever sees it — passed through to the MCP\n // server so create_reminder can check the *original* phrasing against fact-shaped\n // markers, not just whatever `text` the model decided to pass as the tool argument\n // (which is routinely reworded to third person, e.g. \"I'm allergic to peanuts, please\n // remember that\" → \"User is allergic to peanuts\", dodging a check against `text` alone).\n const lastUserMessage = [...messages].reverse().find((m) => m.role === 'user')?.content ?? ''\n // Deliberately not `new URL('./file-tools-mcp-server.mjs', import.meta.url)` as a\n // single literal — Vite's asset-URL plugin statically detects that exact pattern\n // and inlines the whole file as a base64 data: URL at build time, which\n // fileURLToPath can't turn back into a real path. Building the specifier from a\n // variable keeps this a plain runtime URL resolution instead.\n const mcpServerFileName = 'file-tools-mcp-server.mjs'\n const mcpServerPath = fileURLToPath(new URL(mcpServerFileName, import.meta.url))\n // Phase D0: gates read_file/list_directory/fetch_url/web_search/create_reminder/\n // list_reminders — see startToolGateServer's doc comment. Started before the config is\n // built (its port needs to go into TOOL_GATE_PORT) and always closed once this call\n // finishes, gate-decision or not.\n const gate = await startToolGateServer(options.onToolProposal)\n try {\n const mcpConfig = JSON.stringify({\n mcpServers: {\n 'file-tools': {\n command: 'node',\n args: [mcpServerPath],\n env: {\n WORKSPACE_ROOT: workspaceRoot,\n TOOL_GATE_PORT: String(gate.port),\n ...(this.remindersFile ? { REMINDERS_FILE: this.remindersFile, CURRENT_USER_MESSAGE: lastUserMessage } : {}),\n ...(this.shellTools ? { ENABLE_SHELL_TOOLS: '1' } : {}),\n ...(this.webTools?.braveApiKey ? { BRAVE_SEARCH_API_KEY: this.webTools.braveApiKey } : {}),\n ...(this.actionTools ? { ENABLE_EMAIL_TOOL: '1' } : {}),\n },\n },\n },\n })\n\n const args = [\n '--print',\n '--output-format', 'stream-json', // streamed (not the single-object 'json') so tool_use events can be reported live via onToolStep — see invokeClaudeStreaming\n '--verbose', // required by --print when --output-format is stream-json\n '--tools', '', // still disable Claude Code's own built-in Read/Write/Bash tools — Bash is never added, regardless of shellTools\n '--no-session-persistence',\n '--system-prompt', systemPrompt,\n '--mcp-config', mcpConfig,\n '--strict-mcp-config', // ignore any ambient project .mcp.json — the tool surface must be exactly this plan's tools\n '--dangerously-skip-permissions', // headless -p mode has no way to answer an interactive tool-permission prompt\n ]\n const model = options.model ?? this.model\n if (model) args.push('--model', model)\n args.push(prompt)\n\n const callStartedAt = Date.now()\n const { reply, usage, model: resolved } = await invokeClaudeStreaming(this.claudePath, args, options.onToolStep)\n if (resolved) this.resolvedModelId = resolved\n if (usage) options.onUsage?.(usage)\n const staged = await this.findPendingActionStagedSince(workspaceRoot, callStartedAt)\n\n if (staged) {\n return {\n content: '',\n toolCalls: [{ id: `cli-staged-${staged.id}`, name: ALREADY_STAGED_ACTION_TOOL, input: stagedActionInput(staged) }],\n }\n }\n return { content: reply }\n } finally {\n gate.server.close()\n }\n }\n\n /** Diffs .pending-actions/ against the call's start time to detect a write or shell command the MCP server staged during this subprocess call. */\n /**\n * When the MCP server staged more than one action this call (e.g. \"run X AND write Y\" —\n * see file-tools-mcp-server.mjs's stagePendingAction doc comment), every one of them lands\n * in `.pending-actions/` with an mtime after `startTimeMs`, in `readdir`-arbitrary order —\n * not necessarily staging order. Must return the *chain head* (earliest `stagedAt`)\n * specifically, since resolvePendingAction (assistant.ts) walks `nextPendingActionId`\n * forward from whichever record it's handed; returning a later link here would surface the\n * chain out of order and orphan the earlier, still-unresolved action.\n */\n private async findPendingActionStagedSince(workspaceRoot: string, startTimeMs: number): Promise<PendingActionRecord | undefined> {\n const dir = `${workspaceRoot}/.pending-actions`\n let names: string[]\n try {\n names = await readdir(dir)\n } catch {\n return undefined\n }\n let earliest: PendingActionRecord | undefined\n for (const name of names) {\n if (!name.endsWith('.json')) continue\n const filePath = `${dir}/${name}`\n const stats = await stat(filePath)\n // Small buffer against filesystem mtime rounding (e.g. 1s resolution on some\n // filesystems) being coarser than Date.now()'s precision.\n if (stats.mtimeMs < startTimeMs - 1000) continue\n const record = JSON.parse(await readFile(filePath, 'utf-8')) as PendingActionRecord\n if (!earliest || record.stagedAt < earliest.stagedAt) earliest = record\n }\n return earliest\n }\n}\n","import { createServer, connect as netConnect, type Socket, type Server } from 'node:net'\n\n/**\n * Node-level network containment for an approved run_shell_command execution — Decision 6 of\n * plans/lexical_functions_hardening_plan.html (Phase 4 step 2). This is the network-reachability\n * half of that decision; the env-stripping half already existed in shell-executor.ts's\n * allowlistedEnv(). Deliberately not a real OS sandbox (see the plan's Decision 6 for why that\n * tradeoff was chosen): a minimal loopback-only forward proxy that relays a CONNECT tunnel\n * (HTTPS) or a plain-HTTP proxy request only when its target host matches an entry in the\n * caller-supplied allowlist (exact match or subdomain), and otherwise closes the connection with\n * a 403. The spawned subprocess's HTTP(S)_PROXY env vars are forced to point here, so a tool that\n * honors those (curl, wget, most language HTTP clients) can only reach an allowlisted host. This\n * does NOT stop a tool that opens raw sockets and ignores proxy env vars entirely — that\n * limitation is Decision 6's explicitly accepted tradeoff for a dependency-free, Node-only\n * implementation identical across CLI and desktop.\n */\n\nexport interface NetworkContainmentProxy {\n port: number\n close: () => Promise<void>\n}\n\nconst MAX_HEADER_BYTES = 16_384\n\nfunction hostAllowed(host: string, allowlist: readonly string[]): boolean {\n const normalized = host.toLowerCase()\n return allowlist.some((entry) => {\n const allowed = entry.toLowerCase()\n return normalized === allowed || normalized.endsWith(`.${allowed}`)\n })\n}\n\nfunction parseConnectTarget(requestLine: string): { host: string; port: number } | null {\n const match = /^CONNECT\\s+([^:\\s]+):(\\d+)\\s+HTTP/i.exec(requestLine)\n return match ? { host: match[1], port: Number(match[2]) } : null\n}\n\nfunction parsePlainHttpTarget(requestLine: string, headerText: string): { host: string; port: number } | null {\n const absoluteUri = /^[A-Z]+\\s+https?:\\/\\/([^/:\\s]+)(?::(\\d+))?/i.exec(requestLine)\n if (absoluteUri) return { host: absoluteUri[1], port: Number(absoluteUri[2] ?? 80) }\n\n const hostHeader = /^host:\\s*([^\\s:]+)(?::(\\d+))?/im.exec(headerText)\n return hostHeader ? { host: hostHeader[1], port: Number(hostHeader[2] ?? 80) } : null\n}\n\nfunction handleConnection(clientSocket: Socket, allowlist: readonly string[]): void {\n let buffered = Buffer.alloc(0)\n\n const onData = (chunk: Buffer): void => {\n buffered = Buffer.concat([buffered, chunk])\n const headerEnd = buffered.indexOf('\\r\\n\\r\\n')\n if (headerEnd === -1) {\n if (buffered.length > MAX_HEADER_BYTES) clientSocket.destroy()\n return\n }\n clientSocket.removeListener('data', onData)\n\n const headerText = buffered.slice(0, headerEnd).toString('utf-8')\n const requestLine = headerText.split('\\r\\n')[0] ?? ''\n const isConnect = /^CONNECT\\s/i.test(requestLine)\n const target = isConnect ? parseConnectTarget(requestLine) : parsePlainHttpTarget(requestLine, headerText)\n\n if (!target || !hostAllowed(target.host, allowlist)) {\n clientSocket.end('HTTP/1.1 403 Forbidden\\r\\nContent-Length: 0\\r\\n\\r\\n')\n return\n }\n\n const upstream = netConnect(target.port, target.host, () => {\n if (isConnect) {\n clientSocket.write('HTTP/1.1 200 Connection Established\\r\\n\\r\\n')\n const tunnelBytes = buffered.slice(headerEnd + 4)\n if (tunnelBytes.length > 0) upstream.write(tunnelBytes)\n } else {\n upstream.write(buffered)\n }\n clientSocket.pipe(upstream)\n upstream.pipe(clientSocket)\n })\n upstream.on('error', () => clientSocket.destroy())\n }\n\n clientSocket.on('data', onData)\n clientSocket.on('error', () => clientSocket.destroy())\n}\n\nfunction startProxyServer(allowlist: readonly string[]): Promise<NetworkContainmentProxy> {\n return new Promise((resolveStart, rejectStart) => {\n const server: Server = createServer((socket) => handleConnection(socket, allowlist))\n server.on('error', rejectStart)\n server.listen(0, '127.0.0.1', () => {\n const address = server.address()\n if (address === null || typeof address === 'string') {\n rejectStart(new Error('Failed to bind network containment proxy to a loopback port'))\n return\n }\n resolveStart({\n port: address.port,\n close: () => new Promise((resolveClose) => server.close(() => resolveClose())),\n })\n })\n })\n}\n\n/**\n * Lazily starts (or reuses) a loopback-only proxy scoped to `allowlist`, keyed by its\n * (order-independent, case-insensitive) contents so repeated calls with the same allowlist reuse\n * one long-lived proxy instead of spawning a new server per shell command. Left running for the\n * process's lifetime — acceptable for a CLI/desktop session, not meant to be torn down mid-run.\n */\nconst proxyCache = new Map<string, Promise<NetworkContainmentProxy>>()\n\nexport function getNetworkContainmentProxy(allowlist: readonly string[]): Promise<NetworkContainmentProxy> {\n const key = JSON.stringify([...allowlist].map((host) => host.toLowerCase()).sort())\n let proxy = proxyCache.get(key)\n if (!proxy) {\n proxy = startProxyServer(allowlist)\n proxyCache.set(key, proxy)\n }\n return proxy\n}\n\n/** Test-only: drops all cached proxies so a test's allowlist doesn't leak into another test's expectations. */\nexport async function resetNetworkContainmentProxiesForTests(): Promise<void> {\n const proxies = [...proxyCache.values()]\n proxyCache.clear()\n for (const proxyPromise of proxies) {\n const proxy = await proxyPromise\n await proxy.close()\n }\n}\n","import { spawn } from 'node:child_process'\nimport type { ShellExecutionResult } from './file-tools.js'\nimport type { ShellCommandExecutor } from './shell-tools.js'\nimport { getNetworkContainmentProxy } from './network-containment.js'\n\n/**\n * The real child_process.spawn-based implementation of ShellCommandExecutor — deliberately\n * not exported from this package's index (mirrors node-fs-backend.ts): assistant.ts is bundled\n * into the browser build too, so only a Node-only caller (cli.ts) may import this module and\n * wire it in as PersonalAssistantOptions.shellTools.executeCommand.\n */\n\nconst DEFAULT_TIMEOUT_MS = 30_000\nconst DEFAULT_MAX_OUTPUT_BYTES = 20_000\n/** Never the parent process's full env (which would carry ASSISTANT_PROXY_TOKEN/ANTHROPIC_API_KEY/etc. into the command) — only these three. */\nconst ALLOWED_ENV_VARS = ['PATH', 'HOME', 'LANG'] as const\n\nfunction allowlistedEnv(): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = {}\n for (const key of ALLOWED_ENV_VARS) {\n const value = process.env[key]\n if (value !== undefined) env[key] = value\n }\n return env\n}\n\n/**\n * Forces the child's HTTP(S)_PROXY env vars at a loopback-only containment proxy scoped to\n * `networkAllowlist` (see network-containment.ts) — Decision 6's network-reachability half,\n * alongside allowlistedEnv()'s existing secret-stripping half. An empty/undefined allowlist still\n * starts the proxy, just with nothing in it, so every attempt is refused with a 403 rather than\n * skipping containment entirely — deny-all is the safe default until the user opts a host in.\n *\n * Deliberately does NOT set NO_PROXY/no_proxy for 127.0.0.1/localhost: most HTTP clients (curl\n * included) skip the configured proxy entirely for any host listed in NO_PROXY, which would let a\n * command reach an arbitrary loopback service (a local metadata endpoint, an unauthenticated\n * internal API, a Docker socket proxy) without ever going through the allowlist check — a\n * loopback target is not inherently safe and must be subject to the same allowlist as anything\n * else. The one loopback address that does need to stay reachable unconditionally is the\n * containment proxy's own listener, which this env never routes through itself (a client connects\n * to it directly via the proxy env vars, not through the proxy), so no exemption is needed there.\n */\nasync function networkContainmentEnv(networkAllowlist: readonly string[]): Promise<NodeJS.ProcessEnv> {\n const proxy = await getNetworkContainmentProxy(networkAllowlist)\n const proxyUrl = `http://127.0.0.1:${proxy.port}`\n return {\n HTTP_PROXY: proxyUrl,\n http_proxy: proxyUrl,\n HTTPS_PROXY: proxyUrl,\n https_proxy: proxyUrl,\n }\n}\n\nfunction truncateOutput(text: string, maxBytes: number): string {\n const encoded = new TextEncoder().encode(text)\n if (encoded.length <= maxBytes) return text\n const truncated = new TextDecoder('utf-8', { fatal: false }).decode(encoded.slice(0, maxBytes))\n return `${truncated}\\n… (truncated)`\n}\n\n/**\n * Actually runs a previously staged, already-sandboxed command — the real child_process.spawn call,\n * invoked only at approval time via file-tools.ts's applyPendingAction(..., { executeShell }). `cwd`\n * is pinned to the staged (already-validated) path; `env` is reduced to an explicit allowlist so a\n * secret like ASSISTANT_PROXY_TOKEN/ANTHROPIC_API_KEY can't leak into the command's environment,\n * plus network-containment.ts's loopback-only proxy forced via HTTP(S)_PROXY (Decision 6 — see\n * networkContainmentEnv's doc comment). A hard timeout SIGKILLs the whole process group (not just\n * the immediate child — `detached: true` + a negative-pid kill reaches anything the shell itself\n * spawned) rather than leaving it running. A non-zero exit code is not a thrown error — it's\n * reported normally, same as a real shell; only a spawn failure (e.g. the shell itself couldn't\n * start) throws.\n */\nexport const runApprovedShellCommand: ShellCommandExecutor = async (\n command: string,\n cwd: string,\n options: { timeoutMs?: number; maxOutputBytes?: number; networkAllowlist?: string[] } = {},\n): Promise<ShellExecutionResult> => {\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS\n const maxOutputBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES\n const containmentEnv = await networkContainmentEnv(options.networkAllowlist ?? [])\n\n return new Promise((resolvePromise, reject) => {\n const proc = spawn(command, {\n shell: true,\n cwd,\n env: { ...allowlistedEnv(), ...containmentEnv },\n detached: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n\n let output = ''\n let timedOut = false\n let settled = false\n\n const timer = setTimeout(() => {\n timedOut = true\n try {\n if (proc.pid) process.kill(-proc.pid, 'SIGKILL')\n else proc.kill('SIGKILL')\n } catch {\n proc.kill('SIGKILL')\n }\n }, timeoutMs)\n\n proc.stdout?.on('data', (chunk: Buffer) => {\n output += chunk.toString('utf-8')\n })\n proc.stderr?.on('data', (chunk: Buffer) => {\n output += chunk.toString('utf-8')\n })\n proc.on('error', (err: Error) => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n reject(err)\n })\n proc.on('close', (exitCode: number | null) => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n resolvePromise({\n output: truncateOutput(output, maxOutputBytes),\n exitCode: timedOut ? null : exitCode,\n timedOut,\n })\n })\n })\n}\n","import { readFile, writeFile, rename, mkdir } from 'node:fs/promises'\nimport { randomBytes } from 'node:crypto'\nimport { dirname } from 'node:path'\nimport type { AssistantConfig, ConfigStore } from './config.js'\n\nfunction isEnoent(err: unknown): boolean {\n return (err as NodeJS.ErrnoException)?.code === 'ENOENT'\n}\n\n/**\n * JSON-file-backed ConfigStore for the CLI, at a single `config.json` path (typically\n * `<dataDir>/config.json`). Deliberately not exported from this package's index — only cli.ts\n * imports it, same reasoning as node-fs-backend.ts — so a browser build never pulls in\n * node:fs/promises.\n *\n * save() merges the patch onto whatever's already persisted (never a blind overwrite of\n * unrelated keys) and writes through a temp-file-then-rename so a crash mid-write can never\n * leave a half-written config.json behind — the same failure mode file-tools.ts's\n * .pending-actions staging already avoids the same way. Setting a key to `undefined` in a\n * patch removes it from the persisted file (JSON.stringify drops undefined-valued\n * properties), which is what /config reset relies on.\n *\n * Concurrent save() calls on the same instance are serialized through an internal queue —\n * cli.ts's readline 'line' handler doesn't wait for one command to finish before dispatching\n * the next (e.g. piped stdin with two /config set lines back to back), so without this a\n * second save()'s load-modify-write could race the first's: reading stale state before the\n * first's write lands and silently clobbering it, or (with a timestamp-only temp filename)\n * colliding on the exact same temp path within the same millisecond.\n */\nexport class NodeConfigStore implements ConfigStore {\n private queue: Promise<unknown> = Promise.resolve()\n\n constructor(private readonly path: string) {}\n\n async load(): Promise<Partial<AssistantConfig>> {\n let raw: string\n try {\n raw = await readFile(this.path, 'utf-8')\n } catch (err) {\n if (isEnoent(err)) return {}\n throw err\n }\n try {\n return JSON.parse(raw) as Partial<AssistantConfig>\n } catch {\n console.error(`Warning: ${this.path} is not valid JSON — ignoring it and falling back to defaults.`)\n return {}\n }\n }\n\n save(patch: Partial<AssistantConfig>): Promise<void> {\n const task = this.queue.then(() => this.writePatch(patch))\n // A failed save must not poison the queue for saves queued after it.\n this.queue = task.catch(() => {})\n return task\n }\n\n private async writePatch(patch: Partial<AssistantConfig>): Promise<void> {\n const existing = await this.load()\n const merged = { ...existing, ...patch }\n await mkdir(dirname(this.path), { recursive: true })\n const tempPath = `${this.path}.tmp-${process.pid}-${randomBytes(6).toString('hex')}`\n await writeFile(tempPath, JSON.stringify(merged, null, 2), 'utf-8')\n await rename(tempPath, this.path)\n }\n}\n","import { stat } from 'node:fs/promises'\nimport { spawn } from 'node:child_process'\nimport { join } from 'node:path'\nimport type { FsBackend } from '@buildaharness/runtime'\nimport type { DoctorCheck } from './cli-session.js'\n\n/**\n * Real I/O backing /doctor's checks (network fetch, subprocess spawn, filesystem stat) —\n * split out of cli.ts, deliberately not exported from this package's index (mirrors\n * shell-executor.ts/node-fs-backend.ts): only cli.ts, a Node-only caller, imports this.\n * cli-session.ts's formatDoctorReport does the actual rendering — this file only produces\n * the DoctorCheck[] input to that.\n */\n\nconst DOCTOR_CHECK_TIMEOUT_MS = 3000\n\n/** GET <proxyUrl>/health, expecting {status:'ok'} (see packages/proxy/src/index.ts) — hard-timed so a dead proxy can't hang /doctor. */\nexport async function checkProxyHealth(proxyUrl: string): Promise<DoctorCheck> {\n const label = `proxy reachable (${proxyUrl}/health)`\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), DOCTOR_CHECK_TIMEOUT_MS)\n try {\n const res = await fetch(`${proxyUrl}/health`, { signal: controller.signal })\n if (!res.ok) return { label, ok: false, detail: `HTTP ${res.status}` }\n const body = (await res.json().catch(() => undefined)) as { status?: string } | undefined\n return body?.status === 'ok' ? { label, ok: true } : { label, ok: false, detail: 'unexpected response body' }\n } catch (err) {\n const timedOut = err instanceof Error && err.name === 'AbortError'\n return { label, ok: false, detail: timedOut ? 'timed out' : err instanceof Error ? err.message : String(err) }\n } finally {\n clearTimeout(timeout)\n }\n}\n\n/** Spawns `<claudePath> --version`, same path resolution ClaudeCliLLMClient uses — hard-timed so a hung/missing binary can't hang /doctor. */\nexport function checkClaudeCli(claudePath: string): Promise<DoctorCheck> {\n const label = `claude binary (${claudePath})`\n return new Promise((resolvePromise) => {\n let settled = false\n const proc = spawn(claudePath, ['--version'], { stdio: ['ignore', 'ignore', 'ignore'] })\n const timeout = setTimeout(() => {\n if (settled) return\n settled = true\n proc.kill()\n resolvePromise({ label, ok: false, detail: 'timed out' })\n }, DOCTOR_CHECK_TIMEOUT_MS)\n proc.on('error', () => {\n if (settled) return\n settled = true\n clearTimeout(timeout)\n resolvePromise({ label, ok: false, detail: 'not found' })\n })\n proc.on('close', (code) => {\n if (settled) return\n settled = true\n clearTimeout(timeout)\n resolvePromise({ label, ok: code === 0, detail: code === 0 ? undefined : `exited with code ${code}` })\n })\n })\n}\n\nexport async function checkWorkspaceRoot(workspaceRoot: string): Promise<DoctorCheck> {\n const label = `workspace root exists (${workspaceRoot})`\n try {\n const info = await stat(workspaceRoot)\n return info.isDirectory() ? { label, ok: true } : { label, ok: false, detail: 'not a directory' }\n } catch {\n return { label, ok: false, detail: 'not found' }\n }\n}\n\nexport async function checkDataDirWritable(backend: FsBackend, dataDir: string): Promise<DoctorCheck> {\n const label = `data dir writable (${dataDir})`\n const probePath = join(dataDir, '.doctor-check')\n try {\n await backend.mkdir(dataDir)\n await backend.writeTextFile(probePath, 'ok')\n await backend.removeFile(probePath)\n return { label, ok: true }\n } catch (err) {\n return { label, ok: false, detail: err instanceof Error ? err.message : String(err) }\n }\n}\n","export type NonInteractiveApprovalMode = 'decline' | 'require-tty'\n\n/**\n * Pure logic backing cli.ts's ASSISTANT_NON_INTERACTIVE_APPROVAL handling — split out so it's\n * unit-testable in isolation, the same way error-classifier.ts and node-display-names.ts are\n * split out of cli.ts for the same reason (cli.ts runs `main()` at import time, so it can't be\n * imported directly by a test).\n *\n * Turns the piped/scripted-stdin approval-gate behavior into an explicit, documented choice\n * instead of an incidental side effect of readline hitting closed stdin (see cli.ts's askYesNo,\n * whose fail-closed catch stays the unset-env-var default and is unaffected by this — nothing\n * changes for an interactive TTY session either way). 'decline': every approval gate\n * auto-declines immediately, without ever touching stdin — the right choice for a scripted/CI\n * caller that expects every HIGH-risk/staged action to be rejected outright, no exceptions.\n * 'require-tty': cli.ts fails fast at startup with a clear, actionable error if stdin isn't a\n * real TTY, instead of running an entire session that can only surface the problem deep in, at\n * the first approval prompt, as a generic \"[could not read a response]\". An unrecognized value is\n * ignored (falls back to unset) with a startup warning, so a typo can't silently produce\n * different behavior than intended — mirrors ASSISTANT_LLM_BACKEND's own typo-tolerance in\n * cli-config.ts, but warns here since a silently-ignored approval-mode typo is safety-relevant in\n * a way a silently-ignored backend typo (which still resolves to a working default) isn't.\n */\nexport function resolveNonInteractiveApprovalMode(env: NodeJS.ProcessEnv): NonInteractiveApprovalMode | undefined {\n const raw = env.ASSISTANT_NON_INTERACTIVE_APPROVAL\n if (raw === undefined) return undefined\n if (raw === 'decline' || raw === 'require-tty') return raw\n console.error(`[warning] ASSISTANT_NON_INTERACTIVE_APPROVAL=\"${raw}\" is not \"decline\" or \"require-tty\" — ignoring.`)\n return undefined\n}\n","/**\n * First-run setup for the CLI. `npx @buildaharness/aielia` with no\n * env vars and no persisted config used to land on the default `proxy` backend\n * pointed at `http://localhost:8787` — a proxy almost nobody running it for the\n * first time has, so every turn failed with a connection error and no hint about\n * what to do. This runs one short interactive pass instead: reuse an existing\n * `claude` CLI login if there is one, otherwise ask for a provider + API key,\n * and persist the choice via the same ConfigStore `/config set` writes to.\n *\n * Split out of cli.ts (like non-interactive-mode.ts / error-classifier.ts) so it\n * can be unit-tested without a live REPL: every side-effecting dependency —\n * reading a line, detecting the `claude` binary — is injected.\n */\n\nimport type { AssistantConfig, ConfigStore } from './config.js'\n\nexport interface FirstRunDeps {\n configStore: ConfigStore\n /** Result of `configStore.load()` — the setup is skipped entirely if this already names a backend/key. */\n persisted: Partial<AssistantConfig>\n /** Keys pinned by an env var (ASSISTANT_LLM_BACKEND etc.) — also a signal the user has already chosen. */\n overriddenKeys: ReadonlySet<keyof AssistantConfig>\n /** False for piped/scripted stdin — setup is skipped and today's behavior (proxy default) is kept. */\n isInteractive: boolean\n /** Reads one line from the user, already trimmed. */\n ask: (question: string) => Promise<string>\n /** True when an authenticated `claude` CLI is available on PATH. */\n detectClaudeCli: () => Promise<boolean>\n /** Where to print prompts/status (defaults to process.stdout in cli.ts). */\n log: (line: string) => void\n}\n\n/** Any of these present means the user has already configured a backend — don't re-prompt. */\nfunction alreadyConfigured(deps: FirstRunDeps): boolean {\n const { persisted, overriddenKeys } = deps\n if (persisted.llmBackend !== undefined) return true\n if (persisted.apiKey !== undefined || persisted.authToken !== undefined) return true\n for (const key of ['llmBackend', 'apiKey', 'authToken', 'proxyUrl'] as const) {\n if (overriddenKeys.has(key)) return true\n }\n return false\n}\n\nconst PROVIDER_CHOICES: Record<string, { backend: AssistantConfig['llmBackend']; label: string }> = {\n '1': { backend: 'anthropic', label: 'Anthropic (Claude) — needs an sk-ant-… key' },\n '2': { backend: 'openai', label: 'OpenAI — needs an sk-… key' },\n '3': { backend: 'openrouter', label: 'OpenRouter — needs an sk-or-… key' },\n}\n\n/**\n * Runs the first-run pass if needed. Returns the persisted config to use from\n * here on — unchanged when setup was skipped or the user bailed out, or a fresh\n * object reflecting what was just written.\n */\nexport async function maybeRunFirstRunSetup(deps: FirstRunDeps): Promise<Partial<AssistantConfig>> {\n const { configStore, persisted, isInteractive, ask, detectClaudeCli, log } = deps\n\n if (alreadyConfigured(deps)) return persisted\n if (!isInteractive) return persisted\n\n log('')\n log(\"Welcome to Aielia — this looks like your first run. Let's pick how to reach a model.\")\n log('(You can change any of this later with /config, or skip with Ctrl+C.)')\n log('')\n\n const patch: Partial<AssistantConfig> = {}\n\n if (await detectClaudeCli()) {\n const useClaude = await ask(\n 'Found an authenticated `claude` CLI on your PATH. Use it? No API key needed. (Y/n) ',\n )\n if (useClaude === '' || useClaude.toLowerCase().startsWith('y')) {\n patch.llmBackend = 'claude-cli'\n await configStore.save(patch)\n log('')\n log('✓ Using the claude-cli backend. Try \"what time zone is Tokyo in?\", then')\n log(' \"send an email to my boss saying I quit\" to see the approval gate.')\n log('')\n return { ...persisted, ...patch }\n }\n }\n\n log('Pick a provider to use with your own API key:')\n for (const [key, { label }] of Object.entries(PROVIDER_CHOICES)) log(` ${key}) ${label}`)\n const choice = (await ask('Provider [1-3, or Enter to skip]: ')).trim()\n const picked = PROVIDER_CHOICES[choice]\n if (!picked) {\n log('')\n log('Skipped. The assistant will start on the \"proxy\" backend (needs @buildaharness/proxy')\n log('on :8787). Run /config set llmBackend <anthropic|openai|openrouter> and')\n log('/config set apiKey <key> when you are ready, or set ASSISTANT_LLM_BACKEND + ASSISTANT_API_KEY.')\n log('')\n return persisted\n }\n\n const key = (await ask(`Paste your ${picked.backend} API key: `)).trim()\n if (!key) {\n log('')\n log('No key entered — skipping. Set it later with /config set apiKey <key>.')\n log('')\n return persisted\n }\n\n patch.llmBackend = picked.backend\n patch.apiKey = key\n await configStore.save(patch)\n log('')\n log(`✓ Saved. Using ${picked.backend}. The key is stored in plain text in your config file`)\n log(' (~/.buildaharness/personal-assistant/config.json) — the same trust boundary as a .env.')\n log('')\n return { ...persisted, ...patch }\n}\n","#!/usr/bin/env node\nimport { createInterface } from 'node:readline'\nimport { writeFile } from 'node:fs/promises'\nimport { realpathSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join, resolve as resolvePath } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport {\n LLMClient,\n AnthropicLLMClient,\n OpenAICompatibleLLMClient,\n ANTHROPIC_DEFAULT_MODEL,\n OPENAI_BASE_URL,\n OPENAI_DEFAULT_MODEL,\n OPENROUTER_BASE_URL,\n OPENROUTER_DEFAULT_MODEL,\n OPENROUTER_EXTRA_HEADERS,\n FileSystemAdapter,\n FileSystemExperienceStore,\n InMemoryReminderStore,\n type ILLMClient,\n type TokenUsage,\n type FsBackend,\n} from '@buildaharness/runtime'\nimport type { AskAnswer, AskQuestion, AskQuestionOption, AskResponse } from '@buildaharness/harness'\nimport { PersonalAssistant, type AssistantProgress, type AssistantTrace, type AssistantSource, type AssistantTurnResult } from './assistant.js'\nimport type { AssistantToolStep } from './tool-step.js'\nimport { nodeDisplayName, nodeToLayer, buildWhyChain, LAYER_ORDER, LAYER_DISPLAY_NAME, LAYER_SHORT_CODE } from './node-display-names.js'\nimport { classifyError } from './error-classifier.js'\nimport { createNodeFsBackend } from './node-fs-backend.js'\nimport { ClaudeCliLLMClient } from './claude-cli-llm-client.js'\nimport { runApprovedShellCommand } from './shell-executor.js'\nimport { createResendSender, type SendEmail } from './email.js'\nimport { createSmtpSender } from './email-smtp.js'\nimport { braveSearch } from './web-search-provider.js'\nimport { resolveConfig, validateConfig, ConfigValidationError, type AssistantConfig, type ConfigStore } from './config.js'\nimport { NodeConfigStore } from './node-config-store.js'\nimport { isConfigKey, envOverridesFromProcessEnv, parseConfigValue, ConfigValueParseError, formatConfigListing, ENV_VAR_FOR_CONFIG_KEY, CONFIG_KEYS } from './cli-config.js'\nimport { formatHelp, formatStatus, formatTranscriptMarkdown, defaultExportFilename, formatMemorySummary, formatMemoryExport, defaultMemoryExportFilename, formatSearchResults, formatCostSummary, formatDoctorReport, formatUndoLogListing, formatMemoryPendingOutcome } from './cli-session.js'\nimport { estimateCostUsd } from './model-pricing.js'\nimport { formatSpendCapStatus } from './spend-cap.js'\nimport { checkProxyHealth, checkClaudeCli, checkWorkspaceRoot, checkDataDirWritable } from './doctor-checks.js'\nimport { resolveNonInteractiveApprovalMode, type NonInteractiveApprovalMode } from './non-interactive-mode.js'\nimport { maybeRunFirstRunSetup } from './first-run.js'\nimport { shouldLaunchTuiApp } from './tui-mode-flag.js'\n\nconst defaultDataDir = join(homedir(), '.buildaharness', 'personal-assistant')\nconst defaultConfigStore = new NodeConfigStore(join(defaultDataDir, 'config.json'))\n\n// Must match the exact path a FileSystemAdapter({ baseDir: dataDir, namespace:\n// 'reminders' }) uses for the key \"reminders\" — see file-tools-mcp-server.mjs's\n// doc comment — so the claude-cli backend's MCP subprocess and this process's\n// own reminderStore (below) read/write the same file instead of two disconnected\n// reminder lists. FileSystemAdapter's path formula is `${baseDir}/${namespace}/${sanitize(key)}.json`,\n// and \"reminders\" sanitizes to itself (no special characters).\nconst defaultRemindersFile = join(defaultDataDir, 'reminders', 'reminders.json')\n\n// Env vars still win over persisted config — see cli-config.ts's envOverridesFromProcessEnv\n// and config.ts's resolveConfig — so this stays a behavior-preserving migration for anyone\n// who already sets ASSISTANT_ENABLE_WEB etc. and never touches /config.\nconst defaultEnvOverrides = envOverridesFromProcessEnv(process.env)\n\nconst defaultNonInteractiveApprovalMode = resolveNonInteractiveApprovalMode(process.env)\n\n// create() only supplies a default for storage the caller didn't already pass in (and falls\n// back to in-memory outside a browser) — passing this explicit, filesystem-backed store is\n// what gives the CLI real persistence across runs. See plans/tauri_desktop_plan.html Phase 3.\nconst defaultBackend = createNodeFsBackend()\n\n/**\n * Builds the SendEmail transport for an approved `send_email` action, or undefined when email\n * isn't fully configured (validateConfig already rejects `enableEmail` without a usable transport,\n * so in practice this only returns undefined when `enableEmail` is off). The model never reaches\n * this — it only ever proposes; delivery happens here after approval.\n */\nfunction buildEmailSender(config: AssistantConfig): SendEmail | undefined {\n if (!config.enableEmail || !config.emailFrom) return undefined\n if (config.emailProvider === 'smtp' && config.smtpHost && config.smtpPort) {\n return createSmtpSender({\n host: config.smtpHost,\n port: config.smtpPort,\n auth: config.smtpUser && config.smtpPass ? { user: config.smtpUser, pass: config.smtpPass } : undefined,\n from: config.emailFrom,\n })\n }\n if (config.emailProvider === 'resend' && config.resendApiKey) {\n return createResendSender({ apiKey: config.resendApiKey, from: config.emailFrom })\n }\n return undefined\n}\n\n/**\n * Picks the ILLMClient for config.llmBackend — one branch per backend, shared in shape with\n * chat-ui's App.tsx createLlmClient() (both switch over the same 5 values), but this one stays\n * CLI-only: claude-cli here is a real node:child_process subprocess (ClaudeCliLLMClient), not\n * the Tauri-bridged equivalent chat-ui's desktop build uses, and it's the only backend always\n * available on this surface (no \"unsupported on this platform\" branch needed, unlike a plain\n * browser tab which can't spawn `claude` at all).\n */\nfunction buildLlmClient(config: AssistantConfig, workspaceRoot: string, remindersFile: string): ILLMClient {\n switch (config.llmBackend) {\n case 'claude-cli':\n return new ClaudeCliLLMClient({\n fileTools: { workspaceRoot },\n remindersFile,\n shellTools: config.enableShell ? { workspaceRoot } : undefined,\n webTools: config.enableWeb ? { braveApiKey: config.braveApiKey as string | undefined } : undefined,\n actionTools: config.enableEmail ? { workspaceRoot } : undefined,\n })\n case 'anthropic':\n return new AnthropicLLMClient({ apiKey: config.apiKey ?? '' })\n case 'openai':\n return new OpenAICompatibleLLMClient({ apiKey: config.apiKey ?? '', baseUrl: OPENAI_BASE_URL, defaultModel: OPENAI_DEFAULT_MODEL })\n case 'openrouter':\n return new OpenAICompatibleLLMClient({\n apiKey: config.apiKey ?? '',\n baseUrl: OPENROUTER_BASE_URL,\n defaultModel: OPENROUTER_DEFAULT_MODEL,\n extraHeaders: OPENROUTER_EXTRA_HEADERS,\n })\n case 'proxy':\n return new LLMClient({ proxyUrl: config.proxyUrl, authToken: config.authToken })\n }\n}\n\n/**\n * Builds a fresh PersonalAssistant (and the llmClient/search function it depends on) from a\n * resolved AssistantConfig — called once at startup and again after any /config change that\n * takes effect, so nothing requires a process restart. Reuses the same dataDir-backed stores\n * each time, so transcripts/experience/checkpoints/reminders carry over across a rebuild.\n */\ninterface BuildAssistantDeps {\n backend: FsBackend\n dataDir: string\n remindersFile: string\n}\n\nasync function buildAssistant(config: AssistantConfig, { backend, dataDir, remindersFile }: BuildAssistantDeps): Promise<PersonalAssistant> {\n const workspaceRoot = config.workspaceRoot ?? process.cwd()\n const search = (query: string) => braveSearch(query, config.braveApiKey as string)\n\n // `search` above is the proxy backend's injected web_search implementation (its manual\n // tool loop calls it directly). The claude-cli backend can't take an injected function —\n // its tools run inside the `claude -p` subprocess — so buildLlmClient passes the same\n // Brave key through to the file-tools MCP server as env instead. Either way, `enableWeb`\n // now wires web_search on both backends.\n const llmClient = buildLlmClient(config, workspaceRoot, remindersFile)\n\n return PersonalAssistant.create({\n llmClient,\n model: config.model,\n // config.activeProject (set via /project <name>) overrides the default of \"the workspace\n // I'm running in\" — see PersonalAssistantOptions.activeProject's doc comment.\n activeProject: config.activeProject || workspaceRoot,\n memory: new FileSystemAdapter({ backend, baseDir: dataDir, namespace: 'transcripts' }),\n experienceStore: await FileSystemExperienceStore.create({ backend, baseDir: dataDir, namespace: 'experience' }),\n checkpointStore: new FileSystemAdapter({ backend, baseDir: dataDir, namespace: 'checkpoints' }),\n reminderStore: new InMemoryReminderStore(new FileSystemAdapter({ backend, baseDir: dataDir, namespace: 'reminders' })),\n fileTools: { backend, workspaceRoot },\n webTools: config.enableWeb ? { search } : undefined,\n // executeCommand is the real child_process.spawn-based implementation (shell-executor.ts) —\n // wired in here, not inside assistant.ts, so the browser build never needs node:child_process.\n shellTools: config.enableShell\n ? { backend, workspaceRoot, timeoutMs: config.shellTimeoutMs, networkAllowlist: config.shellNetworkAllowlist, executeCommand: runApprovedShellCommand }\n : undefined,\n // The Resend/SMTP transport is wired in here, not inside assistant.ts, so the browser build\n // never needs nodemailer or a mail server — same split as shellTools' executeCommand.\n actionTools: (() => {\n const sendEmail = buildEmailSender(config)\n return sendEmail ? { backend, workspaceRoot, sendEmail } : undefined\n })(),\n dangerouslySkipPermissions: config.dangerouslySkipPermissions,\n spendCap:\n config.sessionCostLimitUsd !== undefined || config.sessionCallLimit !== undefined\n ? { sessionCostLimitUsd: config.sessionCostLimitUsd, sessionCallLimit: config.sessionCallLimit }\n : undefined,\n // Resolved through the shared config seam (cli-config.ts's envOverridesFromProcessEnv reads\n // ASSISTANT_ONE_LOOP into config.oneLoopMode; /config set oneLoopMode persists it) so the CLI,\n // chat-ui, and the Tauri desktop build all decide this the same way. Undefined here means\n // PersonalAssistant falls back to DEFAULT_ONE_LOOP_MODE.\n oneLoopMode: config.oneLoopMode,\n // P11 of plans/ask_question_and_plan_mode_plan.html — resolved through the same shared config\n // seam as oneLoopMode above (ASSISTANT_PLAN_MODE / `/config set planMode`). Undefined here\n // means PersonalAssistant falls back to DEFAULT_PLAN_MODE ('legacy').\n planMode: config.planMode,\n })\n}\n\nexport interface RunCliOptions {\n dataDir?: string\n configStore?: ConfigStore\n backend?: FsBackend\n remindersFile?: string\n envOverrides?: Partial<AssistantConfig>\n nonInteractiveApprovalMode?: NonInteractiveApprovalMode\n input?: NodeJS.ReadableStream\n output?: NodeJS.WritableStream\n /**\n * Bypasses buildAssistant()'s config-driven backend selection entirely — the seam tests use\n * to hand runCli a PersonalAssistant wired to a scripted ILLMClient (same pattern as\n * assistant.test.ts) for deterministic command-dispatch/approval-flow coverage. When set, a\n * /config change that would otherwise rebuild the assistant leaves this instance in place.\n */\n assistant?: PersonalAssistant\n /** Overrides the rl.question-based approval prompt — lets tests script approve/decline answers without faking stdin/a real TTY. */\n askYesNo?: (question: string) => Promise<boolean>\n /** Overrides the rl.question-based free-text prompt the clarification sub-loop uses for option numbers, edit notes, and \"Other\" free text — lets tests script a multi-step answer without faking stdin/a real TTY. */\n askLine?: (question: string) => Promise<string>\n /** Test seam: skip the interactive first-run setup regardless of TTY state (also implied when `assistant` is passed). */\n skipFirstRunSetup?: boolean\n /** Test seam: stub `claude` binary detection for the first-run setup. */\n detectClaudeCli?: () => Promise<boolean>\n /** Test seam: script the first-run setup's line reads. */\n firstRunAsk?: (question: string) => Promise<string>\n}\n\nexport interface CliInstance {\n /** Parses and dispatches one line of input exactly as the REPL's 'line' handler does (same dispatchQueue serialization) — the seam cli.test.ts drives command dispatch through without a live TTY. */\n dispatchLine(line: string): Promise<void>\n close(): void\n /**\n * Additive, read-only accessor for the pinned-input TUI shell's status line\n * (plans/personal_assistant_cli_pinned_input_plan.html Phase 3) — the same two signals\n * printStatus() already renders as part of `/status` (lastPlanStatus !== undefined,\n * spendCapStatusLine()), just exposed here instead of only printed. Returns short indicator\n * strings in priority order; empty when there's nothing to report. Not used by the ordinary\n * readline REPL or by non-interactive/piped mode — only the TUI shell polls this.\n */\n getStatusIndicators(): Promise<string[]>\n}\n\n/**\n * The CLI's REPL, parameterized so cli.test.ts can import and drive it directly instead of only\n * through a live process (see non-interactive-mode.ts's doc comment for why that mattered before\n * this export existed — this is T1's answer to it). `main()` below calls this with no options,\n * getting exactly today's behavior; every option here has a real-filesystem/real-stdio default.\n */\nexport async function runCli(options: RunCliOptions = {}): Promise<CliInstance> {\n const dataDir = options.dataDir ?? defaultDataDir\n const configStore = options.configStore ?? defaultConfigStore\n const backend = options.backend ?? defaultBackend\n const remindersFile = options.remindersFile ?? defaultRemindersFile\n const envOverrides = options.envOverrides ?? defaultEnvOverrides\n const nonInteractiveApprovalMode = options.nonInteractiveApprovalMode ?? defaultNonInteractiveApprovalMode\n\n const inputStream = options.input ?? process.stdin\n const outputStream = options.output ?? process.stdout\n\n let persisted = await configStore.load()\n let { config, overriddenKeys } = resolveConfig(persisted, envOverrides)\n\n // First run with nothing configured: offer a one-line setup rather than\n // silently starting on the proxy backend against a proxy that isn't running.\n if (!options.skipFirstRunSetup && !options.assistant) {\n const isInteractive = (inputStream as NodeJS.ReadStream).isTTY === true\n let setupRl: ReturnType<typeof createInterface> | undefined\n const firstRunAsk =\n options.firstRunAsk ??\n ((question: string) => {\n setupRl ??= createInterface({ input: inputStream, output: outputStream })\n return new Promise<string>((resolvePrompt) => {\n setupRl!.question(question, (answer) => resolvePrompt(answer.trim()))\n })\n })\n try {\n persisted = await maybeRunFirstRunSetup({\n configStore,\n persisted,\n overriddenKeys,\n isInteractive,\n ask: firstRunAsk,\n detectClaudeCli:\n options.detectClaudeCli ??\n (async () => (await checkClaudeCli(process.env.CLAUDE_PATH ?? 'claude')).ok),\n log: (line) => outputStream.write(`${line}\\n`),\n })\n } finally {\n setupRl?.close()\n }\n ;({ config, overriddenKeys } = resolveConfig(persisted, envOverrides))\n }\n\n try {\n validateConfig({}, config)\n } catch (err) {\n if (!(err instanceof ConfigValidationError)) throw err\n console.error(err.message)\n process.exit(1)\n }\n\n // Checked before anything else starts, not lazily at the first approval prompt — a session\n // that ran tool calls for several turns before hitting a HIGH-risk gate would otherwise fail\n // deep in, with no chance to recover the work already done, instead of failing immediately\n // with a clear explanation of what to do about it.\n if (nonInteractiveApprovalMode === 'require-tty' && !process.stdin.isTTY) {\n console.error(\n 'ASSISTANT_NON_INTERACTIVE_APPROVAL=require-tty is set, but stdin is not a real TTY ' +\n '(piped/scripted input). Refusing to start rather than fail confusingly at the first ' +\n 'approval prompt — see README.md\\'s \"Non-interactive / scripted use\" section for the ' +\n 'alternative (ASSISTANT_NON_INTERACTIVE_APPROVAL=decline).',\n )\n process.exit(1)\n }\n\n let assistant = options.assistant ?? (await buildAssistant(config, { dataDir, backend, remindersFile }))\n\n const rl = createInterface({ input: options.input ?? process.stdin, output: options.output ?? process.stdout, prompt: 'you> ' })\n // Paused immediately, synchronously, before any awaits below get a chance to yield the event\n // loop: createInterface() switches its input stream to flowing mode as soon as it attaches its\n // own internal 'data' listener, so once we hit e.g. the listUndoLogEntries() await further\n // down, a piped/heredoc input whose lines are already fully buffered by the OS gets parsed into\n // 'line' events right then — and since EventEmitter#emit never queues an event for a listener\n // that isn't there yet, every line parsed before rl.on('line', ...) near the bottom of this\n // function runs is silently dropped, not just delayed. rl.prompt() below auto-resumes if\n // paused, but that resume is itself deferred (stream.resume() only schedules a nextTick flow\n // start) and everything from here to the rl.on('line', ...)/rl.resume() pair at the bottom\n // runs synchronously with no further awaits, so the listener is always in place first.\n rl.pause()\n\n // With no 'SIGINT' listener, readline's own Ctrl+C handling just pauses the interface\n // instead of exiting (see readline's 'SIGINT' event docs) — silently contradicting the\n // startup banner's \"Ctrl+C to exit\". Registering this listener makes Ctrl+C actually exit.\n rl.on('SIGINT', () => {\n console.log('\\nExiting.')\n process.exit(0)\n })\n\n // Display-only defaults, mirroring each backend's own fallback so the banner shows the\n // model that will actually be used even when config.model is unset — not authoritative\n // (each llmClient picks its own default independently), but sourced from the same\n // @buildaharness/runtime constants those clients use so the banner can't drift from them.\n const backendDisplayModel: Record<AssistantConfig['llmBackend'], string> = {\n proxy: ANTHROPIC_DEFAULT_MODEL,\n 'claude-cli': '(your Claude Code default)',\n anthropic: ANTHROPIC_DEFAULT_MODEL,\n openai: OPENAI_DEFAULT_MODEL,\n openrouter: OPENROUTER_DEFAULT_MODEL,\n }\n console.log(`backend: ${config.llmBackend} (${config.model ?? backendDisplayModel[config.llmBackend]})`)\n\n // No silent default: capabilities only appear in the banner when actually configured,\n // so the banner never implies something is available that isn't.\n const enabledCapabilities: string[] = []\n if (config.enableWeb) enabledCapabilities.push('web search/fetch (brave)')\n if (config.enableShell) enabledCapabilities.push(`shell commands (${config.dangerouslySkipPermissions ? 'NOT approval-gated' : 'approval-gated'})`)\n const capabilitySuffix = enabledCapabilities.length > 0 ? ` — enabled: ${enabledCapabilities.join(', ')}` : ''\n // Loud and separate from the capability list above (which is easy to skim past) —\n // this changes the trust model for every HIGH-risk action, not just shell.\n const dangerBanner = config.dangerouslySkipPermissions\n ? '\\n⚠ dangerouslySkipPermissions is ON — every approval prompt (risky messages, file writes, shell commands) is skipped automatically.\\n'\n : ''\n // Same \"never silently in effect\" reasoning as dangerBanner above — the opposite trust\n // direction (declines instead of skips), but just as important to surface up front.\n const nonInteractiveBanner =\n nonInteractiveApprovalMode === 'decline'\n ? '\\nASSISTANT_NON_INTERACTIVE_APPROVAL=decline is set — every approval prompt auto-declines.\\n'\n : ''\n\n // The undo-log persists across sessions (it's on disk under the workspace, not in-memory) —\n // surfaced here so a user doesn't discover leftover revertible actions from a prior session\n // only by happening to run /undo-action or /status (T6).\n const startupUndoLogEntries = await assistant.listUndoLogEntries()\n const undoableFromBefore = startupUndoLogEntries.filter((e) => e.undoable).length\n const undoBanner =\n undoableFromBefore > 0\n ? `\\n${undoableFromBefore} action${undoableFromBefore === 1 ? '' : 's'} from earlier sessions ${undoableFromBefore === 1 ? 'is' : 'are'} still revertible — see /undo-action.\\n`\n : ''\n\n console.log(`Aielia — your personal assistant on the 11-layer harness, one turn at a time. Ctrl+C to exit.${capabilitySuffix}\\n${dangerBanner}${nonInteractiveBanner}${undoBanner}`)\n console.log('Type /help to see all commands, /config to view settings.\\n')\n rl.prompt()\n\n let lastTrace: AssistantTrace | undefined\n let lastSources: AssistantSource[] | undefined\n let lastPlanStatus: AssistantTurnResult['planStatus']\n let lastTurnUsage: TokenUsage | undefined\n // Tool calls made by the most recent turn — used only so /undo can warn about a real\n // side effect (e.g. a created reminder) it's about to claim to have removed but can't\n // actually reverse. Reset at the start of every handleTurn call, see writeToolStep below.\n let lastTurnToolSteps: AssistantToolStep[] = []\n // Set whenever the last turn was blocked on an approval gate (message-level risk or a\n // staged write/shell command) and then declined, or escalated — lastTrace stays whatever it\n // was before in either case (neither path ever runs the harness), so without this /why and\n // /layers would fall back to the generic \"nothing to explain yet, or it took the fast path\"\n // message even right after a real, user-visible decline. That reads as if nothing happened,\n // when something very much did. Cleared whenever a turn actually produces a trace, and on /new.\n let lastNoTraceReason: string | undefined\n\n /** claude-cli is the only backend that returns a real dollar cost (--output-format json's total_cost_usd) — every other backend (proxy, and now anthropic/openai/openrouter, none of which surface billing via TokenUsage.costUsd) gets an approximate estimate instead, see model-pricing.ts. */\n function withCostEstimate(usage: TokenUsage): TokenUsage {\n if (config.llmBackend === 'claude-cli' || usage.costUsd !== undefined) return usage\n const estimated = estimateCostUsd(config.model ?? ANTHROPIC_DEFAULT_MODEL, usage)\n return estimated !== undefined ? { ...usage, costUsd: estimated } : usage\n }\n\n const PLAN_TASK_STATUS_ICON: Record<string, string> = {\n PENDING: '○', RUNNING: '▶', COMPLETE: '✓', FAILED: '✗', BLOCKED: '✗', HUMAN_REQUIRED: '~',\n }\n\n function printPlan(): void {\n if (!lastPlanStatus) {\n console.log('\\nNo active plan for this session.\\n')\n return\n }\n console.log(`\\nPlan: ${lastPlanStatus.templateName ?? 'custom plan'} (${lastPlanStatus.completionPct.toFixed(1)}% complete)`)\n for (const task of lastPlanStatus.tasks) {\n console.log(` ${PLAN_TASK_STATUS_ICON[task.status] ?? '?'} [${task.status}] ${task.id} — ${task.description}`)\n }\n console.log(`\\nSuccess criteria: ${lastPlanStatus.successCriteria}\\n`)\n }\n\n /**\n * P9 of plans/ask_question_and_plan_mode_plan.html — `/plan sketch <request>` invokes the\n * lightweight plan-sketch delegate (PersonalAssistant.sketchPlan): one bounded, advisory LLM\n * call that prints a proposed task list directly, with no approve/decline controls (nothing was\n * staged — INV-35) and no effect on `/plan`'s own drafting-plan display above.\n */\n async function handlePlanSketch(args: string[]): Promise<void> {\n const request = args.join(' ').trim()\n if (!request) {\n console.log('\\nUsage: /plan sketch <request>\\n')\n return\n }\n console.log('\\nSketching a plan (read-only grounding only, nothing staged)...\\n')\n const result = await assistant.sketchPlan('cli', request)\n console.log(`${result.reply ?? '(no reply)'}\\n`)\n if (result.usage) lastTurnUsage = withCostEstimate(result.usage)\n }\n\n async function handlePlan(args: string[]): Promise<void> {\n if (args[0] === 'sketch') {\n await handlePlanSketch(args.slice(1))\n return\n }\n printPlan()\n }\n\n function verificationHealthLabel({ strength, feasibility }: AssistantTrace['verificationHealth']): string {\n const confidence = Math.min(strength, feasibility)\n if (confidence >= 0.7) return 'High confidence'\n if (confidence >= 0.4) return 'Reasonably confident'\n return 'Worth double-checking'\n }\n\n /** One-line summary of AssistantTrace.batchBudget — the same found/not_found/truncated_while_productive\n * counts a batch turn's synthesized reply is already built from, just as a glance-able tally\n * rather than prose. Shared by /why's compact view and /layers' fuller per-item breakdown. */\n function batchBudgetSummaryLine(batchBudget: NonNullable<AssistantTrace['batchBudget']>): string {\n const found = batchBudget.perItemOutcomes.filter((o) => o.status === 'found').length\n const notFound = batchBudget.perItemOutcomes.filter((o) => o.status === 'not_found').length\n const truncated = batchBudget.perItemOutcomes.filter((o) => o.status === 'truncated_while_productive').length\n return (\n `Batch: ${batchBudget.itemCount} items — ${found} found, ${notFound} not found, ${truncated} truncated ` +\n `(${batchBudget.totalCallsUsed} calls used, projected ~${Math.ceil(batchBudget.projectedTotal)})`\n )\n }\n\n function printWhy(): void {\n if (!lastTrace) {\n console.log(`\\n${lastNoTraceReason ?? 'No harness trace for the last turn (nothing to explain yet, or it took the fast path).'}\\n`)\n return\n }\n console.log(`\\n${verificationHealthLabel(lastTrace.verificationHealth)}`)\n // Only the layers that actually fired, chained in the order they fired — quiet otherwise\n // (Design Principle 3 of the harness layer activation plan: the common, unremarkable case\n // stays quiet, matching the existing \"don't badge LOW risk\" convention). Use /layers for\n // the full fired/skipped picture across all 11.\n const chain = buildWhyChain(lastTrace.layerActivity)\n if (chain.length > 0) {\n console.log(' ' + chain.map((item) => `${LAYER_SHORT_CODE[item.layer]} (${item.reason})`).join(' > '))\n }\n // Absent on every non-batch turn (see AssistantTrace.batchBudget's doc comment) — only a\n // batch-research turn ever has this to show.\n if (lastTrace.batchBudget) {\n console.log(' ' + batchBudgetSummaryLine(lastTrace.batchBudget))\n }\n console.log('')\n }\n\n /** Full fired/skipped picture across all 11 harness layers for the last turn — pure text rendering of the same layer_activity data /why's \"What I checked\" summarizes selectively. */\n function printLayers(): void {\n if (!lastTrace) {\n console.log(`\\n${lastNoTraceReason ?? 'No harness trace for the last turn (nothing to explain yet, or it took the fast path).'}\\n`)\n return\n }\n console.log('')\n const byLayer = new Map(lastTrace.layerActivity.map((e) => [e.layer, e]))\n for (const layer of LAYER_ORDER) {\n const e = byLayer.get(layer)\n const mark = e?.fired ? '✓' : '·'\n const reason = e?.reason ?? 'not evaluated this turn'\n console.log(` [${mark}] ${LAYER_DISPLAY_NAME[layer].padEnd(22)} ${reason}`)\n }\n // The full per-item breakdown behind /why's one-line batchBudgetSummaryLine tally — same\n // \"absent on every non-batch turn\" gating as that summary.\n if (lastTrace.batchBudget) {\n console.log('')\n console.log(` ${batchBudgetSummaryLine(lastTrace.batchBudget)}`)\n const STATUS_MARK: Record<'found' | 'not_found' | 'truncated_while_productive', string> = {\n found: '✓', not_found: '✗', truncated_while_productive: '~',\n }\n for (const outcome of lastTrace.batchBudget.perItemOutcomes) {\n console.log(` [${STATUS_MARK[outcome.status]}] ${outcome.item.padEnd(30)} ${outcome.status} (${outcome.callsUsed} calls)`)\n }\n }\n console.log('')\n }\n\n const SOURCE_TOOL_LABEL: Record<AssistantSource['tool'], string> = {\n read_file: 'Read',\n list_directory: 'Listed',\n web_search: 'Searched',\n fetch_url: 'Fetched',\n }\n\n function printSources(): void {\n if (!lastSources || lastSources.length === 0) {\n console.log('\\nNo sources for the last turn (it used no file/web tool calls).\\n')\n return\n }\n console.log('')\n for (const source of lastSources) {\n console.log(` - ${SOURCE_TOOL_LABEL[source.tool]} ${source.path}`)\n }\n console.log('')\n }\n\n function printHelp(): void {\n console.log(`\\n${formatHelp()}\\n`)\n }\n\n /** Ends the current conversation: clears transcript/facts/plan state and resets local display state so /why, /sources, /plan immediately reflect the fresh session instead of showing stale data. */\n async function handleClear(): Promise<void> {\n await assistant.clearSession('cli')\n lastTrace = undefined\n lastSources = undefined\n lastPlanStatus = undefined\n lastTurnUsage = undefined\n lastNoTraceReason = undefined\n console.log('\\n✓ Started a fresh conversation.\\n')\n }\n\n /**\n * Explicit inspect/clear for a stuck harness checkpoint — a scoped alternative to `/clear`\n * (which also wipes transcript/facts/plan) or moving the whole\n * `~/.buildaharness/personal-assistant/` directory aside by hand, previously the only way to\n * recover from a checkpoint that keeps failing to resume (e.g. left behind by a process killed\n * mid-turn — see assistant.ts's runId doc comment). Bare `/checkpoint` inspects without\n * clearing; `/checkpoint clear` clears it, matching `/config`/`/config set`'s own\n * bare-vs-subcommand shape.\n */\n async function handleCheckpoint(args: string[]): Promise<void> {\n if (args[0] === 'clear') {\n const result = await assistant.clearCheckpoint('cli')\n console.log(\n result.cleared\n ? `\\n✓ Cleared the stuck checkpoint (was at step ${result.stepsUsed}, node \"${result.currentNode}\"). Conversation history is untouched — your next message starts a fresh harness run.\\n`\n : '\\nNo checkpoint to clear — the last turn either completed normally or there was nothing in progress.\\n',\n )\n return\n }\n const status = await assistant.getCheckpointStatus('cli')\n if (!status.present) {\n console.log('\\nNo checkpoint present — the last turn completed normally or there was nothing in progress.\\n')\n return\n }\n const attemptsNote =\n status.failedResumeAttempts > 0\n ? ` — failed to resume ${status.failedResumeAttempts} time${status.failedResumeAttempts === 1 ? '' : 's'} in a row so far`\n : ''\n console.log(\n `\\nA checkpoint is present: step ${status.stepsUsed}, last node \"${status.currentNode}\"${attemptsNote}.\\n` +\n `Your next message will try to resume it automatically. Run /checkpoint clear to discard it and start fresh instead.\\n`,\n )\n }\n\n async function printStatus(): Promise<void> {\n const transcript = await assistant.getTranscript('cli')\n const undoLogEntries = await assistant.listUndoLogEntries()\n const spendCapLine = await spendCapStatusLine()\n console.log(\n `\\n${formatStatus({ config, overriddenKeys, transcriptLength: transcript.length, planActive: lastPlanStatus !== undefined, undoLogEntries, spendCapLine })}\\n`,\n )\n }\n\n /** Shared by /status and /cost (plan T2 step 5: \"not a separate display\") — undefined when no ceiling is configured. */\n async function spendCapStatusLine(): Promise<string | undefined> {\n if (config.sessionCostLimitUsd === undefined && config.sessionCallLimit === undefined) return undefined\n const state = await assistant.getSpendState('cli')\n return formatSpendCapStatus(state, { sessionCostLimitUsd: config.sessionCostLimitUsd, sessionCallLimit: config.sessionCallLimit })\n }\n\n /** Writes the session transcript to a markdown file — an explicit argument overrides the default filename, resolved relative to process.cwd() if not absolute. */\n async function handleExport(args: string[]): Promise<void> {\n const transcript = await assistant.getTranscript('cli')\n if (transcript.length === 0) {\n console.log('\\nNothing to export yet.\\n')\n return\n }\n const filename = resolvePath(process.cwd(), args[0] ?? defaultExportFilename())\n try {\n await writeFile(filename, formatTranscriptMarkdown(transcript), 'utf-8')\n console.log(`\\n✓ Exported ${transcript.length} message${transcript.length === 1 ? '' : 's'} to ${filename}\\n`)\n } catch (err) {\n // Mirrors handleTurn's catch convention (below) — a failed write is reported, not thrown.\n const { message } = classifyError(err)\n console.log(`\\n[error] ${message}\\n`)\n }\n }\n\n /**\n * Removes the last exchange from conversation history. Only affects what the model\n * remembers — a real write_file/run_shell_command effect from the undone turn is not\n * reversed (see the plan's Known limitations). Peeks at the transcript before calling\n * undoLastTurn so the confirmation message can name which side was removed, without\n * changing undoLastTurn's return shape just for that.\n *\n * create_reminder is the one tool call this reversible-sounding command can silently fail\n * to revert without the user realizing it — reminders need no approval (see risk-classifier.ts),\n * so unlike write_file/run_shell_command there's no prior \"Proceed?\" prompt that already told\n * the user this was consequential. ReminderStore has no delete/remove method to actually undo\n * it with (adding one is a packages/runtime change, out of scope here), so the best available\n * fix is surfacing the caveat explicitly instead of letting \"Removed the last exchange\" imply\n * the reminder went away too.\n */\n async function handleUndo(): Promise<void> {\n const transcriptBefore = await assistant.getTranscript('cli')\n if (transcriptBefore.length === 0) {\n console.log('\\nNothing to undo.\\n')\n return\n }\n const wasPendingApproval = transcriptBefore[transcriptBefore.length - 1].role !== 'assistant'\n const undoneReminders = lastTurnToolSteps.filter((s) => s.tool === 'create_reminder')\n\n const result = await assistant.undoLastTurn('cli')\n if (!result.undone) {\n console.log('\\nNothing to undo.\\n')\n return\n }\n // The display state described the now-undone turn — there's no cheap way to recover the\n // exact prior turn's trace/sources without storing turn-scoped snapshots (accepted limitation).\n lastTrace = undefined\n lastSources = undefined\n lastPlanStatus = undefined\n lastTurnToolSteps = []\n const caveat = undoneReminders.length > 0\n ? ` Note: ${undoneReminders.length === 1 ? 'a reminder' : `${undoneReminders.length} reminders`} created in that exchange (${undoneReminders.map((s) => `\"${s.input.text}\"`).join(', ')}) ${undoneReminders.length === 1 ? 'is' : 'are'} still active — /undo only removes chat history, not that side effect.`\n : ''\n console.log(\n `\\n✓ Removed ${wasPendingApproval ? 'the pending message awaiting approval' : 'the last exchange (1 user message, 1 assistant reply)'}.${caveat}\\n`,\n )\n }\n\n /**\n * `/undo-action` with no argument lists real filesystem effects still on record as revertible\n * (T3 step 1). `/undo-action <id>` stages a revert of that entry as its own approval-gated\n * action (T3 step 2) — same \"yes/no, then re-call turn() to apply or discard\" shape as any\n * other staged write/shell action, except staging itself happens synchronously here (via\n * assistant.stageUndoAction) rather than by the model calling a tool, so the confirmation\n * prompt is driven directly rather than via handleTurn's needs_approval branch.\n */\n async function handleUndoAction(args: string[]): Promise<void> {\n if (args.length === 0) {\n const entries = await assistant.listUndoLogEntries()\n console.log(`\\n${formatUndoLogListing(entries)}\\n`)\n return\n }\n\n const staged = await assistant.stageUndoAction(args[0])\n if (staged.status === 'error') {\n console.log(`\\n✗ ${staged.message}\\n`)\n return\n }\n\n console.log(`\\n[needs approval — revert] ${staged.reason}`)\n const confirmed = await askYesNo('Apply this revert? (y/N) ')\n lastTrace = undefined\n lastNoTraceReason = `No harness trace — the last turn was a staged revert that was ${confirmed ? 'approved' : 'declined'} before the harness ran.`\n await handleTurn('', confirmed, staged.pendingActionId)\n }\n\n async function printMemory(): Promise<void> {\n const summary = await assistant.getMemorySummary('cli')\n console.log(`\\n${formatMemorySummary(summary)}\\n`)\n }\n\n /**\n * `/memory export [file]` — writes the full, unbounded learned-experience contents (every\n * strategy weight/decomposition/recovery sequence, not just the 20-entry preview `/memory`\n * prints) plus facts/reminders to a JSON file. Mirrors handleExport's shape: explicit\n * argument overrides the default filename, resolved relative to process.cwd() if not\n * absolute, a failed write is reported rather than thrown. Read-only over already-persisted\n * data — never an LLM call or network request.\n */\n async function handleMemoryExport(args: string[]): Promise<void> {\n const data = await assistant.exportMemory('cli')\n const filename = resolvePath(process.cwd(), args[0] ?? defaultMemoryExportFilename())\n try {\n await writeFile(filename, formatMemoryExport(data), 'utf-8')\n console.log(`\\n✓ Exported learned memory to ${filename}\\n`)\n } catch (err) {\n const { message } = classifyError(err)\n console.log(`\\n[error] ${message}\\n`)\n }\n }\n\n /** `/memory confirm <n|category>` / `/memory reject <n|category>` — see PersonalAssistant.confirmPendingFact/rejectPendingFact's doc comments for `selector`'s shape. */\n async function handleMemoryPending(action: 'confirm' | 'reject', args: string[]): Promise<void> {\n const selector = args[0]\n if (!selector) {\n console.log(`\\nUsage: /memory ${action} <n> or /memory ${action} <category>\\n`)\n return\n }\n const outcome = action === 'confirm' ? await assistant.confirmPendingFact(selector) : await assistant.rejectPendingFact(selector)\n console.log(`\\n${formatMemoryPendingOutcome(action === 'confirm' ? 'confirmed' : 'rejected', outcome)}\\n`)\n }\n\n /** `/memory forget <n>` — removes an already-durable/session fact by its `/memory` display number. Unlike confirm/reject, there's no category form (see PersonalAssistant.forgetFact's doc comment). */\n async function handleMemoryForget(args: string[]): Promise<void> {\n const selector = args[0]\n if (!selector) {\n console.log('\\nUsage: /memory forget <n>\\n')\n return\n }\n const outcome = await assistant.forgetFact(selector, 'cli')\n console.log(`\\n${formatMemoryPendingOutcome('forgotten', outcome)}\\n`)\n }\n\n /** `/memory` with no args shows a preview; `/memory export [file]` writes the full contents to disk (see handleMemoryExport); `/memory confirm|reject <n|category>` resolves a pending-confirmation guess (see handleMemoryPending); `/memory forget <n>` removes an already-learned fact (see handleMemoryForget). */\n async function handleMemory(args: string[]): Promise<void> {\n if (args[0] === 'export') {\n await handleMemoryExport(args.slice(1))\n return\n }\n if (args[0] === 'confirm' || args[0] === 'reject') {\n await handleMemoryPending(args[0], args.slice(1))\n return\n }\n if (args[0] === 'forget') {\n await handleMemoryForget(args.slice(1))\n return\n }\n await printMemory()\n }\n\n /** `/search <query>` — ranked search over past messages (see PersonalAssistant.searchTranscript), never an LLM call or network request. A query with no terms is treated the same as no results, not an error. */\n async function handleSearch(args: string[]): Promise<void> {\n const query = args.join(' ')\n if (!query.trim()) {\n console.log('\\nUsage: /search <query>\\n')\n return\n }\n const hits = await assistant.searchTranscript(query)\n console.log(`\\n${formatSearchResults(hits, query)}\\n`)\n }\n\n async function printCost(): Promise<void> {\n const spendCapLine = await spendCapStatusLine()\n // Source \"This session\" from the persisted spend ledger (assistant.getSpendState) — the same\n // cross-turn, cross-/new store the spend-cap line above is drawn from (recordSpend() always\n // updates it, regardless of backend or whether a cap is configured). An earlier version of\n // this tracked tokens separately in an in-process variable that reset on /new and on every\n // fresh CLI process, while the persisted ledger did not — sourcing the two numbers from\n // different scopes used to show e.g. \"0 in / 0 out tokens\" next to a real nonzero dollar cost\n // once any turn had run before the current /new.\n const spendState = await assistant.getSpendState('cli')\n const session = { inputTokens: spendState.cumulativeInputTokens, outputTokens: spendState.cumulativeOutputTokens, costUsd: spendState.cumulativeCostUsd }\n console.log(`\\n${formatCostSummary({ lastTurn: lastTurnUsage, session, backend: config.llmBackend, spendCapLine })}\\n`)\n }\n\n async function handleDoctor(): Promise<void> {\n const workspaceRoot = config.workspaceRoot ?? process.cwd()\n const checks = await Promise.all([\n // Backend-specific checks only run for the backend actually in use — skipped, not\n // reported as failing, for the one that's inactive.\n ...(config.llmBackend === 'proxy' ? [checkProxyHealth(config.proxyUrl)] : []),\n ...(config.llmBackend === 'claude-cli' ? [checkClaudeCli(process.env.CLAUDE_PATH ?? 'claude')] : []),\n checkWorkspaceRoot(workspaceRoot),\n checkDataDirWritable(backend, dataDir),\n // Informational only (always ok:true) — surfaces the active backend/flags so a user has\n // one command to confirm config, rather than none (see file's own config summary gap).\n { label: `llmBackend: ${config.llmBackend}`, ok: true },\n { label: `enableShell: ${config.enableShell ?? false}, enableWeb: ${config.enableWeb ?? false}`, ok: true },\n // Provider API keys are stored plaintext in config.json, not an OS keychain — confirming\n // that explicitly here rather than leaving it unstated (no keychain integration exists).\n { label: 'provider keys stored: plaintext in config.json (no OS keychain integration)', ok: true },\n ])\n console.log(`\\n${formatDoctorReport(checks)}\\n`)\n }\n\n let lastProgressLineLength = 0\n function writeProgress(progress: AssistantProgress): void {\n // Layer name instead of the raw node id even on a non-plan turn (Phase 3.3: \"Step 3/5 —\n // Verification…\" reads better than \"Step 3/5 — verify…\") — falls back to the node's own\n // display name for loop-scaffolding nodes (context_compression etc.) that map to no layer.\n const layer = nodeToLayer(progress.currentNode)\n const label = layer ? LAYER_DISPLAY_NAME[layer] : nodeDisplayName(progress.currentNode)\n // A plan-driven turn gets a plan-aware prefix instead of the generic step counter for the\n // duration of the run (Phase 3.2) — the node/layer name is still shown after it, and full\n // harness-internal detail is still available via /why once the turn finishes.\n const prefix = progress.planPosition\n ? `[${progress.planPosition.templateName ?? 'custom plan'} — step ${progress.planPosition.stepIndex}/${progress.planPosition.stepCount} (${progress.planPosition.completionPct.toFixed(0)}%)]`\n : `[step ${progress.stepsUsed}/${progress.maxSteps}]`\n const line = `${prefix}${label ? ` ${label}…` : ''}`\n process.stdout.write(`\\r${line.padEnd(lastProgressLineLength)}`)\n lastProgressLineLength = line.length\n }\n function clearProgress(): void {\n if (lastProgressLineLength === 0) return\n process.stdout.write(`\\r${' '.repeat(lastProgressLineLength)}\\r`)\n lastProgressLineLength = 0\n }\n\n // A discrete event, not a progress bar — each call gets its own scrolled-past line\n // (unlike writeProgress's single overwritten \\r line), the way Claude Code's own CLI\n // shows a running log of what it's doing. Clears the progress line first so a tool step\n // never gets mangled mid-overwrite.\n function writeToolStep(step: AssistantToolStep): void {\n clearProgress()\n console.log(` ⚙ ${step.summary}`)\n lastTurnToolSteps.push(step)\n }\n\n /** One question's in-progress answer — the CLI's text-mode counterpart of AskQuestionCard.tsx's QuestionDraft (chat-ui's Q5 rendering). No `showEdit` flag: a note is only ever entered via the explicit `e<n>` command below, so its presence in `editText` already says whether one exists. */\n interface ClarificationDraft {\n selectedLabels: string[]\n editText: string\n freeText: string\n }\n function emptyClarificationDraft(): ClarificationDraft {\n return { selectedLabels: [], editText: '', freeText: '' }\n }\n\n /** Recommended option(s) first, stable otherwise — mirrors AskQuestionCard.tsx's orderedOptions exactly, so the same question renders its options in the same order on both surfaces. */\n function orderedAskOptions(question: AskQuestion): AskQuestionOption[] {\n const options = question.options ?? []\n return options\n .map((option, index) => ({ option, index }))\n .sort((a, b) => Number(b.option.recommended ?? false) - Number(a.option.recommended ?? false) || a.index - b.index)\n .map(({ option }) => option)\n }\n\n /** Collapses one question's draft into the wire AskAnswer shape, or null while still unanswered (INV-28) — mirrors AskQuestionCard.tsx's draftToAnswer. */\n function clarificationDraftToAnswer(questionId: string, draft: ClarificationDraft): AskAnswer | null {\n if (draft.selectedLabels.length > 0) {\n return draft.editText.trim()\n ? { questionId, kind: 'selected_with_edit', selectedLabels: draft.selectedLabels, editText: draft.editText.trim() }\n : { questionId, kind: 'selected', selectedLabels: draft.selectedLabels }\n }\n if (draft.freeText.trim()) {\n return { questionId, kind: 'free_text', freeText: draft.freeText.trim() }\n }\n return null\n }\n\n function printClarificationQuestion(question: AskQuestion, draft: ClarificationDraft, index: number, total: number): void {\n const options = orderedAskOptions(question)\n const allowFreeText = question.allowFreeText !== false\n console.log('')\n if (question.header) console.log(`[${question.header}]`)\n if (total > 1) console.log(`Question ${index + 1} of ${total}`)\n console.log(question.question)\n options.forEach((option, i) => {\n const n = i + 1\n const picked = draft.selectedLabels.includes(option.label) ? '✓' : ' '\n const recommended = option.recommended ? ' (recommended)' : ''\n console.log(` [${picked}] ${n}) ${option.label}${recommended}`)\n if (option.description) console.log(` ${option.description}`)\n if (option.preview) console.log(` preview: ${option.preview}`)\n })\n if (draft.editText) console.log(` note: ${draft.editText}`)\n if (draft.freeText) console.log(` other: ${draft.freeText}`)\n const commands = [\n options.length > 0 ? (question.allowMultiple ? '<n>/t<n> toggle option' : '<n> select option') : undefined,\n options.length > 0 ? 'e<n> add a note to a selected option' : undefined,\n allowFreeText ? 'o other (free text)' : undefined,\n total > 1 ? 'b back, n next' : undefined,\n 's submit',\n ].filter((c): c is string => c !== undefined)\n console.log(` (${commands.join(' | ')})`)\n }\n\n /**\n * Text-mode equivalent of AskQuestionCard.tsx (Q6 of plans/ask_question_and_plan_mode_plan.html):\n * runs a self-contained prompt loop over `result.questions`, then resumes the paused turn by\n * calling back into handleTurn with `pendingClarificationId`/the assembled AskResponse — same\n * \"resolved by ID, never re-derived\" shape as the write/shell/email pause above. Only reached\n * once the harness has already staged the questions (AskClarificationService.stageAndRespond);\n * this function only collects and validates the answer, it never talks to the harness directly.\n */\n async function handleClarification(result: AssistantTurnResult): Promise<void> {\n const questions = result.questions ?? []\n const pendingClarificationId = result.pendingClarificationId\n if (questions.length === 0 || !pendingClarificationId) {\n // Mirrors shouldRenderAskQuestionCard's guard in chat-ui — shouldn't happen in practice\n // (AskClarificationService always sets both together), but fails safe to the plain-text\n // escalation copy rather than entering a loop with nothing to ask.\n lastTrace = undefined\n lastNoTraceReason = `No harness trace — the last turn needed clarification (${result.reason ?? 'no further detail'}) but had no answerable questions.`\n console.log(`\\n[needs clarification] ${result.reason ?? 'This request needs clarification.'}\\n`)\n return\n }\n\n // Same \"never touches stdin, always resolves the same way\" shape as askYesNo's own decline\n // branch — a piped/scripted run has no one to answer these questions, so it falls through to\n // the plain-text escalation copy instead of blocking on a readline read of closed stdin.\n if (nonInteractiveApprovalMode === 'decline') {\n console.log(`\\n[non-interactive mode: auto-declining — ASSISTANT_NON_INTERACTIVE_APPROVAL=decline]`)\n console.log(`\\n[needs clarification] ${result.reason ?? 'This request needs clarification.'}`)\n for (const q of questions) console.log(` - ${q.question}`)\n console.log('')\n lastTrace = undefined\n lastNoTraceReason = 'No harness trace — the last turn needed clarification and was auto-declined (non-interactive mode) before the harness resumed.'\n return\n }\n\n console.log(`\\n[needs clarification] ${questions.length} question${questions.length === 1 ? '' : 's'} to answer.`)\n const drafts = new Map<string, ClarificationDraft>(questions.map((q) => [q.id, emptyClarificationDraft()]))\n let index = 0\n\n while (true) {\n const question = questions[index]\n const draft = drafts.get(question.id)!\n printClarificationQuestion(question, draft, index, questions.length)\n const token = (await askLine('clarify> ')).trim()\n const lower = token.toLowerCase()\n\n if (lower === 's' || lower === 'submit') {\n const answers = questions.map((q) => clarificationDraftToAnswer(q.id, drafts.get(q.id)!))\n const missing = questions.filter((q, i) => answers[i] === null)\n if (missing.length > 0) {\n console.log(`\\nStill need an answer for: ${missing.map((q) => q.question).join('; ')}\\n`)\n continue\n }\n const response: AskResponse = { answers: answers as AskAnswer[] }\n await handleTurn('', false, undefined, pendingClarificationId, response)\n return\n }\n if (lower === 'b' && questions.length > 1) {\n index = Math.max(0, index - 1)\n continue\n }\n if (lower === 'n' && questions.length > 1) {\n index = Math.min(questions.length - 1, index + 1)\n continue\n }\n if (lower === 'o') {\n if (question.allowFreeText === false) {\n console.log('\\nThis question does not accept a free-text answer.\\n')\n continue\n }\n const text = await askLine('Other — type your own answer: ')\n if (!text.trim()) {\n console.log('\\nEmpty answer ignored.\\n')\n continue\n }\n drafts.set(question.id, { selectedLabels: [], editText: '', freeText: text.trim() })\n continue\n }\n const editMatch = /^e(\\d+)$/i.exec(token)\n if (editMatch) {\n const options = orderedAskOptions(question)\n const option = options[Number(editMatch[1]) - 1]\n if (!option) {\n console.log(`\\nNo option ${editMatch[1]}.\\n`)\n continue\n }\n if (!draft.selectedLabels.includes(option.label)) {\n console.log(`\\nSelect option ${editMatch[1]} first, then add a note with e${editMatch[1]}.\\n`)\n continue\n }\n const note = await askLine('Note: ')\n drafts.set(question.id, { ...draft, editText: note.trim() })\n continue\n }\n const toggleMatch = /^t(\\d+)$/i.exec(token) ?? /^(\\d+)$/.exec(token)\n if (toggleMatch) {\n const options = orderedAskOptions(question)\n const option = options[Number(toggleMatch[1]) - 1]\n if (!option) {\n console.log(`\\nNo option ${toggleMatch[1]}.\\n`)\n continue\n }\n if (question.allowMultiple) {\n const already = draft.selectedLabels.includes(option.label)\n drafts.set(question.id, {\n ...draft,\n selectedLabels: already ? draft.selectedLabels.filter((l) => l !== option.label) : [...draft.selectedLabels, option.label],\n freeText: '',\n })\n } else {\n drafts.set(question.id, { ...draft, selectedLabels: [option.label], freeText: '' })\n }\n continue\n }\n\n console.log(`\\nUnrecognized input \"${token}\".\\n`)\n }\n }\n\n async function handleTurn(\n message: string,\n approved = false,\n pendingActionId?: string,\n pendingClarificationId?: string,\n clarificationAnswer?: AskResponse,\n ): Promise<void> {\n lastTurnToolSteps = []\n // Set only once the first token of an actual streamed reply arrives — the\n // message-level approval gate and the file-tools loop both produce a full\n // reply with no streaming, so this stays false for those turns and the\n // final branch below falls back to printing the whole reply at once.\n let streamedAnyTokens = false\n function writeToken(token: string): void {\n if (!streamedAnyTokens) {\n process.stdout.write('\\nAielia> ')\n streamedAnyTokens = true\n }\n process.stdout.write(token)\n }\n\n try {\n const result = await assistant.turn(message, {\n sessionId: 'cli',\n approved,\n pendingActionId,\n pendingClarificationId,\n clarificationAnswer,\n // Gated on streamedAnyTokens: onProgress keeps firing for layers (Memory,\n // Verification) that run after the LLM call, i.e. after writeToken has already put\n // the reply on the current line with no trailing newline. writeProgress's \\r-based\n // overwrite doesn't know that — on a real TTY it wrote the \"[step N/5] ...\" text\n // over the tail of the just-streamed reply, and the following clearProgress() then\n // blanked that same line, so the end of the assistant's answer visibly vanished\n // right after it finished streaming. Once streaming has started this turn, further\n // progress has nothing safe to overwrite, so it's dropped instead.\n onProgress: (progress) => {\n if (!streamedAnyTokens) writeProgress(progress)\n },\n onToken: writeToken,\n onToolStep: writeToolStep,\n })\n // Same streamedAnyTokens gate as onProgress above: lastProgressLineLength describes a\n // pre-streaming progress line that a leading \"\\n\" in writeToken already scrolled past,\n // not the current line (the tail of the streamed reply) — clearing unconditionally here\n // would \\r back onto the reply's last line and blank the start of it with stale-length\n // spaces.\n if (!streamedAnyTokens) clearProgress()\n\n // A write_file/run_shell_command tool call, gated at the point of the call itself\n // rather than by the message text — see the file-tools and web+shell-tools plans'\n // Diagnosis tabs. Unlike the message-level gate below, both a yes and a no need to\n // re-call turn() (to apply or discard the staged action), not just a yes.\n if (result.status === 'needs_approval' && result.pendingActionId) {\n // 'batch' (a projected-search-count confirmation, not a staged write/shell action) has\n // its own label and prompt — falling through to the 'write' case here would print\n // \"[needs approval — write]\" and \"Apply this write?\" over a message that's actually\n // about how many more searches a batch research turn is projected to need.\n const kindLabel =\n result.pendingActionKind === 'shell'\n ? 'shell command'\n : result.pendingActionKind === 'email'\n ? 'send email'\n : result.pendingActionKind === 'batch'\n ? 'batch research'\n : 'write'\n const promptText =\n result.pendingActionKind === 'shell'\n ? 'Run this command? (y/N) '\n : result.pendingActionKind === 'email'\n ? 'Send this email? (y/N) '\n : result.pendingActionKind === 'batch'\n ? 'Continue? (y/N) '\n : 'Apply this write? (y/N) '\n console.log(`\\n[needs approval — ${kindLabel}] ${result.reason}`)\n const confirmed = await askYesNo(promptText)\n lastTrace = undefined\n lastNoTraceReason = `No harness trace — the last turn was a staged ${kindLabel} that was ${confirmed ? 'approved' : 'declined'} before the harness ran.`\n await handleTurn(message, confirmed, result.pendingActionId)\n return\n }\n\n if (result.status === 'needs_approval') {\n console.log(`\\n[needs approval — ${result.riskLevel}] ${result.reason}`)\n console.log(` \"${message}\"`)\n const confirmed = await askYesNo('Proceed? (y/N) ')\n if (confirmed) {\n await handleTurn(message, true)\n } else {\n // Without clearing lastTrace here, /why and /layers only fall back to\n // lastNoTraceReason when lastTrace is unset — so once any earlier turn in the\n // session had produced a real trace, a later decline left that stale trace in\n // place and printWhy/printLayers displayed the wrong (already-finished) turn's\n // explanation instead of this decline's reason.\n lastTrace = undefined\n lastNoTraceReason = 'No harness trace — the last turn was blocked on an approval gate and declined before the harness ran.'\n // Recorded now (not inside turn() itself) because the outcome is only known here —\n // see recordDeclinedRequest's doc comment for why this is safe unlike an eager append\n // inside runTurn. Without it, a later \"did that actually happen?\" question found no\n // trace of the request at all and confidently denied it was ever made.\n await assistant.recordDeclinedRequest('cli', message, result.reason ?? 'This request needed approval.')\n console.log('Cancelled.\\n')\n }\n return\n }\n\n if (result.status === 'needs_clarification') {\n await handleClarification(result)\n return\n }\n\n if (result.status === 'escalated') {\n lastTrace = undefined\n lastNoTraceReason = `No harness trace — the last turn escalated (${result.reason}) before completing.`\n console.log(`\\n[escalated] ${result.reason}\\n`)\n return\n }\n\n // A trivial turn's trace is present-but-empty (see assistant.ts's triviality branch) so\n // chat-ui can still render its \"Why?\"/\"Run detail\" panels with an explanation and an\n // all-unfired grid — the CLI has no such grid, so it keeps printing its existing plain\n // \"took the fast path\" copy instead of an empty/misleading confidence readout.\n //\n // A resolved write_file/run_shell_command/revert pendingAction (assistant.ts's\n // resolvePendingAction, reached whenever this call passed a `pendingActionId`) returns\n // neither harnessSkipped nor trace at all — it's not the trivial fast path, it just never\n // runs the per-turn HarnessRuntime. Both call sites that recurse into this same handleTurn()\n // to resolve one (the pendingActionKind branch below and handleUndoAction) already set\n // lastNoTraceReason to a specific, accurate \"staged ${kind} that was approved/declined\n // before the harness ran\" message right before recursing — unconditionally falling through\n // to the generic fallback here clobbered that back to undefined, which is exactly what made\n // /why and /layers report the wrong (fully generic) reason after any staged tool-call\n // resolution. Guard on the `pendingActionId` parameter (not the result shape, which a test\n // mock may reasonably leave sparse) so this block is skipped entirely for a pendingAction\n // resolution, leaving the caller's already-correct state alone.\n if (!pendingActionId) {\n lastTrace = result.harnessSkipped ? undefined : result.trace\n lastNoTraceReason = result.harnessSkipped\n ? 'No harness trace — the last turn was a simple, self-contained question answered directly without activating the harness (fast path).'\n : undefined\n }\n lastSources = result.sources\n // A trivial fast-path turn never touches plan state at all (assistant.ts's triviality\n // branch returns before ever loading/updating activePlan) — result.planStatus is always\n // undefined there regardless of whether a plan is genuinely active, so clobbering\n // lastPlanStatus on every turn made /plan wrongly report \"No active plan\" right after any\n // ordinary Q&A aside during an in-progress plan (planStatus's own doc comment: it \"can be\n // non-null across many consecutive turns\" — a harness-skipped turn just doesn't know either\n // way). Same reasoning applies to a resolved pendingAction (resolvePendingAction never\n // touches plan state either, same as the trivial path) — the `pendingActionId` guard above\n // covers both cases.\n if (!pendingActionId && !result.harnessSkipped) lastPlanStatus = result.planStatus\n lastTurnUsage = result.usage ? withCostEstimate(result.usage) : undefined\n const riskSuffix = result.riskLevel && result.riskLevel !== 'LOW' ? ` [risk: ${result.riskLevel}]` : ''\n const sourcesHint = result.sources && result.sources.length > 0 ? ` (${result.sources.length} source${result.sources.length > 1 ? 's' : ''} — /sources)` : ''\n const planHint = result.planStatus ? ` (plan: ${result.planStatus.completionPct.toFixed(0)}% — /plan)` : ''\n // Printed as its own line, not folded into riskSuffix/sourcesHint/planHint's inline\n // \"${...}\" style — this is a full sentence, not a short bracketed tag. Must be handled the\n // same way in both branches below (like the other hints), since result.contradictionNotice\n // is only known once HarnessRuntime.run() finishes, well after writeToken already streamed\n // `reply` itself to the screen — see assistant.ts's findContradictionNotice doc comment.\n const contradictionNotice = result.contradictionNotice ? `\\n\\n${result.contradictionNotice}` : ''\n if (streamedAnyTokens) {\n // The reply text is already on screen, printed token-by-token as it streamed in — but\n // onToken only ever saw draftReply, not a Phase 4.1 pausedNote appended afterward (see\n // AssistantTurnResult.pausedNote's doc comment), so that part still needs printing here.\n const pausedNoteText = result.pausedNote ? `\\n\\n${result.pausedNote}` : ''\n process.stdout.write(`${pausedNoteText}${riskSuffix}${sourcesHint}${planHint}${contradictionNotice}\\n\\n`)\n } else {\n console.log(`\\nAielia>${riskSuffix} ${result.reply}${sourcesHint}${planHint}${contradictionNotice}\\n`)\n }\n } catch (err) {\n // Mirrors chat-ui's error bubble: a failed turn (e.g. proxy down) shouldn't\n // crash the REPL via an unhandled rejection — just report it and keep going.\n clearProgress()\n const { message: errorMessage, retryable } = classifyError(err, config.llmBackend)\n console.log(`\\n[error] ${errorMessage}${retryable ? ' Type the message again to retry.' : ''}\\n`)\n }\n }\n\n // Fails closed: a non-TTY (piped/scripted) stdin is read and closed eagerly by readline,\n // often before a slow turn (one that ran tool calls before hitting a risk gate) even reaches\n // this prompt — rl.question() then throws ERR_USE_AFTER_CLOSE instead of ever reading an\n // answer. Left uncaught, that exception unwound past this function entirely: the caller's\n // `confirmed` was never assigned, \"Cancelled.\" was never logged, and the line meant as the\n // answer was left to be dispatched as its own ordinary chat turn instead (readline had already\n // queued it as a 'line' event before this prompt ever ran) — a HIGH-risk/destructive request\n // was silently neither approved nor declined rather than being safely stopped. Any failure to\n // read a real answer must resolve to \"no\" (never \"yes\"), so a broken/absent confirmation\n // channel can't be mistaken for approval.\n function askYesNo(question: string): Promise<boolean> {\n // Test-only seam: bypasses stdin/readline entirely, same \"never touches stdin\" shape as the\n // nonInteractiveApprovalMode === 'decline' branch below, just with a caller-scripted answer\n // instead of a hardcoded decline.\n if (options.askYesNo) return options.askYesNo(question)\n // A designed decision, not a fallback: never touches stdin, so there's no readline race to\n // land in — unlike the catch below, this path is reached deterministically on every\n // approval gate, not only when reading the answer happens to fail.\n if (nonInteractiveApprovalMode === 'decline') {\n console.log(`\\n[non-interactive mode: auto-declining — ASSISTANT_NON_INTERACTIVE_APPROVAL=decline]`)\n return Promise.resolve(false)\n }\n return new Promise((resolve) => {\n try {\n rl.question(question, (answer) => resolve(answer.trim().toLowerCase().startsWith('y')))\n } catch {\n console.log(`\\n[could not read a response — treating as declined]`)\n resolve(false)\n }\n })\n }\n\n /**\n * Free-text counterpart of askYesNo, for the clarification sub-loop's option numbers, edit\n * notes, and \"Other\" free text. handleClarification's own nonInteractiveApprovalMode ===\n * 'decline' branch returns before ever calling this, so — unlike askYesNo — there's no decline\n * branch to mirror here; the same fail-closed \"never throw past the caller\" shape still applies\n * for the read-failure case, just resolving to an empty string (an unrecognized/ignored answer,\n * see printClarificationQuestion's callers) instead of askYesNo's \"treat as declined\" boolean.\n */\n function askLine(question: string): Promise<string> {\n // Test-only seam — same shape as askYesNo's own.\n if (options.askLine) return options.askLine(question)\n return new Promise((resolve) => {\n try {\n rl.question(question, (answer) => resolve(answer.trim()))\n } catch {\n console.log(`\\n[could not read a response — treating as blank]`)\n resolve('')\n }\n })\n }\n\n // Refreshes `config`/`overriddenKeys` from disk and rebuilds `assistant` so a persisted\n // change (set or reset) takes effect on the very next turn — no restart needed. Cheap\n // enough to call unconditionally rather than special-casing which keys \"matter\": it's\n // just re-running the same wiring buildAssistant/main already do once at startup.\n async function reloadAssistant(): Promise<void> {\n const nextPersisted = await configStore.load()\n ;({ config, overriddenKeys } = resolveConfig(nextPersisted, envOverrides))\n assistant = options.assistant ?? (await buildAssistant(config, { dataDir, backend, remindersFile }))\n }\n\n async function handleConfigCommand(args: string[]): Promise<void> {\n if (args.length === 0) {\n console.log(`\\n${formatConfigListing(config, overriddenKeys)}\\n`)\n return\n }\n\n if (args[0] === 'set') {\n const key = args[1]\n const raw = args.slice(2).join(' ')\n if (!key || !isConfigKey(key)) {\n console.log(`\\n✗ Unknown config key \"${key ?? ''}\". Known keys: ${CONFIG_KEYS.join(', ')}\\n`)\n return\n }\n if (!raw) {\n console.log(`\\nUsage: /config set ${key} <value>\\n`)\n return\n }\n if (overriddenKeys.has(key)) {\n console.log(`\\n✗ \"${key}\" is pinned by ${ENV_VAR_FOR_CONFIG_KEY[key]} — unset that env var to change it here.\\n`)\n return\n }\n\n let value: unknown\n try {\n value = parseConfigValue(key, raw)\n } catch (err) {\n if (!(err instanceof ConfigValueParseError)) throw err\n console.log(`\\n✗ ${err.message}\\n`)\n return\n }\n\n const patch = { [key]: value } as Partial<AssistantConfig>\n try {\n validateConfig(patch, config)\n } catch (err) {\n if (!(err instanceof ConfigValidationError)) throw err\n console.log(`\\n✗ ${err.message}\\n`)\n return\n }\n\n await configStore.save(patch)\n await reloadAssistant()\n console.log(`\\n✓ ${key} updated (took effect immediately, no restart needed)\\n`)\n return\n }\n\n if (args[0] === 'reset') {\n const key = args[1]\n if (key && !isConfigKey(key)) {\n console.log(`\\n✗ Unknown config key \"${key}\". Known keys: ${CONFIG_KEYS.join(', ')}\\n`)\n return\n }\n // Takes effect immediately, same as /config set — and can silently break the active\n // session (e.g. reverting llmBackend mid-conversation, see conv81's finding), so this asks\n // first, same as any other consequential action (shell commands, writes, HIGH-risk\n // requests) — /config set is left as-is (a single, explicit, intentional value the user\n // just typed, not a broad \"wipe back to defaults\" that's easy to trigger without meaning to\n // reset something specific).\n const target = key ? `\"${key}\"` : 'ALL settings'\n const confirmed = await askYesNo(`\\nReset ${target} to default? This takes effect immediately. (y/N) `)\n if (!confirmed) {\n console.log('\\nCancelled — nothing was reset.\\n')\n return\n }\n const clearPatch = key\n ? ({ [key]: undefined } as Partial<AssistantConfig>)\n : (Object.fromEntries(CONFIG_KEYS.map((k) => [k, undefined])) as Partial<AssistantConfig>)\n await configStore.save(clearPatch)\n await reloadAssistant()\n console.log(`\\n✓ Reset ${key ?? 'all settings'} to default\\n`)\n return\n }\n\n console.log('\\nUsage: /config | /config set <key> <value> | /config reset [key]\\n')\n }\n\n /** Thin convenience wrapper over /config set model — not a second mechanism. Bare /model shows the current value. */\n async function handleModel(args: string[]): Promise<void> {\n if (args.length === 0) {\n console.log(`\\n${config.model ?? \"(using each backend's default)\"}\\n`)\n return\n }\n await handleConfigCommand(['set', 'model', args.join(' ')])\n }\n\n /**\n * Thin convenience wrapper over /config set|reset activeProject — same relationship handleModel\n * has to /config set model. Bare /project shows the resolved current value (an explicit override\n * if one is set, else the workspace root buildAssistant derived it from — see\n * PersonalAssistant.getActiveProject's doc comment for why this is never itself undefined once\n * the assistant is built). /project clear reverts to that workspace-derived default.\n */\n async function handleProject(args: string[]): Promise<void> {\n if (args.length === 0) {\n console.log(`\\n${assistant.getActiveProject() || '(none)'}\\n`)\n return\n }\n if (args[0] === 'clear') {\n await handleConfigCommand(['reset', 'activeProject'])\n return\n }\n await handleConfigCommand(['set', 'activeProject', args.join(' ')])\n }\n\n // A lookup keyed by the message's first whitespace-separated token — replaces what used to\n // be a growing `if (message === '/why') ... if (message === '/sources') ...` chain. Each\n // handler receives the remaining tokens as `args` (empty for commands that take none).\n // Rebuilt on every dispatch rather than hoisted to module scope because several handlers\n // close over `config`/`overriddenKeys`/`assistant`, which reloadAssistant() reassigns.\n const commands: Record<string, (args: string[]) => void | Promise<void>> = {\n '/why': () => printWhy(),\n '/layers': () => printLayers(),\n '/sources': () => printSources(),\n '/plan': (args) => handlePlan(args),\n '/help': () => printHelp(),\n '/clear': () => handleClear(),\n '/new': () => handleClear(),\n '/status': () => printStatus(),\n '/export': (args) => handleExport(args),\n '/undo': () => handleUndo(),\n '/undo-action': (args) => handleUndoAction(args),\n '/memory': (args) => handleMemory(args),\n '/search': (args) => handleSearch(args),\n '/model': (args) => handleModel(args),\n '/project': (args) => handleProject(args),\n '/cost': () => printCost(),\n '/doctor': () => handleDoctor(),\n '/config': (args) => handleConfigCommand(args),\n '/checkpoint': (args) => handleCheckpoint(args),\n }\n\n // readline's 'line' event fires for every buffered line as soon as it's parsed — it does\n // not wait for a previous line's async handler to finish, so piped/pasted multi-line input\n // can dispatch several commands concurrently. That's harmless for read-only commands\n // (/why, /sources, a plain turn) but not for /config: two concurrent handleConfigCommand\n // calls read and validate against the same in-memory `config` before either's update lands,\n // which can both corrupt the on-screen result and validate against stale state. Chaining\n // every dispatch onto `dispatchQueue` makes the loop a real REPL — one command fully\n // resolves before the next begins — matching what a human typing into the prompt would\n // experience anyway.\n async function dispatchOne(message: string): Promise<void> {\n const [token, ...args] = message.split(/\\s+/)\n const handler = commands[token]\n if (handler) {\n await handler(args)\n return\n }\n await handleTurn(message)\n }\n\n let dispatchQueue: Promise<void> = Promise.resolve()\n // One command fully resolves before the next begins — see the comment this used to carry\n // inline here (still true): readline's 'line' event doesn't wait for a previous line's async\n // handler, so piped/pasted multi-line input (or a test calling dispatchLine back to back)\n // could otherwise dispatch several commands concurrently, corrupting /config's read-modify-write.\n function enqueue(message: string): Promise<void> {\n const result = dispatchQueue\n .then(() => dispatchOne(message))\n // An unexpected throw here must not poison the queue for lines dispatched after it.\n .catch((err) => {\n console.error('[unexpected error]', err)\n })\n dispatchQueue = result\n return result\n }\n\n rl.on('line', (line) => {\n const message = line.trim()\n if (!message) { rl.prompt(); return }\n void enqueue(message).finally(() => rl.prompt())\n })\n // Safe to resume now — the 'line' listener right above is in place, so anything already\n // buffered on stdin (see the rl.pause() call up top) gets parsed and delivered, not dropped.\n rl.resume()\n\n return {\n dispatchLine: async (line: string) => {\n const message = line.trim()\n if (!message) return\n await enqueue(message)\n },\n close: () => rl.close(),\n getStatusIndicators: async () => {\n const indicators: string[] = []\n // Persistent context, not a notable/active state — always shown, same reasoning as the\n // spend-cap gauge below (once relevant, stays visible rather than only flashing on\n // change). fileTools is wired unconditionally in buildAssistant() (unlike shell/web/email\n // tools), so there's always an effective workspace root to show; mirrors the same\n // `config.workspaceRoot ?? process.cwd()` fallback buildAssistant() itself uses.\n indicators.push(`Workspace: ${config.workspaceRoot ?? process.cwd()}`)\n // A standing risk state, not a one-off event — worth keeping visible for the rest of the\n // session the same way dangerBanner warns once at startup (line ~349), since that startup\n // line scrolls out of view long before the session ends. Only shown when ON, mirroring\n // every other indicator here (\"blank when nothing to report\").\n if (config.dangerouslySkipPermissions) indicators.push('⚠ Permissions: auto-approved (dangerouslySkipPermissions)')\n if (lastPlanStatus !== undefined) indicators.push('Plan mode: active')\n const spendCapLine = await spendCapStatusLine()\n if (spendCapLine) indicators.push(spendCapLine)\n return indicators\n },\n }\n}\n\n/**\n * Phase 4 of plans/personal_assistant_cli_pinned_input_plan.html: `tuiMode` is resolved from the\n * same persisted-config + env-override chain every other config key uses (not just the env var\n * alone), so `/config set tuiMode enabled` works exactly like `/config set oneLoopMode enabled`\n * already does. This is a second, separate `configStore.load()` from the one `runCli()` performs\n * internally — an accepted duplication (one extra small JSON read at startup) rather than\n * threading a pre-resolved config through `runCli()`'s options, since `runCli()` must remain\n * callable on its own (tests, `non-interactive-mode.ts`) with no knowledge of this gate.\n *\n * `shouldLaunchTuiApp` (tui-mode-flag.ts) additionally requires both stdio streams to be a real\n * TTY — this repo's documented piped/scripted workflow (`printf 'msg\\nexit\\n' | node dist/cli.js`)\n * and every non-interactive shell always take the `runCli()` branch below regardless of the flag.\n * `runTuiApp` is imported dynamically so a disabled (the default) or non-TTY run never loads Ink\n * at all — `main()` behaves byte-for-byte as it does today in both those cases.\n */\nasync function main(): Promise<void> {\n const persisted = await defaultConfigStore.load()\n const { config } = resolveConfig(persisted, defaultEnvOverrides)\n\n if (shouldLaunchTuiApp(config.tuiMode ?? 'disabled', Boolean(process.stdout.isTTY), Boolean(process.stdin.isTTY))) {\n const { runTuiApp } = await import('./tui-app.js')\n await runTuiApp()\n return\n }\n\n await runCli()\n}\n\n/**\n * True when `entryArg` (a `process.argv[1]` value) refers to the same file as\n * `moduleHref` (an `import.meta.url`).\n *\n * The naive check — `moduleHref === pathToFileURL(entryArg).href` — breaks\n * whenever the script is launched through the npm bin: `npx\n * @buildaharness/aielia`, a global install, or any\n * `node_modules/.bin` shim all pass a **symlink** to this file as\n * `process.argv[1]`, while Node reports `import.meta.url` as the resolved real\n * path. So resolve `entryArg` to its real path before comparing. `resolveReal`\n * is injected for tests; it defaults to `fs.realpathSync`.\n */\nexport function entryArgMatchesModule(\n entryArg: string | undefined,\n moduleHref: string,\n resolveReal: (p: string) => string = realpathSync,\n): boolean {\n if (entryArg === undefined) return false\n try {\n return moduleHref === pathToFileURL(resolveReal(entryArg)).href\n } catch {\n return false\n }\n}\n\nfunction isEntryModule(): boolean {\n return entryArgMatchesModule(process.argv[1], import.meta.url)\n}\n\n// Guarded so importing this module (e.g. from cli.test.ts, which drives runCli() directly\n// instead) never starts a second live REPL against the real process.stdin/stdout and the real\n// ~/.buildaharness/personal-assistant data directory.\nif (isEntryModule()) void main()\n"],"names":["isEnoent","netConnect","key","resolvePath","options","commands","resolve"],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { OneLoopMode } from './one-loop-flag.js';
|
|
2
|
+
import { AskMode } from './ask-mode-flag.js';
|
|
3
|
+
import { PlanRolloutMode } from './plan-mode-flag.js';
|
|
4
|
+
import { TuiMode } from './tui-mode-flag.js';
|
|
5
|
+
export interface AssistantConfig {
|
|
6
|
+
llmBackend: 'proxy' | 'claude-cli' | 'anthropic' | 'openai' | 'openrouter';
|
|
7
|
+
proxyUrl: string;
|
|
8
|
+
authToken: string;
|
|
9
|
+
/**
|
|
10
|
+
* API key for the three direct-to-provider backends (anthropic/openai/openrouter) — one
|
|
11
|
+
* generic field reused across all three, mirroring authToken already being a single field
|
|
12
|
+
* for the proxy's bearer token rather than one field per possible deployment. Switching
|
|
13
|
+
* backends means re-entering the key, an accepted simplification consistent with this
|
|
14
|
+
* app's "keep config flat" convention.
|
|
15
|
+
*
|
|
16
|
+
* Stored at the same (low) trust boundary as authToken/braveApiKey — plaintext, not an OS
|
|
17
|
+
* keychain. Unlike those two, this is a *real* provider key, not a self-hosted proxy token,
|
|
18
|
+
* so the risk of a leaked config file is materially bigger; SettingsScreen must warn about
|
|
19
|
+
* this plainly next to the input (see dangerouslySkipPermissions' warning callout).
|
|
20
|
+
*/
|
|
21
|
+
apiKey?: string;
|
|
22
|
+
model?: string;
|
|
23
|
+
enableWeb: boolean;
|
|
24
|
+
/** Required whenever enableWeb is true — Brave Search is the only backend (see validateConfig). */
|
|
25
|
+
braveApiKey?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Which HTTP path web_search/fetch_url take in a plain browser tab (desktop always ignores
|
|
28
|
+
* this and uses the Tauri path — see chat-ui's createWebTools). 'direct' (default) is today's
|
|
29
|
+
* behavior: Brave and the target URL are called straight from the browser, which fails with a
|
|
30
|
+
* CORS error for most real targets (see createWebTools's doc comment) — kept as
|
|
31
|
+
* the default so a fresh browser install never silently starts sending traffic through a proxy
|
|
32
|
+
* that isn't configured. 'proxy' routes both tools through `${proxyUrl}/web/search`,
|
|
33
|
+
* `/web/fetch`, and `/web/grant` (see plans/browser_web_tools_via_proxy_plan.html) using the
|
|
34
|
+
* same `authToken` already wired for `/llm/chat`.
|
|
35
|
+
*/
|
|
36
|
+
webBackend: 'direct' | 'proxy';
|
|
37
|
+
enableShell: boolean;
|
|
38
|
+
shellTimeoutMs?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Node-level network containment for an approved run_shell_command execution (Decision 6,
|
|
41
|
+
* plans/lexical_functions_hardening_plan.html Phase 4 step 2): the spawned command's
|
|
42
|
+
* HTTP(S)_PROXY env vars are forced to point at a loopback-only proxy (see
|
|
43
|
+
* network-containment.ts) that only relays a CONNECT/request whose target host matches an
|
|
44
|
+
* entry here (exact match or subdomain). Undefined/empty denies all network access from an
|
|
45
|
+
* approved shell command — the safe default, since no host is a legitimate target until the
|
|
46
|
+
* user opts one in. This is a Node-level restriction, not an OS sandbox — see the plan's
|
|
47
|
+
* Decision 6 for why that tradeoff was chosen (identical across CLI/desktop, no new
|
|
48
|
+
* dependency) over real OS-native sandboxing or a container-per-command.
|
|
49
|
+
*/
|
|
50
|
+
shellNetworkAllowlist?: string[];
|
|
51
|
+
workspaceRoot?: string;
|
|
52
|
+
/**
|
|
53
|
+
* When true, `turn()` gives the model a real `send_email` tool — every call staged for
|
|
54
|
+
* approval exactly like `write_file` (adoption plan F2). Off by default: no send tool exists
|
|
55
|
+
* unless a transport is also configured below. The model can only ever propose a message.
|
|
56
|
+
*/
|
|
57
|
+
enableEmail: boolean;
|
|
58
|
+
/** Which transport delivers an approved email. `resend` needs `resendApiKey`; `smtp` needs `smtpHost`/`smtpPort` (+ `smtpUser`/`smtpPass` if the server requires auth). */
|
|
59
|
+
emailProvider?: 'resend' | 'smtp';
|
|
60
|
+
/** The sender address an approved email is delivered as. Required whenever `enableEmail` is set — the model never chooses it. */
|
|
61
|
+
emailFrom?: string;
|
|
62
|
+
/**
|
|
63
|
+
* Resend API key (https://resend.com). Stored at the same low trust boundary as `apiKey` /
|
|
64
|
+
* `braveApiKey` — plaintext in config.json, not an OS keychain; SettingsScreen warns next to
|
|
65
|
+
* the input the same way it does for `apiKey`.
|
|
66
|
+
*/
|
|
67
|
+
resendApiKey?: string;
|
|
68
|
+
smtpHost?: string;
|
|
69
|
+
smtpPort?: number;
|
|
70
|
+
smtpUser?: string;
|
|
71
|
+
/** SMTP password — same plaintext-in-config trust boundary as `resendApiKey` / `apiKey`. */
|
|
72
|
+
smtpPass?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Equivalent of Claude Code's own --dangerously-skip-permissions: when true, both approval
|
|
75
|
+
* gates PersonalAssistant otherwise always enforces — the message-level risk gate
|
|
76
|
+
* (risk-classifier.ts) and write_file/run_shell_command's per-call staging (file-tools.ts/
|
|
77
|
+
* shell-tools.ts) — execute immediately instead of returning `needs_approval`. The
|
|
78
|
+
* sandboxing itself (workspace-root path validation, SSRF guard, shell env allowlist,
|
|
79
|
+
* output truncation, timeout) is unaffected — this only skips asking, never the underlying
|
|
80
|
+
* safety limits. Off by default; named to match Claude Code's own flag so it reads as
|
|
81
|
+
* exactly as dangerous as it is.
|
|
82
|
+
*/
|
|
83
|
+
dangerouslySkipPermissions: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Session-scoped spend ceilings, enforced (not just reported) before each new turn starts —
|
|
86
|
+
* see spend-cap.ts. Undefined (the default) preserves today's unbounded behavior; this is an
|
|
87
|
+
* opt-in safety rail on top of what /cost already computes, not a new default constraint.
|
|
88
|
+
*/
|
|
89
|
+
sessionCostLimitUsd?: number;
|
|
90
|
+
/** Secondary ceiling on completed turns this session — see SpendCapConfig's doc comment in spend-cap.ts for why this counts turns, not raw internal LLM calls. */
|
|
91
|
+
sessionCallLimit?: number;
|
|
92
|
+
/**
|
|
93
|
+
* R5 of plans/harness_d2_one_loop_rewire_plan.html — the rollout flag for the harness-driven
|
|
94
|
+
* one-loop proposer (see one-loop-flag.ts's doc comment for the full rationale). Undefined (the
|
|
95
|
+
* default, for the whole rollout window) means PersonalAssistant falls back to
|
|
96
|
+
* DEFAULT_ONE_LOOP_MODE ('disabled') — today's behavior, byte-for-byte. This is the shared
|
|
97
|
+
* config seam every surface reads it through: the CLI resolves it from `ASSISTANT_ONE_LOOP`
|
|
98
|
+
* (cli-config.ts's envOverridesFromProcessEnv), chat-ui and the Tauri desktop build from the
|
|
99
|
+
* build-time `VITE_ASSISTANT_ONE_LOOP` (browser-config.ts's envOverridesFromImportMetaEnv), and
|
|
100
|
+
* all three from a persisted `oneLoopMode` if one has been set. Kept out of DEFAULT_CONFIG so an
|
|
101
|
+
* unset value stays undefined and PersonalAssistant owns the default, matching every other
|
|
102
|
+
* optional field here.
|
|
103
|
+
*/
|
|
104
|
+
oneLoopMode?: OneLoopMode;
|
|
105
|
+
/**
|
|
106
|
+
* Q2 of plans/ask_question_and_plan_mode_plan.html — the global-flag control point (tier 1 of
|
|
107
|
+
* INV-29's three-tier resolution) for the batched-questions ask-question mechanism. Undefined
|
|
108
|
+
* (the default, for the whole rollout window) means PersonalAssistant falls back to
|
|
109
|
+
* `DEFAULT_ASK_MODE` ('disabled') — today's behavior, byte-for-byte: a structured-question
|
|
110
|
+
* escalation stays on the plain `escalated`/`reply: null` path. Mirrors `oneLoopMode`'s exact
|
|
111
|
+
* chain: env override (`ASSISTANT_ASK_MODE`, cli-config.ts), build-time override
|
|
112
|
+
* (`VITE_ASSISTANT_ASK_MODE`, chat-ui's browser-config.ts), and a package-owned default kept
|
|
113
|
+
* out of DEFAULT_CONFIG so an unset value stays undefined.
|
|
114
|
+
*/
|
|
115
|
+
askMode?: AskMode;
|
|
116
|
+
/**
|
|
117
|
+
* P11 of plans/ask_question_and_plan_mode_plan.html — the rollout flag for P1-P8's plan-mode
|
|
118
|
+
* behavior change (see plan-mode-flag.ts's doc comment for the full rationale). Undefined (the
|
|
119
|
+
* default, for the whole rollout window) means PersonalAssistant falls back to
|
|
120
|
+
* `DEFAULT_PLAN_MODE` ('legacy') — P3's auto-trigger never fires, so a turn never auto-enters
|
|
121
|
+
* the exclusive drafting+approval loop. Mirrors `oneLoopMode`/`askMode`'s exact chain: env
|
|
122
|
+
* override (`ASSISTANT_PLAN_MODE`, cli-config.ts), build-time override
|
|
123
|
+
* (`VITE_ASSISTANT_PLAN_MODE`, chat-ui's browser-config.ts), and a package-owned default kept
|
|
124
|
+
* out of DEFAULT_CONFIG so an unset value stays undefined.
|
|
125
|
+
*/
|
|
126
|
+
planMode?: PlanRolloutMode;
|
|
127
|
+
/**
|
|
128
|
+
* Phase 4 of plans/personal_assistant_cli_pinned_input_plan.html — the rollout flag for the
|
|
129
|
+
* Ink-driven pinned-bottom-input interactive shell (tui-app.ts's runTuiApp()). Undefined (the
|
|
130
|
+
* default, for this plan's whole rollout window) means cli.ts's main() falls back to
|
|
131
|
+
* DEFAULT_TUI_MODE ('disabled') — today's plain readline-based REPL, byte-for-byte. Mirrors
|
|
132
|
+
* oneLoopMode/askMode/planMode's exact chain: env override (ASSISTANT_TUI, cli-config.ts) and
|
|
133
|
+
* a package-owned default kept out of DEFAULT_CONFIG so an unset value stays undefined. Unlike
|
|
134
|
+
* those three, this has no chat-ui/desktop build-time counterpart — there's no terminal to pin
|
|
135
|
+
* input to in a browser tab, so this is a CLI-only flag. Even when 'enabled', main() only
|
|
136
|
+
* launches the Ink shell when stdout/stdin are both a real TTY (see cli.ts) — piped/scripted
|
|
137
|
+
* input always takes the readline path regardless of this setting.
|
|
138
|
+
*/
|
|
139
|
+
tuiMode?: TuiMode;
|
|
140
|
+
/**
|
|
141
|
+
* The project label newly captured project-scoped facts are tagged with, and the one
|
|
142
|
+
* project-scoped facts are filtered against when building a turn's context (see
|
|
143
|
+
* memory-service.ts's `loadFacts`/`recordFacts` and fact-extraction.ts's `UserFact.project`).
|
|
144
|
+
* Undefined (the default) means "derive it from `workspaceRoot` instead" — set this only when
|
|
145
|
+
* the user is explicitly talking about a project other than the one they're currently `cd`'d
|
|
146
|
+
* into. `/project [name]` is sugar over `/config set activeProject <name>` /
|
|
147
|
+
* `/config reset activeProject`, mirroring `/model`'s relationship to `/config set model`.
|
|
148
|
+
*/
|
|
149
|
+
activeProject?: string;
|
|
150
|
+
}
|
|
151
|
+
/** Every AssistantConfig key, in the order every surface's settings UI/listing renders them. */
|
|
152
|
+
export declare const CONFIG_KEYS: readonly (keyof AssistantConfig)[];
|
|
153
|
+
/** Matches today's actual hardcoded defaults (proxy backend, web/shell off) — this plan changes nothing for a caller that never touches config. */
|
|
154
|
+
export declare const DEFAULT_CONFIG: AssistantConfig;
|
|
155
|
+
/** A persistence backend for AssistantConfig — one implementation per surface (Node JSON file, localStorage, Tauri fs). */
|
|
156
|
+
export interface ConfigStore {
|
|
157
|
+
load(): Promise<Partial<AssistantConfig>>;
|
|
158
|
+
/** Merges patch onto whatever is already persisted — never a blind overwrite of unrelated keys. */
|
|
159
|
+
save(patch: Partial<AssistantConfig>): Promise<void>;
|
|
160
|
+
}
|
|
161
|
+
export interface ResolvedConfig {
|
|
162
|
+
config: AssistantConfig;
|
|
163
|
+
/** Keys whose value came from `overrides` (env var / build-time var) rather than persisted config or the default — these should render read-only in any settings UI. */
|
|
164
|
+
overriddenKeys: Set<keyof AssistantConfig>;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Precedence: overrides > persisted > DEFAULT_CONFIG, applied key by key. A key present in
|
|
168
|
+
* `overrides` but set to `undefined` is treated as absent — it must never shadow a persisted
|
|
169
|
+
* value, which is what would happen if this just did `{ ...DEFAULT_CONFIG, ...persisted, ...overrides }`.
|
|
170
|
+
*/
|
|
171
|
+
export declare function resolveConfig(persisted?: Partial<AssistantConfig>, overrides?: Partial<AssistantConfig>): ResolvedConfig;
|
|
172
|
+
/** Thrown by validateConfig instead of returning a falsy value, so callers can't accidentally persist a rejected patch. */
|
|
173
|
+
export declare class ConfigValidationError extends Error {
|
|
174
|
+
}
|
|
175
|
+
export declare function validateConfig(patch: Partial<AssistantConfig>, existing: AssistantConfig): void;
|