@dreb/coding-agent 2.59.1 → 2.60.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.
@@ -20,7 +20,7 @@ import { getDocsPath } from "../config.js";
20
20
  import { theme } from "../modes/interactive/theme/theme.js";
21
21
  import { sleep } from "../utils/sleep.js";
22
22
  import { executeBash as executeBashCommand, executeBashWithOperations } from "./bash-executor.js";
23
- import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
23
+ import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, estimateTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
24
24
  import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
25
25
  import { DispatchArbiter } from "./dispatch-arbiter.js";
26
26
  import { exportSessionToHtml } from "./export-html/index.js";
@@ -36,7 +36,7 @@ import { PerformanceTracker } from "./performance-tracker.js";
36
36
  import { expandPromptTemplate } from "./prompt-templates.js";
37
37
  import { scrubSecrets } from "./secret-scrubber.js";
38
38
  import { isSensitivePath } from "./sensitive-paths.js";
39
- import { CURRENT_SESSION_VERSION, getLatestCompactionEntry } from "./session-manager.js";
39
+ import { CURRENT_SESSION_VERSION, getLatestCompactionEntry, SessionManager, } from "./session-manager.js";
40
40
  import { DEFAULT_BG_PARENT_TURN_LIMIT, DEFAULT_MAX_CONCURRENT_SUBAGENTS, } from "./settings-manager.js";
41
41
  import { createSyntheticSourceInfo } from "./source-info.js";
42
42
  import { buildSystemPrompt } from "./system-prompt.js";
@@ -1233,7 +1233,15 @@ export class AgentSession {
1233
1233
  }
1234
1234
  const loaderSystemPrompt = this._resourceLoader.getSystemPrompt();
1235
1235
  const loaderAppendSystemPrompt = this._resourceLoader.getAppendSystemPrompt();
1236
- const appendSystemPrompt = loaderAppendSystemPrompt.length > 0 ? loaderAppendSystemPrompt.join("\n\n") : undefined;
1236
+ const modelPromptSettings = this.model
1237
+ ? this.settingsManager.getModelPromptSettings(this.model.provider, this.model.id)
1238
+ : undefined;
1239
+ const customPrompt = loaderSystemPrompt ?? modelPromptSettings?.systemPrompt;
1240
+ const appendPromptParts = [...loaderAppendSystemPrompt];
1241
+ if (modelPromptSettings?.appendSystemPrompt) {
1242
+ appendPromptParts.push(modelPromptSettings.appendSystemPrompt);
1243
+ }
1244
+ const appendSystemPrompt = appendPromptParts.length > 0 ? appendPromptParts.join("\n\n") : undefined;
1237
1245
  const loadedSkills = this._getFilteredSkills();
1238
1246
  const loadedContextFiles = this._resourceLoader.getAgentsFiles().agentsFiles;
1239
1247
  const memoryIndexes = this._resourceLoader.getMemoryIndexes();
