@duckmind/dm-windows-x64 0.61.4 → 0.61.9

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 (279) hide show
  1. package/dm.exe +0 -0
  2. package/extensions/.dm-extensions.json +211 -67
  3. package/extensions/dm-subagents/agents/claude-code-writer.md +15 -0
  4. package/extensions/dm-subagents/agents/claude-code.md +15 -0
  5. package/extensions/dm-subagents/agents/codex-exec-writer.md +15 -0
  6. package/extensions/dm-subagents/agents/codex-exec.md +15 -0
  7. package/extensions/dm-subagents/agents/cursor-agent-writer.md +14 -0
  8. package/extensions/dm-subagents/agents/cursor-agent.md +14 -0
  9. package/extensions/dm-subagents/agents/delegate.md +3 -2
  10. package/extensions/dm-subagents/agents/oracle.md +10 -5
  11. package/extensions/dm-subagents/agents/researcher.md +2 -2
  12. package/extensions/dm-subagents/agents/reviewer.md +17 -7
  13. package/extensions/dm-subagents/agents/scout.md +5 -5
  14. package/extensions/dm-subagents/agents/worker.md +6 -2
  15. package/extensions/dm-subagents/async-retention-discovery-worker.mjs +167 -0
  16. package/extensions/dm-subagents/index.js +4 -0
  17. package/extensions/dm-subagents/inspector-runner.mjs +10 -0
  18. package/extensions/dm-subagents/install.mjs +3 -2
  19. package/extensions/dm-subagents/package.json +2 -2
  20. package/extensions/dm-subagents/prompts/council.md +60 -0
  21. package/extensions/dm-subagents/prompts/parallel-review.md +5 -1
  22. package/extensions/dm-subagents/prompts/review-loop.md +13 -7
  23. package/extensions/dm-subagents/skills/council-mode/SKILL.md +59 -0
  24. package/extensions/dm-subagents/skills/council-mode/references/pass-contracts.md +150 -0
  25. package/extensions/dm-subagents/skills/dm-subagents/SKILL.md +96 -911
  26. package/extensions/dm-subagents/skills/dm-subagents/references/constraints-and-recipes.md +70 -0
  27. package/extensions/dm-subagents/skills/dm-subagents/references/execution-controls.md +539 -0
  28. package/extensions/dm-subagents/skills/dm-subagents/references/management-authoring-rpc.md +161 -0
  29. package/extensions/dm-subagents/skills/dm-subagents/references/multi-lane-orchestration.md +51 -0
  30. package/extensions/dm-subagents/skills/dm-subagents/references/prompting-and-roles.md +295 -0
  31. package/extensions/dm-subagents/skills/dm-subagents/references/review-and-validation.md +73 -0
  32. package/extensions/dm-subagents/src/agents/agent-management.js +716 -484
  33. package/extensions/dm-subagents/src/agents/agent-refinements.js +563 -0
  34. package/extensions/dm-subagents/src/agents/agent-serializer.js +70 -5
  35. package/extensions/dm-subagents/src/agents/agents.js +1447 -299
  36. package/extensions/dm-subagents/src/agents/builtin-names.js +15 -0
  37. package/extensions/dm-subagents/src/agents/chain-serializer.js +12 -7
  38. package/extensions/dm-subagents/src/agents/frontmatter.js +64 -14
  39. package/extensions/dm-subagents/src/agents/identity.js +1 -1
  40. package/extensions/dm-subagents/src/agents/proactive-skills.js +14 -11
  41. package/extensions/dm-subagents/src/agents/runtime-agent-events.js +49 -0
  42. package/extensions/dm-subagents/src/agents/runtime-agent-registry.js +412 -0
  43. package/extensions/dm-subagents/src/agents/skills.js +52 -35
  44. package/extensions/dm-subagents/src/api/agents.js +6 -0
  45. package/extensions/dm-subagents/src/api/background-work.js +151 -0
  46. package/extensions/dm-subagents/src/api/capability-ceiling.js +12 -0
  47. package/extensions/dm-subagents/src/api/control-channel.js +3 -0
  48. package/extensions/dm-subagents/src/api/delegation.js +5 -0
  49. package/extensions/dm-subagents/src/api/dm-args.js +3 -0
  50. package/extensions/dm-subagents/src/api/external-job-provider.js +137 -0
  51. package/extensions/dm-subagents/src/api/external-runs.js +233 -0
  52. package/extensions/dm-subagents/src/api/intercom-bridge.js +3 -0
  53. package/extensions/dm-subagents/src/api/preflight.js +322 -0
  54. package/extensions/dm-subagents/src/api/project-panes.js +11 -0
  55. package/extensions/dm-subagents/src/api/shared-types.js +4 -0
  56. package/extensions/dm-subagents/src/extension/config.js +175 -0
  57. package/extensions/dm-subagents/src/extension/control-notices.js +4 -43
  58. package/extensions/dm-subagents/src/extension/doctor.js +71 -17
  59. package/extensions/dm-subagents/src/extension/fanout-child.js +49 -32
  60. package/extensions/dm-subagents/src/extension/index.js +711 -265
  61. package/extensions/dm-subagents/src/extension/public-execution.js +114 -0
  62. package/extensions/dm-subagents/src/extension/rpc.js +427 -22
  63. package/extensions/dm-subagents/src/extension/schemas.js +158 -65
  64. package/extensions/dm-subagents/src/extension/steering-notices.js +23 -0
  65. package/extensions/dm-subagents/src/extension/subagent-guide.js +31 -0
  66. package/extensions/dm-subagents/src/extension/tool-description.js +92 -74
  67. package/extensions/dm-subagents/src/extension/tool-result.js +7 -0
  68. package/extensions/dm-subagents/src/inspectors/herdr/actions.js +218 -0
  69. package/extensions/dm-subagents/src/inspectors/herdr/client.js +123 -0
  70. package/extensions/dm-subagents/src/inspectors/herdr/focus.js +47 -0
  71. package/extensions/dm-subagents/src/inspectors/herdr/inspector-runner.js +160 -0
  72. package/extensions/dm-subagents/src/inspectors/herdr/project-panes.js +618 -0
  73. package/extensions/dm-subagents/src/inspectors/herdr/session-roots-codec.js +21 -0
  74. package/extensions/dm-subagents/src/inspectors/herdr/shell-command.js +15 -0
  75. package/extensions/dm-subagents/src/integrations/herdr-status.js +377 -0
  76. package/extensions/dm-subagents/src/intercom/intercom-bridge.js +17 -13
  77. package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +371 -79
  78. package/extensions/dm-subagents/src/intercom/result-intercom.js +47 -7
  79. package/extensions/dm-subagents/src/missions/actions.js +394 -0
  80. package/extensions/dm-subagents/src/missions/goal-driver.js +149 -0
  81. package/extensions/dm-subagents/src/missions/lifecycle.js +331 -0
  82. package/extensions/dm-subagents/src/missions/store.js +548 -0
  83. package/extensions/dm-subagents/src/missions/types.js +9 -0
  84. package/extensions/dm-subagents/src/missions/workflow-state.js +245 -0
  85. package/extensions/dm-subagents/src/policy/authority.js +37 -0
  86. package/extensions/dm-subagents/src/profiles/profiles.js +36 -18
  87. package/extensions/dm-subagents/src/runs/background/active-async-capacity.js +427 -0
  88. package/extensions/dm-subagents/src/runs/background/active-run-index.js +122 -0
  89. package/extensions/dm-subagents/src/runs/background/async-execution.js +925 -137
  90. package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +515 -148
  91. package/extensions/dm-subagents/src/runs/background/async-resume.js +378 -51
  92. package/extensions/dm-subagents/src/runs/background/async-retention.js +828 -0
  93. package/extensions/dm-subagents/src/runs/background/async-status-snapshot.js +31 -0
  94. package/extensions/dm-subagents/src/runs/background/async-status.js +259 -22
  95. package/extensions/dm-subagents/src/runs/background/auto-drain.js +46 -0
  96. package/extensions/dm-subagents/src/runs/background/chain-append.js +50 -15
  97. package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +67 -12
  98. package/extensions/dm-subagents/src/runs/background/completion-batcher.js +5 -1
  99. package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +3 -11
  100. package/extensions/dm-subagents/src/runs/background/completion-replay.js +245 -0
  101. package/extensions/dm-subagents/src/runs/background/control-channel.js +423 -36
  102. package/extensions/dm-subagents/src/runs/background/fleet-view.js +132 -59
  103. package/extensions/dm-subagents/src/runs/background/index-segment.js +38 -0
  104. package/extensions/dm-subagents/src/runs/background/inspect-rpc.js +373 -0
  105. package/extensions/dm-subagents/src/runs/background/notify.js +357 -57
  106. package/extensions/dm-subagents/src/runs/background/owned-process-tree.js +86 -0
  107. package/extensions/dm-subagents/src/runs/background/process-terminal.js +269 -0
  108. package/extensions/dm-subagents/src/runs/background/result-delivery-ownership.js +34 -0
  109. package/extensions/dm-subagents/src/runs/background/result-files.js +469 -0
  110. package/extensions/dm-subagents/src/runs/background/result-watcher.js +540 -75
  111. package/extensions/dm-subagents/src/runs/background/resume-guidance.js +44 -0
  112. package/extensions/dm-subagents/src/runs/background/retained-children.js +119 -0
  113. package/extensions/dm-subagents/src/runs/background/run-id-query.js +5 -0
  114. package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +93 -9
  115. package/extensions/dm-subagents/src/runs/background/run-status.js +303 -32
  116. package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +784 -376
  117. package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +75 -34
  118. package/extensions/dm-subagents/src/runs/background/steering.js +221 -0
  119. package/extensions/dm-subagents/src/runs/background/subagent-runner.js +3106 -844
  120. package/extensions/dm-subagents/src/runs/background/subagent-wait.js +529 -0
  121. package/extensions/dm-subagents/src/runs/background/terminal-run-index.js +106 -0
  122. package/extensions/dm-subagents/src/runs/background/top-level-async.js +1 -1
  123. package/extensions/dm-subagents/src/runs/background/wait-completions.js +155 -0
  124. package/extensions/dm-subagents/src/runs/background/wait-config.js +46 -0
  125. package/extensions/dm-subagents/src/runs/background/wait-subscriptions.js +278 -0
  126. package/extensions/dm-subagents/src/runs/background/wait-tool.js +47 -0
  127. package/extensions/dm-subagents/src/runs/foreground/async-dismiss-action.js +81 -0
  128. package/extensions/dm-subagents/src/runs/foreground/async-steering-action.js +245 -0
  129. package/extensions/dm-subagents/src/runs/foreground/async-stop-action.js +74 -0
  130. package/extensions/dm-subagents/src/runs/foreground/execution.js +1401 -342
  131. package/extensions/dm-subagents/src/runs/foreground/foreground-control.js +133 -0
  132. package/extensions/dm-subagents/src/runs/foreground/foreground-history.js +148 -0
  133. package/extensions/dm-subagents/src/runs/foreground/prompt-audit.js +139 -0
  134. package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +4278 -1441
  135. package/extensions/dm-subagents/src/runs/foreground/workflow-detach-reconcile.js +278 -0
  136. package/extensions/dm-subagents/src/runs/foreground/workflow-foreground-steering.js +155 -0
  137. package/extensions/dm-subagents/src/runs/shared/abort-recovery.js +97 -0
  138. package/extensions/dm-subagents/src/runs/shared/acceptance.js +597 -148
  139. package/extensions/dm-subagents/src/runs/shared/agent-contract.js +35 -0
  140. package/extensions/dm-subagents/src/runs/shared/async-status-projection.js +472 -0
  141. package/extensions/dm-subagents/src/runs/shared/background-process-options.js +6 -0
  142. package/extensions/dm-subagents/src/runs/shared/capability-ceiling.js +175 -0
  143. package/extensions/dm-subagents/src/runs/shared/child-identity.js +32 -0
  144. package/extensions/dm-subagents/src/runs/shared/child-launch-plan.js +65 -0
  145. package/extensions/dm-subagents/src/runs/shared/child-protocol.js +447 -0
  146. package/extensions/dm-subagents/src/runs/shared/claude-code-adapter.js +120 -0
  147. package/extensions/dm-subagents/src/runs/shared/codex-exec-adapter.js +129 -0
  148. package/extensions/dm-subagents/src/runs/shared/completion-evidence.js +40 -0
  149. package/extensions/dm-subagents/src/runs/shared/completion-guard.js +140 -83
  150. package/extensions/dm-subagents/src/runs/shared/context-mode.js +38 -0
  151. package/extensions/dm-subagents/src/runs/shared/cursor-agent-adapter.js +101 -0
  152. package/extensions/dm-subagents/src/runs/shared/dm-args.js +445 -72
  153. package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +27 -16
  154. package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +19 -6
  155. package/extensions/dm-subagents/src/runs/shared/extension-bindings.js +81 -0
  156. package/extensions/dm-subagents/src/runs/shared/external-cli-contract.js +134 -0
  157. package/extensions/dm-subagents/src/runs/shared/external-cli-preflight.js +98 -0
  158. package/extensions/dm-subagents/src/runs/shared/external-cli-runner.js +419 -0
  159. package/extensions/dm-subagents/src/runs/shared/external-job-bridge.js +404 -0
  160. package/extensions/dm-subagents/src/runs/shared/external-job-runner.js +334 -0
  161. package/extensions/dm-subagents/src/runs/shared/fast-mode-extension.js +8 -0
  162. package/extensions/dm-subagents/src/runs/shared/host-step-status.js +228 -0
  163. package/extensions/dm-subagents/src/runs/shared/lane-metadata.js +104 -0
  164. package/extensions/dm-subagents/src/runs/shared/launch-cwd.js +17 -0
  165. package/extensions/dm-subagents/src/runs/shared/llm-intent-arbiter.js +190 -0
  166. package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +48 -3
  167. package/extensions/dm-subagents/src/runs/shared/mcp-config-sources.js +387 -0
  168. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +212 -137
  169. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-grant.js +131 -0
  170. package/extensions/dm-subagents/src/runs/shared/model-exclusions.js +207 -0
  171. package/extensions/dm-subagents/src/runs/shared/model-fallback.js +225 -55
  172. package/extensions/dm-subagents/src/runs/shared/model-scope.js +85 -28
  173. package/extensions/dm-subagents/src/runs/shared/mutation-evidence.js +182 -0
  174. package/extensions/dm-subagents/src/runs/shared/nested-events.js +264 -102
  175. package/extensions/dm-subagents/src/runs/shared/nested-render.js +15 -5
  176. package/extensions/dm-subagents/src/runs/shared/orca-progress-tabs.js +505 -0
  177. package/extensions/dm-subagents/src/runs/shared/parallel-handoff.js +653 -0
  178. package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +29 -12
  179. package/extensions/dm-subagents/src/runs/shared/permissions.js +108 -0
  180. package/extensions/dm-subagents/src/runs/shared/process-signal.js +13 -0
  181. package/extensions/dm-subagents/src/runs/shared/run-fanout-budget.js +257 -0
  182. package/extensions/dm-subagents/src/runs/shared/run-history.js +133 -13
  183. package/extensions/dm-subagents/src/runs/shared/runtime-acknowledged-extensions.js +62 -0
  184. package/extensions/dm-subagents/src/runs/shared/session-lease.js +225 -0
  185. package/extensions/dm-subagents/src/runs/shared/single-output.js +129 -27
  186. package/extensions/dm-subagents/src/runs/shared/spawn-budget.js +95 -0
  187. package/extensions/dm-subagents/src/runs/shared/structured-output.js +129 -10
  188. package/extensions/dm-subagents/src/runs/shared/subagent-control.js +66 -11
  189. package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +548 -70
  190. package/extensions/dm-subagents/src/runs/shared/subagent-startup-retry.js +50 -0
  191. package/extensions/dm-subagents/src/runs/shared/task-intent.js +130 -0
  192. package/extensions/dm-subagents/src/runs/shared/tool-availability.js +59 -0
  193. package/extensions/dm-subagents/src/runs/shared/tool-budget.js +7 -5
  194. package/extensions/dm-subagents/src/runs/shared/tool-timeout.js +64 -0
  195. package/extensions/dm-subagents/src/runs/shared/usage-budget.js +74 -0
  196. package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +22 -0
  197. package/extensions/dm-subagents/src/runs/shared/worktree-cleanup-plan.js +721 -0
  198. package/extensions/dm-subagents/src/runs/shared/worktree.js +168 -19
  199. package/extensions/dm-subagents/src/shared/accessible-dir.js +35 -0
  200. package/extensions/dm-subagents/src/shared/agent-stream-options.js +3 -0
  201. package/extensions/dm-subagents/src/shared/artifacts.js +170 -11
  202. package/extensions/dm-subagents/src/shared/atomic-json.js +36 -38
  203. package/extensions/dm-subagents/src/shared/capacity-resilient-json.js +77 -0
  204. package/extensions/dm-subagents/src/shared/child-session-name.js +15 -0
  205. package/extensions/dm-subagents/src/shared/child-transcript.js +57 -2
  206. package/extensions/dm-subagents/src/shared/completion-owner.js +7 -0
  207. package/extensions/dm-subagents/src/shared/display-text.js +142 -0
  208. package/extensions/dm-subagents/src/shared/extension-context.js +17 -0
  209. package/extensions/dm-subagents/src/shared/file-coalescer.js +9 -0
  210. package/extensions/dm-subagents/src/shared/file-system-retry.js +56 -0
  211. package/extensions/dm-subagents/src/shared/fork-context.js +96 -33
  212. package/extensions/dm-subagents/src/shared/formatters.js +21 -7
  213. package/extensions/dm-subagents/src/shared/launch-contract.js +94 -0
  214. package/extensions/dm-subagents/src/shared/model-info.js +10 -5
  215. package/extensions/dm-subagents/src/shared/node-executable.js +19 -0
  216. package/extensions/dm-subagents/src/shared/prompt-resources.js +10 -0
  217. package/extensions/dm-subagents/src/shared/pruned-fork.js +427 -0
  218. package/extensions/dm-subagents/src/shared/session-file-trust.js +19 -0
  219. package/extensions/dm-subagents/src/shared/session-tokens.js +14 -3
  220. package/extensions/dm-subagents/src/shared/settings.js +39 -47
  221. package/extensions/dm-subagents/src/shared/shortcuts.js +16 -0
  222. package/extensions/dm-subagents/src/shared/status-format.js +9 -2
  223. package/extensions/dm-subagents/src/shared/thinking-ceiling.js +41 -0
  224. package/extensions/dm-subagents/src/shared/types.js +47 -7
  225. package/extensions/dm-subagents/src/shared/utf8.js +12 -0
  226. package/extensions/dm-subagents/src/shared/utils.js +151 -131
  227. package/extensions/dm-subagents/src/shared/watch-strategy.js +3 -0
  228. package/extensions/dm-subagents/src/shared/workflow-child-permit.js +84 -0
  229. package/extensions/dm-subagents/src/slash/delegation-adapters.js +274 -0
  230. package/extensions/dm-subagents/src/slash/delegation-json.js +113 -0
  231. package/extensions/dm-subagents/src/slash/delegation-request.js +152 -0
  232. package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +294 -243
  233. package/extensions/dm-subagents/src/slash/prompt-workflows.js +35 -73
  234. package/extensions/dm-subagents/src/slash/selector.js +101 -0
  235. package/extensions/dm-subagents/src/slash/slash-bridge.js +17 -1
  236. package/extensions/dm-subagents/src/slash/slash-commands.js +722 -732
  237. package/extensions/dm-subagents/src/slash/slash-live-state.js +37 -19
  238. package/extensions/dm-subagents/src/slash/subagents-admin.js +410 -0
  239. package/extensions/dm-subagents/src/tui/fleet-status.js +824 -0
  240. package/extensions/dm-subagents/src/tui/fleet-transcript.js +479 -0
  241. package/extensions/dm-subagents/src/tui/fleet.js +1326 -0
  242. package/extensions/dm-subagents/src/tui/render-helpers.js +22 -0
  243. package/extensions/dm-subagents/src/tui/render.js +1511 -261
  244. package/extensions/dm-subagents/src/watchdog/change-signature.js +220 -0
  245. package/extensions/dm-subagents/src/watchdog/child-status.js +151 -0
  246. package/extensions/dm-subagents/src/watchdog/emission-guard.js +90 -0
  247. package/extensions/dm-subagents/src/watchdog/lsp-diagnostics.js +484 -0
  248. package/extensions/dm-subagents/src/watchdog/model-selection.js +154 -0
  249. package/extensions/dm-subagents/src/watchdog/permission-arbiter.js +138 -0
  250. package/extensions/dm-subagents/src/watchdog/register-child.js +112 -0
  251. package/extensions/dm-subagents/src/watchdog/register-main.js +419 -0
  252. package/extensions/dm-subagents/src/watchdog/render.js +54 -0
  253. package/extensions/dm-subagents/src/watchdog/review.js +251 -0
  254. package/extensions/dm-subagents/src/watchdog/runtime.js +803 -0
  255. package/extensions/dm-subagents/src/watchdog/scope.js +56 -0
  256. package/extensions/dm-subagents/src/watchdog/settings.js +515 -0
  257. package/extensions/dm-subagents/src/watchdog/tool-actions.js +151 -0
  258. package/extensions/dm-subagents/src/watchdog/turn-delta.js +169 -0
  259. package/extensions/dm-subagents/src/watchdog/types.js +29 -0
  260. package/extensions/dm-subagents/src/watchdog/warning-format.js +58 -0
  261. package/extensions/dm-subagents/src/workflows/chat-progress.js +116 -0
  262. package/extensions/dm-subagents/src/workflows/host-command.js +227 -0
  263. package/extensions/dm-subagents/src/workflows/scripted-workflow.js +2011 -0
  264. package/extensions/dm-subagents/src/workflows/workflow-child-summary.js +116 -0
  265. package/extensions/dm-subagents/src/workflows/workflow-preflight.js +243 -0
  266. package/extensions/dm-subagents/src/workflows/workflow-receipt.js +387 -0
  267. package/extensions/dm-subagents/src/workflows/workflow-settlement.js +189 -0
  268. package/package.json +4 -3
  269. package/extensions/dm-fff/package.json +0 -21
  270. package/extensions/dm-fff/src/index.js +0 -691
  271. package/extensions/dm-fff/src/query.js +0 -60
  272. package/extensions/dm-subagents/agents/context-builder.md +0 -46
  273. package/extensions/dm-subagents/agents/planner.md +0 -55
  274. package/extensions/dm-subagents/prompts/parallel-context-build.md +0 -55
  275. package/extensions/dm-subagents/prompts/parallel-handoff-plan.md +0 -61
  276. package/extensions/dm-subagents/src/runs/background/wait.js +0 -206
  277. package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +0 -1013
  278. package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +0 -981
  279. package/extensions/dm-subagents/src/runs/shared/turn-budget.js +0 -50
