@dreb/coding-agent 2.45.4 → 2.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +10 -4
  2. package/dist/core/agent-session.d.ts +10 -2
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +94 -23
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/context-buffer.d.ts +2 -0
  7. package/dist/core/context-buffer.d.ts.map +1 -1
  8. package/dist/core/context-buffer.js +8 -0
  9. package/dist/core/context-buffer.js.map +1 -1
  10. package/dist/core/dispatch-arbiter.d.ts +94 -0
  11. package/dist/core/dispatch-arbiter.d.ts.map +1 -0
  12. package/dist/core/dispatch-arbiter.js +316 -0
  13. package/dist/core/dispatch-arbiter.js.map +1 -0
  14. package/dist/core/extensions/index.d.ts +1 -1
  15. package/dist/core/extensions/index.d.ts.map +1 -1
  16. package/dist/core/extensions/index.js.map +1 -1
  17. package/dist/core/extensions/runner.d.ts +7 -0
  18. package/dist/core/extensions/runner.d.ts.map +1 -1
  19. package/dist/core/extensions/runner.js +10 -0
  20. package/dist/core/extensions/runner.js.map +1 -1
  21. package/dist/core/extensions/types.d.ts +35 -0
  22. package/dist/core/extensions/types.d.ts.map +1 -1
  23. package/dist/core/extensions/types.js.map +1 -1
  24. package/dist/core/git-repo-state.d.ts +2 -0
  25. package/dist/core/git-repo-state.d.ts.map +1 -1
  26. package/dist/core/git-repo-state.js +19 -0
  27. package/dist/core/git-repo-state.js.map +1 -1
  28. package/dist/core/model-routing-guide.d.ts +15 -0
  29. package/dist/core/model-routing-guide.d.ts.map +1 -0
  30. package/dist/core/model-routing-guide.js +186 -0
  31. package/dist/core/model-routing-guide.js.map +1 -0
  32. package/dist/core/sdk.d.ts.map +1 -1
  33. package/dist/core/sdk.js +1 -0
  34. package/dist/core/sdk.js.map +1 -1
  35. package/dist/core/session-manager.d.ts.map +1 -1
  36. package/dist/core/session-manager.js +38 -2
  37. package/dist/core/session-manager.js.map +1 -1
  38. package/dist/core/settings-manager.d.ts +18 -0
  39. package/dist/core/settings-manager.d.ts.map +1 -1
  40. package/dist/core/settings-manager.js +33 -0
  41. package/dist/core/settings-manager.js.map +1 -1
  42. package/dist/core/thinking.d.ts +14 -0
  43. package/dist/core/thinking.d.ts.map +1 -1
  44. package/dist/core/thinking.js +35 -2
  45. package/dist/core/thinking.js.map +1 -1
  46. package/dist/core/tools/ask-user.d.ts +45 -0
  47. package/dist/core/tools/ask-user.d.ts.map +1 -0
  48. package/dist/core/tools/ask-user.js +210 -0
  49. package/dist/core/tools/ask-user.js.map +1 -0
  50. package/dist/core/tools/index.d.ts +18 -1
  51. package/dist/core/tools/index.d.ts.map +1 -1
  52. package/dist/core/tools/index.js +8 -0
  53. package/dist/core/tools/index.js.map +1 -1
  54. package/dist/core/tools/subagent.d.ts +53 -6
  55. package/dist/core/tools/subagent.d.ts.map +1 -1
  56. package/dist/core/tools/subagent.js +343 -53
  57. package/dist/core/tools/subagent.js.map +1 -1
  58. package/dist/modes/interactive/components/ask-user.d.ts +59 -0
  59. package/dist/modes/interactive/components/ask-user.d.ts.map +1 -0
  60. package/dist/modes/interactive/components/ask-user.js +240 -0
  61. package/dist/modes/interactive/components/ask-user.js.map +1 -0
  62. package/dist/modes/interactive/components/settings-selector.d.ts +4 -0
  63. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  64. package/dist/modes/interactive/components/settings-selector.js +99 -1
  65. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  66. package/dist/modes/interactive/interactive-mode.d.ts +13 -0
  67. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  68. package/dist/modes/interactive/interactive-mode.js +167 -6
  69. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  70. package/dist/modes/rpc/rpc-mode.d.ts +18 -3
  71. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  72. package/dist/modes/rpc/rpc-mode.js +177 -59
  73. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  74. package/dist/modes/rpc/rpc-types.d.ts +32 -0
  75. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  76. package/dist/modes/rpc/rpc-types.js.map +1 -1
  77. package/docs/agent-models.md +33 -1
  78. package/docs/dashboard.md +9 -3
  79. package/docs/extensions.md +33 -3
  80. package/docs/json.md +31 -5
  81. package/docs/rpc.md +105 -15
  82. package/docs/session.md +8 -2
  83. package/docs/settings.md +33 -1
  84. package/docs/skills.md +38 -0
  85. package/package.json +1 -1
  86. package/skills/model-routing-guide/SKILL.md +182 -0
@@ -9,7 +9,7 @@ Extensions are TypeScript modules that extend dreb's behavior. They can subscrib
9
9
  **Key capabilities:**
10
10
  - **Custom tools** - Register tools the LLM can call via `dreb.registerTool()`
11
11
  - **Event interception** - Block or modify tool calls, inject context, customize compaction
12
- - **User interaction** - Prompt users via `ctx.ui` (select, confirm, input, notify)
12
+ - **User interaction** - Prompt users via `ctx.ui` (select, confirm, input, ask, notify)
13
13
  - **Custom UI components** - Full TUI components with keyboard input via `ctx.ui.custom()` for complex interactions
14
14
  - **Custom commands** - Register commands like `/mycommand` via `dreb.registerCommand()`
15
15
  - **Session persistence** - Store state that survives restarts via `dreb.appendEntry()`
