pi-subagents 0.67.0 → 0.68.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 (120) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +1 -1
  3. package/docs/agents.md +37 -12
  4. package/docs/configuration.md +61 -19
  5. package/docs/extension-api.md +5 -1
  6. package/docs/missions.md +2 -2
  7. package/docs/models.md +11 -79
  8. package/docs/observability.md +18 -8
  9. package/docs/standalone-background.md +13 -3
  10. package/docs/tool-reference.md +15 -12
  11. package/docs/watchdog.md +10 -12
  12. package/docs/workflows.md +11 -1
  13. package/index.ts +5 -2
  14. package/package.json +4 -2
  15. package/runner-peer-loader.mjs +24 -0
  16. package/runner-peer-preload.mjs +25 -11
  17. package/skills/pi-subagents/SKILL.md +18 -21
  18. package/skills/pi-subagents/references/constraints-and-recipes.md +3 -2
  19. package/skills/pi-subagents/references/execution-controls.md +4 -4
  20. package/skills/pi-subagents/references/management-authoring-rpc.md +0 -1
  21. package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
  22. package/skills/pi-subagents/references/prompting-and-roles.md +16 -12
  23. package/skills/pi-subagents/references/review-and-validation.md +3 -3
  24. package/src/agents/agent-management.ts +57 -58
  25. package/src/agents/agent-serializer.ts +4 -3
  26. package/src/agents/agents.ts +184 -71
  27. package/src/agents/chain-serializer.ts +5 -0
  28. package/src/agents/runtime-agent-registry.ts +7 -6
  29. package/src/api/preflight.ts +20 -16
  30. package/src/api/required-child-extensions.ts +6 -0
  31. package/src/extension/config.ts +10 -37
  32. package/src/extension/fanout-child.ts +3 -0
  33. package/src/extension/herdr-pi-bridge.ts +160 -0
  34. package/src/extension/index.ts +42 -31
  35. package/src/extension/public-execution.ts +3 -3
  36. package/src/extension/schemas.ts +16 -5
  37. package/src/extension/tool-description.ts +8 -7
  38. package/src/intercom/native-supervisor-channel.ts +22 -18
  39. package/src/policy/authority.ts +4 -0
  40. package/src/profiles/profiles.ts +12 -6
  41. package/src/runs/background/active-run-index.ts +17 -1
  42. package/src/runs/background/async-execution.ts +309 -126
  43. package/src/runs/background/async-job-tracker.ts +8 -6
  44. package/src/runs/background/async-resume.ts +13 -4
  45. package/src/runs/background/async-status.ts +15 -4
  46. package/src/runs/background/auto-drain.ts +20 -10
  47. package/src/runs/background/binary-bootstrap.ts +5 -0
  48. package/src/runs/background/chain-append.ts +1 -1
  49. package/src/runs/background/chain-root-attachment.ts +14 -33
  50. package/src/runs/background/notify.ts +74 -6
  51. package/src/runs/background/result-files.ts +8 -4
  52. package/src/runs/background/result-watcher.ts +19 -2
  53. package/src/runs/background/run-child-session.ts +20 -29
  54. package/src/runs/background/runner-aliases.ts +4 -33
  55. package/src/runs/background/runner-child-launch.ts +4 -1
  56. package/src/runs/background/runner-child-sessions.ts +2 -2
  57. package/src/runs/background/runner-http-dispatcher.ts +119 -0
  58. package/src/runs/background/scheduled-runs.ts +11 -5
  59. package/src/runs/background/stale-run-reconciler.ts +35 -11
  60. package/src/runs/background/subagent-runner.ts +396 -275
  61. package/src/runs/background/subagent-wait.ts +128 -23
  62. package/src/runs/background/wait-completions.ts +75 -27
  63. package/src/runs/background/wait-subscriptions.ts +9 -3
  64. package/src/runs/background/wait-tool.ts +4 -2
  65. package/src/runs/foreground/async-stop-action.ts +93 -3
  66. package/src/runs/foreground/execution.ts +91 -218
  67. package/src/runs/foreground/foreground-history.ts +2 -1
  68. package/src/runs/foreground/subagent-executor.ts +266 -80
  69. package/src/runs/shared/acceptance.ts +34 -10
  70. package/src/runs/shared/async-status-projection.ts +123 -33
  71. package/src/runs/shared/child-launch-plan.ts +15 -3
  72. package/src/runs/shared/child-launch.ts +19 -6
  73. package/src/runs/shared/child-runtime-config.ts +5 -0
  74. package/src/runs/shared/child-session.ts +94 -50
  75. package/src/runs/shared/child-tool-plan.ts +28 -16
  76. package/src/runs/shared/dynamic-fanout.ts +2 -2
  77. package/src/runs/shared/external-cli-contract.ts +11 -1
  78. package/src/runs/shared/external-cli-preflight.ts +6 -2
  79. package/src/runs/shared/herdr-connection.ts +134 -0
  80. package/src/runs/shared/herdr-external-adapters.ts +169 -0
  81. package/src/runs/shared/herdr-machine.ts +279 -0
  82. package/src/runs/shared/herdr-pi-protocol.ts +59 -0
  83. package/src/runs/shared/herdr-placed-run.ts +263 -0
  84. package/src/runs/shared/model-resolution-diagnostic.ts +76 -0
  85. package/src/runs/shared/{model-fallback.ts → model-resolution.ts} +22 -237
  86. package/src/runs/shared/model-scope.ts +1 -1
  87. package/src/runs/shared/nested-events.ts +11 -2
  88. package/src/runs/shared/parallel-utils.ts +7 -2
  89. package/src/runs/shared/pi-spawn.ts +1 -1
  90. package/src/runs/shared/subagent-prompt-runtime.ts +4 -2
  91. package/src/runs/shared/worktree-setup-command.ts +27 -4
  92. package/src/runs/shared/worktree.ts +3 -3
  93. package/src/shared/child-cache-retention.ts +43 -0
  94. package/src/shared/launch-contract.ts +6 -9
  95. package/src/shared/pruned-fork.ts +1 -1
  96. package/src/shared/required-child-extensions.ts +81 -0
  97. package/src/shared/settings.ts +5 -2
  98. package/src/shared/shortcuts.ts +0 -4
  99. package/src/shared/types.ts +70 -29
  100. package/src/slash/slash-commands.ts +0 -6
  101. package/src/slash/subagents-admin.ts +13 -9
  102. package/src/tui/render.ts +20 -10
  103. package/src/watchdog/child-status.ts +28 -36
  104. package/src/watchdog/lsp-diagnostics.ts +1 -1
  105. package/src/watchdog/model-selection.ts +1 -1
  106. package/src/watchdog/register-child.ts +10 -3
  107. package/src/watchdog/register-main.ts +20 -20
  108. package/src/watchdog/render.ts +1 -1
  109. package/src/watchdog/review.ts +14 -30
  110. package/src/watchdog/rules.ts +1 -1
  111. package/src/watchdog/runtime.ts +23 -12
  112. package/src/watchdog/settings.ts +3 -6
  113. package/src/watchdog/types.ts +3 -5
  114. package/src/watchdog/warning-format.ts +1 -1
  115. package/src/workflows/scripted-workflow.ts +42 -3
  116. package/src/workflows/workflow-receipt.ts +21 -3
  117. package/src/workflows/workflow-resources.ts +13 -2
  118. package/src/runs/shared/model-exclusions.ts +0 -374
  119. package/src/runs/shared/readonly-model-continuation.ts +0 -69
  120. package/src/runs/shared/readonly-session-evidence.ts +0 -307
