@dreb/coding-agent 2.53.1 → 2.54.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.
@@ -18,7 +18,7 @@ import { canonicalizeTrustedRoots, matchContextTrust, validateTrustedContextFold
18
18
  import { DailyCostTracker } from "../../core/daily-cost-tracker.js";
19
19
  import { acquireDreamLock, buildDreamPrompt, cleanupDreamTmpDirs, parseDreamCommand, performDreamBackup, pruneOldBackups, resolveDreamContext, validateArchivePath, validateMemoryLinks, } from "../../core/dream.js";
20
20
  import { getGitBranch } from "../../core/git-branch.js";
21
- import { parseModelPattern } from "../../core/model-resolver.js";
21
+ import { parseModelPattern, resolveModelScopePatterns } from "../../core/model-resolver.js";
22
22
  import { takeOverStdout, writeRawStdout } from "../../core/output-guard.js";
23
23
  import { SessionManager } from "../../core/session-manager.js";
24
24
  import { BUILTIN_SLASH_COMMANDS, parseBuiltinSlashCommand } from "../../core/slash-commands.js";
@@ -281,9 +281,12 @@ export function listAgentTypesForRpc(cwd) {
281
281
  * These seed fresh runtimes, NOT the live session state (`get_state` reports that). Extracted
282
282
  * (like {@link deleteSessionForRpc}) so it is unit-testable without a live RPC session.
283
283
  */
284
- export function getSettingsForRpc(settingsManager) {
284
+ export function getSettingsForRpc(settingsManager, availableModels = []) {
285
285
  const contextTrust = settingsManager.getGlobalContextTrustPolicy();
286
286
  const configuredTrustedFolders = settingsManager.getConfiguredTrustedContextFolders();
287
+ const enabledModels = settingsManager.getEnabledModels();
288
+ const hasProjectEnabledModelsOverride = settingsManager.hasProjectEnabledModelsOverride();
289
+ const scopeResolution = resolveModelScopePatterns(enabledModels ?? [], availableModels);
287
290
  return {
288
291
  defaultProvider: settingsManager.getDefaultProvider(),
289
292
  defaultModel: settingsManager.getDefaultModel(),
@@ -302,8 +305,26 @@ export function getSettingsForRpc(settingsManager) {
302
305
  hideThinkingBlock: settingsManager.getHideThinkingBlock(),
303
306
  agentModels: settingsManager.getAgentModels(),
304
307
  subagentArbiter: settingsManager.getGlobalSubagentArbiterSettings(),
308
+ enabledModels,
309
+ resolvedScopedModels: scopeResolution.models.map(({ model, thinkingLevel }) => ({
310
+ provider: model.provider,
311
+ id: model.id,
312
+ name: model.name,
313
+ reasoning: model.reasoning,
314
+ ...(thinkingLevel !== undefined ? { thinkingLevel } : {}),
315
+ })),
316
+ scopeWarnings: scopeResolution.warnings,
317
+ hasProjectEnabledModelsOverride,
318
+ enabledModelsSource: hasProjectEnabledModelsOverride
319
+ ? "project"
320
+ : enabledModels === undefined
321
+ ? "default"
322
+ : "global",
305
323
  };
306
324
  }
325
+ async function buildSettingsSnapshotForRpc(settingsManager, modelRegistry) {
326
+ return getSettingsForRpc(settingsManager, modelRegistry ? await modelRegistry.getAvailable() : []);
327
+ }
307
328
  function formatSettingsErrors(errors) {
308
329
  return errors.map((entry) => `${entry.scope}: ${entry.error.message}`).join("; ");
309
330
  }
@@ -316,7 +337,7 @@ function formatSettingsErrors(errors) {
316
337
  * content. Reload failures leave SettingsManager's previous values in memory, therefore
317
338
  * they must be surfaced as an RPC error instead of producing a stale snapshot.
318
339
  */
319
- export async function getFreshSettingsForRpc(settingsManager) {
340
+ export async function getFreshSettingsForRpc(settingsManager, modelRegistry) {
320
341
  return settingsWriteLock(async () => {
321
342
  try {
322
343
  // A runtime setter may have enqueued a write outside the RPC settings lock.
@@ -349,7 +370,13 @@ export async function getFreshSettingsForRpc(settingsManager) {
349
370
  error: detail ? `Failed to reload settings: ${detail}` : "Failed to reload settings from durable storage",
350
371
  };
351
372
  }
352
- return { ok: true, settings: getSettingsForRpc(settingsManager) };
373
+ try {
374
+ const availableModels = modelRegistry ? await modelRegistry.getAvailable() : [];
375
+ return { ok: true, settings: getSettingsForRpc(settingsManager, availableModels) };
376
+ }
377
+ catch (error) {
378
+ return { ok: false, error: `Failed to load available models: ${error.message}` };
379
+ }
353
380
  });
354
381
  }
355
382
  const SETTINGS_UPDATE_KEYS = [
@@ -368,6 +395,7 @@ const SETTINGS_UPDATE_KEYS = [
368
395
  "transport",
369
396
  "hideThinkingBlock",
370
397
  "agentModels",
398
+ "enabledModels",
371
399
  "subagentArbiter",
372
400
  ];
373
401
  const QUEUE_MODES = ["all", "one-at-a-time"];
@@ -450,7 +478,7 @@ function settingsWriteLock(fn) {
450
478
  settingsWriteQueue = next.catch(() => { });
451
479
  return next;
452
480
  }
453
- async function persistContextTrustMutationForRpc(settingsManager, folders, targetPath, mutation) {
481
+ async function persistContextTrustMutationForRpc(settingsManager, folders, targetPath, mutation, modelRegistry) {
454
482
  settingsManager.drainErrors();
455
483
  if (settingsManager.hasGlobalSettingsLoadError()) {
456
484
  return {
@@ -482,11 +510,15 @@ async function persistContextTrustMutationForRpc(settingsManager, folders, targe
482
510
  return evaluated;
483
511
  return {
484
512
  ok: true,
485
- result: { evaluation: evaluated.evaluation, settings: getSettingsForRpc(settingsManager), ...mutation },
513
+ result: {
514
+ evaluation: evaluated.evaluation,
515
+ settings: await buildSettingsSnapshotForRpc(settingsManager, modelRegistry),
516
+ ...mutation,
517
+ },
486
518
  };
487
519
  }
488
520
  /** Add a canonical directory to the global trusted roots and flush the change. */
489
- export async function trustContextFolderForRpc(settingsManager, path) {
521
+ export async function trustContextFolderForRpc(settingsManager, path, modelRegistry) {
490
522
  return settingsWriteLock(async () => {
491
523
  const evaluated = evaluateContextTrustForRpc(settingsManager, path);
492
524
  if (!evaluated.ok)
@@ -505,11 +537,11 @@ export async function trustContextFolderForRpc(settingsManager, path) {
505
537
  ...(folders.includes(evaluated.evaluation.canonicalTarget)
506
538
  ? { addedRoot: evaluated.evaluation.canonicalTarget }
507
539
  : {}),
508
- });
540
+ }, modelRegistry);
509
541
  });
510
542
  }
511
543
  /** Remove the root actually granting a directory access, rather than the supplied descendant. */
512
- export async function untrustContextFolderForRpc(settingsManager, path) {
544
+ export async function untrustContextFolderForRpc(settingsManager, path, modelRegistry) {
513
545
  return settingsWriteLock(async () => {
514
546
  const evaluated = evaluateContextTrustForRpc(settingsManager, path);
515
547
  if (!evaluated.ok)
@@ -523,16 +555,19 @@ export async function untrustContextFolderForRpc(settingsManager, path) {
523
555
  if (evaluated.evaluation.state === "untrusted") {
524
556
  return {
525
557
  ok: true,
526
- result: { evaluation: evaluated.evaluation, settings: getSettingsForRpc(settingsManager) },
558
+ result: {
559
+ evaluation: evaluated.evaluation,
560
+ settings: await buildSettingsSnapshotForRpc(settingsManager, modelRegistry),
561
+ },
527
562
  };
528
563
  }
529
564
  const removedRoot = evaluated.evaluation.grantingRoot;
530
565
  const roots = canonicalizeTrustedRoots(settingsManager.getConfiguredTrustedContextFolders());
531
- return persistContextTrustMutationForRpc(settingsManager, roots.filter((root) => root !== removedRoot), evaluated.evaluation.canonicalTarget, { removedRoot });
566
+ return persistContextTrustMutationForRpc(settingsManager, roots.filter((root) => root !== removedRoot), evaluated.evaluation.canonicalTarget, { removedRoot }, modelRegistry);
532
567
  });
533
568
  }
534
569
  /** Remove a configured trusted-folder string exactly as stored and flush the change. */
535
- export async function removeTrustedContextFolderForRpc(settingsManager, path) {
570
+ export async function removeTrustedContextFolderForRpc(settingsManager, path, modelRegistry) {
536
571
  return settingsWriteLock(async () => {
537
572
  if (typeof path !== "string" || path.length === 0) {
538
573
  return { ok: false, error: "remove_trusted_context_folder requires a non-empty string path" };
@@ -563,7 +598,13 @@ export async function removeTrustedContextFolderForRpc(settingsManager, path) {
563
598
  settingsManager.reload();
564
599
  return { ok: false, error: `Failed to persist settings: ${detail}` };
565
600
  }
566
- return { ok: true, result: { settings: getSettingsForRpc(settingsManager), removedFolder: path } };
601
+ return {
602
+ ok: true,
603
+ result: {
604
+ settings: await buildSettingsSnapshotForRpc(settingsManager, modelRegistry),
605
+ removedFolder: path,
606
+ },
607
+ };
567
608
  });
568
609
  }
569
610
  /**
@@ -646,6 +687,48 @@ export async function setSettingsForRpc(settingsManager, modelRegistry, update)
646
687
  return validation;
647
688
  }
648
689
  }
690
+ let canonicalEnabledModels;
691
+ if (update.enabledModels !== undefined && update.enabledModels !== null) {
692
+ if (!Array.isArray(update.enabledModels)) {
693
+ return {
694
+ ok: false,
695
+ error: "Invalid enabledModels: must be a non-empty array of exact model references or null",
696
+ };
697
+ }
698
+ if (update.enabledModels.length === 0) {
699
+ return {
700
+ ok: false,
701
+ error: "Invalid enabledModels: at least one model must remain enabled; use null to enable all",
702
+ };
703
+ }
704
+ const availableModels = await modelRegistry.getAvailable();
705
+ const seen = new Set();
706
+ canonicalEnabledModels = [];
707
+ for (const reference of update.enabledModels) {
708
+ if (typeof reference !== "string" || reference.trim().length === 0 || reference !== reference.trim()) {
709
+ return { ok: false, error: "Invalid enabledModels: every entry must be a non-empty exact model reference" };
710
+ }
711
+ const normalizedReference = reference.toLowerCase();
712
+ const matches = availableModels.filter((model) => `${model.provider}/${model.id}`.toLowerCase() === normalizedReference);
713
+ if (matches.length !== 1) {
714
+ return {
715
+ ok: false,
716
+ error: `Invalid enabledModels entry ${JSON.stringify(reference)}: expected an available exact provider/model reference`,
717
+ };
718
+ }
719
+ const match = matches[0];
720
+ const canonical = `${match.provider}/${match.id}`;
721
+ if (seen.has(canonical)) {
722
+ return { ok: false, error: `Invalid enabledModels: duplicate model ${JSON.stringify(canonical)}` };
723
+ }
724
+ seen.add(canonical);
725
+ canonicalEnabledModels.push(canonical);
726
+ }
727
+ if (canonicalEnabledModels.length === availableModels.length &&
728
+ availableModels.every((model) => seen.has(`${model.provider}/${model.id}`))) {
729
+ canonicalEnabledModels = undefined;
730
+ }
731
+ }
649
732
  let subagentArbiter = update.subagentArbiter === null ? undefined : update.subagentArbiter;
650
733
  if (subagentArbiter !== undefined) {
651
734
  if (!isPlainObject(subagentArbiter)) {
@@ -789,6 +872,12 @@ export async function setSettingsForRpc(settingsManager, modelRegistry, update)
789
872
  settingsManager.setHideThinkingBlock(update.hideThinkingBlock);
790
873
  }
791
874
  const warnings = [];
875
+ if (update.enabledModels !== undefined) {
876
+ if (settingsManager.hasProjectEnabledModelsOverride()) {
877
+ warnings.push("A project-level enabledModels value (.dreb/settings.json) takes precedence — this change to global settings will have no effect. Edit the project settings file to change it.");
878
+ }
879
+ settingsManager.setEnabledModels(canonicalEnabledModels);
880
+ }
792
881
  if (update.subagentArbiter !== undefined) {
793
882
  settingsManager.setGlobalSubagentArbiterSettings(subagentArbiter);
794
883
  }
@@ -832,9 +921,10 @@ export async function setSettingsForRpc(settingsManager, modelRegistry, update)
832
921
  return { ok: false, error: `Failed to persist settings: ${detail}` };
833
922
  }
834
923
  }
924
+ const snapshot = getSettingsForRpc(settingsManager, await modelRegistry.getAvailable());
835
925
  return warnings.length > 0
836
- ? { ok: true, settings: getSettingsForRpc(settingsManager), warnings }
837
- : { ok: true, settings: getSettingsForRpc(settingsManager) };
926
+ ? { ok: true, settings: snapshot, warnings }
927
+ : { ok: true, settings: snapshot };
838
928
  }
839
929
  catch (error) {
840
930
  if (updatesContextTrustPolicy) {
@@ -1693,7 +1783,7 @@ export async function runRpcMode(session, modelFallbackMessage) {
1693
1783
  // Settings (persistent defaults)
1694
1784
  // =================================================================
1695
1785
  case "get_settings": {
1696
- const result = await getFreshSettingsForRpc(session.settingsManager);
1786
+ const result = await getFreshSettingsForRpc(session.settingsManager, session.modelRegistry);
1697
1787
  return result.ok ? success(id, "get_settings", result.settings) : error(id, "get_settings", result.error);
1698
1788
  }
1699
1789
  case "set_settings": {
@@ -1712,19 +1802,19 @@ export async function runRpcMode(session, modelFallbackMessage) {
1712
1802
  : error(id, "evaluate_context_trust", result.error);
1713
1803
  }
1714
1804
  case "trust_context_folder": {
1715
- const result = await trustContextFolderForRpc(session.settingsManager, command.path);
1805
+ const result = await trustContextFolderForRpc(session.settingsManager, command.path, session.modelRegistry);
1716
1806
  return result.ok
1717
1807
  ? success(id, "trust_context_folder", result.result)
1718
1808
  : error(id, "trust_context_folder", result.error);
1719
1809
  }
1720
1810
  case "untrust_context_folder": {
1721
- const result = await untrustContextFolderForRpc(session.settingsManager, command.path);
1811
+ const result = await untrustContextFolderForRpc(session.settingsManager, command.path, session.modelRegistry);
1722
1812
  return result.ok
1723
1813
  ? success(id, "untrust_context_folder", result.result)
1724
1814
  : error(id, "untrust_context_folder", result.error);
1725
1815
  }
1726
1816
  case "remove_trusted_context_folder": {
1727
- const result = await removeTrustedContextFolderForRpc(session.settingsManager, command.path);
1817
+ const result = await removeTrustedContextFolderForRpc(session.settingsManager, command.path, session.modelRegistry);
1728
1818
  return result.ok
1729
1819
  ? success(id, "remove_trusted_context_folder", result.result)
1730
1820
  : error(id, "remove_trusted_context_folder", result.error);