@@ -161,6 +161,26 @@ export default function (dreb: ExtensionAPI) {
161
161
  ctx.ui.notify("Done!", "success");
162
162
  ctx.ui.setStatus("my-ext", "Processing..."); // Footer status
163
163
  ctx.ui.setWidget("my-ext", ["Line 1", "Line 2"]); // Widget above editor (default)
164
+
165
+ // ctx.ui.ask — a rich clarifying question with options + free text,
166
+ // rendered natively in the TUI and Dashboard (and over RPC). Resolves to
167
+ // { selected: string[], customText?: string }. Dismissing or timing out an
168
+ // ask stops the current agent turn and resolves undefined. This is the same
169
+ // primitive that powers the built-in `ask_user` tool.
170
+ const answer = await ctx.ui.ask(
171
+ {
172
+ title: "Choose a database",
173
+ question: "Which persistence strategy should I use?",
174
+ options: ["SQLite", "PostgreSQL", "Keep the JSON file"],
175
+ allowFreeText: true, // default; offers a "type your own answer" field
176
+ multiSelect: false, // true → checkboxes, combined with any free text
177
+ multiline: false, // true → multi-line free-text area
178
+ },
179
+ { signal, timeout: 60000 }, // both optional; absent user never deadlocks
180
+ );
181
+ if (!answer) {
182
+ // The ask was dismissed or timed out; the current agent turn is stopping.
183
+ }
164
184
  });
165
185
 
166
186
  // Register tools, commands, shortcuts, flags
@@ -593,7 +613,7 @@ In the default parallel tool execution mode, sibling tool calls from the same as
593
613
  import { isToolCallEventType } from "@dreb/coding-agent";
594
614
 
