@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
@@ -14,17 +14,19 @@ import { parseSkillBlock } from "../../core/agent-session.js";
14
14
  import { BuddyManager, checkOllama } from "../../core/buddy/buddy-manager.js";
15
15
  import { Rarity, Stat } from "../../core/buddy/buddy-types.js";
16
16
  import { BuddyController } from "../../core/buddy/index.js";
17
+ import { formatDispatchArbitrationRecord } from "../../core/dispatch-arbiter.js";
17
18
  import { acquireDreamLock, buildDreamPrompt, cleanupDreamTmpDirs, parseDreamCommand, performDreamBackup, pruneOldBackups, resolveDreamContext, validateArchivePath, validateMemoryLinks, } from "../../core/dream.js";
18
19
  import { FooterDataProvider } from "../../core/footer-data-provider.js";
19
20
  import { KeybindingsManager } from "../../core/keybindings.js";
20
21
  import { createCompactionSummaryMessage } from "../../core/messages.js";
21
22
  import { findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js";
23
+ import { loadAndValidateModelRoutingGuide } from "../../core/model-routing-guide.js";
22
24
  import { DefaultPackageManager } from "../../core/package-manager.js";
23
25
  import { SessionManager } from "../../core/session-manager.js";
24
26
  import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
25
27
  import { restoreStderr, takeOverStderr } from "../../core/stderr-guard.js";
26
28
  import { TabTitleGenerator } from "../../core/tab-title.js";
27
- import { resolveThinkingDisplay } from "../../core/thinking.js";
29
+ import { resolveThinkingDisplay, validateThinkingLevelForModel } from "../../core/thinking.js";
28
30
  import { resolveToCwd } from "../../core/tools/path-utils.js";
29
31
  import { abortBackgroundAgents, discoverAgentTypes, getRunningBackgroundAgents } from "../../core/tools/subagent.js";
30
32
  import { copyToClipboard } from "../../utils/clipboard.js";
@@ -33,6 +35,7 @@ import { parseGitUrl } from "../../utils/git.js";
33
35
  import { extractCopyableText, extractThinkingText, getMessagePreview, getMessageRoleLabel, toSingleLinePreview, } from "../../utils/message-text.js";
34
36
  import { ensureTool } from "../../utils/tools-manager.js";
35
37
  import { ArminComponent } from "./components/armin.js";
38
+ import { AskUserComponent } from "./components/ask-user.js";
36
39
  import { AssistantMessageComponent } from "./components/assistant-message.js";
37
40
  import { BashExecutionComponent } from "./components/bash-execution.js";
38
41
  import { BorderedLoader } from "./components/bordered-loader.js";
@@ -134,6 +137,8 @@ export class InteractiveMode {
134
137
  extensionSelector = undefined;
135
138
  extensionInput = undefined;
136
139
  extensionEditor = undefined;
140
+ extensionAsk = undefined;
141
+ extensionDialogQueue = Promise.resolve();
137
142
  extensionTerminalInputUnsubscribers = new Set();
138
143
  // Extension widgets (components rendered above/below the editor)
139
144
  extensionWidgetsAbove = new Map();
@@ -1182,6 +1187,9 @@ export class InteractiveMode {
1182
1187
  if (this.extensionEditor) {
1183
1188
  this.hideExtensionEditor();
1184
1189
  }
1190
+ if (this.extensionAsk) {
1191
+ this.hideExtensionAsk();
1192
+ }
1185
1193
  this.ui.hideOverlay();
1186
1194
  this.clearExtensionTerminalInputListeners();
1187
1195
  this.setExtensionFooter(undefined);
@@ -1404,9 +1412,22 @@ export class InteractiveMode {
1404
1412
  */
1405
1413
  createExtensionUIContext() {
1406
1414
  return {
1407
- select: (title, options, opts) => this.showExtensionSelector(title, options, opts),
1408
- confirm: (title, message, opts) => this.showExtensionConfirm(title, message, opts),
1409
- input: (title, placeholder, opts) => this.showExtensionInput(title, placeholder, opts),
1415
+ select: (title, options, opts) => this.enqueueExtensionDialog(() => this.showExtensionSelector(title, options, opts), {
1416
+ signal: opts?.signal,
1417
+ abortedValue: undefined,
1418
+ }),
1419
+ confirm: (title, message, opts) => this.enqueueExtensionDialog(() => this.showExtensionConfirm(title, message, opts), {
1420
+ signal: opts?.signal,
1421
+ abortedValue: false,
1422
+ }),
1423
+ input: (title, placeholder, opts) => this.enqueueExtensionDialog(() => this.showExtensionInput(title, placeholder, opts), {
1424
+ signal: opts?.signal,
1425
+ abortedValue: undefined,
1426
+ }),
1427
+ ask: (request, opts) => this.enqueueExtensionDialog(() => this.showExtensionAsk(request, opts), {
1428
+ signal: opts?.signal,
1429
+ abortedValue: undefined,
1430
+ }),
1410
1431
  notify: (message, type) => this.showExtensionNotify(message, type),
1411
1432
  onTerminalInput: (handler) => this.addExtensionTerminalInputListener(handler),
1412
1433
  setStatus: (key, text) => this.setExtensionStatus(key, text),
@@ -1415,11 +1436,11 @@ export class InteractiveMode {
1415
1436
  setFooter: (factory) => this.setExtensionFooter(factory),
1416
1437
  setHeader: (factory) => this.setExtensionHeader(factory),
1417
1438
  setTitle: (title) => this.ui.terminal.setTitle(title),
1418
- custom: (factory, options) => this.showExtensionCustom(factory, options),
1439
+ custom: (factory, options) => this.enqueueExtensionDialog(() => this.showExtensionCustom(factory, options)),
1419
1440
  pasteToEditor: (text) => this.editor.handleInput(`\x1b[200~${text}\x1b[201~`),
1420
1441
  setEditorText: (text) => this.editor.setText(text),
1421
1442
  getEditorText: () => this.editor.getExpandedText?.() ?? this.editor.getText(),
1422
- editor: (title, prefill) => this.showExtensionEditor(title, prefill),
1443
+ editor: (title, prefill) => this.enqueueExtensionDialog(() => this.showExtensionEditor(title, prefill)),
1423
1444
  setEditorComponent: (factory) => this.setCustomEditorComponent(factory),
1424
1445
  get theme() {
1425
1446
  return theme;
@@ -1445,6 +1466,32 @@ export class InteractiveMode {
1445
1466
  setToolsExpanded: (expanded) => this.setToolsExpanded(expanded),
1446
1467
  };
1447
1468
  }
1469
+ /** Serialize every blocking extension dialog that can take over keyboard focus. */
1470
+ enqueueExtensionDialog(open, opts) {
1471
+ if (!opts?.signal) {
1472
+ const result = this.extensionDialogQueue.then(open, open);
1473
+ this.extensionDialogQueue = result.then(() => undefined, () => undefined);
1474
+ return result;
1475
+ }
1476
+ const { signal, abortedValue } = opts;
1477
+ if (signal.aborted)
1478
+ return Promise.resolve(abortedValue);
1479
+ // Settle the caller as soon as a queued dialog aborts, even if an earlier
1480
+ // unbounded dialog still owns the UI. Its queue slot remains as a no-op so
1481
+ // later dialogs keep their FIFO order.
1482
+ let onQueuedAbort;
1483
+ const aborted = new Promise((resolve) => {
1484
+ onQueuedAbort = () => resolve(abortedValue);
1485
+ signal.addEventListener("abort", onQueuedAbort, { once: true });
1486
+ });
1487
+ const run = () => {
1488
+ signal.removeEventListener("abort", onQueuedAbort);
1489
+ return signal.aborted ? Promise.resolve(abortedValue) : open();
1490
+ };
1491
+ const queued = this.extensionDialogQueue.then(run, run);
1492
+ this.extensionDialogQueue = queued.then(() => undefined, () => undefined);
1493
+ return Promise.race([queued, aborted]).finally(() => signal.removeEventListener("abort", onQueuedAbort));
1494
+ }
1448
1495
  /**
1449
1496
  * Show a selector for extensions.
1450
1497
  */
@@ -1526,6 +1573,48 @@ export class InteractiveMode {
1526
1573
  this.extensionInput = undefined;
1527
1574
  this.restoreEditorComponent();
1528
1575
  }
1576
+ /**
1577
+ * Show a rich ask_user question for extensions/built-in tools.
1578
+ */
1579
+ showExtensionAsk(request, opts) {
1580
+ return new Promise((resolve) => {
1581
+ if (opts?.signal?.aborted) {
1582
+ resolve(undefined);
1583
+ return;
1584
+ }
1585
+ const onAbort = () => {
1586
+ this.hideExtensionAsk();
1587
+ resolve(undefined);
1588
+ };
1589
+ opts?.signal?.addEventListener("abort", onAbort, { once: true });
1590
+ this.extensionAsk = new AskUserComponent(request, (result) => {
1591
+ opts?.signal?.removeEventListener("abort", onAbort);
1592
+ this.hideExtensionAsk();
1593
+ resolve(result);
1594
+ }, () => {
1595
+ opts?.signal?.removeEventListener("abort", onAbort);
1596
+ this.hideExtensionAsk();
1597
+ // The question-level escape action means stop the whole turn, not
1598
+ // continue without an answer. Mirror the default editor's Escape
1599
+ // behavior, including background-agent and queued-message cleanup.
1600
+ this.cancelBackgroundAgents();
1601
+ this.restoreQueuedMessagesToEditor({ abort: true });
1602
+ resolve(undefined);
1603
+ }, { tui: this.ui, timeout: opts?.timeout });
1604
+ this.editorContainer.clear();
1605
+ this.editorContainer.addChild(this.extensionAsk);
1606
+ this.ui.setFocus(this.extensionAsk);
1607
+ this.ui.requestRender();
1608
+ });
1609
+ }
1610
+ /**
1611
+ * Hide the ask_user question.
1612
+ */
1613
+ hideExtensionAsk() {
1614
+ this.extensionAsk?.dispose();
1615
+ this.extensionAsk = undefined;
1616
+ this.restoreEditorComponent();
1617
+ }
1529
1618
  /**
1530
1619
  * Show a multi-line editor for extensions (with Ctrl+G support).
1531
1620
  */
@@ -2295,6 +2384,15 @@ export class InteractiveMode {
2295
2384
  this.showWarning(`Response truncated, retrying with larger token budget (${event.attempt}/${event.maxAttempts})…`);
2296
2385
  break;
2297
2386
  }
2387
+ case "subagent_arbitration": {
2388
+ const message = formatDispatchArbitrationRecord(event);
2389
+ if (event.status === "failure")
2390
+ this.showWarning(message);
2391
+ else
2392
+ this.showStatus(message);
2393
+ this.updateBackgroundAgentStatus();
2394
+ break;
2395
+ }
2298
2396
  case "background_agent_start":
2299
2397
  case "background_agent_end": {
2300
2398
  this.updateBackgroundAgentStatus();
@@ -3098,6 +3196,67 @@ export class InteractiveMode {
3098
3196
  this.ui.setFocus(focus);
3099
3197
  this.ui.requestRender();
3100
3198
  }
3199
+ async handleSubagentArbiterSettingsChange(settings) {
3200
+ if (this.settingsManager.hasGlobalSettingsLoadError()) {
3201
+ this.showError("Cannot write Dispatch Arbiter settings: the global settings file failed to load. Fix or remove the corrupt settings.json first.");
3202
+ return false;
3203
+ }
3204
+ if (settings.enabled) {
3205
+ if (!settings.model) {
3206
+ this.showError("Choose an exact Dispatch Arbiter model before enabling it.");
3207
+ return false;
3208
+ }
3209
+ const slash = settings.model.indexOf("/");
3210
+ const arbiterModel = slash > 0
3211
+ ? this.session.modelRegistry.find(settings.model.slice(0, slash), settings.model.slice(slash + 1))
3212
+ : undefined;
3213
+ if (!arbiterModel) {
3214
+ this.showError(`Dispatch Arbiter model "${settings.model}" is not available.`);
3215
+ return false;
3216
+ }
3217
+ const thinking = settings.thinking ?? "off";
3218
+ const thinkingValidation = validateThinkingLevelForModel(arbiterModel, thinking);
3219
+ if (!thinkingValidation.ok) {
3220
+ this.showError(thinkingValidation.error);
3221
+ return false;
3222
+ }
3223
+ try {
3224
+ loadAndValidateModelRoutingGuide(settings.guidePath, process.cwd(), this.session.scopedModels.map(({ model }) => `${model.provider}/${model.id}`));
3225
+ }
3226
+ catch (error) {
3227
+ this.showError(`Dispatch Arbiter is not ready: ${error instanceof Error ? error.message : String(error)}`);
3228
+ return false;
3229
+ }
3230
+ }
3231
+ this.settingsManager.drainErrors();
3232
+ try {
3233
+ this.settingsManager.setGlobalSubagentArbiterSettings(settings);
3234
+ await this.settingsManager.flush();
3235
+ }
3236
+ catch (error) {
3237
+ try {
3238
+ this.settingsManager.reload();
3239
+ }
3240
+ catch { }
3241
+ this.showError(`Failed to persist Dispatch Arbiter settings: ${error instanceof Error ? error.message : String(error)}`);
3242
+ return false;
3243
+ }
3244
+ const writeErrors = this.settingsManager.drainErrors();
3245
+ if (writeErrors.length > 0) {
3246
+ try {
3247
+ this.settingsManager.reload();
3248
+ }
3249
+ catch { }
3250
+ this.showError(`Failed to persist Dispatch Arbiter settings: ${writeErrors
3251
+ .map((entry) => `${entry.scope}: ${entry.error.message}`)
3252
+ .join("; ")}`);
3253
+ return false;
3254
+ }
3255
+ this.showStatus(settings.enabled
3256
+ ? `Dispatch Arbiter enabled (${settings.model}, thinking ${settings.thinking ?? "off"}).`
3257
+ : "Dispatch Arbiter disabled.");
3258
+ return true;
3259
+ }
3101
3260
  showSettingsSelector() {
3102
3261
  this.showSelector((done) => {
3103
3262
  // Discover agent types for agent models section
@@ -3137,6 +3296,7 @@ export class InteractiveMode {
3137
3296
  agentModels: this.settingsManager.getAgentModels(),
3138
3297
  agentNames: agentNames,
3139
3298
  availableModelIds,
3299
+ subagentArbiter: this.settingsManager.getGlobalSubagentArbiterSettings() ?? {},
3140
3300
  }, {
3141
3301
  onAutoCompactChange: (enabled) => {
3142
3302
  this.session.setAutoCompactionEnabled(enabled);
@@ -3251,6 +3411,7 @@ export class InteractiveMode {
3251
3411
  this.editor.setAutocompleteMaxVisible(maxVisible);
3252
3412
  }
3253
3413
  },
3414
+ onSubagentArbiterChange: (settings) => this.handleSubagentArbiterSettingsChange(settings),
3254
3415
  onAgentModelsChange: (agentName, models) => {
3255
3416
  const shadowedByProject = this.settingsManager.hasProjectAgentModelOverride(agentName);
3256
3417
  if (models.length > 0) {