@@ -1,5 +1,5 @@
1
1
  import type { ChildWatchdogProgress, ChildWatchdogWarningSummary } from "../shared/types.ts";
2
- import { SUBAGENT_WATCHDOG_WARNING_TYPE, type ResolvedWatchdogConfig, type WatchdogCadenceConfig, type WatchdogCategory, type WatchdogLspConfig } from "./types.ts";
2
+ import { WATCHDOG_WARNING_CATEGORIES, WATCHDOG_WARNING_IMPORTANCES, type ResolvedWatchdogConfig, type WatchdogCadenceConfig, type WatchdogLspConfig } from "./types.ts";
3
3
 
4
4
  export const CHILD_WATCHDOG_WARNING_LIMIT = 20;
5
5
 
@@ -16,7 +16,6 @@ export interface ChildWatchdogConfig {
16
16
  agentEndTimeoutMs: number;
17
17
  maxWarnings: number | null;
18
18
  model?: string;
19
- fallbackModels?: string[];
20
19
  thinking?: string | false;
21
20
  lsp: WatchdogLspConfig;
22
21
  stalemateRepeats: number;
@@ -34,6 +33,7 @@ export interface ChildWatchdogStatusEvent {
34
33
  phase: ChildWatchdogPhase;
35
34
  ts: number;
36
35
  reason?: string;
36
+ warning?: ChildWatchdogWarningSummary;
37
37
  }
38
38
 
39
39
  export type ChildWatchdogStateSnapshot = ChildWatchdogProgress;
@@ -48,7 +48,6 @@ export function resolveChildWatchdogConfig(input: {
48
48
  const enabled = input.config.enabled && (override?.enabled ?? input.config.children.enabled);
49
49
  if (!enabled) return undefined;
50
50
  const model = override?.model ?? input.config.children.model;
51
- const fallbackModels = override?.fallbackModels ?? input.config.children.fallbackModels;
52
51
  const thinking = override?.thinking ?? input.config.children.thinking;
53
52
  const cadence = override?.cadence ?? input.config.children.cadence ?? input.config.cadence;
54
53
  return {
@@ -59,7 +58,6 @@ export function resolveChildWatchdogConfig(input: {
59
58
  agentEndTimeoutMs: input.config.agentEndTimeoutMs,
60
59
  maxWarnings: input.config.maxWarnings,
61
60
  ...(model ? { model } : {}),
62
- ...(fallbackModels !== undefined ? { fallbackModels: [...fallbackModels] } : {}),
63
61
  ...(thinking !== undefined ? { thinking } : {}),
64
62
  lsp: { ...input.config.lsp },
65
63
  stalemateRepeats: input.config.stalemateRepeats,
@@ -130,6 +128,9 @@ function childConfigLsp(value: unknown): WatchdogLspConfig {
130
128
  export function decodeChildWatchdogConfig(raw: string | undefined): ChildWatchdogConfig | undefined {
131
129
  if (!raw) return undefined;
132
130
  const parsed = childConfigObject(JSON.parse(raw), "root");
131
+ if (Object.hasOwn(parsed, "fallbackModels")) {
132
+ throw new Error("Invalid child watchdog config: fallbackModels was removed; configure one model instead.");
133
+ }
133
134
  if (parsed.enabled === false) return undefined;
134
135
  if ("enabled" in parsed && parsed.enabled !== true) throw new Error("Invalid child watchdog config: enabled must be true or false.");
135
136
  const thinking = parsed.thinking;
@@ -140,10 +141,6 @@ export function decodeChildWatchdogConfig(raw: string | undefined): ChildWatchdo
140
141
  const agent = childConfigOptionalString(parsed, "agent");
141
142
  const childIndex = childConfigOptionalIndex(parsed, "childIndex");
142
143
  const model = childConfigOptionalString(parsed, "model");
143
- const fallbackModels = parsed.fallbackModels;
144
- if (fallbackModels !== undefined && (!Array.isArray(fallbackModels) || fallbackModels.some((value) => typeof value !== "string" || !value.trim()))) {
145
- throw new Error("Invalid child watchdog config: fallbackModels must be an array of non-empty strings.");
146
- }
147
144
  return {
148
145
  ...(runId ? { runId } : {}),
149
146
  ...(agent ? { agent } : {}),
@@ -152,7 +149,6 @@ export function decodeChildWatchdogConfig(raw: string | undefined): ChildWatchdo
152
149
  agentEndTimeoutMs: childConfigPositiveInteger(parsed, "agentEndTimeoutMs"),
153
150
  maxWarnings: childConfigNullableNonNegativeInteger(parsed, "maxWarnings"),
154
151
  ...(model ? { model } : {}),
155
- ...(fallbackModels !== undefined ? { fallbackModels: (fallbackModels as string[]).map((value) => value.trim()) } : {}),
156
152
  ...(thinking !== undefined ? { thinking: thinking as string | false } : {}),
157
153
  lsp: childConfigLsp(parsed.lsp),
158
154
  stalemateRepeats: childConfigPositiveInteger(parsed, "stalemateRepeats"),
@@ -163,6 +159,15 @@ export function decodeChildWatchdogConfig(raw: string | undefined): ChildWatchdo
163
159
  export function isChildWatchdogStatusEvent(value: unknown): value is ChildWatchdogStatusEvent {
164
160
  if (!value || typeof value !== "object") return false;
165
161
  const event = value as Partial<ChildWatchdogStatusEvent>;
162
+ const warningValue = event.warning;
163
+ const warning = warningValue as Partial<ChildWatchdogWarningSummary>;
164
+ const validWarning = warningValue === undefined || (warningValue !== null && typeof warningValue === "object" && !Array.isArray(warningValue)
165
+ && (warning.severity === "concern" || warning.severity === "blocker")
166
+ && typeof warning.importance === "string" && (WATCHDOG_WARNING_IMPORTANCES as readonly string[]).includes(warning.importance)
167
+ && typeof warning.category === "string" && (WATCHDOG_WARNING_CATEGORIES as readonly string[]).includes(warning.category)
168
+ && typeof warning.summary === "string" && typeof warning.evidence === "string" && typeof warning.recommendedAction === "string"
169
+ && typeof warning.addressed === "boolean" && typeof warning.stalemate === "boolean"
170
+ && (warning.displayedAt === undefined || typeof warning.displayedAt === "string"));
166
171
  return event.type === CHILD_WATCHDOG_STATUS_EVENT
167
172
  && typeof event.seq === "number"
168
173
  && Number.isInteger(event.seq)
@@ -170,7 +175,8 @@ export function isChildWatchdogStatusEvent(value: unknown): value is ChildWatchd
170
175
  && typeof event.ts === "number"
171
176
  && Number.isFinite(event.ts)
172
177
  && typeof event.phase === "string"
173
- && (CHILD_WATCHDOG_PHASES as readonly string[]).includes(event.phase);
178
+ && (CHILD_WATCHDOG_PHASES as readonly string[]).includes(event.phase)
179
+ && validWarning;
174
180
  }
175
181
 
176
182
  export function childWatchdogIsActive(snapshot: ChildWatchdogStateSnapshot | undefined): boolean {
@@ -190,40 +196,20 @@ export function acceptChildWatchdogEvent(input: {
190
196
  const eventIndex = input.event.childIndex ?? input.event.stepIndex;
191
197
  if (input.childIndex !== undefined && eventIndex !== input.childIndex) return undefined;
192
198
  if (input.current && input.event.seq <= input.current.seq) return undefined;
199
+ const warnings = input.event.warning
200
+ ? [...(input.current?.warnings ?? []), input.event.warning].slice(-CHILD_WATCHDOG_WARNING_LIMIT)
201
+ : input.current?.warnings;
193
202
  return {
194
203
  phase: input.event.phase,
195
204
  seq: input.event.seq,
196
205
  lastUpdate: input.event.ts,
197
206
  ...(input.event.reason ? { reason: input.event.reason } : {}),
198
- ...(input.current?.warnings?.length ? { warnings: input.current.warnings } : {}),
207
+ ...(warnings?.length ? { warnings } : {}),
199
208
  };
200
209
  }
201
210
 
202
- function childWatchdogWarningFromMessage(message: unknown): ChildWatchdogWarningSummary | undefined {
203
- const candidate = message as { role?: unknown; customType?: unknown; details?: Record<string, unknown> } | undefined;
204
- if (candidate?.role !== "custom" || candidate.customType !== SUBAGENT_WATCHDOG_WARNING_TYPE) return undefined;
205
- const details = candidate.details ?? {};
206
- if (details.severity !== "concern" && details.severity !== "blocker") return undefined;
207
- if (typeof details.summary !== "string" || typeof details.evidence !== "string" || typeof details.recommendedAction !== "string") return undefined;
208
- return {
209
- severity: details.severity,
210
- category: details.category as WatchdogCategory,
211
- summary: details.summary,
212
- evidence: details.evidence,
213
- recommendedAction: details.recommendedAction,
214
- ...(typeof details.displayedAt === "string" ? { displayedAt: details.displayedAt } : {}),
215
- addressed: false,
216
- stalemate: details.state === "stalemate",
217
- };
218
- }
219
-
220
- /** A watchdog warning message is appended; an assistant turn marks earlier warnings addressed. Undefined when unchanged. */
221
- export function applyChildWatchdogMessage(current: ChildWatchdogStateSnapshot | undefined, message: unknown, now = Date.now()): ChildWatchdogStateSnapshot | undefined {
222
- const warning = childWatchdogWarningFromMessage(message);
223
- if (warning) {
224
- const warnings = [...(current?.warnings ?? []), warning].slice(-CHILD_WATCHDOG_WARNING_LIMIT);
225
- return current ? { ...current, warnings } : { phase: "idle", seq: 0, lastUpdate: now, warnings };
226
- }
211
+ /** An assistant turn marks earlier warnings addressed. Undefined when unchanged. */
212
+ export function applyChildWatchdogMessage(current: ChildWatchdogStateSnapshot | undefined, message: unknown): ChildWatchdogStateSnapshot | undefined {
227
213
  if ((message as { role?: unknown } | undefined)?.role !== "assistant" || !current?.warnings?.some((entry) => !entry.addressed)) return undefined;
228
214
  return { ...current, warnings: current.warnings.map((entry) => entry.addressed ? entry : { ...entry, addressed: true }) };
229
215
  }
@@ -231,3 +217,9 @@ export function applyChildWatchdogMessage(current: ChildWatchdogStateSnapshot |
231
217
  export function unresolvedChildWatchdogBlockers(progress: Pick<ChildWatchdogProgress, "warnings"> | undefined): ChildWatchdogWarningSummary[] {
232
218
  return (progress?.warnings ?? []).filter((warning) => warning.severity === "blocker" && (!warning.addressed || warning.stalemate));
233
219
  }
220
+
221
+ export function childWatchdogProgressForModel(progress: ChildWatchdogProgress | undefined): ChildWatchdogProgress | undefined {
222
+ if (!progress) return undefined;
223
+ const warnings = (progress.warnings ?? []).filter((warning) => warning.importance === "high");
224
+ return { ...progress, warnings: warnings.length ? warnings : undefined };
225
+ }
@@ -216,7 +216,7 @@ export function watchdogWarningFromLspDiagnostics(result: WatchdogLspResult): Wa
216
216
  return {
217
217
  severity,
218
218
  category: "correctness",
219
- confidence: "high",
219
+ importance: "high",
220
220
  source: "lsp",
221
221
  summary: `LSP found ${count} ${kind}${count === 1 ? "" : "s"} in changed ${count === 1 ? "file" : "files"}.`,
222
222
  evidence: evidence || formatDiagnostic(primary),
@@ -1,5 +1,5 @@
1
1
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import { normalizeModelSegment, resolveModelCandidate } from "../runs/shared/model-fallback.ts";
2
+ import { normalizeModelSegment, resolveModelCandidate } from "../runs/shared/model-resolution.ts";
3
3
  import type { WatchdogEndpointConfig } from "./types.ts";
4
4
  import {
5
5
  getSupportedThinkingLevels,
@@ -10,7 +10,8 @@ import {
10
10
  type ChildWatchdogPhase,
11
11
  type ChildWatchdogStatusEvent,
12
12
  } from "./child-status.ts";
13
- import type { ResolvedWatchdogConfig, WatchdogWarningDetails } from "./types.ts";
13
+ import type { ChildWatchdogWarningSummary } from "../shared/types.ts";
14
+ import { SUBAGENT_WATCHDOG_WARNING_TYPE, type ResolvedWatchdogConfig, type WatchdogWarningDetails } from "./types.ts";
14
15
 
15
16
  export function childResolvedConfig(config: ChildWatchdogConfig): ResolvedWatchdogConfig {
16
17
  return {
@@ -21,7 +22,6 @@ export function childResolvedConfig(config: ChildWatchdogConfig): ResolvedWatchd
21
22
  main: {
22
23
  enabled: true,
23
24
  ...(config.model ? { model: config.model } : {}),
24
- ...(config.fallbackModels !== undefined ? { fallbackModels: [...config.fallbackModels] } : {}),
25
25
  ...(config.thinking !== undefined ? { thinking: config.thinking } : {}),
26
26
  },
27
27
  stalemateRepeats: config.stalemateRepeats,
@@ -58,7 +58,7 @@ export function registerChildWatchdog(
58
58
  let currentContext: ExtensionContext | undefined;
59
59
  let diffBaseline: WatchdogDiffBaseline | undefined;
60
60
  let seq = 0;
61
- const emitStatus = (phase: ChildWatchdogPhase, reason?: string): void => {
61
+ const emitStatus = (phase: ChildWatchdogPhase, reason?: string, warning?: ChildWatchdogWarningSummary): void => {
62
62
  writeStatus({
63
63
  type: CHILD_WATCHDOG_STATUS_EVENT,
64
64
  ...(childConfig.runId ? { runId: childConfig.runId } : {}),
@@ -68,6 +68,7 @@ export function registerChildWatchdog(
68
68
  phase,
69
69
  ts: Date.now(),
70
70
  ...(reason ? { reason } : {}),
71
+ ...(warning ? { warning } : {}),
71
72
  });
72
73
  };
73
74
  const resolved = childResolvedConfig(childConfig);
@@ -78,8 +79,14 @@ export function registerChildWatchdog(
78
79
  reviewChangesOnly: true,
79
80
  displayWarning: (details, options) => {
80
81
  const childDetails = childWarningDetails(details, childConfig);
82
+ emitStatus("reviewing", undefined, { severity: childDetails.severity, importance: childDetails.importance, category: childDetails.category, summary: childDetails.summary, evidence: childDetails.evidence, recommendedAction: childDetails.recommendedAction, ...(childDetails.displayedAt ? { displayedAt: childDetails.displayedAt } : {}), addressed: false, stalemate: childDetails.state === "stalemate" });
81
83
  pi.sendMessage(createWatchdogWarningMessage(childDetails, { display: true, details: childDetails }), options);
82
84
  },
85
+ displayUserWarning: (details) => {
86
+ const childDetails = childWarningDetails(details, childConfig);
87
+ emitStatus("reviewing", undefined, { severity: childDetails.severity, importance: childDetails.importance, category: childDetails.category, summary: childDetails.summary, evidence: childDetails.evidence, recommendedAction: childDetails.recommendedAction, ...(childDetails.displayedAt ? { displayedAt: childDetails.displayedAt } : {}), addressed: false, stalemate: childDetails.state === "stalemate" });
88
+ pi.appendEntry(SUBAGENT_WATCHDOG_WARNING_TYPE, childDetails);
89
+ },
83
90
  });
84
91
  const rememberContext = (ctx: ExtensionContext) => {
85
92
  currentContext = ctx;
@@ -63,16 +63,11 @@ function mainThinkingLine(snapshot: ReturnType<MainWatchdogRuntime["getSnapshot"
63
63
  }
64
64
 
65
65
  function mainModelLine(snapshot: ReturnType<MainWatchdogRuntime["getSnapshot"]>, ctx: ExtensionContext): string {
66
- const fallbacks = fallbackLine(snapshot.config.main.fallbackModels);
67
66
  if (snapshot.config.main.model) {
68
67
  const source = snapshot.sessionModelOverride?.model ? "session override" : "configured";
69
- return `Main model: ${splitKnownThinkingSuffix(snapshot.config.main.model).baseModel} (${source})${fallbacks}`;
68
+ return `Main model: ${splitKnownThinkingSuffix(snapshot.config.main.model).baseModel} (${source})`;
70
69
  }
71
- return `Main model: ${currentSessionModelLine(ctx)}${fallbacks}`;
72
- }
73
-
74
- function fallbackLine(models: string[] | undefined): string {
75
- return models === undefined ? "" : ` · fallbacks ${models.length ? models.join(" → ") : "none"}`;
70
+ return `Main model: ${currentSessionModelLine(ctx)}`;
76
71
  }
77
72
 
78
73
  function childrenLine(snapshot: ReturnType<MainWatchdogRuntime["getSnapshot"]>): string {
@@ -85,12 +80,11 @@ function childrenLine(snapshot: ReturnType<MainWatchdogRuntime["getSnapshot"]>):
85
80
  const bits = [agent];
86
81
  if (override.enabled !== undefined) bits.push(boolLabel(override.enabled));
87
82
  if (override.model) bits.push(splitKnownThinkingSuffix(override.model).baseModel);
88
- if (override.fallbackModels !== undefined) bits.push(fallbackLine(override.fallbackModels));
89
83
  if (override.thinking !== undefined) bits.push(`thinking ${override.thinking === false ? "off" : override.thinking}`);
90
84
  return bits.join(" ");
91
85
  }).join("; ")}`
92
86
  : "";
93
- return `Children: ${boolLabel(snapshot.config.enabled && children.enabled)} · model ${model}${fallbackLine(children.fallbackModels)} · thinking ${thinking}${overrideText}`;
87
+ return `Children: ${boolLabel(snapshot.config.enabled && children.enabled)} · model ${model} · thinking ${thinking}${overrideText}`;
94
88
  }
95
89
 
96
90
  function recommendationLine(snapshot: ReturnType<MainWatchdogRuntime["getSnapshot"]>, ctx: ExtensionContext): string {
@@ -160,10 +154,10 @@ export function buildWatchdogStatus(snapshot: ReturnType<MainWatchdogRuntime["ge
160
154
  return lines.join("\n");
161
155
  }
162
156
 
163
- function parseTestCommand(input: string): { severity: "concern" | "blocker"; text: string } | undefined {
164
- const match = input.match(/^test\s+(concern|blocker)\s+([\s\S]+)$/);
157
+ function parseTestCommand(input: string): { severity: "concern" | "blocker"; importance: "low" | "medium" | "high"; text: string } | undefined {
158
+ const match = input.match(/^test\s+(concern|blocker)\s+(low|medium|high)\s+([\s\S]+)$/);
165
159
  if (!match) return undefined;
166
- return { severity: match[1] as "concern" | "blocker", text: match[2]!.trim() };
160
+ return { severity: match[1] as "concern" | "blocker", importance: match[2] as "low" | "medium" | "high", text: match[3]!.trim() };
167
161
  }
168
162
 
169
163
  function formatThinking(value: ThinkingLevel | false | undefined): string {
@@ -231,11 +225,11 @@ function buildCheckText(runtime: MainWatchdogRuntime, ctx: ExtensionCommandConte
231
225
  return lines.join("\n");
232
226
  }
233
227
 
234
- function createTestWarning(severity: "concern" | "blocker", text: string): WatchdogWarning {
228
+ function createTestWarning(severity: "concern" | "blocker", importance: "low" | "medium" | "high", text: string): WatchdogWarning {
235
229
  return {
236
230
  severity,
237
231
  category: "other",
238
- confidence: "high",
232
+ importance,
239
233
  source: "main",
240
234
  state: "displayed",
241
235
  summary: text,
@@ -366,15 +360,14 @@ async function handleWatchdogCommand(
366
360
  const test = parseTestCommand(input);
367
361
  if (test) {
368
362
  if (!test.text) {
369
- ctx.ui.notify("Usage: /subagents-watchdog test concern|blocker <text>", "error");
363
+ ctx.ui.notify("Usage: /subagents-watchdog test concern|blocker low|medium|high <text>", "error");
370
364
  return;
371
365
  }
372
- const warning = createTestWarning(test.severity, test.text);
373
- const details = runtime.recordDisplayedWarning(warning);
374
- pi.sendMessage(createWatchdogWarningMessage(details, { display: true, details }));
366
+ const warning = createTestWarning(test.severity, test.importance, test.text);
367
+ runtime.displayRecordedWarning(warning);
375
368
  return;
376
369
  }
377
- ctx.ui.notify(`Usage: /subagents-watchdog [status|on|off|session on|session off|recommend-model|model recommended|model <provider/model[:thinking]>|model inherit|thinking ${THINKING_LEVELS.join("|")}|thinking inherit|session model recommended|check|test concern <text>|test blocker <text>]`, "error");
370
+ ctx.ui.notify(`Usage: /subagents-watchdog [status|on|off|session on|session off|recommend-model|model recommended|model <provider/model[:thinking]>|model inherit|thinking ${THINKING_LEVELS.join("|")}|thinking inherit|session model recommended|check|test concern|blocker low|medium|high <text>]`, "error");
378
371
  }
379
372
 
380
373
  export function registerMainWatchdog(pi: ExtensionAPI, options: RegisterMainWatchdogOptions = {}): MainWatchdogRuntime {
@@ -388,6 +381,7 @@ export function registerMainWatchdog(pi: ExtensionAPI, options: RegisterMainWatc
388
381
  reviewDescription: options.review ? "injected seam" : "real model review",
389
382
  reviewChangesOnly: true,
390
383
  displayWarning: (details, options) => pi.sendMessage(createWatchdogWarningMessage(details, { display: true, details }), options),
384
+ displayUserWarning: (details) => pi.appendEntry(SUBAGENT_WATCHDOG_WARNING_TYPE, details),
391
385
  displayClarification: (content) => pi.sendMessage({ customType: "subagent_watchdog_clarification", content, display: true }, { deliverAs: "steer", triggerTurn: true }),
392
386
  });
393
387
 
@@ -401,6 +395,12 @@ export function registerMainWatchdog(pi: ExtensionAPI, options: RegisterMainWatc
401
395
  }
402
396
  return renderWatchdogWarning(details, renderOptions, theme);
403
397
  });
398
+ pi.registerEntryRenderer<WatchdogWarningDetails>(SUBAGENT_WATCHDOG_WARNING_TYPE, (entry, renderOptions, theme) => {
399
+ const details = entry.data as WatchdogWarningDetails | undefined;
400
+ return details?.summary && details.evidence && details.recommendedAction
401
+ ? renderWatchdogWarning(details, renderOptions, theme)
402
+ : undefined;
403
+ });
404
404
 
405
405
  pi.registerCommand("subagents-watchdog", {
406
406
  description: "Show or toggle the default-off subagent watchdog",
@@ -435,7 +435,7 @@ export function registerMainWatchdog(pi: ExtensionAPI, options: RegisterMainWatc
435
435
  });
436
436
  pi.on("session_before_switch", () => runtime.reset("session switch", { clearReviewInputSignature: true, clearLspLedger: true, clearScope: true }));
437
437
  pi.on("session_before_fork", () => runtime.reset("session fork", { clearReviewInputSignature: true, clearLspLedger: true, clearScope: true }));
438
- pi.on("session_compact", () => runtime.reset("session compact", { clearScope: true }));
438
+ pi.on("session_compact", () => runtime.reset("session compact", { clearActivity: true }));
439
439
  pi.on("session_shutdown", () => {
440
440
  currentContext = undefined;
441
441
  runtime.dispose();
@@ -26,7 +26,7 @@ export function formatWatchdogWarningRenderText(warning: WatchdogWarningDetails)
26
26
  `Subagent watchdog ${subject}${labels.length ? ` (${labels.join(", ")})` : ""}: ${warning.summary}`,
27
27
  `Evidence: ${warning.evidence}`,
28
28
  `Recommended action: ${warning.recommendedAction}`,
29
- `Category: ${titleCase(warning.category)} · Source: ${warning.source}${warning.agent ? ` · Agent: ${warning.agent}` : ""}${warning.runId ? ` · Run: ${warning.runId}` : ""}`,
29
+ `Importance: ${titleCase(warning.importance)} · Category: ${titleCase(warning.category)} · Source: ${warning.source}${warning.agent ? ` · Agent: ${warning.agent}` : ""}${warning.runId ? ` · Run: ${warning.runId}` : ""}`,
30
30
  ];
31
31
  if (warning.state === "failed" && warning.error) lines.push(`Failure: ${warning.error}`);
32
32
  if (warning.state === "stalemate" && warning.stalemateRepeats !== undefined) {
@@ -3,7 +3,7 @@ import { createReadOnlyTools, convertToLlm, type ExtensionContext } from "@earen
3
3
  import { streamSimple } from "@earendil-works/pi-ai/compat";
4
4
  import type { Model, ProviderHeaders } from "@earendil-works/pi-ai";
5
5
  import { Type, type Static } from "typebox";
6
- import { buildModelCandidates, isContextOverflow, isRetryableModelFailureAttempt, resolveModelCandidate } from "../runs/shared/model-fallback.ts";
6
+ import { resolveModelCandidate } from "../runs/shared/model-resolution.ts";
7
7
  import { agentStreamOptions } from "../shared/agent-stream-options.ts";
8
8
  import { opencodeSessionHeaders } from "../shared/opencode-session-headers.ts";
9
9
  import { resolveEffectiveThinking, splitKnownThinkingSuffix, THINKING_LEVELS, toModelInfo } from "../shared/model-info.ts";
@@ -12,11 +12,11 @@ import { loadWatchdogGuidance } from "./guidance.ts";
12
12
  import { boundWatchdogReviewText, type WatchdogReviewFunction, type WatchdogReviewRequest } from "./runtime.ts";
13
13
  import {
14
14
  WATCHDOG_WARNING_CATEGORIES,
15
- WATCHDOG_WARNING_CONFIDENCES,
15
+ WATCHDOG_WARNING_IMPORTANCES,
16
16
  WATCHDOG_WARNING_SEVERITIES,
17
17
  type ResolvedWatchdogConfig,
18
18
  type WatchdogCategory,
19
- type WatchdogConfidence,
19
+ type WatchdogImportance,
20
20
  type WatchdogSeverity,
21
21
  type WatchdogWarning,
22
22
  } from "./types.ts";
@@ -27,11 +27,11 @@ const WatchdogAskParams = Type.Object({ question: Type.String(), evidence: Type.
27
27
 
28
28
  const WatchdogWarnParams = Type.Object({
29
29
  severity: Type.String({ enum: WATCHDOG_WARNING_SEVERITIES, description: "concern for actionable risk, blocker for a likely wrong or unsafe outcome" }),
30
+ importance: Type.String({ enum: WATCHDOG_WARNING_IMPORTANCES, description: "low or medium for user-only visibility; high when the parent model must receive the finding" }),
30
31
  summary: Type.String({ description: "One concise sentence naming the issue." }),
31
32
  evidence: Type.String({ description: "Specific evidence from the turn delta or inspected files." }),
32
33
  recommendedAction: Type.String({ description: "Specific action the parent should take before accepting or continuing." }),
33
34
  category: Type.Optional(Type.String({ enum: WATCHDOG_WARNING_CATEGORIES })),
34
- confidence: Type.Optional(Type.String({ enum: WATCHDOG_WARNING_CONFIDENCES })),
35
35
  }, { additionalProperties: false });
36
36
 
37
37
  type WatchdogWarnParams = Static<typeof WatchdogWarnParams>;
@@ -182,8 +182,8 @@ function nonEmptyString(value: string, field: string): string {
182
182
  function toWatchdogWarning(params: WatchdogWarnParams): WatchdogWarning {
183
183
  return {
184
184
  severity: params.severity as WatchdogSeverity,
185
+ importance: params.importance as WatchdogImportance,
185
186
  category: (params.category ?? "other") as WatchdogCategory,
186
- confidence: (params.confidence ?? "medium") as WatchdogConfidence,
187
187
  source: "main",
188
188
  summary: nonEmptyString(params.summary, "summary"),
189
189
  evidence: nonEmptyString(params.evidence, "evidence"),
@@ -197,7 +197,7 @@ function createWatchdogWarnTool(request: WatchdogReviewRequest): AgentTool<typeo
197
197
  label: "Watchdog warning",
198
198
  description: [
199
199
  "Emit one actionable main-session watchdog warning.",
200
- "Use only for medium/high confidence concerns or blockers that the parent should consider before accepting the work.",
200
+ "Set importance explicitly: low or medium for user-only visibility, high only when the parent model must receive the finding.",
201
201
  "Do not use for nits, praise, informational notes, or clean reviews.",
202
202
  ].join(" "),
203
203
  parameters: WatchdogWarnParams,
@@ -227,8 +227,8 @@ export function buildWatchdogSystemPrompt(ctx: Pick<ExtensionContext, "cwd">, op
227
227
  options.hasScope ? "Use the Current scope record alongside supplied activity evidence; an unrelated user question does not cancel older authorized work." : undefined,
228
228
  `You are read-only. You may use ${options.hasDiff ? "read, grep, find, ls, and watchdog_diff (the full repo diff since the session baseline; pass a path to narrow it)" : "read, grep, find, and ls"}. Do not edit files, run shell commands, spawn agents, or mutate state.`,
229
229
  "Emit warnings only by calling watchdog_warn. Freeform assistant text is ignored and must not be used to report warnings.",
230
- "Emit only medium/high confidence actionable concerns or blockers: missed user constraints, correctness risks, test gaps that matter, unsafe changes, stale facts, loop risks, or scope drift.",
231
- "Do not emit nits, style preferences, low-confidence guesses, informational notes, praise, or summaries.",
230
+ "Emit only actionable concerns or blockers: missed user constraints, correctness risks, test gaps that matter, unsafe changes, stale facts, loop risks, or scope drift.",
231
+ "Do not emit nits, style preferences, unsupported guesses, informational notes, praise, or summaries.",
232
232
  "If the turn is clean, call no tools and end normally.",
233
233
  "Use severity='blocker' only when the issue should stop acceptance until addressed; otherwise use severity='concern'.",
234
234
  guidance ? `\nStanding instructions from WATCHDOG.md (project first, then user):\n${guidance}` : undefined,
@@ -256,32 +256,17 @@ export function createMainWatchdogReview(provider: WatchdogContextProvider, opti
256
256
  if (!ctx) throw new Error("Main watchdog review cannot run without an active Pi extension context.");
257
257
  const aborted = () => ctx.signal?.aborted || request.signal?.aborted;
258
258
  if (aborted()) return { stopReason: "aborted" };
259
- const inherited = !request.config.main.model && ctx.model ? fullModelId(ctx.model) : undefined;
260
- const candidates = request.config.main.fallbackModels?.length
261
- ? buildModelCandidates(request.config.main.model ?? inherited, request.config.main.fallbackModels,
262
- ctx.modelRegistry.getAvailable().map(toModelInfo), ctx.model?.provider, { primaryModelFromParent: Boolean(inherited) })
263
- : [request.config.main.model];
264
- for (let index = 0; index < candidates.length; index++) {
265
- if (aborted()) return { stopReason: "aborted" };
266
- const candidate = candidates[index];
267
- const config = { ...request.config, main: { ...request.config.main, model: candidate === inherited ? undefined : candidate } };
268
- try {
269
- const attempt = await runWatchdogAttempt(ctx, { ...request, config }, options);
270
- if (aborted()) return { stopReason: "aborted" };
271
- if (!attempt.retryable || index === candidates.length - 1) return attempt.result;
272
- } catch (error) {
273
- if (aborted()) return { stopReason: "aborted" };
274
- if (!(error instanceof WatchdogAuthError)) throw error;
275
- if (isContextOverflow(error.message) || !isRetryableModelFailureAttempt({ error: error.message }) || index === candidates.length - 1) throw error.cause ?? error;
276
- }
259
+ try {
260
+ return (await runWatchdogAttempt(ctx, request, options)).result;
261
+ } catch (error) {
262
+ if (error instanceof WatchdogAuthError) throw error.cause ?? error;
263
+ throw error;
277
264
  }
278
- throw new Error("No usable watchdog model candidates.");
279
265
  };
280
266
  }
281
267
 
282
268
  async function runWatchdogAttempt(ctx: ExtensionContext, request: WatchdogReviewRequest, options: CreateMainWatchdogReviewOptions): Promise<{
283
269
  result: Awaited<ReturnType<WatchdogReviewFunction>>;
284
- retryable?: boolean;
285
270
  }> {
286
271
  const selection = await resolveWatchdogReviewModel(ctx, request.config, {
287
272
  currentThinkingLevel: options.getThinkingLevel?.(),
@@ -373,7 +358,6 @@ async function runWatchdogAttempt(ctx: ExtensionContext, request: WatchdogReview
373
358
  const stopReason = reason === "error" || reason === "aborted" || reason === "length" ? reason : "stop";
374
359
  const error = terminal && "errorMessage" in terminal && typeof terminal.errorMessage === "string" ? terminal.errorMessage : undefined;
375
360
  return {
376
- result: clarification ? { clarification } : { stopReason },
377
- retryable: !clarification && stopReason === "error" && !isContextOverflow(error) && isRetryableModelFailureAttempt({ error, messages: agent.state.messages, toolCount }),
361
+ result: clarification ? { clarification } : error ? { stopReason, errorMessage: error } : { stopReason },
378
362
  };
379
363
  }
@@ -51,7 +51,7 @@ export function evaluateLaunchRule(rules: WatchdogRulesConfig | undefined, agent
51
51
  }
52
52
 
53
53
  export function ruleViolationWarning(violation: WatchdogRuleViolation): WatchdogWarning {
54
- return { severity: "concern", category: "missed-constraint", confidence: "high", source: "main", ...violation };
54
+ return { severity: "concern", category: "missed-constraint", importance: "high", source: "main", ...violation };
55
55
  }
56
56
 
57
57
  export function applyWatchdogLaunchRules(input: { cwd: string; agent: string; model?: string; warn?: (violation: WatchdogRuleViolation) => void }): string | undefined {
@@ -14,6 +14,7 @@ import { WatchdogScopeArtifact } from "./scope.ts";
14
14
  import { resolveWatchdogConfig } from "./settings.ts";
15
15
  import { formatWatchdogOrchestrationActivity, formatWatchdogTurnDelta } from "./turn-delta.ts";
16
16
  import {
17
+ WATCHDOG_WARNING_IMPORTANCES,
17
18
  type ResolvedWatchdogConfig,
18
19
  type WatchdogEndpointConfig,
19
20
  type WatchdogLspRuntimeSnapshot,
@@ -31,6 +32,8 @@ type ReviewStopReason = "stop" | "error" | "aborted" | "length";
31
32
  export interface WatchdogReviewResult {
32
33
  warnings?: WatchdogWarning[];
33
34
  stopReason?: ReviewStopReason;
35
+ /** Provider error text for a failed review, surfaced in status `Last error`. */
36
+ errorMessage?: string;
34
37
  clarification?: { question: string; evidence: string };
35
38
  }
36
39
 
@@ -83,6 +86,7 @@ interface MainWatchdogRuntimeOptions {
83
86
  review?: WatchdogReviewFunction;
84
87
  reviewDescription?: string;
85
88
  displayWarning?: (warning: WatchdogWarningDetails, options?: WatchdogWarningSendOptions) => void;
89
+ displayUserWarning?: (warning: WatchdogWarningDetails) => void;
86
90
  /** Supplying this main-session delivery capability gates clarification (children omit it). */
87
91
  displayClarification?: (content: string) => void;
88
92
  reviewChangesOnly?: boolean;
@@ -130,6 +134,7 @@ export class MainWatchdogRuntime {
130
134
  private readonly reviewConnected: boolean;
131
135
  private readonly reviewDescription: string;
132
136
  private readonly displayWarning: ((warning: WatchdogWarningDetails, options?: WatchdogWarningSendOptions) => void) | undefined;
137
+ private readonly displayUserWarning: ((warning: WatchdogWarningDetails) => void) | undefined;
133
138
  private readonly reviewChangesOnly: boolean;
134
139
  private readonly lspDiagnostics: WatchdogLspDiagnosticsFunction;
135
140
  private readonly repoChangeSignature: typeof computeWatchdogRepoChangeSignature;
@@ -187,6 +192,7 @@ export class MainWatchdogRuntime {
187
192
  this.reviewConnected = Boolean(options.review);
188
193
  this.reviewDescription = options.reviewDescription ?? (options.review ? "injected seam" : "not wired");
189
194
  this.displayWarning = options.displayWarning;
195
+ this.displayUserWarning = options.displayUserWarning;
190
196
  this.displayClarification = options.displayClarification;
191
197
  this.reviewChangesOnly = options.reviewChangesOnly === true;
192
198
  this.lspDiagnostics = options.lspDiagnostics ?? collectWatchdogLspDiagnostics;
@@ -268,7 +274,7 @@ export class MainWatchdogRuntime {
268
274
  return this.getSnapshot();
269
275
  }
270
276
 
271
- reset(_reason = "reset", options: { clearReviewInputSignature?: boolean; resetChangeSignature?: boolean; clearLspLedger?: boolean; clearScope?: boolean } = {}): void {
277
+ reset(_reason = "reset", options: { clearReviewInputSignature?: boolean; resetChangeSignature?: boolean; clearLspLedger?: boolean; clearScope?: boolean; clearActivity?: boolean } = {}): void {
272
278
  this.activeReviewAbortController?.abort();
273
279
  this.abortActiveAgentEnd();
274
280
  this.epoch++;
@@ -290,10 +296,8 @@ export class MainWatchdogRuntime {
290
296
  this.lspLedger.reset();
291
297
  this.lastLspSnapshot = undefined;
292
298
  }
293
- if (options.clearScope) {
294
- this.scope.reset();
295
- this.clearActivity();
296
- }
299
+ if (options.clearScope) this.scope.reset();
300
+ if (options.clearScope || options.clearActivity) this.clearActivity();
297
301
  if (options.clearReviewInputSignature) this.lastReviewInputSignature = undefined;
298
302
  if (options.resetChangeSignature) this.resetRepoChangeBaseline({ reviewed: true });
299
303
  this.guard.reset();
@@ -467,13 +471,13 @@ export class MainWatchdogRuntime {
467
471
  this.ruleWarningsThisRun.add(violation.summary);
468
472
  const details = normalizeWatchdogWarningDetails(ruleViolationWarning(violation), { state: "displayed", displayedAt: new Date().toISOString() });
469
473
  this.lastWarning = details;
470
- this.displayWarning?.(details, { deliverAs: "steer" });
474
+ this.routeWarning(details, { deliverAs: "steer" });
471
475
  }
472
476
 
473
- recordDisplayedWarning(warning: WatchdogWarning): WatchdogWarningDetails {
477
+ displayRecordedWarning(warning: WatchdogWarning): void {
474
478
  const details = normalizeWatchdogWarningDetails(warning, { state: "displayed", source: warning.source ?? "main" });
475
479
  this.lastWarning = details;
476
- return details;
480
+ this.routeWarning(details);
477
481
  }
478
482
 
479
483
  getSnapshot(cwd?: string): WatchdogRuntimeSnapshot {
@@ -536,7 +540,13 @@ export class MainWatchdogRuntime {
536
540
  }
537
541
 
538
542
  private warningMeetsThreshold(warning: WatchdogWarning): boolean {
539
- return this.configResult.config.severityThreshold === "concern" || warning.severity === "blocker";
543
+ return (WATCHDOG_WARNING_IMPORTANCES as readonly string[]).includes(warning.importance)
544
+ && (this.configResult.config.severityThreshold === "concern" || warning.severity === "blocker");
545
+ }
546
+
547
+ private routeWarning(details: WatchdogWarningDetails, options?: WatchdogWarningSendOptions): void {
548
+ if (details.importance === "high") this.displayWarning?.(details, options);
549
+ else this.displayUserWarning?.(details);
540
550
  }
541
551
 
542
552
  private acceptWarning(epoch: number, reviewId: number, warning: WatchdogWarning): boolean {
@@ -658,7 +668,8 @@ export class MainWatchdogRuntime {
658
668
  }
659
669
  for (const warning of result.warnings ?? []) this.acceptWarning(reviewEpoch, reviewId, warning);
660
670
  if (result.stopReason && result.stopReason !== "stop") {
661
- this.fail(`Watchdog review ended with stop reason '${result.stopReason}'.`);
671
+ const detail = result.errorMessage?.trim() ? ` ${boundWatchdogReviewText(result.errorMessage.trim(), 600)}` : "";
672
+ this.fail(`Watchdog review ended with stop reason '${result.stopReason}'.${detail}`);
662
673
  return "completed";
663
674
  }
664
675
  this.displayAcceptedReviewWarning(options.correction);
@@ -690,7 +701,7 @@ export class MainWatchdogRuntime {
690
701
  };
691
702
  if (correction) {
692
703
  this.lastWarning = details;
693
- this.displayWarning?.(details, { deliverAs: "steer" });
704
+ this.routeWarning(details, { deliverAs: "steer" });
694
705
  return;
695
706
  }
696
707
  this.deliverBoundaryWarning(details);
@@ -711,7 +722,7 @@ export class MainWatchdogRuntime {
711
722
  : details;
712
723
  this.stalemate = stalemate;
713
724
  this.lastWarning = delivered;
714
- this.displayWarning?.(delivered, stalemate ? { triggerTurn: false } : undefined);
725
+ this.routeWarning(delivered, stalemate ? { triggerTurn: false } : undefined);
715
726
  }
716
727
 
717
728
  // The previous boundary finding is a repeat to count, not a duplicate, until stalemate.
@@ -111,9 +111,9 @@ const ROLE_MODEL_RULE_FIELDS = new Set(["allow", "deny", "note"]);
111
111
  const GUIDANCE_FIELDS = new Set(["watchdogMd"]);
112
112
  const SCOPE_FIELDS = new Set(["enabled"]);
113
113
  const CADENCE_FIELDS = new Set(["everyNTools"]);
114
- const ENDPOINT_FIELDS = new Set(["enabled", "model", "fallbackModels", "thinking"]);
115
- const CHILDREN_FIELDS = new Set(["enabled", "model", "fallbackModels", "thinking", "watchdogTailTimeoutMs", "cadence", "overrides"]);
116
- const CHILD_OVERRIDE_FIELDS = new Set(["enabled", "model", "fallbackModels", "thinking", "cadence"]);
114
+ const ENDPOINT_FIELDS = new Set(["enabled", "model", "thinking"]);
115
+ const CHILDREN_FIELDS = new Set(["enabled", "model", "thinking", "watchdogTailTimeoutMs", "cadence", "overrides"]);
116
+ const CHILD_OVERRIDE_FIELDS = new Set(["enabled", "model", "thinking", "cadence"]);
117
117
  const LSP_FIELDS = new Set(["enabled", "timeoutMs", "maxFiles", "maxDiagnostics"]);
118
118
 
119
119
  function cloneDefaultConfig(): ResolvedWatchdogConfig {
@@ -221,7 +221,6 @@ function parseEndpointPatch(value: unknown, field: string, meta: ParseMeta): Wat
221
221
  const input = parseObject(value, field, meta);
222
222
  assertKnownFields(input, ENDPOINT_FIELDS, field, meta);
223
223
  const patch: WatchdogEndpointPatch = {};
224
- if ("fallbackModels" in input) patch.fallbackModels = parseStringList(input.fallbackModels, `${field}.fallbackModels`, meta);
225
224
  if ("enabled" in input) patch.enabled = parseBoolean(input.enabled, `${field}.enabled`, meta);
226
225
  if ("model" in input) patch.model = parseNonEmptyString(input.model, `${field}.model`, meta);
227
226
  if ("thinking" in input) patch.thinking = parseThinking(input.thinking, `${field}.thinking`, meta);
@@ -232,7 +231,6 @@ function parseChildOverridePatch(value: unknown, field: string, meta: ParseMeta)
232
231
  const input = parseObject(value, field, meta);
233
232
  assertKnownFields(input, CHILD_OVERRIDE_FIELDS, field, meta);
234
233
  const patch: WatchdogChildOverridePatch = {};
235
- if ("fallbackModels" in input) patch.fallbackModels = parseStringList(input.fallbackModels, `${field}.fallbackModels`, meta);
236
234
  if ("enabled" in input) patch.enabled = parseBoolean(input.enabled, `${field}.enabled`, meta);
237
235
  if ("model" in input) patch.model = parseNonEmptyString(input.model, `${field}.model`, meta);
238
236
  if ("thinking" in input) patch.thinking = parseThinking(input.thinking, `${field}.thinking`, meta);
@@ -244,7 +242,6 @@ function parseChildrenPatch(value: unknown, field: string, meta: ParseMeta): Wat
244
242
  const input = parseObject(value, field, meta);
245
243
  assertKnownFields(input, CHILDREN_FIELDS, field, meta);
246
244
  const patch: WatchdogChildrenPatch = {};
247
- if ("fallbackModels" in input) patch.fallbackModels = parseStringList(input.fallbackModels, `${field}.fallbackModels`, meta);
248
245
  if ("enabled" in input) patch.enabled = parseBoolean(input.enabled, `${field}.enabled`, meta);
249
246
  if ("model" in input) patch.model = parseNonEmptyString(input.model, `${field}.model`, meta);
250
247
  if ("thinking" in input) patch.thinking = parseThinking(input.thinking, `${field}.thinking`, meta);