opencode-repos 0.2.0 → 0.3.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 (475) hide show
  1. package/AGENTS.md +180 -0
  2. package/README.md +103 -3
  3. package/TODO.md +3 -0
  4. package/index.ts +1590 -158
  5. package/oh-my-opencode/.github/FUNDING.yml +15 -0
  6. package/oh-my-opencode/.github/ISSUE_TEMPLATE/bug_report.yml +129 -0
  7. package/oh-my-opencode/.github/ISSUE_TEMPLATE/config.yml +8 -0
  8. package/oh-my-opencode/.github/ISSUE_TEMPLATE/feature_request.yml +100 -0
  9. package/oh-my-opencode/.github/ISSUE_TEMPLATE/general.yml +83 -0
  10. package/oh-my-opencode/.github/assets/google.jpg +0 -0
  11. package/oh-my-opencode/.github/assets/hero.jpg +0 -0
  12. package/oh-my-opencode/.github/assets/indent.jpg +0 -0
  13. package/oh-my-opencode/.github/assets/microsoft.jpg +0 -0
  14. package/oh-my-opencode/.github/assets/omo.png +0 -0
  15. package/oh-my-opencode/.github/assets/orchestrator-atlas.png +0 -0
  16. package/oh-my-opencode/.github/assets/sisyphus.png +0 -0
  17. package/oh-my-opencode/.github/assets/sisyphuslabs.png +0 -0
  18. package/oh-my-opencode/.github/pull_request_template.md +34 -0
  19. package/oh-my-opencode/.github/workflows/ci.yml +138 -0
  20. package/oh-my-opencode/.github/workflows/cla.yml +41 -0
  21. package/oh-my-opencode/.github/workflows/lint-workflows.yml +22 -0
  22. package/oh-my-opencode/.github/workflows/publish.yml +165 -0
  23. package/oh-my-opencode/.github/workflows/sisyphus-agent.yml +500 -0
  24. package/oh-my-opencode/.opencode/background-tasks.json +27 -0
  25. package/oh-my-opencode/.opencode/command/get-unpublished-changes.md +84 -0
  26. package/oh-my-opencode/.opencode/command/omomomo.md +37 -0
  27. package/oh-my-opencode/.opencode/command/publish.md +257 -0
  28. package/oh-my-opencode/AGENTS.md +179 -0
  29. package/oh-my-opencode/CLA.md +58 -0
  30. package/oh-my-opencode/CONTRIBUTING.md +268 -0
  31. package/oh-my-opencode/LICENSE.md +82 -0
  32. package/oh-my-opencode/README.ja.md +370 -0
  33. package/oh-my-opencode/README.md +376 -0
  34. package/oh-my-opencode/README.zh-cn.md +380 -0
  35. package/oh-my-opencode/assets/oh-my-opencode.schema.json +2171 -0
  36. package/oh-my-opencode/bin/oh-my-opencode.js +80 -0
  37. package/oh-my-opencode/bin/platform.js +38 -0
  38. package/oh-my-opencode/bin/platform.test.ts +148 -0
  39. package/oh-my-opencode/bun.lock +314 -0
  40. package/oh-my-opencode/bunfig.toml +2 -0
  41. package/oh-my-opencode/docs/category-skill-guide.md +200 -0
  42. package/oh-my-opencode/docs/cli-guide.md +272 -0
  43. package/oh-my-opencode/docs/configurations.md +654 -0
  44. package/oh-my-opencode/docs/features.md +550 -0
  45. package/oh-my-opencode/docs/guide/installation.md +288 -0
  46. package/oh-my-opencode/docs/guide/overview.md +97 -0
  47. package/oh-my-opencode/docs/guide/understanding-orchestration-system.md +445 -0
  48. package/oh-my-opencode/docs/orchestration-guide.md +152 -0
  49. package/oh-my-opencode/docs/ultrawork-manifesto.md +197 -0
  50. package/oh-my-opencode/package.json +89 -0
  51. package/oh-my-opencode/packages/darwin-arm64/bin/.gitkeep +0 -0
  52. package/oh-my-opencode/packages/darwin-arm64/package.json +22 -0
  53. package/oh-my-opencode/packages/darwin-x64/bin/.gitkeep +0 -0
  54. package/oh-my-opencode/packages/darwin-x64/package.json +22 -0
  55. package/oh-my-opencode/packages/linux-arm64/bin/.gitkeep +0 -0
  56. package/oh-my-opencode/packages/linux-arm64/package.json +25 -0
  57. package/oh-my-opencode/packages/linux-arm64-musl/bin/.gitkeep +0 -0
  58. package/oh-my-opencode/packages/linux-arm64-musl/package.json +25 -0
  59. package/oh-my-opencode/packages/linux-x64/bin/.gitkeep +0 -0
  60. package/oh-my-opencode/packages/linux-x64/package.json +25 -0
  61. package/oh-my-opencode/packages/linux-x64-musl/bin/.gitkeep +0 -0
  62. package/oh-my-opencode/packages/linux-x64-musl/package.json +25 -0
  63. package/oh-my-opencode/packages/windows-x64/bin/.gitkeep +0 -0
  64. package/oh-my-opencode/packages/windows-x64/package.json +22 -0
  65. package/oh-my-opencode/postinstall.mjs +43 -0
  66. package/oh-my-opencode/script/build-binaries.ts +103 -0
  67. package/oh-my-opencode/script/build-schema.ts +28 -0
  68. package/oh-my-opencode/script/generate-changelog.ts +92 -0
  69. package/oh-my-opencode/script/publish.ts +344 -0
  70. package/oh-my-opencode/signatures/cla.json +676 -0
  71. package/oh-my-opencode/src/agents/AGENTS.md +67 -0
  72. package/oh-my-opencode/src/agents/atlas.ts +1383 -0
  73. package/oh-my-opencode/src/agents/dynamic-agent-prompt-builder.ts +400 -0
  74. package/oh-my-opencode/src/agents/explore.ts +122 -0
  75. package/oh-my-opencode/src/agents/index.ts +13 -0
  76. package/oh-my-opencode/src/agents/librarian.ts +326 -0
  77. package/oh-my-opencode/src/agents/metis.ts +315 -0
  78. package/oh-my-opencode/src/agents/momus.test.ts +57 -0
  79. package/oh-my-opencode/src/agents/momus.ts +444 -0
  80. package/oh-my-opencode/src/agents/multimodal-looker.ts +56 -0
  81. package/oh-my-opencode/src/agents/oracle.ts +122 -0
  82. package/oh-my-opencode/src/agents/prometheus-prompt.test.ts +22 -0
  83. package/oh-my-opencode/src/agents/prometheus-prompt.ts +1196 -0
  84. package/oh-my-opencode/src/agents/sisyphus-junior.test.ts +232 -0
  85. package/oh-my-opencode/src/agents/sisyphus-junior.ts +134 -0
  86. package/oh-my-opencode/src/agents/sisyphus.ts +633 -0
  87. package/oh-my-opencode/src/agents/types.ts +80 -0
  88. package/oh-my-opencode/src/agents/utils.test.ts +311 -0
  89. package/oh-my-opencode/src/agents/utils.ts +240 -0
  90. package/oh-my-opencode/src/cli/AGENTS.md +91 -0
  91. package/oh-my-opencode/src/cli/config-manager.test.ts +364 -0
  92. package/oh-my-opencode/src/cli/config-manager.ts +641 -0
  93. package/oh-my-opencode/src/cli/doctor/checks/auth.test.ts +114 -0
  94. package/oh-my-opencode/src/cli/doctor/checks/auth.ts +115 -0
  95. package/oh-my-opencode/src/cli/doctor/checks/config.test.ts +103 -0
  96. package/oh-my-opencode/src/cli/doctor/checks/config.ts +123 -0
  97. package/oh-my-opencode/src/cli/doctor/checks/dependencies.test.ts +152 -0
  98. package/oh-my-opencode/src/cli/doctor/checks/dependencies.ts +163 -0
  99. package/oh-my-opencode/src/cli/doctor/checks/gh.test.ts +151 -0
  100. package/oh-my-opencode/src/cli/doctor/checks/gh.ts +171 -0
  101. package/oh-my-opencode/src/cli/doctor/checks/index.ts +34 -0
  102. package/oh-my-opencode/src/cli/doctor/checks/lsp.test.ts +134 -0
  103. package/oh-my-opencode/src/cli/doctor/checks/lsp.ts +77 -0
  104. package/oh-my-opencode/src/cli/doctor/checks/mcp.test.ts +115 -0
  105. package/oh-my-opencode/src/cli/doctor/checks/mcp.ts +128 -0
  106. package/oh-my-opencode/src/cli/doctor/checks/opencode.test.ts +227 -0
  107. package/oh-my-opencode/src/cli/doctor/checks/opencode.ts +178 -0
  108. package/oh-my-opencode/src/cli/doctor/checks/plugin.test.ts +109 -0
  109. package/oh-my-opencode/src/cli/doctor/checks/plugin.ts +124 -0
  110. package/oh-my-opencode/src/cli/doctor/checks/version.test.ts +148 -0
  111. package/oh-my-opencode/src/cli/doctor/checks/version.ts +135 -0
  112. package/oh-my-opencode/src/cli/doctor/constants.ts +72 -0
  113. package/oh-my-opencode/src/cli/doctor/formatter.test.ts +218 -0
  114. package/oh-my-opencode/src/cli/doctor/formatter.ts +140 -0
  115. package/oh-my-opencode/src/cli/doctor/index.ts +11 -0
  116. package/oh-my-opencode/src/cli/doctor/runner.test.ts +153 -0
  117. package/oh-my-opencode/src/cli/doctor/runner.ts +132 -0
  118. package/oh-my-opencode/src/cli/doctor/types.ts +113 -0
  119. package/oh-my-opencode/src/cli/get-local-version/formatter.ts +66 -0
  120. package/oh-my-opencode/src/cli/get-local-version/index.ts +106 -0
  121. package/oh-my-opencode/src/cli/get-local-version/types.ts +14 -0
  122. package/oh-my-opencode/src/cli/index.ts +153 -0
  123. package/oh-my-opencode/src/cli/install.ts +523 -0
  124. package/oh-my-opencode/src/cli/model-fallback.ts +246 -0
  125. package/oh-my-opencode/src/cli/run/completion.test.ts +170 -0
  126. package/oh-my-opencode/src/cli/run/completion.ts +79 -0
  127. package/oh-my-opencode/src/cli/run/events.test.ts +155 -0
  128. package/oh-my-opencode/src/cli/run/events.ts +325 -0
  129. package/oh-my-opencode/src/cli/run/index.ts +2 -0
  130. package/oh-my-opencode/src/cli/run/runner.ts +159 -0
  131. package/oh-my-opencode/src/cli/run/types.ts +76 -0
  132. package/oh-my-opencode/src/cli/types.ts +40 -0
  133. package/oh-my-opencode/src/config/index.ts +26 -0
  134. package/oh-my-opencode/src/config/schema.test.ts +444 -0
  135. package/oh-my-opencode/src/config/schema.ts +339 -0
  136. package/oh-my-opencode/src/features/AGENTS.md +77 -0
  137. package/oh-my-opencode/src/features/background-agent/concurrency.test.ts +418 -0
  138. package/oh-my-opencode/src/features/background-agent/concurrency.ts +137 -0
  139. package/oh-my-opencode/src/features/background-agent/index.ts +3 -0
  140. package/oh-my-opencode/src/features/background-agent/manager.test.ts +1928 -0
  141. package/oh-my-opencode/src/features/background-agent/manager.ts +1335 -0
  142. package/oh-my-opencode/src/features/background-agent/types.ts +66 -0
  143. package/oh-my-opencode/src/features/boulder-state/constants.ts +13 -0
  144. package/oh-my-opencode/src/features/boulder-state/index.ts +3 -0
  145. package/oh-my-opencode/src/features/boulder-state/storage.test.ts +250 -0
  146. package/oh-my-opencode/src/features/boulder-state/storage.ts +150 -0
  147. package/oh-my-opencode/src/features/boulder-state/types.ts +26 -0
  148. package/oh-my-opencode/src/features/builtin-commands/commands.ts +89 -0
  149. package/oh-my-opencode/src/features/builtin-commands/index.ts +2 -0
  150. package/oh-my-opencode/src/features/builtin-commands/templates/init-deep.ts +300 -0
  151. package/oh-my-opencode/src/features/builtin-commands/templates/ralph-loop.ts +38 -0
  152. package/oh-my-opencode/src/features/builtin-commands/templates/refactor.ts +619 -0
  153. package/oh-my-opencode/src/features/builtin-commands/templates/start-work.ts +72 -0
  154. package/oh-my-opencode/src/features/builtin-commands/types.ts +9 -0
  155. package/oh-my-opencode/src/features/builtin-skills/frontend-ui-ux/SKILL.md +78 -0
  156. package/oh-my-opencode/src/features/builtin-skills/git-master/SKILL.md +1105 -0
  157. package/oh-my-opencode/src/features/builtin-skills/index.ts +2 -0
  158. package/oh-my-opencode/src/features/builtin-skills/skills.ts +1203 -0
  159. package/oh-my-opencode/src/features/builtin-skills/types.ts +16 -0
  160. package/oh-my-opencode/src/features/claude-code-agent-loader/index.ts +2 -0
  161. package/oh-my-opencode/src/features/claude-code-agent-loader/loader.ts +90 -0
  162. package/oh-my-opencode/src/features/claude-code-agent-loader/types.ts +17 -0
  163. package/oh-my-opencode/src/features/claude-code-command-loader/index.ts +2 -0
  164. package/oh-my-opencode/src/features/claude-code-command-loader/loader.ts +144 -0
  165. package/oh-my-opencode/src/features/claude-code-command-loader/types.ts +46 -0
  166. package/oh-my-opencode/src/features/claude-code-mcp-loader/env-expander.ts +27 -0
  167. package/oh-my-opencode/src/features/claude-code-mcp-loader/index.ts +11 -0
  168. package/oh-my-opencode/src/features/claude-code-mcp-loader/loader.test.ts +162 -0
  169. package/oh-my-opencode/src/features/claude-code-mcp-loader/loader.ts +113 -0
  170. package/oh-my-opencode/src/features/claude-code-mcp-loader/transformer.ts +53 -0
  171. package/oh-my-opencode/src/features/claude-code-mcp-loader/types.ts +42 -0
  172. package/oh-my-opencode/src/features/claude-code-plugin-loader/index.ts +3 -0
  173. package/oh-my-opencode/src/features/claude-code-plugin-loader/loader.ts +486 -0
  174. package/oh-my-opencode/src/features/claude-code-plugin-loader/types.ts +210 -0
  175. package/oh-my-opencode/src/features/claude-code-session-state/index.ts +1 -0
  176. package/oh-my-opencode/src/features/claude-code-session-state/state.test.ts +126 -0
  177. package/oh-my-opencode/src/features/claude-code-session-state/state.ts +37 -0
  178. package/oh-my-opencode/src/features/context-injector/collector.test.ts +330 -0
  179. package/oh-my-opencode/src/features/context-injector/collector.ts +85 -0
  180. package/oh-my-opencode/src/features/context-injector/index.ts +14 -0
  181. package/oh-my-opencode/src/features/context-injector/injector.test.ts +122 -0
  182. package/oh-my-opencode/src/features/context-injector/injector.ts +167 -0
  183. package/oh-my-opencode/src/features/context-injector/types.ts +91 -0
  184. package/oh-my-opencode/src/features/hook-message-injector/constants.ts +6 -0
  185. package/oh-my-opencode/src/features/hook-message-injector/index.ts +4 -0
  186. package/oh-my-opencode/src/features/hook-message-injector/injector.ts +195 -0
  187. package/oh-my-opencode/src/features/hook-message-injector/types.ts +47 -0
  188. package/oh-my-opencode/src/features/opencode-skill-loader/async-loader.test.ts +448 -0
  189. package/oh-my-opencode/src/features/opencode-skill-loader/async-loader.ts +180 -0
  190. package/oh-my-opencode/src/features/opencode-skill-loader/blocking.test.ts +210 -0
  191. package/oh-my-opencode/src/features/opencode-skill-loader/blocking.ts +62 -0
  192. package/oh-my-opencode/src/features/opencode-skill-loader/discover-worker.ts +59 -0
  193. package/oh-my-opencode/src/features/opencode-skill-loader/index.ts +4 -0
  194. package/oh-my-opencode/src/features/opencode-skill-loader/loader.test.ts +273 -0
  195. package/oh-my-opencode/src/features/opencode-skill-loader/loader.ts +259 -0
  196. package/oh-my-opencode/src/features/opencode-skill-loader/merger.ts +267 -0
  197. package/oh-my-opencode/src/features/opencode-skill-loader/skill-content.test.ts +267 -0
  198. package/oh-my-opencode/src/features/opencode-skill-loader/skill-content.ts +206 -0
  199. package/oh-my-opencode/src/features/opencode-skill-loader/types.ts +38 -0
  200. package/oh-my-opencode/src/features/skill-mcp-manager/env-cleaner.test.ts +201 -0
  201. package/oh-my-opencode/src/features/skill-mcp-manager/env-cleaner.ts +27 -0
  202. package/oh-my-opencode/src/features/skill-mcp-manager/index.ts +2 -0
  203. package/oh-my-opencode/src/features/skill-mcp-manager/manager.test.ts +611 -0
  204. package/oh-my-opencode/src/features/skill-mcp-manager/manager.ts +520 -0
  205. package/oh-my-opencode/src/features/skill-mcp-manager/types.ts +14 -0
  206. package/oh-my-opencode/src/features/task-toast-manager/index.ts +2 -0
  207. package/oh-my-opencode/src/features/task-toast-manager/manager.test.ts +249 -0
  208. package/oh-my-opencode/src/features/task-toast-manager/manager.ts +215 -0
  209. package/oh-my-opencode/src/features/task-toast-manager/types.ts +24 -0
  210. package/oh-my-opencode/src/hooks/AGENTS.md +73 -0
  211. package/oh-my-opencode/src/hooks/agent-usage-reminder/constants.ts +54 -0
  212. package/oh-my-opencode/src/hooks/agent-usage-reminder/index.ts +109 -0
  213. package/oh-my-opencode/src/hooks/agent-usage-reminder/storage.ts +42 -0
  214. package/oh-my-opencode/src/hooks/agent-usage-reminder/types.ts +6 -0
  215. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/executor.test.ts +307 -0
  216. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/executor.ts +485 -0
  217. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/index.ts +151 -0
  218. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/parser.ts +201 -0
  219. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/pruning-deduplication.test.ts +33 -0
  220. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/pruning-deduplication.ts +184 -0
  221. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/pruning-types.ts +44 -0
  222. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/storage.test.ts +77 -0
  223. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/storage.ts +250 -0
  224. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/types.ts +42 -0
  225. package/oh-my-opencode/src/hooks/atlas/index.test.ts +953 -0
  226. package/oh-my-opencode/src/hooks/atlas/index.ts +771 -0
  227. package/oh-my-opencode/src/hooks/auto-slash-command/constants.ts +12 -0
  228. package/oh-my-opencode/src/hooks/auto-slash-command/detector.test.ts +296 -0
  229. package/oh-my-opencode/src/hooks/auto-slash-command/detector.ts +65 -0
  230. package/oh-my-opencode/src/hooks/auto-slash-command/executor.ts +205 -0
  231. package/oh-my-opencode/src/hooks/auto-slash-command/index.test.ts +254 -0
  232. package/oh-my-opencode/src/hooks/auto-slash-command/index.ts +89 -0
  233. package/oh-my-opencode/src/hooks/auto-slash-command/types.ts +23 -0
  234. package/oh-my-opencode/src/hooks/auto-update-checker/cache.ts +93 -0
  235. package/oh-my-opencode/src/hooks/auto-update-checker/checker.test.ts +24 -0
  236. package/oh-my-opencode/src/hooks/auto-update-checker/checker.ts +284 -0
  237. package/oh-my-opencode/src/hooks/auto-update-checker/constants.ts +64 -0
  238. package/oh-my-opencode/src/hooks/auto-update-checker/index.test.ts +254 -0
  239. package/oh-my-opencode/src/hooks/auto-update-checker/index.ts +260 -0
  240. package/oh-my-opencode/src/hooks/auto-update-checker/types.ts +29 -0
  241. package/oh-my-opencode/src/hooks/background-compaction/index.ts +87 -0
  242. package/oh-my-opencode/src/hooks/background-notification/index.ts +28 -0
  243. package/oh-my-opencode/src/hooks/background-notification/types.ts +5 -0
  244. package/oh-my-opencode/src/hooks/claude-code-hooks/AGENTS.md +70 -0
  245. package/oh-my-opencode/src/hooks/claude-code-hooks/config-loader.ts +107 -0
  246. package/oh-my-opencode/src/hooks/claude-code-hooks/config.ts +103 -0
  247. package/oh-my-opencode/src/hooks/claude-code-hooks/index.ts +401 -0
  248. package/oh-my-opencode/src/hooks/claude-code-hooks/plugin-config.ts +12 -0
  249. package/oh-my-opencode/src/hooks/claude-code-hooks/post-tool-use.ts +199 -0
  250. package/oh-my-opencode/src/hooks/claude-code-hooks/pre-compact.ts +109 -0
  251. package/oh-my-opencode/src/hooks/claude-code-hooks/pre-tool-use.ts +172 -0
  252. package/oh-my-opencode/src/hooks/claude-code-hooks/stop.ts +118 -0
  253. package/oh-my-opencode/src/hooks/claude-code-hooks/todo.ts +76 -0
  254. package/oh-my-opencode/src/hooks/claude-code-hooks/tool-input-cache.ts +47 -0
  255. package/oh-my-opencode/src/hooks/claude-code-hooks/transcript.ts +252 -0
  256. package/oh-my-opencode/src/hooks/claude-code-hooks/types.ts +204 -0
  257. package/oh-my-opencode/src/hooks/claude-code-hooks/user-prompt-submit.ts +117 -0
  258. package/oh-my-opencode/src/hooks/comment-checker/cli.test.ts +68 -0
  259. package/oh-my-opencode/src/hooks/comment-checker/cli.ts +221 -0
  260. package/oh-my-opencode/src/hooks/comment-checker/downloader.ts +196 -0
  261. package/oh-my-opencode/src/hooks/comment-checker/index.ts +171 -0
  262. package/oh-my-opencode/src/hooks/comment-checker/types.ts +33 -0
  263. package/oh-my-opencode/src/hooks/compaction-context-injector/index.ts +61 -0
  264. package/oh-my-opencode/src/hooks/context-window-monitor.ts +99 -0
  265. package/oh-my-opencode/src/hooks/delegate-task-retry/index.test.ts +119 -0
  266. package/oh-my-opencode/src/hooks/delegate-task-retry/index.ts +136 -0
  267. package/oh-my-opencode/src/hooks/directory-agents-injector/constants.ts +9 -0
  268. package/oh-my-opencode/src/hooks/directory-agents-injector/index.ts +182 -0
  269. package/oh-my-opencode/src/hooks/directory-agents-injector/storage.ts +48 -0
  270. package/oh-my-opencode/src/hooks/directory-agents-injector/types.ts +5 -0
  271. package/oh-my-opencode/src/hooks/directory-readme-injector/constants.ts +9 -0
  272. package/oh-my-opencode/src/hooks/directory-readme-injector/index.ts +177 -0
  273. package/oh-my-opencode/src/hooks/directory-readme-injector/storage.ts +48 -0
  274. package/oh-my-opencode/src/hooks/directory-readme-injector/types.ts +5 -0
  275. package/oh-my-opencode/src/hooks/edit-error-recovery/index.test.ts +126 -0
  276. package/oh-my-opencode/src/hooks/edit-error-recovery/index.ts +57 -0
  277. package/oh-my-opencode/src/hooks/empty-task-response-detector.ts +27 -0
  278. package/oh-my-opencode/src/hooks/index.ts +32 -0
  279. package/oh-my-opencode/src/hooks/interactive-bash-session/constants.ts +15 -0
  280. package/oh-my-opencode/src/hooks/interactive-bash-session/index.ts +262 -0
  281. package/oh-my-opencode/src/hooks/interactive-bash-session/storage.ts +59 -0
  282. package/oh-my-opencode/src/hooks/interactive-bash-session/types.ts +11 -0
  283. package/oh-my-opencode/src/hooks/keyword-detector/constants.ts +300 -0
  284. package/oh-my-opencode/src/hooks/keyword-detector/detector.ts +52 -0
  285. package/oh-my-opencode/src/hooks/keyword-detector/index.test.ts +529 -0
  286. package/oh-my-opencode/src/hooks/keyword-detector/index.ts +100 -0
  287. package/oh-my-opencode/src/hooks/keyword-detector/types.ts +4 -0
  288. package/oh-my-opencode/src/hooks/non-interactive-env/constants.ts +70 -0
  289. package/oh-my-opencode/src/hooks/non-interactive-env/detector.ts +19 -0
  290. package/oh-my-opencode/src/hooks/non-interactive-env/index.test.ts +323 -0
  291. package/oh-my-opencode/src/hooks/non-interactive-env/index.ts +63 -0
  292. package/oh-my-opencode/src/hooks/non-interactive-env/types.ts +3 -0
  293. package/oh-my-opencode/src/hooks/prometheus-md-only/constants.ts +32 -0
  294. package/oh-my-opencode/src/hooks/prometheus-md-only/index.test.ts +488 -0
  295. package/oh-my-opencode/src/hooks/prometheus-md-only/index.ts +136 -0
  296. package/oh-my-opencode/src/hooks/ralph-loop/constants.ts +5 -0
  297. package/oh-my-opencode/src/hooks/ralph-loop/index.test.ts +835 -0
  298. package/oh-my-opencode/src/hooks/ralph-loop/index.ts +417 -0
  299. package/oh-my-opencode/src/hooks/ralph-loop/storage.ts +115 -0
  300. package/oh-my-opencode/src/hooks/ralph-loop/types.ts +19 -0
  301. package/oh-my-opencode/src/hooks/rules-injector/constants.ts +30 -0
  302. package/oh-my-opencode/src/hooks/rules-injector/finder.test.ts +381 -0
  303. package/oh-my-opencode/src/hooks/rules-injector/finder.ts +263 -0
  304. package/oh-my-opencode/src/hooks/rules-injector/index.ts +223 -0
  305. package/oh-my-opencode/src/hooks/rules-injector/matcher.ts +63 -0
  306. package/oh-my-opencode/src/hooks/rules-injector/parser.test.ts +226 -0
  307. package/oh-my-opencode/src/hooks/rules-injector/parser.ts +211 -0
  308. package/oh-my-opencode/src/hooks/rules-injector/storage.ts +59 -0
  309. package/oh-my-opencode/src/hooks/rules-injector/types.ts +57 -0
  310. package/oh-my-opencode/src/hooks/session-notification-utils.ts +140 -0
  311. package/oh-my-opencode/src/hooks/session-notification.test.ts +361 -0
  312. package/oh-my-opencode/src/hooks/session-notification.ts +330 -0
  313. package/oh-my-opencode/src/hooks/session-recovery/constants.ts +10 -0
  314. package/oh-my-opencode/src/hooks/session-recovery/index.test.ts +223 -0
  315. package/oh-my-opencode/src/hooks/session-recovery/index.ts +435 -0
  316. package/oh-my-opencode/src/hooks/session-recovery/storage.ts +390 -0
  317. package/oh-my-opencode/src/hooks/session-recovery/types.ts +98 -0
  318. package/oh-my-opencode/src/hooks/start-work/index.test.ts +402 -0
  319. package/oh-my-opencode/src/hooks/start-work/index.ts +242 -0
  320. package/oh-my-opencode/src/hooks/task-resume-info/index.ts +36 -0
  321. package/oh-my-opencode/src/hooks/think-mode/detector.ts +57 -0
  322. package/oh-my-opencode/src/hooks/think-mode/index.test.ts +353 -0
  323. package/oh-my-opencode/src/hooks/think-mode/index.ts +89 -0
  324. package/oh-my-opencode/src/hooks/think-mode/switcher.test.ts +461 -0
  325. package/oh-my-opencode/src/hooks/think-mode/switcher.ts +222 -0
  326. package/oh-my-opencode/src/hooks/think-mode/types.ts +21 -0
  327. package/oh-my-opencode/src/hooks/thinking-block-validator/index.ts +171 -0
  328. package/oh-my-opencode/src/hooks/todo-continuation-enforcer.test.ts +876 -0
  329. package/oh-my-opencode/src/hooks/todo-continuation-enforcer.ts +480 -0
  330. package/oh-my-opencode/src/hooks/tool-output-truncator.test.ts +168 -0
  331. package/oh-my-opencode/src/hooks/tool-output-truncator.ts +61 -0
  332. package/oh-my-opencode/src/index.ts +589 -0
  333. package/oh-my-opencode/src/mcp/AGENTS.md +70 -0
  334. package/oh-my-opencode/src/mcp/context7.ts +6 -0
  335. package/oh-my-opencode/src/mcp/grep-app.ts +6 -0
  336. package/oh-my-opencode/src/mcp/index.test.ts +86 -0
  337. package/oh-my-opencode/src/mcp/index.ts +32 -0
  338. package/oh-my-opencode/src/mcp/types.ts +9 -0
  339. package/oh-my-opencode/src/mcp/websearch.ts +10 -0
  340. package/oh-my-opencode/src/plugin-config.test.ts +119 -0
  341. package/oh-my-opencode/src/plugin-config.ts +135 -0
  342. package/oh-my-opencode/src/plugin-handlers/config-handler.test.ts +103 -0
  343. package/oh-my-opencode/src/plugin-handlers/config-handler.ts +399 -0
  344. package/oh-my-opencode/src/plugin-handlers/index.ts +1 -0
  345. package/oh-my-opencode/src/plugin-state.ts +30 -0
  346. package/oh-my-opencode/src/shared/AGENTS.md +63 -0
  347. package/oh-my-opencode/src/shared/agent-tool-restrictions.ts +44 -0
  348. package/oh-my-opencode/src/shared/agent-variant.test.ts +83 -0
  349. package/oh-my-opencode/src/shared/agent-variant.ts +40 -0
  350. package/oh-my-opencode/src/shared/claude-config-dir.test.ts +60 -0
  351. package/oh-my-opencode/src/shared/claude-config-dir.ts +11 -0
  352. package/oh-my-opencode/src/shared/command-executor.ts +225 -0
  353. package/oh-my-opencode/src/shared/config-errors.ts +18 -0
  354. package/oh-my-opencode/src/shared/config-path.ts +47 -0
  355. package/oh-my-opencode/src/shared/data-path.ts +22 -0
  356. package/oh-my-opencode/src/shared/deep-merge.test.ts +336 -0
  357. package/oh-my-opencode/src/shared/deep-merge.ts +53 -0
  358. package/oh-my-opencode/src/shared/dynamic-truncator.ts +193 -0
  359. package/oh-my-opencode/src/shared/external-plugin-detector.test.ts +133 -0
  360. package/oh-my-opencode/src/shared/external-plugin-detector.ts +132 -0
  361. package/oh-my-opencode/src/shared/file-reference-resolver.ts +85 -0
  362. package/oh-my-opencode/src/shared/file-utils.ts +40 -0
  363. package/oh-my-opencode/src/shared/first-message-variant.test.ts +32 -0
  364. package/oh-my-opencode/src/shared/first-message-variant.ts +28 -0
  365. package/oh-my-opencode/src/shared/frontmatter.test.ts +262 -0
  366. package/oh-my-opencode/src/shared/frontmatter.ts +31 -0
  367. package/oh-my-opencode/src/shared/hook-disabled.ts +22 -0
  368. package/oh-my-opencode/src/shared/index.ts +29 -0
  369. package/oh-my-opencode/src/shared/jsonc-parser.test.ts +266 -0
  370. package/oh-my-opencode/src/shared/jsonc-parser.ts +66 -0
  371. package/oh-my-opencode/src/shared/logger.ts +20 -0
  372. package/oh-my-opencode/src/shared/migration.test.ts +602 -0
  373. package/oh-my-opencode/src/shared/migration.ts +191 -0
  374. package/oh-my-opencode/src/shared/model-resolver.test.ts +101 -0
  375. package/oh-my-opencode/src/shared/model-resolver.ts +35 -0
  376. package/oh-my-opencode/src/shared/model-sanitizer.ts +12 -0
  377. package/oh-my-opencode/src/shared/opencode-config-dir.test.ts +318 -0
  378. package/oh-my-opencode/src/shared/opencode-config-dir.ts +142 -0
  379. package/oh-my-opencode/src/shared/opencode-version.test.ts +223 -0
  380. package/oh-my-opencode/src/shared/opencode-version.ts +72 -0
  381. package/oh-my-opencode/src/shared/pattern-matcher.ts +29 -0
  382. package/oh-my-opencode/src/shared/permission-compat.test.ts +134 -0
  383. package/oh-my-opencode/src/shared/permission-compat.ts +77 -0
  384. package/oh-my-opencode/src/shared/session-cursor.test.ts +66 -0
  385. package/oh-my-opencode/src/shared/session-cursor.ts +85 -0
  386. package/oh-my-opencode/src/shared/shell-env.test.ts +278 -0
  387. package/oh-my-opencode/src/shared/shell-env.ts +111 -0
  388. package/oh-my-opencode/src/shared/snake-case.ts +49 -0
  389. package/oh-my-opencode/src/shared/system-directive.ts +40 -0
  390. package/oh-my-opencode/src/shared/tool-name.ts +26 -0
  391. package/oh-my-opencode/src/shared/zip-extractor.ts +83 -0
  392. package/oh-my-opencode/src/tools/AGENTS.md +74 -0
  393. package/oh-my-opencode/src/tools/ast-grep/cli.ts +230 -0
  394. package/oh-my-opencode/src/tools/ast-grep/constants.ts +261 -0
  395. package/oh-my-opencode/src/tools/ast-grep/downloader.ts +128 -0
  396. package/oh-my-opencode/src/tools/ast-grep/index.ts +13 -0
  397. package/oh-my-opencode/src/tools/ast-grep/tools.ts +112 -0
  398. package/oh-my-opencode/src/tools/ast-grep/types.ts +61 -0
  399. package/oh-my-opencode/src/tools/ast-grep/utils.ts +102 -0
  400. package/oh-my-opencode/src/tools/background-task/constants.ts +7 -0
  401. package/oh-my-opencode/src/tools/background-task/index.ts +7 -0
  402. package/oh-my-opencode/src/tools/background-task/tools.ts +479 -0
  403. package/oh-my-opencode/src/tools/background-task/types.ts +16 -0
  404. package/oh-my-opencode/src/tools/call-omo-agent/constants.ts +7 -0
  405. package/oh-my-opencode/src/tools/call-omo-agent/index.ts +3 -0
  406. package/oh-my-opencode/src/tools/call-omo-agent/tools.ts +338 -0
  407. package/oh-my-opencode/src/tools/call-omo-agent/types.ts +27 -0
  408. package/oh-my-opencode/src/tools/delegate-task/constants.ts +205 -0
  409. package/oh-my-opencode/src/tools/delegate-task/index.ts +3 -0
  410. package/oh-my-opencode/src/tools/delegate-task/tools.test.ts +1575 -0
  411. package/oh-my-opencode/src/tools/delegate-task/tools.ts +885 -0
  412. package/oh-my-opencode/src/tools/delegate-task/types.ts +9 -0
  413. package/oh-my-opencode/src/tools/glob/cli.test.ts +158 -0
  414. package/oh-my-opencode/src/tools/glob/cli.ts +191 -0
  415. package/oh-my-opencode/src/tools/glob/constants.ts +12 -0
  416. package/oh-my-opencode/src/tools/glob/index.ts +3 -0
  417. package/oh-my-opencode/src/tools/glob/tools.ts +41 -0
  418. package/oh-my-opencode/src/tools/glob/types.ts +22 -0
  419. package/oh-my-opencode/src/tools/glob/utils.ts +26 -0
  420. package/oh-my-opencode/src/tools/grep/cli.ts +229 -0
  421. package/oh-my-opencode/src/tools/grep/constants.ts +127 -0
  422. package/oh-my-opencode/src/tools/grep/downloader.test.ts +103 -0
  423. package/oh-my-opencode/src/tools/grep/downloader.ts +145 -0
  424. package/oh-my-opencode/src/tools/grep/index.ts +3 -0
  425. package/oh-my-opencode/src/tools/grep/tools.ts +40 -0
  426. package/oh-my-opencode/src/tools/grep/types.ts +39 -0
  427. package/oh-my-opencode/src/tools/grep/utils.ts +53 -0
  428. package/oh-my-opencode/src/tools/index.ts +72 -0
  429. package/oh-my-opencode/src/tools/interactive-bash/constants.ts +18 -0
  430. package/oh-my-opencode/src/tools/interactive-bash/index.ts +4 -0
  431. package/oh-my-opencode/src/tools/interactive-bash/tools.ts +126 -0
  432. package/oh-my-opencode/src/tools/interactive-bash/utils.ts +71 -0
  433. package/oh-my-opencode/src/tools/look-at/constants.ts +3 -0
  434. package/oh-my-opencode/src/tools/look-at/index.ts +3 -0
  435. package/oh-my-opencode/src/tools/look-at/tools.test.ts +73 -0
  436. package/oh-my-opencode/src/tools/look-at/tools.ts +173 -0
  437. package/oh-my-opencode/src/tools/look-at/types.ts +4 -0
  438. package/oh-my-opencode/src/tools/lsp/client.ts +596 -0
  439. package/oh-my-opencode/src/tools/lsp/config.test.ts +130 -0
  440. package/oh-my-opencode/src/tools/lsp/config.ts +285 -0
  441. package/oh-my-opencode/src/tools/lsp/constants.ts +390 -0
  442. package/oh-my-opencode/src/tools/lsp/index.ts +7 -0
  443. package/oh-my-opencode/src/tools/lsp/tools.ts +261 -0
  444. package/oh-my-opencode/src/tools/lsp/types.ts +124 -0
  445. package/oh-my-opencode/src/tools/lsp/utils.ts +406 -0
  446. package/oh-my-opencode/src/tools/session-manager/constants.ts +97 -0
  447. package/oh-my-opencode/src/tools/session-manager/index.ts +3 -0
  448. package/oh-my-opencode/src/tools/session-manager/storage.test.ts +315 -0
  449. package/oh-my-opencode/src/tools/session-manager/storage.ts +238 -0
  450. package/oh-my-opencode/src/tools/session-manager/tools.test.ts +124 -0
  451. package/oh-my-opencode/src/tools/session-manager/tools.ts +146 -0
  452. package/oh-my-opencode/src/tools/session-manager/types.ts +99 -0
  453. package/oh-my-opencode/src/tools/session-manager/utils.test.ts +160 -0
  454. package/oh-my-opencode/src/tools/session-manager/utils.ts +199 -0
  455. package/oh-my-opencode/src/tools/skill/constants.ts +8 -0
  456. package/oh-my-opencode/src/tools/skill/index.ts +3 -0
  457. package/oh-my-opencode/src/tools/skill/tools.test.ts +239 -0
  458. package/oh-my-opencode/src/tools/skill/tools.ts +200 -0
  459. package/oh-my-opencode/src/tools/skill/types.ts +31 -0
  460. package/oh-my-opencode/src/tools/skill-mcp/constants.ts +3 -0
  461. package/oh-my-opencode/src/tools/skill-mcp/index.ts +3 -0
  462. package/oh-my-opencode/src/tools/skill-mcp/tools.test.ts +215 -0
  463. package/oh-my-opencode/src/tools/skill-mcp/tools.ts +172 -0
  464. package/oh-my-opencode/src/tools/skill-mcp/types.ts +8 -0
  465. package/oh-my-opencode/src/tools/slashcommand/index.ts +2 -0
  466. package/oh-my-opencode/src/tools/slashcommand/tools.ts +252 -0
  467. package/oh-my-opencode/src/tools/slashcommand/types.ts +28 -0
  468. package/oh-my-opencode/test-setup.ts +6 -0
  469. package/oh-my-opencode/tsconfig.json +20 -0
  470. package/package.json +1 -1
  471. package/src/__tests__/git.test.ts +7 -2
  472. package/src/__tests__/manifest.test.ts +5 -5
  473. package/src/agents/repo-explorer.ts +2 -1
  474. package/src/git.ts +18 -3
  475. package/src/manifest.ts +22 -15
