@dreb/coding-agent 2.23.0 → 2.25.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 (43) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +2 -0
  3. package/dist/core/agent-session.d.ts +11 -0
  4. package/dist/core/agent-session.d.ts.map +1 -1
  5. package/dist/core/agent-session.js +35 -2
  6. package/dist/core/agent-session.js.map +1 -1
  7. package/dist/core/extensions/index.d.ts +1 -1
  8. package/dist/core/extensions/index.d.ts.map +1 -1
  9. package/dist/core/extensions/index.js.map +1 -1
  10. package/dist/core/extensions/types.d.ts +14 -1
  11. package/dist/core/extensions/types.d.ts.map +1 -1
  12. package/dist/core/extensions/types.js.map +1 -1
  13. package/dist/core/sdk.d.ts.map +1 -1
  14. package/dist/core/sdk.js +4 -0
  15. package/dist/core/sdk.js.map +1 -1
  16. package/dist/core/settings-manager.d.ts +14 -0
  17. package/dist/core/settings-manager.d.ts.map +1 -1
  18. package/dist/core/settings-manager.js +41 -0
  19. package/dist/core/settings-manager.js.map +1 -1
  20. package/dist/core/tools/index.d.ts +1 -1
  21. package/dist/core/tools/index.d.ts.map +1 -1
  22. package/dist/core/tools/index.js +1 -1
  23. package/dist/core/tools/index.js.map +1 -1
  24. package/dist/core/tools/subagent.d.ts +5 -1
  25. package/dist/core/tools/subagent.d.ts.map +1 -1
  26. package/dist/core/tools/subagent.js +51 -13
  27. package/dist/core/tools/subagent.js.map +1 -1
  28. package/dist/modes/interactive/components/settings-selector.d.ts +36 -1
  29. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  30. package/dist/modes/interactive/components/settings-selector.js +195 -1
  31. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  32. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  33. package/dist/modes/interactive/interactive-mode.js +51 -1
  34. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  35. package/dist/modes/interactive/tab-title.d.ts +5 -0
  36. package/dist/modes/interactive/tab-title.d.ts.map +1 -1
  37. package/dist/modes/interactive/tab-title.js +6 -0
  38. package/dist/modes/interactive/tab-title.js.map +1 -1
  39. package/docs/agent-models.md +64 -0
  40. package/docs/extensions.md +13 -0
  41. package/docs/mach6.md +2 -0
  42. package/docs/settings.md +18 -0
  43. package/package.json +1 -1
@@ -65,7 +65,7 @@ export function parseAgentFrontmatter(content) {
65
65
  },
66
66
  };
67
67
  }
