@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 +4 -2
- package/dist/core/tools/ask-user.d.ts +7 -3
- package/dist/core/tools/ask-user.d.ts.map +1 -1
- package/dist/core/tools/ask-user.js +69 -17
- package/dist/core/tools/ask-user.js.map +1 -1
- package/dist/core/tools/index.d.ts +7 -3
- package/dist/core/tools/index.d.ts.map +1 -1
- package/docs/dashboard.md +113 -18
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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":"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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"]}
|
|
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
|
|
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.
|
|
619
|
-
IBM Plex Mono, JetBrains Mono,
|
|
620
|
-
(
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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.**
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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.
|