@@ -0,0 +1,418 @@
1
+ import { describe, test, expect, beforeEach } from "bun:test"
2
+ import { ConcurrencyManager } from "./concurrency"
3
+ import type { BackgroundTaskConfig } from "../../config/schema"
4
+
5
+ describe("ConcurrencyManager.getConcurrencyLimit", () => {
6
+ test("should return model-specific limit when modelConcurrency is set", () => {
7
+ // #given
8
+ const config: BackgroundTaskConfig = {
9
+ modelConcurrency: { "anthropic/claude-sonnet-4-5": 5 }
10
+ }
11
+ const manager = new ConcurrencyManager(config)
12
+
13
+ // #when
14
+ const limit = manager.getConcurrencyLimit("anthropic/claude-sonnet-4-5")
15
+
16
+ // #then
17
+ expect(limit).toBe(5)
18
+ })
19
+
20
+ test("should return provider limit when providerConcurrency is set for model provider", () => {
21
+ // #given
22
+ const config: BackgroundTaskConfig = {
23
+ providerConcurrency: { anthropic: 3 }
24
+ }
25
+ const manager = new ConcurrencyManager(config)
26
+
27
+ // #when
28
+ const limit = manager.getConcurrencyLimit("anthropic/claude-sonnet-4-5")
29
+
30
+ // #then
31
+ expect(limit).toBe(3)
32
+ })
33
+
34
+ test("should return provider limit even when modelConcurrency exists but doesn't match", () => {
35
+ // #given
36
+ const config: BackgroundTaskConfig = {
37
+ modelConcurrency: { "google/gemini-3-pro": 5 },
38
+ providerConcurrency: { anthropic: 3 }
39
+ }
40
+ const manager = new ConcurrencyManager(config)
41
+
42
+ // #when
43
+ const limit = manager.getConcurrencyLimit("anthropic/claude-sonnet-4-5")
44
+
45
+ // #then
46
+ expect(limit).toBe(3)
47
+ })
48
+
49
+ test("should return default limit when defaultConcurrency is set", () => {
50
+ // #given
51
+ const config: BackgroundTaskConfig = {
52
+ defaultConcurrency: 2
53
+ }
54
+ const manager = new ConcurrencyManager(config)
55
+
56
+ // #when
57
+ const limit = manager.getConcurrencyLimit("anthropic/claude-sonnet-4-5")
58
+
59
+ // #then
60
+ expect(limit).toBe(2)
61
+ })
62
+
63
+ test("should return default 5 when no config provided", () => {
64
+ // #given
65
+ const manager = new ConcurrencyManager()
66
+
67
+ // #when
68
+ const limit = manager.getConcurrencyLimit("anthropic/claude-sonnet-4-5")
69
+
70
+ // #then
71
+ expect(limit).toBe(5)
72
+ })
73
+
74
+ test("should return default 5 when config exists but no concurrency settings", () => {
75
+ // #given
76
+ const config: BackgroundTaskConfig = {}
77
+ const manager = new ConcurrencyManager(config)
78
+
79
+ // #when
80
+ const limit = manager.getConcurrencyLimit("anthropic/claude-sonnet-4-5")
81
+
82
+ // #then
83
+ expect(limit).toBe(5)
84
+ })
85
+
86
+ test("should prioritize model-specific over provider-specific over default", () => {
87
+ // #given
88
+ const config: BackgroundTaskConfig = {
89
+ modelConcurrency: { "anthropic/claude-sonnet-4-5": 10 },
90
+ providerConcurrency: { anthropic: 5 },
91
+ defaultConcurrency: 2
92
+ }
93
+ const manager = new ConcurrencyManager(config)
94
+
95
+ // #when
96
+ const modelLimit = manager.getConcurrencyLimit("anthropic/claude-sonnet-4-5")
97
+ const providerLimit = manager.getConcurrencyLimit("anthropic/claude-opus-4-5")
98
+ const defaultLimit = manager.getConcurrencyLimit("google/gemini-3-pro")
99
+
100
+ // #then
101
+ expect(modelLimit).toBe(10)
102
+ expect(providerLimit).toBe(5)
103
+ expect(defaultLimit).toBe(2)
104
+ })
105
+
106
+ test("should handle models without provider part", () => {
107
+ // #given
108
+ const config: BackgroundTaskConfig = {
109
+ providerConcurrency: { "custom-model": 4 }
110
+ }
111
+ const manager = new ConcurrencyManager(config)
112
+
113
+ // #when
114
+ const limit = manager.getConcurrencyLimit("custom-model")
115
+
116
+ // #then
117
+ expect(limit).toBe(4)
118
+ })
119
+
120
+ test("should return Infinity when defaultConcurrency is 0", () => {
121
+ // #given
122
+ const config: BackgroundTaskConfig = { defaultConcurrency: 0 }
123
+ const manager = new ConcurrencyManager(config)
124
+
125
+ // #when
126
+ const limit = manager.getConcurrencyLimit("any-model")
127
+
128
+ // #then
129
+ expect(limit).toBe(Infinity)
130
+ })
131
+
132
+ test("should return Infinity when providerConcurrency is 0", () => {
133
+ // #given
134
+ const config: BackgroundTaskConfig = {
135
+ providerConcurrency: { anthropic: 0 }
136
+ }
137
+ const manager = new ConcurrencyManager(config)
138
+
139
+ // #when
140
+ const limit = manager.getConcurrencyLimit("anthropic/claude-sonnet-4-5")
141
+
142
+ // #then
143
+ expect(limit).toBe(Infinity)
144
+ })
145
+
146
+ test("should return Infinity when modelConcurrency is 0", () => {
147
+ // #given
148
+ const config: BackgroundTaskConfig = {
149
+ modelConcurrency: { "anthropic/claude-sonnet-4-5": 0 }
150
+ }
151
+ const manager = new ConcurrencyManager(config)
152
+
153
+ // #when
154
+ const limit = manager.getConcurrencyLimit("anthropic/claude-sonnet-4-5")
155
+
156
+ // #then
157
+ expect(limit).toBe(Infinity)
158
+ })
159
+ })
160
+
161
+ describe("ConcurrencyManager.acquire/release", () => {
162
+ let manager: ConcurrencyManager
163
+
164
+ beforeEach(() => {
165
+ // #given
166
+ const config: BackgroundTaskConfig = {}
167
+ manager = new ConcurrencyManager(config)
168
+ })
169
+
170
+ test("should allow acquiring up to limit", async () => {
171
+ // #given
172
+ const config: BackgroundTaskConfig = { defaultConcurrency: 2 }
173
+ manager = new ConcurrencyManager(config)
174
+
175
+ // #when
176
+ await manager.acquire("model-a")
177
+ await manager.acquire("model-a")
178
+
179
+ // #then - both resolved without waiting
180
+ expect(true).toBe(true)
181
+ })
182
+
183
+ test("should allow acquires up to default limit of 5", async () => {
184
+ // #given - no config = default limit of 5
185
+
186
+ // #when
187
+ await manager.acquire("model-a")
188
+ await manager.acquire("model-a")
189
+ await manager.acquire("model-a")
190
+ await manager.acquire("model-a")
191
+ await manager.acquire("model-a")
192
+
193
+ // #then - all 5 resolved
194
+ expect(true).toBe(true)
195
+ })
196
+
197
+ test("should queue when limit reached", async () => {
198
+ // #given
199
+ const config: BackgroundTaskConfig = { defaultConcurrency: 1 }
200
+ manager = new ConcurrencyManager(config)
201
+ await manager.acquire("model-a")
202
+
203
+ // #when
204
+ let resolved = false
205
+ const waitPromise = manager.acquire("model-a").then(() => { resolved = true })
206
+
207
+ // Give microtask queue a chance to run
208
+ await Promise.resolve()
209
+
210
+ // #then - should still be waiting
211
+ expect(resolved).toBe(false)
212
+
213
+ // #when - release
214
+ manager.release("model-a")
215
+ await waitPromise
216
+
217
+ // #then - now resolved
218
+ expect(resolved).toBe(true)
219
+ })
220
+
221
+ test("should queue multiple tasks and process in order", async () => {
222
+ // #given
223
+ const config: BackgroundTaskConfig = { defaultConcurrency: 1 }
224
+ manager = new ConcurrencyManager(config)
225
+ await manager.acquire("model-a")
226
+
227
+ // #when
228
+ const order: string[] = []
229
+ const task1 = manager.acquire("model-a").then(() => { order.push("1") })
230
+ const task2 = manager.acquire("model-a").then(() => { order.push("2") })
231
+ const task3 = manager.acquire("model-a").then(() => { order.push("3") })
232
+
233
+ // Give microtask queue a chance to run
234
+ await Promise.resolve()
235
+
236
+ // #then - none resolved yet
237
+ expect(order).toEqual([])
238
+
239
+ // #when - release one at a time
240
+ manager.release("model-a")
241
+ await task1
242
+ expect(order).toEqual(["1"])
243
+
244
+ manager.release("model-a")
245
+ await task2
246
+ expect(order).toEqual(["1", "2"])
247
+
248
+ manager.release("model-a")
249
+ await task3
250
+ expect(order).toEqual(["1", "2", "3"])
251
+ })
252
+
253
+ test("should handle independent models separately", async () => {
254
+ // #given
255
+ const config: BackgroundTaskConfig = { defaultConcurrency: 1 }
256
+ manager = new ConcurrencyManager(config)
257
+ await manager.acquire("model-a")
258
+
259
+ // #when - acquire different model
260
+ const resolved = await Promise.race([
261
+ manager.acquire("model-b").then(() => "resolved"),
262
+ Promise.resolve("timeout").then(() => "timeout")
263
+ ])
264
+
265
+ // #then - different model should resolve immediately
266
+ expect(resolved).toBe("resolved")
267
+ })
268
+
269
+ test("should allow re-acquiring after release", async () => {
270
+ // #given
271
+ const config: BackgroundTaskConfig = { defaultConcurrency: 1 }
272
+ manager = new ConcurrencyManager(config)
273
+
274
+ // #when
275
+ await manager.acquire("model-a")
276
+ manager.release("model-a")
277
+ await manager.acquire("model-a")
278
+
279
+ // #then
280
+ expect(true).toBe(true)
281
+ })
282
+
283
+ test("should handle release when no acquire", () => {
284
+ // #given
285
+ const config: BackgroundTaskConfig = { defaultConcurrency: 2 }
286
+ manager = new ConcurrencyManager(config)
287
+
288
+ // #when - release without acquire
289
+ manager.release("model-a")
290
+
291
+ // #then - should not throw
292
+ expect(true).toBe(true)
293
+ })
294
+
295
+ test("should handle release when no prior acquire", () => {
296
+ // #given - default config
297
+
298
+ // #when - release without acquire
299
+ manager.release("model-a")
300
+
301
+ // #then - should not throw
302
+ expect(true).toBe(true)
303
+ })
304
+
305
+ test("should handle multiple acquires and releases correctly", async () => {
306
+ // #given
307
+ const config: BackgroundTaskConfig = { defaultConcurrency: 3 }
308
+ manager = new ConcurrencyManager(config)
309
+
310
+ // #when
311
+ await manager.acquire("model-a")
312
+ await manager.acquire("model-a")
313
+ await manager.acquire("model-a")
314
+
315
+ // Release all
316
+ manager.release("model-a")
317
+ manager.release("model-a")
318
+ manager.release("model-a")
319
+
320
+ // Should be able to acquire again
321
+ await manager.acquire("model-a")
322
+
323
+ // #then
324
+ expect(true).toBe(true)
325
+ })
326
+
327
+ test("should use model-specific limit for acquire", async () => {
328
+ // #given
329
+ const config: BackgroundTaskConfig = {
330
+ modelConcurrency: { "anthropic/claude-sonnet-4-5": 2 },
331
+ defaultConcurrency: 5
332
+ }
333
+ manager = new ConcurrencyManager(config)
334
+ await manager.acquire("anthropic/claude-sonnet-4-5")
335
+ await manager.acquire("anthropic/claude-sonnet-4-5")
336
+
337
+ // #when
338
+ let resolved = false
339
+ const waitPromise = manager.acquire("anthropic/claude-sonnet-4-5").then(() => { resolved = true })
340
+
341
+ // Give microtask queue a chance to run
342
+ await Promise.resolve()
343
+
344
+ // #then - should be waiting (model-specific limit is 2)
345
+ expect(resolved).toBe(false)
346
+
347
+ // Cleanup
348
+ manager.release("anthropic/claude-sonnet-4-5")
349
+ await waitPromise
350
+ })
351
+ })
352
+
353
+ describe("ConcurrencyManager.cleanup", () => {
354
+ test("cancelWaiters should reject all pending acquires", async () => {
355
+ // #given
356
+ const config: BackgroundTaskConfig = { defaultConcurrency: 1 }
357
+ const manager = new ConcurrencyManager(config)
358
+ await manager.acquire("model-a")
359
+
360
+ // Queue waiters
361
+ const errors: Error[] = []
362
+ const p1 = manager.acquire("model-a").catch(e => errors.push(e))
363
+ const p2 = manager.acquire("model-a").catch(e => errors.push(e))
364
+
365
+ // #when
366
+ manager.cancelWaiters("model-a")
367
+ await Promise.all([p1, p2])
368
+
369
+ // #then
370
+ expect(errors.length).toBe(2)
371
+ expect(errors[0].message).toContain("cancelled")
372
+ })
373
+
374
+ test("clear should cancel all models and reset state", async () => {
375
+ // #given
376
+ const config: BackgroundTaskConfig = { defaultConcurrency: 1 }
377
+ const manager = new ConcurrencyManager(config)
378
+ await manager.acquire("model-a")
379
+ await manager.acquire("model-b")
380
+
381
+ const errors: Error[] = []
382
+ const p1 = manager.acquire("model-a").catch(e => errors.push(e))
383
+ const p2 = manager.acquire("model-b").catch(e => errors.push(e))
384
+
385
+ // #when
386
+ manager.clear()
387
+ await Promise.all([p1, p2])
388
+
389
+ // #then
390
+ expect(errors.length).toBe(2)
391
+ expect(manager.getCount("model-a")).toBe(0)
392
+ expect(manager.getCount("model-b")).toBe(0)
393
+ })
394
+
395
+ test("getCount and getQueueLength should return correct values", async () => {
396
+ // #given
397
+ const config: BackgroundTaskConfig = { defaultConcurrency: 2 }
398
+ const manager = new ConcurrencyManager(config)
399
+
400
+ // #when
401
+ await manager.acquire("model-a")
402
+ expect(manager.getCount("model-a")).toBe(1)
403
+ expect(manager.getQueueLength("model-a")).toBe(0)
404
+
405
+ await manager.acquire("model-a")
406
+ expect(manager.getCount("model-a")).toBe(2)
407
+
408
+ // Queue one more
409
+ const p = manager.acquire("model-a").catch(() => {})
410
+ await Promise.resolve() // let it queue
411
+
412
+ expect(manager.getQueueLength("model-a")).toBe(1)
413
+
414
+ // Cleanup
415
+ manager.cancelWaiters("model-a")
416
+ await p
417
+ })
418
+ })
@@ -0,0 +1,137 @@
1
+ import type { BackgroundTaskConfig } from "../../config/schema"
2
+
3
+ /**
4
+ * Queue entry with settled-flag pattern to prevent double-resolution.
5
+ *
6
+ * The settled flag ensures that cancelWaiters() doesn't reject
7
+ * an entry that was already resolved by release().
8
+ */
9
+ interface QueueEntry {
10
+ resolve: () => void
11
+ rawReject: (error: Error) => void
12
+ settled: boolean
13
+ }
14
+
15
+ export class ConcurrencyManager {
16
+ private config?: BackgroundTaskConfig
17
+ private counts: Map<string, number> = new Map()
18
+ private queues: Map<string, QueueEntry[]> = new Map()
19
+
20
+ constructor(config?: BackgroundTaskConfig) {
21
+ this.config = config
22
+ }
23
+
24
+ getConcurrencyLimit(model: string): number {
25
+ const modelLimit = this.config?.modelConcurrency?.[model]
26
+ if (modelLimit !== undefined) {
27
+ return modelLimit === 0 ? Infinity : modelLimit
28
+ }
29
+ const provider = model.split('/')[0]
30
+ const providerLimit = this.config?.providerConcurrency?.[provider]
31
+ if (providerLimit !== undefined) {
32
+ return providerLimit === 0 ? Infinity : providerLimit
33
+ }
34
+ const defaultLimit = this.config?.defaultConcurrency
35
+ if (defaultLimit !== undefined) {
36
+ return defaultLimit === 0 ? Infinity : defaultLimit
37
+ }
38
+ return 5
39
+ }
40
+
41
+ async acquire(model: string): Promise<void> {
42
+ const limit = this.getConcurrencyLimit(model)
43
+ if (limit === Infinity) {
44
+ return
45
+ }
46
+
47
+ const current = this.counts.get(model) ?? 0
48
+ if (current < limit) {
49
+ this.counts.set(model, current + 1)
50
+ return
51
+ }
52
+
53
+ return new Promise<void>((resolve, reject) => {
54
+ const queue = this.queues.get(model) ?? []
55
+
56
+ const entry: QueueEntry = {
57
+ resolve: () => {
58
+ if (entry.settled) return
59
+ entry.settled = true
60
+ resolve()
61
+ },
62
+ rawReject: reject,
63
+ settled: false,
64
+ }
65
+
66
+ queue.push(entry)
67
+ this.queues.set(model, queue)
68
+ })
69
+ }
70
+
71
+ release(model: string): void {
72
+ const limit = this.getConcurrencyLimit(model)
73
+ if (limit === Infinity) {
74
+ return
75
+ }
76
+
77
+ const queue = this.queues.get(model)
78
+
79
+ // Try to hand off to a waiting entry (skip any settled entries from cancelWaiters)
80
+ while (queue && queue.length > 0) {
81
+ const next = queue.shift()!
82
+ if (!next.settled) {
83
+ // Hand off the slot to this waiter (count stays the same)
84
+ next.resolve()
85
+ return
86
+ }
87
+ }
88
+
89
+ // No handoff occurred - decrement the count to free the slot
90
+ const current = this.counts.get(model) ?? 0
91
+ if (current > 0) {
92
+ this.counts.set(model, current - 1)
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Cancel all waiting acquires for a model. Used during cleanup.
98
+ */
99
+ cancelWaiters(model: string): void {
100
+ const queue = this.queues.get(model)
101
+ if (queue) {
102
+ for (const entry of queue) {
103
+ if (!entry.settled) {
104
+ entry.settled = true
105
+ entry.rawReject(new Error(`Concurrency queue cancelled for model: ${model}`))
106
+ }
107
+ }
108
+ this.queues.delete(model)
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Clear all state. Used during manager cleanup/shutdown.
114
+ * Cancels all pending waiters.
115
+ */
116
+ clear(): void {
117
+ for (const [model] of this.queues) {
118
+ this.cancelWaiters(model)
119
+ }
120
+ this.counts.clear()
121
+ this.queues.clear()
122
+ }
123
+
124
+ /**
125
+ * Get current count for a model (for testing/debugging)
126
+ */
127
+ getCount(model: string): number {
128
+ return this.counts.get(model) ?? 0
129
+ }
130
+
131
+ /**
132
+ * Get queue length for a model (for testing/debugging)
133
+ */
134
+ getQueueLength(model: string): number {
135
+ return this.queues.get(model)?.length ?? 0
136
+ }
137
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./types"
2
+ export { BackgroundManager } from "./manager"
3
+ export { ConcurrencyManager } from "./concurrency"