@heidi-dang/oh-my-opencode 3.12.4 → 3.13.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 (499) hide show
  1. package/README.md +61 -367
  2. package/dist/agents/atlas/agent.d.ts +1 -1
  3. package/dist/agents/atlas/default.d.ts +1 -1
  4. package/dist/agents/atlas/gemini.d.ts +1 -1
  5. package/dist/agents/atlas/gpt.d.ts +1 -1
  6. package/dist/agents/atlas/prompt-section-builder.d.ts +1 -1
  7. package/dist/agents/builtin-agents/atlas-agent.d.ts +4 -1
  8. package/dist/agents/builtin-agents/available-skills.d.ts +1 -1
  9. package/dist/agents/builtin-agents/general-agents.d.ts +4 -1
  10. package/dist/agents/builtin-agents/hephaestus-agent.d.ts +5 -1
  11. package/dist/agents/builtin-agents/model-resolution.d.ts +2 -0
  12. package/dist/agents/builtin-agents/resolve-file-uri.test.d.ts +1 -0
  13. package/dist/agents/builtin-agents/sisyphus-agent.d.ts +4 -1
  14. package/dist/agents/builtin-agents.d.ts +3 -3
  15. package/dist/agents/chat.d.ts +7 -0
  16. package/dist/agents/dynamic-agent-prompt-builder.test.d.ts +1 -0
  17. package/dist/agents/env-context.test.d.ts +1 -0
  18. package/dist/agents/hephaestus/agent.d.ts +19 -0
  19. package/dist/agents/hephaestus/agent.test.d.ts +1 -0
  20. package/dist/agents/hephaestus/gpt-5-3-codex.d.ts +21 -0
  21. package/dist/agents/hephaestus/gpt-5-4.d.ts +3 -0
  22. package/dist/agents/hephaestus/gpt.d.ts +3 -0
  23. package/dist/agents/hephaestus/index.d.ts +2 -0
  24. package/dist/agents/hephaestus.d.ts +1 -1
  25. package/dist/agents/index.d.ts +1 -1
  26. package/dist/agents/metis.d.ts +1 -1
  27. package/dist/agents/momus.d.ts +1 -1
  28. package/dist/agents/momus.test.d.ts +1 -0
  29. package/dist/agents/prometheus-prompt.test.d.ts +1 -0
  30. package/dist/agents/prompts/agent-role.d.ts +2 -0
  31. package/dist/agents/prompts/anti-patterns.d.ts +2 -0
  32. package/dist/agents/prompts/base-system.d.ts +1 -0
  33. package/dist/agents/prompts/execution-rules.d.ts +2 -0
  34. package/dist/agents/prompts/hard-blocks.d.ts +2 -0
  35. package/dist/agents/prompts/index.d.ts +6 -0
  36. package/dist/agents/{dynamic-agent-prompt-builder.d.ts → prompts/orchestration.d.ts} +2 -23
  37. package/dist/agents/prompts/skill-context.d.ts +2 -0
  38. package/dist/agents/runtime/action-validator.d.ts +31 -0
  39. package/dist/agents/runtime/agent-logger.d.ts +15 -0
  40. package/dist/agents/runtime/loop-guard.d.ts +2 -0
  41. package/dist/agents/runtime/verify-action.d.ts +58 -0
  42. package/dist/agents/sisyphus/default.d.ts +9 -0
  43. package/dist/agents/sisyphus/gemini.d.ts +20 -0
  44. package/dist/agents/sisyphus/gpt-5-4.d.ts +16 -0
  45. package/dist/agents/sisyphus/index.d.ts +11 -0
  46. package/dist/agents/sisyphus-junior/index.test.d.ts +1 -0
  47. package/dist/agents/sisyphus.d.ts +1 -1
  48. package/dist/agents/tool-restrictions.test.d.ts +1 -0
  49. package/dist/agents/types.d.ts +24 -6
  50. package/dist/agents/types.test.d.ts +1 -0
  51. package/dist/agents/utils.test.d.ts +1 -0
  52. package/dist/cli/cli-installer.test.d.ts +1 -0
  53. package/dist/cli/config-manager/write-omo-config.test.d.ts +1 -0
  54. package/dist/cli/config-manager.test.d.ts +1 -0
  55. package/dist/cli/doctor/checks/config.test.d.ts +1 -0
  56. package/dist/cli/doctor/checks/dependencies.test.d.ts +1 -0
  57. package/dist/cli/doctor/checks/edit-atomicity.d.ts +5 -0
  58. package/dist/cli/doctor/checks/issue-resolution.d.ts +7 -0
  59. package/dist/cli/doctor/checks/model-resolution.test.d.ts +1 -0
  60. package/dist/cli/doctor/checks/plan-compiler.d.ts +8 -0
  61. package/dist/cli/doctor/checks/progress.d.ts +8 -0
  62. package/dist/cli/doctor/checks/run-state-watchdog.d.ts +2 -0
  63. package/dist/cli/doctor/checks/system-loaded-version.test.d.ts +1 -0
  64. package/dist/cli/doctor/checks/system.test.d.ts +1 -0
  65. package/dist/cli/doctor/checks/tool-contract.d.ts +8 -0
  66. package/dist/cli/doctor/checks/tool-metadata.d.ts +2 -0
  67. package/dist/cli/doctor/constants.d.ts +3 -0
  68. package/dist/cli/doctor/format-default.test.d.ts +1 -0
  69. package/dist/cli/doctor/formatter.test.d.ts +1 -0
  70. package/dist/cli/doctor/runner.test.d.ts +1 -0
  71. package/dist/cli/doctor/types.d.ts +3 -0
  72. package/dist/cli/index.js +2542 -214
  73. package/dist/cli/index.test.d.ts +1 -0
  74. package/dist/cli/install.test.d.ts +1 -0
  75. package/dist/cli/mcp-oauth/index.test.d.ts +1 -0
  76. package/dist/cli/mcp-oauth/login.test.d.ts +1 -0
  77. package/dist/cli/mcp-oauth/logout.test.d.ts +1 -0
  78. package/dist/cli/mcp-oauth/status.test.d.ts +1 -0
  79. package/dist/cli/model-fallback.test.d.ts +1 -0
  80. package/dist/cli/provider-model-id-transform.test.d.ts +1 -0
  81. package/dist/cli/run/completion-continuation.test.d.ts +1 -0
  82. package/dist/cli/run/completion-verbose-logging.test.d.ts +1 -0
  83. package/dist/cli/run/completion.test.d.ts +1 -0
  84. package/dist/cli/run/continuation-state-marker.test.d.ts +1 -0
  85. package/dist/cli/run/event-handlers.test.d.ts +1 -0
  86. package/dist/cli/run/events.test.d.ts +1 -0
  87. package/dist/cli/run/integration.test.d.ts +1 -0
  88. package/dist/cli/run/json-output.test.d.ts +1 -0
  89. package/dist/cli/run/message-part-delta.test.d.ts +1 -0
  90. package/dist/cli/run/on-complete-hook.test.d.ts +1 -0
  91. package/dist/cli/run/opencode-binary-resolver.test.d.ts +1 -0
  92. package/dist/cli/run/poll-for-completion.test.d.ts +1 -0
  93. package/dist/cli/run/runner.test.d.ts +1 -0
  94. package/dist/cli/run/server-connection.test.d.ts +1 -0
  95. package/dist/cli/run/session-resolver.test.d.ts +1 -0
  96. package/dist/cli/run/stdin-suppression.test.d.ts +1 -0
  97. package/dist/cli/run/timestamp-output.test.d.ts +1 -0
  98. package/dist/config/schema/agent-names.d.ts +6 -3
  99. package/dist/config/schema/agent-overrides.d.ts +150 -30
  100. package/dist/config/schema/background-task.test.d.ts +1 -0
  101. package/dist/config/schema/categories.d.ts +21 -3
  102. package/dist/config/schema/categories.test.d.ts +1 -0
  103. package/dist/config/schema/fallback-models.d.ts +11 -1
  104. package/dist/config/schema/hooks.d.ts +7 -0
  105. package/dist/config/schema/oh-my-opencode-config.d.ts +151 -29
  106. package/dist/config/schema.test.d.ts +1 -0
  107. package/dist/create-hooks.d.ts +11 -1
  108. package/dist/create-managers.d.ts +2 -0
  109. package/dist/create-tools.d.ts +1 -1
  110. package/dist/features/background-agent/compaction-aware-message-resolver.test.d.ts +1 -0
  111. package/dist/features/background-agent/concurrency.test.d.ts +1 -0
  112. package/dist/features/background-agent/error-classifier.test.d.ts +1 -0
  113. package/dist/features/background-agent/fallback-retry-handler.test.d.ts +1 -0
  114. package/dist/features/background-agent/manager.d.ts +13 -0
  115. package/dist/features/background-agent/manager.polling.test.d.ts +1 -0
  116. package/dist/features/background-agent/manager.test.d.ts +1 -0
  117. package/dist/features/background-agent/process-cleanup.test.d.ts +1 -0
  118. package/dist/features/background-agent/session-idle-event-handler.test.d.ts +1 -0
  119. package/dist/features/background-agent/spawner/parent-directory-resolver.test.d.ts +1 -0
  120. package/dist/features/background-agent/spawner.test.d.ts +1 -0
  121. package/dist/features/background-agent/task-history.test.d.ts +1 -0
  122. package/dist/features/background-agent/task-poller.test.d.ts +1 -0
  123. package/dist/features/background-agent/types.d.ts +9 -4
  124. package/dist/features/boulder-state/storage.test.d.ts +1 -0
  125. package/dist/features/builtin-commands/commands.test.d.ts +1 -0
  126. package/dist/features/builtin-commands/templates/stop-continuation.test.d.ts +1 -0
  127. package/dist/features/builtin-skills/skills/git/git-commit.d.ts +2 -0
  128. package/dist/features/builtin-skills/skills/git/git-history.d.ts +1 -0
  129. package/dist/features/builtin-skills/skills/git/git-push-pr.d.ts +1 -0
  130. package/dist/features/builtin-skills/skills/git/git-rebase.d.ts +1 -0
  131. package/dist/features/builtin-skills/skills/git/git-shared.d.ts +2 -0
  132. package/dist/features/builtin-skills/skills.test.d.ts +1 -0
  133. package/dist/features/claude-code-mcp-loader/loader.test.d.ts +1 -0
  134. package/dist/features/claude-code-session-state/state.d.ts +4 -0
  135. package/dist/features/claude-code-session-state/state.test.d.ts +1 -0
  136. package/dist/features/claude-tasks/session-storage.test.d.ts +1 -0
  137. package/dist/features/claude-tasks/storage.test.d.ts +1 -0
  138. package/dist/features/claude-tasks/types.test.d.ts +1 -0
  139. package/dist/features/context-injector/collector.test.d.ts +1 -0
  140. package/dist/features/context-injector/injector.test.d.ts +1 -0
  141. package/dist/features/hook-message-injector/injector.d.ts +2 -0
  142. package/dist/features/hook-message-injector/injector.test.d.ts +1 -0
  143. package/dist/features/issue-resolution/state.d.ts +12 -0
  144. package/dist/features/issue-resolution/tests/integration.test.d.ts +1 -0
  145. package/dist/features/mcp-oauth/callback-server.test.d.ts +1 -0
  146. package/dist/features/mcp-oauth/dcr.test.d.ts +1 -0
  147. package/dist/features/mcp-oauth/discovery.test.d.ts +1 -0
  148. package/dist/features/mcp-oauth/provider.test.d.ts +1 -0
  149. package/dist/features/mcp-oauth/resource-indicator.test.d.ts +1 -0
  150. package/dist/features/mcp-oauth/schema.test.d.ts +1 -0
  151. package/dist/features/mcp-oauth/step-up.test.d.ts +1 -0
  152. package/dist/features/mcp-oauth/storage.test.d.ts +1 -0
  153. package/dist/features/opencode-skill-loader/agents-skills-global.test.d.ts +1 -0
  154. package/dist/features/opencode-skill-loader/async-loader.test.d.ts +1 -0
  155. package/dist/features/opencode-skill-loader/blocking.test.d.ts +1 -0
  156. package/dist/features/opencode-skill-loader/config-source-discovery.test.d.ts +1 -0
  157. package/dist/features/opencode-skill-loader/loader.test.d.ts +1 -0
  158. package/dist/features/opencode-skill-loader/merger.test.d.ts +1 -0
  159. package/dist/features/opencode-skill-loader/skill-content.test.d.ts +1 -0
  160. package/dist/features/pr-state/storage.d.ts +10 -0
  161. package/dist/features/run-continuation-state/storage.test.d.ts +1 -0
  162. package/dist/features/run-state-watchdog/index.d.ts +1 -0
  163. package/dist/features/run-state-watchdog/manager.d.ts +31 -0
  164. package/dist/features/run-state-watchdog/manager.test.d.ts +1 -0
  165. package/dist/features/run-state-watchdog/reconnect.test.d.ts +1 -0
  166. package/dist/features/skill-mcp-manager/env-cleaner.test.d.ts +1 -0
  167. package/dist/features/skill-mcp-manager/manager.test.d.ts +1 -0
  168. package/dist/features/task-toast-manager/manager.d.ts +9 -0
  169. package/dist/features/task-toast-manager/manager.test.d.ts +1 -0
  170. package/dist/features/task-toast-manager/types.d.ts +5 -0
  171. package/dist/features/tmux-subagent/action-executor.test.d.ts +1 -0
  172. package/dist/features/tmux-subagent/decision-engine.test.d.ts +1 -0
  173. package/dist/features/tmux-subagent/layout-config.test.d.ts +1 -0
  174. package/dist/features/tmux-subagent/manager.test.d.ts +1 -0
  175. package/dist/features/tmux-subagent/polling-manager.test.d.ts +1 -0
  176. package/dist/features/tool-metadata-store/index.test.d.ts +1 -0
  177. package/dist/hooks/anthropic-context-window-limit-recovery/empty-content-recovery-sdk.test.d.ts +1 -0
  178. package/dist/hooks/anthropic-context-window-limit-recovery/executor.test.d.ts +1 -0
  179. package/dist/hooks/anthropic-context-window-limit-recovery/parser.test.d.ts +1 -0
  180. package/dist/hooks/anthropic-context-window-limit-recovery/pruning-deduplication.test.d.ts +1 -0
  181. package/dist/hooks/anthropic-context-window-limit-recovery/recovery-deduplication.test.d.ts +1 -0
  182. package/dist/hooks/anthropic-context-window-limit-recovery/recovery-hook.test.d.ts +1 -0
  183. package/dist/hooks/anthropic-context-window-limit-recovery/storage.test.d.ts +1 -0
  184. package/dist/hooks/anthropic-context-window-limit-recovery/summarize-retry-strategy.test.d.ts +1 -0
  185. package/dist/hooks/anthropic-effort/index.test.d.ts +1 -0
  186. package/dist/hooks/atlas/index.test.d.ts +1 -0
  187. package/dist/hooks/auto-slash-command/constants.test.d.ts +1 -0
  188. package/dist/hooks/auto-slash-command/detector.test.d.ts +1 -0
  189. package/dist/hooks/auto-slash-command/executor.test.d.ts +1 -0
  190. package/dist/hooks/auto-slash-command/index.test.d.ts +1 -0
  191. package/dist/hooks/auto-update-checker/checker/pinned-version-updater.test.d.ts +1 -0
  192. package/dist/hooks/auto-update-checker/checker/plugin-entry.test.d.ts +1 -0
  193. package/dist/hooks/auto-update-checker/checker.test.d.ts +1 -0
  194. package/dist/hooks/auto-update-checker/hook/background-update-check.test.d.ts +1 -0
  195. package/dist/hooks/auto-update-checker/hook.test.d.ts +1 -0
  196. package/dist/hooks/auto-update-checker/index.test.d.ts +1 -0
  197. package/dist/hooks/category-skill-reminder/formatter.d.ts +1 -1
  198. package/dist/hooks/category-skill-reminder/hook.d.ts +1 -1
  199. package/dist/hooks/category-skill-reminder/index.test.d.ts +1 -0
  200. package/dist/hooks/claude-code-hooks/execute-http-hook.test.d.ts +1 -0
  201. package/dist/hooks/claude-code-hooks/stop.test.d.ts +1 -0
  202. package/dist/hooks/claude-code-hooks/transcript.test.d.ts +1 -0
  203. package/dist/hooks/claude-code-hooks/user-prompt-submit.test.d.ts +1 -0
  204. package/dist/hooks/comment-checker/cli.test.d.ts +1 -0
  205. package/dist/hooks/comment-checker/hook.apply-patch.test.d.ts +1 -0
  206. package/dist/hooks/comment-checker/pending-calls.test.d.ts +1 -0
  207. package/dist/hooks/compaction-context-injector/index.test.d.ts +1 -0
  208. package/dist/hooks/compaction-todo-preserver/index.test.d.ts +1 -0
  209. package/dist/hooks/context-window-monitor.test.d.ts +1 -0
  210. package/dist/hooks/delegate-task-retry/index.test.d.ts +1 -0
  211. package/dist/hooks/directory-agents-injector/injector.test.d.ts +1 -0
  212. package/dist/hooks/directory-readme-injector/injector.test.d.ts +1 -0
  213. package/dist/hooks/edit-error-recovery/hook.d.ts +2 -1
  214. package/dist/hooks/edit-error-recovery/index.test.d.ts +1 -0
  215. package/dist/hooks/edit-safeguard/hook.d.ts +2 -0
  216. package/dist/hooks/execution-journal/hook.d.ts +19 -0
  217. package/dist/hooks/execution-journal/index.d.ts +1 -0
  218. package/dist/hooks/hashline-read-enhancer/index.test.d.ts +1 -0
  219. package/dist/hooks/index.d.ts +11 -3
  220. package/dist/hooks/json-error-recovery/index.test.d.ts +1 -0
  221. package/dist/hooks/keyword-detector/constants.d.ts +1 -0
  222. package/dist/hooks/keyword-detector/detector.d.ts +1 -1
  223. package/dist/hooks/keyword-detector/index.test.d.ts +1 -0
  224. package/dist/hooks/keyword-detector/issue.d.ts +2 -0
  225. package/dist/hooks/model-fallback/hook.test.d.ts +1 -0
  226. package/dist/hooks/no-hephaestus-non-gpt/index.test.d.ts +1 -0
  227. package/dist/hooks/no-sisyphus-gpt/index.test.d.ts +1 -0
  228. package/dist/hooks/non-interactive-env/index.test.d.ts +1 -0
  229. package/dist/hooks/plan-enforcement/hook.d.ts +29 -0
  230. package/dist/hooks/plan-enforcement/hook.test.d.ts +1 -0
  231. package/dist/hooks/plan-enforcement/index.d.ts +1 -0
  232. package/dist/hooks/preemptive-compaction.test.d.ts +1 -0
  233. package/dist/hooks/prometheus-md-only/index.test.d.ts +1 -0
  234. package/dist/hooks/question-label-truncator/index.test.d.ts +1 -0
  235. package/dist/hooks/ralph-loop/completion-handler.d.ts +14 -0
  236. package/dist/hooks/ralph-loop/completion-promise-detector.d.ts +1 -1
  237. package/dist/hooks/ralph-loop/completion-promise-detector.test.d.ts +1 -0
  238. package/dist/hooks/ralph-loop/constants.d.ts +1 -0
  239. package/dist/hooks/ralph-loop/index.test.d.ts +1 -0
  240. package/dist/hooks/ralph-loop/loop-state-controller.d.ts +2 -0
  241. package/dist/hooks/ralph-loop/ralph-loop-event-handler.d.ts +2 -0
  242. package/dist/hooks/ralph-loop/reset-strategy-race-condition.test.d.ts +1 -0
  243. package/dist/hooks/ralph-loop/session-event-handler.d.ts +12 -0
  244. package/dist/hooks/ralph-loop/types.d.ts +5 -1
  245. package/dist/hooks/ralph-loop/ulw-loop-verification.test.d.ts +1 -0
  246. package/dist/hooks/read-image-resizer/hook.test.d.ts +1 -0
  247. package/dist/hooks/read-image-resizer/image-dimensions.test.d.ts +1 -0
  248. package/dist/hooks/read-image-resizer/image-resizer.test.d.ts +1 -0
  249. package/dist/hooks/rules-injector/finder.test.d.ts +1 -0
  250. package/dist/hooks/rules-injector/injector.test.d.ts +1 -0
  251. package/dist/hooks/rules-injector/output-path.test.d.ts +1 -0
  252. package/dist/hooks/rules-injector/parser.test.d.ts +1 -0
  253. package/dist/hooks/run-state-watchdog/hook.d.ts +5 -0
  254. package/dist/hooks/run-state-watchdog/index.d.ts +1 -0
  255. package/dist/hooks/runtime-enforcement/hook.d.ts +10 -0
  256. package/dist/hooks/runtime-enforcement/hook.test.d.ts +1 -0
  257. package/dist/hooks/runtime-enforcement/index.d.ts +1 -0
  258. package/dist/hooks/runtime-enforcement/synthetic-injection.test.d.ts +1 -0
  259. package/dist/hooks/runtime-fallback/index.test.d.ts +1 -0
  260. package/dist/hooks/semantic-loop-guard/hook.d.ts +10 -0
  261. package/dist/hooks/semantic-loop-guard/hook.test.d.ts +1 -0
  262. package/dist/hooks/semantic-loop-guard/index.d.ts +1 -0
  263. package/dist/hooks/session-notification-input-needed.test.d.ts +1 -0
  264. package/dist/hooks/session-notification.test.d.ts +1 -0
  265. package/dist/hooks/session-recovery/detect-error-type.test.d.ts +1 -0
  266. package/dist/hooks/session-recovery/index.test.d.ts +1 -0
  267. package/dist/hooks/session-recovery/recover-empty-content-message-sdk.test.d.ts +1 -0
  268. package/dist/hooks/session-recovery/resume.test.d.ts +1 -0
  269. package/dist/hooks/session-recovery/storage/readers-from-sdk.test.d.ts +1 -0
  270. package/dist/hooks/start-work/index.test.d.ts +1 -0
  271. package/dist/hooks/start-work/parse-user-request.test.d.ts +1 -0
  272. package/dist/hooks/start-work/worktree-detector.test.d.ts +1 -0
  273. package/dist/hooks/stop-continuation-guard/index.test.d.ts +1 -0
  274. package/dist/hooks/task-reminder/index.test.d.ts +1 -0
  275. package/dist/hooks/task-resume-info/index.test.d.ts +1 -0
  276. package/dist/hooks/tasks-todowrite-disabler/index.test.d.ts +1 -0
  277. package/dist/hooks/think-mode/index.test.d.ts +1 -0
  278. package/dist/hooks/think-mode/switcher.test.d.ts +1 -0
  279. package/dist/hooks/todo-continuation-enforcer/continuation-injection.test.d.ts +1 -0
  280. package/dist/hooks/todo-continuation-enforcer/pending-question-detection.test.d.ts +1 -0
  281. package/dist/hooks/todo-continuation-enforcer/todo-continuation-enforcer.test.d.ts +1 -0
  282. package/dist/hooks/tool-contract/hook.d.ts +19 -0
  283. package/dist/hooks/tool-contract/hook.test.d.ts +1 -0
  284. package/dist/hooks/tool-contract/index.d.ts +1 -0
  285. package/dist/hooks/tool-output-truncator.test.d.ts +1 -0
  286. package/dist/hooks/unstable-agent-babysitter/index.test.d.ts +1 -0
  287. package/dist/hooks/write-existing-file-guard/index.test.d.ts +1 -0
  288. package/dist/hooks/xai-usage-patch/hook.d.ts +20 -0
  289. package/dist/hooks/xai-usage-patch/hook.test.d.ts +1 -0
  290. package/dist/index.compaction-model-agnostic.static.test.d.ts +1 -0
  291. package/dist/index.js +4980 -14453
  292. package/dist/index.test.d.ts +1 -0
  293. package/dist/mcp/index.test.d.ts +1 -0
  294. package/dist/mcp/websearch.test.d.ts +1 -0
  295. package/dist/oh-my-opencode.schema.json +622 -15
  296. package/dist/plugin/available-categories.d.ts +1 -1
  297. package/dist/plugin/chat-headers.test.d.ts +1 -0
  298. package/dist/plugin/chat-message.test.d.ts +1 -0
  299. package/dist/plugin/chat-params.test.d.ts +1 -0
  300. package/dist/plugin/event.model-fallback.test.d.ts +1 -0
  301. package/dist/plugin/event.test.d.ts +1 -0
  302. package/dist/plugin/hooks/create-core-hooks.d.ts +10 -0
  303. package/dist/plugin/hooks/create-session-hooks.d.ts +4 -1
  304. package/dist/plugin/hooks/create-skill-hooks.d.ts +1 -1
  305. package/dist/plugin/hooks/create-tool-guard-hooks.d.ts +7 -1
  306. package/dist/plugin/recent-synthetic-idles.test.d.ts +1 -0
  307. package/dist/plugin/session-agent-resolver.test.d.ts +1 -0
  308. package/dist/plugin/session-status-normalizer.test.d.ts +1 -0
  309. package/dist/plugin/skill-context.d.ts +1 -1
  310. package/dist/plugin/tool-execute-before-session-notification.test.d.ts +1 -0
  311. package/dist/plugin/tool-execute-before.test.d.ts +1 -0
  312. package/dist/plugin/tool-normalization.test.d.ts +1 -0
  313. package/dist/plugin/tool-registry.d.ts +1 -1
  314. package/dist/plugin/truth-model-integration.test.d.ts +1 -0
  315. package/dist/plugin/ultrawork-db-model-override.test.d.ts +1 -0
  316. package/dist/plugin/ultrawork-model-override.test.d.ts +1 -0
  317. package/dist/plugin-config.test.d.ts +1 -0
  318. package/dist/plugin-handlers/agent-key-remapper.test.d.ts +1 -0
  319. package/dist/plugin-handlers/config-handler-formatter.test.d.ts +1 -0
  320. package/dist/plugin-handlers/config-handler.test.d.ts +1 -0
  321. package/dist/plugin-handlers/mcp-config-handler.test.d.ts +1 -0
  322. package/dist/plugin-handlers/plan-model-inheritance.test.d.ts +1 -0
  323. package/dist/plugin-handlers/prometheus-agent-config-builder.d.ts +3 -2
  324. package/dist/plugin-handlers/tool-config-handler.test.d.ts +1 -0
  325. package/dist/runtime/journal.d.ts +33 -0
  326. package/dist/runtime/plan-compiler.d.ts +20 -0
  327. package/dist/runtime/plan-compiler.test.d.ts +1 -0
  328. package/dist/runtime/startup-validation.d.ts +7 -0
  329. package/dist/runtime/state-ledger.d.ts +52 -0
  330. package/dist/runtime/tools/complete-task.d.ts +4 -0
  331. package/dist/runtime/tools/fs-safe.contract.test.d.ts +1 -0
  332. package/dist/runtime/tools/fs-safe.d.ts +1 -0
  333. package/dist/runtime/tools/gh-safe.d.ts +1 -0
  334. package/dist/runtime/tools/git-safe.d.ts +1 -0
  335. package/dist/runtime/tools/index.d.ts +8 -0
  336. package/dist/runtime/tools/plan.d.ts +3 -0
  337. package/dist/runtime/tools/query-ledger.d.ts +3 -0
  338. package/dist/runtime/tools/registry.d.ts +8 -0
  339. package/dist/runtime/tools/report-issue-verification.d.ts +1 -0
  340. package/dist/runtime/tools/verify.d.ts +1 -0
  341. package/dist/runtime/truth-model.test.d.ts +1 -0
  342. package/dist/runtime/vcs-detection.test.d.ts +1 -0
  343. package/dist/shared/active-task-storage.d.ts +15 -0
  344. package/dist/shared/agent-config-integration.test.d.ts +1 -0
  345. package/dist/shared/agent-display-names.test.d.ts +1 -0
  346. package/dist/shared/agent-variant.test.d.ts +1 -0
  347. package/dist/shared/claude-config-dir.test.d.ts +1 -0
  348. package/dist/shared/connected-providers-cache.test.d.ts +1 -0
  349. package/dist/shared/deep-merge.test.d.ts +1 -0
  350. package/dist/shared/dynamic-truncator.test.d.ts +1 -0
  351. package/dist/shared/external-plugin-detector.test.d.ts +1 -0
  352. package/dist/shared/file-utils.test.d.ts +1 -0
  353. package/dist/shared/first-message-variant.test.d.ts +1 -0
  354. package/dist/shared/frontmatter.test.d.ts +1 -0
  355. package/dist/shared/git-worktree/collect-git-diff-stats.test.d.ts +1 -0
  356. package/dist/shared/git-worktree/git-worktree.test.d.ts +1 -0
  357. package/dist/shared/git-worktree/parse-status-porcelain-line.test.d.ts +1 -0
  358. package/dist/shared/index.d.ts +2 -0
  359. package/dist/shared/jsonc-parser.test.d.ts +1 -0
  360. package/dist/shared/merge-categories.test.d.ts +1 -0
  361. package/dist/shared/migration.test.d.ts +1 -0
  362. package/dist/shared/model-availability.d.ts +6 -0
  363. package/dist/shared/model-availability.test.d.ts +1 -0
  364. package/dist/shared/model-error-classifier.d.ts +5 -0
  365. package/dist/shared/model-error-classifier.test.d.ts +1 -0
  366. package/dist/shared/model-format-normalizer.test.d.ts +1 -0
  367. package/dist/shared/model-normalization.test.d.ts +1 -0
  368. package/dist/shared/model-requirements.d.ts +6 -0
  369. package/dist/shared/model-requirements.test.d.ts +1 -0
  370. package/dist/shared/model-resolution-acceptance.test.d.ts +1 -0
  371. package/dist/shared/model-resolution-pipeline.d.ts +5 -1
  372. package/dist/shared/model-resolution-pipeline.test.d.ts +1 -0
  373. package/dist/shared/model-resolution-tracker.d.ts +17 -0
  374. package/dist/shared/model-resolution-types.d.ts +7 -1
  375. package/dist/shared/model-resolver.d.ts +10 -3
  376. package/dist/shared/model-resolver.test.d.ts +1 -0
  377. package/dist/shared/model-suggestion-retry.test.d.ts +1 -0
  378. package/dist/shared/normalize-sdk-response.test.d.ts +1 -0
  379. package/dist/shared/opencode-config-dir.test.d.ts +1 -0
  380. package/dist/shared/opencode-http-api.test.d.ts +1 -0
  381. package/dist/shared/opencode-message-dir.test.d.ts +1 -0
  382. package/dist/shared/opencode-server-auth.test.d.ts +1 -0
  383. package/dist/shared/opencode-storage-detection.test.d.ts +1 -0
  384. package/dist/shared/opencode-version.test.d.ts +1 -0
  385. package/dist/shared/pattern-matcher.test.d.ts +1 -0
  386. package/dist/shared/permission-compat.test.d.ts +1 -0
  387. package/dist/shared/plugin-command-discovery.test.d.ts +1 -0
  388. package/dist/shared/port-utils.test.d.ts +1 -0
  389. package/dist/shared/prompt-tools.test.d.ts +1 -0
  390. package/dist/shared/read-permission-tracker.d.ts +15 -0
  391. package/dist/shared/safe-create-hook.test.d.ts +1 -0
  392. package/dist/shared/session-cursor.test.d.ts +1 -0
  393. package/dist/shared/session-directory-resolver.test.d.ts +1 -0
  394. package/dist/shared/session-model-state.test.d.ts +1 -0
  395. package/dist/shared/session-tools-store.test.d.ts +1 -0
  396. package/dist/shared/shell-env.test.d.ts +1 -0
  397. package/dist/shared/skill-path-resolver.test.d.ts +1 -0
  398. package/dist/shared/system-directive.test.d.ts +1 -0
  399. package/dist/shared/tmux/tmux-utils/layout.test.d.ts +1 -0
  400. package/dist/shared/tmux/tmux-utils.test.d.ts +1 -0
  401. package/dist/shared/tool-name.test.d.ts +1 -0
  402. package/dist/shared/truncate-description.test.d.ts +1 -0
  403. package/dist/shared/verify-task-completion.d.ts +1 -0
  404. package/dist/tools/background-task/create-background-output.blocking.test.d.ts +1 -0
  405. package/dist/tools/background-task/create-background-task.test.d.ts +1 -0
  406. package/dist/tools/background-task/tools.test.d.ts +1 -0
  407. package/dist/tools/call-omo-agent/background-agent-executor.test.d.ts +1 -0
  408. package/dist/tools/call-omo-agent/background-executor.test.d.ts +1 -0
  409. package/dist/tools/call-omo-agent/session-creator.test.d.ts +1 -0
  410. package/dist/tools/call-omo-agent/subagent-session-creator.test.d.ts +1 -0
  411. package/dist/tools/call-omo-agent/sync-executor.test.d.ts +1 -0
  412. package/dist/tools/call-omo-agent/tools.test.d.ts +1 -0
  413. package/dist/tools/delegate-task/background-continuation.test.d.ts +1 -0
  414. package/dist/tools/delegate-task/background-task.d.ts +1 -1
  415. package/dist/tools/delegate-task/background-task.test.d.ts +1 -0
  416. package/dist/tools/delegate-task/category-resolver.d.ts +1 -0
  417. package/dist/tools/delegate-task/category-resolver.test.d.ts +1 -0
  418. package/dist/tools/delegate-task/constants.d.ts +1 -1
  419. package/dist/tools/delegate-task/metadata-await.test.d.ts +1 -0
  420. package/dist/tools/delegate-task/metadata-model-unification.test.d.ts +1 -0
  421. package/dist/tools/delegate-task/model-selection.d.ts +2 -0
  422. package/dist/tools/delegate-task/subagent-resolver.d.ts +2 -1
  423. package/dist/tools/delegate-task/subagent-resolver.test.d.ts +1 -0
  424. package/dist/tools/delegate-task/sync-continuation.test.d.ts +1 -0
  425. package/dist/tools/delegate-task/sync-poll-timeout.test.d.ts +1 -0
  426. package/dist/tools/delegate-task/sync-prompt-sender.d.ts +1 -0
  427. package/dist/tools/delegate-task/sync-prompt-sender.test.d.ts +1 -0
  428. package/dist/tools/delegate-task/sync-result-fetcher.test.d.ts +1 -0
  429. package/dist/tools/delegate-task/sync-session-poller.test.d.ts +1 -0
  430. package/dist/tools/delegate-task/sync-task.d.ts +1 -1
  431. package/dist/tools/delegate-task/sync-task.test.d.ts +1 -0
  432. package/dist/tools/delegate-task/timing.test.d.ts +1 -0
  433. package/dist/tools/delegate-task/token-limiter.test.d.ts +1 -0
  434. package/dist/tools/delegate-task/tools.test.d.ts +1 -0
  435. package/dist/tools/delegate-task/types.d.ts +1 -1
  436. package/dist/tools/delegate-task/unstable-agent-task.test.d.ts +1 -0
  437. package/dist/tools/delegate-task/unstable-agent-timeout.test.d.ts +1 -0
  438. package/dist/tools/glob/cli.test.d.ts +1 -0
  439. package/dist/tools/grep/downloader.test.d.ts +1 -0
  440. package/dist/tools/grep/result-formatter.test.d.ts +1 -0
  441. package/dist/tools/hashline-edit/diff-utils.test.d.ts +1 -0
  442. package/dist/tools/hashline-edit/edit-operations.test.d.ts +1 -0
  443. package/dist/tools/hashline-edit/edit-text-normalization.test.d.ts +1 -0
  444. package/dist/tools/hashline-edit/hash-computation.test.d.ts +1 -0
  445. package/dist/tools/hashline-edit/normalize-edits.test.d.ts +1 -0
  446. package/dist/tools/hashline-edit/tools.test.d.ts +1 -0
  447. package/dist/tools/hashline-edit/validation.test.d.ts +1 -0
  448. package/dist/tools/look-at/create-look-at-error-handling.test.d.ts +1 -0
  449. package/dist/tools/look-at/create-look-at-image-data.test.d.ts +1 -0
  450. package/dist/tools/look-at/create-look-at-model-passthrough.test.d.ts +1 -0
  451. package/dist/tools/look-at/create-look-at-sync-prompt.test.d.ts +1 -0
  452. package/dist/tools/look-at/create-look-at-unhandled-error.test.d.ts +1 -0
  453. package/dist/tools/look-at/image-converter.test.d.ts +1 -0
  454. package/dist/tools/look-at/mime-type-inference.test.d.ts +1 -0
  455. package/dist/tools/look-at/normalize-args.test.d.ts +1 -0
  456. package/dist/tools/look-at/session-poller.test.d.ts +1 -0
  457. package/dist/tools/look-at/tools.test.d.ts +7 -0
  458. package/dist/tools/look-at/validate-args.test.d.ts +1 -0
  459. package/dist/tools/lsp/client.test.d.ts +1 -0
  460. package/dist/tools/lsp/config.test.d.ts +1 -0
  461. package/dist/tools/lsp/lsp-client-wrapper.d.ts +3 -0
  462. package/dist/tools/lsp/lsp-process.test.d.ts +1 -0
  463. package/dist/tools/lsp/server-config-loader.test.d.ts +1 -0
  464. package/dist/tools/lsp/utils.test.d.ts +1 -0
  465. package/dist/tools/session-manager/storage.test.d.ts +1 -0
  466. package/dist/tools/session-manager/tools.test.d.ts +1 -0
  467. package/dist/tools/session-manager/utils.test.d.ts +1 -0
  468. package/dist/tools/skill/tools.test.agent-restriction.d.ts +1 -0
  469. package/dist/tools/skill/tools.test.d.ts +1 -0
  470. package/dist/tools/skill/tools.test.description.d.ts +1 -0
  471. package/dist/tools/skill/tools.test.mcp-schema.d.ts +1 -0
  472. package/dist/tools/skill/tools.test.ordering.d.ts +1 -0
  473. package/dist/tools/skill/tools.test.utils.d.ts +10 -0
  474. package/dist/tools/skill-mcp/builtin-mcp-hint.test.d.ts +1 -0
  475. package/dist/tools/skill-mcp/tools.test.d.ts +1 -0
  476. package/dist/tools/slashcommand/command-discovery.test.d.ts +1 -0
  477. package/dist/tools/slashcommand/command-output-formatter.test.d.ts +1 -0
  478. package/dist/tools/slashcommand/index.test.d.ts +1 -0
  479. package/dist/tools/task/task-create-input.test.d.ts +1 -0
  480. package/dist/tools/task/task-create.test.d.ts +1 -0
  481. package/dist/tools/task/task-delete-input.test.d.ts +1 -0
  482. package/dist/tools/task/task-get-input.test.d.ts +1 -0
  483. package/dist/tools/task/task-get.test.d.ts +1 -0
  484. package/dist/tools/task/task-list-input.test.d.ts +1 -0
  485. package/dist/tools/task/task-list.test.d.ts +1 -0
  486. package/dist/tools/task/task-schema-core.test.d.ts +1 -0
  487. package/dist/tools/task/task-schema-fields.test.d.ts +1 -0
  488. package/dist/tools/task/task-status.test.d.ts +1 -0
  489. package/dist/tools/task/task-update-input.test.d.ts +1 -0
  490. package/dist/tools/task/task-update.test.d.ts +1 -0
  491. package/dist/tools/task/todo-sync-all-fetch.test.d.ts +1 -0
  492. package/dist/tools/task/todo-sync-all-write.test.d.ts +1 -0
  493. package/dist/tools/task/todo-sync-sync.test.d.ts +1 -0
  494. package/dist/tools/task/todo-sync-update.test.d.ts +1 -0
  495. package/dist/tools/task/todo-sync.test.d.ts +1 -0
  496. package/dist/utils/context-trimmer.d.ts +21 -0
  497. package/dist/utils/safety-tool-result.d.ts +31 -0
  498. package/dist/utils/tool-contract-wrapper.d.ts +10 -0
  499. package/package.json +17 -16