@@ -0,0 +1,484 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import * as fs from "node:fs";
4
+ import * as path from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+ const TS_JS_EXTENSIONS = new Map([
7
+ [".ts", "typescript"],
8
+ [".tsx", "typescriptreact"],
9
+ [".mts", "typescript"],
10
+ [".cts", "typescript"],
11
+ [".js", "javascript"],
12
+ [".jsx", "javascriptreact"],
13
+ [".mjs", "javascript"],
14
+ [".cjs", "javascript"]
15
+ ]);
16
+ const PROVIDER_NAME = "typescript-language-server";
17
+ const MAX_MESSAGE_LENGTH = 500;
18
+ const MAX_STDERR_LENGTH = 2000;
19
+ const SHUTDOWN_TIMEOUT_MS = 250;
20
+ function normalizeRelPath(value) {
21
+ return value.replaceAll(path.sep, "/").replace(/^\.\//, "");
22
+ }
23
+ function isPathInsideRoot(absPath, root) {
24
+ const rel = path.relative(root, absPath);
25
+ return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
26
+ }
27
+ function languageIdForPath(filePath) {
28
+ return TS_JS_EXTENSIONS.get(path.extname(filePath).toLowerCase());
29
+ }
30
+ function trimDiagnosticMessage(message) {
31
+ const normalized = message.replace(/\s+/g, " ").trim();
32
+ return normalized.length > MAX_MESSAGE_LENGTH ? `${normalized.slice(0, MAX_MESSAGE_LENGTH - 1)}…` : normalized;
33
+ }
34
+ function severityFromLsp(value) {
35
+ if (value === 1)
36
+ return "error";
37
+ if (value === 2)
38
+ return "warning";
39
+ if (value === 3)
40
+ return "info";
41
+ return "hint";
42
+ }
43
+ function pathExecutable(filePath) {
44
+ try {
45
+ const stat = fs.statSync(filePath);
46
+ if (!stat.isFile())
47
+ return false;
48
+ if (process.platform === "win32")
49
+ return true;
50
+ fs.accessSync(filePath, fs.constants.X_OK);
51
+ return true;
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
56
+ function pathExecutableNames(name) {
57
+ if (process.platform !== "win32")
58
+ return [name];
59
+ const extensions = (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean);
60
+ return [name, ...extensions.map((ext) => `${name}${ext.toLowerCase()}`), ...extensions.map((ext) => `${name}${ext.toUpperCase()}`)];
61
+ }
62
+ function resolveTypeScriptLanguageServer(root) {
63
+ for (const name of pathExecutableNames(PROVIDER_NAME)) {
64
+ const local = path.join(root, "node_modules", ".bin", name);
65
+ if (pathExecutable(local))
66
+ return { command: local, args: ["--stdio"], label: `${PROVIDER_NAME} (project)` };
67
+ }
68
+ for (const dir of (process.env.PATH ?? "").split(path.delimiter).filter(Boolean)) {
69
+ for (const name of pathExecutableNames(PROVIDER_NAME)) {
70
+ const candidate = path.join(dir, name);
71
+ if (pathExecutable(candidate))
72
+ return { command: candidate, args: ["--stdio"], label: PROVIDER_NAME };
73
+ }
74
+ }
75
+ return;
76
+ }
77
+ function collectTargetFiles(root, changedPaths, maxFiles) {
78
+ const targets = [];
79
+ const skippedPaths = [];
80
+ for (const changedPath of changedPaths) {
81
+ const relPath = normalizeRelPath(changedPath);
82
+ const absPath = path.resolve(root, relPath);
83
+ const languageId = languageIdForPath(absPath);
84
+ if (!languageId || !isPathInsideRoot(absPath, root)) {
85
+ skippedPaths.push(relPath);
86
+ continue;
87
+ }
88
+ let stat;
89
+ try {
90
+ stat = fs.statSync(absPath);
91
+ } catch (error) {
92
+ if (error.code === "ENOENT") {
93
+ skippedPaths.push(relPath);
94
+ continue;
95
+ }
96
+ throw error;
97
+ }
98
+ if (!stat.isFile()) {
99
+ skippedPaths.push(relPath);
100
+ continue;
101
+ }
102
+ if (targets.length >= maxFiles) {
103
+ skippedPaths.push(relPath);
104
+ continue;
105
+ }
106
+ targets.push({ relPath, absPath, uri: pathToFileURL(absPath).href, languageId });
107
+ }
108
+ return { targets, skippedPaths };
109
+ }
110
+ function diagnosticIdentity(diagnostic) {
111
+ return createHash("sha256").update([
112
+ diagnostic.path,
113
+ diagnostic.severity,
114
+ diagnostic.source,
115
+ diagnostic.code ?? "",
116
+ diagnostic.message
117
+ ].join(`
118
+ `)).digest("hex");
119
+ }
120
+
121
+ export class WatchdogLspDiagnosticsLedger {
122
+ seen = new Map;
123
+ reset() {
124
+ this.seen.clear();
125
+ }
126
+ reduce(result) {
127
+ if (result.status === "disabled" || result.status === "unavailable" || result.status === "failed")
128
+ return result;
129
+ const currentByPath = new Map;
130
+ const fresh = [];
131
+ for (const diagnostic of result.diagnostics) {
132
+ const identity = diagnosticIdentity(diagnostic);
133
+ const current = currentByPath.get(diagnostic.path) ?? new Set;
134
+ current.add(identity);
135
+ currentByPath.set(diagnostic.path, current);
136
+ if (!this.seen.get(diagnostic.path)?.has(identity))
137
+ fresh.push(diagnostic);
138
+ }
139
+ for (const [filePath, identities] of currentByPath)
140
+ this.seen.set(filePath, identities);
141
+ if (result.status === "ok") {
142
+ for (const checkedPath of result.checkedPaths) {
143
+ if (!currentByPath.has(checkedPath))
144
+ this.seen.delete(checkedPath);
145
+ }
146
+ }
147
+ return { ...result, diagnostics: fresh };
148
+ }
149
+ }
150
+ function formatDiagnostic(diagnostic) {
151
+ const code = diagnostic.code ? ` ${diagnostic.code}` : "";
152
+ return `${diagnostic.path}:${diagnostic.line}:${diagnostic.column} ${diagnostic.severity}${code} ${diagnostic.source}: ${diagnostic.message}`;
153
+ }
154
+ export function formatWatchdogLspDiagnosticsBlock(result) {
155
+ const actionable = result.diagnostics.filter((diagnostic) => diagnostic.severity === "error" || diagnostic.severity === "warning");
156
+ if (!actionable.length)
157
+ return "";
158
+ return [
159
+ "LSP diagnostics:",
160
+ ...actionable.map((diagnostic) => `- ${formatDiagnostic(diagnostic)}`)
161
+ ].join(`
162
+ `);
163
+ }
164
+ export function watchdogWarningFromLspDiagnostics(result) {
165
+ const actionable = result.diagnostics.filter((diagnostic) => diagnostic.severity === "error" || diagnostic.severity === "warning");
166
+ if (!actionable.length)
167
+ return;
168
+ const errors = actionable.filter((diagnostic) => diagnostic.severity === "error");
169
+ const severity = errors.length ? "blocker" : "concern";
170
+ const primary = errors[0] ?? actionable[0];
171
+ const count = errors.length || actionable.length;
172
+ const kind = errors.length ? "error" : "warning";
173
+ const evidence = actionable.slice(0, 5).map(formatDiagnostic).join(`
174
+ `);
175
+ return {
176
+ severity,
177
+ category: "correctness",
178
+ confidence: "high",
179
+ source: "lsp",
180
+ summary: `LSP found ${count} ${kind}${count === 1 ? "" : "s"} in changed ${count === 1 ? "file" : "files"}.`,
181
+ evidence: evidence || formatDiagnostic(primary),
182
+ recommendedAction: "Fix the reported diagnostics or explain why they are expected before accepting the change."
183
+ };
184
+ }
185
+ function withTimeout(promise, timeoutMs, message, signal) {
186
+ if (signal?.aborted)
187
+ return Promise.reject(new Error("aborted"));
188
+ let timer;
189
+ return new Promise((resolve, reject) => {
190
+ const onAbort = () => {
191
+ if (timer)
192
+ clearTimeout(timer);
193
+ reject(new Error("aborted"));
194
+ };
195
+ timer = setTimeout(() => {
196
+ signal?.removeEventListener("abort", onAbort);
197
+ reject(new Error(message));
198
+ }, timeoutMs);
199
+ signal?.addEventListener("abort", onAbort, { once: true });
200
+ promise.then((value) => {
201
+ if (timer)
202
+ clearTimeout(timer);
203
+ signal?.removeEventListener("abort", onAbort);
204
+ resolve(value);
205
+ }, (error) => {
206
+ if (timer)
207
+ clearTimeout(timer);
208
+ signal?.removeEventListener("abort", onAbort);
209
+ reject(error);
210
+ });
211
+ });
212
+ }
213
+
214
+ class JsonRpcLspClient {
215
+ nextId = 1;
216
+ stdoutBuffer = Buffer.alloc(0);
217
+ pending = new Map;
218
+ diagnostics = new Map;
219
+ child;
220
+ stderr = "";
221
+ exited = false;
222
+ terminating = false;
223
+ exitWaiters = [];
224
+ constructor(child) {
225
+ this.child = child;
226
+ child.stdout.on("data", (chunk) => this.handleStdout(chunk));
227
+ child.stderr.on("data", (chunk) => {
228
+ this.stderr = `${this.stderr}${chunk.toString("utf-8")}`.slice(-MAX_STDERR_LENGTH);
229
+ });
230
+ child.on("error", (error) => {
231
+ this.exited = true;
232
+ this.rejectPending(error);
233
+ this.resolveExitWaiters();
234
+ });
235
+ child.on("exit", (code, signal) => {
236
+ this.exited = true;
237
+ this.rejectPending(new Error(`language server exited${code === null ? "" : ` with code ${code}`}${signal ? ` signal ${signal}` : ""}`));
238
+ this.resolveExitWaiters();
239
+ });
240
+ }
241
+ request(method, params, timeoutMs, signal) {
242
+ const id = this.nextId++;
243
+ const promise = new Promise((resolve, reject) => {
244
+ this.pending.set(id, { resolve, reject });
245
+ this.send({ jsonrpc: "2.0", id, method, params });
246
+ });
247
+ return withTimeout(promise, timeoutMs, `${method} timed out`, signal);
248
+ }
249
+ notify(method, params) {
250
+ this.send({ jsonrpc: "2.0", method, params });
251
+ }
252
+ async shutdown() {
253
+ if (this.exited)
254
+ return;
255
+ if (!this.terminating) {
256
+ try {
257
+ await this.request("shutdown", null, SHUTDOWN_TIMEOUT_MS);
258
+ this.notify("exit", null);
259
+ } catch {
260
+ this.kill();
261
+ }
262
+ }
263
+ await this.waitForExit(SHUTDOWN_TIMEOUT_MS);
264
+ }
265
+ kill() {
266
+ if (this.exited || this.terminating)
267
+ return;
268
+ this.terminating = true;
269
+ this.child.kill("SIGTERM");
270
+ }
271
+ stderrTail() {
272
+ return this.stderr.trim();
273
+ }
274
+ send(payload) {
275
+ if (this.exited)
276
+ throw new Error("language server already exited");
277
+ const body = JSON.stringify(payload);
278
+ this.child.stdin.write(`Content-Length: ${Buffer.byteLength(body, "utf-8")}\r
279
+ \r
280
+ ${body}`);
281
+ }
282
+ handleStdout(chunk) {
283
+ this.stdoutBuffer = Buffer.concat([this.stdoutBuffer, chunk]);
284
+ while (true) {
285
+ const headerEnd = this.stdoutBuffer.indexOf(`\r
286
+ \r
287
+ `);
288
+ if (headerEnd === -1)
289
+ return;
290
+ const header = this.stdoutBuffer.slice(0, headerEnd).toString("utf-8");
291
+ const match = header.match(/content-length:\s*(\d+)/i);
292
+ if (!match) {
293
+ this.stdoutBuffer = this.stdoutBuffer.slice(headerEnd + 4);
294
+ continue;
295
+ }
296
+ const length = Number(match[1]);
297
+ const bodyStart = headerEnd + 4;
298
+ const bodyEnd = bodyStart + length;
299
+ if (this.stdoutBuffer.length < bodyEnd)
300
+ return;
301
+ const body = this.stdoutBuffer.slice(bodyStart, bodyEnd).toString("utf-8");
302
+ this.stdoutBuffer = this.stdoutBuffer.slice(bodyEnd);
303
+ try {
304
+ this.handleMessage(JSON.parse(body));
305
+ } catch (error) {
306
+ const message = error instanceof Error ? error.message : String(error);
307
+ this.failProtocol(new Error(`Invalid LSP JSON-RPC response: ${message}`));
308
+ return;
309
+ }
310
+ }
311
+ }
312
+ handleMessage(message) {
313
+ if (message.method === "textDocument/publishDiagnostics") {
314
+ const params = message.params;
315
+ if (typeof params?.uri === "string" && Array.isArray(params.diagnostics)) {
316
+ this.diagnostics.set(params.uri, params.diagnostics);
317
+ }
318
+ return;
319
+ }
320
+ if (message.id === undefined || message.id === null)
321
+ return;
322
+ const pending = this.pending.get(message.id);
323
+ if (!pending)
324
+ return;
325
+ this.pending.delete(message.id);
326
+ if (message.error) {
327
+ pending.reject(new Error(message.error.message || `LSP request ${message.id} failed`));
328
+ } else {
329
+ pending.resolve(message.result);
330
+ }
331
+ }
332
+ failProtocol(error) {
333
+ if (this.exited)
334
+ return;
335
+ this.rejectPending(error);
336
+ this.kill();
337
+ }
338
+ waitForExit(timeoutMs) {
339
+ if (this.exited)
340
+ return Promise.resolve();
341
+ return new Promise((resolve) => {
342
+ const timer = setTimeout(resolve, timeoutMs);
343
+ this.exitWaiters.push(() => {
344
+ clearTimeout(timer);
345
+ resolve();
346
+ });
347
+ });
348
+ }
349
+ resolveExitWaiters() {
350
+ for (const resolve of this.exitWaiters.splice(0))
351
+ resolve();
352
+ }
353
+ rejectPending(error) {
354
+ for (const pending of this.pending.values())
355
+ pending.reject(error);
356
+ this.pending.clear();
357
+ }
358
+ }
359
+ function convertDiagnostics(target, diagnostics) {
360
+ return diagnostics.filter((diagnostic) => typeof diagnostic.message === "string" && diagnostic.range?.start).map((diagnostic) => ({
361
+ path: target.relPath,
362
+ line: Math.max(1, (diagnostic.range?.start?.line ?? 0) + 1),
363
+ column: Math.max(1, (diagnostic.range?.start?.character ?? 0) + 1),
364
+ severity: severityFromLsp(diagnostic.severity),
365
+ source: diagnostic.source || PROVIDER_NAME,
366
+ ...diagnostic.code !== undefined ? { code: String(diagnostic.code) } : {},
367
+ message: trimDiagnosticMessage(diagnostic.message ?? "")
368
+ }));
369
+ }
370
+ function initializeParams(root) {
371
+ const rootUri = pathToFileURL(root).href;
372
+ return {
373
+ processId: process.pid,
374
+ rootUri,
375
+ capabilities: {
376
+ textDocument: {
377
+ publishDiagnostics: { relatedInformation: false, versionSupport: true }
378
+ },
379
+ workspace: { configuration: false, workspaceFolders: true }
380
+ },
381
+ workspaceFolders: [{ uri: rootUri, name: path.basename(root) || "workspace" }]
382
+ };
383
+ }
384
+ async function waitForDiagnostics(client, targets, timeoutMs, signal) {
385
+ const started = Date.now();
386
+ while (!signal?.aborted && Date.now() - started < timeoutMs) {
387
+ if (targets.every((target) => client.diagnostics.has(target.uri)))
388
+ return true;
389
+ await new Promise((resolve) => setTimeout(resolve, Math.min(50, Math.max(1, timeoutMs - (Date.now() - started)))));
390
+ }
391
+ return targets.every((target) => client.diagnostics.has(target.uri));
392
+ }
393
+ async function collectWithTypeScriptLanguageServer(input) {
394
+ const started = Date.now();
395
+ const child = spawn(input.command.command, input.command.args, {
396
+ windowsHide: true,
397
+ cwd: input.root,
398
+ stdio: "pipe",
399
+ env: { ...process.env, NO_COLOR: "1" },
400
+ shell: process.platform === "win32" && /\.(cmd|bat)$/i.test(input.command.command)
401
+ });
402
+ const client = new JsonRpcLspClient(child);
403
+ const remaining = () => Math.max(1, input.config.timeoutMs - (Date.now() - started));
404
+ const abort = () => client.kill();
405
+ input.signal?.addEventListener("abort", abort, { once: true });
406
+ try {
407
+ await client.request("initialize", initializeParams(input.root), remaining(), input.signal);
408
+ client.notify("initialized", {});
409
+ for (const target of input.targets) {
410
+ const text = fs.readFileSync(target.absPath, "utf-8");
411
+ client.notify("textDocument/didOpen", {
412
+ textDocument: { uri: target.uri, languageId: target.languageId, version: 1, text }
413
+ });
414
+ client.notify("textDocument/didSave", {
415
+ textDocument: { uri: target.uri },
416
+ text
417
+ });
418
+ }
419
+ const complete = await waitForDiagnostics(client, input.targets, remaining(), input.signal);
420
+ const diagnostics = input.targets.flatMap((target) => convertDiagnostics(target, client.diagnostics.get(target.uri) ?? [])).slice(0, input.config.maxDiagnostics);
421
+ return {
422
+ status: complete ? "ok" : "timeout",
423
+ provider: input.command.label,
424
+ checkedPaths: input.targets.map((target) => target.relPath),
425
+ skippedPaths: input.skippedPaths,
426
+ diagnostics,
427
+ ...complete ? {} : { message: `Timed out waiting ${input.config.timeoutMs}ms for fresh LSP diagnostics.` }
428
+ };
429
+ } catch (error) {
430
+ const message = error instanceof Error ? error.message : String(error);
431
+ const timedOut = message.includes("timed out") || message === "aborted";
432
+ const stderr = client.stderrTail();
433
+ return {
434
+ status: timedOut ? "timeout" : "failed",
435
+ provider: input.command.label,
436
+ checkedPaths: input.targets.map((target) => target.relPath),
437
+ skippedPaths: input.skippedPaths,
438
+ diagnostics: [],
439
+ message: stderr ? `${message}; ${stderr}` : message
440
+ };
441
+ } finally {
442
+ input.signal?.removeEventListener("abort", abort);
443
+ await client.shutdown();
444
+ }
445
+ }
446
+ export async function collectWatchdogLspDiagnostics(request) {
447
+ if (!request.config.enabled) {
448
+ return { status: "disabled", checkedPaths: [], skippedPaths: [], diagnostics: [] };
449
+ }
450
+ const root = path.resolve(request.root || request.cwd);
451
+ const { targets, skippedPaths } = collectTargetFiles(root, request.changedPaths, request.config.maxFiles);
452
+ if (!targets.length) {
453
+ return {
454
+ status: "skipped",
455
+ checkedPaths: [],
456
+ skippedPaths,
457
+ diagnostics: [],
458
+ message: "No changed TypeScript or JavaScript files to check."
459
+ };
460
+ }
461
+ const command = resolveTypeScriptLanguageServer(root);
462
+ if (!command) {
463
+ return {
464
+ status: "unavailable",
465
+ provider: PROVIDER_NAME,
466
+ checkedPaths: [],
467
+ skippedPaths: [...skippedPaths, ...targets.map((target) => target.relPath)],
468
+ diagnostics: [],
469
+ message: `${PROVIDER_NAME} was not found in project node_modules/.bin or PATH.`
470
+ };
471
+ }
472
+ try {
473
+ return await collectWithTypeScriptLanguageServer({ root, targets, skippedPaths, command, config: request.config, signal: request.signal });
474
+ } catch (error) {
475
+ return {
476
+ status: "failed",
477
+ provider: command.label,
478
+ checkedPaths: targets.map((target) => target.relPath),
479
+ skippedPaths,
480
+ diagnostics: [],
481
+ message: error instanceof Error ? error.message : String(error)
482
+ };
483
+ }
484
+ }
@@ -0,0 +1,154 @@
1
+ import { normalizeModelSegment, resolveModelCandidate } from "../runs/shared/model-fallback.js";
2
+ import {
3
+ getSupportedThinkingLevels,
4
+ splitKnownThinkingSuffix,
5
+ THINKING_LEVELS,
6
+ toModelInfo
7
+ } from "../shared/model-info.js";
8
+ export const STRONG_WATCHDOG_THINKING = "high";
9
+ const STRONG_WATCHDOG_MODELS = {
10
+ opus48: {
11
+ label: "Opus 4.8",
12
+ queries: [
13
+ "anthropic/claude-opus-4-8",
14
+ "anthropic/claude-opus-4.8",
15
+ "anthropic/opus-4-8",
16
+ "anthropic/opus-4.8"
17
+ ]
18
+ },
19
+ gpt55: {
20
+ label: "GPT 5.5",
21
+ queries: [
22
+ "openai-codex/gpt-5.5",
23
+ "openai-codex/gpt-5-5",
24
+ "openai/gpt-5.5",
25
+ "openai/gpt-5-5"
26
+ ]
27
+ }
28
+ };
29
+ function fullModelId(model) {
30
+ return `${model.provider}/${model.id}`;
31
+ }
32
+ function modelRegistryEntries(ctx) {
33
+ return ctx.modelRegistry.getAvailable().map(toModelInfo);
34
+ }
35
+ function splitProviderModel(value) {
36
+ const slashIndex = value.indexOf("/");
37
+ if (slashIndex <= 0 || slashIndex === value.length - 1)
38
+ return;
39
+ return { provider: value.slice(0, slashIndex), id: value.slice(slashIndex + 1) };
40
+ }
41
+ function assertSupportedThinking(value, source) {
42
+ if (THINKING_LEVELS.includes(value))
43
+ return value;
44
+ throw new Error(`Unsupported watchdog thinking '${value}' from ${source}; expected ${THINKING_LEVELS.join(", ")}, false, or inherit.`);
45
+ }
46
+ export function parseWatchdogThinkingInput(value, source = "watchdog input") {
47
+ if (value === undefined || value === "")
48
+ return;
49
+ if (value === false)
50
+ return false;
51
+ if (value === "false")
52
+ return false;
53
+ return assertSupportedThinking(value, source);
54
+ }
55
+ export function resolveWatchdogModelInput(ctx, rawModel) {
56
+ const trimmed = rawModel.trim();
57
+ if (!trimmed)
58
+ throw new Error("Watchdog model must be a non-empty provider/model value.");
59
+ const availableModels = modelRegistryEntries(ctx);
60
+ const preferredProvider = typeof ctx.model?.provider === "string" ? ctx.model.provider : undefined;
61
+ const resolved = resolveModelCandidate(trimmed, availableModels, preferredProvider) ?? trimmed;
62
+ const { baseModel, thinkingSuffix } = splitKnownThinkingSuffix(resolved);
63
+ const named = splitProviderModel(baseModel);
64
+ if (!named)
65
+ throw new Error(`Watchdog model '${rawModel}' did not resolve to provider/model. Use a provider-qualified model such as openai-codex/gpt-5.5:high or anthropic/claude-opus-4-8:high.`);
66
+ const registryModel = ctx.modelRegistry.find(named.provider, named.id);
67
+ if (!registryModel)
68
+ throw new Error(`Watchdog model '${rawModel}' was not found as '${baseModel}'.`);
69
+ if (!ctx.modelRegistry.hasConfiguredAuth(registryModel)) {
70
+ throw new Error(`Watchdog model '${baseModel}' is not authenticated. Configure credentials for provider '${named.provider}' or choose an authenticated model.`);
71
+ }
72
+ return {
73
+ model: `${named.provider}/${named.id}`,
74
+ ...thinkingSuffix ? { thinking: assertSupportedThinking(thinkingSuffix.slice(1), "watchdog model suffix") } : {},
75
+ registryModel
76
+ };
77
+ }
78
+ function familyForModel(model) {
79
+ if (!model)
80
+ return;
81
+ const provider = normalizeModelSegment(model.provider);
82
+ const id = normalizeModelSegment(model.id);
83
+ if (provider.includes("openai") && /^gpt-5-5(-\d{8}|-\d{4}-\d{2}-\d{2})?$/.test(id))
84
+ return "gpt55";
85
+ if (provider.includes("anthropic") && /^(claude-opus-4-8|opus-4-8)(-\d{8}|-\d{4}-\d{2}-\d{2})?$/.test(id))
86
+ return "opus48";
87
+ return;
88
+ }
89
+ function currentProviderFamily(ctx) {
90
+ const provider = typeof ctx.model?.provider === "string" ? normalizeModelSegment(ctx.model.provider) : "";
91
+ if (provider.includes("openai"))
92
+ return "openai";
93
+ if (provider.includes("anthropic"))
94
+ return "anthropic";
95
+ return;
96
+ }
97
+ function strongFamilyOrder(ctx) {
98
+ const current = ctx.model ? familyForModel(toModelInfo(ctx.model)) : undefined;
99
+ if (current === "gpt55")
100
+ return ["opus48"];
101
+ if (current === "opus48")
102
+ return ["gpt55"];
103
+ const providerFamily = currentProviderFamily(ctx);
104
+ if (providerFamily === "openai")
105
+ return ["opus48", "gpt55"];
106
+ if (providerFamily === "anthropic")
107
+ return ["gpt55", "opus48"];
108
+ return ["gpt55", "opus48"];
109
+ }
110
+ function findFamilyMatch(family, availableModels) {
111
+ const matches = availableModels.filter((entry) => familyForModel(entry) === family);
112
+ if (matches.length === 1)
113
+ return matches[0].fullId;
114
+ return;
115
+ }
116
+ function resolveStrongCandidate(ctx, family) {
117
+ const availableModels = modelRegistryEntries(ctx);
118
+ const preference = STRONG_WATCHDOG_MODELS[family];
119
+ const queries = [...preference.queries];
120
+ const familyMatch = findFamilyMatch(family, availableModels);
121
+ if (familyMatch)
122
+ queries.push(familyMatch);
123
+ for (const query of queries) {
124
+ let resolved;
125
+ try {
126
+ resolved = resolveWatchdogModelInput(ctx, query);
127
+ } catch {
128
+ continue;
129
+ }
130
+ const modelInfo = toModelInfo(resolved.registryModel);
131
+ if (familyForModel(modelInfo) !== family)
132
+ continue;
133
+ if (!getSupportedThinkingLevels(modelInfo).includes(STRONG_WATCHDOG_THINKING))
134
+ continue;
135
+ const current = ctx.model ? fullModelId(toModelInfo(ctx.model)) : "no current session model";
136
+ return {
137
+ model: resolved.model,
138
+ thinking: STRONG_WATCHDOG_THINKING,
139
+ label: preference.label,
140
+ reason: `Use ${preference.label} with thinking high as a strong independent watchdog for ${current}.`,
141
+ registryModel: resolved.registryModel
142
+ };
143
+ }
144
+ return;
145
+ }
146
+ export function recommendStrongWatchdogModel(ctx) {
147
+ for (const family of strongFamilyOrder(ctx)) {
148
+ const recommendation = resolveStrongCandidate(ctx, family);
149
+ if (recommendation)
150
+ return recommendation;
151
+ }
152
+ const current = ctx.model ? fullModelId(toModelInfo(ctx.model)) : "the current session";
153
+ throw new Error(`No authenticated strong complementary watchdog model was found for ${current}. Configure access to Opus 4.8 or GPT 5.5, then run the recommendation again.`);
154
+ }