@dreb/coding-agent 2.69.0 → 2.70.1

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 CHANGED
@@ -81,7 +81,9 @@ Or use a custom provider (corporate proxy, Bedrock, etc.) — see [Custom provid
81
81
 
82
82
  Then just talk to dreb. All 13 standard built-in tools are enabled by default (unless `backgroundAgents.maxConcurrentSubagents` is `0`, which removes `subagent` from new parent sessions): `read`, `write`, `edit`, `bash`, `grep`, `find`, `ls`, `web_search`, `web_fetch`, `subagent`, `wait`, `watch_github_ci`, and `ask_user`. Use `--tools` to restrict to a subset (e.g., `--tools read,grep,find,ls` for read-only). Three additional tools — `search`, `skill`, and `tasks_update` — are always active regardless of `--tools`. `suggest_next` is active by default but excluded when `--tools` is specified. The model uses these to fulfill your requests. Add capabilities via [skills](#skills), [prompt templates](#prompt-templates), [extensions](#extensions), or [packages](#packages).
83
83
 
84
- **Also available:** [`@dreb/telegram`](https://www.npmjs.com/package/@dreb/telegram) — run dreb as a Telegram bot with live tool status and visible results for user-facing tools (`npm install -g @dreb/telegram`). [`@dreb/dashboard`](https://www.npmjs.com/package/@dreb/dashboard) — run `dreb dashboard` for a browser UI with fleet overview, full chat steering, dismissible session banners confined to the transcript column without shifting the fleet sidebar, a grouped header with back navigation on the left, model/thinking controls in a separate row, and the fleet sidebar toggle in the bottom usage/context-stats row (also available with details collapsed), explicit navigation that preserves viewed closed transcripts as read-only snapshots, a TUI-parity rolling TPS indicator with long-term delta, generic fail-closed built-in slash-command discovery and execution, inline provider/API failures with partial output preserved, sanitized raster tool images plus sent user uploads retained as bounded transcript previews by default, a bounded all-agent subagent panel with drill-in, a fleet sidebar on session/subagent views with the fleet cards' project/status/reasons, activity/latest-assistant preview, subagents, tasks, model/context/cost, and message/activity metadata (deterministic order, persisted desktop collapse and pointer/keyboard-resizable width, highest-priority status border while hidden, safe-area-aware mobile drawer with managed focus and Escape-to-close without aborting the agent), host file browser, dreb memory editor with exact-revision saves and automatic index cleanup on delete, curated appearance themes with per-browser light/dark mode and font selection (theme default, Google-hosted IBM Plex Mono, or the bundled self-hosted JetBrains Mono, Fira Code, Iosevka, OpenDyslexic, and Atkinson Hyperlegible), and resilient Tailscale/rotating-code pairing with a configurable 180-day default and advance expiry warnings (`npm install -g @dreb/dashboard`; see [docs/dashboard.md](docs/dashboard.md)). Tool images cross browser-facing transport as content-addressed references; in dashboard mode each unique image crosses the RPC stdout pipe at most once per child process (later occurrences become references), and browser-local Settings offers placeholders, bounded previews, or informed-opt-in originals, with size disclosure and confirmation above 1 MiB. Full-resolution HTML export remains self-contained. The Memories screen is dreb-only (`~/.dreb/memory` and populated active/on-disk-session project `.dreb/memory`; empty projects are omitted), shows complete indexes with a >200-line warning, opens local index links within the selected scope, replaces stale editor content with visible loading feedback, surfaces malformed entry frontmatter for repair, preserves drafts on conflicts, and does not create/rename entries or expose Claude paths. Live-first startup fetches runtime cards and opens SSE before the historical inventory scan finishes; unchanged session metadata is reused on later inventory and recovery reads. Compact SSE snapshots then update live fleet cards without repeatedly fetching the cross-project inventory, and session drill-in hydrates state, messages, and background agents through one ordered snapshot request. Direct sidebar switching, including leaving a closed session, loads the destination with its own screen state and per-session text draft; abandoned hydration cannot overwrite the new view. While drill-in remains mounted, authoritative detail refreshes keep its header current, confirmed model/thinking changes are reconciled without stale snapshot rollback, and post-compaction context uses a conservative estimate until fresh provider usage arrives. Terminal provider failures show their reason on fleet cards, while transient failures clear terminal state when automatic retry begins and remain recorded inline on the failed attempt. Its top bar and persistent session header indicators report connecting, connected, retrying, resyncing, disconnected, or auth failed; bounded SSE replay plus an explicit snapshot barrier restores session state, tasks, and image references after a reload, restart, gap, backpressure disconnect, or stalled stream, while authenticated image routes recover bytes separately from authoritative transcripts. Persisted sessions whose recorded working directory is missing remain visible and resumable: Dashboard asks for an existing runtime directory, validates it before starting the child, shows both historical and effective paths, and leaves the original session header unchanged.
84
+ **Also available:** [`@dreb/telegram`](https://www.npmjs.com/package/@dreb/telegram) — run dreb as a Telegram bot with live tool status and visible results for user-facing tools (`npm install -g @dreb/telegram`). [`@dreb/dashboard`](https://www.npmjs.com/package/@dreb/dashboard) — run `dreb dashboard` for a browser UI with fleet overview, full chat steering, dismissible session banners confined to the transcript column without shifting the fleet sidebar, a grouped header with back navigation on the left, model/thinking controls in a separate row, and the fleet sidebar toggle in the bottom usage/context-stats row (also available with details collapsed), explicit navigation that preserves viewed closed transcripts as read-only snapshots, a TUI-parity rolling TPS indicator with long-term delta, generic fail-closed built-in slash-command discovery and execution, inline provider/API failures with partial output preserved, sanitized raster tool images plus sent user uploads retained as bounded transcript previews by default, a bounded all-agent subagent panel with drill-in, a fleet sidebar on session/subagent views with the fleet cards' project/status/reasons, activity/latest-assistant preview, subagents, tasks, model/context/cost, and message/activity metadata (deterministic order, persisted desktop collapse and pointer/keyboard-resizable width, highest-priority status border while hidden, safe-area-aware mobile drawer with managed focus and Escape-to-close without aborting the agent), host file browser, dreb memory editor with exact-revision saves and automatic index cleanup on delete, curated appearance themes with per-browser light/dark mode and a font picker (36 explicit choices plus Theme default: the existing Google-hosted IBM Plex Mono and bundled JetBrains Mono, Fira Code, Iosevka, OpenDyslexic, and Atkinson Hyperlegible, plus 15 sans-serif and 15 serif families; visible open-listbox options preview their regular faces, restricted derivatives have neutral primary names with secondary source attribution, and alternate self-hosted fonts remain lazy when closed), and resilient Tailscale/rotating-code pairing with a configurable 180-day default and advance expiry warnings (`npm install -g @dreb/dashboard`; see [docs/dashboard.md](docs/dashboard.md)). Tool images cross browser-facing transport as content-addressed references; in dashboard mode each unique image crosses the RPC stdout pipe at most once per child process (later occurrences become references), and browser-local Settings offers placeholders, bounded previews, or informed-opt-in originals, with size disclosure and confirmation above 1 MiB. Full-resolution HTML export remains self-contained. The Memories screen is dreb-only (`~/.dreb/memory` and populated active/on-disk-session project `.dreb/memory`; empty projects are omitted), shows complete indexes with a >200-line warning, opens local index links within the selected scope, replaces stale editor content with visible loading feedback, surfaces malformed entry frontmatter for repair, preserves drafts on conflicts, and does not create/rename entries or expose Claude paths. Live-first startup fetches runtime cards and opens SSE before the historical inventory scan finishes; unchanged session metadata is reused on later inventory and recovery reads. Compact SSE snapshots then update live fleet cards without repeatedly fetching the cross-project inventory, and session drill-in hydrates state, messages, and background agents through one ordered snapshot request. Direct sidebar switching, including leaving a closed session, loads the destination with its own screen state and per-session text draft; abandoned hydration cannot overwrite the new view. While drill-in remains mounted, authoritative detail refreshes keep its header current, confirmed model/thinking changes are reconciled without stale snapshot rollback, and post-compaction context uses a conservative estimate until fresh provider usage arrives. Terminal provider failures show their reason on fleet cards, while transient failures clear terminal state when automatic retry begins and remain recorded inline on the failed attempt. Its top bar and persistent session header indicators report connecting, connected, retrying, resyncing, disconnected, or auth failed; bounded SSE replay plus an explicit snapshot barrier restores session state, tasks, and image references after a reload, restart, gap, backpressure disconnect, or stalled stream, while authenticated image routes recover bytes separately from authoritative transcripts. Persisted sessions whose recorded working directory is missing remain visible and resumable: Dashboard asks for an existing runtime directory, validates it before starting the child, shows both historical and effective paths, and leaves the original session header unchanged.
85
+
86
+ Dashboard theme cards keep IBM Plex Mono for Theme default previews, while the font picker's Theme default previews the active theme's built-in family. All 30 additions have real regular, italic, bold, and bold-italic styles, with previews loaded as rows approach the visible scroll window. The 30 added families are source-attributed Latin subsets with neutral internal names (all OFL-licensed); existing IBM Google hosting is unchanged. No font uploads or new multilingual bundles are added; missing scripts use browser fallback. See [expanded font provenance](../dashboard/src/client/assets/fonts/expanded/PROVENANCE.md), [keyboard controls and lazy loading](docs/dashboard.md#appearance-system), and [manual custom fonts in a private source build](docs/dashboard.md#custom-fonts-in-a-source-build). There is no runtime font-upload or install-directory mechanism.
85
87
 
86
88
  **Platform notes:** [Windows](docs/windows.md) | [Termux (Android)](docs/termux.md) | [tmux](docs/tmux.md) | [Terminal setup](docs/terminal-setup.md) | [Shell aliases](docs/shell-aliases.md)
87
89
 
@@ -676,7 +678,7 @@ cat README.md | dreb -p "Summarize this text"
676
678
 
677
679
  Available standard tools: `read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`, `web_search`, `web_fetch`, `subagent`, `wait`, `watch_github_ci`, `ask_user`
678
680
 
679
- `ask_user` pauses the turn and asks you one or more structured clarifying questions in a single call — batched together and answered as one wizard. Each question has Markdown-formatted question text, optional 2-4 multiple-choice options (single- or multi-select), and a "type your own answer" field — rendered natively in the TUI and the Dashboard, and over RPC. Submitting sends the whole batch of structured answers back at once; choosing **Stop agent** or pressing `Esc` aborts the whole current turn rather than continuing without an answer. Options must contain nonblank text. When a question has no options its free-text field is always shown, so every question is answerable. Unanswered questions come back flagged as skipped rather than blocking the turn. An optional `timeoutSeconds` auto-stops the turn after the given number of seconds with a live countdown on every surface; recovered Dashboard sessions retain the original deadline rather than restarting it. Tool abort, timeout, host teardown, and headless/no-UI modes settle cleanly, so the agent never deadlocks waiting on an absent user. UI or response-protocol failures remain distinct from question closure. In the TUI: `↑`/`↓` move between questions, their options, and the free-text fields, `Space` toggles a checkbox (multi-select), `Enter` submits the batch, and `Esc` stops the turn. In the Dashboard: native radios/checkboxes plus a text field per question, an in-card **Stop agent** button that remains accessible on mobile, and `Esc` to stop.
681
+ `ask_user` pauses the turn and asks you one or more structured clarifying questions in a single call — batched together and answered as one wizard. Each question has Markdown-formatted question text, optional 2-4 multiple-choice options (single- or multi-select), and a "type your own answer" field — rendered natively in the TUI and the Dashboard, and over RPC. Submitting sends the whole batch of structured answers back at once; choosing **Stop agent** or pressing `Esc` aborts the whole current turn rather than continuing without an answer. Options must contain nonblank text. Options may also be sent as `{label, description}` objects (shown as `label — description`), `header` is accepted in place of a question's `title`, and `questions` may arrive as a JSON-encoded string; all are converted to the standard shape before display. When a question has no options its free-text field is always shown, so every question is answerable. Unanswered questions come back flagged as skipped rather than blocking the turn. An optional `timeoutSeconds` auto-stops the turn after the given number of seconds with a live countdown on every surface; recovered Dashboard sessions retain the original deadline rather than restarting it. Tool abort, timeout, host teardown, and headless/no-UI modes settle cleanly, so the agent never deadlocks waiting on an absent user. UI or response-protocol failures remain distinct from question closure. In the TUI: `↑`/`↓` move between questions, their options, and the free-text fields, `Space` toggles a checkbox (multi-select), `Enter` submits the batch, and `Esc` stops the turn. In the Dashboard: native radios/checkboxes plus a text field per question, an in-card **Stop agent** button that remains accessible on mobile, and `Esc` to stop.
680
682
 
681
683
  Three additional tools are always active and don't need to appear in `--tools`:
682
684
  - `search` — semantic codebase search
@@ -30,14 +30,18 @@ export interface AskUserDetails {
30
30
  failed?: boolean;
31
31
  }
32
32
  declare const askUserSchema: import("@sinclair/typebox").TObject<{
33
- questions: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
33
+ questions: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
34
34
  question: import("@sinclair/typebox").TString;
35
35
  title: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
36
- options: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
36
+ header: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
37
+ options: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TString, import("@sinclair/typebox").TObject<{
38
+ label: import("@sinclair/typebox").TString;
39
+ description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
40
+ }>]>>>;
37
41
  allowFreeText: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
38
42
  multiSelect: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
39
43
  multiline: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
40
- }>>;
44
+ }>>, import("@sinclair/typebox").TString]>;
41
45
  timeoutSeconds: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
42
46
  }>;
43
47
  export type AskUserInput = Static<typeof askUserSchema>;
@@ -1 +1 @@
1
- {"version":3,"file":"ask-user.d.ts","sourceRoot":"","sources":["../../../src/core/tools/ask-user.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,KAAK,MAAM,EAAQ,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAwD,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAKnH,mEAAmE;AACnE,MAAM,WAAW,mBAAmB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,OAAO,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC9B,8CAA8C;IAC9C,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,uEAAuE;IACvE,WAAW,EAAE,OAAO,CAAC;IACrB,yDAAyD;IACzD,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAsCD,QAAA,MAAM,aAAa;;;;;;;;;;EAiBjB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAiIxD;;;;;;GAMG;AACH,wBAAgB,2BAA2B,IAAI,cAAc,CAAC,OAAO,aAAa,EAAE,cAAc,GAAG,SAAS,CAAC,CAsE9G","sourcesContent":["/**\n * ask_user tool.\n *\n * Lets the agent pause and ask the user one or more structured clarifying\n * questions in a single call — each with optional multiple-choice options,\n * single- or multi-select, and a \"type your own answer\" free-text field —\n * rendered natively as a wizard in the TUI, the Dashboard, and over RPC. The\n * whole batch is answered together and returned as one result. Answering,\n * stopping the turn, aborting, or timing out always settles cleanly so the\n * agent never deadlocks on an absent user. Unanswered questions come back\n * flagged as skipped rather than blocking the turn.\n */\n\nimport { Text } from \"@dreb/tui\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport type { AskQuestion, AskRequest, AskResult, ExtensionContext, ToolDefinition } from \"../extensions/types.js\";\n\n// ============================================================================\n// Types\n\n/** One question's outcome, surfaced in the tool result details. */\nexport interface AskUserAnswerDetail {\n\tquestion: string;\n\ttitle?: string;\n\tselected: string[];\n\tcustomText?: string;\n\t/** True when this question closed without an answer. */\n\tskipped: boolean;\n}\n\nexport interface AskUserDetails {\n\t/** One entry per question asked, in order. */\n\tanswers: AskUserAnswerDetail[];\n\t/** True when no interactive UI was available (headless/print mode). */\n\tunavailable: boolean;\n\t/** True when the UI host or response protocol failed. */\n\tfailed?: boolean;\n}\n\n// ============================================================================\n// Schema\n\nconst questionSchema = Type.Object({\n\tquestion: Type.String({\n\t\tdescription: \"The Markdown-formatted question to ask the user. Be specific about what you need to decide.\",\n\t}),\n\ttitle: Type.Optional(\n\t\tType.String({\n\t\t\tdescription: \"Short bold header shown above this question.\",\n\t\t}),\n\t),\n\toptions: Type.Optional(\n\t\tType.Array(Type.String({ minLength: 1, pattern: \"^.*[^ \\\\t\\\\r\\\\n].*$\" }), {\n\t\t\tminItems: 2,\n\t\t\tmaxItems: 4,\n\t\t\tdescription: \"2-4 nonblank suggested answers the user can pick from.\",\n\t\t}),\n\t),\n\tallowFreeText: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription: \"Offer a 'type your own answer' field. Defaults to true.\",\n\t\t}),\n\t),\n\tmultiSelect: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription: \"Allow selecting multiple options (checkboxes). Only meaningful with options.\",\n\t\t}),\n\t),\n\tmultiline: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription: \"Use a large multi-line text area for open-ended answers.\",\n\t\t}),\n\t),\n});\n\nconst askUserSchema = Type.Object({\n\tquestions: Type.Array(questionSchema, {\n\t\tminItems: 1,\n\t\tmaxItems: 10,\n\t\tdescription:\n\t\t\t\"One or more clarifying questions to ask together in a single wizard. \" +\n\t\t\t\"Batch every question you need answered in one call — the user answers them all and submits once.\",\n\t}),\n\ttimeoutSeconds: Type.Optional(\n\t\tType.Number({\n\t\t\tminimum: 5,\n\t\t\tmaximum: 3600,\n\t\t\tdescription:\n\t\t\t\t\"Optional: stop the current agent turn after this many seconds if the user does not respond. \" +\n\t\t\t\t\"Shows a live countdown. Omit to wait indefinitely.\",\n\t\t}),\n\t),\n});\n\nexport type AskUserInput = Static<typeof askUserSchema>;\ntype AskUserQuestion = Static<typeof questionSchema>;\n\n// ============================================================================\n// Normalization\n\n/**\n * Normalize a single question so every rendered surface has at least one usable\n * answer control and no impossible flag combinations survive.\n */\nfunction normalizeQuestion(q: AskUserQuestion): AskQuestion {\n\tconst hasOptions = (q.options?.length ?? 0) > 0;\n\treturn {\n\t\tquestion: q.question,\n\t\ttitle: q.title,\n\t\toptions: q.options,\n\t\t// Guarantee at least one answer control: with no options, free text must\n\t\t// be offered regardless of the requested flag, otherwise a question would\n\t\t// render only a Skip control and no way to answer.\n\t\tallowFreeText: hasOptions ? q.allowFreeText : true,\n\t\t// multiSelect is only meaningful with options; multiline only with free\n\t\t// text — normalize away impossible combinations.\n\t\tmultiSelect: hasOptions ? q.multiSelect : undefined,\n\t\tmultiline: hasOptions ? (q.allowFreeText === false ? undefined : q.multiline) : q.multiline,\n\t};\n}\n\n// ============================================================================\n// Result helpers\n\nfunction textResult(text: string, details: AskUserDetails) {\n\treturn {\n\t\tcontent: [{ type: \"text\" as const, text }],\n\t\tdetails,\n\t};\n}\n\nfunction skippedAnswer(q: AskQuestion): AskUserAnswerDetail {\n\treturn { question: q.question, title: q.title, selected: [], skipped: true };\n}\n\nfunction unavailableResult(questions: AskQuestion[]) {\n\treturn textResult(\n\t\t\"The ask_user tool requires an interactive UI, which is not available in this mode. \" +\n\t\t\t\"Proceed using your best judgment without waiting for an answer.\",\n\t\t{ answers: questions.map(skippedAnswer), unavailable: true },\n\t);\n}\n\nfunction unansweredResult(questions: AskQuestion[]) {\n\treturn textResult(\"The questions closed without an answer.\", {\n\t\tanswers: questions.map(skippedAnswer),\n\t\tunavailable: false,\n\t});\n}\n\nfunction failedResult(questions: AskQuestion[]) {\n\treturn textResult(\n\t\t\"The questions could not be delivered because the interactive UI or response protocol failed. \" +\n\t\t\t\"Continue without this input.\",\n\t\t{ answers: questions.map(skippedAnswer), unavailable: false, failed: true },\n\t);\n}\n\n/** Build a per-question detail entry from the user's answer. */\nfunction toAnswerDetail(q: AskQuestion, answer: AskResult[\"answers\"][number] | undefined): AskUserAnswerDetail {\n\tif (!answer || answer.skipped) return skippedAnswer(q);\n\tconst customText = answer.customText?.trim() || undefined;\n\tconst selected = answer.selected ?? [];\n\tif (selected.length === 0 && !customText) return skippedAnswer(q);\n\treturn { question: q.question, title: q.title, selected, customText, skipped: false };\n}\n\n/** Compose the model-facing summary, one block per question. */\nfunction summarizeAnswers(details: AskUserAnswerDetail[]): string {\n\tconst blocks = details.map((d) => {\n\t\tconst heading = (d.title || d.question || \"\").replace(/\\s+/g, \" \").trim();\n\t\tif (d.skipped) return `**${heading}** → (no answer)`;\n\t\tconst parts: string[] = [];\n\t\tif (d.selected.length > 0) parts.push(`Selected: ${d.selected.join(\", \")}`);\n\t\tif (d.customText) parts.push(`wrote: \"${d.customText}\"`);\n\t\treturn `**${heading}** → ${parts.join(\" / \")}`;\n\t});\n\treturn blocks.join(\"\\n\");\n}\n\nfunction answeredResult(questions: AskQuestion[], result: AskResult) {\n\tconst details = questions.map((q, i) => toAnswerDetail(q, result.answers[i]));\n\treturn textResult(summarizeAnswers(details), { answers: details, unavailable: false });\n}\n\n// ============================================================================\n// Render helpers\n\nfunction callLabel(args: { questions?: Array<{ title?: string; question?: string }> } | undefined): string {\n\tconst questions = args?.questions ?? [];\n\tconst count = questions.length;\n\tif (count === 1) {\n\t\tconst first = questions[0];\n\t\tconst label = (first?.title || first?.question || \"\").replace(/\\s+/g, \" \").trim();\n\t\treturn label.length > 80 ? `${label.slice(0, 79)}…` : label;\n\t}\n\treturn `${count} questions`;\n}\n\nfunction formatCall(\n\targs: { questions?: Array<{ title?: string; question?: string }> } | undefined,\n\ttheme: any,\n): string {\n\treturn `${theme.fg(\"toolTitle\", theme.bold(\"ask_user\"))} ${theme.fg(\"accent\", callLabel(args))}`;\n}\n\nfunction formatResult(details: AskUserDetails, theme: any): string {\n\tif (details.unavailable) return theme.fg(\"toolOutput\", \"no interactive UI — continued without asking\");\n\tif (details.failed) return theme.fg(\"toolOutput\", \"interactive UI failed — continued without an answer\");\n\tconst answered = details.answers.filter((a) => !a.skipped);\n\tif (answered.length === 0) return theme.fg(\"toolOutput\", \"closed without an answer\");\n\tif (details.answers.length === 1) {\n\t\tconst parts: string[] = [];\n\t\tif (answered[0].selected.length > 0) parts.push(answered[0].selected.join(\", \"));\n\t\tif (answered[0].customText) parts.push(`\"${answered[0].customText}\"`);\n\t\treturn theme.fg(\"toolOutput\", `→ ${parts.join(\" + \")}`);\n\t}\n\treturn theme.fg(\"toolOutput\", `→ answered ${answered.length} of ${details.answers.length}`);\n}\n\n// ============================================================================\n// Tool definition factory\n\n/**\n * Create an `ask_user` tool definition.\n *\n * One call carries one or more questions, collected together in a single\n * wizard and returned as a batch of answers. Every path — answer, skip, abort,\n * timeout, or host failure — settles gracefully and never orphans a promise.\n */\nexport function createAskUserToolDefinition(): ToolDefinition<typeof askUserSchema, AskUserDetails | undefined> {\n\treturn {\n\t\tname: \"ask_user\",\n\t\tlabel: \"ask_user\",\n\t\tdescription:\n\t\t\t\"Pause and ask the user one or more structured clarifying questions in a single call, each with \" +\n\t\t\t\"optional multiple-choice options and a free-text answer. The questions are shown together as one \" +\n\t\t\t\"wizard and answered as a batch. Use only when genuinely blocked by ambiguity with multiple viable \" +\n\t\t\t\"paths — not for routine confirmation.\",\n\n\t\tparameters: askUserSchema,\n\n\t\tpromptSnippet:\n\t\t\t\"Ask the user one or more clarifying questions with optional multiple-choice options and free text\",\n\n\t\tpromptGuidelines: [\n\t\t\t\"Call ask_user ONLY when you are genuinely blocked by ambiguity and there are multiple viable paths forward\",\n\t\t\t\"Do NOT use it for routine confirmation, permission, or things you can reasonably decide yourself\",\n\t\t\t\"Batch everything you need in ONE call: put each distinct decision in the `questions` array — the user answers them all and submits once\",\n\t\t\t\"Provide 2-4 concrete `options` per question when there are clear candidate answers; the user can always type their own\",\n\t\t\t\"Set `multiSelect: true` when several options can be combined; `multiline: true` for open-ended answers\",\n\t\t\t\"The user may stop the current turn instead of answering; never treat that as an answer, and unanswered questions come back as skipped\",\n\t\t],\n\n\t\tasync execute(_toolCallId, input: AskUserInput, signal, _onUpdate, ctx?: ExtensionContext) {\n\t\t\tconst questions = input.questions.map(normalizeQuestion);\n\t\t\tconst request: AskRequest = { questions };\n\n\t\t\t// Optional auto-stop timeout, forwarded to every UI surface (TUI\n\t\t\t// countdown, RPC/Dashboard). Model-facing units are seconds.\n\t\t\tconst timeout = input.timeoutSeconds && input.timeoutSeconds > 0 ? input.timeoutSeconds * 1000 : undefined;\n\n\t\t\t// Headless / print / no-host modes: never block on an unreachable UI.\n\t\t\tif (!ctx?.hasUI) {\n\t\t\t\treturn unavailableResult(questions);\n\t\t\t}\n\n\t\t\t// A call whose signal already aborted settles without ever opening the\n\t\t\t// UI; the parent turn is already stopping.\n\t\t\tif (signal?.aborted) return unansweredResult(questions);\n\n\t\t\ttry {\n\t\t\t\tconst answer = await ctx.ui.ask(request, { signal, timeout });\n\t\t\t\tif (!answer) return unansweredResult(questions);\n\t\t\t\treturn answeredResult(questions, answer);\n\t\t\t} catch {\n\t\t\t\t// Host/protocol failure must still settle and never deadlock, but it\n\t\t\t\t// must not masquerade as an intentional user skip.\n\t\t\t\treturn failedResult(questions);\n\t\t\t}\n\t\t},\n\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0, undefined, true);\n\t\t\ttext.setText(formatCall(args, theme));\n\t\t\treturn text;\n\t\t},\n\n\t\trenderResult(result, _options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0, undefined, true);\n\t\t\tconst details = (result as any).details as AskUserDetails | undefined;\n\t\t\tif (details) {\n\t\t\t\ttext.setText(formatResult(details, theme));\n\t\t\t} else {\n\t\t\t\tconst content = result.content?.[0];\n\t\t\t\ttext.setText(theme.fg(\"toolOutput\", content?.type === \"text\" ? content.text : \"\"));\n\t\t\t}\n\t\t\treturn text;\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"ask-user.d.ts","sourceRoot":"","sources":["../../../src/core/tools/ask-user.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,KAAK,MAAM,EAAQ,MAAM,mBAAmB,CAAC;AAEtD,OAAO,KAAK,EAAwD,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAKnH,mEAAmE;AACnE,MAAM,WAAW,mBAAmB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,OAAO,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC9B,8CAA8C;IAC9C,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,uEAAuE;IACvE,WAAW,EAAE,OAAO,CAAC;IACrB,yDAAyD;IACzD,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAkED,QAAA,MAAM,aAAa;;;;;;;;;;;;;;EAajB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,aAAa,CAAC,CAAC;AAqKxD;;;;;;GAMG;AACH,wBAAgB,2BAA2B,IAAI,cAAc,CAAC,OAAO,aAAa,EAAE,cAAc,GAAG,SAAS,CAAC,CAsE9G","sourcesContent":["/**\n * ask_user tool.\n *\n * Lets the agent pause and ask the user one or more structured clarifying\n * questions in a single call — each with optional multiple-choice options,\n * single- or multi-select, and a \"type your own answer\" free-text field —\n * rendered natively as a wizard in the TUI, the Dashboard, and over RPC. The\n * whole batch is answered together and returned as one result. Answering,\n * stopping the turn, aborting, or timing out always settles cleanly so the\n * agent never deadlocks on an absent user. Unanswered questions come back\n * flagged as skipped rather than blocking the turn.\n */\n\nimport { Text } from \"@dreb/tui\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport { Value } from \"@sinclair/typebox/value\";\nimport type { AskQuestion, AskRequest, AskResult, ExtensionContext, ToolDefinition } from \"../extensions/types.js\";\n\n// ============================================================================\n// Types\n\n/** One question's outcome, surfaced in the tool result details. */\nexport interface AskUserAnswerDetail {\n\tquestion: string;\n\ttitle?: string;\n\tselected: string[];\n\tcustomText?: string;\n\t/** True when this question closed without an answer. */\n\tskipped: boolean;\n}\n\nexport interface AskUserDetails {\n\t/** One entry per question asked, in order. */\n\tanswers: AskUserAnswerDetail[];\n\t/** True when no interactive UI was available (headless/print mode). */\n\tunavailable: boolean;\n\t/** True when the UI host or response protocol failed. */\n\tfailed?: boolean;\n}\n\n// ============================================================================\n// Schema\n\n/** Nonblank text: at least one non-whitespace character (llama.cpp-parseable pattern). */\nconst NONBLANK_PATTERN = \"^.*[^ \\\\t\\\\r\\\\n].*$\";\n\n/**\n * One option: plain text, or a labeled object (the shape many models are\n * trained on). Objects are folded back to plain text before reaching any UI.\n */\nconst optionSchema = Type.Union([\n\tType.String({ minLength: 1, pattern: NONBLANK_PATTERN }),\n\tType.Object({\n\t\tlabel: Type.String({ minLength: 1, pattern: NONBLANK_PATTERN }),\n\t\tdescription: Type.Optional(Type.String()),\n\t}),\n]);\n\nconst questionSchema = Type.Object({\n\tquestion: Type.String({\n\t\tdescription: \"The Markdown-formatted question to ask the user. Be specific about what you need to decide.\",\n\t}),\n\ttitle: Type.Optional(\n\t\tType.String({\n\t\t\tdescription: \"Short bold title shown above this question.\",\n\t\t}),\n\t),\n\theader: Type.Optional(\n\t\tType.String({\n\t\t\tdescription: \"Alias for title; title wins when both are given.\",\n\t\t}),\n\t),\n\toptions: Type.Optional(\n\t\tType.Array(optionSchema, {\n\t\t\tminItems: 2,\n\t\t\tmaxItems: 4,\n\t\t\tdescription: \"2-4 nonblank suggested answers the user can pick from.\",\n\t\t}),\n\t),\n\tallowFreeText: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription: \"Offer a 'type your own answer' field. Defaults to true.\",\n\t\t}),\n\t),\n\tmultiSelect: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription: \"Allow selecting multiple options (checkboxes). Only meaningful with options.\",\n\t\t}),\n\t),\n\tmultiline: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription: \"Use a large multi-line text area for open-ended answers.\",\n\t\t}),\n\t),\n});\n\nconst questionsArraySchema = Type.Array(questionSchema, {\n\tminItems: 1,\n\tmaxItems: 10,\n\tdescription:\n\t\t\"One or more clarifying questions to ask together in a single wizard. \" +\n\t\t\"Batch every question you need answered in one call — the user answers them all and submits once.\",\n});\n\nconst askUserSchema = Type.Object({\n\t// A JSON-encoded string of the array is also accepted; it is parsed and\n\t// checked against the same array schema in execute.\n\tquestions: Type.Union([questionsArraySchema, Type.String()]),\n\ttimeoutSeconds: Type.Optional(\n\t\tType.Number({\n\t\t\tminimum: 5,\n\t\t\tmaximum: 3600,\n\t\t\tdescription:\n\t\t\t\t\"Optional: stop the current agent turn after this many seconds if the user does not respond. \" +\n\t\t\t\t\"Shows a live countdown. Omit to wait indefinitely.\",\n\t\t}),\n\t),\n});\n\nexport type AskUserInput = Static<typeof askUserSchema>;\ntype AskUserQuestion = Static<typeof questionSchema>;\ntype AskUserOption = Static<typeof optionSchema>;\n\n// ============================================================================\n// Normalization\n\n/** Fold an option to plain text: \"label\" or \"label — description\". */\nfunction optionText(option: AskUserOption): string {\n\tif (typeof option === \"string\") return option;\n\tconst description = option.description?.trim();\n\treturn description ? `${option.label} — ${description}` : option.label;\n}\n\n/**\n * Resolve `questions` to an array. A JSON-encoded string is parsed and checked\n * against the same schema as a real array; anything invalid throws.\n */\nfunction resolveQuestions(questions: AskUserInput[\"questions\"]): AskUserQuestion[] {\n\tif (typeof questions !== \"string\") return questions;\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(questions);\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`ask_user: \\`questions\\` was a string but not valid JSON (${error instanceof Error ? error.message : String(error)}). Pass \\`questions\\` as an array of question objects.`,\n\t\t);\n\t}\n\tif (!Value.Check(questionsArraySchema, parsed)) {\n\t\tconst errors = [...Value.Errors(questionsArraySchema, parsed)]\n\t\t\t.slice(0, 10)\n\t\t\t.map((e) => ` - ${e.path || \"questions\"}: ${e.message}`)\n\t\t\t.join(\"\\n\");\n\t\tthrow new Error(\n\t\t\t`ask_user: \\`questions\\` was a JSON string that is not a valid questions array:\\n${errors}\\nPass \\`questions\\` as an array of question objects.`,\n\t\t);\n\t}\n\treturn parsed;\n}\n\n/**\n * Normalize a single question so every rendered surface has at least one usable\n * answer control and no impossible flag combinations survive.\n */\nfunction normalizeQuestion(q: AskUserQuestion): AskQuestion {\n\tconst options = q.options?.map(optionText);\n\tconst hasOptions = (options?.length ?? 0) > 0;\n\treturn {\n\t\tquestion: q.question,\n\t\ttitle: q.title ?? q.header,\n\t\toptions,\n\t\t// Guarantee at least one answer control: with no options, free text must\n\t\t// be offered regardless of the requested flag, otherwise a question would\n\t\t// render only a Skip control and no way to answer.\n\t\tallowFreeText: hasOptions ? q.allowFreeText : true,\n\t\t// multiSelect is only meaningful with options; multiline only with free\n\t\t// text — normalize away impossible combinations.\n\t\tmultiSelect: hasOptions ? q.multiSelect : undefined,\n\t\tmultiline: hasOptions ? (q.allowFreeText === false ? undefined : q.multiline) : q.multiline,\n\t};\n}\n\n// ============================================================================\n// Result helpers\n\nfunction textResult(text: string, details: AskUserDetails) {\n\treturn {\n\t\tcontent: [{ type: \"text\" as const, text }],\n\t\tdetails,\n\t};\n}\n\nfunction skippedAnswer(q: AskQuestion): AskUserAnswerDetail {\n\treturn { question: q.question, title: q.title, selected: [], skipped: true };\n}\n\nfunction unavailableResult(questions: AskQuestion[]) {\n\treturn textResult(\n\t\t\"The ask_user tool requires an interactive UI, which is not available in this mode. \" +\n\t\t\t\"Proceed using your best judgment without waiting for an answer.\",\n\t\t{ answers: questions.map(skippedAnswer), unavailable: true },\n\t);\n}\n\nfunction unansweredResult(questions: AskQuestion[]) {\n\treturn textResult(\"The questions closed without an answer.\", {\n\t\tanswers: questions.map(skippedAnswer),\n\t\tunavailable: false,\n\t});\n}\n\nfunction failedResult(questions: AskQuestion[]) {\n\treturn textResult(\n\t\t\"The questions could not be delivered because the interactive UI or response protocol failed. \" +\n\t\t\t\"Continue without this input.\",\n\t\t{ answers: questions.map(skippedAnswer), unavailable: false, failed: true },\n\t);\n}\n\n/** Build a per-question detail entry from the user's answer. */\nfunction toAnswerDetail(q: AskQuestion, answer: AskResult[\"answers\"][number] | undefined): AskUserAnswerDetail {\n\tif (!answer || answer.skipped) return skippedAnswer(q);\n\tconst customText = answer.customText?.trim() || undefined;\n\tconst selected = answer.selected ?? [];\n\tif (selected.length === 0 && !customText) return skippedAnswer(q);\n\treturn { question: q.question, title: q.title, selected, customText, skipped: false };\n}\n\n/** Compose the model-facing summary, one block per question. */\nfunction summarizeAnswers(details: AskUserAnswerDetail[]): string {\n\tconst blocks = details.map((d) => {\n\t\tconst heading = (d.title || d.question || \"\").replace(/\\s+/g, \" \").trim();\n\t\tif (d.skipped) return `**${heading}** → (no answer)`;\n\t\tconst parts: string[] = [];\n\t\tif (d.selected.length > 0) parts.push(`Selected: ${d.selected.join(\", \")}`);\n\t\tif (d.customText) parts.push(`wrote: \"${d.customText}\"`);\n\t\treturn `**${heading}** → ${parts.join(\" / \")}`;\n\t});\n\treturn blocks.join(\"\\n\");\n}\n\nfunction answeredResult(questions: AskQuestion[], result: AskResult) {\n\tconst details = questions.map((q, i) => toAnswerDetail(q, result.answers[i]));\n\treturn textResult(summarizeAnswers(details), { answers: details, unavailable: false });\n}\n\n// ============================================================================\n// Render helpers\n\ntype CallArgs = { questions?: Array<{ title?: string; header?: string; question?: string }> | string } | undefined;\n\nfunction callLabel(args: CallArgs): string {\n\t// While streaming (or for a JSON-string batch) questions may not be an array yet.\n\tconst questions = Array.isArray(args?.questions) ? args.questions : [];\n\tif (typeof args?.questions === \"string\") return \"questions\";\n\tconst count = questions.length;\n\tif (count === 1) {\n\t\tconst first = questions[0];\n\t\tconst label = (first?.title || first?.header || first?.question || \"\").replace(/\\s+/g, \" \").trim();\n\t\treturn label.length > 80 ? `${label.slice(0, 79)}…` : label;\n\t}\n\treturn `${count} questions`;\n}\n\nfunction formatCall(args: CallArgs, theme: any): string {\n\treturn `${theme.fg(\"toolTitle\", theme.bold(\"ask_user\"))} ${theme.fg(\"accent\", callLabel(args))}`;\n}\n\nfunction formatResult(details: AskUserDetails, theme: any): string {\n\tif (details.unavailable) return theme.fg(\"toolOutput\", \"no interactive UI — continued without asking\");\n\tif (details.failed) return theme.fg(\"toolOutput\", \"interactive UI failed — continued without an answer\");\n\tconst answered = details.answers.filter((a) => !a.skipped);\n\tif (answered.length === 0) return theme.fg(\"toolOutput\", \"closed without an answer\");\n\tif (details.answers.length === 1) {\n\t\tconst parts: string[] = [];\n\t\tif (answered[0].selected.length > 0) parts.push(answered[0].selected.join(\", \"));\n\t\tif (answered[0].customText) parts.push(`\"${answered[0].customText}\"`);\n\t\treturn theme.fg(\"toolOutput\", `→ ${parts.join(\" + \")}`);\n\t}\n\treturn theme.fg(\"toolOutput\", `→ answered ${answered.length} of ${details.answers.length}`);\n}\n\n// ============================================================================\n// Tool definition factory\n\n/**\n * Create an `ask_user` tool definition.\n *\n * One call carries one or more questions, collected together in a single\n * wizard and returned as a batch of answers. Every path — answer, skip, abort,\n * timeout, or host failure — settles gracefully and never orphans a promise.\n */\nexport function createAskUserToolDefinition(): ToolDefinition<typeof askUserSchema, AskUserDetails | undefined> {\n\treturn {\n\t\tname: \"ask_user\",\n\t\tlabel: \"ask_user\",\n\t\tdescription:\n\t\t\t\"Pause and ask the user one or more structured clarifying questions in a single call, each with \" +\n\t\t\t\"optional multiple-choice options and a free-text answer. The questions are shown together as one \" +\n\t\t\t\"wizard and answered as a batch. Use only when genuinely blocked by ambiguity with multiple viable \" +\n\t\t\t\"paths — not for routine confirmation.\",\n\n\t\tparameters: askUserSchema,\n\n\t\tpromptSnippet:\n\t\t\t\"Ask the user one or more clarifying questions with optional multiple-choice options and free text\",\n\n\t\tpromptGuidelines: [\n\t\t\t\"Call ask_user ONLY when you are genuinely blocked by ambiguity and there are multiple viable paths forward\",\n\t\t\t\"Do NOT use it for routine confirmation, permission, or things you can reasonably decide yourself\",\n\t\t\t\"Batch everything you need in ONE call: put each distinct decision in the `questions` array — the user answers them all and submits once\",\n\t\t\t\"Provide 2-4 concrete `options` per question (plain strings) when there are clear candidate answers; the user can always type their own\",\n\t\t\t\"Set `multiSelect: true` when several options can be combined; `multiline: true` for open-ended answers\",\n\t\t\t\"The user may stop the current turn instead of answering; never treat that as an answer, and unanswered questions come back as skipped\",\n\t\t],\n\n\t\tasync execute(_toolCallId, input: AskUserInput, signal, _onUpdate, ctx?: ExtensionContext) {\n\t\t\tconst questions = resolveQuestions(input.questions).map(normalizeQuestion);\n\t\t\tconst request: AskRequest = { questions };\n\n\t\t\t// Optional auto-stop timeout, forwarded to every UI surface (TUI\n\t\t\t// countdown, RPC/Dashboard). Model-facing units are seconds.\n\t\t\tconst timeout = input.timeoutSeconds && input.timeoutSeconds > 0 ? input.timeoutSeconds * 1000 : undefined;\n\n\t\t\t// Headless / print / no-host modes: never block on an unreachable UI.\n\t\t\tif (!ctx?.hasUI) {\n\t\t\t\treturn unavailableResult(questions);\n\t\t\t}\n\n\t\t\t// A call whose signal already aborted settles without ever opening the\n\t\t\t// UI; the parent turn is already stopping.\n\t\t\tif (signal?.aborted) return unansweredResult(questions);\n\n\t\t\ttry {\n\t\t\t\tconst answer = await ctx.ui.ask(request, { signal, timeout });\n\t\t\t\tif (!answer) return unansweredResult(questions);\n\t\t\t\treturn answeredResult(questions, answer);\n\t\t\t} catch {\n\t\t\t\t// Host/protocol failure must still settle and never deadlock, but it\n\t\t\t\t// must not masquerade as an intentional user skip.\n\t\t\t\treturn failedResult(questions);\n\t\t\t}\n\t\t},\n\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0, undefined, true);\n\t\t\ttext.setText(formatCall(args, theme));\n\t\t\treturn text;\n\t\t},\n\n\t\trenderResult(result, _options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0, undefined, true);\n\t\t\tconst details = (result as any).details as AskUserDetails | undefined;\n\t\t\tif (details) {\n\t\t\t\ttext.setText(formatResult(details, theme));\n\t\t\t} else {\n\t\t\t\tconst content = result.content?.[0];\n\t\t\t\ttext.setText(theme.fg(\"toolOutput\", content?.type === \"text\" ? content.text : \"\"));\n\t\t\t}\n\t\t\treturn text;\n\t\t},\n\t};\n}\n"]}
@@ -12,16 +12,33 @@
12
12
  */
13
13
  import { Text } from "@dreb/tui";
14
14
  import { Type } from "@sinclair/typebox";
15
+ import { Value } from "@sinclair/typebox/value";
15
16
  // ============================================================================
16
17
  // Schema
18
+ /** Nonblank text: at least one non-whitespace character (llama.cpp-parseable pattern). */
19
+ const NONBLANK_PATTERN = "^.*[^ \\t\\r\\n].*$";
20
+ /**
21
+ * One option: plain text, or a labeled object (the shape many models are
22
+ * trained on). Objects are folded back to plain text before reaching any UI.
23
+ */
24
+ const optionSchema = Type.Union([
25
+ Type.String({ minLength: 1, pattern: NONBLANK_PATTERN }),
26
+ Type.Object({
27
+ label: Type.String({ minLength: 1, pattern: NONBLANK_PATTERN }),
28
+ description: Type.Optional(Type.String()),
29
+ }),
30
+ ]);
17
31
  const questionSchema = Type.Object({
18
32
  question: Type.String({
19
33
  description: "The Markdown-formatted question to ask the user. Be specific about what you need to decide.",
20
34
  }),
21
35
  title: Type.Optional(Type.String({
22
- description: "Short bold header shown above this question.",
36
+ description: "Short bold title shown above this question.",
37
+ })),
38
+ header: Type.Optional(Type.String({
39
+ description: "Alias for title; title wins when both are given.",
23
40
  })),
24
- options: Type.Optional(Type.Array(Type.String({ minLength: 1, pattern: "^.*[^ \\t\\r\\n].*$" }), {
41
+ options: Type.Optional(Type.Array(optionSchema, {
25
42
  minItems: 2,
26
43
  maxItems: 4,
27
44
  description: "2-4 nonblank suggested answers the user can pick from.",
@@ -36,13 +53,16 @@ const questionSchema = Type.Object({
36
53
  description: "Use a large multi-line text area for open-ended answers.",
37
54
  })),
38
55
  });
56
+ const questionsArraySchema = Type.Array(questionSchema, {
57
+ minItems: 1,
58
+ maxItems: 10,
59
+ description: "One or more clarifying questions to ask together in a single wizard. " +
60
+ "Batch every question you need answered in one call — the user answers them all and submits once.",
61
+ });
39
62
  const askUserSchema = Type.Object({
40
- questions: Type.Array(questionSchema, {
41
- minItems: 1,
42
- maxItems: 10,
43
- description: "One or more clarifying questions to ask together in a single wizard. " +
44
- "Batch every question you need answered in one call — the user answers them all and submits once.",
45
- }),
63
+ // A JSON-encoded string of the array is also accepted; it is parsed and
64
+ // checked against the same array schema in execute.
65
+ questions: Type.Union([questionsArraySchema, Type.String()]),
46
66
  timeoutSeconds: Type.Optional(Type.Number({
47
67
  minimum: 5,
48
68
  maximum: 3600,
@@ -52,16 +72,47 @@ const askUserSchema = Type.Object({
52
72
  });
53
73
  // ============================================================================
54
74
  // Normalization
75
+ /** Fold an option to plain text: "label" or "label — description". */
76
+ function optionText(option) {
77
+ if (typeof option === "string")
78
+ return option;
79
+ const description = option.description?.trim();
80
+ return description ? `${option.label} — ${description}` : option.label;
81
+ }
82
+ /**
83
+ * Resolve `questions` to an array. A JSON-encoded string is parsed and checked
84
+ * against the same schema as a real array; anything invalid throws.
85
+ */
86
+ function resolveQuestions(questions) {
87
+ if (typeof questions !== "string")
88
+ return questions;
89
+ let parsed;
90
+ try {
91
+ parsed = JSON.parse(questions);
92
+ }
93
+ catch (error) {
94
+ throw new Error(`ask_user: \`questions\` was a string but not valid JSON (${error instanceof Error ? error.message : String(error)}). Pass \`questions\` as an array of question objects.`);
95
+ }
96
+ if (!Value.Check(questionsArraySchema, parsed)) {
97
+ const errors = [...Value.Errors(questionsArraySchema, parsed)]
98
+ .slice(0, 10)
99
+ .map((e) => ` - ${e.path || "questions"}: ${e.message}`)
100
+ .join("\n");
101
+ throw new Error(`ask_user: \`questions\` was a JSON string that is not a valid questions array:\n${errors}\nPass \`questions\` as an array of question objects.`);
102
+ }
103
+ return parsed;
104
+ }
55
105
  /**
56
106
  * Normalize a single question so every rendered surface has at least one usable
57
107
  * answer control and no impossible flag combinations survive.
58
108
  */
59
109
  function normalizeQuestion(q) {
60
- const hasOptions = (q.options?.length ?? 0) > 0;
110
+ const options = q.options?.map(optionText);
111
+ const hasOptions = (options?.length ?? 0) > 0;
61
112
  return {
62
113
  question: q.question,
63
- title: q.title,
64
- options: q.options,
114
+ title: q.title ?? q.header,
115
+ options,
65
116
  // Guarantee at least one answer control: with no options, free text must
66
117
  // be offered regardless of the requested flag, otherwise a question would
67
118
  // render only a Skip control and no way to answer.
@@ -126,14 +177,15 @@ function answeredResult(questions, result) {
126
177
  const details = questions.map((q, i) => toAnswerDetail(q, result.answers[i]));
127
178
  return textResult(summarizeAnswers(details), { answers: details, unavailable: false });
128
179
  }
129
- // ============================================================================
130
- // Render helpers
131
180
  function callLabel(args) {
132
- const questions = args?.questions ?? [];
181
+ // While streaming (or for a JSON-string batch) questions may not be an array yet.
182
+ const questions = Array.isArray(args?.questions) ? args.questions : [];
183
+ if (typeof args?.questions === "string")
184
+ return "questions";
133
185
  const count = questions.length;
134
186
  if (count === 1) {
135
187
  const first = questions[0];
136
- const label = (first?.title || first?.question || "").replace(/\s+/g, " ").trim();
188
+ const label = (first?.title || first?.header || first?.question || "").replace(/\s+/g, " ").trim();
137
189
  return label.length > 80 ? `${label.slice(0, 79)}…` : label;
138
190
  }
139
191
  return `${count} questions`;
@@ -182,12 +234,12 @@ export function createAskUserToolDefinition() {
182
234
  "Call ask_user ONLY when you are genuinely blocked by ambiguity and there are multiple viable paths forward",
183
235
  "Do NOT use it for routine confirmation, permission, or things you can reasonably decide yourself",
184
236
  "Batch everything you need in ONE call: put each distinct decision in the `questions` array — the user answers them all and submits once",
185
- "Provide 2-4 concrete `options` per question when there are clear candidate answers; the user can always type their own",
237
+ "Provide 2-4 concrete `options` per question (plain strings) when there are clear candidate answers; the user can always type their own",
186
238
  "Set `multiSelect: true` when several options can be combined; `multiline: true` for open-ended answers",
187
239
  "The user may stop the current turn instead of answering; never treat that as an answer, and unanswered questions come back as skipped",
188
240
  ],
189
241
  async execute(_toolCallId, input, signal, _onUpdate, ctx) {
190
- const questions = input.questions.map(normalizeQuestion);
242
+ const questions = resolveQuestions(input.questions).map(normalizeQuestion);
191
243
  const request = { questions };
192
244
  // Optional auto-stop timeout, forwarded to every UI surface (TUI
193
245
  // countdown, RPC/Dashboard). Model-facing units are seconds.
@@ -1 +1 @@
1
- {"version":3,"file":"ask-user.js","sourceRoot":"","sources":["../../../src/core/tools/ask-user.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ask_user tool.\n *\n * Lets the agent pause and ask the user one or more structured clarifying\n * questions in a single call — each with optional multiple-choice options,\n * single- or multi-select, and a \"type your own answer\" free-text field —\n * rendered natively as a wizard in the TUI, the Dashboard, and over RPC. The\n * whole batch is answered together and returned as one result. Answering,\n * stopping the turn, aborting, or timing out always settles cleanly so the\n * agent never deadlocks on an absent user. Unanswered questions come back\n * flagged as skipped rather than blocking the turn.\n */\n\nimport { Text } from \"@dreb/tui\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport type { AskQuestion, AskRequest, AskResult, ExtensionContext, ToolDefinition } from \"../extensions/types.js\";\n\n// ============================================================================\n// Types\n\n/** One question's outcome, surfaced in the tool result details. */\nexport interface AskUserAnswerDetail {\n\tquestion: string;\n\ttitle?: string;\n\tselected: string[];\n\tcustomText?: string;\n\t/** True when this question closed without an answer. */\n\tskipped: boolean;\n}\n\nexport interface AskUserDetails {\n\t/** One entry per question asked, in order. */\n\tanswers: AskUserAnswerDetail[];\n\t/** True when no interactive UI was available (headless/print mode). */\n\tunavailable: boolean;\n\t/** True when the UI host or response protocol failed. */\n\tfailed?: boolean;\n}\n\n// ============================================================================\n// Schema\n\nconst questionSchema = Type.Object({\n\tquestion: Type.String({\n\t\tdescription: \"The Markdown-formatted question to ask the user. Be specific about what you need to decide.\",\n\t}),\n\ttitle: Type.Optional(\n\t\tType.String({\n\t\t\tdescription: \"Short bold header shown above this question.\",\n\t\t}),\n\t),\n\toptions: Type.Optional(\n\t\tType.Array(Type.String({ minLength: 1, pattern: \"^.*[^ \\\\t\\\\r\\\\n].*$\" }), {\n\t\t\tminItems: 2,\n\t\t\tmaxItems: 4,\n\t\t\tdescription: \"2-4 nonblank suggested answers the user can pick from.\",\n\t\t}),\n\t),\n\tallowFreeText: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription: \"Offer a 'type your own answer' field. Defaults to true.\",\n\t\t}),\n\t),\n\tmultiSelect: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription: \"Allow selecting multiple options (checkboxes). Only meaningful with options.\",\n\t\t}),\n\t),\n\tmultiline: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription: \"Use a large multi-line text area for open-ended answers.\",\n\t\t}),\n\t),\n});\n\nconst askUserSchema = Type.Object({\n\tquestions: Type.Array(questionSchema, {\n\t\tminItems: 1,\n\t\tmaxItems: 10,\n\t\tdescription:\n\t\t\t\"One or more clarifying questions to ask together in a single wizard. \" +\n\t\t\t\"Batch every question you need answered in one call — the user answers them all and submits once.\",\n\t}),\n\ttimeoutSeconds: Type.Optional(\n\t\tType.Number({\n\t\t\tminimum: 5,\n\t\t\tmaximum: 3600,\n\t\t\tdescription:\n\t\t\t\t\"Optional: stop the current agent turn after this many seconds if the user does not respond. \" +\n\t\t\t\t\"Shows a live countdown. Omit to wait indefinitely.\",\n\t\t}),\n\t),\n});\n\nexport type AskUserInput = Static<typeof askUserSchema>;\ntype AskUserQuestion = Static<typeof questionSchema>;\n\n// ============================================================================\n// Normalization\n\n/**\n * Normalize a single question so every rendered surface has at least one usable\n * answer control and no impossible flag combinations survive.\n */\nfunction normalizeQuestion(q: AskUserQuestion): AskQuestion {\n\tconst hasOptions = (q.options?.length ?? 0) > 0;\n\treturn {\n\t\tquestion: q.question,\n\t\ttitle: q.title,\n\t\toptions: q.options,\n\t\t// Guarantee at least one answer control: with no options, free text must\n\t\t// be offered regardless of the requested flag, otherwise a question would\n\t\t// render only a Skip control and no way to answer.\n\t\tallowFreeText: hasOptions ? q.allowFreeText : true,\n\t\t// multiSelect is only meaningful with options; multiline only with free\n\t\t// text — normalize away impossible combinations.\n\t\tmultiSelect: hasOptions ? q.multiSelect : undefined,\n\t\tmultiline: hasOptions ? (q.allowFreeText === false ? undefined : q.multiline) : q.multiline,\n\t};\n}\n\n// ============================================================================\n// Result helpers\n\nfunction textResult(text: string, details: AskUserDetails) {\n\treturn {\n\t\tcontent: [{ type: \"text\" as const, text }],\n\t\tdetails,\n\t};\n}\n\nfunction skippedAnswer(q: AskQuestion): AskUserAnswerDetail {\n\treturn { question: q.question, title: q.title, selected: [], skipped: true };\n}\n\nfunction unavailableResult(questions: AskQuestion[]) {\n\treturn textResult(\n\t\t\"The ask_user tool requires an interactive UI, which is not available in this mode. \" +\n\t\t\t\"Proceed using your best judgment without waiting for an answer.\",\n\t\t{ answers: questions.map(skippedAnswer), unavailable: true },\n\t);\n}\n\nfunction unansweredResult(questions: AskQuestion[]) {\n\treturn textResult(\"The questions closed without an answer.\", {\n\t\tanswers: questions.map(skippedAnswer),\n\t\tunavailable: false,\n\t});\n}\n\nfunction failedResult(questions: AskQuestion[]) {\n\treturn textResult(\n\t\t\"The questions could not be delivered because the interactive UI or response protocol failed. \" +\n\t\t\t\"Continue without this input.\",\n\t\t{ answers: questions.map(skippedAnswer), unavailable: false, failed: true },\n\t);\n}\n\n/** Build a per-question detail entry from the user's answer. */\nfunction toAnswerDetail(q: AskQuestion, answer: AskResult[\"answers\"][number] | undefined): AskUserAnswerDetail {\n\tif (!answer || answer.skipped) return skippedAnswer(q);\n\tconst customText = answer.customText?.trim() || undefined;\n\tconst selected = answer.selected ?? [];\n\tif (selected.length === 0 && !customText) return skippedAnswer(q);\n\treturn { question: q.question, title: q.title, selected, customText, skipped: false };\n}\n\n/** Compose the model-facing summary, one block per question. */\nfunction summarizeAnswers(details: AskUserAnswerDetail[]): string {\n\tconst blocks = details.map((d) => {\n\t\tconst heading = (d.title || d.question || \"\").replace(/\\s+/g, \" \").trim();\n\t\tif (d.skipped) return `**${heading}** → (no answer)`;\n\t\tconst parts: string[] = [];\n\t\tif (d.selected.length > 0) parts.push(`Selected: ${d.selected.join(\", \")}`);\n\t\tif (d.customText) parts.push(`wrote: \"${d.customText}\"`);\n\t\treturn `**${heading}** → ${parts.join(\" / \")}`;\n\t});\n\treturn blocks.join(\"\\n\");\n}\n\nfunction answeredResult(questions: AskQuestion[], result: AskResult) {\n\tconst details = questions.map((q, i) => toAnswerDetail(q, result.answers[i]));\n\treturn textResult(summarizeAnswers(details), { answers: details, unavailable: false });\n}\n\n// ============================================================================\n// Render helpers\n\nfunction callLabel(args: { questions?: Array<{ title?: string; question?: string }> } | undefined): string {\n\tconst questions = args?.questions ?? [];\n\tconst count = questions.length;\n\tif (count === 1) {\n\t\tconst first = questions[0];\n\t\tconst label = (first?.title || first?.question || \"\").replace(/\\s+/g, \" \").trim();\n\t\treturn label.length > 80 ? `${label.slice(0, 79)}…` : label;\n\t}\n\treturn `${count} questions`;\n}\n\nfunction formatCall(\n\targs: { questions?: Array<{ title?: string; question?: string }> } | undefined,\n\ttheme: any,\n): string {\n\treturn `${theme.fg(\"toolTitle\", theme.bold(\"ask_user\"))} ${theme.fg(\"accent\", callLabel(args))}`;\n}\n\nfunction formatResult(details: AskUserDetails, theme: any): string {\n\tif (details.unavailable) return theme.fg(\"toolOutput\", \"no interactive UI — continued without asking\");\n\tif (details.failed) return theme.fg(\"toolOutput\", \"interactive UI failed — continued without an answer\");\n\tconst answered = details.answers.filter((a) => !a.skipped);\n\tif (answered.length === 0) return theme.fg(\"toolOutput\", \"closed without an answer\");\n\tif (details.answers.length === 1) {\n\t\tconst parts: string[] = [];\n\t\tif (answered[0].selected.length > 0) parts.push(answered[0].selected.join(\", \"));\n\t\tif (answered[0].customText) parts.push(`\"${answered[0].customText}\"`);\n\t\treturn theme.fg(\"toolOutput\", `→ ${parts.join(\" + \")}`);\n\t}\n\treturn theme.fg(\"toolOutput\", `→ answered ${answered.length} of ${details.answers.length}`);\n}\n\n// ============================================================================\n// Tool definition factory\n\n/**\n * Create an `ask_user` tool definition.\n *\n * One call carries one or more questions, collected together in a single\n * wizard and returned as a batch of answers. Every path — answer, skip, abort,\n * timeout, or host failure — settles gracefully and never orphans a promise.\n */\nexport function createAskUserToolDefinition(): ToolDefinition<typeof askUserSchema, AskUserDetails | undefined> {\n\treturn {\n\t\tname: \"ask_user\",\n\t\tlabel: \"ask_user\",\n\t\tdescription:\n\t\t\t\"Pause and ask the user one or more structured clarifying questions in a single call, each with \" +\n\t\t\t\"optional multiple-choice options and a free-text answer. The questions are shown together as one \" +\n\t\t\t\"wizard and answered as a batch. Use only when genuinely blocked by ambiguity with multiple viable \" +\n\t\t\t\"paths — not for routine confirmation.\",\n\n\t\tparameters: askUserSchema,\n\n\t\tpromptSnippet:\n\t\t\t\"Ask the user one or more clarifying questions with optional multiple-choice options and free text\",\n\n\t\tpromptGuidelines: [\n\t\t\t\"Call ask_user ONLY when you are genuinely blocked by ambiguity and there are multiple viable paths forward\",\n\t\t\t\"Do NOT use it for routine confirmation, permission, or things you can reasonably decide yourself\",\n\t\t\t\"Batch everything you need in ONE call: put each distinct decision in the `questions` array — the user answers them all and submits once\",\n\t\t\t\"Provide 2-4 concrete `options` per question when there are clear candidate answers; the user can always type their own\",\n\t\t\t\"Set `multiSelect: true` when several options can be combined; `multiline: true` for open-ended answers\",\n\t\t\t\"The user may stop the current turn instead of answering; never treat that as an answer, and unanswered questions come back as skipped\",\n\t\t],\n\n\t\tasync execute(_toolCallId, input: AskUserInput, signal, _onUpdate, ctx?: ExtensionContext) {\n\t\t\tconst questions = input.questions.map(normalizeQuestion);\n\t\t\tconst request: AskRequest = { questions };\n\n\t\t\t// Optional auto-stop timeout, forwarded to every UI surface (TUI\n\t\t\t// countdown, RPC/Dashboard). Model-facing units are seconds.\n\t\t\tconst timeout = input.timeoutSeconds && input.timeoutSeconds > 0 ? input.timeoutSeconds * 1000 : undefined;\n\n\t\t\t// Headless / print / no-host modes: never block on an unreachable UI.\n\t\t\tif (!ctx?.hasUI) {\n\t\t\t\treturn unavailableResult(questions);\n\t\t\t}\n\n\t\t\t// A call whose signal already aborted settles without ever opening the\n\t\t\t// UI; the parent turn is already stopping.\n\t\t\tif (signal?.aborted) return unansweredResult(questions);\n\n\t\t\ttry {\n\t\t\t\tconst answer = await ctx.ui.ask(request, { signal, timeout });\n\t\t\t\tif (!answer) return unansweredResult(questions);\n\t\t\t\treturn answeredResult(questions, answer);\n\t\t\t} catch {\n\t\t\t\t// Host/protocol failure must still settle and never deadlock, but it\n\t\t\t\t// must not masquerade as an intentional user skip.\n\t\t\t\treturn failedResult(questions);\n\t\t\t}\n\t\t},\n\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0, undefined, true);\n\t\t\ttext.setText(formatCall(args, theme));\n\t\t\treturn text;\n\t\t},\n\n\t\trenderResult(result, _options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0, undefined, true);\n\t\t\tconst details = (result as any).details as AskUserDetails | undefined;\n\t\t\tif (details) {\n\t\t\t\ttext.setText(formatResult(details, theme));\n\t\t\t} else {\n\t\t\t\tconst content = result.content?.[0];\n\t\t\t\ttext.setText(theme.fg(\"toolOutput\", content?.type === \"text\" ? content.text : \"\"));\n\t\t\t}\n\t\t\treturn text;\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"ask-user.js","sourceRoot":"","sources":["../../../src/core/tools/ask-user.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ask_user tool.\n *\n * Lets the agent pause and ask the user one or more structured clarifying\n * questions in a single call — each with optional multiple-choice options,\n * single- or multi-select, and a \"type your own answer\" free-text field —\n * rendered natively as a wizard in the TUI, the Dashboard, and over RPC. The\n * whole batch is answered together and returned as one result. Answering,\n * stopping the turn, aborting, or timing out always settles cleanly so the\n * agent never deadlocks on an absent user. Unanswered questions come back\n * flagged as skipped rather than blocking the turn.\n */\n\nimport { Text } from \"@dreb/tui\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport { Value } from \"@sinclair/typebox/value\";\nimport type { AskQuestion, AskRequest, AskResult, ExtensionContext, ToolDefinition } from \"../extensions/types.js\";\n\n// ============================================================================\n// Types\n\n/** One question's outcome, surfaced in the tool result details. */\nexport interface AskUserAnswerDetail {\n\tquestion: string;\n\ttitle?: string;\n\tselected: string[];\n\tcustomText?: string;\n\t/** True when this question closed without an answer. */\n\tskipped: boolean;\n}\n\nexport interface AskUserDetails {\n\t/** One entry per question asked, in order. */\n\tanswers: AskUserAnswerDetail[];\n\t/** True when no interactive UI was available (headless/print mode). */\n\tunavailable: boolean;\n\t/** True when the UI host or response protocol failed. */\n\tfailed?: boolean;\n}\n\n// ============================================================================\n// Schema\n\n/** Nonblank text: at least one non-whitespace character (llama.cpp-parseable pattern). */\nconst NONBLANK_PATTERN = \"^.*[^ \\\\t\\\\r\\\\n].*$\";\n\n/**\n * One option: plain text, or a labeled object (the shape many models are\n * trained on). Objects are folded back to plain text before reaching any UI.\n */\nconst optionSchema = Type.Union([\n\tType.String({ minLength: 1, pattern: NONBLANK_PATTERN }),\n\tType.Object({\n\t\tlabel: Type.String({ minLength: 1, pattern: NONBLANK_PATTERN }),\n\t\tdescription: Type.Optional(Type.String()),\n\t}),\n]);\n\nconst questionSchema = Type.Object({\n\tquestion: Type.String({\n\t\tdescription: \"The Markdown-formatted question to ask the user. Be specific about what you need to decide.\",\n\t}),\n\ttitle: Type.Optional(\n\t\tType.String({\n\t\t\tdescription: \"Short bold title shown above this question.\",\n\t\t}),\n\t),\n\theader: Type.Optional(\n\t\tType.String({\n\t\t\tdescription: \"Alias for title; title wins when both are given.\",\n\t\t}),\n\t),\n\toptions: Type.Optional(\n\t\tType.Array(optionSchema, {\n\t\t\tminItems: 2,\n\t\t\tmaxItems: 4,\n\t\t\tdescription: \"2-4 nonblank suggested answers the user can pick from.\",\n\t\t}),\n\t),\n\tallowFreeText: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription: \"Offer a 'type your own answer' field. Defaults to true.\",\n\t\t}),\n\t),\n\tmultiSelect: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription: \"Allow selecting multiple options (checkboxes). Only meaningful with options.\",\n\t\t}),\n\t),\n\tmultiline: Type.Optional(\n\t\tType.Boolean({\n\t\t\tdescription: \"Use a large multi-line text area for open-ended answers.\",\n\t\t}),\n\t),\n});\n\nconst questionsArraySchema = Type.Array(questionSchema, {\n\tminItems: 1,\n\tmaxItems: 10,\n\tdescription:\n\t\t\"One or more clarifying questions to ask together in a single wizard. \" +\n\t\t\"Batch every question you need answered in one call — the user answers them all and submits once.\",\n});\n\nconst askUserSchema = Type.Object({\n\t// A JSON-encoded string of the array is also accepted; it is parsed and\n\t// checked against the same array schema in execute.\n\tquestions: Type.Union([questionsArraySchema, Type.String()]),\n\ttimeoutSeconds: Type.Optional(\n\t\tType.Number({\n\t\t\tminimum: 5,\n\t\t\tmaximum: 3600,\n\t\t\tdescription:\n\t\t\t\t\"Optional: stop the current agent turn after this many seconds if the user does not respond. \" +\n\t\t\t\t\"Shows a live countdown. Omit to wait indefinitely.\",\n\t\t}),\n\t),\n});\n\nexport type AskUserInput = Static<typeof askUserSchema>;\ntype AskUserQuestion = Static<typeof questionSchema>;\ntype AskUserOption = Static<typeof optionSchema>;\n\n// ============================================================================\n// Normalization\n\n/** Fold an option to plain text: \"label\" or \"label — description\". */\nfunction optionText(option: AskUserOption): string {\n\tif (typeof option === \"string\") return option;\n\tconst description = option.description?.trim();\n\treturn description ? `${option.label} — ${description}` : option.label;\n}\n\n/**\n * Resolve `questions` to an array. A JSON-encoded string is parsed and checked\n * against the same schema as a real array; anything invalid throws.\n */\nfunction resolveQuestions(questions: AskUserInput[\"questions\"]): AskUserQuestion[] {\n\tif (typeof questions !== \"string\") return questions;\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(questions);\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`ask_user: \\`questions\\` was a string but not valid JSON (${error instanceof Error ? error.message : String(error)}). Pass \\`questions\\` as an array of question objects.`,\n\t\t);\n\t}\n\tif (!Value.Check(questionsArraySchema, parsed)) {\n\t\tconst errors = [...Value.Errors(questionsArraySchema, parsed)]\n\t\t\t.slice(0, 10)\n\t\t\t.map((e) => ` - ${e.path || \"questions\"}: ${e.message}`)\n\t\t\t.join(\"\\n\");\n\t\tthrow new Error(\n\t\t\t`ask_user: \\`questions\\` was a JSON string that is not a valid questions array:\\n${errors}\\nPass \\`questions\\` as an array of question objects.`,\n\t\t);\n\t}\n\treturn parsed;\n}\n\n/**\n * Normalize a single question so every rendered surface has at least one usable\n * answer control and no impossible flag combinations survive.\n */\nfunction normalizeQuestion(q: AskUserQuestion): AskQuestion {\n\tconst options = q.options?.map(optionText);\n\tconst hasOptions = (options?.length ?? 0) > 0;\n\treturn {\n\t\tquestion: q.question,\n\t\ttitle: q.title ?? q.header,\n\t\toptions,\n\t\t// Guarantee at least one answer control: with no options, free text must\n\t\t// be offered regardless of the requested flag, otherwise a question would\n\t\t// render only a Skip control and no way to answer.\n\t\tallowFreeText: hasOptions ? q.allowFreeText : true,\n\t\t// multiSelect is only meaningful with options; multiline only with free\n\t\t// text — normalize away impossible combinations.\n\t\tmultiSelect: hasOptions ? q.multiSelect : undefined,\n\t\tmultiline: hasOptions ? (q.allowFreeText === false ? undefined : q.multiline) : q.multiline,\n\t};\n}\n\n// ============================================================================\n// Result helpers\n\nfunction textResult(text: string, details: AskUserDetails) {\n\treturn {\n\t\tcontent: [{ type: \"text\" as const, text }],\n\t\tdetails,\n\t};\n}\n\nfunction skippedAnswer(q: AskQuestion): AskUserAnswerDetail {\n\treturn { question: q.question, title: q.title, selected: [], skipped: true };\n}\n\nfunction unavailableResult(questions: AskQuestion[]) {\n\treturn textResult(\n\t\t\"The ask_user tool requires an interactive UI, which is not available in this mode. \" +\n\t\t\t\"Proceed using your best judgment without waiting for an answer.\",\n\t\t{ answers: questions.map(skippedAnswer), unavailable: true },\n\t);\n}\n\nfunction unansweredResult(questions: AskQuestion[]) {\n\treturn textResult(\"The questions closed without an answer.\", {\n\t\tanswers: questions.map(skippedAnswer),\n\t\tunavailable: false,\n\t});\n}\n\nfunction failedResult(questions: AskQuestion[]) {\n\treturn textResult(\n\t\t\"The questions could not be delivered because the interactive UI or response protocol failed. \" +\n\t\t\t\"Continue without this input.\",\n\t\t{ answers: questions.map(skippedAnswer), unavailable: false, failed: true },\n\t);\n}\n\n/** Build a per-question detail entry from the user's answer. */\nfunction toAnswerDetail(q: AskQuestion, answer: AskResult[\"answers\"][number] | undefined): AskUserAnswerDetail {\n\tif (!answer || answer.skipped) return skippedAnswer(q);\n\tconst customText = answer.customText?.trim() || undefined;\n\tconst selected = answer.selected ?? [];\n\tif (selected.length === 0 && !customText) return skippedAnswer(q);\n\treturn { question: q.question, title: q.title, selected, customText, skipped: false };\n}\n\n/** Compose the model-facing summary, one block per question. */\nfunction summarizeAnswers(details: AskUserAnswerDetail[]): string {\n\tconst blocks = details.map((d) => {\n\t\tconst heading = (d.title || d.question || \"\").replace(/\\s+/g, \" \").trim();\n\t\tif (d.skipped) return `**${heading}** → (no answer)`;\n\t\tconst parts: string[] = [];\n\t\tif (d.selected.length > 0) parts.push(`Selected: ${d.selected.join(\", \")}`);\n\t\tif (d.customText) parts.push(`wrote: \"${d.customText}\"`);\n\t\treturn `**${heading}** → ${parts.join(\" / \")}`;\n\t});\n\treturn blocks.join(\"\\n\");\n}\n\nfunction answeredResult(questions: AskQuestion[], result: AskResult) {\n\tconst details = questions.map((q, i) => toAnswerDetail(q, result.answers[i]));\n\treturn textResult(summarizeAnswers(details), { answers: details, unavailable: false });\n}\n\n// ============================================================================\n// Render helpers\n\ntype CallArgs = { questions?: Array<{ title?: string; header?: string; question?: string }> | string } | undefined;\n\nfunction callLabel(args: CallArgs): string {\n\t// While streaming (or for a JSON-string batch) questions may not be an array yet.\n\tconst questions = Array.isArray(args?.questions) ? args.questions : [];\n\tif (typeof args?.questions === \"string\") return \"questions\";\n\tconst count = questions.length;\n\tif (count === 1) {\n\t\tconst first = questions[0];\n\t\tconst label = (first?.title || first?.header || first?.question || \"\").replace(/\\s+/g, \" \").trim();\n\t\treturn label.length > 80 ? `${label.slice(0, 79)}…` : label;\n\t}\n\treturn `${count} questions`;\n}\n\nfunction formatCall(args: CallArgs, theme: any): string {\n\treturn `${theme.fg(\"toolTitle\", theme.bold(\"ask_user\"))} ${theme.fg(\"accent\", callLabel(args))}`;\n}\n\nfunction formatResult(details: AskUserDetails, theme: any): string {\n\tif (details.unavailable) return theme.fg(\"toolOutput\", \"no interactive UI — continued without asking\");\n\tif (details.failed) return theme.fg(\"toolOutput\", \"interactive UI failed — continued without an answer\");\n\tconst answered = details.answers.filter((a) => !a.skipped);\n\tif (answered.length === 0) return theme.fg(\"toolOutput\", \"closed without an answer\");\n\tif (details.answers.length === 1) {\n\t\tconst parts: string[] = [];\n\t\tif (answered[0].selected.length > 0) parts.push(answered[0].selected.join(\", \"));\n\t\tif (answered[0].customText) parts.push(`\"${answered[0].customText}\"`);\n\t\treturn theme.fg(\"toolOutput\", `→ ${parts.join(\" + \")}`);\n\t}\n\treturn theme.fg(\"toolOutput\", `→ answered ${answered.length} of ${details.answers.length}`);\n}\n\n// ============================================================================\n// Tool definition factory\n\n/**\n * Create an `ask_user` tool definition.\n *\n * One call carries one or more questions, collected together in a single\n * wizard and returned as a batch of answers. Every path — answer, skip, abort,\n * timeout, or host failure — settles gracefully and never orphans a promise.\n */\nexport function createAskUserToolDefinition(): ToolDefinition<typeof askUserSchema, AskUserDetails | undefined> {\n\treturn {\n\t\tname: \"ask_user\",\n\t\tlabel: \"ask_user\",\n\t\tdescription:\n\t\t\t\"Pause and ask the user one or more structured clarifying questions in a single call, each with \" +\n\t\t\t\"optional multiple-choice options and a free-text answer. The questions are shown together as one \" +\n\t\t\t\"wizard and answered as a batch. Use only when genuinely blocked by ambiguity with multiple viable \" +\n\t\t\t\"paths — not for routine confirmation.\",\n\n\t\tparameters: askUserSchema,\n\n\t\tpromptSnippet:\n\t\t\t\"Ask the user one or more clarifying questions with optional multiple-choice options and free text\",\n\n\t\tpromptGuidelines: [\n\t\t\t\"Call ask_user ONLY when you are genuinely blocked by ambiguity and there are multiple viable paths forward\",\n\t\t\t\"Do NOT use it for routine confirmation, permission, or things you can reasonably decide yourself\",\n\t\t\t\"Batch everything you need in ONE call: put each distinct decision in the `questions` array — the user answers them all and submits once\",\n\t\t\t\"Provide 2-4 concrete `options` per question (plain strings) when there are clear candidate answers; the user can always type their own\",\n\t\t\t\"Set `multiSelect: true` when several options can be combined; `multiline: true` for open-ended answers\",\n\t\t\t\"The user may stop the current turn instead of answering; never treat that as an answer, and unanswered questions come back as skipped\",\n\t\t],\n\n\t\tasync execute(_toolCallId, input: AskUserInput, signal, _onUpdate, ctx?: ExtensionContext) {\n\t\t\tconst questions = resolveQuestions(input.questions).map(normalizeQuestion);\n\t\t\tconst request: AskRequest = { questions };\n\n\t\t\t// Optional auto-stop timeout, forwarded to every UI surface (TUI\n\t\t\t// countdown, RPC/Dashboard). Model-facing units are seconds.\n\t\t\tconst timeout = input.timeoutSeconds && input.timeoutSeconds > 0 ? input.timeoutSeconds * 1000 : undefined;\n\n\t\t\t// Headless / print / no-host modes: never block on an unreachable UI.\n\t\t\tif (!ctx?.hasUI) {\n\t\t\t\treturn unavailableResult(questions);\n\t\t\t}\n\n\t\t\t// A call whose signal already aborted settles without ever opening the\n\t\t\t// UI; the parent turn is already stopping.\n\t\t\tif (signal?.aborted) return unansweredResult(questions);\n\n\t\t\ttry {\n\t\t\t\tconst answer = await ctx.ui.ask(request, { signal, timeout });\n\t\t\t\tif (!answer) return unansweredResult(questions);\n\t\t\t\treturn answeredResult(questions, answer);\n\t\t\t} catch {\n\t\t\t\t// Host/protocol failure must still settle and never deadlock, but it\n\t\t\t\t// must not masquerade as an intentional user skip.\n\t\t\t\treturn failedResult(questions);\n\t\t\t}\n\t\t},\n\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0, undefined, true);\n\t\t\ttext.setText(formatCall(args, theme));\n\t\t\treturn text;\n\t\t},\n\n\t\trenderResult(result, _options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0, undefined, true);\n\t\t\tconst details = (result as any).details as AskUserDetails | undefined;\n\t\t\tif (details) {\n\t\t\t\ttext.setText(formatResult(details, theme));\n\t\t\t} else {\n\t\t\t\tconst content = result.content?.[0];\n\t\t\t\ttext.setText(theme.fg(\"toolOutput\", content?.type === \"text\" ? content.text : \"\"));\n\t\t\t}\n\t\t\treturn text;\n\t\t},\n\t};\n}\n"]}
@@ -193,14 +193,18 @@ export declare const allToolDefinitions: {
193
193
  rebuild: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
194
194
  }>, import("./search.js").SearchToolDetails, any>;
195
195
  ask_user: ToolDefinition<import("@sinclair/typebox").TObject<{
196
- questions: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
196
+ questions: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
197
197
  question: import("@sinclair/typebox").TString;
198
198
  title: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
199
- options: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
199
+ header: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
200
+ options: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TString, import("@sinclair/typebox").TObject<{
201
+ label: import("@sinclair/typebox").TString;
202
+ description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
203
+ }>]>>>;
200
204
  allowFreeText: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
201
205
  multiSelect: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
202
206
  multiline: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
203
- }>>;
207
+ }>>, import("@sinclair/typebox").TString]>;
204
208
  timeoutSeconds: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
205
209
  }>, import("./ask-user.js").AskUserDetails | undefined, any>;
206
210
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,2BAA2B,GAC3B,MAAM,eAAe,CAAC;AACvB,OAAO,EACN,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,QAAQ,EACR,kBAAkB,EAClB,cAAc,EACd,wBAAwB,EACxB,yBAAyB,GACzB,MAAM,WAAW,CAAC;AACnB,OAAO,EACN,cAAc,EACd,wBAAwB,EACxB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,QAAQ,EACR,kBAAkB,GAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EACN,cAAc,EACd,wBAAwB,EACxB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,QAAQ,EACR,kBAAkB,GAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EACN,cAAc,EACd,wBAAwB,EACxB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,QAAQ,EACR,kBAAkB,GAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EACN,YAAY,EACZ,sBAAsB,EACtB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,MAAM,EACN,gBAAgB,GAChB,MAAM,SAAS,CAAC;AACjB,OAAO,EACN,cAAc,EACd,wBAAwB,EACxB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,QAAQ,EACR,kBAAkB,GAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EACN,gBAAgB,EAChB,0BAA0B,EAC1B,iBAAiB,EACjB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,UAAU,EACV,oBAAoB,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,eAAe,EACf,yBAAyB,EACzB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,gBAAgB,GACrB,MAAM,YAAY,CAAC;AACpB,OAAO,EACN,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,6BAA6B,EAC7B,kBAAkB,EAClB,4BAA4B,EAC5B,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,0BAA0B,EAC1B,qBAAqB,EACrB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,YAAY,EACZ,sBAAsB,GACtB,MAAM,eAAe,CAAC;AACvB,OAAO,EACN,+BAA+B,EAC/B,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACN,yBAAyB,EACzB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACxB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EACN,iBAAiB,EACjB,iBAAiB,EACjB,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,YAAY,EACZ,YAAY,EACZ,YAAY,GACZ,MAAM,eAAe,CAAC;AACvB,OAAO,EACN,wBAAwB,EACxB,cAAc,EACd,gBAAgB,EAChB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,kBAAkB,GAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EACN,6BAA6B,EAC7B,uBAAuB,EACvB,iCAAiC,EACjC,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,iBAAiB,EACjB,2BAA2B,GAC3B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACN,kBAAkB,EAClB,4BAA4B,EAC5B,mBAAmB,EACnB,6BAA6B,EAC7B,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACvB,YAAY,EACZ,sBAAsB,EACtB,aAAa,EACb,uBAAuB,GACvB,MAAM,UAAU,CAAC;AAClB,OAAO,EACN,eAAe,EACf,yBAAyB,EACzB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,SAAS,EACT,mBAAmB,GACnB,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D,OAAO,EACN,KAAK,eAAe,EAKpB,MAAM,WAAW,CAAC;AAKnB,OAAO,EAGN,KAAK,eAAe,EAGpB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAA8C,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC/F,OAAO,EAIN,KAAK,mBAAmB,EAGxB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAmC,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC9F,OAAO,EAA6B,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAsBjF,MAAM,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;AAClC,MAAM,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AAE/C,eAAO,MAAM,WAAW,EAAE,IAAI,EAA8C,CAAC;AAC7E,eAAO,MAAM,aAAa,EAAE,IAAI,EAA2C,CAAC;AAQ5E,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgBpB,CAAC;AAEF,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgB9B,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,QAAQ,CAAC;AAE7C,MAAM,WAAW,YAAY;IAC5B,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,KAAK,CAAC,EAAE;QAAE,QAAQ,EAAE,mBAAmB,CAAA;KAAE,CAAC;IAC1C,WAAW,CAAC,EAAE;QAAE,SAAS,EAAE,mBAAmB,CAAA;KAAE,CAAC;CACjD;AAED,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,EAAE,CAO1F;AAED,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,EAAE,CAO5F;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC,QAAQ,GAAG,OAAO,EAAE,OAAO,CAAC,CA4BjH;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,IAAI,EAAE,CAO7E;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,IAAI,EAAE,CAE/E;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC,QAAQ,GAAG,OAAO,EAAE,IAAI,CAAC,CA4BpG","sourcesContent":["export {\n\ttype AskUserAnswerDetail,\n\ttype AskUserDetails,\n\ttype AskUserInput,\n\tcreateAskUserToolDefinition,\n} from \"./ask-user.js\";\nexport {\n\ttype BashOperations,\n\ttype BashSpawnContext,\n\ttype BashSpawnHook,\n\ttype BashToolDetails,\n\ttype BashToolInput,\n\ttype BashToolOptions,\n\tbashTool,\n\tbashToolDefinition,\n\tcreateBashTool,\n\tcreateBashToolDefinition,\n\tcreateLocalBashOperations,\n} from \"./bash.js\";\nexport {\n\tcreateEditTool,\n\tcreateEditToolDefinition,\n\ttype EditOperations,\n\ttype EditToolDetails,\n\ttype EditToolInput,\n\ttype EditToolOptions,\n\teditTool,\n\teditToolDefinition,\n} from \"./edit.js\";\nexport { withFileMutationQueue } from \"./file-mutation-queue.js\";\nexport {\n\tcreateFindTool,\n\tcreateFindToolDefinition,\n\ttype FindOperations,\n\ttype FindToolDetails,\n\ttype FindToolInput,\n\ttype FindToolOptions,\n\tfindTool,\n\tfindToolDefinition,\n} from \"./find.js\";\nexport {\n\tcreateGrepTool,\n\tcreateGrepToolDefinition,\n\ttype GrepOperations,\n\ttype GrepToolDetails,\n\ttype GrepToolInput,\n\ttype GrepToolOptions,\n\tgrepTool,\n\tgrepToolDefinition,\n} from \"./grep.js\";\nexport {\n\tcreateLsTool,\n\tcreateLsToolDefinition,\n\ttype LsOperations,\n\ttype LsToolDetails,\n\ttype LsToolInput,\n\ttype LsToolOptions,\n\tlsTool,\n\tlsToolDefinition,\n} from \"./ls.js\";\nexport {\n\tcreateReadTool,\n\tcreateReadToolDefinition,\n\ttype ReadOperations,\n\ttype ReadToolDetails,\n\ttype ReadToolInput,\n\ttype ReadToolOptions,\n\treadTool,\n\treadToolDefinition,\n} from \"./read.js\";\nexport {\n\tcreateSearchTool,\n\tcreateSearchToolDefinition,\n\tisSearchAvailable,\n\ttype SearchToolDetails,\n\ttype SearchToolInput,\n\tsearchTool,\n\tsearchToolDefinition,\n} from \"./search.js\";\nexport {\n\tcreateSkillTool,\n\tcreateSkillToolDefinition,\n\ttype SkillToolDetails,\n\ttype SkillToolInput,\n\ttype SkillToolOptions,\n} from \"./skill.js\";\nexport {\n\tabortBackgroundAgents,\n\ttype BackgroundAgentInfo,\n\tcreateSubagentConcurrencyGate,\n\tcreateSubagentTool,\n\tcreateSubagentToolDefinition,\n\tdiscoverAgentTypes,\n\tfilterSubagentTools,\n\tgetBackgroundAgents,\n\tgetRunningBackgroundAgents,\n\tpruneBackgroundAgents,\n\ttype SubagentArbitrationEvent,\n\ttype SubagentConcurrencyGate,\n\ttype SubagentResult,\n\ttype SubagentStepMetadata,\n\ttype SubagentToolDetails,\n\ttype SubagentToolInput,\n\ttype SubagentToolOptions,\n\tsubagentTool,\n\tsubagentToolDefinition,\n} from \"./subagent.js\";\nexport {\n\tcreateSuggestNextToolDefinition,\n\ttype SuggestNextCallback,\n\ttype SuggestNextDetails,\n\ttype SuggestNextInput,\n} from \"./suggest-next.js\";\nexport {\n\tcreateTasksToolDefinition,\n\ttype SessionTask,\n\ttype TaskStatus,\n\ttype TasksToolDetails,\n\ttype TasksToolInput,\n\ttype TasksUpdateCallback,\n} from \"./tasks.js\";\nexport { createTmpReadToolDefinition } from \"./tmp-read.js\";\nexport {\n\tDEFAULT_MAX_BYTES,\n\tDEFAULT_MAX_LINES,\n\tformatSize,\n\ttype TruncationOptions,\n\ttype TruncationResult,\n\ttruncateHead,\n\ttruncateLine,\n\ttruncateTail,\n} from \"./truncate.js\";\nexport {\n\tcreateWaitToolDefinition,\n\tformatWaitCall,\n\tformatWaitResult,\n\ttype WaitAgentInfo,\n\ttype WaitToolDetails,\n\ttype WaitToolInput,\n\ttype WaitToolOptions,\n\twaitToolDefinition,\n} from \"./wait.js\";\nexport {\n\tcreateLocalGithubCiOperations,\n\tcreateWatchGithubCiTool,\n\tcreateWatchGithubCiToolDefinition,\n\ttype GithubCiOperations,\n\ttype WatchGithubCiToolDetails,\n\ttype WatchGithubCiToolInput,\n\ttype WatchGithubCiToolOptions,\n\twatchGithubCiTool,\n\twatchGithubCiToolDefinition,\n} from \"./watch-github-ci.js\";\nexport {\n\tcreateWebFetchTool,\n\tcreateWebFetchToolDefinition,\n\tcreateWebSearchTool,\n\tcreateWebSearchToolDefinition,\n\ttype WebFetchToolDetails,\n\ttype WebFetchToolInput,\n\ttype WebSearchConfig,\n\ttype WebSearchToolDetails,\n\ttype WebSearchToolInput,\n\twebFetchTool,\n\twebFetchToolDefinition,\n\twebSearchTool,\n\twebSearchToolDefinition,\n} from \"./web.js\";\nexport {\n\tcreateWriteTool,\n\tcreateWriteToolDefinition,\n\ttype WriteOperations,\n\ttype WriteToolInput,\n\ttype WriteToolOptions,\n\twriteTool,\n\twriteToolDefinition,\n} from \"./write.js\";\n\nimport type { AgentTool } from \"@dreb/agent-core\";\nimport type { ToolDefinition } from \"../extensions/types.js\";\nimport { createAskUserToolDefinition } from \"./ask-user.js\";\nimport {\n\ttype BashToolOptions,\n\tbashTool,\n\tbashToolDefinition,\n\tcreateBashTool,\n\tcreateBashToolDefinition,\n} from \"./bash.js\";\nimport { createEditTool, createEditToolDefinition, editTool, editToolDefinition } from \"./edit.js\";\nimport { createFindTool, createFindToolDefinition, findTool, findToolDefinition } from \"./find.js\";\nimport { createGrepTool, createGrepToolDefinition, grepTool, grepToolDefinition } from \"./grep.js\";\nimport { createLsTool, createLsToolDefinition, lsTool, lsToolDefinition } from \"./ls.js\";\nimport {\n\tcreateReadTool,\n\tcreateReadToolDefinition,\n\ttype ReadToolOptions,\n\treadTool,\n\treadToolDefinition,\n} from \"./read.js\";\nimport { createSearchTool, createSearchToolDefinition, searchTool, searchToolDefinition } from \"./search.js\";\nimport { createSkillTool, createSkillToolDefinition, type SkillToolOptions } from \"./skill.js\";\nimport {\n\tcreateSubagentTool,\n\tcreateSubagentToolDefinition,\n\tgetRunningBackgroundAgents,\n\ttype SubagentToolOptions,\n\tsubagentTool,\n\tsubagentToolDefinition,\n} from \"./subagent.js\";\nimport { createSuggestNextToolDefinition, type SuggestNextCallback } from \"./suggest-next.js\";\nimport { createTasksToolDefinition, type TasksUpdateCallback } from \"./tasks.js\";\nimport { createTmpReadToolDefinition } from \"./tmp-read.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\nimport { createWaitToolDefinition, waitToolDefinition } from \"./wait.js\";\nimport {\n\tcreateWatchGithubCiTool,\n\tcreateWatchGithubCiToolDefinition,\n\twatchGithubCiTool,\n\twatchGithubCiToolDefinition,\n} from \"./watch-github-ci.js\";\nimport {\n\tcreateWebFetchTool,\n\tcreateWebFetchToolDefinition,\n\tcreateWebSearchTool,\n\tcreateWebSearchToolDefinition,\n\twebFetchTool,\n\twebFetchToolDefinition,\n\twebSearchTool,\n\twebSearchToolDefinition,\n} from \"./web.js\";\nimport { createWriteTool, createWriteToolDefinition, writeTool, writeToolDefinition } from \"./write.js\";\n\nexport type Tool = AgentTool<any>;\nexport type ToolDef = ToolDefinition<any, any>;\n\nexport const codingTools: Tool[] = [readTool, bashTool, editTool, writeTool];\nexport const readOnlyTools: Tool[] = [readTool, grepTool, findTool, lsTool];\n\nconst tmpReadToolDefinition = createTmpReadToolDefinition();\nconst tmpReadTool = wrapToolDefinition(tmpReadToolDefinition);\nconst waitTool = wrapToolDefinition(waitToolDefinition);\nconst askUserToolDefinition = createAskUserToolDefinition();\nconst askUserTool = wrapToolDefinition(askUserToolDefinition);\n\nexport const allTools = {\n\tread: readTool,\n\tbash: bashTool,\n\tedit: editTool,\n\twrite: writeTool,\n\tgrep: grepTool,\n\tfind: findTool,\n\tls: lsTool,\n\tweb_search: webSearchTool,\n\tweb_fetch: webFetchTool,\n\tsubagent: subagentTool,\n\ttmp_read: tmpReadTool,\n\twait: waitTool,\n\twatch_github_ci: watchGithubCiTool,\n\tsearch: searchTool,\n\task_user: askUserTool,\n};\n\nexport const allToolDefinitions = {\n\tread: readToolDefinition,\n\tbash: bashToolDefinition,\n\tedit: editToolDefinition,\n\twrite: writeToolDefinition,\n\tgrep: grepToolDefinition,\n\tfind: findToolDefinition,\n\tls: lsToolDefinition,\n\tweb_search: webSearchToolDefinition,\n\tweb_fetch: webFetchToolDefinition,\n\tsubagent: subagentToolDefinition,\n\ttmp_read: tmpReadToolDefinition,\n\twait: waitToolDefinition,\n\twatch_github_ci: watchGithubCiToolDefinition,\n\tsearch: searchToolDefinition,\n\task_user: askUserToolDefinition,\n};\n\nexport type ToolName = keyof typeof allTools;\n\nexport interface ToolsOptions {\n\tread?: ReadToolOptions;\n\tbash?: BashToolOptions;\n\tsubagent?: SubagentToolOptions;\n\tskill?: SkillToolOptions;\n\ttasks?: { onUpdate: TasksUpdateCallback };\n\tsuggestNext?: { onSuggest: SuggestNextCallback };\n}\n\nexport function createCodingToolDefinitions(cwd: string, options?: ToolsOptions): ToolDef[] {\n\treturn [\n\t\tcreateReadToolDefinition(cwd, options?.read),\n\t\tcreateBashToolDefinition(cwd, options?.bash),\n\t\tcreateEditToolDefinition(cwd),\n\t\tcreateWriteToolDefinition(cwd),\n\t];\n}\n\nexport function createReadOnlyToolDefinitions(cwd: string, options?: ToolsOptions): ToolDef[] {\n\treturn [\n\t\tcreateReadToolDefinition(cwd, options?.read),\n\t\tcreateGrepToolDefinition(cwd),\n\t\tcreateFindToolDefinition(cwd),\n\t\tcreateLsToolDefinition(cwd),\n\t];\n}\n\nexport function createAllToolDefinitions(cwd: string, options?: ToolsOptions): Record<ToolName | \"skill\", ToolDef> {\n\tconst tools: Record<string, ToolDef> = {\n\t\tread: createReadToolDefinition(cwd, options?.read),\n\t\tbash: createBashToolDefinition(cwd, options?.bash),\n\t\tedit: createEditToolDefinition(cwd),\n\t\twrite: createWriteToolDefinition(cwd),\n\t\tgrep: createGrepToolDefinition(cwd),\n\t\tfind: createFindToolDefinition(cwd),\n\t\tls: createLsToolDefinition(cwd),\n\t\tweb_search: createWebSearchToolDefinition(cwd),\n\t\tweb_fetch: createWebFetchToolDefinition(cwd),\n\t\tsubagent: createSubagentToolDefinition(cwd, options?.subagent),\n\t\ttmp_read: createTmpReadToolDefinition(options?.read),\n\t\twait: createWaitToolDefinition({ getRunningAgents: getRunningBackgroundAgents }),\n\t\twatch_github_ci: createWatchGithubCiToolDefinition(cwd),\n\t\tsearch: createSearchToolDefinition(cwd),\n\t\task_user: createAskUserToolDefinition(),\n\t};\n\tif (options?.skill) {\n\t\ttools.skill = createSkillToolDefinition(cwd, options.skill);\n\t}\n\tif (options?.tasks) {\n\t\ttools.tasks_update = createTasksToolDefinition(options.tasks.onUpdate);\n\t}\n\tif (options?.suggestNext) {\n\t\ttools.suggest_next = createSuggestNextToolDefinition(options.suggestNext.onSuggest);\n\t}\n\treturn tools as Record<ToolName | \"skill\", ToolDef>;\n}\n\nexport function createCodingTools(cwd: string, options?: ToolsOptions): Tool[] {\n\treturn [\n\t\tcreateReadTool(cwd, options?.read),\n\t\tcreateBashTool(cwd, options?.bash),\n\t\tcreateEditTool(cwd),\n\t\tcreateWriteTool(cwd),\n\t];\n}\n\nexport function createReadOnlyTools(cwd: string, options?: ToolsOptions): Tool[] {\n\treturn [createReadTool(cwd, options?.read), createGrepTool(cwd), createFindTool(cwd), createLsTool(cwd)];\n}\n\nexport function createAllTools(cwd: string, options?: ToolsOptions): Record<ToolName | \"skill\", Tool> {\n\tconst tools: Record<string, Tool> = {\n\t\tread: createReadTool(cwd, options?.read),\n\t\tbash: createBashTool(cwd, options?.bash),\n\t\tedit: createEditTool(cwd),\n\t\twrite: createWriteTool(cwd),\n\t\tgrep: createGrepTool(cwd),\n\t\tfind: createFindTool(cwd),\n\t\tls: createLsTool(cwd),\n\t\tweb_search: createWebSearchTool(cwd),\n\t\tweb_fetch: createWebFetchTool(cwd),\n\t\tsubagent: createSubagentTool(cwd, options?.subagent),\n\t\ttmp_read: wrapToolDefinition(createTmpReadToolDefinition(options?.read)),\n\t\twait: wrapToolDefinition(createWaitToolDefinition({ getRunningAgents: getRunningBackgroundAgents })),\n\t\twatch_github_ci: createWatchGithubCiTool(cwd),\n\t\tsearch: createSearchTool(cwd),\n\t\task_user: wrapToolDefinition(createAskUserToolDefinition()),\n\t};\n\tif (options?.skill) {\n\t\ttools.skill = createSkillTool(cwd, options.skill);\n\t}\n\tif (options?.tasks) {\n\t\ttools.tasks_update = wrapToolDefinition(createTasksToolDefinition(options.tasks.onUpdate));\n\t}\n\tif (options?.suggestNext) {\n\t\ttools.suggest_next = wrapToolDefinition(createSuggestNextToolDefinition(options.suggestNext.onSuggest));\n\t}\n\treturn tools as Record<ToolName | \"skill\", Tool>;\n}\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/tools/index.ts"],"names":[],"mappings":"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","sourcesContent":["export {\n\ttype AskUserAnswerDetail,\n\ttype AskUserDetails,\n\ttype AskUserInput,\n\tcreateAskUserToolDefinition,\n} from \"./ask-user.js\";\nexport {\n\ttype BashOperations,\n\ttype BashSpawnContext,\n\ttype BashSpawnHook,\n\ttype BashToolDetails,\n\ttype BashToolInput,\n\ttype BashToolOptions,\n\tbashTool,\n\tbashToolDefinition,\n\tcreateBashTool,\n\tcreateBashToolDefinition,\n\tcreateLocalBashOperations,\n} from \"./bash.js\";\nexport {\n\tcreateEditTool,\n\tcreateEditToolDefinition,\n\ttype EditOperations,\n\ttype EditToolDetails,\n\ttype EditToolInput,\n\ttype EditToolOptions,\n\teditTool,\n\teditToolDefinition,\n} from \"./edit.js\";\nexport { withFileMutationQueue } from \"./file-mutation-queue.js\";\nexport {\n\tcreateFindTool,\n\tcreateFindToolDefinition,\n\ttype FindOperations,\n\ttype FindToolDetails,\n\ttype FindToolInput,\n\ttype FindToolOptions,\n\tfindTool,\n\tfindToolDefinition,\n} from \"./find.js\";\nexport {\n\tcreateGrepTool,\n\tcreateGrepToolDefinition,\n\ttype GrepOperations,\n\ttype GrepToolDetails,\n\ttype GrepToolInput,\n\ttype GrepToolOptions,\n\tgrepTool,\n\tgrepToolDefinition,\n} from \"./grep.js\";\nexport {\n\tcreateLsTool,\n\tcreateLsToolDefinition,\n\ttype LsOperations,\n\ttype LsToolDetails,\n\ttype LsToolInput,\n\ttype LsToolOptions,\n\tlsTool,\n\tlsToolDefinition,\n} from \"./ls.js\";\nexport {\n\tcreateReadTool,\n\tcreateReadToolDefinition,\n\ttype ReadOperations,\n\ttype ReadToolDetails,\n\ttype ReadToolInput,\n\ttype ReadToolOptions,\n\treadTool,\n\treadToolDefinition,\n} from \"./read.js\";\nexport {\n\tcreateSearchTool,\n\tcreateSearchToolDefinition,\n\tisSearchAvailable,\n\ttype SearchToolDetails,\n\ttype SearchToolInput,\n\tsearchTool,\n\tsearchToolDefinition,\n} from \"./search.js\";\nexport {\n\tcreateSkillTool,\n\tcreateSkillToolDefinition,\n\ttype SkillToolDetails,\n\ttype SkillToolInput,\n\ttype SkillToolOptions,\n} from \"./skill.js\";\nexport {\n\tabortBackgroundAgents,\n\ttype BackgroundAgentInfo,\n\tcreateSubagentConcurrencyGate,\n\tcreateSubagentTool,\n\tcreateSubagentToolDefinition,\n\tdiscoverAgentTypes,\n\tfilterSubagentTools,\n\tgetBackgroundAgents,\n\tgetRunningBackgroundAgents,\n\tpruneBackgroundAgents,\n\ttype SubagentArbitrationEvent,\n\ttype SubagentConcurrencyGate,\n\ttype SubagentResult,\n\ttype SubagentStepMetadata,\n\ttype SubagentToolDetails,\n\ttype SubagentToolInput,\n\ttype SubagentToolOptions,\n\tsubagentTool,\n\tsubagentToolDefinition,\n} from \"./subagent.js\";\nexport {\n\tcreateSuggestNextToolDefinition,\n\ttype SuggestNextCallback,\n\ttype SuggestNextDetails,\n\ttype SuggestNextInput,\n} from \"./suggest-next.js\";\nexport {\n\tcreateTasksToolDefinition,\n\ttype SessionTask,\n\ttype TaskStatus,\n\ttype TasksToolDetails,\n\ttype TasksToolInput,\n\ttype TasksUpdateCallback,\n} from \"./tasks.js\";\nexport { createTmpReadToolDefinition } from \"./tmp-read.js\";\nexport {\n\tDEFAULT_MAX_BYTES,\n\tDEFAULT_MAX_LINES,\n\tformatSize,\n\ttype TruncationOptions,\n\ttype TruncationResult,\n\ttruncateHead,\n\ttruncateLine,\n\ttruncateTail,\n} from \"./truncate.js\";\nexport {\n\tcreateWaitToolDefinition,\n\tformatWaitCall,\n\tformatWaitResult,\n\ttype WaitAgentInfo,\n\ttype WaitToolDetails,\n\ttype WaitToolInput,\n\ttype WaitToolOptions,\n\twaitToolDefinition,\n} from \"./wait.js\";\nexport {\n\tcreateLocalGithubCiOperations,\n\tcreateWatchGithubCiTool,\n\tcreateWatchGithubCiToolDefinition,\n\ttype GithubCiOperations,\n\ttype WatchGithubCiToolDetails,\n\ttype WatchGithubCiToolInput,\n\ttype WatchGithubCiToolOptions,\n\twatchGithubCiTool,\n\twatchGithubCiToolDefinition,\n} from \"./watch-github-ci.js\";\nexport {\n\tcreateWebFetchTool,\n\tcreateWebFetchToolDefinition,\n\tcreateWebSearchTool,\n\tcreateWebSearchToolDefinition,\n\ttype WebFetchToolDetails,\n\ttype WebFetchToolInput,\n\ttype WebSearchConfig,\n\ttype WebSearchToolDetails,\n\ttype WebSearchToolInput,\n\twebFetchTool,\n\twebFetchToolDefinition,\n\twebSearchTool,\n\twebSearchToolDefinition,\n} from \"./web.js\";\nexport {\n\tcreateWriteTool,\n\tcreateWriteToolDefinition,\n\ttype WriteOperations,\n\ttype WriteToolInput,\n\ttype WriteToolOptions,\n\twriteTool,\n\twriteToolDefinition,\n} from \"./write.js\";\n\nimport type { AgentTool } from \"@dreb/agent-core\";\nimport type { ToolDefinition } from \"../extensions/types.js\";\nimport { createAskUserToolDefinition } from \"./ask-user.js\";\nimport {\n\ttype BashToolOptions,\n\tbashTool,\n\tbashToolDefinition,\n\tcreateBashTool,\n\tcreateBashToolDefinition,\n} from \"./bash.js\";\nimport { createEditTool, createEditToolDefinition, editTool, editToolDefinition } from \"./edit.js\";\nimport { createFindTool, createFindToolDefinition, findTool, findToolDefinition } from \"./find.js\";\nimport { createGrepTool, createGrepToolDefinition, grepTool, grepToolDefinition } from \"./grep.js\";\nimport { createLsTool, createLsToolDefinition, lsTool, lsToolDefinition } from \"./ls.js\";\nimport {\n\tcreateReadTool,\n\tcreateReadToolDefinition,\n\ttype ReadToolOptions,\n\treadTool,\n\treadToolDefinition,\n} from \"./read.js\";\nimport { createSearchTool, createSearchToolDefinition, searchTool, searchToolDefinition } from \"./search.js\";\nimport { createSkillTool, createSkillToolDefinition, type SkillToolOptions } from \"./skill.js\";\nimport {\n\tcreateSubagentTool,\n\tcreateSubagentToolDefinition,\n\tgetRunningBackgroundAgents,\n\ttype SubagentToolOptions,\n\tsubagentTool,\n\tsubagentToolDefinition,\n} from \"./subagent.js\";\nimport { createSuggestNextToolDefinition, type SuggestNextCallback } from \"./suggest-next.js\";\nimport { createTasksToolDefinition, type TasksUpdateCallback } from \"./tasks.js\";\nimport { createTmpReadToolDefinition } from \"./tmp-read.js\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.js\";\nimport { createWaitToolDefinition, waitToolDefinition } from \"./wait.js\";\nimport {\n\tcreateWatchGithubCiTool,\n\tcreateWatchGithubCiToolDefinition,\n\twatchGithubCiTool,\n\twatchGithubCiToolDefinition,\n} from \"./watch-github-ci.js\";\nimport {\n\tcreateWebFetchTool,\n\tcreateWebFetchToolDefinition,\n\tcreateWebSearchTool,\n\tcreateWebSearchToolDefinition,\n\twebFetchTool,\n\twebFetchToolDefinition,\n\twebSearchTool,\n\twebSearchToolDefinition,\n} from \"./web.js\";\nimport { createWriteTool, createWriteToolDefinition, writeTool, writeToolDefinition } from \"./write.js\";\n\nexport type Tool = AgentTool<any>;\nexport type ToolDef = ToolDefinition<any, any>;\n\nexport const codingTools: Tool[] = [readTool, bashTool, editTool, writeTool];\nexport const readOnlyTools: Tool[] = [readTool, grepTool, findTool, lsTool];\n\nconst tmpReadToolDefinition = createTmpReadToolDefinition();\nconst tmpReadTool = wrapToolDefinition(tmpReadToolDefinition);\nconst waitTool = wrapToolDefinition(waitToolDefinition);\nconst askUserToolDefinition = createAskUserToolDefinition();\nconst askUserTool = wrapToolDefinition(askUserToolDefinition);\n\nexport const allTools = {\n\tread: readTool,\n\tbash: bashTool,\n\tedit: editTool,\n\twrite: writeTool,\n\tgrep: grepTool,\n\tfind: findTool,\n\tls: lsTool,\n\tweb_search: webSearchTool,\n\tweb_fetch: webFetchTool,\n\tsubagent: subagentTool,\n\ttmp_read: tmpReadTool,\n\twait: waitTool,\n\twatch_github_ci: watchGithubCiTool,\n\tsearch: searchTool,\n\task_user: askUserTool,\n};\n\nexport const allToolDefinitions = {\n\tread: readToolDefinition,\n\tbash: bashToolDefinition,\n\tedit: editToolDefinition,\n\twrite: writeToolDefinition,\n\tgrep: grepToolDefinition,\n\tfind: findToolDefinition,\n\tls: lsToolDefinition,\n\tweb_search: webSearchToolDefinition,\n\tweb_fetch: webFetchToolDefinition,\n\tsubagent: subagentToolDefinition,\n\ttmp_read: tmpReadToolDefinition,\n\twait: waitToolDefinition,\n\twatch_github_ci: watchGithubCiToolDefinition,\n\tsearch: searchToolDefinition,\n\task_user: askUserToolDefinition,\n};\n\nexport type ToolName = keyof typeof allTools;\n\nexport interface ToolsOptions {\n\tread?: ReadToolOptions;\n\tbash?: BashToolOptions;\n\tsubagent?: SubagentToolOptions;\n\tskill?: SkillToolOptions;\n\ttasks?: { onUpdate: TasksUpdateCallback };\n\tsuggestNext?: { onSuggest: SuggestNextCallback };\n}\n\nexport function createCodingToolDefinitions(cwd: string, options?: ToolsOptions): ToolDef[] {\n\treturn [\n\t\tcreateReadToolDefinition(cwd, options?.read),\n\t\tcreateBashToolDefinition(cwd, options?.bash),\n\t\tcreateEditToolDefinition(cwd),\n\t\tcreateWriteToolDefinition(cwd),\n\t];\n}\n\nexport function createReadOnlyToolDefinitions(cwd: string, options?: ToolsOptions): ToolDef[] {\n\treturn [\n\t\tcreateReadToolDefinition(cwd, options?.read),\n\t\tcreateGrepToolDefinition(cwd),\n\t\tcreateFindToolDefinition(cwd),\n\t\tcreateLsToolDefinition(cwd),\n\t];\n}\n\nexport function createAllToolDefinitions(cwd: string, options?: ToolsOptions): Record<ToolName | \"skill\", ToolDef> {\n\tconst tools: Record<string, ToolDef> = {\n\t\tread: createReadToolDefinition(cwd, options?.read),\n\t\tbash: createBashToolDefinition(cwd, options?.bash),\n\t\tedit: createEditToolDefinition(cwd),\n\t\twrite: createWriteToolDefinition(cwd),\n\t\tgrep: createGrepToolDefinition(cwd),\n\t\tfind: createFindToolDefinition(cwd),\n\t\tls: createLsToolDefinition(cwd),\n\t\tweb_search: createWebSearchToolDefinition(cwd),\n\t\tweb_fetch: createWebFetchToolDefinition(cwd),\n\t\tsubagent: createSubagentToolDefinition(cwd, options?.subagent),\n\t\ttmp_read: createTmpReadToolDefinition(options?.read),\n\t\twait: createWaitToolDefinition({ getRunningAgents: getRunningBackgroundAgents }),\n\t\twatch_github_ci: createWatchGithubCiToolDefinition(cwd),\n\t\tsearch: createSearchToolDefinition(cwd),\n\t\task_user: createAskUserToolDefinition(),\n\t};\n\tif (options?.skill) {\n\t\ttools.skill = createSkillToolDefinition(cwd, options.skill);\n\t}\n\tif (options?.tasks) {\n\t\ttools.tasks_update = createTasksToolDefinition(options.tasks.onUpdate);\n\t}\n\tif (options?.suggestNext) {\n\t\ttools.suggest_next = createSuggestNextToolDefinition(options.suggestNext.onSuggest);\n\t}\n\treturn tools as Record<ToolName | \"skill\", ToolDef>;\n}\n\nexport function createCodingTools(cwd: string, options?: ToolsOptions): Tool[] {\n\treturn [\n\t\tcreateReadTool(cwd, options?.read),\n\t\tcreateBashTool(cwd, options?.bash),\n\t\tcreateEditTool(cwd),\n\t\tcreateWriteTool(cwd),\n\t];\n}\n\nexport function createReadOnlyTools(cwd: string, options?: ToolsOptions): Tool[] {\n\treturn [createReadTool(cwd, options?.read), createGrepTool(cwd), createFindTool(cwd), createLsTool(cwd)];\n}\n\nexport function createAllTools(cwd: string, options?: ToolsOptions): Record<ToolName | \"skill\", Tool> {\n\tconst tools: Record<string, Tool> = {\n\t\tread: createReadTool(cwd, options?.read),\n\t\tbash: createBashTool(cwd, options?.bash),\n\t\tedit: createEditTool(cwd),\n\t\twrite: createWriteTool(cwd),\n\t\tgrep: createGrepTool(cwd),\n\t\tfind: createFindTool(cwd),\n\t\tls: createLsTool(cwd),\n\t\tweb_search: createWebSearchTool(cwd),\n\t\tweb_fetch: createWebFetchTool(cwd),\n\t\tsubagent: createSubagentTool(cwd, options?.subagent),\n\t\ttmp_read: wrapToolDefinition(createTmpReadToolDefinition(options?.read)),\n\t\twait: wrapToolDefinition(createWaitToolDefinition({ getRunningAgents: getRunningBackgroundAgents })),\n\t\twatch_github_ci: createWatchGithubCiTool(cwd),\n\t\tsearch: createSearchTool(cwd),\n\t\task_user: wrapToolDefinition(createAskUserToolDefinition()),\n\t};\n\tif (options?.skill) {\n\t\ttools.skill = createSkillTool(cwd, options.skill);\n\t}\n\tif (options?.tasks) {\n\t\ttools.tasks_update = wrapToolDefinition(createTasksToolDefinition(options.tasks.onUpdate));\n\t}\n\tif (options?.suggestNext) {\n\t\ttools.suggest_next = wrapToolDefinition(createSuggestNextToolDefinition(options.suggestNext.onSuggest));\n\t}\n\treturn tools as Record<ToolName | \"skill\", Tool>;\n}\n"]}
package/docs/dashboard.md CHANGED
@@ -143,7 +143,7 @@ networking window above.
143
143
  | **Subagent view** | Transcript of a background agent: live events via the RPC relay, hydrated from the agent's on-disk session log (`/subagents/:agentId/messages`) so the transcript survives browser reloads. Shows the task, streaming output, tool activity, and any safe Dispatch Arbiter changed/unchanged/failure records with the final agent/model/thinking. No raw arbiter output is displayed or transported. While the child is running, a composer sends the user's text unchanged to that specific child as steering input, displays its pending steering queue, and reports its effective `one-at-a-time` or `all` delivery mode. Completed, failed, rehydrated, and unavailable children remain read-only. |
144
144
  | **Files** | Host-wide browser with places shortcuts (home, /tmp, project roots), breadcrumbs to `/`, new-folder, download, drop-zone/picker upload with explicit collision prompts, and "new session here" on any directory. It also shows the **effective global nested-context trust** for the displayed canonical directory: untrusted, trusted by that root, inherited from a granting root, or global expert trust-all. You can trust the displayed folder and descendants, or untrust the actual granting root; untrusting an inherited folder removes that root's trust for all descendants. |
145
145
  | **Memories** | Dreb-only memory management for `~/.dreb/memory` and `.dreb/memory` under active/disk project roots. It lists existing `MEMORY.md` indexes and direct child `.md` entries only (no Claude paths, create, or rename), shows entry frontmatter or metadata errors so malformed files can be repaired, renders sanitized Markdown preview beside a raw editor, and uses exact SHA-256 revisions so stale saves/deletes return conflicts while preserving drafts. The index view is complete, not truncated, and warns when it exceeds the 200-line memory-index convention. Entry deletion requires both entry and index revisions, removes only matching safe Markdown-link index lines (`file.md` / `./file.md`), writes the cleaned index atomically before unlinking the entry, and rolls back loudly if the unlink fails. |
146
- | **Settings** | Persistent defaults (default model, thinking level, steering/follow-up queue modes, auto-compaction, opt-in continuation of pending work after successful automatic compaction, auto-retry) via `get_settings`/`set_settings` — validation errors are shown verbatim. The continuation option is off by default, can keep unattended model turns and costs running indefinitely, does not restart completed answers, and never affects manual `/compact`. The tab-title card exposes the default-enabled generator toggle and an exact authenticated `provider/model` picker; an unset model clearly retains Explore-agent routing, a pinned model can be cleared back to that route, and edits apply to new unnamed sessions. The scoped-models editor controls model cycling for new sessions only: grouped search, model/provider/all toggles, responsive controls, accessible up/down partial-scope ordering, and save/reset. An absent `enabledModels` is future-inclusive all models in registry order and cannot be reordered; a partial scope is a non-empty ordered list of canonical `provider/model` references. Editing legacy glob, fuzzy, or thinking-suffix values saves normalized exact references. The selected context reads effective global + project settings but writes global; a project-level `enabledModels` shadow is warned. The global-only Dispatch Arbiter card exposes enable/disable, exact authenticated model selection, thinking, guide path, and readiness guidance; model-less enablement is blocked and RPC/runtime validation remains fail-closed. Entering Settings flushes pending writes and reloads durable global + project settings, so external edits appear; read, parse, or write failures fail loudly instead of showing stale settings. The global-only nested-context policy lists every explicit trusted root for audit and revoke, offers a simple add-by-path control, and includes a prominently warned expert trust-all toggle; the Files view remains the primary place to grant trust while browsing. Most defaults seed new sessions, including **Max concurrent subagents** (default 4). Setting it to 0 starts new parents without the subagent tool and tells the parent model to perform normally delegated work itself; positive values cap running children per parent session. Context-trust changes are observed by active main/subagent processes for future lazy loads, but cannot remove already injected content. Dashboard-local preferences (always expand thinking, transcript image display mode, needs-attention notification permission) live in the browser, alongside an appearance section: a theme gallery of eight curated themes (entropist.ca, Dim, Solarized, Gruvbox, Caves of Qud, Van Gogh, and the colorblind-safe Okabe-Ito and Paul Tol) with live preview cards plus system/light/dark mode and Theme default/IBM Plex Mono/JetBrains Mono/Fira Code/Iosevka/OpenDyslexic/Atkinson Hyperlegible font selectors, saved per browser. Shows the current rotating pairing code on the host/local dashboard, the 1–3650 day lifetime used only for future pairings (180-day default), and paired-device expiry dates with unpair. |
146
+ | **Settings** | Persistent defaults (default model, thinking level, steering/follow-up queue modes, auto-compaction, opt-in continuation of pending work after successful automatic compaction, auto-retry) via `get_settings`/`set_settings` — validation errors are shown verbatim. The continuation option is off by default, can keep unattended model turns and costs running indefinitely, does not restart completed answers, and never affects manual `/compact`. The tab-title card exposes the default-enabled generator toggle and an exact authenticated `provider/model` picker; an unset model clearly retains Explore-agent routing, a pinned model can be cleared back to that route, and edits apply to new unnamed sessions. The scoped-models editor controls model cycling for new sessions only: grouped search, model/provider/all toggles, responsive controls, accessible up/down partial-scope ordering, and save/reset. An absent `enabledModels` is future-inclusive all models in registry order and cannot be reordered; a partial scope is a non-empty ordered list of canonical `provider/model` references. Editing legacy glob, fuzzy, or thinking-suffix values saves normalized exact references. The selected context reads effective global + project settings but writes global; a project-level `enabledModels` shadow is warned. The global-only Dispatch Arbiter card exposes enable/disable, exact authenticated model selection, thinking, guide path, and readiness guidance; model-less enablement is blocked and RPC/runtime validation remains fail-closed. Entering Settings flushes pending writes and reloads durable global + project settings, so external edits appear; read, parse, or write failures fail loudly instead of showing stale settings. The global-only nested-context policy lists every explicit trusted root for audit and revoke, offers a simple add-by-path control, and includes a prominently warned expert trust-all toggle; the Files view remains the primary place to grant trust while browsing. Most defaults seed new sessions, including **Max concurrent subagents** (default 4). Setting it to 0 starts new parents without the subagent tool and tells the parent model to perform normally delegated work itself; positive values cap running children per parent session. Context-trust changes are observed by active main/subagent processes for future lazy loads, but cannot remove already injected content. Dashboard-local preferences (always expand thinking, transcript image display mode, needs-attention notification permission) live in the browser, alongside an appearance section: a theme gallery of eight curated themes (entropist.ca, Dim, Solarized, Gruvbox, Caves of Qud, Van Gogh, and the colorblind-safe Okabe-Ito and Paul Tol) with live preview cards plus system/light/dark mode and a font picker with 36 explicit choices plus Theme default (the existing IBM Plex Mono/JetBrains Mono/Fira Code/Iosevka/OpenDyslexic/Atkinson Hyperlegible plus 15 sans-serif and 15 serif families), regular-face previews in the open listbox, saved per browser. Shows the current rotating pairing code on the host/local dashboard, the 1–3650 day lifetime used only for future pairings (180-day default), and paired-device expiry dates with unpair. |
147
147
  | **Pairing** | Remote first-login: identity echo, rotating-code entry, and the security copy explaining what pairing grants. |
148
148
 
149
149
  ### Fleet sidebar controls and session switching
@@ -615,13 +615,30 @@ TUI theme system** — dashboard themes intentionally do not map to TUI themes.
615
615
  - **Theme gallery and font selection.** The settings appearance section shows
616
616
  mode and font selectors plus a grid of live preview cards (one per theme).
617
617
  Each card previews its palette locally without touching the page until you
618
- commit by clicking it. Theme default preserves each theme's built-in font;
619
- IBM Plex Mono, JetBrains Mono, Fira Code (coding ligatures), Iosevka
620
- (compact, coding ligatures), the bundled dyslexia-friendly OpenDyslexic, and
621
- the low-vision-friendly Atkinson Hyperlegible can also be selected
622
- independently. Explicit
623
- choices are reflected in the previews, while Theme default previews stay on
624
- IBM Plex Mono so the inactive Gruvbox card does not fetch JetBrains Mono.
618
+ commit by clicking it. The font picker offers **36 explicit choices plus
619
+ Theme default**: the six existing choices — IBM Plex Mono, JetBrains Mono,
620
+ Fira Code (coding ligatures), Iosevka (compact, coding ligatures), bundled
621
+ dyslexia-friendly OpenDyslexic, and low-vision-friendly Atkinson Hyperlegible —
622
+ plus 15 sans-serif and 15 serif families. Existing IDs and saved choices are
623
+ preserved; explicit choices override any theme and appear in theme-card
624
+ previews. Theme default preserves each theme's built-in font. Theme-card
625
+ previews stay on IBM Plex Mono for Theme default so an inactive Gruvbox card
626
+ does not fetch JetBrains Mono; the font picker's Theme default option instead
627
+ previews the **active** theme's built-in family, even if an explicit override
628
+ is currently selected.
629
+ Visible options preview their own regular face in the open listbox. The select-only
630
+ combobox supports Arrow Up/Down, Home/End, and typeahead to highlight options
631
+ without changing the saved choice; Enter/Space opens or commits, Escape
632
+ closes without committing, and Tab closes and moves focus without committing.
633
+ “Existing choices”, “Sans-serif fonts”, and “Serif fonts” are non-selectable
634
+ category headings, shown as uppercase, full-width dividers—not font options
635
+ or the smaller “Based on …” source descriptions.
636
+ Opening explicitly focuses the trigger. Background scrolling dismisses the
637
+ menu when its trigger leaves the viewport; internal catalog scrolling does not.
638
+ RFN-restricted additions have neutral primary option/trigger names and separate
639
+ “Based on …” descriptions naming the source (for example, Dreb Sans 07 / Based
640
+ on Lato). Typeahead also searches source families, and preference IDs are
641
+ unchanged. PT Serif uses Dreb Text 07 to avoid the reserved individual word Serif.
625
642
  - **Per-browser persistence.** Selections are stored in per-browser
626
643
  `localStorage` (`dreb.dashboard.theme`, `dreb.dashboard.colorMode`, and
627
644
  `dreb.dashboard.font`), with a cross-tab sync listener; a pristine install
@@ -631,20 +648,98 @@ TUI theme system** — dashboard themes intentionally do not map to TUI themes.
631
648
  - **No wrong-appearance flash.** A synchronous bootstrap in `index.html` paints
632
649
  the correct theme, mode, and explicit font before any CSS loads, and keeps a
633
650
  live `theme-color` meta in sync with the active background.
634
- - **Font loading.** Most themes default to Google-hosted IBM Plex Mono; Gruvbox
635
- defaults to the bundled self-hosted JetBrains Mono (OFL, provenance in
636
- `src/client/assets/fonts/`). Fira Code, Iosevka, the dyslexia-friendly
637
- OpenDyslexic, and the low-vision-friendly Atkinson Hyperlegible are bundled
638
- self-hosted explicit options (OFL, same directory, one provenance file each),
639
- and any explicit font selection overrides the theme default. The self-hosted
640
- families are lazy-loaded only
641
- when active typography uses them — rendering a theme or its preview card
642
- never fetches an alternate font. No `light-dark()` is used, keeping an iOS
643
- Safari 16.4 floor.
651
+ - **Font loading.** Existing Google-hosted IBM Plex Mono is unchanged; most
652
+ themes use it by default, while Gruvbox uses bundled self-hosted JetBrains
653
+ Mono. The existing five self-hosted families remain OFL-licensed with their
654
+ provenance in `packages/dashboard/src/client/assets/fonts/`. The 30 additions
655
+ are source-attributed Latin subsets with neutral internal names, all OFL-licensed; see
656
+ [`expanded/PROVENANCE.md`](../../dashboard/src/client/assets/fonts/expanded/PROVENANCE.md)
657
+ for sources, processing, checksums, and licenses.
658
+ Self-hosted faces load lazily when selected typography or open font-picker
659
+ previews use them. With the picker closed, alternate families are not used;
660
+ opening its listbox loads regular previews only as rows approach the visible
661
+ scroll window, not every family or unused bold/italic resource. Older browsers
662
+ without IntersectionObserver preview the complete open list. Every added
663
+ family has real regular, italic, bold, and bold-italic (400/700) support. Theme cards never introduce an alternate family beyond
664
+ the explicit selection. No font uploads or new multilingual bundles are
665
+ added; missing scripts use browser fallback.
666
+ New font source assets add **5,457,304 B** to the existing **1,395,464 B**, for
667
+ **6,852,768 B** combined. This source-asset total is not a startup download;
668
+ all new regular previews total **2,350,548 B**, downloaded progressively as
669
+ the list is explored. A local cold desktop fixture opened in about **14 ms**
670
+ and requested **109,412 B** of new fonts; closed startup requested none and
671
+ reopening reused loaded resources. Network/device speeds vary; the font
672
+ count is inexpensive DOM work (136 additional elements), while downloads
673
+ remain the main tradeoff. The same-toolchain production comparison adds
674
+ **4,737 B gzipped** across JavaScript and CSS; selected styles are fetched
675
+ on demand, and legal packaging does not duplicate source font binaries.
676
+ No `light-dark()` is used, keeping an iOS Safari 16.4 floor.
644
677
  - **PWA launch colors.** The static `manifest.webmanifest` keeps white
645
678
  (default-light) launch colors as the fallback; the live `theme-color` meta
646
679
  follows the active appearance once the app loads.
647
680
 
681
+ ### Custom fonts in a source build
682
+
683
+ The released Dashboard supports its bundled catalog only. There is **no font
684
+ upload control, custom-font install directory, settings-file font path, or
685
+ runtime font discovery**. Uploading a font through the host Files screen does
686
+ not register it with the appearance picker; installing it in your OS does not
687
+ add a picker entry either. Font uploads are a separate feature, not a hidden
688
+ configuration option.
689
+
690
+ For a private customization, manually add the font to a **source checkout** and
691
+ rebuild Dashboard. This is a developer workflow; npm upgrades do not preserve
692
+ edits to an installed package:
693
+
694
+ 1. Put legally usable WOFF2 files and their license/provenance under
695
+ `packages/dashboard/src/client/assets/fonts/custom/`. Keep them separate from
696
+ `expanded/`, which is the hash-locked, generated catalog. Retain required
697
+ notices and obey Reserved Font Name rules if modifying a font. Supply genuine
698
+ normal/italic and weight faces for the styles you intend to use.
699
+ 2. Add a unique ID, label, CSS family, and existing `Sans-serif` or `Serif` group
700
+ to `packages/dashboard/src/client/state/font-catalog.ts`. For example:
701
+ ```ts
702
+ { id: "my-custom-font", label: "My Custom Font", family: "My Custom Font", group: "Sans-serif" },
703
+ ```
704
+ `appearance.ts` derives the valid IDs, picker entries and preview stack from
705
+ this registry. Keep restricted derivative names neutral, with secondary
706
+ source attribution using the registry's existing naming pattern.
707
+ 3. Create `packages/dashboard/src/client/styles/custom-fonts.css` with the real
708
+ faces and a scoped font override. For a regular-400 face:
709
+ ```css
710
+ @font-face {
711
+ font-family: "My Custom Font";
712
+ src: url("../assets/fonts/custom/my-custom-font-regular.woff2") format("woff2");
713
+ font-style: normal;
714
+ font-weight: 400;
715
+ font-display: swap;
716
+ }
717
+ [data-font="my-custom-font"] {
718
+ --mono-font: "My Custom Font", "IBM Plex Mono", "Courier New", monospace;
719
+ }
720
+ ```
721
+ Add corresponding rules for the other real faces. Link this stylesheet in
722
+ `packages/dashboard/src/client/index.html` **after** `font-catalog.css`; do
723
+ not append hand-written rules to the generated stylesheet.
724
+ 4. Add `my-custom-font` to the inline `FONTS` allowlist in that same HTML file so
725
+ saved selection restores synchronously. Update the catalog/count/group and
726
+ bootstrap test expectations for your private extra entry; retain the
727
+ existing checks for the managed `expanded/` records. If distributing your
728
+ build, include custom notices/provenance in Dashboard's `package.json`
729
+ `files` list as well as the built font resources.
730
+ 5. From the repository root, using Node 22, run `npm run check`, `npm run build`,
731
+ and `npm run verify-workspace-links`, then the Dashboard tests:
732
+ ```sh
733
+ npm run test --workspace @dreb/dashboard
734
+ node packages/dashboard/dist/index.js
735
+ ```
736
+ Use this built server, not an unchanged globally installed Dashboard. Check
737
+ selection, reload restoration, actual face loading and fallback behavior.
738
+
739
+ There is no need to change server/RPC code for a private bundled font. A GUI
740
+ upload feature would additionally need validation, storage, registration,
741
+ persistence and licensing handling; it remains separate from this catalog.
742
+
648
743
  ## Limitations (deliberate, sequenced later)
649
744
 
650
745
  - No shell passthrough from the browser.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dreb/coding-agent",
3
- "version": "2.69.0",
3
+ "version": "2.70.1",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "drebConfig": {