package/dist/cli/index.js CHANGED
@@ -6294,142 +6294,140 @@ var init_binary_downloader = __esm(() => {
6294
6294
  });
6295
6295
 
6296
6296
  // src/shared/model-requirements.ts
6297
- var AGENT_MODEL_REQUIREMENTS, CATEGORY_MODEL_REQUIREMENTS;
6297
+ var DEFAULT_AGENT_MODEL_REQUIREMENTS, DEFAULT_CATEGORY_MODEL_REQUIREMENTS, AGENT_MODEL_REQUIREMENTS, CATEGORY_MODEL_REQUIREMENTS;
6298
6298
  var init_model_requirements = __esm(() => {
6299
- AGENT_MODEL_REQUIREMENTS = {
6299
+ DEFAULT_AGENT_MODEL_REQUIREMENTS = {
6300
6300
  sisyphus: {
6301
6301
  fallbackChain: [
6302
- { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
6303
- { providers: ["zai-coding-plan", "opencode"], model: "glm-5" },
6302
+ { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-3-5-sonnet", variant: "max" },
6304
6303
  { providers: ["opencode"], model: "big-pickle" }
6305
6304
  ],
6306
6305
  requiresAnyModel: true
6307
6306
  },
6308
6307
  hephaestus: {
6309
6308
  fallbackChain: [
6310
- { providers: ["openai", "venice", "opencode"], model: "gpt-5.3-codex", variant: "medium" },
6311
- { providers: ["github-copilot"], model: "gpt-5.2", variant: "medium" }
6309
+ { providers: ["openai", "opencode"], model: "gpt-4o", variant: "medium" },
6310
+ { providers: ["anthropic", "opencode"], model: "claude-3-5-sonnet", variant: "max" }
6312
6311
  ],
6313
- requiresProvider: ["openai", "github-copilot", "venice", "opencode"]
6312
+ requiresAnyModel: true
6314
6313
  },
6315
6314
  oracle: {
6316
6315
  fallbackChain: [
6317
- { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
6318
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" },
6319
- { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" }
6316
+ { providers: ["openai", "github-copilot", "opencode"], model: "gpt-4o", variant: "high" },
6317
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-1.5-pro", variant: "high" },
6318
+ { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-3-5-sonnet", variant: "max" }
6320
6319
  ]
6321
6320
  },
6322
6321
  librarian: {
6323
6322
  fallbackChain: [
6324
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" },
6325
- { providers: ["opencode"], model: "minimax-m2.5-free" },
6323
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-1.5-flash" },
6324
+ { providers: ["opencode"], model: "minimax-text-01" },
6326
6325
  { providers: ["opencode"], model: "big-pickle" }
6327
6326
  ]
6328
6327
  },
6329
6328
  explore: {
6330
6329
  fallbackChain: [
6331
- { providers: ["github-copilot"], model: "grok-code-fast-1" },
6332
- { providers: ["opencode"], model: "minimax-m2.5-free" },
6333
- { providers: ["anthropic", "opencode"], model: "claude-haiku-4-5" },
6334
- { providers: ["opencode"], model: "gpt-5-nano" }
6330
+ { providers: ["github-copilot"], model: "gpt-4o-mini" },
6331
+ { providers: ["opencode"], model: "minimax-text-01" },
6332
+ { providers: ["anthropic", "opencode"], model: "claude-3-5-haiku" },
6333
+ { providers: ["opencode"], model: "gpt-4o-mini" }
6335
6334
  ]
6336
6335
  },
6337
6336
  "multimodal-looker": {
6338
6337
  fallbackChain: [
6339
- { providers: ["openai", "opencode"], model: "gpt-5.3-codex", variant: "medium" },
6340
- { providers: ["kimi-for-coding"], model: "k2p5" },
6341
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" },
6342
- { providers: ["zai-coding-plan"], model: "glm-4.6v" },
6343
- { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5-nano" }
6338
+ { providers: ["openai", "opencode"], model: "gpt-4o", variant: "medium" },
6339
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-1.5-flash" },
6340
+ { providers: ["openai", "github-copilot", "opencode"], model: "gpt-4o-mini" }
6344
6341
  ]
6345
6342
  },
6346
6343
  prometheus: {
6347
6344
  fallbackChain: [
6348
- { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
6349
- { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
6350
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro" }
6345
+ { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-3-5-sonnet", variant: "max" },
6346
+ { providers: ["openai", "github-copilot", "opencode"], model: "gpt-4o", variant: "high" },
6347
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-1.5-pro" }
6351
6348
  ]
6352
6349
  },
6353
6350
  metis: {
6354
6351
  fallbackChain: [
6355
- { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
6356
- { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
6357
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" }
6352
+ { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-3-5-sonnet", variant: "max" },
6353
+ { providers: ["openai", "github-copilot", "opencode"], model: "gpt-4o", variant: "high" },
6354
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-1.5-pro", variant: "high" }
6358
6355
  ]
6359
6356
  },
6360
6357
  momus: {
6361
6358
  fallbackChain: [
6362
- { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "medium" },
6363
- { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
6364
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" }
6359
+ { providers: ["openai", "github-copilot", "opencode"], model: "gpt-4o", variant: "medium" },
6360
+ { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-3-5-sonnet", variant: "max" },
6361
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-1.5-pro", variant: "high" }
6365
6362
  ]
6366
6363
  },
6367
6364
  atlas: {
6368
6365
  fallbackChain: [
6369
- { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-6" },
6370
- { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2" }
6366
+ { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-3-5-sonnet" },
6367
+ { providers: ["openai", "github-copilot", "opencode"], model: "gpt-4o" }
6371
6368
  ]
6372
6369
  }
6373
6370
  };
6374
- CATEGORY_MODEL_REQUIREMENTS = {
6371
+ DEFAULT_CATEGORY_MODEL_REQUIREMENTS = {
6375
6372
  "visual-engineering": {
6376
6373
  fallbackChain: [
6377
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" },
6378
- { providers: ["zai-coding-plan", "opencode"], model: "glm-5" },
6379
- { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" }
6374
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-1.5-pro", variant: "high" },
6375
+ { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-3-5-sonnet", variant: "max" }
6380
6376
  ]
6381
6377
  },
6382
6378
  ultrabrain: {
6383
6379
  fallbackChain: [
6384
- { providers: ["openai", "opencode"], model: "gpt-5.3-codex", variant: "xhigh" },
6385
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" },
6386
- { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" }
6380
+ { providers: ["openai", "opencode"], model: "o3-mini", variant: "xhigh" },
6381
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-1.5-pro", variant: "high" },
6382
+ { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-3-5-sonnet", variant: "max" }
6387
6383
  ]
6388
6384
  },
6389
6385
  deep: {
6390
6386
  fallbackChain: [
6391
- { providers: ["openai", "opencode"], model: "gpt-5.3-codex", variant: "medium" },
6392
- { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
6393
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" }
6387
+ { providers: ["openai", "opencode"], model: "o3-mini", variant: "medium" },
6388
+ { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-3-5-sonnet", variant: "max" },
6389
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-1.5-pro", variant: "high" }
6394
6390
  ],
6395
- requiresModel: "gpt-5.3-codex"
6391
+ requiresModel: "o3-mini"
6396
6392
  },
6397
6393
  artistry: {
6398
6394
  fallbackChain: [
6399
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" },
6400
- { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
6401
- { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2" }
6395
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-1.5-pro", variant: "high" },
6396
+ { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-3-5-sonnet", variant: "max" },
6397
+ { providers: ["openai", "github-copilot", "opencode"], model: "gpt-4o" }
6402
6398
  ],
6403
- requiresModel: "gemini-3.1-pro"
6399
+ requiresModel: "gemini-1.5-pro"
6404
6400
  },
6405
6401
  quick: {
6406
6402
  fallbackChain: [
6407
- { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-haiku-4-5" },
6408
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" },
6409
- { providers: ["opencode"], model: "gpt-5-nano" }
6403
+ { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-3-5-haiku" },
6404
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-1.5-flash" },
6405
+ { providers: ["opencode"], model: "gpt-4o-mini" }
6410
6406
  ]
6411
6407
  },
6412
6408
  "unspecified-low": {
6413
6409
  fallbackChain: [
6414
- { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-6" },
6415
- { providers: ["openai", "opencode"], model: "gpt-5.3-codex", variant: "medium" },
6416
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" }
6410
+ { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-3-5-sonnet" },
6411
+ { providers: ["openai", "opencode"], model: "gpt-4o", variant: "medium" },
6412
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-1.5-flash" }
6417
6413
  ]
6418
6414
  },
6419
6415
  "unspecified-high": {
6420
6416
  fallbackChain: [
6421
- { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
6422
- { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
6423
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro" }
6417
+ { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-3-5-sonnet", variant: "max" },
6418
+ { providers: ["openai", "github-copilot", "opencode"], model: "gpt-4o", variant: "high" },
6419
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-1.5-pro" }
6424
6420
  ]
6425
6421
  },
6426
6422
  writing: {
6427
6423
  fallbackChain: [
6428
- { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" },
6429
- { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-6" }
6424
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-1.5-flash" },
6425
+ { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-3-5-sonnet" }
6430
6426
  ]
6431
6427
  }
6432
6428
  };
6429
+ AGENT_MODEL_REQUIREMENTS = DEFAULT_AGENT_MODEL_REQUIREMENTS;
6430
+ CATEGORY_MODEL_REQUIREMENTS = DEFAULT_CATEGORY_MODEL_REQUIREMENTS;
6433
6431
  });
6434
6432
 
6435
6433
  // src/shared/agent-variant.ts
@@ -6447,8 +6445,15 @@ var init_system_directive = () => {};
6447
6445
 
6448
6446
  // src/shared/agent-tool-restrictions.ts
6449
6447
  var init_agent_tool_restrictions = () => {};
6448
+
6449
+ // src/shared/model-normalization.ts
6450
+ function normalizeModel(model) {
6451
+ const trimmed = model?.trim();
6452
+ return trimmed || undefined;
6453
+ }
6454
+
6450
6455
  // src/shared/connected-providers-cache.ts
6451
- import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync } from "fs";
6456
+ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync, statSync } from "fs";
6452
6457
  import { join as join4 } from "path";
6453
6458
  function getCacheFilePath(filename) {
6454
6459
  return join4(getOmoOpenCodeCacheDir(), filename);
@@ -6459,6 +6464,30 @@ function ensureCacheDir() {
6459
6464
  mkdirSync(cacheDir, { recursive: true });
6460
6465
  }
6461
6466
  }
6467
+ function readConnectedProvidersCache() {
6468
+ const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE);
6469
+ if (!existsSync3(cacheFile)) {
6470
+ memoryConnectedProviders = null;
6471
+ memoryConnectedProvidersMtime = 0;
6472
+ log("[connected-providers-cache] Cache file not found", { cacheFile });
6473
+ return null;
6474
+ }
6475
+ try {
6476
+ const mtime = statSync(cacheFile).mtimeMs;
6477
+ if (memoryConnectedProviders && memoryConnectedProvidersMtime === mtime) {
6478
+ return memoryConnectedProviders;
6479
+ }
6480
+ const content = readFileSync2(cacheFile, "utf-8");
6481
+ const data = JSON.parse(content);
6482
+ memoryConnectedProviders = data.connected;
6483
+ memoryConnectedProvidersMtime = mtime;
6484
+ log("[connected-providers-cache] Read cache", { count: data.connected.length, updatedAt: data.updatedAt });
6485
+ return data.connected;
6486
+ } catch (err) {
6487
+ log("[connected-providers-cache] Error reading cache", { error: String(err) });
6488
+ return null;
6489
+ }
6490
+ }
6462
6491
  function writeConnectedProvidersCache(connected) {
6463
6492
  ensureCacheDir();
6464
6493
  const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE);
@@ -6468,6 +6497,7 @@ function writeConnectedProvidersCache(connected) {
6468
6497
  };
6469
6498
  try {
6470
6499
  writeFileSync2(cacheFile, JSON.stringify(data, null, 2));
6500
+ memoryConnectedProviders = null;
6471
6501
  log("[connected-providers-cache] Cache written", { count: connected.length });
6472
6502
  } catch (err) {
6473
6503
  log("[connected-providers-cache] Error writing cache", { error: String(err) });
@@ -6486,6 +6516,7 @@ function writeProviderModelsCache(data) {
6486
6516
  };
6487
6517
  try {
6488
6518
  writeFileSync2(cacheFile, JSON.stringify(cacheData, null, 2));
6519
+ memoryProviderModels = null;
6489
6520
  log("[connected-providers-cache] Provider-models cache written", {
6490
6521
  providerCount: Object.keys(data.models).length
6491
6522
  });
@@ -6525,15 +6556,62 @@ async function updateConnectedProvidersCache(client) {
6525
6556
  log("[connected-providers-cache] Error updating cache", { error: String(err) });
6526
6557
  }
6527
6558
  }
6528
- var CONNECTED_PROVIDERS_CACHE_FILE = "connected-providers.json", PROVIDER_MODELS_CACHE_FILE = "provider-models.json";
6559
+ var CONNECTED_PROVIDERS_CACHE_FILE = "connected-providers.json", PROVIDER_MODELS_CACHE_FILE = "provider-models.json", memoryConnectedProviders = null, memoryConnectedProvidersMtime = 0, memoryProviderModels = null;
6529
6560
  var init_connected_providers_cache = __esm(() => {
6530
6561
  init_logger();
6531
6562
  init_data_path();
6532
6563
  });
6533
6564
 
6534
6565
  // src/shared/model-availability.ts
6535
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
6566
+ import { existsSync as existsSync4, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
6536
6567
  import { join as join5 } from "path";
6568
+ function normalizeModelName(name) {
6569
+ return name.toLowerCase().replace(/claude-(opus|sonnet|haiku)-(\d+)[.-](\d+)/g, "claude-$1-$2.$3");
6570
+ }
6571
+ function fuzzyMatchModel(target, available, providers) {
6572
+ log("[fuzzyMatchModel] called", { target, availableCount: available.size, providers });
6573
+ if (available.size === 0) {
6574
+ log("[fuzzyMatchModel] empty available set");
6575
+ return null;
6576
+ }
6577
+ const targetNormalized = normalizeModelName(target);
6578
+ let candidates = Array.from(available);
6579
+ if (providers && providers.length > 0) {
6580
+ const providerSet = new Set(providers);
6581
+ candidates = candidates.filter((model) => {
6582
+ const [provider] = model.split("/");
6583
+ return providerSet.has(provider);
6584
+ });
6585
+ log("[fuzzyMatchModel] filtered by providers", { candidateCount: candidates.length, candidates: candidates.slice(0, 10) });
6586
+ }
6587
+ if (candidates.length === 0) {
6588
+ log("[fuzzyMatchModel] no candidates after filter");
6589
+ return null;
6590
+ }
6591
+ const matches = candidates.filter((model) => normalizeModelName(model).includes(targetNormalized));
6592
+ log("[fuzzyMatchModel] substring matches", { targetNormalized, matchCount: matches.length, matches });
6593
+ if (matches.length === 0) {
6594
+ log("[fuzzyMatchModel] WARNING: no match found", { target, availableCount: available.size, providers });
6595
+ return null;
6596
+ }
6597
+ const exactMatch = matches.find((model) => normalizeModelName(model) === targetNormalized);
6598
+ if (exactMatch) {
6599
+ log("[fuzzyMatchModel] exact match found", { exactMatch });
6600
+ return exactMatch;
6601
+ }
6602
+ const exactModelIdMatches = matches.filter((model) => {
6603
+ const modelId = model.split("/").slice(1).join("/");
6604
+ return normalizeModelName(modelId) === targetNormalized;
6605
+ });
6606
+ if (exactModelIdMatches.length > 0) {
6607
+ const result2 = exactModelIdMatches.reduce((shortest, current) => current.length < shortest.length ? current : shortest);
6608
+ log("[fuzzyMatchModel] exact model ID match found", { result: result2, candidateCount: exactModelIdMatches.length });
6609
+ return result2;
6610
+ }
6611
+ const result = matches.reduce((shortest, current) => current.length < shortest.length ? current : shortest);
6612
+ log("[fuzzyMatchModel] shortest match", { result });
6613
+ return result;
6614
+ }
6537
6615
  function isModelCacheAvailable() {
6538
6616
  if (hasProviderModelsCache()) {
6539
6617
  return true;
@@ -6559,6 +6637,113 @@ function transformModelForProvider(provider, model) {
6559
6637
  }
6560
6638
 
6561
6639
  // src/shared/model-resolution-pipeline.ts
6640
+ function resolveModelPipeline(request) {
6641
+ const attempted = [];
6642
+ const { intent, constraints, policy } = request;
6643
+ const availableModels = constraints.availableModels;
6644
+ const fallbackChain = policy?.fallbackChain;
6645
+ const systemDefaultModel = policy?.systemDefaultModel;
6646
+ const tryResolve = (model, provenance = "override") => {
6647
+ const normalized = normalizeModel(model);
6648
+ if (!normalized)
6649
+ return;
6650
+ attempted.push(normalized);
6651
+ if (availableModels.size > 0) {
6652
+ const parts = normalized.split("/");
6653
+ const providerHint = parts.length >= 2 ? [parts[0]] : undefined;
6654
+ const match = fuzzyMatchModel(normalized, availableModels, providerHint);
6655
+ if (match) {
6656
+ log(`[MODEL-RESOLUTION] Resolved via ${provenance}`, { requested: normalized, resolved: match });
6657
+ return { model: match, provenance, attempted };
6658
+ }
6659
+ log(`[MODEL-RESOLUTION] Requested model not available in current providers`, { requested: normalized, provenance });
6660
+ return;
6661
+ } else {
6662
+ log(`[MODEL-RESOLUTION] Resolved via ${provenance} (no availability cache)`, { model: normalized });
6663
+ return { model: normalized, provenance, attempted };
6664
+ }
6665
+ };
6666
+ const uiResult = tryResolve(intent?.uiSelectedModel);
6667
+ if (uiResult)
6668
+ return uiResult;
6669
+ const userResult = tryResolve(intent?.userModel);
6670
+ if (userResult)
6671
+ return userResult;
6672
+ const userFallbackResult = tryResolve(intent?.userFallbackModel, "user-fallback");
6673
+ if (userFallbackResult)
6674
+ return userFallbackResult;
6675
+ const sessionResult = tryResolve(intent?.sessionModel);
6676
+ if (sessionResult)
6677
+ return sessionResult;
6678
+ const categoryResult = tryResolve(intent?.categoryDefaultModel, "category-default");
6679
+ if (categoryResult)
6680
+ return categoryResult;
6681
+ const categoryFallbackResult = tryResolve(intent?.categoryFallbackModel, "category-default");
6682
+ if (categoryFallbackResult)
6683
+ return categoryFallbackResult;
6684
+ const userFallbackModels = intent?.userFallbackModels;
6685
+ if (userFallbackModels && userFallbackModels.length > 0) {
6686
+ for (const model of userFallbackModels) {
6687
+ const res = tryResolve(model, "user-fallback");
6688
+ if (res)
6689
+ return res;
6690
+ }
6691
+ }
6692
+ if (fallbackChain && fallbackChain.length > 0) {
6693
+ if (availableModels.size === 0) {
6694
+ const connectedProviders = constraints.connectedProviders ?? readConnectedProvidersCache();
6695
+ const connectedSet = connectedProviders ? new Set(connectedProviders) : null;
6696
+ if (connectedSet !== null) {
6697
+ for (const entry of fallbackChain) {
6698
+ for (const provider of entry.providers) {
6699
+ if (connectedSet.has(provider)) {
6700
+ const transformedModelId = transformModelForProvider(provider, entry.model);
6701
+ const model = `${provider}/${transformedModelId}`;
6702
+ log("[MODEL-RESOLUTION] Resolved via provider-specific fallback chain (no cache)", {
6703
+ provider,
6704
+ model: transformedModelId
6705
+ });
6706
+ return {
6707
+ model,
6708
+ provenance: "provider-fallback",
6709
+ variant: entry.variant,
6710
+ attempted
6711
+ };
6712
+ }
6713
+ }
6714
+ }
6715
+ }
6716
+ } else {
6717
+ for (const entry of fallbackChain) {
6718
+ for (const provider of entry.providers) {
6719
+ const fullModel = `${provider}/${entry.model}`;
6720
+ const match = fuzzyMatchModel(fullModel, availableModels, [provider]);
6721
+ if (match) {
6722
+ log("[MODEL-RESOLUTION] Resolved via provider-specific fallback chain", {
6723
+ provider,
6724
+ model: entry.model,
6725
+ match
6726
+ });
6727
+ return {
6728
+ model: match,
6729
+ provenance: "provider-fallback",
6730
+ variant: entry.variant,
6731
+ attempted
6732
+ };
6733
+ }
6734
+ }
6735
+ }
6736
+ }
6737
+ }
6738
+ const systemDefaultResult = tryResolve(systemDefaultModel, "system-default");
6739
+ if (systemDefaultResult)
6740
+ return systemDefaultResult;
6741
+ const systemDefaultFallbackResult = tryResolve(policy?.systemDefaultFallbackModel, "system-default");
6742
+ if (systemDefaultFallbackResult)
6743
+ return systemDefaultFallbackResult;
6744
+ log("[MODEL-RESOLUTION] FAILED to resolve any supported model", { attempted });
6745
+ return;
6746
+ }
6562
6747
  var init_model_resolution_pipeline = __esm(() => {
6563
6748
  init_logger();
6564
6749
  init_connected_providers_cache();
@@ -6570,6 +6755,39 @@ var init_model_resolver = __esm(() => {
6570
6755
  init_model_resolution_pipeline();
6571
6756
  });
6572
6757
 
6758
+ // src/shared/model-resolution-tracker.ts
6759
+ class ModelResolutionTracker {
6760
+ lastResolved = new Map;
6761
+ resolve(contextKey, request) {
6762
+ const result = resolveModelPipeline(request);
6763
+ if (!result)
6764
+ return;
6765
+ const previousVisibleModel = this.lastResolved.get(contextKey);
6766
+ const currentModel = result.model;
6767
+ if (currentModel !== previousVisibleModel) {
6768
+ this.lastResolved.set(contextKey, currentModel);
6769
+ if (true) {
6770
+ console.log(`[MODEL-RESOLUTION] [${contextKey}] Resolved to: ${currentModel} (via ${result.provenance})`);
6771
+ }
6772
+ log(`Model resolution changed for ${contextKey}`, {
6773
+ model: currentModel,
6774
+ provenance: result.provenance,
6775
+ previous: previousVisibleModel
6776
+ });
6777
+ }
6778
+ return result;
6779
+ }
6780
+ clear(contextKey) {
6781
+ this.lastResolved.delete(contextKey);
6782
+ }
6783
+ }
6784
+ var modelResolutionTracker;
6785
+ var init_model_resolution_tracker = __esm(() => {
6786
+ init_model_resolution_pipeline();
6787
+ init_logger();
6788
+ modelResolutionTracker = new ModelResolutionTracker;
6789
+ });
6790
+
6573
6791
  // src/shared/fallback-model-availability.ts
6574
6792
  var init_fallback_model_availability = __esm(() => {
6575
6793
  init_connected_providers_cache();
@@ -6877,11 +7095,84 @@ var init_session_category_registry = __esm(() => {
6877
7095
  sessionCategoryMap = new Map;
6878
7096
  });
6879
7097
 
7098
+ // src/shared/verify-task-completion.ts
7099
+ async function verifyTaskCompletionState(client, sessionID) {
7100
+ try {
7101
+ const response = await client.session.messages({
7102
+ path: { id: sessionID }
7103
+ });
7104
+ const messages = normalizeSDKResponse(response, [], { preferResponseOnMissingData: true });
7105
+ let lastCompleteTaskIndex = -1;
7106
+ let lastCompleteTaskPart = null;
7107
+ for (let i2 = messages.length - 1;i2 >= 0; i2--) {
7108
+ const msg = messages[i2];
7109
+ if (msg.info?.role === "assistant" || msg.info?.role === "tool") {
7110
+ const parts = msg.parts ?? [];
7111
+ for (const p of parts) {
7112
+ if (p.type === "tool" && (p.toolName === "complete_task" || p.name === "complete_task" || p.tool === "complete_task")) {
7113
+ lastCompleteTaskIndex = i2;
7114
+ lastCompleteTaskPart = p;
7115
+ break;
7116
+ }
7117
+ }
7118
+ }
7119
+ if (lastCompleteTaskIndex !== -1)
7120
+ break;
7121
+ }
7122
+ if (lastCompleteTaskIndex !== -1 && lastCompleteTaskPart) {
7123
+ let hasErrorResult = false;
7124
+ let foundResult = false;
7125
+ if (lastCompleteTaskPart.state && (lastCompleteTaskPart.state.status === "completed" || lastCompleteTaskPart.state.status === "error")) {
7126
+ foundResult = true;
7127
+ const output = typeof lastCompleteTaskPart.state.output === "string" ? lastCompleteTaskPart.state.output : JSON.stringify(lastCompleteTaskPart.state.output || "");
7128
+ if (output.includes("[ERROR] TASK COMPLETION REJECTED") || output.includes("[ERROR] STRICT ISSUE RESOLUTION MODE") || output.includes("explicitly failed") || lastCompleteTaskPart.state.status === "error") {
7129
+ hasErrorResult = true;
7130
+ }
7131
+ }
7132
+ if (!foundResult) {
7133
+ for (let i2 = lastCompleteTaskIndex + 1;i2 < messages.length; i2++) {
7134
+ const msg = messages[i2];
7135
+ if (msg.info?.role === "user" || msg.info?.role === "tool") {
7136
+ const parts = msg.parts ?? [];
7137
+ for (const p of parts) {
7138
+ if (p.type === "tool_result" || p.type === "tool") {
7139
+ foundResult = true;
7140
+ const content = typeof p.content === "string" ? p.content : typeof p.state?.output === "string" ? p.state.output : JSON.stringify(p.content || p.state?.output || "");
7141
+ if (content.includes("[ERROR] TASK COMPLETION REJECTED") || content.includes("[ERROR] STRICT ISSUE RESOLUTION MODE") || content.includes("explicitly failed") || p.state?.status === "error") {
7142
+ hasErrorResult = true;
7143
+ }
7144
+ } else if (p.type === "text" && typeof p.text === "string" && p.text.includes("[tool result]")) {
7145
+ if (p.text.includes("[ERROR] TASK COMPLETION REJECTED") || p.text.includes("[ERROR] STRICT ISSUE RESOLUTION MODE") || p.text.includes("explicitly failed")) {
7146
+ foundResult = true;
7147
+ hasErrorResult = true;
7148
+ }
7149
+ }
7150
+ }
7151
+ }
7152
+ }
7153
+ }
7154
+ if (foundResult && hasErrorResult) {
7155
+ log("[verifyTaskCompletionState] Rejected completion because the last complete_task call failed:", sessionID);
7156
+ return false;
7157
+ }
7158
+ }
7159
+ return true;
7160
+ } catch (error) {
7161
+ log("[verifyTaskCompletionState] Error verifying task completion state, defaulting to true:", error);
7162
+ return true;
7163
+ }
7164
+ }
7165
+ var init_verify_task_completion = __esm(() => {
7166
+ init_logger();
7167
+ });
7168
+
6880
7169
  // src/shared/index.ts
6881
7170
  var init_shared = __esm(() => {
6882
7171
  init_model_resolver();
6883
7172
  init_model_resolution_pipeline();
7173
+ init_model_resolution_tracker();
6884
7174
  init_session_category_registry();
7175
+ init_verify_task_completion();
6885
7176
  init_frontmatter();
6886
7177
  init_command_executor();
6887
7178
  init_file_reference_resolver();
@@ -7056,13 +7347,13 @@ var init_opencode_config_format = __esm(() => {
7056
7347
  });
7057
7348
 
7058
7349
  // src/cli/config-manager/parse-opencode-config-file.ts
7059
- import { readFileSync as readFileSync4, statSync } from "fs";
7350
+ import { readFileSync as readFileSync4, statSync as statSync3 } from "fs";
7060
7351
  function isEmptyOrWhitespace(content) {
7061
7352
  return content.trim().length === 0;
7062
7353
  }
7063
7354
  function parseOpenCodeConfigFileWithError(path3) {
7064
7355
  try {
7065
- const stat = statSync(path3);
7356
+ const stat = statSync3(path3);
7066
7357
  if (stat.size === 0) {
7067
7358
  return { config: null, error: `Config file is empty: ${path3}. Delete it or add valid JSON content.` };
7068
7359
  }
@@ -7183,9 +7474,10 @@ var init_model_fallback_requirements = __esm(() => {
7183
7474
  },
7184
7475
  hephaestus: {
7185
7476
  fallbackChain: [
7186
- { providers: ["openai", "opencode"], model: "gpt-5.3-codex", variant: "medium" }
7477
+ { providers: ["openai", "opencode"], model: "gpt-5.3-codex", variant: "medium" },
7478
+ { providers: ["opencode"], model: "claude-opus-4-6", variant: "max" }
7187
7479
  ],
7188
- requiresProvider: ["openai", "opencode"]
7480
+ requiresAnyModel: true
7189
7481
  },
7190
7482
  oracle: {
7191
7483
  fallbackChain: [
@@ -7270,8 +7562,7 @@ var init_model_fallback_requirements = __esm(() => {
7270
7562
  { providers: ["openai", "opencode"], model: "gpt-5.3-codex", variant: "medium" },
7271
7563
  { providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
7272
7564
  { providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro", variant: "high" }
7273
- ],
7274
- requiresModel: "gpt-5.3-codex"
7565
+ ]
7275
7566
  },
7276
7567
  artistry: {
7277
7568
  fallbackChain: [
@@ -7385,7 +7676,7 @@ function generateModelConfig(config) {
7385
7676
  if (!hasAnyProvider) {
7386
7677
  return {
7387
7678
  $schema: SCHEMA_URL,
7388
- agents: Object.fromEntries(Object.entries(CLI_AGENT_MODEL_REQUIREMENTS).filter(([role, req]) => !(role === "sisyphus" && req.requiresAnyModel)).map(([role]) => [role, { model: ULTIMATE_FALLBACK }])),
7679
+ agents: Object.fromEntries(Object.entries(CLI_AGENT_MODEL_REQUIREMENTS).filter(([_, req]) => !req.requiresAnyModel).map(([role]) => [role, { model: ULTIMATE_FALLBACK }])),
7389
7680
  categories: Object.fromEntries(Object.keys(CLI_CATEGORY_MODEL_REQUIREMENTS).map((cat) => [cat, { model: ULTIMATE_FALLBACK }]))
7390
7681
  };
7391
7682
  }
@@ -7430,7 +7721,7 @@ function generateModelConfig(config) {
7430
7721
  if (resolved) {
7431
7722
  const variant = resolved.variant ?? req.variant;
7432
7723
  agents[role] = variant ? { model: resolved.model, variant } : { model: resolved.model };
7433
- } else {
7724
+ } else if (!req.requiresAnyModel) {
7434
7725
  agents[role] = { model: ULTIMATE_FALLBACK };
7435
7726
  }
7436
7727
  }
@@ -7488,7 +7779,7 @@ function deepMergeRecord(target, source) {
7488
7779
  }
7489
7780
 
7490
7781
  // src/cli/config-manager/write-omo-config.ts
7491
- import { existsSync as existsSync7, readFileSync as readFileSync6, statSync as statSync2, writeFileSync as writeFileSync4 } from "fs";
7782
+ import { existsSync as existsSync7, readFileSync as readFileSync6, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
7492
7783
  function isEmptyOrWhitespace2(content) {
7493
7784
  return content.trim().length === 0;
7494
7785
  }
@@ -7507,7 +7798,7 @@ function writeOmoConfig(installConfig) {
7507
7798
  const newConfig = generateOmoConfig(installConfig);
7508
7799
  if (existsSync7(omoConfigPath)) {
7509
7800
  try {
7510
- const stat = statSync2(omoConfigPath);
7801
+ const stat = statSync4(omoConfigPath);
7511
7802
  const content = readFileSync6(omoConfigPath, "utf-8");
7512
7803
  if (stat.size === 0 || isEmptyOrWhitespace2(content)) {
7513
7804
  writeFileSync4(omoConfigPath, JSON.stringify(newConfig, null, 2) + `
@@ -8633,7 +8924,7 @@ var {
8633
8924
  // package.json
8634
8925
  var package_default = {
8635
8926
  name: "@heidi-dang/oh-my-opencode",
8636
- version: "3.12.4",
8927
+ version: "3.13.0",
8637
8928
  description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
8638
8929
  main: "dist/index.js",
8639
8930
  types: "dist/index.d.ts",
@@ -8699,30 +8990,31 @@ var package_default = {
8699
8990
  picocolors: "^1.1.1",
8700
8991
  picomatch: "^4.0.2",
8701
8992
  "vscode-jsonrpc": "^8.2.0",
8702
- zod: "^4.1.8"
8993
+ zod: "^4.1.8",
8994
+ playwright: "^1.58.2"
8703
8995
  },
8704
8996
  devDependencies: {
8705
8997
  "@types/js-yaml": "^4.0.9",
8706
8998
  "@types/picomatch": "^3.0.2",
8707
8999
  "bun-types": "1.3.6",
8708
- typescript: "^5.7.3",
8709
- playwright: "^1.58.2"
9000
+ typescript: "^5.7.3"
8710
9001
  },
8711
9002
  optionalDependencies: {
8712
- "@heidi-dang/oh-my-opencode-darwin-arm64": "3.12.4",
8713
- "@heidi-dang/oh-my-opencode-darwin-x64": "3.12.4",
8714
- "@heidi-dang/oh-my-opencode-darwin-x64-baseline": "3.12.4",
8715
- "@heidi-dang/oh-my-opencode-linux-arm64": "3.12.4",
8716
- "@heidi-dang/oh-my-opencode-linux-arm64-musl": "3.12.4",
8717
- "@heidi-dang/oh-my-opencode-linux-x64": "3.12.4",
8718
- "@heidi-dang/oh-my-opencode-linux-x64-baseline": "3.12.4",
8719
- "@heidi-dang/oh-my-opencode-linux-x64-musl": "3.12.4",
8720
- "@heidi-dang/oh-my-opencode-linux-x64-musl-baseline": "3.12.4",
8721
- "@heidi-dang/oh-my-opencode-windows-x64": "3.12.4",
8722
- "@heidi-dang/oh-my-opencode-windows-x64-baseline": "3.12.4"
9003
+ "@heidi-dang/oh-my-opencode-darwin-arm64": "3.12.5",
9004
+ "@heidi-dang/oh-my-opencode-darwin-x64": "3.12.5",
9005
+ "@heidi-dang/oh-my-opencode-darwin-x64-baseline": "3.12.5",
9006
+ "@heidi-dang/oh-my-opencode-linux-arm64": "3.12.5",
9007
+ "@heidi-dang/oh-my-opencode-linux-arm64-musl": "3.12.5",
9008
+ "@heidi-dang/oh-my-opencode-linux-x64": "3.12.5",
9009
+ "@heidi-dang/oh-my-opencode-linux-x64-baseline": "3.12.5",
9010
+ "@heidi-dang/oh-my-opencode-linux-x64-musl": "3.12.5",
9011
+ "@heidi-dang/oh-my-opencode-linux-x64-musl-baseline": "3.12.5",
9012
+ "@heidi-dang/oh-my-opencode-windows-x64": "3.12.5",
9013
+ "@heidi-dang/oh-my-opencode-windows-x64-baseline": "3.12.5"
8723
9014
  },
8724
9015
  overrides: {
8725
- "@opencode-ai/sdk": "^1.2.17"
9016
+ "@opencode-ai/sdk": "^1.2.17",
9017
+ zod: "^4.1.8"
8726
9018
  },
8727
9019
  trustedDependencies: [
8728
9020
  "@ast-grep/cli",
@@ -9754,39 +10046,274 @@ function loadDefaultConfig() {
9754
10046
  `;
9755
10047
  }
9756
10048
  var EMBEDDED_DEFAULT_CONFIG = {
9757
- $schema: "https://raw.githubusercontent.com/heidi-dang/oh-my-opencode/refs/heads/dev/assets/oh-my-opencode.schema.json",
10049
+ $schema: "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/dev/assets/oh-my-opencode.schema.json",
10050
+ new_task_system_enabled: true,
10051
+ default_run_agent: "sisyphus",
10052
+ hashline_edit: true,
10053
+ model_fallback: true,
10054
+ fallback_model: "github-copilot/gpt-5-mini",
10055
+ disabled_skills: [],
10056
+ disabled_hooks: [],
10057
+ disabled_tools: [],
10058
+ disabled_commands: [],
10059
+ experimental: {
10060
+ aggressive_truncation: true,
10061
+ auto_resume: true,
10062
+ preemptive_compaction: true,
10063
+ truncate_all_tool_outputs: true,
10064
+ task_system: true,
10065
+ safe_hook_creation: true,
10066
+ disable_omo_env: false,
10067
+ hashline_edit: true,
10068
+ model_fallback_title: true,
10069
+ dynamic_context_pruning: {
10070
+ enabled: true,
10071
+ notification: "minimal",
10072
+ turn_protection: {
10073
+ enabled: true,
10074
+ turns: 3
10075
+ },
10076
+ protected_tools: [
10077
+ "task",
10078
+ "todowrite",
10079
+ "todoread",
10080
+ "lsp_rename",
10081
+ "session_read",
10082
+ "session_write",
10083
+ "session_search"
10084
+ ],
10085
+ strategies: {
10086
+ deduplication: {
10087
+ enabled: true
10088
+ },
10089
+ supersede_writes: {
10090
+ enabled: true,
10091
+ aggressive: false
10092
+ },
10093
+ purge_errors: {
10094
+ enabled: true,
10095
+ turns: 5
10096
+ }
10097
+ }
10098
+ }
10099
+ },
10100
+ runtime_fallback: {
10101
+ enabled: true,
10102
+ max_fallback_attempts: 2,
10103
+ cooldown_seconds: 8,
10104
+ timeout_seconds: 45,
10105
+ notify_on_fallback: true
10106
+ },
10107
+ background_task: {
10108
+ defaultConcurrency: 2,
10109
+ providerConcurrency: {
10110
+ xai: 2,
10111
+ "github-copilot": 2,
10112
+ "opencode-go": 1
10113
+ },
10114
+ modelConcurrency: {
10115
+ "xai/grok-4-1-fast": 2,
10116
+ "xai/grok-4-1-fast-non-reasoning": 2,
10117
+ "github-copilot/gpt-5-mini": 2,
10118
+ "opencode-go/minimax-m2.5": 1
10119
+ },
10120
+ staleTimeoutMs: 120000,
10121
+ messageStalenessTimeoutMs: 120000,
10122
+ syncPollTimeoutMs: 120000
10123
+ },
10124
+ babysitting: {
10125
+ timeout_ms: 120000
10126
+ },
10127
+ browser_automation_engine: {
10128
+ provider: "playwright"
10129
+ },
10130
+ sisyphus_agent: {
10131
+ disabled: false,
10132
+ default_builder_enabled: true,
10133
+ planner_enabled: true,
10134
+ replace_plan: false
10135
+ },
9758
10136
  agents: {
9759
10137
  sisyphus: {
9760
10138
  model: "xai/grok-4-1-fast",
9761
- ultrawork: { model: "xai/grok-4-1-fast", variant: "max" }
10139
+ fallback_model: "github-copilot/gpt-5-mini",
10140
+ reasoningEffort: "medium",
10141
+ textVerbosity: "medium",
10142
+ temperature: 0.2,
10143
+ top_p: 0.95,
10144
+ maxTokens: 120000,
10145
+ thinking: {
10146
+ type: "enabled",
10147
+ budgetTokens: 24000
10148
+ },
10149
+ mode: "primary",
10150
+ description: "Main orchestrator. Strong planning, routing, and repo-wide coordination."
10151
+ },
10152
+ plan: {
10153
+ model: "xai/grok-4-1-fast",
10154
+ fallback_model: "github-copilot/gpt-5-mini",
10155
+ reasoningEffort: "high",
10156
+ textVerbosity: "medium",
10157
+ temperature: 0.1,
10158
+ top_p: 0.9,
10159
+ maxTokens: 64000,
10160
+ thinking: {
10161
+ type: "enabled",
10162
+ budgetTokens: 32000
10163
+ },
10164
+ mode: "all",
10165
+ description: "Deterministic planner. Keep conservative and low-noise."
10166
+ },
10167
+ build: {
10168
+ model: "xai/grok-4-1-fast-non-reasoning",
10169
+ fallback_models: [
10170
+ "github-copilot/gpt-5-mini",
10171
+ "opencode-go/minimax-m2.5"
10172
+ ],
10173
+ reasoningEffort: "low",
10174
+ textVerbosity: "low",
10175
+ temperature: 0.1,
10176
+ top_p: 0.9,
10177
+ maxTokens: 64000,
10178
+ thinking: {
10179
+ type: "disabled"
10180
+ },
10181
+ mode: "all",
10182
+ description: "Fast code execution/build lane."
10183
+ },
10184
+ hephaestus: {
10185
+ model: "xai/grok-4-1-fast-non-reasoning",
10186
+ fallback_models: [
10187
+ "github-copilot/gpt-5-mini",
10188
+ "opencode-go/minimax-m2.5"
10189
+ ],
10190
+ reasoningEffort: "low",
10191
+ textVerbosity: "low",
10192
+ temperature: 0.1,
10193
+ top_p: 0.9,
10194
+ maxTokens: 64000,
10195
+ thinking: {
10196
+ type: "disabled"
10197
+ },
10198
+ mode: "subagent",
10199
+ allow_non_gpt_model: true,
10200
+ description: "Deep worker optimized for fast implementation, not long reasoning."
9762
10201
  },
9763
- librarian: { model: "xai/grok-4-1-fast-non-reasoning" },
9764
- explore: { model: "xai/grok-4-1-fast-non-reasoning" },
9765
- oracle: { model: "xai/grok-4-1-fast", variant: "high" },
9766
10202
  prometheus: {
9767
- prompt_append: "Prefer fast agents; only trigger ultrawork when necessary. Parallelize but avoid duplicate work."
10203
+ model: "xai/grok-4-1-fast",
10204
+ fallback_model: "github-copilot/gpt-5-mini",
10205
+ reasoningEffort: "high",
10206
+ textVerbosity: "medium",
10207
+ temperature: 0.2,
10208
+ top_p: 0.95,
10209
+ maxTokens: 96000,
10210
+ thinking: {
10211
+ type: "enabled",
10212
+ budgetTokens: 28000
10213
+ },
10214
+ mode: "subagent",
10215
+ description: "Research/planning/strategy agent."
10216
+ },
10217
+ atlas: {
10218
+ model: "xai/grok-4-1-fast",
10219
+ fallback_model: "github-copilot/gpt-5-mini",
10220
+ reasoningEffort: "medium",
10221
+ textVerbosity: "medium",
10222
+ temperature: 0.2,
10223
+ top_p: 0.95,
10224
+ maxTokens: 96000,
10225
+ thinking: {
10226
+ type: "enabled",
10227
+ budgetTokens: 20000
10228
+ },
10229
+ mode: "subagent",
10230
+ description: "Repository analysis and architecture navigation."
10231
+ },
10232
+ explore: {
10233
+ model: "xai/grok-4-1-fast",
10234
+ fallback_model: "github-copilot/gpt-5-mini",
10235
+ reasoningEffort: "medium",
10236
+ textVerbosity: "medium",
10237
+ temperature: 0.3,
10238
+ top_p: 0.95,
10239
+ maxTokens: 64000,
10240
+ thinking: {
10241
+ type: "enabled",
10242
+ budgetTokens: 16000
10243
+ },
10244
+ mode: "subagent",
10245
+ description: "Codebase exploration and discovery."
10246
+ },
10247
+ librarian: {
10248
+ model: "github-copilot/gpt-5-mini",
10249
+ fallback_model: "xai/grok-4-1-fast",
10250
+ reasoningEffort: "low",
10251
+ textVerbosity: "low",
10252
+ temperature: 0.1,
10253
+ top_p: 0.9,
10254
+ maxTokens: 32000,
10255
+ thinking: {
10256
+ type: "disabled"
10257
+ },
10258
+ mode: "subagent",
10259
+ description: "Cheap summarizer and retrieval helper."
10260
+ },
10261
+ "multimodal-looker": {
10262
+ model: "xai/grok-4-1-fast",
10263
+ fallback_model: "github-copilot/gpt-5-mini",
10264
+ reasoningEffort: "medium",
10265
+ textVerbosity: "medium",
10266
+ temperature: 0.2,
10267
+ top_p: 0.95,
10268
+ maxTokens: 48000,
10269
+ thinking: {
10270
+ type: "enabled",
10271
+ budgetTokens: 12000
10272
+ },
10273
+ mode: "subagent",
10274
+ description: "Image/screenshot-oriented inspection."
9768
10275
  }
9769
10276
  },
9770
10277
  categories: {
9771
- quick: { model: "opencode-go/minimax-m2.5" },
9772
- "unspecified-low": { model: "xai/grok-4-1-fast-non-reasoning" },
9773
- "unspecified-high": { model: "xai/grok-4-1-fast", variant: "high" },
9774
- "visual-engineering": { model: "google/gemini-3-pro-preview", variant: "high" },
9775
- writing: { model: "xai/grok-4-1-fast-non-reasoning" }
9776
- },
9777
- background_task: {
9778
- providerConcurrency: {
9779
- xai: 8,
9780
- "opencode-go": 6,
9781
- opencode: 2,
9782
- google: 1,
9783
- openai: 1,
9784
- anthropic: 1
10278
+ "fast-build": {
10279
+ description: "High-speed implementation path",
10280
+ model: "xai/grok-4-1-fast-non-reasoning",
10281
+ fallback_model: "github-copilot/gpt-5-mini",
10282
+ temperature: 0.1,
10283
+ top_p: 0.9,
10284
+ thinking: {
10285
+ type: "disabled"
10286
+ },
10287
+ reasoningEffort: "low",
10288
+ textVerbosity: "low",
10289
+ max_prompt_tokens: 120000
9785
10290
  },
9786
- modelConcurrency: {
9787
- "xai/grok-4-1-fast-non-reasoning": 8,
9788
- "xai/grok-4-1-fast": 2,
9789
- "opencode-go/minimax-m2.5": 6
10291
+ "deep-plan": {
10292
+ description: "High-quality planning path",
10293
+ model: "xai/grok-4-1-fast",
10294
+ fallback_model: "github-copilot/gpt-5-mini",
10295
+ temperature: 0.1,
10296
+ top_p: 0.9,
10297
+ thinking: {
10298
+ type: "enabled",
10299
+ budgetTokens: 24000
10300
+ },
10301
+ reasoningEffort: "high",
10302
+ textVerbosity: "medium",
10303
+ max_prompt_tokens: 160000
10304
+ },
10305
+ "cheap-helper": {
10306
+ description: "Low-cost utility path",
10307
+ model: "github-copilot/gpt-5-mini",
10308
+ fallback_model: "xai/grok-4-1-fast-non-reasoning",
10309
+ temperature: 0.1,
10310
+ top_p: 0.9,
10311
+ thinking: {
10312
+ type: "disabled"
10313
+ },
10314
+ reasoningEffort: "low",
10315
+ textVerbosity: "low",
10316
+ max_prompt_tokens: 48000
9790
10317
  }
9791
10318
  }
9792
10319
  };
@@ -9978,7 +10505,11 @@ function logEventVerbose(ctx, payload) {
9978
10505
  case "session.error": {
9979
10506
  const errorProps = props;
9980
10507
  const errorMsg = serializeError(errorProps?.error);
9981
- console.error(import_picocolors6.default.red(`${sessionTag} SESSION.ERROR: ${errorMsg}`));
10508
+ if (errorMsg.includes("[Semantic Loop Guard]")) {
10509
+ console.error(import_picocolors6.default.green(`${sessionTag} ${errorMsg}`));
10510
+ } else {
10511
+ console.error(import_picocolors6.default.red(`${sessionTag} SESSION.ERROR: ${errorMsg}`));
10512
+ }
9982
10513
  break;
9983
10514
  }
9984
10515
  default:
@@ -10257,10 +10788,17 @@ function handleSessionError(ctx, payload, state) {
10257
10788
  return;
10258
10789
  const props = payload.properties;
10259
10790
  if (getSessionId(props) === ctx.sessionID) {
10791
+ const errorName = props?.error?.name;
10260
10792
  state.mainSessionError = true;
10261
- state.lastError = serializeError(props?.error);
10262
- console.error(import_picocolors8.default.red(`
10793
+ if (errorName === "AbortError" || errorName === "MessageAbortedError") {
10794
+ state.lastError = "Task aborted.";
10795
+ console.error(import_picocolors8.default.yellow(`
10263
10796
  [session.error] ${state.lastError}`));
10797
+ } else {
10798
+ state.lastError = serializeError(props?.error);
10799
+ console.error(import_picocolors8.default.red(`
10800
+ [session.error] ${state.lastError}`));
10801
+ }
10264
10802
  }
10265
10803
  }
10266
10804
  function handleMessagePartUpdated(ctx, payload, state) {
@@ -24069,7 +24607,8 @@ var BuiltinAgentNameSchema = exports_external.enum([
24069
24607
  "multimodal-looker",
24070
24608
  "metis",
24071
24609
  "momus",
24072
- "atlas"
24610
+ "atlas",
24611
+ "chat"
24073
24612
  ]);
24074
24613
  var BuiltinSkillNameSchema = exports_external.enum([
24075
24614
  "playwright",
@@ -24092,10 +24631,19 @@ var OverridableAgentNameSchema = exports_external.enum([
24092
24631
  "librarian",
24093
24632
  "explore",
24094
24633
  "multimodal-looker",
24095
- "atlas"
24634
+ "atlas",
24635
+ "chat"
24096
24636
  ]);
24097
24637
  // src/config/schema/fallback-models.ts
24098
- var FallbackModelsSchema = exports_external.union([exports_external.string(), exports_external.array(exports_external.string())]);
24638
+ var FallbackEntrySchema = exports_external.object({
24639
+ providers: exports_external.array(exports_external.string()).optional(),
24640
+ model: exports_external.string(),
24641
+ variant: exports_external.string().optional()
24642
+ });
24643
+ var FallbackModelsSchema = exports_external.union([
24644
+ exports_external.string(),
24645
+ exports_external.array(exports_external.union([exports_external.string(), FallbackEntrySchema]))
24646
+ ]);
24099
24647
 
24100
24648
  // src/config/schema/internal/permission.ts
24101
24649
  var PermissionValueSchema = exports_external.enum(["ask", "allow", "deny"]);
@@ -24115,6 +24663,7 @@ var AgentPermissionSchema = exports_external.object({
24115
24663
  // src/config/schema/agent-overrides.ts
24116
24664
  var AgentOverrideConfigSchema = exports_external.object({
24117
24665
  model: exports_external.string().optional(),
24666
+ fallback_model: exports_external.string().optional(),
24118
24667
  fallback_models: FallbackModelsSchema.optional(),
24119
24668
  variant: exports_external.string().optional(),
24120
24669
  category: exports_external.string().optional(),
@@ -24137,6 +24686,9 @@ var AgentOverrideConfigSchema = exports_external.object({
24137
24686
  reasoningEffort: exports_external.enum(["low", "medium", "high", "xhigh"]).optional(),
24138
24687
  textVerbosity: exports_external.enum(["low", "medium", "high"]).optional(),
24139
24688
  providerOptions: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
24689
+ requires_model: exports_external.string().optional(),
24690
+ requires_any_model: exports_external.boolean().optional(),
24691
+ requires_provider: exports_external.array(exports_external.string()).optional(),
24140
24692
  ultrawork: exports_external.object({
24141
24693
  model: exports_external.string().optional(),
24142
24694
  variant: exports_external.string().optional()
@@ -24191,6 +24743,7 @@ var BrowserAutomationConfigSchema = exports_external.object({
24191
24743
  var CategoryConfigSchema = exports_external.object({
24192
24744
  description: exports_external.string().optional(),
24193
24745
  model: exports_external.string().optional(),
24746
+ fallback_model: exports_external.string().optional(),
24194
24747
  fallback_models: FallbackModelsSchema.optional(),
24195
24748
  variant: exports_external.string().optional(),
24196
24749
  temperature: exports_external.number().min(0).max(2).optional(),
@@ -24206,7 +24759,11 @@ var CategoryConfigSchema = exports_external.object({
24206
24759
  prompt_append: exports_external.string().optional(),
24207
24760
  max_prompt_tokens: exports_external.number().int().positive().optional(),
24208
24761
  is_unstable_agent: exports_external.boolean().optional(),
24209
- disable: exports_external.boolean().optional()
24762
+ disable: exports_external.boolean().optional(),
24763
+ requires_model: exports_external.string().optional(),
24764
+ requires_any_model: exports_external.boolean().optional(),
24765
+ requires_provider: exports_external.array(exports_external.string()).optional(),
24766
+ background_task: exports_external.never().optional().describe("Mis-nested background_task config - move to root level")
24210
24767
  });
24211
24768
  var BuiltinCategoryNameSchema = exports_external.enum([
24212
24769
  "visual-engineering",
@@ -24341,7 +24898,14 @@ var HookNameSchema = exports_external.enum([
24341
24898
  "write-existing-file-guard",
24342
24899
  "anthropic-effort",
24343
24900
  "hashline-read-enhancer",
24344
- "read-image-resizer"
24901
+ "read-image-resizer",
24902
+ "execution-journal",
24903
+ "tool-contract",
24904
+ "runtime-enforcement",
24905
+ "plan-enforcement",
24906
+ "semantic-loop-guard",
24907
+ "edit-safeguard",
24908
+ "xai-usage-patch"
24345
24909
  ]);
24346
24910
  // src/config/schema/notification.ts
24347
24911
  var NotificationConfigSchema = exports_external.object({
@@ -24460,6 +25024,7 @@ var OhMyOpenCodeConfigSchema = exports_external.object({
24460
25024
  disabled_tools: exports_external.array(exports_external.string()).optional(),
24461
25025
  hashline_edit: exports_external.boolean().optional(),
24462
25026
  model_fallback: exports_external.boolean().optional(),
25027
+ fallback_model: exports_external.string().optional(),
24463
25028
  agents: AgentOverridesSchema.optional(),
24464
25029
  categories: CategoriesConfigSchema.optional(),
24465
25030
  claude_code: ClaudeCodeConfigSchema.optional(),
@@ -26502,16 +27067,22 @@ function readState(directory, customPath) {
26502
27067
  const str3 = String(val ?? "");
26503
27068
  return str3.replace(/^["']|["']$/g, "");
26504
27069
  };
27070
+ const ultrawork = data.ultrawork === true || data.ultrawork === "true" ? true : undefined;
27071
+ const maxIterations = data.max_iterations === undefined || data.max_iterations === "" ? ultrawork ? undefined : DEFAULT_MAX_ITERATIONS : Number(data.max_iterations) || DEFAULT_MAX_ITERATIONS;
26505
27072
  return {
26506
27073
  active: isActive,
26507
27074
  iteration: iterationNum,
26508
- max_iterations: Number(data.max_iterations) || DEFAULT_MAX_ITERATIONS,
27075
+ max_iterations: maxIterations,
26509
27076
  message_count_at_start: typeof data.message_count_at_start === "number" ? data.message_count_at_start : typeof data.message_count_at_start === "string" && data.message_count_at_start.trim() !== "" ? Number(data.message_count_at_start) : undefined,
26510
27077
  completion_promise: stripQuotes(data.completion_promise) || DEFAULT_COMPLETION_PROMISE,
27078
+ initial_completion_promise: data.initial_completion_promise ? stripQuotes(data.initial_completion_promise) : undefined,
27079
+ verification_attempt_id: data.verification_attempt_id ? stripQuotes(data.verification_attempt_id) : undefined,
27080
+ verification_session_id: data.verification_session_id ? stripQuotes(data.verification_session_id) : undefined,
26511
27081
  started_at: stripQuotes(data.started_at) || new Date().toISOString(),
26512
27082
  prompt: body.trim(),
26513
27083
  session_id: data.session_id ? stripQuotes(data.session_id) : undefined,
26514
- ultrawork: data.ultrawork === true || data.ultrawork === "true" ? true : undefined,
27084
+ ultrawork,
27085
+ verification_pending: data.verification_pending === true || data.verification_pending === "true" ? true : undefined,
26515
27086
  strategy: data.strategy === "reset" || data.strategy === "continue" ? data.strategy : undefined
26516
27087
  };
26517
27088
  } catch {
@@ -26568,6 +27139,11 @@ async function checkCompletionConditions(ctx) {
26568
27139
  if (!areContinuationHooksIdle(ctx, continuationState)) {
26569
27140
  return false;
26570
27141
  }
27142
+ const isLegitimatelyComplete = await verifyTaskCompletionState(ctx.client, ctx.sessionID);
27143
+ if (!isLegitimatelyComplete) {
27144
+ logWaiting(ctx, "failed verification constraints or complete_task explicitly rejected");
27145
+ return false;
27146
+ }
26571
27147
  return true;
26572
27148
  } catch (err) {
26573
27149
  console.error(import_picocolors14.default.red(`[completion] API error: ${err}`));
@@ -27164,7 +27740,10 @@ var CHECK_IDS = {
27164
27740
  TOOLS: "tools",
27165
27741
  MODELS: "models",
27166
27742
  FORK: "fork",
27167
- DEFAULT_CONFIG: "default-config"
27743
+ DEFAULT_CONFIG: "default-config",
27744
+ PLAN_COMPILER: "plan-compiler",
27745
+ PROGRESS: "progress",
27746
+ EDIT_ATOMICITY: "edit-atomicity"
27168
27747
  };
27169
27748
  var CHECK_NAMES = {
27170
27749
  [CHECK_IDS.SYSTEM]: "System",
@@ -27172,7 +27751,10 @@ var CHECK_NAMES = {
27172
27751
  [CHECK_IDS.TOOLS]: "Tools",
27173
27752
  [CHECK_IDS.MODELS]: "Models",
27174
27753
  [CHECK_IDS.FORK]: "Fork Integrity",
27175
- [CHECK_IDS.DEFAULT_CONFIG]: "Default Config"
27754
+ [CHECK_IDS.DEFAULT_CONFIG]: "Default Config",
27755
+ [CHECK_IDS.PLAN_COMPILER]: "Plan Compiler",
27756
+ [CHECK_IDS.PROGRESS]: "Subagent Progress",
27757
+ [CHECK_IDS.EDIT_ATOMICITY]: "Edit Atomicity & Validation"
27176
27758
  };
27177
27759
  var EXIT_CODES = {
27178
27760
  SUCCESS: 0,
@@ -27654,19 +28236,19 @@ function buildModelResolutionDetails(options) {
27654
28236
  details.push("");
27655
28237
  details.push("Agents:");
27656
28238
  for (const agent of options.info.agents) {
27657
- const marker = agent.userOverride ? "\u25CF" : "\u25CB";
28239
+ const marker = agent.userOverride ? "\u25CF" : agent.requirement.fallbackModel ? "\u25D3" : "\u25CB";
27658
28240
  const display = formatModelWithVariant(agent.effectiveModel, getEffectiveVariant(agent.name, agent.requirement, options.config));
27659
28241
  details.push(` ${marker} ${agent.name}: ${display}`);
27660
28242
  }
27661
28243
  details.push("");
27662
28244
  details.push("Categories:");
27663
28245
  for (const category of options.info.categories) {
27664
- const marker = category.userOverride ? "\u25CF" : "\u25CB";
28246
+ const marker = category.userOverride ? "\u25CF" : category.requirement.fallbackModel ? "\u25D3" : "\u25CB";
27665
28247
  const display = formatModelWithVariant(category.effectiveModel, getCategoryEffectiveVariant(category.name, category.requirement, options.config));
27666
28248
  details.push(` ${marker} ${category.name}: ${display}`);
27667
28249
  }
27668
28250
  details.push("");
27669
- details.push("\u25CF = user override, \u25CB = provider fallback");
28251
+ details.push("\u25CF = user override, \u25D3 = config fallback (fallback_model), \u25CB = provider fallback");
27670
28252
  return details;
27671
28253
  }
27672
28254
 
@@ -27678,6 +28260,9 @@ function getEffectiveModel(requirement, userOverride) {
27678
28260
  if (userOverride) {
27679
28261
  return userOverride;
27680
28262
  }
28263
+ if (requirement.fallbackModel) {
28264
+ return requirement.fallbackModel;
28265
+ }
27681
28266
  const firstEntry = requirement.fallbackChain[0];
27682
28267
  if (!firstEntry) {
27683
28268
  return "unknown";
@@ -27688,6 +28273,9 @@ function buildEffectiveResolution(requirement, userOverride) {
27688
28273
  if (userOverride) {
27689
28274
  return `User override: ${userOverride}`;
27690
28275
  }
28276
+ if (requirement.fallbackModel) {
28277
+ return `Config fallback: ${requirement.fallbackModel}`;
28278
+ }
27691
28279
  const firstEntry = requirement.fallbackChain[0];
27692
28280
  if (!firstEntry) {
27693
28281
  return "No fallback chain defined";
@@ -28415,6 +29003,142 @@ async function checkFork() {
28415
29003
  };
28416
29004
  }
28417
29005
 
29006
+ // src/runtime/plan-compiler.ts
29007
+ class PlanCompiler {
29008
+ static instance;
29009
+ sessionStates = new Map;
29010
+ constructor() {}
29011
+ static getInstance() {
29012
+ if (!PlanCompiler.instance) {
29013
+ PlanCompiler.instance = new PlanCompiler;
29014
+ }
29015
+ return PlanCompiler.instance;
29016
+ }
29017
+ submit(sessionID, steps) {
29018
+ const taskID = Math.random().toString(36).substring(7);
29019
+ const graph = this.injectVerificationDependencies(steps.map((n) => ({ ...n, status: "pending" })));
29020
+ this.sessionStates.set(sessionID, {
29021
+ graph,
29022
+ currentStepIndex: 0,
29023
+ taskID
29024
+ });
29025
+ return taskID;
29026
+ }
29027
+ getActiveStep(sessionID) {
29028
+ const state = this.sessionStates.get(sessionID);
29029
+ if (!state)
29030
+ return null;
29031
+ if (state.currentStepIndex >= 0 && state.currentStepIndex < state.graph.length) {
29032
+ return state.graph[state.currentStepIndex];
29033
+ }
29034
+ return null;
29035
+ }
29036
+ markStepComplete(sessionID, id) {
29037
+ const state = this.sessionStates.get(sessionID);
29038
+ if (!state)
29039
+ return;
29040
+ const node = state.graph.find((n) => n.id === id);
29041
+ if (node) {
29042
+ node.status = "completed";
29043
+ state.currentStepIndex++;
29044
+ if (state.currentStepIndex >= state.graph.length) {
29045
+ this.clear(sessionID);
29046
+ }
29047
+ }
29048
+ }
29049
+ injectForcedReplan(sessionID, reason) {
29050
+ const state = this.sessionStates.get(sessionID);
29051
+ if (!state)
29052
+ return;
29053
+ const active = this.getActiveStep(sessionID);
29054
+ if (active) {
29055
+ active.status = "failed";
29056
+ }
29057
+ this.submit(sessionID, [
29058
+ {
29059
+ id: "forced_replan_" + Date.now(),
29060
+ action: "submit_plan",
29061
+ dependencies: []
29062
+ }
29063
+ ]);
29064
+ }
29065
+ clear(sessionID) {
29066
+ this.sessionStates.delete(sessionID);
29067
+ }
29068
+ resetAll() {
29069
+ this.sessionStates.clear();
29070
+ }
29071
+ injectVerificationDependencies(nodes) {
29072
+ const compiled = [];
29073
+ for (const node of nodes) {
29074
+ compiled.push(node);
29075
+ if (node.action.includes("commit") || node.action.includes("push") || node.action.includes("fix")) {
29076
+ compiled.push({
29077
+ id: `${node.id}_verify`,
29078
+ action: "verify_action",
29079
+ dependencies: [node.id],
29080
+ status: "pending"
29081
+ });
29082
+ }
29083
+ }
29084
+ return compiled;
29085
+ }
29086
+ }
29087
+ var compiler = PlanCompiler.getInstance();
29088
+
29089
+ // src/cli/doctor/checks/plan-compiler.ts
29090
+ async function checkPlanCompiler() {
29091
+ const findings = [];
29092
+ let passed = true;
29093
+ try {
29094
+ const sessionA = "doctor-session-a-" + Date.now();
29095
+ const sessionB = "doctor-session-b-" + Date.now();
29096
+ compiler.submit(sessionA, [
29097
+ { id: "step1", action: "exclusive-action-a", dependencies: [] }
29098
+ ]);
29099
+ const activeA = compiler.getActiveStep(sessionA);
29100
+ const activeB = compiler.getActiveStep(sessionB);
29101
+ if (activeA?.action !== "exclusive-action-a") {
29102
+ passed = false;
29103
+ findings.push(`${SYMBOLS3.cross} Session A plan not correctly stored.`);
29104
+ }
29105
+ if (activeB !== null) {
29106
+ passed = false;
29107
+ findings.push(`${SYMBOLS3.cross} Session B is polluted by Session A's plan. Cross-session leakage detected!`);
29108
+ } else {
29109
+ findings.push(`${SYMBOLS3.check} Session isolation verified.`);
29110
+ }
29111
+ compiler.clear(sessionA);
29112
+ if (compiler.getActiveStep(sessionA) !== null) {
29113
+ passed = false;
29114
+ findings.push(`${SYMBOLS3.cross} Manual unlock failed to clear session state.`);
29115
+ } else {
29116
+ findings.push(`${SYMBOLS3.check} Manual unlock (clear) verified.`);
29117
+ }
29118
+ const taskID1 = compiler.submit(sessionA, [{ id: "s1", action: "a1", dependencies: [] }]);
29119
+ const taskID2 = compiler.submit(sessionA, [{ id: "s2", action: "a2", dependencies: [] }]);
29120
+ if (taskID1 === taskID2) {
29121
+ findings.push(`${SYMBOLS3.warn} Task IDs are not unique across subsequent submits in the same session.`);
29122
+ } else {
29123
+ findings.push(`${SYMBOLS3.check} Task ID rotation verified.`);
29124
+ }
29125
+ } catch (error48) {
29126
+ passed = false;
29127
+ findings.push(`${SYMBOLS3.cross} Plan Compiler check crashed: ${error48.message}`);
29128
+ }
29129
+ return {
29130
+ name: "Plan Compiler",
29131
+ status: passed ? "pass" : "fail",
29132
+ message: passed ? "Plan Compiler is healthy and correctly isolating sessions." : "Plan Compiler has integrity issues (isolation or recovery failure).",
29133
+ details: findings,
29134
+ issues: passed ? [] : [{
29135
+ title: "Plan Compiler integrity failure",
29136
+ description: "Deterministic plan enforcement or session isolation is broken.",
29137
+ severity: "error"
29138
+ }]
29139
+ };
29140
+ }
29141
+
28418
29142
  // src/cli/doctor/checks/default-config.ts
28419
29143
  import { existsSync as existsSync28, readFileSync as readFileSync28 } from "fs";
28420
29144
  import * as path10 from "path";
@@ -28528,20 +29252,1532 @@ async function checkDefaultConfig() {
28528
29252
  issues
28529
29253
  };
28530
29254
  }
28531
- // src/cli/doctor/checks/index.ts
28532
- function getAllCheckDefinitions() {
28533
- return [
28534
- {
28535
- id: CHECK_IDS.SYSTEM,
28536
- name: CHECK_NAMES[CHECK_IDS.SYSTEM],
28537
- check: checkSystem,
28538
- critical: true
28539
- },
28540
- {
28541
- id: CHECK_IDS.CONFIG,
28542
- name: CHECK_NAMES[CHECK_IDS.CONFIG],
28543
- check: checkConfig
28544
- },
29255
+
29256
+ // src/shared/active-task-storage.ts
29257
+ init_opencode_config_dir();
29258
+ init_logger();
29259
+ import { join as join28 } from "path";
29260
+ import { writeFileSync as writeFileSync11, readFileSync as readFileSync29, existsSync as existsSync29, mkdirSync as mkdirSync7 } from "fs";
29261
+ var STORAGE_FILENAME = "active_background_tasks.json";
29262
+ function getStoragePath() {
29263
+ const configDir = getOpenCodeConfigDir({ binary: "opencode" });
29264
+ if (!existsSync29(configDir)) {
29265
+ mkdirSync7(configDir, { recursive: true });
29266
+ }
29267
+ return join28(configDir, STORAGE_FILENAME);
29268
+ }
29269
+ function saveActiveTasks(tasks) {
29270
+ try {
29271
+ const path11 = getStoragePath();
29272
+ writeFileSync11(path11, JSON.stringify(tasks, null, 2), "utf-8");
29273
+ } catch (err) {
29274
+ log("[active-task-storage] Failed to save active tasks:", err);
29275
+ }
29276
+ }
29277
+ function readActiveTasks() {
29278
+ try {
29279
+ const path11 = getStoragePath();
29280
+ if (!existsSync29(path11))
29281
+ return [];
29282
+ const content = readFileSync29(path11, "utf-8");
29283
+ return JSON.parse(content);
29284
+ } catch (err) {
29285
+ log("[active-task-storage] Failed to read active tasks:", err);
29286
+ return [];
29287
+ }
29288
+ }
29289
+
29290
+ // src/cli/doctor/checks/progress.ts
29291
+ async function checkProgress() {
29292
+ const findings = [];
29293
+ let passed = true;
29294
+ try {
29295
+ const testTask = {
29296
+ id: "doctor-test-task-" + Date.now(),
29297
+ sessionID: "test-session",
29298
+ description: "Doctor progress check",
29299
+ agent: "sisyphus",
29300
+ status: "running",
29301
+ startedAt: new Date().toISOString(),
29302
+ progress: {
29303
+ phase: "Testing",
29304
+ percent: 50,
29305
+ message: "Halfway there"
29306
+ }
29307
+ };
29308
+ const originalTasks = readActiveTasks();
29309
+ saveActiveTasks([...originalTasks, testTask]);
29310
+ const updatedTasks = readActiveTasks();
29311
+ const found = updatedTasks.find((t) => t.id === testTask.id);
29312
+ if (!found) {
29313
+ passed = false;
29314
+ findings.push(`${SYMBOLS3.cross} Failed to persist active task.`);
29315
+ } else if (found.progress?.percent !== 50) {
29316
+ passed = false;
29317
+ findings.push(`${SYMBOLS3.cross} Persisted progress data is incorrect.`);
29318
+ } else {
29319
+ findings.push(`${SYMBOLS3.check} Progress storage persistence verified.`);
29320
+ }
29321
+ saveActiveTasks(originalTasks);
29322
+ } catch (error48) {
29323
+ passed = false;
29324
+ findings.push(`${SYMBOLS3.cross} Progress check crashed: ${error48.message}`);
29325
+ }
29326
+ return {
29327
+ name: "Subagent Progress",
29328
+ status: passed ? "pass" : "fail",
29329
+ message: passed ? "Subagent progress tracking is healthy." : "Subagent progress tracking has issues.",
29330
+ issues: passed ? [] : [{
29331
+ title: "Progress tracking failure",
29332
+ description: "The system failed to persist or retrieve background task progress.",
29333
+ severity: "error"
29334
+ }]
29335
+ };
29336
+ }
29337
+
29338
+ // src/runtime/tools/fs-safe.ts
29339
+ import * as fs11 from "fs";
29340
+ import * as path11 from "path";
29341
+
29342
+ // node_modules/@opencode-ai/plugin/dist/tool.js
29343
+ function tool(input) {
29344
+ return input;
29345
+ }
29346
+ tool.schema = exports_external;
29347
+ // src/utils/safety-tool-result.ts
29348
+ function createSuccessResult(params) {
29349
+ const result = {
29350
+ success: true,
29351
+ verified: params.verified,
29352
+ changedState: params.changedState,
29353
+ message: params.message
29354
+ };
29355
+ if (params.changedState) {
29356
+ if (!params.stateChange) {
29357
+ throw new Error("[Safety Helper] 'stateChange' metadata is required when 'changedState' is true.");
29358
+ }
29359
+ result.stateChange = params.stateChange;
29360
+ }
29361
+ return result;
29362
+ }
29363
+ function createFailureResult(message) {
29364
+ return {
29365
+ success: false,
29366
+ verified: false,
29367
+ changedState: false,
29368
+ message
29369
+ };
29370
+ }
29371
+
29372
+ // src/features/tool-metadata-store/store.ts
29373
+ var pendingStore = new Map;
29374
+ var STALE_TIMEOUT_MS = 15 * 60 * 1000;
29375
+ function makeKey(sessionID, callID) {
29376
+ return `${sessionID}:${callID}`;
29377
+ }
29378
+ function cleanupStaleEntries() {
29379
+ const now = Date.now();
29380
+ for (const [key, entry] of pendingStore) {
29381
+ if (now - entry.storedAt > STALE_TIMEOUT_MS) {
29382
+ pendingStore.delete(key);
29383
+ }
29384
+ }
29385
+ }
29386
+ function storeToolMetadata(sessionID, callID, data) {
29387
+ cleanupStaleEntries();
29388
+ pendingStore.set(makeKey(sessionID, callID), { ...data, storedAt: Date.now() });
29389
+ }
29390
+ function consumeToolMetadata(sessionID, callID) {
29391
+ const key = makeKey(sessionID, callID);
29392
+ const stored = pendingStore.get(key);
29393
+ if (stored) {
29394
+ pendingStore.delete(key);
29395
+ const { storedAt: _3, ...data } = stored;
29396
+ return data;
29397
+ }
29398
+ return;
29399
+ }
29400
+ function clearPendingStore() {
29401
+ pendingStore.clear();
29402
+ }
29403
+ // src/utils/tool-contract-wrapper.ts
29404
+ function withToolContract(toolName, executeFn) {
29405
+ const safetyCriticalTools = ["git_safe", "fs_safe", "verify_action", "submit_plan", "mark_step_complete", "unlock_plan", "query_ledger", "complete_task", "report_issue_verification"];
29406
+ return async (args, context) => {
29407
+ let metadataCalled = false;
29408
+ let lastMetadata = null;
29409
+ const originalMetadata = context.metadata.bind(context);
29410
+ context.metadata = (data) => {
29411
+ metadataCalled = true;
29412
+ let flattened = data;
29413
+ if (data && typeof data === "object") {
29414
+ if (data.metadata && typeof data.metadata === "object") {
29415
+ const { metadata, ...rest } = data;
29416
+ flattened = { ...rest, ...metadata };
29417
+ }
29418
+ }
29419
+ lastMetadata = flattened;
29420
+ if (context.callID) {
29421
+ const pendingData = {
29422
+ title: flattened.title || toolName,
29423
+ metadata: flattened
29424
+ };
29425
+ storeToolMetadata(context.sessionID, context.callID, pendingData);
29426
+ }
29427
+ return originalMetadata(flattened);
29428
+ };
29429
+ let executionMessage;
29430
+ try {
29431
+ executionMessage = await executeFn(args, context);
29432
+ if (!metadataCalled) {
29433
+ const result = createFailureResult(`[Tool Contract Violation] Tool ${toolName} finished without calling context.metadata().`);
29434
+ const meta3 = {
29435
+ title: `${toolName} protocol error`,
29436
+ ...result,
29437
+ success: false,
29438
+ verified: false
29439
+ };
29440
+ context.metadata(meta3);
29441
+ return result.message;
29442
+ }
29443
+ } catch (error48) {
29444
+ const result = createFailureResult(`Exception in ${toolName}: ${error48.message}`);
29445
+ const meta3 = {
29446
+ title: `${toolName} failed`,
29447
+ ...result,
29448
+ success: false,
29449
+ verified: false
29450
+ };
29451
+ context.metadata(meta3);
29452
+ return result.message || error48.message;
29453
+ }
29454
+ return executionMessage;
29455
+ };
29456
+ }
29457
+
29458
+ // src/runtime/tools/fs-safe.ts
29459
+ function createFsSafeTool() {
29460
+ return tool({
29461
+ description: "Safe, structured execution of filesystem writes. Returns verifiable status.",
29462
+ args: {
29463
+ operation: exports_external.enum(["write", "delete", "mkdir"]).describe("The filesystem operation to perform."),
29464
+ filePath: exports_external.string().describe("Absolute or relative path to the file/directory."),
29465
+ content: exports_external.string().optional().describe("File content (only used if operation is 'write').")
29466
+ },
29467
+ execute: withToolContract("fs_safe", async (args, context) => {
29468
+ try {
29469
+ const { operation, filePath, content } = args;
29470
+ const fullPath = path11.resolve(context.directory || process.cwd(), filePath);
29471
+ let changedState = false;
29472
+ let stateChangePayload = null;
29473
+ if (operation === "write") {
29474
+ const dir = path11.dirname(fullPath);
29475
+ if (!fs11.existsSync(dir)) {
29476
+ fs11.mkdirSync(dir, { recursive: true });
29477
+ }
29478
+ fs11.writeFileSync(fullPath, content || "", "utf8");
29479
+ changedState = true;
29480
+ stateChangePayload = { type: "file.write", key: filePath, details: { fullPath } };
29481
+ } else if (operation === "delete") {
29482
+ if (fs11.existsSync(fullPath)) {
29483
+ const stats = fs11.statSync(fullPath);
29484
+ if (stats.isDirectory()) {
29485
+ fs11.rmSync(fullPath, { recursive: true, force: true });
29486
+ } else {
29487
+ fs11.unlinkSync(fullPath);
29488
+ }
29489
+ changedState = true;
29490
+ stateChangePayload = { type: "file.delete", key: filePath, details: { fullPath } };
29491
+ }
29492
+ } else if (operation === "mkdir") {
29493
+ if (!fs11.existsSync(fullPath)) {
29494
+ fs11.mkdirSync(fullPath, { recursive: true });
29495
+ changedState = true;
29496
+ stateChangePayload = { type: "command.execute", key: `mkdir ${filePath}`, details: { fullPath } };
29497
+ }
29498
+ }
29499
+ const result = createSuccessResult({
29500
+ verified: true,
29501
+ changedState,
29502
+ stateChange: stateChangePayload || undefined
29503
+ });
29504
+ context.metadata({
29505
+ title: `fs ${operation}`,
29506
+ ...result
29507
+ });
29508
+ return `Successfully executed ${operation} on ${filePath}`;
29509
+ } catch (err) {
29510
+ const result = createFailureResult(`Failed: ${err.message}`);
29511
+ context.metadata({
29512
+ title: `fs ${args.operation} error`,
29513
+ ...result
29514
+ });
29515
+ return result.message;
29516
+ }
29517
+ })
29518
+ });
29519
+ }
29520
+
29521
+ // src/runtime/tools/git-safe.ts
29522
+ var {spawn: spawn2 } = globalThis.Bun;
29523
+ function createGitSafeTool() {
29524
+ return tool({
29525
+ description: "Safe, structured execution of git commands. Returns verifiable status.",
29526
+ args: {
29527
+ command: exports_external.string().describe(`The git command without the 'git ' prefix (e.g. 'commit -m "msg"', 'push origin main')`)
29528
+ },
29529
+ execute: withToolContract("git_safe", async (args, context) => {
29530
+ let commandArgs = [];
29531
+ try {
29532
+ commandArgs = args.command.match(/([^\\"]\S*|".+?")\s*/g)?.map((s) => s.trim().replace(/^"(.*)"$/, "$1")) || [];
29533
+ if (commandArgs.length === 0) {
29534
+ const result2 = createFailureResult("No command provided");
29535
+ context.metadata({ title: "Git Exec Error", ...result2 });
29536
+ return result2.message;
29537
+ }
29538
+ const proc = spawn2(["git", ...commandArgs], {
29539
+ cwd: context.directory || process.cwd(),
29540
+ stdout: "pipe",
29541
+ stderr: "pipe"
29542
+ });
29543
+ const stdoutText = await new Response(proc.stdout).text();
29544
+ const stderrText = await new Response(proc.stderr).text();
29545
+ const exitCode = await proc.exited;
29546
+ const success2 = exitCode === 0;
29547
+ const isPush = commandArgs[0] === "push";
29548
+ const isCommit = commandArgs[0] === "commit";
29549
+ const isStateChanging = ["commit", "push", "checkout", "branch", "rebase", "merge", "reset", "revert", "clean", "rm", "add"].includes(commandArgs[0]);
29550
+ const changedState = success2 && isStateChanging;
29551
+ let stateChangePayload = null;
29552
+ if (changedState) {
29553
+ if (isPush)
29554
+ stateChangePayload = { type: "git.push", key: "origin", details: { exitCode } };
29555
+ else if (isCommit)
29556
+ stateChangePayload = { type: "git.commit", key: "HEAD", details: { exitCode } };
29557
+ else
29558
+ stateChangePayload = { type: "command.execute", key: `git ${commandArgs[0]}`, details: { exitCode } };
29559
+ }
29560
+ const result = createSuccessResult({
29561
+ verified: !isStateChanging,
29562
+ changedState,
29563
+ stateChange: stateChangePayload || undefined
29564
+ });
29565
+ context.metadata({
29566
+ title: `git ${commandArgs[0]}`,
29567
+ ...result
29568
+ });
29569
+ return `Exit Code: ${exitCode}
29570
+
29571
+ STDOUT:
29572
+ ${stdoutText}
29573
+
29574
+ STDERR:
29575
+ ${stderrText}`;
29576
+ } catch (e2) {
29577
+ const result = createFailureResult(`JSON parse error on args or execution failed: ${e2.message}`);
29578
+ context.metadata({ title: "Git Exec Error", ...result });
29579
+ return result.message;
29580
+ }
29581
+ })
29582
+ });
29583
+ }
29584
+
29585
+ // src/runtime/tools/plan.ts
29586
+ function createSubmitPlanTool() {
29587
+ return tool({
29588
+ description: "Submits a deterministic execution plan. The Runtime Compiler will take over and guide you strictly through the resulting dependency graph.",
29589
+ args: {
29590
+ steps: exports_external.array(exports_external.object({
29591
+ id: exports_external.string().describe("Unique step ID (e.g. 'step1')"),
29592
+ action: exports_external.string().describe("The high-level action (e.g. 'run_tests', 'fix_linter', 'commit')"),
29593
+ dependencies: exports_external.array(exports_external.string()).describe("IDs of steps that must complete before this one")
29594
+ })).describe("The execution DAG (Directed Acyclic Graph) of operations")
29595
+ },
29596
+ execute: withToolContract("submit_plan", async (args, toolContext) => {
29597
+ const taskID = compiler.submit(toolContext.sessionID, args.steps);
29598
+ const result = createSuccessResult({
29599
+ verified: true,
29600
+ changedState: false,
29601
+ metadata: { planLength: args.steps.length, taskID }
29602
+ });
29603
+ toolContext.metadata({
29604
+ title: "Plan Submitted",
29605
+ ...result
29606
+ });
29607
+ const active = compiler.getActiveStep(toolContext.sessionID);
29608
+ return `Plan successfully compiled into an executable graph (including implicit verification nodes).
29609
+
29610
+ CURRENT FORCED STEP: ${active?.action} (ID: ${active?.id}).
29611
+ Do not execute any other tools until this step is complete.`;
29612
+ })
29613
+ });
29614
+ }
29615
+ function createMarkStepCompleteTool() {
29616
+ return tool({
29617
+ description: "Marks the current forced execution step as complete and retrieves the next step.",
29618
+ args: {
29619
+ id: exports_external.string().describe("The ID of the step that was completed")
29620
+ },
29621
+ execute: withToolContract("mark_step_complete", async (args, toolContext) => {
29622
+ compiler.markStepComplete(toolContext.sessionID, args.id);
29623
+ const result = createSuccessResult({
29624
+ verified: true,
29625
+ changedState: false,
29626
+ metadata: { stepId: args.id }
29627
+ });
29628
+ toolContext.metadata({
29629
+ title: `Step ${args.id} Complete`,
29630
+ ...result
29631
+ });
29632
+ const active = compiler.getActiveStep(toolContext.sessionID);
29633
+ if (!active) {
29634
+ return `Step ${args.id} marked complete. The plan graph is now fully exhausted. You may report final success to the user.`;
29635
+ }
29636
+ return `Step ${args.id} marked complete.
29637
+
29638
+ NEXT FORCED STEP: ${active.action} (ID: ${active.id}).`;
29639
+ })
29640
+ });
29641
+ }
29642
+ function createUnlockPlanTool() {
29643
+ return tool({
29644
+ description: "Manually unlocks the deterministic execution plan, clearing any active steps for the current session.",
29645
+ args: {},
29646
+ execute: withToolContract("unlock_plan", async (_3, toolContext) => {
29647
+ compiler.clear(toolContext.sessionID);
29648
+ const result = createSuccessResult({
29649
+ verified: true,
29650
+ changedState: false
29651
+ });
29652
+ toolContext.metadata({
29653
+ title: "Plan Unlocked",
29654
+ ...result
29655
+ });
29656
+ return "The deterministic execution plan has been cleared for this session. You are now in freestyle mode.";
29657
+ })
29658
+ });
29659
+ }
29660
+
29661
+ // src/runtime/tools/verify.ts
29662
+ var {spawn: spawn3 } = globalThis.Bun;
29663
+
29664
+ // src/agents/runtime/verify-action.ts
29665
+ var VERIFICATION_COMMANDS = {
29666
+ gitPush: {
29667
+ name: "Git Push Verification",
29668
+ command: "git rev-list --count origin/${BRANCH}..HEAD",
29669
+ successCondition: 'output === "0"',
29670
+ failureMessage: "Push failed \u2014 unpushed commits remain"
29671
+ },
29672
+ gitCommit: {
29673
+ name: "Git Commit Verification",
29674
+ command: 'git log -1 --format="%H"',
29675
+ successCondition: "output !== previousHash",
29676
+ failureMessage: "Commit failed \u2014 hash unchanged from before work"
29677
+ },
29678
+ prCreated: {
29679
+ name: "PR Creation Verification",
29680
+ command: 'gh pr view --json url --jq ".url"',
29681
+ successCondition: 'output starts with "https://"',
29682
+ failureMessage: "PR not found \u2014 do not fabricate URL"
29683
+ },
29684
+ cleanWorkDir: {
29685
+ name: "Clean Working Directory",
29686
+ command: "git status --porcelain",
29687
+ successCondition: "output is empty",
29688
+ failureMessage: "Uncommitted changes exist \u2014 cannot proceed"
29689
+ },
29690
+ branchExists: {
29691
+ name: "Branch Existence",
29692
+ command: "git rev-parse --verify ${BRANCH}",
29693
+ successCondition: "exit code === 0",
29694
+ failureMessage: "Branch does not exist"
29695
+ },
29696
+ commandExit: {
29697
+ name: "Command Exit Code",
29698
+ command: undefined,
29699
+ successCondition: "exit code === 0",
29700
+ failureMessage: "Command failed with non-zero exit code"
29701
+ },
29702
+ fileWrite: {
29703
+ name: "File Write Verification",
29704
+ command: undefined,
29705
+ successCondition: "write tool returned success",
29706
+ failureMessage: "File write failed \u2014 check tool output"
29707
+ }
29708
+ };
29709
+
29710
+ // src/runtime/tools/verify.ts
29711
+ function createVerifyTool() {
29712
+ return tool({
29713
+ description: "Centrally verifies system state changes. Required before claiming success.",
29714
+ args: {
29715
+ action: exports_external.enum(Object.keys(VERIFICATION_COMMANDS)).describe("The predefined verification action to perform."),
29716
+ context: exports_external.record(exports_external.string(), exports_external.any()).optional().describe("Context variables needed for the command (e.g., { BRANCH: 'feature-1' })")
29717
+ },
29718
+ execute: withToolContract("verify_action", async (args, toolContext) => {
29719
+ const actionKey = args.action;
29720
+ const config2 = VERIFICATION_COMMANDS[actionKey];
29721
+ if (!config2 || !config2.command) {
29722
+ const result2 = createFailureResult(`Unknown verification action or missing command: ${args.action}`);
29723
+ toolContext.metadata({ title: "Verify Error", ...result2 });
29724
+ return result2.message;
29725
+ }
29726
+ let cmdString = config2.command;
29727
+ if (args.context) {
29728
+ for (const [key, value] of Object.entries(args.context)) {
29729
+ cmdString = cmdString.replace(`\${${key}}`, String(value));
29730
+ }
29731
+ }
29732
+ const commandArgs = cmdString.split(" ");
29733
+ const proc = spawn3(commandArgs, {
29734
+ cwd: toolContext.directory || process.cwd(),
29735
+ stdout: "pipe",
29736
+ stderr: "pipe"
29737
+ });
29738
+ const stdoutText = await new Response(proc.stdout).text();
29739
+ const exitCode = await proc.exited;
29740
+ let isSuccess = false;
29741
+ if (config2.successCondition === 'output === "0"') {
29742
+ isSuccess = stdoutText.trim() === "0";
29743
+ } else if (config2.successCondition === "exit code === 0") {
29744
+ isSuccess = exitCode === 0;
29745
+ } else if (config2.successCondition === 'output starts with "https://"') {
29746
+ isSuccess = stdoutText.trim().startsWith("https://");
29747
+ } else if (config2.successCondition === "output is empty") {
29748
+ isSuccess = stdoutText.trim() === "";
29749
+ } else {
29750
+ isSuccess = exitCode === 0;
29751
+ }
29752
+ const result = createSuccessResult({
29753
+ verified: isSuccess,
29754
+ changedState: false,
29755
+ message: isSuccess ? undefined : config2.failureMessage
29756
+ });
29757
+ toolContext.metadata({
29758
+ title: `Verify: ${config2.name}`,
29759
+ ...result
29760
+ });
29761
+ return isSuccess ? `Verification SUCCESS. (Output: ${stdoutText.trim()})` : `Verification FAILED. ${config2.failureMessage} (Output: ${stdoutText.trim()})`;
29762
+ })
29763
+ });
29764
+ }
29765
+
29766
+ // src/runtime/state-ledger.ts
29767
+ class StateLedger {
29768
+ static instance;
29769
+ entries = [];
29770
+ lastFlowStartTime = 0;
29771
+ constructor() {}
29772
+ static getInstance() {
29773
+ if (!StateLedger.instance) {
29774
+ StateLedger.instance = new StateLedger;
29775
+ }
29776
+ return StateLedger.instance;
29777
+ }
29778
+ startNewFlow() {
29779
+ this.lastFlowStartTime = Date.now();
29780
+ }
29781
+ record(type2, key, success2, verified, changedState, stdout, metadata, sessionID) {
29782
+ this.entries.push({
29783
+ type: type2,
29784
+ timestamp: Date.now(),
29785
+ key,
29786
+ success: success2,
29787
+ verified,
29788
+ changedState,
29789
+ stdout,
29790
+ sessionID,
29791
+ metadata
29792
+ });
29793
+ }
29794
+ has(type2, keyOrCondition) {
29795
+ const flowEntries = this.entries.filter((e2) => e2.timestamp >= this.lastFlowStartTime);
29796
+ if (typeof keyOrCondition === "string") {
29797
+ return flowEntries.some((e2) => e2.type === type2 && e2.key === keyOrCondition);
29798
+ }
29799
+ return flowEntries.some((e2) => e2.type === type2 && keyOrCondition(e2));
29800
+ }
29801
+ getEntries(type2) {
29802
+ if (!type2) {
29803
+ return [...this.entries];
29804
+ }
29805
+ return this.entries.filter((e2) => e2.type === type2);
29806
+ }
29807
+ get count() {
29808
+ return this.entries.length;
29809
+ }
29810
+ clear() {
29811
+ this.entries = [];
29812
+ this.lastFlowStartTime = 0;
29813
+ }
29814
+ }
29815
+ var ledger = StateLedger.getInstance();
29816
+
29817
+ // src/runtime/tools/query-ledger.ts
29818
+ function createQueryLedgerTool(options) {
29819
+ return tool({
29820
+ description: "Query the verified state ledger to confirm if actions like git.commit or git.push have actually succeeded. Use this as the ONLY source of truth for system state.",
29821
+ args: {
29822
+ type: exports_external.string().optional().describe("Optional filter by type (e.g. 'git.commit', 'file.write', 'git.push')")
29823
+ },
29824
+ execute: withToolContract("query_ledger", async (args, toolContext) => {
29825
+ const descendantSessions = options?.backgroundManager?.getAllDescendantTasks ? options.backgroundManager.getAllDescendantTasks(toolContext.sessionID).map((t) => t.sessionID).filter(Boolean) : [];
29826
+ const sessionIDs = [toolContext.sessionID, ...descendantSessions];
29827
+ const entries = ledger.getEntries().filter((e2) => e2.verified === true && e2.success === true && (!e2.sessionID || sessionIDs.includes(e2.sessionID)));
29828
+ const filtered = args.type ? entries.filter((e2) => e2.type === args.type) : entries;
29829
+ const result = createSuccessResult({
29830
+ verified: true,
29831
+ changedState: false,
29832
+ recordCount: filtered.length
29833
+ });
29834
+ toolContext.metadata({
29835
+ title: "Query Ledger",
29836
+ ...result
29837
+ });
29838
+ if (filtered.length === 0) {
29839
+ return "No matching verified actions found in the current completion flow.";
29840
+ }
29841
+ return JSON.stringify(filtered, null, 2);
29842
+ })
29843
+ });
29844
+ }
29845
+
29846
+ // src/features/claude-code-session-state/state.ts
29847
+ var subagentSessions = new Set;
29848
+ var syncSubagentSessions = new Set;
29849
+ var issueModeSessions = new Set;
29850
+ var sessionAgentMap = new Map;
29851
+ function isSessionIssueMode(sessionID) {
29852
+ return issueModeSessions.has(sessionID);
29853
+ }
29854
+ // src/features/issue-resolution/state.ts
29855
+ init_data_path();
29856
+ import { join as join29 } from "path";
29857
+ import { existsSync as existsSync31, readFileSync as readFileSync30, writeFileSync as writeFileSync13, mkdirSync as mkdirSync9 } from "fs";
29858
+ var DATA_DIR = join29(getDataDir(), "oh-my-opencode");
29859
+ var ISSUE_STATE_FILE = join29(DATA_DIR, "issue-verification-state.json");
29860
+ function ensureDataDir() {
29861
+ if (!existsSync31(DATA_DIR)) {
29862
+ mkdirSync9(DATA_DIR, { recursive: true });
29863
+ }
29864
+ }
29865
+ function loadFromDisk() {
29866
+ try {
29867
+ if (existsSync31(ISSUE_STATE_FILE)) {
29868
+ const data = JSON.parse(readFileSync30(ISSUE_STATE_FILE, "utf8"));
29869
+ return new Map(Object.entries(data));
29870
+ }
29871
+ } catch (e2) {}
29872
+ return new Map;
29873
+ }
29874
+ function saveToDisk(states) {
29875
+ ensureDataDir();
29876
+ const data = Object.fromEntries(states);
29877
+ writeFileSync13(ISSUE_STATE_FILE, JSON.stringify(data, null, 2));
29878
+ }
29879
+ var issueStates = loadFromDisk();
29880
+ function getIssueState(sessionID) {
29881
+ if (!issueStates.has(sessionID)) {
29882
+ issueStates.set(sessionID, {
29883
+ reproduced: false,
29884
+ fixApplied: false,
29885
+ reproAfterPassed: false,
29886
+ failureModeChecksPassed: false
29887
+ });
29888
+ saveToDisk(issueStates);
29889
+ }
29890
+ return issueStates.get(sessionID);
29891
+ }
29892
+ function updateIssueState(sessionID, partialState) {
29893
+ const currentState = getIssueState(sessionID);
29894
+ const newState = { ...currentState, ...partialState };
29895
+ issueStates.set(sessionID, newState);
29896
+ saveToDisk(issueStates);
29897
+ return newState;
29898
+ }
29899
+
29900
+ // src/runtime/tools/complete-task.ts
29901
+ function createCompleteTaskTool(options) {
29902
+ return tool({
29903
+ description: "Signal that the task is complete. The runtime will compose the final verified state report. DO NOT output your own summary, just call this tool.",
29904
+ args: {
29905
+ message: exports_external.string().describe("Optional short note about what was done. Do not include PR URLs or commit hashes here.")
29906
+ },
29907
+ execute: withToolContract("complete_task", async (args, toolContext) => {
29908
+ const client3 = options?.client;
29909
+ const sessionID = toolContext.sessionID;
29910
+ if (client3) {
29911
+ try {
29912
+ const todosRes = await client3.session.todo({
29913
+ path: { id: sessionID }
29914
+ });
29915
+ const todos = normalizeSDKResponse(todosRes, []);
29916
+ const incompleteTodos = todos.filter((t) => t.status !== "completed" && t.status !== "cancelled" && t.status !== "blocked" && t.status !== "deleted");
29917
+ if (incompleteTodos.length > 0) {
29918
+ const failMsg = `[ERROR] TASK COMPLETION REJECTED.
29919
+
29920
+ You have ${incompleteTodos.length} incomplete TODOs remaining in the UI tracker. You CANNOT mark the task as complete until all TODOs are explicitly marked as completed or cancelled using the TodoWrite tool (or TaskUpdate if experimental tasks are enabled).
29921
+
29922
+ Please review your TODOs, complete them, and then retry calling complete_task.`;
29923
+ const result2 = createFailureResult(failMsg);
29924
+ const meta3 = { title: "Task Completion Rejected", ...result2 };
29925
+ toolContext.metadata(meta3);
29926
+ return failMsg;
29927
+ }
29928
+ } catch (e2) {}
29929
+ }
29930
+ if (isSessionIssueMode(sessionID)) {
29931
+ const issueState = getIssueState(sessionID);
29932
+ if (!issueState.reproduced || !issueState.fixApplied || !issueState.reproAfterPassed) {
29933
+ const failMsg = `[ERROR] STRICT ISSUE RESOLUTION MODE ACTIVE.
29934
+
29935
+ You cannot mark this task as complete until you have explicitly verified the fix.
29936
+
29937
+ Current Verification State:
29938
+ - Reproduced: ${issueState.reproduced}
29939
+ - Fix Applied: ${issueState.fixApplied}
29940
+ - Repro After Fix Passed: ${issueState.reproAfterPassed}
29941
+
29942
+ You MUST use the 'report_issue_verification' tool to truthfully log your progress as you perform each step. If you only performed static reasoning without live verification, your state is incomplete.`;
29943
+ const result2 = createFailureResult(failMsg);
29944
+ toolContext.metadata({ title: "Task Completion Rejected", ...result2 });
29945
+ return failMsg;
29946
+ }
29947
+ }
29948
+ const descendantSessions = options?.backgroundManager?.getAllDescendantTasks ? options.backgroundManager.getAllDescendantTasks(toolContext.sessionID).map((t) => t.sessionID).filter(Boolean) : [];
29949
+ const sessionIDs = [toolContext.sessionID, ...descendantSessions];
29950
+ const entries = ledger.getEntries().filter((e2) => e2.verified === true && e2.success === true && (!e2.sessionID || sessionIDs.includes(e2.sessionID)));
29951
+ let report = `TASK COMPLETE.
29952
+
29953
+ Runtime Verified Actions (Current Flow):
29954
+ `;
29955
+ if (entries.length === 0) {
29956
+ report += `- No state changes recorded in this session.
29957
+ `;
29958
+ } else {
29959
+ for (const entry of entries) {
29960
+ report += `- [${entry.type}] ${entry.key}
29961
+ `;
29962
+ }
29963
+ }
29964
+ if (args.message) {
29965
+ report += `
29966
+ Agent Note: ${args.message}
29967
+ `;
29968
+ }
29969
+ const result = createSuccessResult({
29970
+ verified: true,
29971
+ changedState: false,
29972
+ message: args.message
29973
+ });
29974
+ toolContext.metadata({
29975
+ title: "Task Completed",
29976
+ ...result,
29977
+ sessionID: toolContext.sessionID,
29978
+ entries: entries.length
29979
+ });
29980
+ return `[RUNTIME AUTHORIZATION]
29981
+
29982
+ ${report}
29983
+
29984
+ You may now conclude your response using EXACTLY this report as your final output.`;
29985
+ })
29986
+ });
29987
+ }
29988
+
29989
+ // src/cli/doctor/checks/tool-contract.ts
29990
+ async function checkToolContract() {
29991
+ const issues = [];
29992
+ const toolsToTest = [
29993
+ { name: "fs_safe", factory: createFsSafeTool, args: { operation: "read", filePath: "non-existent-test-file" } },
29994
+ { name: "git_safe", factory: createGitSafeTool, args: { command: "status" } },
29995
+ { name: "submit_plan", factory: createSubmitPlanTool, args: { steps: [] } },
29996
+ { name: "mark_step_complete", factory: createMarkStepCompleteTool, args: { id: "step1" } },
29997
+ { name: "unlock_plan", factory: createUnlockPlanTool, args: {} },
29998
+ { name: "verify_action", factory: createVerifyTool, args: { action: "LS_FILES" } },
29999
+ { name: "query_ledger", factory: createQueryLedgerTool, args: {} },
30000
+ { name: "complete_task", factory: createCompleteTaskTool, args: { message: "test completion" } }
30001
+ ];
30002
+ for (const toolSpec of toolsToTest) {
30003
+ try {
30004
+ const tool3 = toolSpec.factory();
30005
+ const sessionID = `doctor-test-${Date.now()}`;
30006
+ const callID = `call-${toolSpec.name}`;
30007
+ clearPendingStore();
30008
+ const mockContext = {
30009
+ sessionID,
30010
+ callID,
30011
+ directory: process.cwd(),
30012
+ metadata: () => {},
30013
+ client: {}
30014
+ };
30015
+ await tool3.execute(toolSpec.args, mockContext).catch(() => {});
30016
+ const stored = consumeToolMetadata(sessionID, callID);
30017
+ if (!stored || !stored.metadata) {
30018
+ issues.push({
30019
+ title: `Tool Contract Violation: ${toolSpec.name}`,
30020
+ description: `Tool did not call storeToolMetadata or returned no metadata.`,
30021
+ severity: "error",
30022
+ affects: [toolSpec.name]
30023
+ });
30024
+ continue;
30025
+ }
30026
+ const meta3 = stored.metadata;
30027
+ if (typeof meta3.success !== "boolean" || typeof meta3.verified !== "boolean") {
30028
+ issues.push({
30029
+ title: `Tool Contract Violation: ${toolSpec.name}`,
30030
+ description: `Tool metadata missing 'success' or 'verified' booleans. Found: ${JSON.stringify(meta3)}`,
30031
+ severity: "error",
30032
+ affects: [toolSpec.name]
30033
+ });
30034
+ }
30035
+ } catch (e2) {
30036
+ issues.push({
30037
+ title: `Doctor internal error testing ${toolSpec.name}`,
30038
+ description: e2.message,
30039
+ severity: "warning"
30040
+ });
30041
+ }
30042
+ }
30043
+ return {
30044
+ name: "Tool Contract Compliance",
30045
+ status: issues.length === 0 ? "pass" : "fail",
30046
+ message: issues.length === 0 ? "All safety tools comply with the result contract." : `${issues.length} tool contract violation(s) detected.`,
30047
+ details: toolsToTest.map((t) => `${t.name}: tested`),
30048
+ issues
30049
+ };
30050
+ }
30051
+
30052
+ // src/cli/doctor/checks/edit-atomicity.ts
30053
+ import { readFileSync as readFileSync32, writeFileSync as writeFileSync15, unlinkSync as unlinkSync3, existsSync as existsSync33 } from "fs";
30054
+ import { join as join30 } from "path";
30055
+
30056
+ // src/hooks/edit-safeguard/hook.ts
30057
+ init_shared();
30058
+ import { existsSync as existsSync32, readFileSync as readFileSync31, writeFileSync as writeFileSync14 } from "fs";
30059
+ import { spawnSync } from "child_process";
30060
+
30061
+ // src/shared/read-permission-tracker.ts
30062
+ init_logger();
30063
+
30064
+ class ReadPermissionTracker {
30065
+ readFiles = new Map;
30066
+ recordRead(sessionID, filePath) {
30067
+ if (!this.readFiles.has(sessionID)) {
30068
+ this.readFiles.set(sessionID, new Set);
30069
+ }
30070
+ const normalizedPath = this.normalizePath(filePath);
30071
+ this.readFiles.get(sessionID).add(normalizedPath);
30072
+ log(`[ReadTracker] Recorded read for ${normalizedPath} in session ${sessionID}`);
30073
+ }
30074
+ hasRead(sessionID, filePath) {
30075
+ const sessionReads = this.readFiles.get(sessionID);
30076
+ if (!sessionReads)
30077
+ return false;
30078
+ const normalizedPath = this.normalizePath(filePath);
30079
+ return sessionReads.has(normalizedPath);
30080
+ }
30081
+ clearSession(sessionID) {
30082
+ this.readFiles.delete(sessionID);
30083
+ }
30084
+ normalizePath(filePath) {
30085
+ return filePath.trim().replace(/\/+$/, "");
30086
+ }
30087
+ }
30088
+ var readTracker = new ReadPermissionTracker;
30089
+
30090
+ // src/hooks/edit-safeguard/hook.ts
30091
+ var FAILURE_PATTERNS = [
30092
+ "oldstring not found",
30093
+ "oldstring found multiple times",
30094
+ "oldstring and newstring must be different",
30095
+ "verification failed",
30096
+ "failed to find expected lines"
30097
+ ];
30098
+ function createEditSafeguardHook(ctx) {
30099
+ const backups = new Map;
30100
+ function getFilePath(toolName, args) {
30101
+ if (toolName === "edit")
30102
+ return args.filePath;
30103
+ if (toolName === "apply_patch")
30104
+ return args.path || args.filePath;
30105
+ return;
30106
+ }
30107
+ return {
30108
+ "tool.execute.before": async (input, output) => {
30109
+ const toolName = input.tool?.toLowerCase();
30110
+ const isEditTool = toolName === "edit" || toolName === "apply_patch";
30111
+ if (!isEditTool)
30112
+ return;
30113
+ const filePath = getFilePath(toolName, output.args);
30114
+ if (!filePath)
30115
+ return;
30116
+ if (!readTracker.hasRead(input.sessionID, filePath) && existsSync32(filePath)) {
30117
+ log("[edit-safeguard] REJECTED: Read-before-write violation", { filePath, sessionID: input.sessionID });
30118
+ throw new Error(`[Edit Discipline Violation] Path REJECTED: ${filePath}
30119
+ ` + `You attempted to edit or patch a file that you have not read in the current session.
30120
+ ` + `The rule is path-specific: you MUST read the exact target file path before attempting to edit or overwrite it.
30121
+ ` + `Reading a different file or a grep snippet does not count.`);
30122
+ }
30123
+ try {
30124
+ if (existsSync32(filePath)) {
30125
+ const content = readFileSync31(filePath, "utf-8");
30126
+ backups.set(`${input.sessionID}:${input.callID}:${filePath}`, content);
30127
+ }
30128
+ } catch (err) {
30129
+ log("[edit-safeguard] Failed to create backup", { filePath, error: String(err) });
30130
+ }
30131
+ },
30132
+ "tool.execute.after": async (input, output) => {
30133
+ const toolName = input.tool?.toLowerCase();
30134
+ const isEditTool = toolName === "edit" || toolName === "apply_patch";
30135
+ if (!isEditTool)
30136
+ return;
30137
+ const filePath = getFilePath(toolName, output.metadata?.args || {});
30138
+ if (!filePath)
30139
+ return;
30140
+ const backupKey = `${input.sessionID}:${input.callID}:${filePath}`;
30141
+ const originalContent = backups.get(backupKey);
30142
+ if (originalContent === undefined)
30143
+ return;
30144
+ const resultOutput = (output.output ?? "").toLowerCase();
30145
+ const hasFailurePattern = FAILURE_PATTERNS.some((p2) => resultOutput.includes(p2));
30146
+ let currentContent = "";
30147
+ try {
30148
+ currentContent = readFileSync31(filePath, "utf-8");
30149
+ } catch (err) {
30150
+ if (hasFailurePattern) {
30151
+ writeFileSync14(filePath, originalContent);
30152
+ log("[edit-safeguard] Restored deleted file after failed edit", { filePath });
30153
+ }
30154
+ return;
30155
+ }
30156
+ const contentChanged = currentContent !== originalContent;
30157
+ if (hasFailurePattern && contentChanged) {
30158
+ writeFileSync14(filePath, originalContent);
30159
+ log("[edit-safeguard] Reverted partial mutation after " + resultOutput, { filePath });
30160
+ output.output += `
30161
+
30162
+ [SAFEGUARD] Reverted partial mutation to prevent corruption.`;
30163
+ }
30164
+ if (hasFailurePattern) {
30165
+ output.output += `
30166
+
30167
+ **ACTION REQUIRED**: The edit or patch verification failed. This usually means the file content has drifted or the match block is incorrect.
30168
+ ` + `You MUST run 'read_file' (or equivalent) on '${filePath}' to synchronize your context with the real file contents, and then regenerate your edit/patch from the exact lines found in the file.`;
30169
+ return;
30170
+ }
30171
+ if (!hasFailurePattern && contentChanged) {
30172
+ if (filePath.endsWith(".py")) {
30173
+ const isValid = validatePythonSyntax(filePath);
30174
+ if (!isValid) {
30175
+ writeFileSync14(filePath, originalContent);
30176
+ log("[edit-safeguard] Reverted edit due to Python syntax error", { filePath });
30177
+ throw new Error(`[edit-safeguard] Syntax validation failed for ${filePath}. Edit reverted to prevent corruption.`);
30178
+ }
30179
+ }
30180
+ }
30181
+ backups.delete(backupKey);
30182
+ }
30183
+ };
30184
+ }
30185
+ function validatePythonSyntax(filePath) {
30186
+ try {
30187
+ const result = spawnSync("python3", ["-m", "py_compile", filePath]);
30188
+ return result.status === 0;
30189
+ } catch (err) {
30190
+ log("[edit-safeguard] Syntax validation execution failed", { error: String(err) });
30191
+ return true;
30192
+ }
30193
+ }
30194
+
30195
+ // src/cli/doctor/checks/edit-atomicity.ts
30196
+ var checkEditAtomicity = {
30197
+ id: "EDIT_ATOMICITY",
30198
+ name: "Edit Atomicity & Validation",
30199
+ async check() {
30200
+ const testFile = join30(process.cwd(), "test_edit_atomicity.py");
30201
+ const originalContent = `def main():
30202
+ print("hello")
30203
+ `;
30204
+ try {
30205
+ writeFileSync15(testFile, originalContent);
30206
+ const hook = createEditSafeguardHook({ directory: process.cwd() });
30207
+ const sessionID = "test-session";
30208
+ const callID = "test-call";
30209
+ await hook["tool.execute.before"]?.({ tool: "edit", sessionID, callID }, { args: { filePath: testFile } });
30210
+ writeFileSync15(testFile, `def main():
30211
+ print("corrupted"
30212
+ `);
30213
+ const output = {
30214
+ title: "Edit",
30215
+ output: "Error: oldString not found",
30216
+ metadata: { args: { filePath: testFile } }
30217
+ };
30218
+ await hook["tool.execute.after"]?.({ tool: "edit", sessionID, callID, args: { filePath: testFile } }, output);
30219
+ const contentAfterFail = readFileSync32(testFile, "utf-8");
30220
+ if (contentAfterFail !== originalContent) {
30221
+ return {
30222
+ status: "fail",
30223
+ name: this.name,
30224
+ message: "Edit is NOT atomic. Partial mutation was not reverted after error.",
30225
+ issues: [{
30226
+ title: "Partial Mutation",
30227
+ description: "File content changed despite tool reporting failure.",
30228
+ severity: "error"
30229
+ }]
30230
+ };
30231
+ }
30232
+ await hook["tool.execute.before"]?.({ tool: "edit", sessionID, callID: "call2" }, { args: { filePath: testFile } });
30233
+ writeFileSync15(testFile, `def main():
30234
+ print("invalid")
30235
+ x = [`);
30236
+ const outputSuccess = {
30237
+ title: "Edit",
30238
+ output: "Successfully replaced string",
30239
+ metadata: { args: { filePath: testFile } }
30240
+ };
30241
+ try {
30242
+ await hook["tool.execute.after"]?.({ tool: "edit", sessionID, callID: "call2", args: { filePath: testFile } }, outputSuccess);
30243
+ return {
30244
+ status: "fail",
30245
+ name: this.name,
30246
+ message: "Syntax validation failed to block invalid Python code.",
30247
+ issues: [{
30248
+ title: "Validation Bypass",
30249
+ description: "Edit tool allowed syntactically invalid Python code.",
30250
+ severity: "error"
30251
+ }]
30252
+ };
30253
+ } catch (err) {
30254
+ if (!String(err).includes("Syntax validation failed")) {
30255
+ return {
30256
+ status: "fail",
30257
+ name: this.name,
30258
+ message: `Unexpected error during syntax validation: ${err}`,
30259
+ issues: [{
30260
+ title: "Unexpected Error",
30261
+ description: String(err),
30262
+ severity: "error"
30263
+ }]
30264
+ };
30265
+ }
30266
+ }
30267
+ const contentAfterSyntaxFail = readFileSync32(testFile, "utf-8");
30268
+ if (contentAfterSyntaxFail !== originalContent) {
30269
+ return {
30270
+ status: "fail",
30271
+ name: this.name,
30272
+ message: "File was not reverted after syntax validation failure.",
30273
+ issues: [{
30274
+ title: "Revert Failed",
30275
+ description: "File was not restored to its original state after syntax error.",
30276
+ severity: "error"
30277
+ }]
30278
+ };
30279
+ }
30280
+ return {
30281
+ status: "pass",
30282
+ name: this.name,
30283
+ message: "Edit atomicity and syntax validation are working correctly.",
30284
+ issues: []
30285
+ };
30286
+ } catch (err) {
30287
+ return {
30288
+ status: "fail",
30289
+ name: this.name,
30290
+ message: `Check failed with error: ${err}`,
30291
+ issues: [{
30292
+ title: "Check Error",
30293
+ description: String(err),
30294
+ severity: "error"
30295
+ }]
30296
+ };
30297
+ } finally {
30298
+ if (existsSync33(testFile))
30299
+ unlinkSync3(testFile);
30300
+ }
30301
+ }
30302
+ };
30303
+
30304
+ // src/cli/doctor/checks/issue-resolution.ts
30305
+ import { existsSync as existsSync35, mkdirSync as mkdirSync11, writeFileSync as writeFileSync17, unlinkSync as unlinkSync4 } from "fs";
30306
+ import { join as join32 } from "path";
30307
+
30308
+ // src/runtime/tools/report-issue-verification.ts
30309
+ function createReportIssueVerificationTool() {
30310
+ return tool({
30311
+ name: "report_issue_verification",
30312
+ description: "Report progress on issue verification. Use this tool to permanently record that you have reproduced the issue, applied a fix, and verified the fix. Required before completing any issue-resolution task. Fields are additive, so you can report them one at a time as you proceed.",
30313
+ args: {
30314
+ reproduced: exports_external.boolean().optional().describe("Set to true once you have successfully reproduced the issue live."),
30315
+ errorSignatureBefore: exports_external.string().optional().describe("The exact error message, traceback, or symptom before the fix."),
30316
+ fixApplied: exports_external.boolean().optional().describe("Set to true once you have applied a fix to the codebase."),
30317
+ reproAfterPassed: exports_external.boolean().optional().describe("True if you re-ran the reproduction steps after the fix, and the issue is gone."),
30318
+ failureModeChecksPassed: exports_external.boolean().optional().describe("Set to true if you checked nearby/related failure modes and they pass.")
30319
+ },
30320
+ execute: withToolContract("report_issue_verification", async (args, toolContext) => {
30321
+ const sessionID = toolContext.sessionID;
30322
+ const newState = updateIssueState(sessionID, args);
30323
+ const result = createSuccessResult({
30324
+ verified: true,
30325
+ changedState: true,
30326
+ stateChange: newState,
30327
+ message: `Verification state updated.`
30328
+ });
30329
+ toolContext.metadata({
30330
+ title: "Issue Verification Update",
30331
+ ...result,
30332
+ sessionID,
30333
+ state: newState
30334
+ });
30335
+ let response = `[VERIFICATION LOGGED]
30336
+
30337
+ Current Verification State:
30338
+ - Reproduced: ${newState.reproduced}
30339
+ - Error Signature: ${newState.errorSignatureBefore ?? "None"}
30340
+ - Fix Applied: ${newState.fixApplied}
30341
+ - Repro After Fix Passed: ${newState.reproAfterPassed}
30342
+ - Nearby Checks Passed: ${newState.failureModeChecksPassed}
30343
+
30344
+ `;
30345
+ if (isSessionIssueMode(sessionID)) {
30346
+ response += `Note: You are in Strict Issue Resolution Mode. 'reproduced', 'fixApplied', and 'reproAfterPassed' MUST be true to complete this task.`;
30347
+ }
30348
+ return response;
30349
+ })
30350
+ });
30351
+ }
30352
+
30353
+ // src/runtime/tools/gh-safe.ts
30354
+ var {spawn: spawn4 } = globalThis.Bun;
30355
+
30356
+ // src/features/pr-state/storage.ts
30357
+ import { existsSync as existsSync34, readFileSync as readFileSync33, writeFileSync as writeFileSync16, mkdirSync as mkdirSync10 } from "fs";
30358
+ import { join as join31 } from "path";
30359
+ import { homedir as homedir9 } from "os";
30360
+ var STORAGE_DIR = join31(homedir9(), ".local", "share", "oh-my-opencode");
30361
+ var STORAGE_FILE = join31(STORAGE_DIR, "pr-state.json");
30362
+ var state2 = {};
30363
+ function loadPRState() {
30364
+ try {
30365
+ if (existsSync34(STORAGE_FILE)) {
30366
+ state2 = JSON.parse(readFileSync33(STORAGE_FILE, "utf-8"));
30367
+ }
30368
+ } catch (e2) {
30369
+ state2 = {};
30370
+ }
30371
+ }
30372
+ function savePRState() {
30373
+ try {
30374
+ if (!existsSync34(STORAGE_DIR)) {
30375
+ mkdirSync10(STORAGE_DIR, { recursive: true });
30376
+ }
30377
+ writeFileSync16(STORAGE_FILE, JSON.stringify(state2, null, 2));
30378
+ } catch (e2) {}
30379
+ }
30380
+ function updatePRState(sessionID, pr) {
30381
+ const current = state2[sessionID] || { updatedAt: new Date().toISOString() };
30382
+ state2[sessionID] = {
30383
+ ...current,
30384
+ ...pr,
30385
+ updatedAt: new Date().toISOString()
30386
+ };
30387
+ savePRState();
30388
+ }
30389
+ loadPRState();
30390
+
30391
+ // src/runtime/tools/gh-safe.ts
30392
+ function createGhSafeTool() {
30393
+ return tool({
30394
+ description: "Safe, structured execution of GitHub CLI (gh) commands. Returns verifiable status.",
30395
+ args: {
30396
+ command: exports_external.string().describe(`The gh command without the 'gh ' prefix (e.g. 'pr create --title "..." --body "..."')`)
30397
+ },
30398
+ execute: withToolContract("gh_safe", async (args, context) => {
30399
+ let commandArgs = [];
30400
+ try {
30401
+ commandArgs = args.command.match(/([^\\"]\S*|".+?")\s*/g)?.map((s) => s.trim().replace(/^"(.*)"$/, "$1")) || [];
30402
+ if (commandArgs.length === 0) {
30403
+ const result2 = createFailureResult("No command provided");
30404
+ context.metadata({ title: "GH Exec Error", ...result2 });
30405
+ return result2.message;
30406
+ }
30407
+ const proc = spawn4(["gh", ...commandArgs], {
30408
+ cwd: context.directory || process.cwd(),
30409
+ stdout: "pipe",
30410
+ stderr: "pipe"
30411
+ });
30412
+ const stdoutText = await new Response(proc.stdout).text();
30413
+ const stderrText = await new Response(proc.stderr).text();
30414
+ const exitCode = await proc.exited;
30415
+ const success2 = exitCode === 0;
30416
+ let prURL = "";
30417
+ if (success2 && commandArgs[0] === "pr" && commandArgs[1] === "create") {
30418
+ const match = stdoutText.match(/https:\/\/github\.com\/[^\/]+\/[^\/]+\/pull\/\d+/);
30419
+ if (match) {
30420
+ prURL = match[0];
30421
+ const prNumber = parseInt(prURL.split("/").pop() || "0");
30422
+ updatePRState(context.sessionID, {
30423
+ url: prURL,
30424
+ number: prNumber,
30425
+ status: "open"
30426
+ });
30427
+ }
30428
+ }
30429
+ if (success2 && commandArgs[0] === "pr" && commandArgs[1] === "merge") {
30430
+ updatePRState(context.sessionID, {
30431
+ status: "merged"
30432
+ });
30433
+ }
30434
+ const isStateChanging = ["pr", "issue", "branch", "repo"].includes(commandArgs[0]);
30435
+ const result = createSuccessResult({
30436
+ verified: !isStateChanging,
30437
+ changedState: success2 && isStateChanging,
30438
+ stateChange: prURL ? { type: "git.pr", key: prURL, details: { url: prURL } } : undefined
30439
+ });
30440
+ context.metadata({
30441
+ title: `gh ${commandArgs[0]} ${commandArgs[1] || ""}`,
30442
+ ...result
30443
+ });
30444
+ return `Exit Code: ${exitCode}
30445
+
30446
+ STDOUT:
30447
+ ${stdoutText}
30448
+
30449
+ STDERR:
30450
+ ${stderrText}`;
30451
+ } catch (e2) {
30452
+ const result = createFailureResult(`Execution failed: ${e2.message}`);
30453
+ context.metadata({ title: "GH Exec Error", ...result });
30454
+ return result.message;
30455
+ }
30456
+ })
30457
+ });
30458
+ }
30459
+
30460
+ // src/runtime/tools/registry.ts
30461
+ var DETERMINISTIC_TOOLS = {
30462
+ git_safe: createGitSafeTool,
30463
+ fs_safe: createFsSafeTool,
30464
+ verify_action: createVerifyTool,
30465
+ submit_plan: createSubmitPlanTool,
30466
+ mark_step_complete: createMarkStepCompleteTool,
30467
+ unlock_plan: createUnlockPlanTool,
30468
+ query_ledger: createQueryLedgerTool,
30469
+ complete_task: createCompleteTaskTool,
30470
+ report_issue_verification: createReportIssueVerificationTool,
30471
+ gh_safe: createGhSafeTool
30472
+ };
30473
+ function getToolFromRegistry(name) {
30474
+ const toolFactory = DETERMINISTIC_TOOLS[name];
30475
+ if (!toolFactory) {
30476
+ throw new Error(`[Tool Registry] Unsupported or unauthorized tool requested: ${name}`);
30477
+ }
30478
+ return toolFactory();
30479
+ }
30480
+
30481
+ // src/cli/doctor/checks/issue-resolution.ts
30482
+ init_data_path();
30483
+ async function checkIssueResolutionWorkflow() {
30484
+ const issues = [];
30485
+ const requiredTools = ["report_issue_verification", "complete_task", "gh_safe", "query_ledger"];
30486
+ for (const toolName of requiredTools) {
30487
+ if (!DETERMINISTIC_TOOLS[toolName]) {
30488
+ issues.push({
30489
+ severity: "error",
30490
+ title: "Missing Tool",
30491
+ description: `${toolName} tool is missing from deterministic registry`,
30492
+ fix: `Ensure ${toolName} is registered in src/runtime/tools/registry.ts`
30493
+ });
30494
+ }
30495
+ }
30496
+ try {
30497
+ const dataDir = join32(getDataDir(), "oh-my-opencode");
30498
+ const testFile = join32(dataDir, ".doctor-persistence-test");
30499
+ if (!existsSync35(dataDir)) {
30500
+ mkdirSync11(dataDir, { recursive: true });
30501
+ }
30502
+ writeFileSync17(testFile, "test");
30503
+ unlinkSync4(testFile);
30504
+ } catch (e2) {
30505
+ issues.push({
30506
+ severity: "warning",
30507
+ title: "Persistence Issue",
30508
+ description: `Cannot write to OMO data directory: ${e2.message}`,
30509
+ fix: "Check permissions for your local share directory"
30510
+ });
30511
+ }
30512
+ try {
30513
+ if (DETERMINISTIC_TOOLS["complete_task"]) {
30514
+ const tool3 = DETERMINISTIC_TOOLS["complete_task"]();
30515
+ if (!tool3 || typeof tool3.execute !== "function") {
30516
+ issues.push({
30517
+ severity: "error",
30518
+ title: "Invalid Tool Implementation",
30519
+ description: "complete_task tool has missing or invalid execute function",
30520
+ fix: "Check src/runtime/tools/complete-task.ts"
30521
+ });
30522
+ }
30523
+ }
30524
+ } catch (e2) {
30525
+ issues.push({
30526
+ severity: "error",
30527
+ title: "Tool Initialization Error",
30528
+ description: `Error initializing complete_task for check: ${e2.message}`,
30529
+ fix: "Fix syntax or import errors in complete-task.ts"
30530
+ });
30531
+ }
30532
+ return {
30533
+ name: "Issue Resolution Workflow",
30534
+ message: issues.length === 0 ? "Workflow is intact and persistent" : "Workflow check found issues",
30535
+ status: issues.length === 0 ? "pass" : issues.some((i2) => i2.severity === "error") ? "fail" : "warn",
30536
+ issues
30537
+ };
30538
+ }
30539
+
30540
+ // src/cli/doctor/checks/tool-metadata.ts
30541
+ var import_picocolors19 = __toESM(require_picocolors(), 1);
30542
+ var CRITICAL_TOOLS = ["complete_task", "fs_safe", "verify_action", "git_safe", "report_issue_verification"];
30543
+ var checkToolMetadataContract = {
30544
+ id: "tool-metadata-contract",
30545
+ name: "Tool Metadata Contract",
30546
+ critical: true,
30547
+ check: async () => {
30548
+ const issues = [];
30549
+ let hasError = false;
30550
+ const mockContext = {
30551
+ sessionID: "doctor-test",
30552
+ callID: "call-test",
30553
+ metadata: (meta3) => {
30554
+ if (typeof meta3.success !== "boolean" || typeof meta3.verified !== "boolean") {
30555
+ hasError = true;
30556
+ issues.push({
30557
+ severity: "error",
30558
+ title: "Malformed Metadata",
30559
+ description: `Tool did not return boolean 'success' and 'verified' fields in context.metadata(). Found: ${JSON.stringify(meta3)}`,
30560
+ fix: "Wrap the tool with withToolContract."
30561
+ });
30562
+ }
30563
+ }
30564
+ };
30565
+ for (const toolName of CRITICAL_TOOLS) {
30566
+ try {
30567
+ const toolSpec = getToolFromRegistry(toolName);
30568
+ if (!toolSpec) {
30569
+ issues.push({
30570
+ severity: "warning",
30571
+ title: "Unregistered Tool",
30572
+ description: `Critical tool '${toolName}' is not registered.`,
30573
+ fix: "Check src/runtime/tools/registry.ts."
30574
+ });
30575
+ continue;
30576
+ }
30577
+ await toolSpec.execute({ invalid_arg: "should_fail" }, mockContext);
30578
+ } catch (err) {
30579
+ issues.push({
30580
+ severity: "error",
30581
+ title: "Unhandled Tool Exception",
30582
+ description: `Tool '${toolName}' threw an unhandled exception instead of returning a safe failure result. Error: ${err.message}`,
30583
+ fix: "Wrap the tool's execute block with withToolContract to ensure safe fallbacks."
30584
+ });
30585
+ hasError = true;
30586
+ }
30587
+ }
30588
+ if (hasError) {
30589
+ return {
30590
+ name: "Tool Metadata Contract",
30591
+ status: "fail",
30592
+ message: import_picocolors19.default.red("Critical tools are violating the metadata contract on error paths."),
30593
+ issues
30594
+ };
30595
+ }
30596
+ return {
30597
+ name: "Tool Metadata Contract",
30598
+ status: "pass",
30599
+ message: import_picocolors19.default.green("All critical tools conform to the structured metadata contract."),
30600
+ issues
30601
+ };
30602
+ }
30603
+ };
30604
+
30605
+ // src/cli/doctor/checks/run-state-watchdog.ts
30606
+ var import_picocolors20 = __toESM(require_picocolors(), 1);
30607
+
30608
+ // src/features/run-state-watchdog/manager.ts
30609
+ init_logger();
30610
+
30611
+ class RunStateWatchdogManager {
30612
+ client;
30613
+ activeSessions = new Map;
30614
+ pollingIntervalMs;
30615
+ stallThresholdMs;
30616
+ timer = null;
30617
+ constructor(client3, opts) {
30618
+ this.client = client3;
30619
+ this.pollingIntervalMs = opts?.pollingIntervalMs ?? 5000;
30620
+ this.stallThresholdMs = opts?.stallThresholdMs ?? 15000;
30621
+ }
30622
+ start() {
30623
+ if (this.timer)
30624
+ return;
30625
+ this.timer = setInterval(() => this.checkStalledRuns(), this.pollingIntervalMs);
30626
+ }
30627
+ stop() {
30628
+ if (this.timer) {
30629
+ clearInterval(this.timer);
30630
+ this.timer = null;
30631
+ }
30632
+ }
30633
+ updateState(sessionID, state3) {
30634
+ const ctx = this.getOrCreate(sessionID);
30635
+ ctx.currentState = state3;
30636
+ ctx.lastActivityAt = Date.now();
30637
+ }
30638
+ recordActivity(sessionID, type2) {
30639
+ const ctx = this.getOrCreate(sessionID);
30640
+ const now = Date.now();
30641
+ ctx.lastActivityAt = now;
30642
+ if (type2 === "text") {
30643
+ ctx.lastTextFragmentAt = now;
30644
+ } else if (type2 === "tool") {
30645
+ ctx.lastToolCallAt = now;
30646
+ }
30647
+ }
30648
+ updateTodos(sessionID, count) {
30649
+ const ctx = this.getOrCreate(sessionID);
30650
+ ctx.openTodos = count;
30651
+ }
30652
+ getContext(sessionID) {
30653
+ return this.activeSessions.get(sessionID);
30654
+ }
30655
+ getOrCreate(sessionID) {
30656
+ let ctx = this.activeSessions.get(sessionID);
30657
+ if (!ctx) {
30658
+ ctx = {
30659
+ sessionID,
30660
+ currentState: "idle",
30661
+ lastActivityAt: Date.now(),
30662
+ lastTextFragmentAt: Date.now(),
30663
+ lastToolCallAt: Date.now(),
30664
+ openTodos: 0
30665
+ };
30666
+ this.activeSessions.set(sessionID, ctx);
30667
+ }
30668
+ return ctx;
30669
+ }
30670
+ async checkStalledRuns() {
30671
+ const now = Date.now();
30672
+ for (const [sessionID, ctx] of this.activeSessions.entries()) {
30673
+ if (ctx.currentState !== "running" && ctx.currentState !== "waiting")
30674
+ continue;
30675
+ const timeSinceLastActivity = now - ctx.lastActivityAt;
30676
+ const timeSinceText = now - ctx.lastTextFragmentAt;
30677
+ const timeSinceTool = now - ctx.lastToolCallAt;
30678
+ if (timeSinceText > this.stallThresholdMs && timeSinceTool > this.stallThresholdMs) {
30679
+ log(`[RunStateWatchdog] Detected stalled run for session ${sessionID}.`, {
30680
+ timeSinceText,
30681
+ timeSinceTool,
30682
+ openTodos: ctx.openTodos
30683
+ });
30684
+ log(`[RunStateWatchdog] TERMINATING stalled session ${sessionID}.`);
30685
+ this.client.session.abort({
30686
+ path: { id: sessionID }
30687
+ }).catch((err) => {
30688
+ log(`[RunStateWatchdog] Failed to abort stalled session ${sessionID}`, { error: String(err) });
30689
+ });
30690
+ const tuiClient = this.client;
30691
+ if (tuiClient.tui?.showToast) {
30692
+ tuiClient.tui.showToast({
30693
+ body: {
30694
+ title: "Task Aborted",
30695
+ message: "Session terminated due to inactivity / stall detection.",
30696
+ variant: "error",
30697
+ duration: 5000
30698
+ }
30699
+ }).catch(() => {});
30700
+ }
30701
+ this.activeSessions.delete(sessionID);
30702
+ }
30703
+ }
30704
+ }
30705
+ }
30706
+
30707
+ // src/cli/doctor/checks/run-state-watchdog.ts
30708
+ var checkRunStateWatchdog = {
30709
+ id: "run-state-watchdog",
30710
+ name: "Run State Watchdog",
30711
+ critical: true,
30712
+ check: async () => {
30713
+ const issues = [];
30714
+ let hasError = false;
30715
+ let toastCalls = 0;
30716
+ const mockClient = {
30717
+ tui: {
30718
+ showToast: async () => {
30719
+ toastCalls++;
30720
+ }
30721
+ }
30722
+ };
30723
+ const manager = new RunStateWatchdogManager(mockClient, { pollingIntervalMs: 5, stallThresholdMs: 20 });
30724
+ manager.updateState("sess-doctor", "running");
30725
+ manager.recordActivity("sess-doctor", "text");
30726
+ await new Promise((r2) => setTimeout(r2, 25));
30727
+ await manager.checkStalledRuns();
30728
+ if (toastCalls === 0) {
30729
+ hasError = true;
30730
+ issues.push({
30731
+ severity: "error",
30732
+ title: "Watchdog Stall Detection Failed",
30733
+ description: "Watchdog did not emit a toast when a session with status 'running' had no recent text/tool activity.",
30734
+ fix: "Verify RunStateWatchdogManager polling thresholds and state storage."
30735
+ });
30736
+ }
30737
+ toastCalls = 0;
30738
+ manager.updateState("sess-doctor", "idle");
30739
+ await new Promise((r2) => setTimeout(r2, 25));
30740
+ await manager.checkStalledRuns();
30741
+ if (toastCalls > 0) {
30742
+ hasError = true;
30743
+ issues.push({
30744
+ severity: "warning",
30745
+ title: "Watchdog False Positive",
30746
+ description: "Watchdog emitted a stall toast for an 'idle' session.",
30747
+ fix: "Watchdog should ignore idle or non-active sessions."
30748
+ });
30749
+ }
30750
+ manager.stop();
30751
+ if (hasError) {
30752
+ return {
30753
+ name: "Run State Watchdog",
30754
+ status: "fail",
30755
+ message: import_picocolors20.default.red("Run State Watchdog failed coverage checks."),
30756
+ issues
30757
+ };
30758
+ }
30759
+ return {
30760
+ name: "Run State Watchdog",
30761
+ status: "pass",
30762
+ message: import_picocolors20.default.green("Watchdog detects and surfaces stalled runs correctly."),
30763
+ issues
30764
+ };
30765
+ }
30766
+ };
30767
+ // src/cli/doctor/checks/index.ts
30768
+ function getAllCheckDefinitions() {
30769
+ return [
30770
+ {
30771
+ id: CHECK_IDS.SYSTEM,
30772
+ name: CHECK_NAMES[CHECK_IDS.SYSTEM],
30773
+ check: checkSystem,
30774
+ critical: true
30775
+ },
30776
+ {
30777
+ id: CHECK_IDS.CONFIG,
30778
+ name: CHECK_NAMES[CHECK_IDS.CONFIG],
30779
+ check: checkConfig
30780
+ },
28545
30781
  {
28546
30782
  id: CHECK_IDS.TOOLS,
28547
30783
  name: CHECK_NAMES[CHECK_IDS.TOOLS],
@@ -28561,15 +30797,50 @@ function getAllCheckDefinitions() {
28561
30797
  id: CHECK_IDS.DEFAULT_CONFIG,
28562
30798
  name: CHECK_NAMES[CHECK_IDS.DEFAULT_CONFIG],
28563
30799
  check: checkDefaultConfig
30800
+ },
30801
+ {
30802
+ id: CHECK_IDS.PLAN_COMPILER,
30803
+ name: CHECK_NAMES[CHECK_IDS.PLAN_COMPILER],
30804
+ check: checkPlanCompiler
30805
+ },
30806
+ {
30807
+ id: CHECK_IDS.PROGRESS,
30808
+ name: CHECK_NAMES[CHECK_IDS.PROGRESS],
30809
+ check: checkProgress
30810
+ },
30811
+ {
30812
+ id: CHECK_IDS.EDIT_ATOMICITY,
30813
+ name: CHECK_NAMES[CHECK_IDS.EDIT_ATOMICITY],
30814
+ check: checkEditAtomicity.check
30815
+ },
30816
+ {
30817
+ id: "issue-resolution",
30818
+ name: "Issue Resolution Workflow",
30819
+ check: checkIssueResolutionWorkflow
30820
+ },
30821
+ {
30822
+ id: checkToolMetadataContract.id,
30823
+ name: checkToolMetadataContract.name,
30824
+ check: checkToolMetadataContract.check
30825
+ },
30826
+ {
30827
+ id: checkRunStateWatchdog.id,
30828
+ name: checkRunStateWatchdog.name,
30829
+ check: checkRunStateWatchdog.check
30830
+ },
30831
+ {
30832
+ id: "TOOL_CONTRACT",
30833
+ name: "Tool Contract Compliance",
30834
+ check: checkToolContract
28564
30835
  }
28565
30836
  ];
28566
30837
  }
28567
30838
 
28568
30839
  // src/cli/doctor/format-default.ts
28569
- var import_picocolors20 = __toESM(require_picocolors(), 1);
30840
+ var import_picocolors22 = __toESM(require_picocolors(), 1);
28570
30841
 
28571
30842
  // src/cli/doctor/format-shared.ts
28572
- var import_picocolors19 = __toESM(require_picocolors(), 1);
30843
+ var import_picocolors21 = __toESM(require_picocolors(), 1);
28573
30844
  function formatStatusSymbol(status) {
28574
30845
  const colorFn = STATUS_COLORS[status];
28575
30846
  switch (status) {
@@ -28584,23 +30855,23 @@ function formatStatusSymbol(status) {
28584
30855
  }
28585
30856
  }
28586
30857
  function formatStatusMark(available) {
28587
- return available ? import_picocolors19.default.green(SYMBOLS3.check) : import_picocolors19.default.red(SYMBOLS3.cross);
30858
+ return available ? import_picocolors21.default.green(SYMBOLS3.check) : import_picocolors21.default.red(SYMBOLS3.cross);
28588
30859
  }
28589
30860
  function formatHeader() {
28590
30861
  return `
28591
- ${import_picocolors19.default.bgMagenta(import_picocolors19.default.white(" oMoMoMoMo Doctor "))}
30862
+ ${import_picocolors21.default.bgMagenta(import_picocolors21.default.white(" oMoMoMoMo Doctor "))}
28592
30863
  `;
28593
30864
  }
28594
30865
  function formatIssue(issue2, index) {
28595
30866
  const lines = [];
28596
- const severityColor = issue2.severity === "error" ? import_picocolors19.default.red : import_picocolors19.default.yellow;
30867
+ const severityColor = issue2.severity === "error" ? import_picocolors21.default.red : import_picocolors21.default.yellow;
28597
30868
  lines.push(`${index}. ${severityColor(issue2.title)}`);
28598
- lines.push(` ${import_picocolors19.default.dim(issue2.description)}`);
30869
+ lines.push(` ${import_picocolors21.default.dim(issue2.description)}`);
28599
30870
  if (issue2.fix) {
28600
- lines.push(` ${import_picocolors19.default.cyan("Fix:")} ${import_picocolors19.default.dim(issue2.fix)}`);
30871
+ lines.push(` ${import_picocolors21.default.cyan("Fix:")} ${import_picocolors21.default.dim(issue2.fix)}`);
28601
30872
  }
28602
30873
  if (issue2.affects && issue2.affects.length > 0) {
28603
- lines.push(` ${import_picocolors19.default.cyan("Affects:")} ${import_picocolors19.default.dim(issue2.affects.join(", "))}`);
30874
+ lines.push(` ${import_picocolors21.default.cyan("Affects:")} ${import_picocolors21.default.dim(issue2.affects.join(", "))}`);
28604
30875
  }
28605
30876
  return lines.join(`
28606
30877
  `);
@@ -28614,12 +30885,12 @@ function formatDefault(result) {
28614
30885
  if (allIssues.length === 0) {
28615
30886
  const opencodeVer = result.systemInfo.opencodeVersion ?? "unknown";
28616
30887
  const pluginVer = result.systemInfo.pluginVersion ?? "unknown";
28617
- lines.push(` ${import_picocolors20.default.green(SYMBOLS3.check)} ${import_picocolors20.default.green(`System OK (opencode ${opencodeVer} \xB7 oh-my-opencode ${pluginVer})`)}`);
30888
+ lines.push(` ${import_picocolors22.default.green(SYMBOLS3.check)} ${import_picocolors22.default.green(`System OK (opencode ${opencodeVer} \xB7 oh-my-opencode ${pluginVer})`)}`);
28618
30889
  } else {
28619
30890
  const issueCount = allIssues.filter((i2) => i2.severity === "error").length;
28620
30891
  const warnCount = allIssues.filter((i2) => i2.severity === "warning").length;
28621
30892
  const totalStr = `${issueCount + warnCount} ${issueCount + warnCount === 1 ? "issue" : "issues"}`;
28622
- lines.push(` ${import_picocolors20.default.yellow(SYMBOLS3.warn)} ${totalStr} found:
30893
+ lines.push(` ${import_picocolors22.default.yellow(SYMBOLS3.warn)} ${totalStr} found:
28623
30894
  `);
28624
30895
  allIssues.forEach((issue2, index) => {
28625
30896
  lines.push(formatIssue(issue2, index + 1));
@@ -28631,7 +30902,7 @@ function formatDefault(result) {
28631
30902
  }
28632
30903
 
28633
30904
  // src/cli/doctor/format-status.ts
28634
- var import_picocolors21 = __toESM(require_picocolors(), 1);
30905
+ var import_picocolors23 = __toESM(require_picocolors(), 1);
28635
30906
  function formatStatus(result) {
28636
30907
  const lines = [];
28637
30908
  lines.push(formatHeader());
@@ -28642,7 +30913,7 @@ function formatStatus(result) {
28642
30913
  const bunVer = systemInfo.bunVersion ?? "unknown";
28643
30914
  lines.push(` ${padding}System ${opencodeVer} \xB7 ${pluginVer} \xB7 Bun ${bunVer}`);
28644
30915
  const configPath = systemInfo.configPath ?? "unknown";
28645
- const configStatus = systemInfo.configValid ? import_picocolors21.default.green("(valid)") : import_picocolors21.default.red("(invalid)");
30916
+ const configStatus = systemInfo.configValid ? import_picocolors23.default.green("(valid)") : import_picocolors23.default.red("(invalid)");
28646
30917
  lines.push(` ${padding}Config ${configPath} ${configStatus}`);
28647
30918
  const lspText = `LSP ${tools.lspInstalled}/${tools.lspTotal}`;
28648
30919
  const astGrepMark = formatStatusMark(tools.astGrepCli);
@@ -28654,18 +30925,40 @@ function formatStatus(result) {
28654
30925
  const builtinText = builtinCount > 0 ? tools.mcpBuiltin.join(" \xB7 ") : "none";
28655
30926
  const userText = userCount > 0 ? `+ ${userCount} user` : "";
28656
30927
  lines.push(` ${padding}MCPs ${builtinText} ${userText}`);
30928
+ if (result.activeTasks && result.activeTasks.length > 0) {
30929
+ const activeTasks = result.activeTasks;
30930
+ lines.push("");
30931
+ lines.push(` ${padding}Active Tasks (${activeTasks.length}):`);
30932
+ for (const task of activeTasks) {
30933
+ const { agent, description, progress } = task;
30934
+ const bar = renderProgressBar(progress?.percent);
30935
+ const phase = progress?.phase ? ` [${progress.phase}]` : "";
30936
+ const message = progress?.message ? ` (${progress.message})` : "";
30937
+ lines.push(` ${import_picocolors23.default.blue("\xB7")} ${import_picocolors23.default.bold(agent)}: ${description}${phase}${message}`);
30938
+ lines.push(` ${bar}`);
30939
+ }
30940
+ }
28657
30941
  return lines.join(`
28658
30942
  `);
28659
30943
  }
30944
+ function renderProgressBar(percent) {
30945
+ if (percent === undefined)
30946
+ return import_picocolors23.default.dim("[\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591]");
30947
+ const totalBlocks = 10;
30948
+ const filledBlocks = Math.round(percent / 100 * totalBlocks);
30949
+ const emptyBlocks = totalBlocks - filledBlocks;
30950
+ const bar = import_picocolors23.default.cyan("\u2588".repeat(filledBlocks)) + import_picocolors23.default.dim("\u2591".repeat(emptyBlocks));
30951
+ return `[${bar}] ${import_picocolors23.default.bold(percent + "%")}`;
30952
+ }
28660
30953
 
28661
30954
  // src/cli/doctor/format-verbose.ts
28662
- var import_picocolors22 = __toESM(require_picocolors(), 1);
30955
+ var import_picocolors24 = __toESM(require_picocolors(), 1);
28663
30956
  function formatVerbose(result) {
28664
30957
  const lines = [];
28665
30958
  lines.push(formatHeader());
28666
30959
  const { systemInfo, tools, results, summary } = result;
28667
- lines.push(`${import_picocolors22.default.bold("System Information")}`);
28668
- lines.push(`${import_picocolors22.default.dim("\u2500".repeat(40))}`);
30960
+ lines.push(`${import_picocolors24.default.bold("System Information")}`);
30961
+ lines.push(`${import_picocolors24.default.dim("\u2500".repeat(40))}`);
28669
30962
  lines.push(` ${formatStatusSymbol("pass")} opencode ${systemInfo.opencodeVersion ?? "unknown"}`);
28670
30963
  lines.push(` ${formatStatusSymbol("pass")} oh-my-opencode ${systemInfo.pluginVersion ?? "unknown"}`);
28671
30964
  if (systemInfo.loadedVersion) {
@@ -28676,33 +30969,33 @@ function formatVerbose(result) {
28676
30969
  }
28677
30970
  lines.push(` ${formatStatusSymbol("pass")} path ${systemInfo.opencodePath ?? "unknown"}`);
28678
30971
  if (systemInfo.isLocalDev) {
28679
- lines.push(` ${import_picocolors22.default.yellow("*")} ${import_picocolors22.default.dim("(local development mode)")}`);
30972
+ lines.push(` ${import_picocolors24.default.yellow("*")} ${import_picocolors24.default.dim("(local development mode)")}`);
28680
30973
  }
28681
30974
  lines.push("");
28682
- lines.push(`${import_picocolors22.default.bold("Configuration")}`);
28683
- lines.push(`${import_picocolors22.default.dim("\u2500".repeat(40))}`);
28684
- const configStatus = systemInfo.configValid ? import_picocolors22.default.green("valid") : import_picocolors22.default.red("invalid");
30975
+ lines.push(`${import_picocolors24.default.bold("Configuration")}`);
30976
+ lines.push(`${import_picocolors24.default.dim("\u2500".repeat(40))}`);
30977
+ const configStatus = systemInfo.configValid ? import_picocolors24.default.green("valid") : import_picocolors24.default.red("invalid");
28685
30978
  lines.push(` ${formatStatusSymbol(systemInfo.configValid ? "pass" : "fail")} ${systemInfo.configPath ?? "unknown"} (${configStatus})`);
28686
30979
  lines.push("");
28687
- lines.push(`${import_picocolors22.default.bold("Tools")}`);
28688
- lines.push(`${import_picocolors22.default.dim("\u2500".repeat(40))}`);
30980
+ lines.push(`${import_picocolors24.default.bold("Tools")}`);
30981
+ lines.push(`${import_picocolors24.default.dim("\u2500".repeat(40))}`);
28689
30982
  lines.push(` ${formatStatusSymbol("pass")} LSP ${tools.lspInstalled}/${tools.lspTotal} installed`);
28690
30983
  lines.push(` ${formatStatusSymbol(tools.astGrepCli ? "pass" : "fail")} ast-grep CLI ${tools.astGrepCli ? "installed" : "not found"}`);
28691
30984
  lines.push(` ${formatStatusSymbol(tools.astGrepNapi ? "pass" : "fail")} ast-grep napi ${tools.astGrepNapi ? "installed" : "not found"}`);
28692
30985
  lines.push(` ${formatStatusSymbol(tools.commentChecker ? "pass" : "fail")} comment-checker ${tools.commentChecker ? "installed" : "not found"}`);
28693
30986
  lines.push(` ${formatStatusSymbol(tools.ghCli.installed && tools.ghCli.authenticated ? "pass" : "fail")} gh CLI ${tools.ghCli.installed ? "installed" : "not found"}${tools.ghCli.authenticated && tools.ghCli.username ? ` (${tools.ghCli.username})` : ""}`);
28694
30987
  lines.push("");
28695
- lines.push(`${import_picocolors22.default.bold("MCPs")}`);
28696
- lines.push(`${import_picocolors22.default.dim("\u2500".repeat(40))}`);
30988
+ lines.push(`${import_picocolors24.default.bold("MCPs")}`);
30989
+ lines.push(`${import_picocolors24.default.dim("\u2500".repeat(40))}`);
28697
30990
  if (tools.mcpBuiltin.length === 0) {
28698
- lines.push(` ${import_picocolors22.default.dim("No built-in MCPs")}`);
30991
+ lines.push(` ${import_picocolors24.default.dim("No built-in MCPs")}`);
28699
30992
  } else {
28700
30993
  for (const mcp of tools.mcpBuiltin) {
28701
30994
  lines.push(` ${formatStatusSymbol("pass")} ${mcp}`);
28702
30995
  }
28703
30996
  }
28704
30997
  if (tools.mcpUser.length > 0) {
28705
- lines.push(` ${import_picocolors22.default.cyan("+")} ${tools.mcpUser.length} user MCP(s):`);
30998
+ lines.push(` ${import_picocolors24.default.cyan("+")} ${tools.mcpUser.length} user MCP(s):`);
28706
30999
  for (const mcp of tools.mcpUser) {
28707
31000
  lines.push(` ${formatStatusSymbol("pass")} ${mcp}`);
28708
31001
  }
@@ -28710,20 +31003,20 @@ function formatVerbose(result) {
28710
31003
  lines.push("");
28711
31004
  const allIssues = results.flatMap((r2) => r2.issues);
28712
31005
  if (allIssues.length > 0) {
28713
- lines.push(`${import_picocolors22.default.bold("Issues")}`);
28714
- lines.push(`${import_picocolors22.default.dim("\u2500".repeat(40))}`);
31006
+ lines.push(`${import_picocolors24.default.bold("Issues")}`);
31007
+ lines.push(`${import_picocolors24.default.dim("\u2500".repeat(40))}`);
28715
31008
  allIssues.forEach((issue2, index) => {
28716
31009
  lines.push(formatIssue(issue2, index + 1));
28717
31010
  lines.push("");
28718
31011
  });
28719
31012
  }
28720
- lines.push(`${import_picocolors22.default.bold("Summary")}`);
28721
- lines.push(`${import_picocolors22.default.dim("\u2500".repeat(40))}`);
28722
- const passText = summary.passed > 0 ? import_picocolors22.default.green(`${summary.passed} passed`) : `${summary.passed} passed`;
28723
- const failText = summary.failed > 0 ? import_picocolors22.default.red(`${summary.failed} failed`) : `${summary.failed} failed`;
28724
- const warnText = summary.warnings > 0 ? import_picocolors22.default.yellow(`${summary.warnings} warnings`) : `${summary.warnings} warnings`;
31013
+ lines.push(`${import_picocolors24.default.bold("Summary")}`);
31014
+ lines.push(`${import_picocolors24.default.dim("\u2500".repeat(40))}`);
31015
+ const passText = summary.passed > 0 ? import_picocolors24.default.green(`${summary.passed} passed`) : `${summary.passed} passed`;
31016
+ const failText = summary.failed > 0 ? import_picocolors24.default.red(`${summary.failed} failed`) : `${summary.failed} failed`;
31017
+ const warnText = summary.warnings > 0 ? import_picocolors24.default.yellow(`${summary.warnings} warnings`) : `${summary.warnings} warnings`;
28725
31018
  lines.push(` ${passText}, ${failText}, ${warnText}`);
28726
- lines.push(` ${import_picocolors22.default.dim(`Total: ${summary.total} checks in ${summary.duration}ms`)}`);
31019
+ lines.push(` ${import_picocolors24.default.dim(`Total: ${summary.total} checks in ${summary.duration}ms`)}`);
28727
31020
  return lines.join(`
28728
31021
  `);
28729
31022
  }
@@ -28776,10 +31069,11 @@ function determineExitCode(results) {
28776
31069
  async function runDoctor(options) {
28777
31070
  const start = performance.now();
28778
31071
  const allChecks = getAllCheckDefinitions();
28779
- const [results, systemInfo, tools] = await Promise.all([
31072
+ const [results, systemInfo, tools, activeTasks] = await Promise.all([
28780
31073
  Promise.all(allChecks.map(runCheck)),
28781
31074
  gatherSystemInfo(),
28782
- gatherToolsSummary()
31075
+ gatherToolsSummary(),
31076
+ Promise.resolve(readActiveTasks())
28783
31077
  ]);
28784
31078
  const duration3 = performance.now() - start;
28785
31079
  const summary = calculateSummary(results, duration3);
@@ -28789,6 +31083,7 @@ async function runDoctor(options) {
28789
31083
  systemInfo,
28790
31084
  tools,
28791
31085
  summary,
31086
+ activeTasks,
28792
31087
  exitCode
28793
31088
  };
28794
31089
  if (options.json) {
@@ -28807,11 +31102,11 @@ async function doctor(options = { mode: "default" }) {
28807
31102
 
28808
31103
  // src/features/mcp-oauth/storage.ts
28809
31104
  init_shared();
28810
- import { chmodSync, existsSync as existsSync29, mkdirSync as mkdirSync7, readFileSync as readFileSync29, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "fs";
28811
- import { dirname as dirname10, join as join28 } from "path";
31105
+ import { chmodSync, existsSync as existsSync36, mkdirSync as mkdirSync12, readFileSync as readFileSync34, unlinkSync as unlinkSync5, writeFileSync as writeFileSync18 } from "fs";
31106
+ import { dirname as dirname11, join as join33 } from "path";
28812
31107
  var STORAGE_FILE_NAME = "mcp-oauth.json";
28813
31108
  function getMcpOauthStoragePath() {
28814
- return join28(getOpenCodeConfigDir({ binary: "opencode" }), STORAGE_FILE_NAME);
31109
+ return join33(getOpenCodeConfigDir({ binary: "opencode" }), STORAGE_FILE_NAME);
28815
31110
  }
28816
31111
  function normalizeHost(serverHost) {
28817
31112
  let host = serverHost.trim();
@@ -28848,11 +31143,11 @@ function buildKey(serverHost, resource) {
28848
31143
  }
28849
31144
  function readStore() {
28850
31145
  const filePath = getMcpOauthStoragePath();
28851
- if (!existsSync29(filePath)) {
31146
+ if (!existsSync36(filePath)) {
28852
31147
  return null;
28853
31148
  }
28854
31149
  try {
28855
- const content = readFileSync29(filePath, "utf-8");
31150
+ const content = readFileSync34(filePath, "utf-8");
28856
31151
  return JSON.parse(content);
28857
31152
  } catch {
28858
31153
  return null;
@@ -28861,11 +31156,11 @@ function readStore() {
28861
31156
  function writeStore(store2) {
28862
31157
  const filePath = getMcpOauthStoragePath();
28863
31158
  try {
28864
- const dir = dirname10(filePath);
28865
- if (!existsSync29(dir)) {
28866
- mkdirSync7(dir, { recursive: true });
31159
+ const dir = dirname11(filePath);
31160
+ if (!existsSync36(dir)) {
31161
+ mkdirSync12(dir, { recursive: true });
28867
31162
  }
28868
- writeFileSync11(filePath, JSON.stringify(store2, null, 2), { encoding: "utf-8", mode: 384 });
31163
+ writeFileSync18(filePath, JSON.stringify(store2, null, 2), { encoding: "utf-8", mode: 384 });
28869
31164
  chmodSync(filePath, 384);
28870
31165
  return true;
28871
31166
  } catch {
@@ -28897,8 +31192,8 @@ function deleteToken(serverHost, resource) {
28897
31192
  if (Object.keys(store2).length === 0) {
28898
31193
  try {
28899
31194
  const filePath = getMcpOauthStoragePath();
28900
- if (existsSync29(filePath)) {
28901
- unlinkSync2(filePath);
31195
+ if (existsSync36(filePath)) {
31196
+ unlinkSync5(filePath);
28902
31197
  }
28903
31198
  return true;
28904
31199
  } catch {
@@ -29077,7 +31372,7 @@ async function findAvailablePort2(startPort = DEFAULT_PORT) {
29077
31372
  }
29078
31373
 
29079
31374
  // src/features/mcp-oauth/oauth-authorization-flow.ts
29080
- import { spawn as spawn2 } from "child_process";
31375
+ import { spawn as spawn5 } from "child_process";
29081
31376
  import { createHash, randomBytes as randomBytes2 } from "crypto";
29082
31377
  import { createServer } from "http";
29083
31378
  function generateCodeVerifier() {
@@ -29104,13 +31399,13 @@ function buildAuthorizationUrl(authorizationEndpoint, options) {
29104
31399
  }
29105
31400
  var CALLBACK_TIMEOUT_MS = 5 * 60 * 1000;
29106
31401
  function startCallbackServer(port) {
29107
- return new Promise((resolve2, reject) => {
31402
+ return new Promise((resolve3, reject) => {
29108
31403
  let timeoutId;
29109
31404
  const server2 = createServer((request, response) => {
29110
31405
  clearTimeout(timeoutId);
29111
31406
  const requestUrl = new URL(request.url ?? "/", `http://localhost:${port}`);
29112
31407
  const code = requestUrl.searchParams.get("code");
29113
- const state = requestUrl.searchParams.get("state");
31408
+ const state3 = requestUrl.searchParams.get("state");
29114
31409
  const error48 = requestUrl.searchParams.get("error");
29115
31410
  if (error48) {
29116
31411
  const errorDescription = requestUrl.searchParams.get("error_description") ?? error48;
@@ -29120,7 +31415,7 @@ function startCallbackServer(port) {
29120
31415
  reject(new Error(`OAuth authorization error: ${errorDescription}`));
29121
31416
  return;
29122
31417
  }
29123
- if (!code || !state) {
31418
+ if (!code || !state3) {
29124
31419
  response.writeHead(400, { "content-type": "text/html" });
29125
31420
  response.end("<html><body><h1>Missing code or state</h1></body></html>");
29126
31421
  server2.close();
@@ -29130,7 +31425,7 @@ function startCallbackServer(port) {
29130
31425
  response.writeHead(200, { "content-type": "text/html" });
29131
31426
  response.end("<html><body><h1>Authorization successful. You can close this tab.</h1></body></html>");
29132
31427
  server2.close();
29133
- resolve2({ code, state });
31428
+ resolve3({ code, state: state3 });
29134
31429
  });
29135
31430
  timeoutId = setTimeout(() => {
29136
31431
  server2.close();
@@ -29158,7 +31453,7 @@ function openBrowser(url2) {
29158
31453
  args = [url2];
29159
31454
  }
29160
31455
  try {
29161
- const child = spawn2(command, args, { stdio: "ignore", detached: true });
31456
+ const child = spawn5(command, args, { stdio: "ignore", detached: true });
29162
31457
  child.on("error", () => {});
29163
31458
  child.unref();
29164
31459
  } catch {}
@@ -29166,19 +31461,19 @@ function openBrowser(url2) {
29166
31461
  async function runAuthorizationCodeRedirect(options) {
29167
31462
  const verifier = generateCodeVerifier();
29168
31463
  const challenge = generateCodeChallenge(verifier);
29169
- const state = randomBytes2(16).toString("hex");
31464
+ const state3 = randomBytes2(16).toString("hex");
29170
31465
  const authorizationUrl = buildAuthorizationUrl(options.authorizationEndpoint, {
29171
31466
  clientId: options.clientId,
29172
31467
  redirectUri: options.redirectUri,
29173
31468
  codeChallenge: challenge,
29174
- state,
31469
+ state: state3,
29175
31470
  scopes: options.scopes,
29176
31471
  resource: options.resource
29177
31472
  });
29178
31473
  const callbackPromise = startCallbackServer(options.callbackPort);
29179
31474
  openBrowser(authorizationUrl);
29180
31475
  const result = await callbackPromise;
29181
- if (result.state !== state) {
31476
+ if (result.state !== state3) {
29182
31477
  throw new Error("OAuth state mismatch");
29183
31478
  }
29184
31479
  return { code: result.code, verifier };
@@ -29428,21 +31723,54 @@ function createMcpOAuthCommand() {
29428
31723
  }
29429
31724
 
29430
31725
  // src/cli/master-login/index.ts
29431
- var import_picocolors23 = __toESM(require_picocolors(), 1);
29432
- import { writeFileSync as writeFileSync12, mkdirSync as mkdirSync8, existsSync as existsSync30 } from "fs";
29433
- import { join as join29, dirname as dirname11 } from "path";
31726
+ var import_picocolors25 = __toESM(require_picocolors(), 1);
31727
+ import { writeFileSync as writeFileSync19, mkdirSync as mkdirSync13, existsSync as existsSync37 } from "fs";
31728
+ import { join as join34, dirname as dirname12 } from "path";
29434
31729
  import * as os5 from "os";
31730
+ import { createRequire as createRequire2 } from "module";
31731
+ async function resolvePlaywright() {
31732
+ try {
31733
+ return await import("playwright");
31734
+ } catch (e2) {}
31735
+ const binaryDir = dirname12(process.execPath);
31736
+ let currentDir = binaryDir;
31737
+ for (let i2 = 0;i2 < 6; i2++) {
31738
+ const potentialPath = join34(currentDir, "node_modules", "playwright");
31739
+ if (existsSync37(potentialPath)) {
31740
+ try {
31741
+ const require2 = createRequire2(join34(currentDir, "index.js"));
31742
+ return require2("playwright");
31743
+ } catch (e2) {}
31744
+ }
31745
+ const parent = dirname12(currentDir);
31746
+ if (parent === currentDir)
31747
+ break;
31748
+ currentDir = parent;
31749
+ }
31750
+ throw new Error("Cannot find package 'playwright'. Please ensure it's installed via 'npm install -g @heidi-dang/oh-my-opencode'.");
31751
+ }
29435
31752
  async function masterLogin(options) {
29436
- const configPath = join29(os5.homedir(), ".ygka_config.json");
29437
- if (existsSync30(configPath) && !options.force) {
29438
- console.log(`${import_picocolors23.default.yellow("[exists]")} ${import_picocolors23.default.dim(configPath)}`);
29439
- console.log(import_picocolors23.default.dim("YGKA config already exists. Use --force to overwrite."));
31753
+ const configPath = join34(os5.homedir(), ".ygka_config.json");
31754
+ if (existsSync37(configPath) && !options.force) {
31755
+ console.log(`${import_picocolors25.default.yellow("[exists]")} ${import_picocolors25.default.dim(configPath)}`);
31756
+ console.log(import_picocolors25.default.dim("YGKA config already exists. Use --force to overwrite."));
29440
31757
  return 0;
29441
31758
  }
29442
- console.log(import_picocolors23.default.cyan("\uD83D\uDE80 Launching browser for ChatGPT login..."));
29443
- console.log(import_picocolors23.default.dim("Please log in to chat.openai.com in the window that appears."));
29444
- console.log(import_picocolors23.default.dim("Once logged in, return here. I will automatically detect the session."));
29445
- const { chromium } = await import("playwright");
31759
+ let playwrightModule;
31760
+ try {
31761
+ playwrightModule = await resolvePlaywright();
31762
+ } catch (err) {
31763
+ console.error(import_picocolors25.default.red(`
31764
+ [error] Playwright not found.`));
31765
+ console.log(import_picocolors25.default.dim("This command requires Playwright to automate the login process."));
31766
+ console.log(import_picocolors25.default.dim("Please try running: npm install -g @heidi-dang/oh-my-opencode"));
31767
+ console.log(import_picocolors25.default.dim(`Diagnostic: looked for 'playwright' in ${dirname12(process.execPath)} and parents.`));
31768
+ return 1;
31769
+ }
31770
+ console.log(import_picocolors25.default.cyan("\uD83D\uDE80 Launching browser for ChatGPT login..."));
31771
+ console.log(import_picocolors25.default.dim("Please log in to chat.openai.com in the window that appears."));
31772
+ console.log(import_picocolors25.default.dim("Once logged in, return here. I will automatically detect the session."));
31773
+ const { chromium } = playwrightModule;
29446
31774
  const browser = await chromium.launch({ headless: false });
29447
31775
  const context = await browser.newContext();
29448
31776
  const page = await context.newPage();
@@ -29459,18 +31787,18 @@ async function masterLogin(options) {
29459
31787
  sessionToken = sessionCookie.value;
29460
31788
  break;
29461
31789
  }
29462
- process.stdout.write(`\r${import_picocolors23.default.blue(spinner[spinnerIdx])} Waiting for login... `);
31790
+ process.stdout.write(`\r${import_picocolors25.default.blue(spinner[spinnerIdx])} Waiting for login... `);
29463
31791
  spinnerIdx = (spinnerIdx + 1) % spinner.length;
29464
- await new Promise((resolve2) => setTimeout(resolve2, 500));
31792
+ await new Promise((resolve3) => setTimeout(resolve3, 500));
29465
31793
  }
29466
31794
  process.stdout.write("\r");
29467
31795
  if (!sessionToken) {
29468
- console.error(import_picocolors23.default.red(`
31796
+ console.error(import_picocolors25.default.red(`
29469
31797
  [error] Login timed out or session token not found.`));
29470
31798
  await browser.close();
29471
31799
  return 1;
29472
31800
  }
29473
- console.log(import_picocolors23.default.green(`
31801
+ console.log(import_picocolors25.default.green(`
29474
31802
  [ok] Session token captured successfully!`));
29475
31803
  let accessToken;
29476
31804
  try {
@@ -29491,12 +31819,12 @@ async function masterLogin(options) {
29491
31819
  browser_name: "playwright-internal"
29492
31820
  };
29493
31821
  try {
29494
- mkdirSync8(dirname11(configPath), { recursive: true });
29495
- writeFileSync12(configPath, JSON.stringify(config2, null, 2), "utf-8");
29496
- console.log(`${import_picocolors23.default.green("[ok]")} Saved YGKA config to ${import_picocolors23.default.white(configPath)}`);
31822
+ mkdirSync13(dirname12(configPath), { recursive: true });
31823
+ writeFileSync19(configPath, JSON.stringify(config2, null, 2), "utf-8");
31824
+ console.log(`${import_picocolors25.default.green("[ok]")} Saved YGKA config to ${import_picocolors25.default.white(configPath)}`);
29497
31825
  return 0;
29498
31826
  } catch (err) {
29499
- console.error(import_picocolors23.default.red(`[error] Failed to write config: ${err instanceof Error ? err.message : String(err)}`));
31827
+ console.error(import_picocolors25.default.red(`[error] Failed to write config: ${err instanceof Error ? err.message : String(err)}`));
29500
31828
  return 1;
29501
31829
  }
29502
31830
  }