@@ -1242,7 +1250,7 @@ export class AgentSession {
1242
1250
  skills: loadedSkills,
1243
1251
  contextFiles: loadedContextFiles,
1244
1252
  memoryIndexes,
1245
- customPrompt: loaderSystemPrompt,
1253
+ customPrompt,
1246
1254
  appendSystemPrompt,
1247
1255
  selectedTools: validToolNames,
1248
1256
  toolSnippets,
@@ -1723,6 +1731,7 @@ export class AgentSession {
1723
1731
  if (!apiKey) {
1724
1732
  throw new Error(`No API key for ${model.provider}/${model.id}`);
1725
1733
  }
1734
+ this._validateModelPromptSettings(model);
1726
1735
  const previousModel = this.model;
1727
1736
  const thinkingLevel = this._getThinkingLevelForModelSwitch();
1728
1737
  this.agent.setModel(this._applyContextTier(model));
@@ -1744,6 +1753,10 @@ export class AgentSession {
1744
1753
  _refreshThinkingDisplay(model) {
1745
1754
  this.agent.thinkingDisplay = resolveThinkingDisplay(model, this.settingsManager.getModelThinkingDisplay(model.id));
1746
1755
  }
1756
+ /** Reject malformed target-model prompt settings before a model switch mutates session state. */
1757
+ _validateModelPromptSettings(model) {
1758
+ this.settingsManager.getModelPromptSettings(model.provider, model.id);
1759
+ }
1747
1760
  /**
1748
1761
  * Cycle to next/previous model.
1749
1762
  * Uses scoped models (from --models flag) if available, otherwise all available models.
@@ -1786,6 +1799,7 @@ export class AgentSession {
1786
1799
  const len = scopedModels.length;
1787
1800
  const nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
1788
1801
  const next = scopedModels[nextIndex];
1802
+ this._validateModelPromptSettings(next.model);
1789
1803
  const thinkingLevel = this._getThinkingLevelForModelSwitch(next.thinkingLevel);
1790
1804
  // Apply model
1791
1805
  this.agent.setModel(this._applyContextTier(next.model));
@@ -1817,6 +1831,7 @@ export class AgentSession {
1817
1831
  if (!apiKey) {
1818
1832
  throw new Error(`No API key for ${nextModel.provider}/${nextModel.id}`);
1819
1833
  }
1834
+ this._validateModelPromptSettings(nextModel);
1820
1835
  const thinkingLevel = this._getThinkingLevelForModelSwitch();
1821
1836
  this.agent.setModel(this._applyContextTier(nextModel));
1822
1837
  this._refreshThinkingDisplay(nextModel);
@@ -2959,6 +2974,17 @@ export class AgentSession {
2959
2974
  return false;
2960
2975
  }
2961
2976
  }
2977
+ // Resolve and validate the target model before disconnecting or mutating the active session.
2978
+ // Prompt validation can throw for malformed settings, so it belongs in this preflight phase.
2979
+ const targetModel = SessionManager.open(sessionPath).buildSessionContext().model;
2980
+ let restoredModel;
2981
+ if (targetModel) {
2982
+ const availableModels = await this._modelRegistry.getAvailable();
2983
+ restoredModel = availableModels.find((m) => m.provider === targetModel.provider && m.id === targetModel.modelId);
2984
+ if (restoredModel) {
2985
+ this._validateModelPromptSettings(restoredModel);
2986
+ }
2987
+ }
2962
2988
  this._disconnectFromAgent();
2963
2989
  await this.abort();
2964
2990
  this._steeringMessages = [];
@@ -2980,16 +3006,12 @@ export class AgentSession {
2980
3006
  }
2981
3007
  // Emit session event to custom tools
2982
3008
  this.agent.replaceMessages(sessionContext.messages);
2983
- // Restore model if saved
2984
- if (sessionContext.model) {
3009
+ // Restore the preflighted model if the target session saved one that is still available.
3010
+ if (restoredModel) {
2985
3011
  const previousModel = this.model;
2986
- const availableModels = await this._modelRegistry.getAvailable();
2987
- const match = availableModels.find((m) => m.provider === sessionContext.model.provider && m.id === sessionContext.model.modelId);
2988
- if (match) {
2989
- this.agent.setModel(this._applyContextTier(match));
2990
- this._refreshThinkingDisplay(match);
2991
- await this._emitModelSelect(match, previousModel, "restore");
2992
- }
3012
+ this.agent.setModel(this._applyContextTier(restoredModel));
3013
+ this._refreshThinkingDisplay(restoredModel);
3014
+ await this._emitModelSelect(restoredModel, previousModel, "restore");
2993
3015
  }
2994
3016
  const hasThinkingEntry = this.sessionManager.getBranch().some((entry) => entry.type === "thinking_level_change");
2995
3017
  const defaultThinkingLevel = this.settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;
@@ -3009,6 +3031,7 @@ export class AgentSession {
3009
3031
  this._gitRepoState = getGitRepoState(this._cwd) ?? undefined;
3010
3032
  this._resourceLoader.refreshDreamLastRun();
3011
3033
  this._baseSystemPrompt = this._rebuildSystemPrompt(this.getActiveToolNames());
3034
+ this.agent.setSystemPrompt(this._baseSystemPrompt);
3012
3035
  this._reconnectToAgent();
3013
3036
  return true;
3014
3037
  }
@@ -3346,11 +3369,12 @@ export class AgentSession {
3346
3369
  return undefined;
3347
3370
  // After compaction, the last assistant usage reflects pre-compaction context size.
3348
3371
  // We can only trust usage from an assistant that responded after the latest compaction.
3349
- // If no such assistant exists, context token count is unknown until the next LLM response.
3372
+ // Until then, estimate every rebuilt message independently so the stale kept
3373
+ // assistant usage cannot leak into the current context value.
3350
3374
  const branchEntries = this.sessionManager.getBranch();
3351
3375
  const latestCompaction = getLatestCompactionEntry(branchEntries);
3352
3376
  if (latestCompaction) {
3353
- // Check if there's a valid assistant usage after the compaction boundary
3377
+ // Check if there's a valid assistant usage after the compaction boundary.
3354
3378
  const compactionIndex = branchEntries.lastIndexOf(latestCompaction);
3355
3379
  let hasPostCompactionUsage = false;
3356
3380
  for (let i = branchEntries.length - 1; i > compactionIndex; i--) {
@@ -3359,23 +3383,22 @@ export class AgentSession {
3359
3383
  const assistant = entry.message;
3360
3384
  if (assistant.stopReason !== "aborted" && assistant.stopReason !== "error") {
3361
3385
  const contextTokens = calculateContextTokens(assistant.usage);
3362
- if (contextTokens > 0) {
3386
+ if (contextTokens > 0)
3363
3387
  hasPostCompactionUsage = true;
3364
- }
3365
3388
  break;
3366
3389
  }
3367
3390
  }
3368
3391
  }
3369
3392
  if (!hasPostCompactionUsage) {
3370
- return { tokens: null, contextWindow, percent: null };
3393
+ const tokens = this.messages.reduce((total, message) => total + estimateTokens(message), 0);
3394
+ return { tokens, contextWindow, percent: (tokens / contextWindow) * 100 };
3371
3395
  }
3372
3396
  }
3373
3397
  const estimate = estimateContextTokens(this.messages);
3374
- const percent = (estimate.tokens / contextWindow) * 100;
3375
3398
  return {
3376
3399
  tokens: estimate.tokens,
3377
3400
  contextWindow,
3378
- percent,
3401
+ percent: (estimate.tokens / contextWindow) * 100,
3379
3402
  };
3380
3403
  }
3381
3404
  /**