@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
@@ -22,11 +22,13 @@ import { sleep } from "../utils/sleep.js";
22
22
  import { executeBash as executeBashCommand, executeBashWithOperations } from "./bash-executor.js";
23
23
  import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
24
24
  import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
25
+ import { DispatchArbiter } from "./dispatch-arbiter.js";
25
26
  import { exportSessionToHtml } from "./export-html/index.js";
26
27
  import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
27
28
  import { ExtensionRunner, wrapRegisteredTools, } from "./extensions/index.js";
28
29
  import { checkScriptContent, extractScriptPaths, isForbiddenCommand } from "./forbidden-commands.js";
29
- import { getGitRepoState } from "./git-repo-state.js";
30
+ import { getGitRepoState, getGitStatusMetadata } from "./git-repo-state.js";
31
+ import { findGitRoot } from "./git-root.js";
30
32
  import { log } from "./logger.js";
31
33
  import { computeNestedContextBlock } from "./nested-context.js";
32
34
  import { PerformanceTracker } from "./performance-tracker.js";
@@ -153,6 +155,8 @@ export class AgentSession {
153
155
  _ownsPerformanceTracker;
154
156
  // Git repo state captured once at session start
155
157
  _gitRepoState;
158
+ // Fully headless pre-spawn router. It owns only bounded parent activity context.
159
+ _dispatchArbiter;
156
160
  constructor(config) {
157
161
  this.agent = config.agent;
158
162
  this.sessionManager = config.sessionManager;
@@ -181,6 +185,29 @@ export class AgentSession {
181
185
  this._uiType = config.uiType;
182
186
  // Capture git repo state once at session start (before building runtime/system prompt)
183
187
  this._gitRepoState = getGitRepoState(this._cwd) ?? undefined;
188
+ this._dispatchArbiter = new DispatchArbiter({
189
+ getSettings: () => this.settingsManager.getGlobalSubagentArbiterSettings(),
190
+ getCandidateModels: () => this._scopedModels,
191
+ getModelRegistry: () => this._modelRegistry,
192
+ getMessages: () => this.agent.state.messages,
193
+ getParentModel: () => this.model,
194
+ getSessionTitle: () => this.sessionName,
195
+ getRepoMetadata: (cwd) => {
196
+ const gitRoot = findGitRoot(cwd);
197
+ const isSessionCwd = resolve(cwd) === resolve(this._cwd);
198
+ const currentStatus = gitRoot
199
+ ? (getGitStatusMetadata(cwd) ?? (isSessionCwd ? this._gitRepoState : undefined))
200
+ : undefined;
201
+ return {
202
+ repo: gitRoot ? basename(gitRoot) : undefined,
203
+ cwd,
204
+ branch: currentStatus?.branch,
205
+ dirtyCount: currentStatus?.dirtyCount,
206
+ };
207
+ },
208
+ getExtraSecretPatterns: () => this._compileExtraSecretPatterns(),
209
+ complete: config.dispatchArbiterComplete,
210
+ });
184
211
  // Always subscribe to agent events for internal handling
185
212
  // (session persistence, extensions, auto-compaction, retry logic)
186
213
  this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
@@ -199,6 +226,21 @@ export class AgentSession {
199
226
  getPerformanceTracker() {
200
227
  return this.performanceTracker;
201
228
  }
229
+ _compileExtraSecretPatterns() {
230
+ return this.settingsManager.getSecretOutputPatterns()?.flatMap((pattern) => {
231
+ if (!pattern.pattern || typeof pattern.pattern !== "string" || pattern.pattern.trim() === "") {
232
+ console.warn(`[secret-scrubber] Skipping empty or invalid pattern in secretOutputPatterns: "${pattern.name}"`);
233
+ return [];
234
+ }
235
+ try {
236
+ return [{ name: pattern.name, pattern: new RegExp(pattern.pattern, "g") }];
237
+ }
238
+ catch (error) {
239
+ console.warn(`[secret-scrubber] Skipping invalid regex in secretOutputPatterns "${pattern.name}": ${pattern.pattern} — ${error instanceof Error ? error.message : String(error)}`);
240
+ return [];
241
+ }
242
+ });
243
+ }
202
244
  /**
203
245
  * Install tool hooks once on the Agent instance.
204
246
  *
@@ -285,20 +327,7 @@ export class AgentSession {
285
327
  this.agent.setAfterToolCall(async ({ toolCall, args, result, isError }) => {
286
328
  // Scrub secrets from tool output — runs before extensions, cannot be bypassed
287
329
  let scrubbedContent = result.content;
288
- const extraSecretPatterns = this.settingsManager?.getSecretOutputPatterns();
289
- const compiledExtras = extraSecretPatterns?.flatMap((p) => {
290
- if (!p.pattern || typeof p.pattern !== "string" || p.pattern.trim() === "") {
291
- console.warn(`[secret-scrubber] Skipping empty or invalid pattern in secretOutputPatterns: "${p.name}"`);
292
- return [];
293
- }
294
- try {
295
- return [{ name: p.name, pattern: new RegExp(p.pattern, "g") }];
296
- }
297
- catch (err) {
298
- console.warn(`[secret-scrubber] Skipping invalid regex in secretOutputPatterns "${p.name}": ${p.pattern} — ${err instanceof Error ? err.message : String(err)}`);
299
- return [];
300
- }
301
- });
330
+ const compiledExtras = this._compileExtraSecretPatterns();
302
331
  let totalRedactions = 0;
303
332
  scrubbedContent = scrubbedContent.map((item) => {
304
333
  if (item.type === "text" && item.text) {
@@ -513,6 +542,15 @@ export class AgentSession {
513
542
  */
514
543
  _handleBackgroundComplete(agentId, result, cancelled) {
515
544
  const parts = [];
545
+ if (result.model || result.thinking) {
546
+ const metadata = [
547
+ result.model ? `model: ${result.model}` : undefined,
548
+ result.thinking ? `thinking: ${result.thinking}` : undefined,
549
+ ]
550
+ .filter((value) => value !== undefined)
551
+ .join(", ");
552
+ parts.push(`Execution metadata: ${metadata}`);
553
+ }
516
554
  if (cancelled) {
517
555
  parts.push("This agent was cancelled by the user.");
518
556
  }
@@ -585,6 +623,9 @@ export class AgentSession {
585
623
  agentId,
586
624
  agentType: result.agent,
587
625
  success: result.exitCode === 0,
626
+ model: result.model,
627
+ thinking: result.thinking,
628
+ steps: result.steps,
588
629
  sessionFile: result.sessionFile,
589
630
  });
590
631
  }
@@ -738,6 +779,17 @@ export class AgentSession {
738
779
  await this._emitExtensionEvent(event);
739
780
  // Notify all listeners
740
781
  this._emit(event);
782
+ // Keep a small, mode-independent rolling context for the headless arbiter.
783
+ if (event.type === "message_end") {
784
+ this._dispatchArbiter.onMessageEnd(event.message);
785
+ }
786
+ else if (event.type === "tool_execution_end") {
787
+ this._dispatchArbiter.onToolEnd({
788
+ toolName: event.toolName,
789
+ isError: event.isError,
790
+ result: event.result,
791
+ });
792
+ }
741
793
  // Handle session persistence
742
794
  if (event.type === "message_end") {
743
795
  // Check if this is a custom message from extensions
@@ -831,7 +883,12 @@ export class AgentSession {
831
883
  }
832
884
  /** Emit extension events based on agent events */
833
885
  async _emitExtensionEvent(event) {
834
- if (!this._extensionRunner)
886
+ // The runner is created unconditionally (so built-in tools like ask_user
887
+ // always have a UI context). When no extensions are loaded there are no
888
+ // handlers to invoke — return synchronously to avoid inserting an extra
889
+ // await tick per event, which would otherwise delay the final agent_end
890
+ // emission past when prompt() resolves.
891
+ if (!this._extensionRunner || !this._extensionRunner.hasExtensions)
835
892
  return;
836
893
  if (event.type === "agent_start") {
837
894
  this._turnIndex = 0;
@@ -1587,6 +1644,7 @@ export class AgentSession {
1587
1644
  this._disconnectFromAgent();
1588
1645
  await this.abort();
1589
1646
  this.agent.reset();
1647
+ this._dispatchArbiter.clearContext();
1590
1648
  this.sessionManager.newSession({ parentSession: options?.parentSession });
1591
1649
  this.agent.sessionId = this.sessionManager.getSessionId();
1592
1650
  this._steeringMessages = [];
@@ -2390,9 +2448,15 @@ export class AgentSession {
2390
2448
  const wrappedExtensionTools = this._extensionRunner
2391
2449
  ? wrapRegisteredTools(allCustomTools, this._extensionRunner)
2392
2450
  : [];
2451
+ // Give base tools a per-execution extension context so built-ins like
2452
+ // ask_user can reach ctx.ui. createContext() snapshots hasUI at call time,
2453
+ // so print/RPC-without-host modes degrade to the no-op UI (hasUI === false).
2454
+ const baseToolCtxFactory = this._extensionRunner
2455
+ ? () => this._extensionRunner.createContext()
2456
+ : undefined;
2393
2457
  const toolRegistry = new Map(Array.from(this._baseToolDefinitions.values()).map((definition) => [
2394
2458
  definition.name,
2395
- wrapToolDefinition(definition),
2459
+ wrapToolDefinition(definition, baseToolCtxFactory),
2396
2460
  ]));
2397
2461
  for (const tool of wrappedExtensionTools) {
2398
2462
  toolRegistry.set(tool.name, tool);
@@ -2467,6 +2531,12 @@ export class AgentSession {
2467
2531
  parentSessionFile: () => this.sessionFile,
2468
2532
  modelRegistry: this._modelRegistry,
2469
2533
  getAgentModelsForAgent: (name) => this.settingsManager?.getAgentModelsForAgent(name),
2534
+ defaultThinkingLevel: () => this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL,
2535
+ arbitrate: (request, signal) => this._dispatchArbiter.arbitrate(request, signal),
2536
+ onArbitration: (event) => {
2537
+ this.sessionManager.appendCustomEntry("subagent_arbitration", event);
2538
+ this._emit(event);
2539
+ },
2470
2540
  onBackgroundStart: (agentId, agentType, taskSummary, sessionDir) => {
2471
2541
  this._emit({ type: "background_agent_start", agentId, agentType, taskSummary, sessionDir });
2472
2542
  },
@@ -2485,12 +2555,11 @@ export class AgentSession {
2485
2555
  extensionsResult.runtime.flagValues.set(name, value);
2486
2556
  }
2487
2557
  }
2488
- const hasExtensions = extensionsResult.extensions.length > 0;
2489
- const hasCustomTools = this._customTools.length > 0;
2490
- this._extensionRunner =
2491
- hasExtensions || hasCustomTools
2492
- ? new ExtensionRunner(extensionsResult.extensions, extensionsResult.runtime, this._cwd, this.sessionManager, this._modelRegistry)
2493
- : undefined;
2558
+ // The runner also owns the cross-surface UI context used by built-in
2559
+ // tools such as ask_user. Create it even when no third-party extensions
2560
+ // are loaded; otherwise ordinary TUI/Dashboard sessions give base tools
2561
+ // no ctx.ui and ask_user can never open its dialog.
2562
+ this._extensionRunner = new ExtensionRunner(extensionsResult.extensions, extensionsResult.runtime, this._cwd, this.sessionManager, this._modelRegistry);
2494
2563
  if (this._extensionRunnerRef) {
2495
2564
  this._extensionRunnerRef.current = this._extensionRunner;
2496
2565
  }
@@ -2513,6 +2582,7 @@ export class AgentSession {
2513
2582
  "subagent",
2514
2583
  "wait",
2515
2584
  "search",
2585
+ "ask_user",
2516
2586
  "skill",
2517
2587
  "tasks_update",
2518
2588
  "suggest_next",
@@ -2783,6 +2853,7 @@ export class AgentSession {
2783
2853
  this._steeringMessages = [];
2784
2854
  this._followUpMessages = [];
2785
2855
  this._pendingNextTurnMessages = [];
2856
+ this._dispatchArbiter.clearContext();
2786
2857
  // Set new session
2787
2858
  this.sessionManager.setSessionFile(sessionPath);
2788
2859
  this.agent.sessionId = this.sessionManager.getSessionId();