68
- function discoverAgentTypes(cwd) {
68
+ export function discoverAgentTypes(cwd) {
69
69
  const agents = new Map();
70
70
  // Package-bundled agents (shipped with dreb — the canonical source of truth for built-in agents)
71
71
  const packageAgentsDir = join(getPackageDir(), "agents");
@@ -321,6 +321,12 @@ async function spawnSubagent(agentConfig, task, cwd, signal, onProgress, parentP
321
321
  }
322
322
  }
323
323
  const output = outputParts.join("\n\n");
324
+ // Inspect the final assistant message's stopReason to detect truncation
325
+ // ("length") or a loud truncation failure ("error") from the core agent loop.
326
+ // stopReason is present at runtime even though collectedMessages is loosely typed.
327
+ const lastMsg = collectedMessages.length > 0 ? collectedMessages[collectedMessages.length - 1] : undefined;
328
+ const lastStopReason = lastMsg ? lastMsg.stopReason : undefined;
329
+ const lastErrorMessage = lastMsg ? lastMsg.errorMessage : undefined;
324
330
  // Build error message from best available source: stderr, plain stdout lines, or generic
325
331
  let errorMessage = null;
326
332
  if (exitCode !== 0) {
@@ -329,6 +335,29 @@ async function spawnSubagent(agentConfig, task, cwd, signal, onProgress, parentP
329
335
  errorMessage =
330
336
  stderrTrimmed.slice(0, 500) || plainOutput.slice(0, 500) || `Subagent exited with code ${exitCode}`;
331
337
  }
338
+ else if (output.trim() === "") {
339
+ // Clean exit but no output — surface why instead of returning a silent empty result.
340
+ if (lastStopReason === "length") {
341
+ errorMessage = "Subagent response was truncated at the model's token limit before producing any output.";
342
+ }
343
+ else if (lastStopReason === "error" && lastErrorMessage) {
344
+ errorMessage = String(lastErrorMessage).slice(0, 500);
345
+ }
346
+ else {
347
+ errorMessage = "Subagent completed with no output.";
348
+ }
349
+ }
350
+ else if (lastStopReason === "length") {
351
+ // Clean exit with partial output — keep the output but make the truncation loud.
352
+ errorMessage = "Subagent response was truncated at the model's token limit; output may be incomplete.";
353
+ }
354
+ else if (lastStopReason === "error" && lastErrorMessage) {
355
+ // Clean exit with partial output but a loud failure (e.g. length retries
356
+ // exhausted → the core agent loop converts the truncation to stopReason
357
+ // "error" while preserving the partial text). Surface the error instead of
358
+ // letting the partial output masquerade as a clean success.
359
+ errorMessage = String(lastErrorMessage).slice(0, 500);
360
+ }
332
361
  // Discover the session file written by the child process
333
362
  const sessionFile = sessionDir ? discoverSessionFile(sessionDir, agentConfig.name) : undefined;
334
363
  resolvePromise({
@@ -681,7 +710,7 @@ function clampCwd(defaultCwd, itemCwd) {
681
710
  }
682
711
  return { ok: true, cwd: resolved };
683
712
  }
684
- export async function executeSingle(agents, agentName, task, cwd, signal, onProgress, modelOverride, parentProvider, registry, sessionDir, parentModel) {
713
+ export async function executeSingle(agents, agentName, task, cwd, signal, onProgress, modelOverride, parentProvider, registry, sessionDir, parentModel, agentModels) {
685
714
  const name = agentName || DEFAULT_AGENT;
686
715
  const config = agents.get(name);
687
716
  if (!config) {
@@ -705,9 +734,9 @@ export async function executeSingle(agents, agentName, task, cwd, signal, onProg
705
734
  errorMessage: `Task prompt too long (${task.length} chars, max ${MAX_TASK_LENGTH}). Shorten the prompt.`,
706
735
  };
707
736
  }
708
- // Per-invocation model override takes precedence over agent definition model.
709
- // Override is always a single string; agent config may be a string or fallback list.
710
- const modelSpec = modelOverride || config.model;
737
+ // Per-invocation model override takes precedence over agent settings, which take precedence over agent definition model.
738
+ // Override is always a single string; agentModels and agent config may be arrays.
739
+ const modelSpec = modelOverride || (agentModels && agentModels.length > 0 ? agentModels : undefined) || config.model;
711
740
  let effectiveConfig = modelOverride ? { ...config, model: modelOverride } : config;
712
741
  let resolvedProvider = parentProvider;
713
742
  let warning;
@@ -738,15 +767,16 @@ export async function executeSingle(agents, agentName, task, cwd, signal, onProg
738
767
  }
739
768
  warning = resolved.warning;
740
769
  }
741
- onProgress?.(`Running ${name} agent...`);
770
+ const usedModel = effectiveConfig.model?.toString();
771
+ onProgress?.(`Running ${name} agent${usedModel ? ` (${usedModel})` : ""}...`);
742
772
  const result = await spawnSubagent(effectiveConfig, task, cwd, signal, onProgress, resolvedProvider, sessionDir);
743
- result.output = prependModelFallbackSummary(result.output, skippedModels, result.model ?? effectiveConfig.model?.toString());
773
+ result.output = prependModelFallbackSummary(result.output, skippedModels, result.model ?? usedModel);
744
774
  if (warning) {
745
775
  result.output = `[WARNING: ${warning}]\n\n${result.output}`;
746
776
  }
747
777
  return result;
748
778
  }
749
- async function executeChain(agents, chain, defaultCwd, signal, onProgress, parentProvider, registry, sessionBaseDir, defaultAgent, defaultModel, parentModel) {
779
+ async function executeChain(agents, chain, defaultCwd, signal, onProgress, parentProvider, registry, sessionBaseDir, defaultAgent, defaultModel, parentModel, getAgentModelsForAgentFn) {
750
780
  const results = [];
751
781
  let previousOutput = "";
752
782
  for (let i = 0; i < chain.length; i++) {
@@ -781,7 +811,9 @@ async function executeChain(agents, chain, defaultCwd, signal, onProgress, paren
781
811
  }
782
812
  // Each chain step gets its own session subdirectory
783
813
  const stepSessionDir = sessionBaseDir ? join(sessionBaseDir, `step-${i + 1}`) : undefined;
784
- const result = await executeSingle(agents, step.agent || defaultAgent, task, cwdResult.cwd, signal, onProgress, step.model || defaultModel, parentProvider, registry, stepSessionDir, parentModel);
814
+ const stepAgentName = step.agent || defaultAgent || DEFAULT_AGENT;
815
+ const stepMach6Models = getAgentModelsForAgentFn?.(stepAgentName);
816
+ const result = await executeSingle(agents, step.agent || defaultAgent, task, cwdResult.cwd, signal, onProgress, step.model || defaultModel, parentProvider, registry, stepSessionDir, parentModel, stepMach6Models);
785
817
  results.push(result);
786
818
  if (result.exitCode !== 0) {
787
819
  break; // stop chain on error
@@ -918,7 +950,7 @@ function formatSubagentResult(result, options, theme, showImages) {
918
950
  }
919
951
  return text;
920
952
  }
921
- function formatSingleResult(result) {
953
+ export function formatSingleResult(result) {
922
954
  let text = `## Agent: ${result.agent}${result.model ? ` (model: ${result.model})` : ""}\n`;
923
955
  if (result.exitCode !== 0) {
924
956
  text += `**Error** (exit ${result.exitCode}): ${result.errorMessage || "Unknown error"}\n`;
@@ -926,10 +958,14 @@ function formatSingleResult(result) {
926
958
  text += `\nStderr:\n${result.stderr}\n`;
927
959
  }
928
960
  }
961
+ else if (result.errorMessage) {
962
+ // Clean exit but an error was surfaced (e.g. truncation at the token limit).
963
+ text += `**Error**: ${result.errorMessage}\n`;
964
+ }
929
965
  if (result.output) {
930
966
  text += `\n${result.output}`;
931
967
  }
932
- else if (result.exitCode === 0) {
968
+ else if (result.exitCode === 0 && !result.errorMessage) {
933
969
  text += "\n(No output)";
934
970
  }
935
971
  if (result.sessionFile) {
@@ -943,6 +979,7 @@ export function createSubagentToolDefinition(cwd, options) {
943
979
  const getParentProvider = options?.parentProvider ?? (() => undefined);
944
980
  const getParentModel = options?.parentModel ?? (() => undefined);
945
981
  const modelRegistry = options?.modelRegistry;
982
+ const getAgentModelsForAgent = options?.getAgentModelsForAgent;
946
983
  // Discover agents at definition time to build the prompt guidelines.
947
984
  // This is cheap (reads .md files) and the same call happens on every execute().
948
985
  const knownAgents = discoverAgentTypes(cwd);
@@ -1095,7 +1132,8 @@ export function createSubagentToolDefinition(cwd, options) {
1095
1132
  // Each background agent gets its own session subdirectory
1096
1133
  const sessionId = generateAgentId();
1097
1134
  const sessionDir = join(subagentSessionsBase, sessionId);
1098
- return launchBackgroundLifecycle(agentName, taskLabel, (signal) => executeSingle(agents, agentName === DEFAULT_AGENT ? undefined : agentName, task, resolvedCwd, signal, undefined, modelOverride, getParentProvider(), modelRegistry, sessionDir, getParentModel()));
1135
+ const agentModels = getAgentModelsForAgent?.(agentName || DEFAULT_AGENT);
1136
+ return launchBackgroundLifecycle(agentName, taskLabel, (signal) => executeSingle(agents, agentName === DEFAULT_AGENT ? undefined : agentName, task, resolvedCwd, signal, undefined, modelOverride, getParentProvider(), modelRegistry, sessionDir, getParentModel(), agentModels));
1099
1137
  };
1100
1138
  if (params.task) {
1101
1139
  // Single background task
@@ -1161,7 +1199,7 @@ export function createSubagentToolDefinition(cwd, options) {
1161
1199
  const chainSteps = params.chain;
1162
1200
  const chainSessionDir = join(subagentSessionsBase, `chain-${generateAgentId()}`);
1163
1201
  const agentId = launchBackgroundLifecycle(agentName, taskSummary, async (signal) => {
1164
- const results = await executeChain(agents, chainSteps, cwd, signal, undefined, getParentProvider(), modelRegistry, chainSessionDir, params.agent, params.model, getParentModel());
1202
+ const results = await executeChain(agents, chainSteps, cwd, signal, undefined, getParentProvider(), modelRegistry, chainSessionDir, params.agent, params.model, getParentModel(), getAgentModelsForAgent);
1165
1203
  const resultText = results
1166
1204
  .map((r, i) => `### Step ${i + 1}\n${formatSingleResult(r)}`)
1167
1205
  .join("\n\n---\n\n");