595
615
  dreb.on("tool_call", async (event, ctx) => {
596
- // event.toolName - "bash", "read", "write", "edit", "grep", "find", "ls", "web_search", "web_fetch", "subagent", "wait", "search", "skill", "tasks_update", "suggest_next", or custom tool names
616
+ // event.toolName - "bash", "read", "write", "edit", "grep", "find", "ls", "web_search", "web_fetch", "subagent", "wait", "search", "ask_user", "skill", "tasks_update", "suggest_next", or custom tool names
597
617
  // event.toolCallId
598
618
  // event.input - tool parameters
599
619
 
@@ -1498,7 +1518,7 @@ async execute(toolCallId, params) {
1498
1518
 
1499
1519
  ### Overriding Built-in Tools
1500
1520
 
1501
- Extensions can override built-in tools (`read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`, `web_search`, `web_fetch`, `subagent`, `wait`, `search`) by registering a tool with the same name. Interactive mode displays a warning when this happens. The factory-only tools (`skill`, `tasks_update`, `suggest_next`) can also be overridden.
1521
+ Extensions can override built-in tools (`read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`, `web_search`, `web_fetch`, `subagent`, `wait`, `search`, `ask_user`) by registering a tool with the same name. Interactive mode displays a warning when this happens. The factory-only tools (`skill`, `tasks_update`, `suggest_next`) can also be overridden.
1502
1522
 
1503
1523
  ```bash
1504
1524
  # Extension's read tool replaces built-in read
@@ -1782,6 +1802,15 @@ const ok = await ctx.ui.confirm("Delete?", "This cannot be undone");
1782
1802
  // Text input
1783
1803
  const name = await ctx.ui.input("Name:", "placeholder");
1784
1804
 
1805
+ // Rich question: question text supports Markdown and options can be combined
1806
+ // with free text. Dismissal stops the current agent turn and returns undefined.
1807
+ const answer = await ctx.ui.ask({
1808
+ title: "Choose a database",
1809
+ question: "Which persistence strategy should I use?",
1810
+ options: ["SQLite", "PostgreSQL"],
1811
+ allowFreeText: true,
1812
+ });
1813
+
1785
1814
  // Multi-line editor
1786
1815
  const text = await ctx.ui.editor("Edit:", "prefilled text");
1787
1816
 
@@ -1812,6 +1841,7 @@ if (confirmed) {
1812
1841
  - `select()` returns `undefined`
1813
1842
  - `confirm()` returns `false`
1814
1843
  - `input()` returns `undefined`
1844
+ - `ask()` returns `undefined` and stops the current agent turn
1815
1845
 
1816
1846
  #### Manual Dismissal with AbortSignal
1817
1847
 
package/docs/json.md CHANGED
@@ -17,8 +17,12 @@ type AgentSessionEvent =
17
17
  | { type: "auto_compaction_end"; result: CompactionResult | undefined; aborted: boolean; willRetry: boolean; errorMessage?: string }
18
18
  | { type: "auto_retry_start"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string }
19
19
  | { type: "auto_retry_end"; success: boolean; attempt: number; finalError?: string }
20
- | { type: "background_agent_start"; agentId: string; agentType: string; taskSummary: string }
21
- | { type: "background_agent_end"; agentId: string; agentType: string; success: boolean }
20
+ | { type: "background_agent_start"; agentId: string; agentType: string; taskSummary: string; sessionDir?: string }
21
+ | { type: "subagent_arbitration"; agentId: string; status: "success" | "failure"; proposed: DispatchRoute; final: DispatchRoute | null; changed: ("agent" | "model" | "thinking")[]; step?: number; errorCode?: string; errorMessage?: string }
22
+ | { type: "background_agent_end"; agentId: string; agentType: string; success: boolean; model?: string; thinking?: ThinkingLevel; steps?: SubagentStepMetadata[]; sessionFile?: string }
23
+ | { type: "background_agent_event"; agentId: string; event: Record<string, unknown> }
24
+ | { type: "parent_paused_for_background_agents"; runningAgentCount: number; turnsUsed: number; turnLimit: number }
25
+ | { type: "session_name_changed"; name: string }
22
26
  | { type: "tasks_update"; tasks: readonly SessionTask[] }
23
27
  | { type: "suggest_next"; command: string };
24
28
 
@@ -28,6 +32,20 @@ interface SessionTask {
28
32
  title: string;
29
33
  status: "pending" | "in_progress" | "completed";
30
34
  }
35
+
36
+ interface SubagentStepMetadata {
37
+ step: number;
38
+ agent: string;
39
+ success: boolean;
40
+ model?: string; // canonical provider/model
41
+ thinking?: ThinkingLevel;
42
+ }
43
+
44
+ interface DispatchRoute {
45
+ agent: string;
46
+ model: string; // canonical provider/model
47
+ thinking: ThinkingLevel;
48
+ }
31
49
  ```
32
50
 
33
51
  Base events from [`AgentEvent`](https://github.com/aebrer/dreb/blob/master/packages/agent/src/types.ts):
@@ -35,7 +53,7 @@ Base events from [`AgentEvent`](https://github.com/aebrer/dreb/blob/master/packa
35
53
  ```typescript
36
54
  type AgentEvent =
37
55
  // Agent lifecycle
38
- | { type: "agent_start"; model?: { provider: string; id: string } }
56
+ | { type: "agent_start"; model?: { provider: string; id: string }; thinkingLevel?: ThinkingLevel }
39
57
  | { type: "agent_end"; messages: AgentMessage[] }
40
58
  // Turn lifecycle
41
59
  | { type: "turn_start" }
@@ -75,7 +93,7 @@ Each line is a JSON object. The first line is the session header (`version` is t
75
93
  Followed by events as they occur:
76
94
 
77
95
  ```json
78
- {"type":"agent_start","model":{"provider":"anthropic","id":"claude-sonnet-4-20250514"}}
96
+ {"type":"agent_start","model":{"provider":"anthropic","id":"claude-sonnet-4-20250514"},"thinkingLevel":"high"}
79
97
  {"type":"turn_start"}
80
98
  {"type":"message_start","message":{"role":"assistant","content":[],...}}
81
99
  {"type":"message_update","message":{...},"assistantMessageEvent":{"type":"text_delta","delta":"Hello",...}}
@@ -84,6 +102,14 @@ Followed by events as they occur:
84
102
  {"type":"agent_end","messages":[...]}
85
103
  ```
86
104
 
105
+ With global Dispatch Arbiter enabled, each background start is followed before child events/spawn by one safe structured decision. Unchanged decisions use an empty `changed`; failed decisions use `final: null`, prevent spawn, and carry only host-generated errors. Chain decisions also include `step`.
106
+
107
+ ```json
108
+ {"type":"subagent_arbitration","agentId":"a1b2c3","status":"success","proposed":{"agent":"Explore","model":"provider/frontier","thinking":"high"},"final":{"agent":"feature-dev","model":"provider/worker","thinking":"medium"},"changed":["agent","model","thinking"]}
109
+ ```
110
+
111
+ The event never contains the arbiter prompt, response, reasoning, guide, task, or conversation excerpt. The matching parent session persists the same safe fields as a non-context custom JSONL entry.
112
+
87
113
  ## Error handling
88
114
 
89
115
  Errors surface as events, not as broken JSON or exit codes:
@@ -104,7 +130,7 @@ Events are emitted in a predictable sequence:
104
130
 
105
131
  `message_update` events stream as chunks arrive — expect many per message. `tool_execution_update` events are optional (only emitted for tools that report progress).
106
132
 
107
- Auto-compaction and auto-retry events can appear between turns.
133
+ Auto-compaction and auto-retry events can appear between turns. Background lifecycle events are asynchronous: `background_agent_start` is emitted immediately, then an enabled `subagent_arbitration` record appears before that child's first `background_agent_event`; a failed arbitration is followed by `background_agent_end` without any child event.
108
134
 
109
135
  ## Example
110
136
 
package/docs/rpc.md CHANGED
@@ -224,17 +224,17 @@ The `model` field is a full [Model](#model) object or `null`. `scopedModels` is
224
224
 
225
225
  #### get_dashboard_snapshot
226
226
 
227
- Capture the dashboard-visible parent-session state, full parent transcript, and background-agent registry at one RPC command boundary. This is for authoritative recovery, not ordinary incremental refreshes.
227
+ Capture the dashboard-visible parent-session state, full parent transcript, background-agent registry, and blocking extension UI requests at one RPC command boundary. This is for authoritative recovery, not ordinary incremental refreshes.
228
228
 
229
229
  ```json
230
230
  {"id": "snapshot-7", "type": "get_dashboard_snapshot"}
231
231
  ```
232
232
 
233
- The `RpcDashboardSnapshot` result is a `snapshotId`, a complete `RpcSessionState` (including `tasks`), `messages`, and `backgroundAgents`. The RPC child writes a `RpcDashboardSnapshotBarrierEvent` to stdout **immediately before** the matching response line:
233
+ The `RpcDashboardSnapshot` result is a `snapshotId`, a complete `RpcSessionState` (including `tasks`), `messages`, `backgroundAgents`, and `pendingExtensionUiRequests`. The last field contains every blocking `select`, `confirm`, `input`, `editor`, or `ask` request still awaiting a host response, so a recovering Dashboard can restore the answer UI instead of leaving the runtime blocked. The RPC child writes a `RpcDashboardSnapshotBarrierEvent` to stdout **immediately before** the matching response line:
234
234
 
235
235
  ```json
236
236
  {"type":"dashboard_snapshot_barrier","snapshotId":"snapshot-7"}
237
- {"id":"snapshot-7","type":"response","command":"get_dashboard_snapshot","success":true,"data":{"snapshotId":"snapshot-7","state":{...},"messages":[...],"backgroundAgents":[...]}}
237
+ {"id":"snapshot-7","type":"response","command":"get_dashboard_snapshot","success":true,"data":{"snapshotId":"snapshot-7","state":{...},"messages":[...],"backgroundAgents":[...],"pendingExtensionUiRequests":[...]}}
238
238
  ```
239
239
 
240
240
  Stdout JSONL ordering is the contract: a relay records its current event-stream sequence when the marker arrives, before resolving the response, and pairs the snapshot only with that exact marker. The dashboard returns that captured sequence as `/api/resync.barrierSeq`; consumers discard queued events through it and replay only later events. The marker itself is not broadcast as another browser event, so one recovering client does not interrupt healthy clients. Do not infer ordering from request/response timing; see [dashboard recovery](dashboard.md#live-connection-and-recovery).
@@ -1243,17 +1243,18 @@ Note: with `summarize: true` the command is LLM-bound and can take a while. `Rpc
1243
1243
 
1244
1244
  ### Settings
1245
1245
 
1246
- Persistent settings, backed by the settings file (see [settings.md](settings.md)). They are normally distinct from live session state, with one security-policy exception:
1246
+ Persistent settings, backed by the settings file (see [settings.md](settings.md)). They are normally distinct from live session state, with global-only control/security-policy exceptions:
1247
1247
 
1248
1248
  - **Persistent defaults** (`get_settings` / `set_settings`): provider/model, thinking level, queue modes, compaction/retry/image/skill/thinking-display/transport toggles, and per-agent model fallback lists seed fresh runtimes. Writing these ordinary defaults does **not** change a running session.
1249
1249
  - **Global nested-context trust policy** (`autoLoadNestedContext`, `trustedContextFolders`, `effectiveTrustedContextRoots`, and the trust commands below): this is read from `~/.dreb/agent/settings.json` only, never project settings. Active main/subagent processes observe it for **future lazy nested/out-of-cwd loads**; it cannot remove content already injected into a conversation. It does not govern the separate initial upward context scan from the launch cwd.
1250
+ - **Global Dispatch Arbiter policy** (`subagentArbiter`): the complete object is read/written globally and project settings cannot shadow it. Enabled runtimes consume it before future subagent spawns; it does not rewrite already-started children.
1250
1251
  - **Runtime state** (`get_state` / `set_model` / `set_thinking_level` / `set_steering_mode` / `set_follow_up_mode` / `set_auto_compaction` / `set_auto_retry`): the state of the live session. Note that the runtime setters also persist their values as new defaults as a side effect.
1251
1252
 
1252
1253
  A dashboard settings tab typically reads `get_state` for what is active now and `get_settings` for persistent defaults plus the current global context-trust policy.
1253
1254
 
1254
1255
  #### get_settings
1255
1256
 
1256
- Get persistent settings. Before replying, RPC flushes pending settings writes, reloads durable global and project settings, and then reads the merged view; reopening dashboard Settings therefore sees external file edits. A pending write failure, unreadable file, parse error, or reload failure returns an explicit RPC error rather than a stale snapshot. Ordinary fields are the merged global + project view; the nested-context trust fields are always global-only.
1257
+ Get persistent settings. Before replying, RPC flushes pending settings writes, reloads durable global and project settings, and then reads the merged view; reopening dashboard Settings therefore sees external file edits. A pending write failure, unreadable file, parse error, or reload failure returns an explicit RPC error rather than a stale snapshot. Ordinary fields are the merged global + project view; nested-context trust and `subagentArbiter` are always global-only.
1257
1258
 
1258
1259
  ```json
1259
1260
  {"type": "get_settings"}
@@ -1283,6 +1284,12 @@ Response:
1283
1284
  "hideThinkingBlock": false,
1284
1285
  "agentModels": {
1285
1286
  "Explore": ["anthropic/sonnet", "openai/gpt-5"]
1287
+ },
1288
+ "subagentArbiter": {
1289
+ "enabled": true,
1290
+ "model": "anthropic/claude-sonnet-4-5",
1291
+ "thinking": "medium",
1292
+ "guidePath": "~/.dreb/agent/model-routing-guide.md"
1286
1293
  }
1287
1294
  }
1288
1295
  }
@@ -1292,6 +1299,8 @@ Response:
1292
1299
 
1293
1300
  `trustedContextFolders` is the raw global configured list, including invalid legacy paths that are ignored fail-closed. `effectiveTrustedContextRoots` is the canonical, existing root set actually enforced after `~` expansion, native `realpath`, deduplication, and ancestor subsumption. `autoLoadNestedContext` defaults to `false`; when `true` it is global expert trust-all for every resolvable target, not a project override. Project `.dreb/settings.json` cannot affect any of these three fields.
1294
1301
 
1302
+ `subagentArbiter` is absent when unconfigured. It is always the global object; project `.dreb/settings.json` cannot enable, disable, or alter it.
1303
+
1295
1304
  #### set_settings
1296
1305
 
1297
1306
  Update persistent default settings. Takes a partial payload — only the supplied keys change. The whole payload is validated before anything is applied: on any invalid field, nothing changes and the response is an explicit error. Writes target the global settings file (same scope as every runtime setter).
@@ -1330,6 +1339,14 @@ Setting per-agent model fallback lists:
1330
1339
 
1331
1340
  For `agentModels`, a non-empty array writes the global fallback list for that agent. An empty array removes the global entry, so that agent uses its agent-definition default unless a project-level override exists.
1332
1341
 
1342
+ Replace the complete global-only Dispatch Arbiter policy (exact model is validated; explicit thinking is capability-validated):
1343
+
1344
+ ```json
1345
+ {"type":"set_settings","settings":{"subagentArbiter":{"enabled":true,"model":"anthropic/claude-sonnet-4-5","thinking":"medium","guidePath":"~/.dreb/agent/model-routing-guide.md"}}}
1346
+ ```
1347
+
1348
+ Set `subagentArbiter: null` to remove the global policy. Enabling requires `model`; `guidePath` defaults to the standard guide path and omitted thinking runs the arbiter call with thinking off. Runtime guide/scope validation still occurs at each spawn because the live explicit scope can change.
1349
+
1333
1350
  Response is the full settings snapshot after the write (same shape as `get_settings`), plus `warnings` when the write was accepted but a project-level override shadows part of it:
1334
1351
 
1335
1352
  ```json
@@ -1406,6 +1423,7 @@ Valid keys and values:
1406
1423
  | `transport` | `"sse"`, `"websocket"`, `"auto"` |
1407
1424
  | `hideThinkingBlock` | boolean |
1408
1425
  | `agentModels` | Plain object mapping agent names to arrays of non-empty model id strings; empty arrays remove the global entry for that agent |
1426
+ | `subagentArbiter` | Complete global-only object or `null`. Keys: `enabled` boolean, exact available `model`, optional valid/capability-supported `thinking`, non-empty `guidePath`. Enabling requires `model`. Unknown nested keys are rejected. |
1409
1427
 
1410
1428
  Errors are explicit `success: false` responses (nothing is applied on any of them):
1411
1429
 
@@ -1419,6 +1437,7 @@ Errors are explicit `success: false` responses (nothing is applied on any of the
1419
1437
  - Invalid trusted-root list: `trustedContextFolders must be an array of non-empty path strings` or `Invalid trustedContextFolders[0]: path must be absolute after ~ expansion` / `path must be an existing directory`
1420
1438
  - Provider without model (or vice versa): `defaultProvider and defaultModel must be set together`
1421
1439
  - Unavailable model: `Model not found: provider/model-id`
1440
+ - Invalid arbiter policy: `Enabling subagentArbiter requires an exact provider/model`, `Arbiter model not found: ...`, or a nested-key/type/thinking capability error
1422
1441
  - Corrupt settings file: `Cannot write settings: the global settings file failed to load (fix or remove the corrupt settings.json first)` — without this guard the write would silently no-op
1423
1442
  - Write failure (I/O error): `Failed to persist settings: ...`
1424
1443
 
@@ -1584,7 +1603,7 @@ Response:
1584
1603
 
1585
1604
  | Event | Description |
1586
1605
  |-------|-------------|
1587
- | `agent_start` | Agent begins processing |
1606
+ | `agent_start` | Agent begins processing (resolved model and effective thinking level) |
1588
1607
  | `agent_end` | Agent completes (includes all generated messages) |
1589
1608
  | `turn_start` | New turn begins |
1590
1609
  | `turn_end` | Turn completes (includes assistant message and tool results) |
@@ -1601,7 +1620,7 @@ Response:
1601
1620
  | `auto_retry_start` | Auto-retry begins (after transient error) |
1602
1621
  | `auto_retry_end` | Auto-retry completes (success or final failure) |
1603
1622
  | `background_agent_start` | Background subagent launched (includes `sessionDir`) |
1604
- | `background_agent_end` | Background subagent finished (includes `sessionFile` when known) |
1623
+ | `background_agent_end` | Background subagent finished (canonical model/thinking, per-step chain metadata, and `sessionFile` when known) |
1605
1624
  | `background_agent_event` | Relayed event from a background subagent's own stream |
1606
1625
  | `parent_paused_for_background_agents` | Parent paused waiting on background agents |
1607
1626
  | `session_name_changed` | Session display name changed (manual rename, extension rename, or auto-title) |
@@ -1615,10 +1634,10 @@ rather than validating against a closed list; new event types may be added.
1615
1634
 
1616
1635
  ### agent_start
1617
1636
 
1618
- Emitted when the agent begins processing a prompt. Includes the resolved model.
1637
+ Emitted when the agent begins processing a prompt. Includes the resolved model and effective thinking level sent to the provider.
1619
1638
 
1620
1639
  ```json
1621
- {"type": "agent_start", "model": {"provider": "anthropic", "id": "claude-sonnet-4-20250514"}}
1640
+ {"type": "agent_start", "model": {"provider": "anthropic", "id": "claude-sonnet-4-20250514"}, "thinkingLevel": "high"}
1622
1641
  ```
1623
1642
 
1624
1643
  ### agent_end
@@ -1806,9 +1825,9 @@ On final failure (max retries exceeded):
1806
1825
  }
1807
1826
  ```
1808
1827
 
1809
- ### background_agent_start / background_agent_end / background_agent_event
1828
+ ### background_agent_start / subagent_arbitration / background_agent_end / background_agent_event
1810
1829
 
1811
- Lifecycle and live-observability events for background subagents (the `subagent` tool's background mode).
1830
+ Lifecycle, pre-spawn routing, and live-observability events for background subagents (the `subagent` tool's background mode).
1812
1831
 
1813
1832
  `background_agent_start` fires at launch. `sessionDir` is the directory the child will write its session JSONL into (per-launch, known before spawn):
1814
1833
 
@@ -1822,7 +1841,22 @@ Lifecycle and live-observability events for background subagents (the `subagent`
1822
1841
  }
1823
1842
  ```
1824
1843
 
1825
- `background_agent_end` fires after the result is delivered to the parent agent. `sessionFile` is the child's session JSONL path when one was written:
1844
+ When the global Dispatch Arbiter is enabled, `subagent_arbitration` fires after the requested route is made concrete and before child spawn/events. It appears for changed and unchanged successful decisions, and for failures that prevent spawn. Chain records include `step`; failures have `final: null` and bounded host-generated `errorCode`/`errorMessage`.
1845
+
1846
+ ```json
1847
+ {
1848
+ "type": "subagent_arbitration",
1849
+ "agentId": "a1b2c3d4e5f6",
1850
+ "status": "success",
1851
+ "proposed": {"agent": "Explore", "model": "provider/frontier", "thinking": "high"},
1852
+ "final": {"agent": "feature-dev", "model": "provider/worker", "thinking": "medium"},
1853
+ "changed": ["agent", "model", "thinking"]
1854
+ }
1855
+ ```
1856
+
1857
+ The event is deliberately safe and programmatic: it never contains the task, guide, conversation context, prompt, raw response, or model reasoning. The parent session persists the same fields as a non-context `custom` entry. Consumers should update displayed background-agent identity from `final.agent` before child events arrive.
1858
+
1859
+ `background_agent_end` fires after the result is delivered to the parent agent. For a single child it includes the canonical resolved `provider/model` and effective thinking level when reported; `sessionFile` is the child's session JSONL path when one was written:
1826
1860
 
1827
1861
  ```json
1828
1862
  {
@@ -1830,10 +1864,27 @@ Lifecycle and live-observability events for background subagents (the `subagent`
1830
1864
  "agentId": "a1b2c3d4e5f6",
1831
1865
  "agentType": "Explore",
1832
1866
  "success": true,
1867
+ "model": "anthropic/claude-sonnet-4-20250514",
1868
+ "thinking": "medium",
1833
1869
  "sessionFile": "/home/user/.dreb/agent/subagent-sessions/a1b2c3d4e5f6/2026-07-07T12-00-00-000Z_uuid.jsonl"
1834
1870
  }
1835
1871
  ```
1836
1872
 
1873
+ Chain completions omit ambiguous scalar model/thinking fields and instead include ordered per-step metadata, since steps may use different agents, providers, models, or thinking levels:
1874
+
1875
+ ```json
1876
+ {
1877
+ "type": "background_agent_end",
1878
+ "agentId": "a1b2c3d4e5f6",
1879
+ "agentType": "Explore",
1880
+ "success": true,
1881
+ "steps": [
1882
+ {"step": 1, "agent": "Explore", "success": true, "model": "anthropic/claude-sonnet-4-6", "thinking": "low"},
1883
+ {"step": 2, "agent": "feature-dev", "success": true, "model": "openai/gpt-5.6-sol", "thinking": "high"}
1884
+ ]
1885
+ }
1886
+ ```
1887
+
1837
1888
  `background_agent_event` relays every JSONL event the child process emits (the same event union documented here, plus the initial session header), verbatim, tagged with the child's `agentId`. This is the live-transcript transport for observers like the dashboard — no session-file tailing needed. Streaming children emit `message_update` deltas at high frequency; consumers that fan events out further (e.g. over a network) should batch or throttle:
1838
1889
 
1839
1890
  ```json
@@ -1874,7 +1925,7 @@ Extensions can request user interaction via `ctx.ui.select()`, `ctx.ui.confirm()
1874
1925
 
1875
1926
  There are two categories of extension UI methods:
1876
1927
 
1877
- - **Dialog methods** (`select`, `confirm`, `input`, `editor`): emit an `extension_ui_request` on stdout and block until the client sends back an `extension_ui_response` on stdin with the matching `id`.
1928
+ - **Dialog methods** (`select`, `confirm`, `input`, `editor`, `ask`): emit an `extension_ui_request` on stdout and block until the client sends back an `extension_ui_response` on stdin with the matching `id`.
1878
1929
  - **Fire-and-forget methods** (`notify`, `setStatus`, `setWidget`, `setTitle`, `set_editor_text`): emit an `extension_ui_request` on stdout but do not expect a response. The client can display the information or ignore it.
1879
1930
 
1880
1931
  If a dialog method includes a `timeout` field, the agent-side will auto-resolve with a default value when the timeout expires. The client does not need to track timeouts.
@@ -1961,6 +2012,34 @@ Open a multi-line text editor with optional prefilled content.
1961
2012
 
1962
2013
  Expected response: `extension_ui_response` with `value` (the edited text) or `cancelled: true`.
1963
2014
 
2015
+ #### ask
2016
+
2017
+ Ask the user a rich clarifying question with Markdown-formatted question text, optional single- or multi-select options, and an optional free-text field. This powers the built-in `ask_user` tool. `options` (2-4 nonblank strings) is optional; `allowFreeText` (default `true`), `multiSelect`, and `multiline` are optional booleans.
2018
+
2019
+ ```json
2020
+ {
2021
+ "type": "extension_ui_request",
2022
+ "id": "uuid-5",
2023
+ "method": "ask",
2024
+ "title": "Choose a database",
2025
+ "question": "Which persistence strategy should I use?",
2026
+ "options": ["SQLite", "PostgreSQL", "Keep the JSON file"],
2027
+ "allowFreeText": true,
2028
+ "multiSelect": false,
2029
+ "multiline": false,
2030
+ "timeout": 60000,
2031
+ "expiresAt": 1785434460000
2032
+ }
2033
+ ```
2034
+
2035
+ `timeout` is the original duration in milliseconds. `expiresAt` is the corresponding absolute Unix timestamp in milliseconds; Dashboard clients should use it for the visible countdown so reload, resync, or drill-in recovery does not restart the full duration.
2036
+
2037
+ Expected response: `extension_ui_response` with `selected` (an array of strings) and optional string `customText` (the typed answer). Sending `cancelled: true`, or an empty `selected` with no nonblank `customText`, stops the current agent turn rather than continuing without an answer. A timeout has the same stop semantics. Other malformed ask responses are rejected as protocol failures.
2038
+
2039
+ ```json
2040
+ { "type": "extension_ui_response", "id": "uuid-5", "selected": ["SQLite"], "customText": "with WAL enabled" }
2041
+ ```
2042
+
1964
2043
  #### notify
1965
2044
 
1966
2045
  Display a notification. Fire-and-forget, no response expected.
@@ -2038,7 +2117,7 @@ Set the text in the input editor. Fire-and-forget.
2038
2117
 
2039
2118
  ### Extension UI Responses (stdin)
2040
2119
 
2041
- Responses are sent for dialog methods only (`select`, `confirm`, `input`, `editor`). The `id` must match the request.
2120
+ Responses are sent for dialog methods only (`select`, `confirm`, `input`, `editor`, `ask`). The `id` must match the request.
2042
2121
 
2043
2122
  #### Value response (select, input, editor)
2044
2123
 
@@ -2054,12 +2133,23 @@ Responses are sent for dialog methods only (`select`, `confirm`, `input`, `edito
2054
2133
 
2055
2134
  #### Cancellation response (any dialog)
2056
2135
 
2057
- Dismiss any dialog method. The extension receives `undefined` (for select/input/editor) or `false` (for confirm).
2136
+ Dismiss any dialog method. The extension receives `undefined` (for select/input/editor) or `false` (for confirm). For `ask`, cancellation returns `undefined` while stopping the current agent turn.
2058
2137
 
2059
2138
  ```json
2060
2139
  {"type": "extension_ui_response", "id": "uuid-3", "cancelled": true}
2061
2140
  ```
2062
2141
 
2142
+ After any dialog settles, RPC emits a lifecycle event on stdout so hosts can
2143
+ remove the matching UI even when the dialog ended locally because of timeout,
2144
+ abort, or runtime shutdown:
2145
+
2146
+ ```json
2147
+ {"type": "extension_ui_response_handled", "id": "uuid-3"}
2148
+ ```
2149
+
2150
+ Hosts should treat this event as idempotent; it can arrive after the host has
2151
+ already removed a successfully answered request.
2152
+
2063
2153
  ## Error Handling
2064
2154
 
2065
2155
  Failed commands return a response with `success: false`:
package/docs/session.md CHANGED
@@ -261,7 +261,13 @@ Extension state persistence. Does NOT participate in LLM context.
261
261
  {"type":"custom","id":"h8i9j0k1","parentId":"g7h8i9j0","timestamp":"2024-12-03T14:20:00.000Z","customType":"my-extension","data":{"count":42}}
262
262
  ```
263
263
 
264
- Use `customType` to identify your extension's entries on reload.
264
+ Use `customType` to identify your extension's entries on reload. Core host features can use the same non-context channel. When the Dispatch Arbiter is enabled, every attempted pre-spawn decision is stored as `customType: "subagent_arbitration"` with only safe host-validated metadata:
265
+
266
+ ```json
267
+ {"type":"custom","customType":"subagent_arbitration","data":{"type":"subagent_arbitration","agentId":"a1b2c3","status":"success","proposed":{"agent":"Explore","model":"provider/frontier","thinking":"high"},"final":{"agent":"feature-dev","model":"provider/worker","thinking":"medium"},"changed":["agent","model","thinking"]}}
268
+ ```
269
+
270
+ Failure records have `final: null` plus bounded host-generated `errorCode`/`errorMessage`; chain records include `step`. Arbiter prompts, raw responses, and reasoning are never stored. Because this is `custom`, replay/build-context skips it and neither the parent nor child model receives the record.
265
271
 
266
272
  ### CustomMessageEntry
267
273
 
@@ -382,7 +388,7 @@ Key methods for working with sessions programmatically.
382
388
  ### Instance Methods - Session Management
383
389
  - `newSession(options?)` - Start a new session (options: `{ parentSession?: string }`)
384
390
  - `setSessionFile(path)` - Switch to a different session file
385
- - `createBranchedSession(leafId)` - Extract branch to new session file
391
+ - `createBranchedSession(leafId)` - Extract a branch to a new session file while preserving the current session name metadata
386
392
 
387
393
  ### Instance Methods - Appending (all return entry ID)
388
394
  - `appendMessage(message)` - Add message
package/docs/settings.md CHANGED
@@ -224,6 +224,38 @@ Set `parentTurnGuardrail` to `false` to let the parent keep running with no turn
224
224
  }
225
225
  ```
226
226
 
227
+ ### Dispatch Arbiter
228
+
229
+ The optional Dispatch Arbiter is a fully headless, tool-less model call in the subagent control path. It is disabled by default and can be configured **only** in global `~/.dreb/agent/settings.json` (or the global RPC settings API). A project `.dreb/settings.json` cannot enable, disable, or reconfigure it.
230
+
231
+ | Setting | Type | Default | Description |
232
+ |---------|------|---------|-------------|
233
+ | `subagentArbiter.enabled` | boolean | `false` | Run fail-closed arbitration before every actual child spawn |
234
+ | `subagentArbiter.model` | string | - | Required when enabled; exact canonical `provider/model` used for the direct arbiter call |
235
+ | `subagentArbiter.thinking` | string | `off` | Optional arbiter-call thinking: `off`, `minimal`, `low`, `medium`, `high`, or `xhigh`; explicit values are capability-validated |
236
+ | `subagentArbiter.guidePath` | string | `~/.dreb/agent/model-routing-guide.md` | Routing guide generated by `/skill:model-routing-guide`; `~` expands normally and relative paths resolve against the child cwd |
237
+
238
+ ```json
239
+ {
240
+ "subagentArbiter": {
241
+ "enabled": true,
242
+ "model": "provider/router-model",
243
+ "thinking": "medium",
244
+ "guidePath": "~/.dreb/agent/model-routing-guide.md"
245
+ }
246
+ }
247
+ ```
248
+
249
+ First-class controls are available in both interactive `/settings` and the web dashboard Settings screen. Each exposes enable/disable, an authenticated exact-model picker, thinking level, and guide path. The TUI validates current live scope and guide coverage before accepting enablement; the dashboard prevents model-less enablement, validates model/thinking through RPC, shows readiness guidance, and the runtime still revalidates live scope/guide before every dispatch. Validation errors are loud and do not silently persist an unusable enabled policy.
250
+
251
+ Enabling also requires a non-empty **live explicit session model scope** from `--models` or `enabledModels`, plus a schema-valid guide whose canonical covered IDs and `## Model:` sections exactly match that live scope. The research skill cannot discover runtime `--models`; generate its guide with matching explicit skill arguments when needed.
252
+
253
+ For each single child, parallel item, and post-substitution chain step, the arbiter may return only an existing agent name, an exact model in the live scope, and a thinking level supported by that model. Missing/invalid settings, auth, guide, scope, timeout/provider errors, malformed output, unknown agents, out-of-scope models, and unsupported thinking all stop the affected spawn. There is no fallback to the proposed route or parent model after arbitration is enabled.
254
+
255
+ The arbiter receives the immutable child task/cwd, proposed route, available agent names/descriptions/effective tools/model defaults, validated guide, canonical live candidates, bounded first/latest user intent and recent labeled parent activity—including bounded tool outputs—parent model/session title, repository/cwd/branch/dirty-count metadata, and lineage identifiers where available. Following the title setter's rolling-context pattern, ordinary file contents, diffs, command output, and other useful tool-result content are intentional routing context rather than a separate security boundary; the package receives the existing secret scrubbing before inference. The arbiter itself receives no tools. Guide files are capped at 128 KiB; the complete serialized package is capped at 180,000 characters. Individual intent/description/activity fields also have fixed bounds. Required task/guide/scope/agent data is never silently truncated—exceeding the package cap stops inference. The real child's task and cwd are not modified by scrubbing or arbitration.
256
+
257
+ Only host-validated decision metadata is persisted/emitted. Raw arbiter prompts, responses, and reasoning never enter the parent transcript, child context, session history, JSON/RPC events, or dashboard. See [Agent Model Settings](agent-models.md#dispatch-arbiter).
258
+
227
259
  ### Message Delivery
228
260
 
229
261
  | Setting | Type | Default | Description |
@@ -385,7 +417,7 @@ See [packages.md](packages.md) for package management details.
385
417
 
386
418
  ## Project Overrides
387
419
 
388
- Project settings (`.dreb/settings.json`) override global settings. Nested objects are merged. **Exception:** `context.trustedFolders` and `context.autoLoadNested` are global-only security policy: project settings cannot add, replace, override, or enable either one. Nested context from the initial startup upward scan remains separate from this lazy-load policy.
420
+ Project settings (`.dreb/settings.json`) override global settings. Nested objects are merged. **Exceptions:** `context.trustedFolders`, `context.autoLoadNested`, and the complete `subagentArbiter` policy are global-only security/control-path settings. Project settings cannot add, replace, override, enable, or disable them. Nested context from the initial startup upward scan remains separate from the lazy-load policy.
389
421
 
390
422
  ```json
391
423
  // ~/.dreb/agent/settings.json (global)
package/docs/skills.md CHANGED
@@ -285,11 +285,49 @@ dreb ships with **mach6**, a development workflow that orchestrates the full iss
285
285
  | `mach6-review` | Explicitly user-triggered multi-agent review with scope-aware independent assessment |
286
286
  | `mach6-implement` | Implement plans, fix review findings, or fix CI failures |
287
287
  | `mach6-publish` | Pre-merge checks, docs update, merge, tag, release |
288
+ | `model-routing-guide` | Research scoped models and sanitized local subagent evidence into a validated routing guide |
288
289
 
289
290
  Built-in skills are always available and can be overridden by placing a skill with the same name in any [user or project location](#locations). `mach6-review` is model-invocable when the user directly asks an agent to run it, as well as user-invocable through its slash command. Agents must never start formal review autonomously; implementation must first be committed and pushed.
290
291
 
291
292
  See [docs/mach6.md](mach6.md) for full documentation.
292
293
 
294
+ ### model-routing-guide
295
+
296
+ `model-routing-guide` is an explicit, potentially expensive research workflow with exactly two supported scope sources.
297
+
298
+ Pass comma-separated model patterns directly to make them authoritative:
299
+
300
+ ```text
301
+ /skill:model-routing-guide anthropic/claude-*,openai/gpt-5.6-sol
302
+ ```
303
+
304
+ Or invoke it without arguments to use the effective non-empty `enabledModels` array:
305
+
306
+ ```text
307
+ /skill:model-routing-guide
308
+ ```
309
+
310
+ When model scope rotates, update the existing guide incrementally instead of regenerating every retained entry:
311
+
312
+ ```text
313
+ /skill:model-routing-guide update
314
+ /skill:model-routing-guide update anthropic/claude-*,openai/gpt-5.6-sol
315
+ ```
316
+
317
+ `update` must be the first argument. The skill validates the existing guide against its own coverage, diffs that coverage against the newly resolved authoritative scope, preserves retained model sections, removes unscoped sections and summary references, and researches only newly added canonical models. It refreshes scope/local-evidence metadata and performs the same complete final validation before atomically replacing the guide. A missing or malformed existing guide fails loudly and requires normal generation.
318
+
319
+ Model-pattern arguments after the optional `update` keyword take precedence over `enabledModels`; the skill selects one source and does not search for any other runtime or session scope. Because this Stage 1 workflow is implemented only as a skill, it cannot discover the current session's `--models` value or later in-session scope changes. To research that runtime scope, pass the same comma-separated patterns as skill arguments.
320
+
321
+ The skill refuses missing/empty and effectively all-model scopes. It obtains available candidates with `dreb --list-models`, resolves the selected patterns to canonical provider/model IDs, snapshots existing files under `~/.dreb/agent/subagent-sessions/`, and combines sanitized aggregate local observations with official documentation, model cards, benchmarks, issue trackers, forums, and practitioner reports. Existing unreadable or malformed child logs fail the run; a genuinely empty history is labeled cold-start.
322
+
323
+ The generated `~/.dreb/agent/model-routing-guide.md` is human-readable Markdown with schema-versioned YAML frontmatter and one validated section per canonical candidate. Evidence is labeled as vendor claims, measured benchmarks, community reports, or local observations, with dates, confidence, sample counts, contrary findings, and explicit unknowns. The workflow prohibits copied prompts, outputs, tool arguments, secrets, paths, and identifying project details.
324
+
325
+ Semantic local-evidence assessment uses normal dreb tools, so inspected session content is processed by the active research model's configured provider. The persisted guide is sanitized and generalized, but invoking the skill is still a decision to send the inspected evidence to that provider.
326
+
327
+ Its primary routing safeguards are practical: `Explore` is for factual collection and navigation, not planning or implementation; routine lookup, extraction, repetitive file inspection, and straightforward summarization should use the least expensive scoped model demonstrated adequate by the evidence.
328
+
329
+ The optional global-only [Dispatch Arbiter](agent-models.md#dispatch-arbiter) now consumes this file before every subagent spawn. Its live scope is the current session's exact explicit candidate set, so guide frontmatter and model headings must match that set exactly. A guide generated from `enabledModels` will work when the session uses the same resolved scope; for runtime `--models`, pass those patterns to the skill. Missing/stale/malformed guide coverage fails the child launch rather than bypassing arbitration.
330
+
293
331
  ## Skill Repositories
294
332
 
295
333
  - [Anthropic Skills](https://github.com/anthropics/skills) - Document processing (docx, pdf, pptx, xlsx), web development
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dreb/coding-agent",
3
- "version": "2.45.4",
3
+ "version": "2.47.0",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "drebConfig": {