opencode-repos 0.1.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 (477) hide show
  1. package/AGENTS.md +180 -0
  2. package/README.md +103 -3
  3. package/TODO.md +3 -0
  4. package/bun.lock +0 -85
  5. package/index.ts +1585 -153
  6. package/oh-my-opencode/.github/FUNDING.yml +15 -0
  7. package/oh-my-opencode/.github/ISSUE_TEMPLATE/bug_report.yml +129 -0
  8. package/oh-my-opencode/.github/ISSUE_TEMPLATE/config.yml +8 -0
  9. package/oh-my-opencode/.github/ISSUE_TEMPLATE/feature_request.yml +100 -0
  10. package/oh-my-opencode/.github/ISSUE_TEMPLATE/general.yml +83 -0
  11. package/oh-my-opencode/.github/assets/google.jpg +0 -0
  12. package/oh-my-opencode/.github/assets/hero.jpg +0 -0
  13. package/oh-my-opencode/.github/assets/indent.jpg +0 -0
  14. package/oh-my-opencode/.github/assets/microsoft.jpg +0 -0
  15. package/oh-my-opencode/.github/assets/omo.png +0 -0
  16. package/oh-my-opencode/.github/assets/orchestrator-atlas.png +0 -0
  17. package/oh-my-opencode/.github/assets/sisyphus.png +0 -0
  18. package/oh-my-opencode/.github/assets/sisyphuslabs.png +0 -0
  19. package/oh-my-opencode/.github/pull_request_template.md +34 -0
  20. package/oh-my-opencode/.github/workflows/ci.yml +138 -0
  21. package/oh-my-opencode/.github/workflows/cla.yml +41 -0
  22. package/oh-my-opencode/.github/workflows/lint-workflows.yml +22 -0
  23. package/oh-my-opencode/.github/workflows/publish.yml +165 -0
  24. package/oh-my-opencode/.github/workflows/sisyphus-agent.yml +500 -0
  25. package/oh-my-opencode/.opencode/background-tasks.json +27 -0
  26. package/oh-my-opencode/.opencode/command/get-unpublished-changes.md +84 -0
  27. package/oh-my-opencode/.opencode/command/omomomo.md +37 -0
  28. package/oh-my-opencode/.opencode/command/publish.md +257 -0
  29. package/oh-my-opencode/AGENTS.md +179 -0
  30. package/oh-my-opencode/CLA.md +58 -0
  31. package/oh-my-opencode/CONTRIBUTING.md +268 -0
  32. package/oh-my-opencode/LICENSE.md +82 -0
  33. package/oh-my-opencode/README.ja.md +370 -0
  34. package/oh-my-opencode/README.md +376 -0
  35. package/oh-my-opencode/README.zh-cn.md +380 -0
  36. package/oh-my-opencode/assets/oh-my-opencode.schema.json +2171 -0
  37. package/oh-my-opencode/bin/oh-my-opencode.js +80 -0
  38. package/oh-my-opencode/bin/platform.js +38 -0
  39. package/oh-my-opencode/bin/platform.test.ts +148 -0
  40. package/oh-my-opencode/bun.lock +314 -0
  41. package/oh-my-opencode/bunfig.toml +2 -0
  42. package/oh-my-opencode/docs/category-skill-guide.md +200 -0
  43. package/oh-my-opencode/docs/cli-guide.md +272 -0
  44. package/oh-my-opencode/docs/configurations.md +654 -0
  45. package/oh-my-opencode/docs/features.md +550 -0
  46. package/oh-my-opencode/docs/guide/installation.md +288 -0
  47. package/oh-my-opencode/docs/guide/overview.md +97 -0
  48. package/oh-my-opencode/docs/guide/understanding-orchestration-system.md +445 -0
  49. package/oh-my-opencode/docs/orchestration-guide.md +152 -0
  50. package/oh-my-opencode/docs/ultrawork-manifesto.md +197 -0
  51. package/oh-my-opencode/package.json +89 -0
  52. package/oh-my-opencode/packages/darwin-arm64/bin/.gitkeep +0 -0
  53. package/oh-my-opencode/packages/darwin-arm64/package.json +22 -0
  54. package/oh-my-opencode/packages/darwin-x64/bin/.gitkeep +0 -0
  55. package/oh-my-opencode/packages/darwin-x64/package.json +22 -0
  56. package/oh-my-opencode/packages/linux-arm64/bin/.gitkeep +0 -0
  57. package/oh-my-opencode/packages/linux-arm64/package.json +25 -0
  58. package/oh-my-opencode/packages/linux-arm64-musl/bin/.gitkeep +0 -0
  59. package/oh-my-opencode/packages/linux-arm64-musl/package.json +25 -0
  60. package/oh-my-opencode/packages/linux-x64/bin/.gitkeep +0 -0
  61. package/oh-my-opencode/packages/linux-x64/package.json +25 -0
  62. package/oh-my-opencode/packages/linux-x64-musl/bin/.gitkeep +0 -0
  63. package/oh-my-opencode/packages/linux-x64-musl/package.json +25 -0
  64. package/oh-my-opencode/packages/windows-x64/bin/.gitkeep +0 -0
  65. package/oh-my-opencode/packages/windows-x64/package.json +22 -0
  66. package/oh-my-opencode/postinstall.mjs +43 -0
  67. package/oh-my-opencode/script/build-binaries.ts +103 -0
  68. package/oh-my-opencode/script/build-schema.ts +28 -0
  69. package/oh-my-opencode/script/generate-changelog.ts +92 -0
  70. package/oh-my-opencode/script/publish.ts +344 -0
  71. package/oh-my-opencode/signatures/cla.json +676 -0
  72. package/oh-my-opencode/src/agents/AGENTS.md +67 -0
  73. package/oh-my-opencode/src/agents/atlas.ts +1383 -0
  74. package/oh-my-opencode/src/agents/dynamic-agent-prompt-builder.ts +400 -0
  75. package/oh-my-opencode/src/agents/explore.ts +122 -0
  76. package/oh-my-opencode/src/agents/index.ts +13 -0
  77. package/oh-my-opencode/src/agents/librarian.ts +326 -0
  78. package/oh-my-opencode/src/agents/metis.ts +315 -0
  79. package/oh-my-opencode/src/agents/momus.test.ts +57 -0
  80. package/oh-my-opencode/src/agents/momus.ts +444 -0
  81. package/oh-my-opencode/src/agents/multimodal-looker.ts +56 -0
  82. package/oh-my-opencode/src/agents/oracle.ts +122 -0
  83. package/oh-my-opencode/src/agents/prometheus-prompt.test.ts +22 -0
  84. package/oh-my-opencode/src/agents/prometheus-prompt.ts +1196 -0
  85. package/oh-my-opencode/src/agents/sisyphus-junior.test.ts +232 -0
  86. package/oh-my-opencode/src/agents/sisyphus-junior.ts +134 -0
  87. package/oh-my-opencode/src/agents/sisyphus.ts +633 -0
  88. package/oh-my-opencode/src/agents/types.ts +80 -0
  89. package/oh-my-opencode/src/agents/utils.test.ts +311 -0
  90. package/oh-my-opencode/src/agents/utils.ts +240 -0
  91. package/oh-my-opencode/src/cli/AGENTS.md +91 -0
  92. package/oh-my-opencode/src/cli/config-manager.test.ts +364 -0
  93. package/oh-my-opencode/src/cli/config-manager.ts +641 -0
  94. package/oh-my-opencode/src/cli/doctor/checks/auth.test.ts +114 -0
  95. package/oh-my-opencode/src/cli/doctor/checks/auth.ts +115 -0
  96. package/oh-my-opencode/src/cli/doctor/checks/config.test.ts +103 -0
  97. package/oh-my-opencode/src/cli/doctor/checks/config.ts +123 -0
  98. package/oh-my-opencode/src/cli/doctor/checks/dependencies.test.ts +152 -0
  99. package/oh-my-opencode/src/cli/doctor/checks/dependencies.ts +163 -0
  100. package/oh-my-opencode/src/cli/doctor/checks/gh.test.ts +151 -0
  101. package/oh-my-opencode/src/cli/doctor/checks/gh.ts +171 -0
  102. package/oh-my-opencode/src/cli/doctor/checks/index.ts +34 -0
  103. package/oh-my-opencode/src/cli/doctor/checks/lsp.test.ts +134 -0
  104. package/oh-my-opencode/src/cli/doctor/checks/lsp.ts +77 -0
  105. package/oh-my-opencode/src/cli/doctor/checks/mcp.test.ts +115 -0
  106. package/oh-my-opencode/src/cli/doctor/checks/mcp.ts +128 -0
  107. package/oh-my-opencode/src/cli/doctor/checks/opencode.test.ts +227 -0
  108. package/oh-my-opencode/src/cli/doctor/checks/opencode.ts +178 -0
  109. package/oh-my-opencode/src/cli/doctor/checks/plugin.test.ts +109 -0
  110. package/oh-my-opencode/src/cli/doctor/checks/plugin.ts +124 -0
  111. package/oh-my-opencode/src/cli/doctor/checks/version.test.ts +148 -0
  112. package/oh-my-opencode/src/cli/doctor/checks/version.ts +135 -0
  113. package/oh-my-opencode/src/cli/doctor/constants.ts +72 -0
  114. package/oh-my-opencode/src/cli/doctor/formatter.test.ts +218 -0
  115. package/oh-my-opencode/src/cli/doctor/formatter.ts +140 -0
  116. package/oh-my-opencode/src/cli/doctor/index.ts +11 -0
  117. package/oh-my-opencode/src/cli/doctor/runner.test.ts +153 -0
  118. package/oh-my-opencode/src/cli/doctor/runner.ts +132 -0
  119. package/oh-my-opencode/src/cli/doctor/types.ts +113 -0
  120. package/oh-my-opencode/src/cli/get-local-version/formatter.ts +66 -0
  121. package/oh-my-opencode/src/cli/get-local-version/index.ts +106 -0
  122. package/oh-my-opencode/src/cli/get-local-version/types.ts +14 -0
  123. package/oh-my-opencode/src/cli/index.ts +153 -0
  124. package/oh-my-opencode/src/cli/install.ts +523 -0
  125. package/oh-my-opencode/src/cli/model-fallback.ts +246 -0
  126. package/oh-my-opencode/src/cli/run/completion.test.ts +170 -0
  127. package/oh-my-opencode/src/cli/run/completion.ts +79 -0
  128. package/oh-my-opencode/src/cli/run/events.test.ts +155 -0
  129. package/oh-my-opencode/src/cli/run/events.ts +325 -0
  130. package/oh-my-opencode/src/cli/run/index.ts +2 -0
  131. package/oh-my-opencode/src/cli/run/runner.ts +159 -0
  132. package/oh-my-opencode/src/cli/run/types.ts +76 -0
  133. package/oh-my-opencode/src/cli/types.ts +40 -0
  134. package/oh-my-opencode/src/config/index.ts +26 -0
  135. package/oh-my-opencode/src/config/schema.test.ts +444 -0
  136. package/oh-my-opencode/src/config/schema.ts +339 -0
  137. package/oh-my-opencode/src/features/AGENTS.md +77 -0
  138. package/oh-my-opencode/src/features/background-agent/concurrency.test.ts +418 -0
  139. package/oh-my-opencode/src/features/background-agent/concurrency.ts +137 -0
  140. package/oh-my-opencode/src/features/background-agent/index.ts +3 -0
  141. package/oh-my-opencode/src/features/background-agent/manager.test.ts +1928 -0
  142. package/oh-my-opencode/src/features/background-agent/manager.ts +1335 -0
  143. package/oh-my-opencode/src/features/background-agent/types.ts +66 -0
  144. package/oh-my-opencode/src/features/boulder-state/constants.ts +13 -0
  145. package/oh-my-opencode/src/features/boulder-state/index.ts +3 -0
  146. package/oh-my-opencode/src/features/boulder-state/storage.test.ts +250 -0
  147. package/oh-my-opencode/src/features/boulder-state/storage.ts +150 -0
  148. package/oh-my-opencode/src/features/boulder-state/types.ts +26 -0
  149. package/oh-my-opencode/src/features/builtin-commands/commands.ts +89 -0
  150. package/oh-my-opencode/src/features/builtin-commands/index.ts +2 -0
  151. package/oh-my-opencode/src/features/builtin-commands/templates/init-deep.ts +300 -0
  152. package/oh-my-opencode/src/features/builtin-commands/templates/ralph-loop.ts +38 -0
  153. package/oh-my-opencode/src/features/builtin-commands/templates/refactor.ts +619 -0
  154. package/oh-my-opencode/src/features/builtin-commands/templates/start-work.ts +72 -0
  155. package/oh-my-opencode/src/features/builtin-commands/types.ts +9 -0
  156. package/oh-my-opencode/src/features/builtin-skills/frontend-ui-ux/SKILL.md +78 -0
  157. package/oh-my-opencode/src/features/builtin-skills/git-master/SKILL.md +1105 -0
  158. package/oh-my-opencode/src/features/builtin-skills/index.ts +2 -0
  159. package/oh-my-opencode/src/features/builtin-skills/skills.ts +1203 -0
  160. package/oh-my-opencode/src/features/builtin-skills/types.ts +16 -0
  161. package/oh-my-opencode/src/features/claude-code-agent-loader/index.ts +2 -0
  162. package/oh-my-opencode/src/features/claude-code-agent-loader/loader.ts +90 -0
  163. package/oh-my-opencode/src/features/claude-code-agent-loader/types.ts +17 -0
  164. package/oh-my-opencode/src/features/claude-code-command-loader/index.ts +2 -0
  165. package/oh-my-opencode/src/features/claude-code-command-loader/loader.ts +144 -0
  166. package/oh-my-opencode/src/features/claude-code-command-loader/types.ts +46 -0
  167. package/oh-my-opencode/src/features/claude-code-mcp-loader/env-expander.ts +27 -0
  168. package/oh-my-opencode/src/features/claude-code-mcp-loader/index.ts +11 -0
  169. package/oh-my-opencode/src/features/claude-code-mcp-loader/loader.test.ts +162 -0
  170. package/oh-my-opencode/src/features/claude-code-mcp-loader/loader.ts +113 -0
  171. package/oh-my-opencode/src/features/claude-code-mcp-loader/transformer.ts +53 -0
  172. package/oh-my-opencode/src/features/claude-code-mcp-loader/types.ts +42 -0
  173. package/oh-my-opencode/src/features/claude-code-plugin-loader/index.ts +3 -0
  174. package/oh-my-opencode/src/features/claude-code-plugin-loader/loader.ts +486 -0
  175. package/oh-my-opencode/src/features/claude-code-plugin-loader/types.ts +210 -0
  176. package/oh-my-opencode/src/features/claude-code-session-state/index.ts +1 -0
  177. package/oh-my-opencode/src/features/claude-code-session-state/state.test.ts +126 -0
  178. package/oh-my-opencode/src/features/claude-code-session-state/state.ts +37 -0
  179. package/oh-my-opencode/src/features/context-injector/collector.test.ts +330 -0
  180. package/oh-my-opencode/src/features/context-injector/collector.ts +85 -0
  181. package/oh-my-opencode/src/features/context-injector/index.ts +14 -0
  182. package/oh-my-opencode/src/features/context-injector/injector.test.ts +122 -0
  183. package/oh-my-opencode/src/features/context-injector/injector.ts +167 -0
  184. package/oh-my-opencode/src/features/context-injector/types.ts +91 -0
  185. package/oh-my-opencode/src/features/hook-message-injector/constants.ts +6 -0
  186. package/oh-my-opencode/src/features/hook-message-injector/index.ts +4 -0
  187. package/oh-my-opencode/src/features/hook-message-injector/injector.ts +195 -0
  188. package/oh-my-opencode/src/features/hook-message-injector/types.ts +47 -0
  189. package/oh-my-opencode/src/features/opencode-skill-loader/async-loader.test.ts +448 -0
  190. package/oh-my-opencode/src/features/opencode-skill-loader/async-loader.ts +180 -0
  191. package/oh-my-opencode/src/features/opencode-skill-loader/blocking.test.ts +210 -0
  192. package/oh-my-opencode/src/features/opencode-skill-loader/blocking.ts +62 -0
  193. package/oh-my-opencode/src/features/opencode-skill-loader/discover-worker.ts +59 -0
  194. package/oh-my-opencode/src/features/opencode-skill-loader/index.ts +4 -0
  195. package/oh-my-opencode/src/features/opencode-skill-loader/loader.test.ts +273 -0
  196. package/oh-my-opencode/src/features/opencode-skill-loader/loader.ts +259 -0
  197. package/oh-my-opencode/src/features/opencode-skill-loader/merger.ts +267 -0
  198. package/oh-my-opencode/src/features/opencode-skill-loader/skill-content.test.ts +267 -0
  199. package/oh-my-opencode/src/features/opencode-skill-loader/skill-content.ts +206 -0
  200. package/oh-my-opencode/src/features/opencode-skill-loader/types.ts +38 -0
  201. package/oh-my-opencode/src/features/skill-mcp-manager/env-cleaner.test.ts +201 -0
  202. package/oh-my-opencode/src/features/skill-mcp-manager/env-cleaner.ts +27 -0
  203. package/oh-my-opencode/src/features/skill-mcp-manager/index.ts +2 -0
  204. package/oh-my-opencode/src/features/skill-mcp-manager/manager.test.ts +611 -0
  205. package/oh-my-opencode/src/features/skill-mcp-manager/manager.ts +520 -0
  206. package/oh-my-opencode/src/features/skill-mcp-manager/types.ts +14 -0
  207. package/oh-my-opencode/src/features/task-toast-manager/index.ts +2 -0
  208. package/oh-my-opencode/src/features/task-toast-manager/manager.test.ts +249 -0
  209. package/oh-my-opencode/src/features/task-toast-manager/manager.ts +215 -0
  210. package/oh-my-opencode/src/features/task-toast-manager/types.ts +24 -0
  211. package/oh-my-opencode/src/hooks/AGENTS.md +73 -0
  212. package/oh-my-opencode/src/hooks/agent-usage-reminder/constants.ts +54 -0
  213. package/oh-my-opencode/src/hooks/agent-usage-reminder/index.ts +109 -0
  214. package/oh-my-opencode/src/hooks/agent-usage-reminder/storage.ts +42 -0
  215. package/oh-my-opencode/src/hooks/agent-usage-reminder/types.ts +6 -0
  216. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/executor.test.ts +307 -0
  217. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/executor.ts +485 -0
  218. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/index.ts +151 -0
  219. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/parser.ts +201 -0
  220. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/pruning-deduplication.test.ts +33 -0
  221. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/pruning-deduplication.ts +184 -0
  222. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/pruning-types.ts +44 -0
  223. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/storage.test.ts +77 -0
  224. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/storage.ts +250 -0
  225. package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/types.ts +42 -0
  226. package/oh-my-opencode/src/hooks/atlas/index.test.ts +953 -0
  227. package/oh-my-opencode/src/hooks/atlas/index.ts +771 -0
  228. package/oh-my-opencode/src/hooks/auto-slash-command/constants.ts +12 -0
  229. package/oh-my-opencode/src/hooks/auto-slash-command/detector.test.ts +296 -0
  230. package/oh-my-opencode/src/hooks/auto-slash-command/detector.ts +65 -0
  231. package/oh-my-opencode/src/hooks/auto-slash-command/executor.ts +205 -0
  232. package/oh-my-opencode/src/hooks/auto-slash-command/index.test.ts +254 -0
  233. package/oh-my-opencode/src/hooks/auto-slash-command/index.ts +89 -0
  234. package/oh-my-opencode/src/hooks/auto-slash-command/types.ts +23 -0
  235. package/oh-my-opencode/src/hooks/auto-update-checker/cache.ts +93 -0
  236. package/oh-my-opencode/src/hooks/auto-update-checker/checker.test.ts +24 -0
  237. package/oh-my-opencode/src/hooks/auto-update-checker/checker.ts +284 -0
  238. package/oh-my-opencode/src/hooks/auto-update-checker/constants.ts +64 -0
  239. package/oh-my-opencode/src/hooks/auto-update-checker/index.test.ts +254 -0
  240. package/oh-my-opencode/src/hooks/auto-update-checker/index.ts +260 -0
  241. package/oh-my-opencode/src/hooks/auto-update-checker/types.ts +29 -0
  242. package/oh-my-opencode/src/hooks/background-compaction/index.ts +87 -0
  243. package/oh-my-opencode/src/hooks/background-notification/index.ts +28 -0
  244. package/oh-my-opencode/src/hooks/background-notification/types.ts +5 -0
  245. package/oh-my-opencode/src/hooks/claude-code-hooks/AGENTS.md +70 -0
  246. package/oh-my-opencode/src/hooks/claude-code-hooks/config-loader.ts +107 -0
  247. package/oh-my-opencode/src/hooks/claude-code-hooks/config.ts +103 -0
  248. package/oh-my-opencode/src/hooks/claude-code-hooks/index.ts +401 -0
  249. package/oh-my-opencode/src/hooks/claude-code-hooks/plugin-config.ts +12 -0
  250. package/oh-my-opencode/src/hooks/claude-code-hooks/post-tool-use.ts +199 -0
  251. package/oh-my-opencode/src/hooks/claude-code-hooks/pre-compact.ts +109 -0
  252. package/oh-my-opencode/src/hooks/claude-code-hooks/pre-tool-use.ts +172 -0
  253. package/oh-my-opencode/src/hooks/claude-code-hooks/stop.ts +118 -0
  254. package/oh-my-opencode/src/hooks/claude-code-hooks/todo.ts +76 -0
  255. package/oh-my-opencode/src/hooks/claude-code-hooks/tool-input-cache.ts +47 -0
  256. package/oh-my-opencode/src/hooks/claude-code-hooks/transcript.ts +252 -0
  257. package/oh-my-opencode/src/hooks/claude-code-hooks/types.ts +204 -0
  258. package/oh-my-opencode/src/hooks/claude-code-hooks/user-prompt-submit.ts +117 -0
  259. package/oh-my-opencode/src/hooks/comment-checker/cli.test.ts +68 -0
  260. package/oh-my-opencode/src/hooks/comment-checker/cli.ts +221 -0
  261. package/oh-my-opencode/src/hooks/comment-checker/downloader.ts +196 -0
  262. package/oh-my-opencode/src/hooks/comment-checker/index.ts +171 -0
  263. package/oh-my-opencode/src/hooks/comment-checker/types.ts +33 -0
  264. package/oh-my-opencode/src/hooks/compaction-context-injector/index.ts +61 -0
  265. package/oh-my-opencode/src/hooks/context-window-monitor.ts +99 -0
  266. package/oh-my-opencode/src/hooks/delegate-task-retry/index.test.ts +119 -0
  267. package/oh-my-opencode/src/hooks/delegate-task-retry/index.ts +136 -0
  268. package/oh-my-opencode/src/hooks/directory-agents-injector/constants.ts +9 -0
  269. package/oh-my-opencode/src/hooks/directory-agents-injector/index.ts +182 -0
  270. package/oh-my-opencode/src/hooks/directory-agents-injector/storage.ts +48 -0
  271. package/oh-my-opencode/src/hooks/directory-agents-injector/types.ts +5 -0
  272. package/oh-my-opencode/src/hooks/directory-readme-injector/constants.ts +9 -0
  273. package/oh-my-opencode/src/hooks/directory-readme-injector/index.ts +177 -0
  274. package/oh-my-opencode/src/hooks/directory-readme-injector/storage.ts +48 -0
  275. package/oh-my-opencode/src/hooks/directory-readme-injector/types.ts +5 -0
  276. package/oh-my-opencode/src/hooks/edit-error-recovery/index.test.ts +126 -0
  277. package/oh-my-opencode/src/hooks/edit-error-recovery/index.ts +57 -0
  278. package/oh-my-opencode/src/hooks/empty-task-response-detector.ts +27 -0
  279. package/oh-my-opencode/src/hooks/index.ts +32 -0
  280. package/oh-my-opencode/src/hooks/interactive-bash-session/constants.ts +15 -0
  281. package/oh-my-opencode/src/hooks/interactive-bash-session/index.ts +262 -0
  282. package/oh-my-opencode/src/hooks/interactive-bash-session/storage.ts +59 -0
  283. package/oh-my-opencode/src/hooks/interactive-bash-session/types.ts +11 -0
  284. package/oh-my-opencode/src/hooks/keyword-detector/constants.ts +300 -0
  285. package/oh-my-opencode/src/hooks/keyword-detector/detector.ts +52 -0
  286. package/oh-my-opencode/src/hooks/keyword-detector/index.test.ts +529 -0
  287. package/oh-my-opencode/src/hooks/keyword-detector/index.ts +100 -0
  288. package/oh-my-opencode/src/hooks/keyword-detector/types.ts +4 -0
  289. package/oh-my-opencode/src/hooks/non-interactive-env/constants.ts +70 -0
  290. package/oh-my-opencode/src/hooks/non-interactive-env/detector.ts +19 -0
  291. package/oh-my-opencode/src/hooks/non-interactive-env/index.test.ts +323 -0
  292. package/oh-my-opencode/src/hooks/non-interactive-env/index.ts +63 -0
  293. package/oh-my-opencode/src/hooks/non-interactive-env/types.ts +3 -0
  294. package/oh-my-opencode/src/hooks/prometheus-md-only/constants.ts +32 -0
  295. package/oh-my-opencode/src/hooks/prometheus-md-only/index.test.ts +488 -0
  296. package/oh-my-opencode/src/hooks/prometheus-md-only/index.ts +136 -0
  297. package/oh-my-opencode/src/hooks/ralph-loop/constants.ts +5 -0
  298. package/oh-my-opencode/src/hooks/ralph-loop/index.test.ts +835 -0
  299. package/oh-my-opencode/src/hooks/ralph-loop/index.ts +417 -0
  300. package/oh-my-opencode/src/hooks/ralph-loop/storage.ts +115 -0
  301. package/oh-my-opencode/src/hooks/ralph-loop/types.ts +19 -0
  302. package/oh-my-opencode/src/hooks/rules-injector/constants.ts +30 -0
  303. package/oh-my-opencode/src/hooks/rules-injector/finder.test.ts +381 -0
  304. package/oh-my-opencode/src/hooks/rules-injector/finder.ts +263 -0
  305. package/oh-my-opencode/src/hooks/rules-injector/index.ts +223 -0
  306. package/oh-my-opencode/src/hooks/rules-injector/matcher.ts +63 -0
  307. package/oh-my-opencode/src/hooks/rules-injector/parser.test.ts +226 -0
  308. package/oh-my-opencode/src/hooks/rules-injector/parser.ts +211 -0
  309. package/oh-my-opencode/src/hooks/rules-injector/storage.ts +59 -0
  310. package/oh-my-opencode/src/hooks/rules-injector/types.ts +57 -0
  311. package/oh-my-opencode/src/hooks/session-notification-utils.ts +140 -0
  312. package/oh-my-opencode/src/hooks/session-notification.test.ts +361 -0
  313. package/oh-my-opencode/src/hooks/session-notification.ts +330 -0
  314. package/oh-my-opencode/src/hooks/session-recovery/constants.ts +10 -0
  315. package/oh-my-opencode/src/hooks/session-recovery/index.test.ts +223 -0
  316. package/oh-my-opencode/src/hooks/session-recovery/index.ts +435 -0
  317. package/oh-my-opencode/src/hooks/session-recovery/storage.ts +390 -0
  318. package/oh-my-opencode/src/hooks/session-recovery/types.ts +98 -0
  319. package/oh-my-opencode/src/hooks/start-work/index.test.ts +402 -0
  320. package/oh-my-opencode/src/hooks/start-work/index.ts +242 -0
  321. package/oh-my-opencode/src/hooks/task-resume-info/index.ts +36 -0
  322. package/oh-my-opencode/src/hooks/think-mode/detector.ts +57 -0
  323. package/oh-my-opencode/src/hooks/think-mode/index.test.ts +353 -0
  324. package/oh-my-opencode/src/hooks/think-mode/index.ts +89 -0
  325. package/oh-my-opencode/src/hooks/think-mode/switcher.test.ts +461 -0
  326. package/oh-my-opencode/src/hooks/think-mode/switcher.ts +222 -0
  327. package/oh-my-opencode/src/hooks/think-mode/types.ts +21 -0
  328. package/oh-my-opencode/src/hooks/thinking-block-validator/index.ts +171 -0
  329. package/oh-my-opencode/src/hooks/todo-continuation-enforcer.test.ts +876 -0
  330. package/oh-my-opencode/src/hooks/todo-continuation-enforcer.ts +480 -0
  331. package/oh-my-opencode/src/hooks/tool-output-truncator.test.ts +168 -0
  332. package/oh-my-opencode/src/hooks/tool-output-truncator.ts +61 -0
  333. package/oh-my-opencode/src/index.ts +589 -0
  334. package/oh-my-opencode/src/mcp/AGENTS.md +70 -0
  335. package/oh-my-opencode/src/mcp/context7.ts +6 -0
  336. package/oh-my-opencode/src/mcp/grep-app.ts +6 -0
  337. package/oh-my-opencode/src/mcp/index.test.ts +86 -0
  338. package/oh-my-opencode/src/mcp/index.ts +32 -0
  339. package/oh-my-opencode/src/mcp/types.ts +9 -0
  340. package/oh-my-opencode/src/mcp/websearch.ts +10 -0
  341. package/oh-my-opencode/src/plugin-config.test.ts +119 -0
  342. package/oh-my-opencode/src/plugin-config.ts +135 -0
  343. package/oh-my-opencode/src/plugin-handlers/config-handler.test.ts +103 -0
  344. package/oh-my-opencode/src/plugin-handlers/config-handler.ts +399 -0
  345. package/oh-my-opencode/src/plugin-handlers/index.ts +1 -0
  346. package/oh-my-opencode/src/plugin-state.ts +30 -0
  347. package/oh-my-opencode/src/shared/AGENTS.md +63 -0
  348. package/oh-my-opencode/src/shared/agent-tool-restrictions.ts +44 -0
  349. package/oh-my-opencode/src/shared/agent-variant.test.ts +83 -0
  350. package/oh-my-opencode/src/shared/agent-variant.ts +40 -0
  351. package/oh-my-opencode/src/shared/claude-config-dir.test.ts +60 -0
  352. package/oh-my-opencode/src/shared/claude-config-dir.ts +11 -0
  353. package/oh-my-opencode/src/shared/command-executor.ts +225 -0
  354. package/oh-my-opencode/src/shared/config-errors.ts +18 -0
  355. package/oh-my-opencode/src/shared/config-path.ts +47 -0
  356. package/oh-my-opencode/src/shared/data-path.ts +22 -0
  357. package/oh-my-opencode/src/shared/deep-merge.test.ts +336 -0
  358. package/oh-my-opencode/src/shared/deep-merge.ts +53 -0
  359. package/oh-my-opencode/src/shared/dynamic-truncator.ts +193 -0
  360. package/oh-my-opencode/src/shared/external-plugin-detector.test.ts +133 -0
  361. package/oh-my-opencode/src/shared/external-plugin-detector.ts +132 -0
  362. package/oh-my-opencode/src/shared/file-reference-resolver.ts +85 -0
  363. package/oh-my-opencode/src/shared/file-utils.ts +40 -0
  364. package/oh-my-opencode/src/shared/first-message-variant.test.ts +32 -0
  365. package/oh-my-opencode/src/shared/first-message-variant.ts +28 -0
  366. package/oh-my-opencode/src/shared/frontmatter.test.ts +262 -0
  367. package/oh-my-opencode/src/shared/frontmatter.ts +31 -0
  368. package/oh-my-opencode/src/shared/hook-disabled.ts +22 -0
  369. package/oh-my-opencode/src/shared/index.ts +29 -0
  370. package/oh-my-opencode/src/shared/jsonc-parser.test.ts +266 -0
  371. package/oh-my-opencode/src/shared/jsonc-parser.ts +66 -0
  372. package/oh-my-opencode/src/shared/logger.ts +20 -0
  373. package/oh-my-opencode/src/shared/migration.test.ts +602 -0
  374. package/oh-my-opencode/src/shared/migration.ts +191 -0
  375. package/oh-my-opencode/src/shared/model-resolver.test.ts +101 -0
  376. package/oh-my-opencode/src/shared/model-resolver.ts +35 -0
  377. package/oh-my-opencode/src/shared/model-sanitizer.ts +12 -0
  378. package/oh-my-opencode/src/shared/opencode-config-dir.test.ts +318 -0
  379. package/oh-my-opencode/src/shared/opencode-config-dir.ts +142 -0
  380. package/oh-my-opencode/src/shared/opencode-version.test.ts +223 -0
  381. package/oh-my-opencode/src/shared/opencode-version.ts +72 -0
  382. package/oh-my-opencode/src/shared/pattern-matcher.ts +29 -0
  383. package/oh-my-opencode/src/shared/permission-compat.test.ts +134 -0
  384. package/oh-my-opencode/src/shared/permission-compat.ts +77 -0
  385. package/oh-my-opencode/src/shared/session-cursor.test.ts +66 -0
  386. package/oh-my-opencode/src/shared/session-cursor.ts +85 -0
  387. package/oh-my-opencode/src/shared/shell-env.test.ts +278 -0
  388. package/oh-my-opencode/src/shared/shell-env.ts +111 -0
  389. package/oh-my-opencode/src/shared/snake-case.ts +49 -0
  390. package/oh-my-opencode/src/shared/system-directive.ts +40 -0
  391. package/oh-my-opencode/src/shared/tool-name.ts +26 -0
  392. package/oh-my-opencode/src/shared/zip-extractor.ts +83 -0
  393. package/oh-my-opencode/src/tools/AGENTS.md +74 -0
  394. package/oh-my-opencode/src/tools/ast-grep/cli.ts +230 -0
  395. package/oh-my-opencode/src/tools/ast-grep/constants.ts +261 -0
  396. package/oh-my-opencode/src/tools/ast-grep/downloader.ts +128 -0
  397. package/oh-my-opencode/src/tools/ast-grep/index.ts +13 -0
  398. package/oh-my-opencode/src/tools/ast-grep/tools.ts +112 -0
  399. package/oh-my-opencode/src/tools/ast-grep/types.ts +61 -0
  400. package/oh-my-opencode/src/tools/ast-grep/utils.ts +102 -0
  401. package/oh-my-opencode/src/tools/background-task/constants.ts +7 -0
  402. package/oh-my-opencode/src/tools/background-task/index.ts +7 -0
  403. package/oh-my-opencode/src/tools/background-task/tools.ts +479 -0
  404. package/oh-my-opencode/src/tools/background-task/types.ts +16 -0
  405. package/oh-my-opencode/src/tools/call-omo-agent/constants.ts +7 -0
  406. package/oh-my-opencode/src/tools/call-omo-agent/index.ts +3 -0
  407. package/oh-my-opencode/src/tools/call-omo-agent/tools.ts +338 -0
  408. package/oh-my-opencode/src/tools/call-omo-agent/types.ts +27 -0
  409. package/oh-my-opencode/src/tools/delegate-task/constants.ts +205 -0
  410. package/oh-my-opencode/src/tools/delegate-task/index.ts +3 -0
  411. package/oh-my-opencode/src/tools/delegate-task/tools.test.ts +1575 -0
  412. package/oh-my-opencode/src/tools/delegate-task/tools.ts +885 -0
  413. package/oh-my-opencode/src/tools/delegate-task/types.ts +9 -0
  414. package/oh-my-opencode/src/tools/glob/cli.test.ts +158 -0
  415. package/oh-my-opencode/src/tools/glob/cli.ts +191 -0
  416. package/oh-my-opencode/src/tools/glob/constants.ts +12 -0
  417. package/oh-my-opencode/src/tools/glob/index.ts +3 -0
  418. package/oh-my-opencode/src/tools/glob/tools.ts +41 -0
  419. package/oh-my-opencode/src/tools/glob/types.ts +22 -0
  420. package/oh-my-opencode/src/tools/glob/utils.ts +26 -0
  421. package/oh-my-opencode/src/tools/grep/cli.ts +229 -0
  422. package/oh-my-opencode/src/tools/grep/constants.ts +127 -0
  423. package/oh-my-opencode/src/tools/grep/downloader.test.ts +103 -0
  424. package/oh-my-opencode/src/tools/grep/downloader.ts +145 -0
  425. package/oh-my-opencode/src/tools/grep/index.ts +3 -0
  426. package/oh-my-opencode/src/tools/grep/tools.ts +40 -0
  427. package/oh-my-opencode/src/tools/grep/types.ts +39 -0
  428. package/oh-my-opencode/src/tools/grep/utils.ts +53 -0
  429. package/oh-my-opencode/src/tools/index.ts +72 -0
  430. package/oh-my-opencode/src/tools/interactive-bash/constants.ts +18 -0
  431. package/oh-my-opencode/src/tools/interactive-bash/index.ts +4 -0
  432. package/oh-my-opencode/src/tools/interactive-bash/tools.ts +126 -0
  433. package/oh-my-opencode/src/tools/interactive-bash/utils.ts +71 -0
  434. package/oh-my-opencode/src/tools/look-at/constants.ts +3 -0
  435. package/oh-my-opencode/src/tools/look-at/index.ts +3 -0
  436. package/oh-my-opencode/src/tools/look-at/tools.test.ts +73 -0
  437. package/oh-my-opencode/src/tools/look-at/tools.ts +173 -0
  438. package/oh-my-opencode/src/tools/look-at/types.ts +4 -0
  439. package/oh-my-opencode/src/tools/lsp/client.ts +596 -0
  440. package/oh-my-opencode/src/tools/lsp/config.test.ts +130 -0
  441. package/oh-my-opencode/src/tools/lsp/config.ts +285 -0
  442. package/oh-my-opencode/src/tools/lsp/constants.ts +390 -0
  443. package/oh-my-opencode/src/tools/lsp/index.ts +7 -0
  444. package/oh-my-opencode/src/tools/lsp/tools.ts +261 -0
  445. package/oh-my-opencode/src/tools/lsp/types.ts +124 -0
  446. package/oh-my-opencode/src/tools/lsp/utils.ts +406 -0
  447. package/oh-my-opencode/src/tools/session-manager/constants.ts +97 -0
  448. package/oh-my-opencode/src/tools/session-manager/index.ts +3 -0
  449. package/oh-my-opencode/src/tools/session-manager/storage.test.ts +315 -0
  450. package/oh-my-opencode/src/tools/session-manager/storage.ts +238 -0
  451. package/oh-my-opencode/src/tools/session-manager/tools.test.ts +124 -0
  452. package/oh-my-opencode/src/tools/session-manager/tools.ts +146 -0
  453. package/oh-my-opencode/src/tools/session-manager/types.ts +99 -0
  454. package/oh-my-opencode/src/tools/session-manager/utils.test.ts +160 -0
  455. package/oh-my-opencode/src/tools/session-manager/utils.ts +199 -0
  456. package/oh-my-opencode/src/tools/skill/constants.ts +8 -0
  457. package/oh-my-opencode/src/tools/skill/index.ts +3 -0
  458. package/oh-my-opencode/src/tools/skill/tools.test.ts +239 -0
  459. package/oh-my-opencode/src/tools/skill/tools.ts +200 -0
  460. package/oh-my-opencode/src/tools/skill/types.ts +31 -0
  461. package/oh-my-opencode/src/tools/skill-mcp/constants.ts +3 -0
  462. package/oh-my-opencode/src/tools/skill-mcp/index.ts +3 -0
  463. package/oh-my-opencode/src/tools/skill-mcp/tools.test.ts +215 -0
  464. package/oh-my-opencode/src/tools/skill-mcp/tools.ts +172 -0
  465. package/oh-my-opencode/src/tools/skill-mcp/types.ts +8 -0
  466. package/oh-my-opencode/src/tools/slashcommand/index.ts +2 -0
  467. package/oh-my-opencode/src/tools/slashcommand/tools.ts +252 -0
  468. package/oh-my-opencode/src/tools/slashcommand/types.ts +28 -0
  469. package/oh-my-opencode/test-setup.ts +6 -0
  470. package/oh-my-opencode/tsconfig.json +20 -0
  471. package/package.json +1 -4
  472. package/src/__tests__/git.test.ts +7 -2
  473. package/src/__tests__/manifest.test.ts +5 -5
  474. package/src/agents/repo-explorer.ts +2 -1
  475. package/src/git.ts +18 -3
  476. package/src/manifest.ts +22 -15
  477. package/src/scanner.ts +41 -32
@@ -0,0 +1,85 @@
1
+ import type {
2
+ ContextEntry,
3
+ ContextPriority,
4
+ PendingContext,
5
+ RegisterContextOptions,
6
+ } from "./types"
7
+
8
+ const PRIORITY_ORDER: Record<ContextPriority, number> = {
9
+ critical: 0,
10
+ high: 1,
11
+ normal: 2,
12
+ low: 3,
13
+ }
14
+
15
+ const CONTEXT_SEPARATOR = "\n\n---\n\n"
16
+
17
+ export class ContextCollector {
18
+ private sessions: Map<string, Map<string, ContextEntry>> = new Map()
19
+
20
+ register(sessionID: string, options: RegisterContextOptions): void {
21
+ if (!this.sessions.has(sessionID)) {
22
+ this.sessions.set(sessionID, new Map())
23
+ }
24
+
25
+ const sessionMap = this.sessions.get(sessionID)!
26
+ const key = `${options.source}:${options.id}`
27
+
28
+ const entry: ContextEntry = {
29
+ id: options.id,
30
+ source: options.source,
31
+ content: options.content,
32
+ priority: options.priority ?? "normal",
33
+ timestamp: Date.now(),
34
+ metadata: options.metadata,
35
+ }
36
+
37
+ sessionMap.set(key, entry)
38
+ }
39
+
40
+ getPending(sessionID: string): PendingContext {
41
+ const sessionMap = this.sessions.get(sessionID)
42
+
43
+ if (!sessionMap || sessionMap.size === 0) {
44
+ return {
45
+ merged: "",
46
+ entries: [],
47
+ hasContent: false,
48
+ }
49
+ }
50
+
51
+ const entries = this.sortEntries([...sessionMap.values()])
52
+ const merged = entries.map((e) => e.content).join(CONTEXT_SEPARATOR)
53
+
54
+ return {
55
+ merged,
56
+ entries,
57
+ hasContent: entries.length > 0,
58
+ }
59
+ }
60
+
61
+ consume(sessionID: string): PendingContext {
62
+ const pending = this.getPending(sessionID)
63
+ this.clear(sessionID)
64
+ return pending
65
+ }
66
+
67
+ clear(sessionID: string): void {
68
+ this.sessions.delete(sessionID)
69
+ }
70
+
71
+ hasPending(sessionID: string): boolean {
72
+ const sessionMap = this.sessions.get(sessionID)
73
+ return sessionMap !== undefined && sessionMap.size > 0
74
+ }
75
+
76
+ private sortEntries(entries: ContextEntry[]): ContextEntry[] {
77
+ return entries.sort((a, b) => {
78
+ const priorityDiff = PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority]
79
+ if (priorityDiff !== 0) return priorityDiff
80
+ return a.timestamp - b.timestamp
81
+ })
82
+ }
83
+ }
84
+
85
+ export const contextCollector = new ContextCollector()
@@ -0,0 +1,14 @@
1
+ export { ContextCollector, contextCollector } from "./collector"
2
+ export {
3
+ createContextInjectorMessagesTransformHook,
4
+ } from "./injector"
5
+ export type {
6
+ ContextSourceType,
7
+ ContextPriority,
8
+ ContextEntry,
9
+ RegisterContextOptions,
10
+ PendingContext,
11
+ MessageContext,
12
+ OutputParts,
13
+ InjectionStrategy,
14
+ } from "./types"
@@ -0,0 +1,122 @@
1
+ import { describe, it, expect, beforeEach } from "bun:test"
2
+ import { ContextCollector } from "./collector"
3
+ import {
4
+ createContextInjectorMessagesTransformHook,
5
+ } from "./injector"
6
+
7
+ describe("createContextInjectorMessagesTransformHook", () => {
8
+ let collector: ContextCollector
9
+
10
+ beforeEach(() => {
11
+ collector = new ContextCollector()
12
+ })
13
+
14
+ const createMockMessage = (
15
+ role: "user" | "assistant",
16
+ text: string,
17
+ sessionID: string
18
+ ) => ({
19
+ info: {
20
+ id: `msg_${Date.now()}_${Math.random()}`,
21
+ sessionID,
22
+ role,
23
+ time: { created: Date.now() },
24
+ agent: "Sisyphus",
25
+ model: { providerID: "test", modelID: "test" },
26
+ path: { cwd: "/", root: "/" },
27
+ },
28
+ parts: [
29
+ {
30
+ id: `part_${Date.now()}`,
31
+ sessionID,
32
+ messageID: `msg_${Date.now()}`,
33
+ type: "text" as const,
34
+ text,
35
+ },
36
+ ],
37
+ })
38
+
39
+ it("inserts synthetic part before text part in last user message", async () => {
40
+ // #given
41
+ const hook = createContextInjectorMessagesTransformHook(collector)
42
+ const sessionID = "ses_transform1"
43
+ collector.register(sessionID, {
44
+ id: "ulw",
45
+ source: "keyword-detector",
46
+ content: "Ultrawork context",
47
+ })
48
+ const messages = [
49
+ createMockMessage("user", "First message", sessionID),
50
+ createMockMessage("assistant", "Response", sessionID),
51
+ createMockMessage("user", "Second message", sessionID),
52
+ ]
53
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
+ const output = { messages } as any
55
+
56
+ // #when
57
+ await hook["experimental.chat.messages.transform"]!({}, output)
58
+
59
+ // #then - synthetic part inserted before original text part
60
+ expect(output.messages.length).toBe(3)
61
+ expect(output.messages[2].parts.length).toBe(2)
62
+ expect(output.messages[2].parts[0].text).toBe("Ultrawork context")
63
+ expect(output.messages[2].parts[0].synthetic).toBe(true)
64
+ expect(output.messages[2].parts[1].text).toBe("Second message")
65
+ })
66
+
67
+ it("does nothing when no pending context", async () => {
68
+ // #given
69
+ const hook = createContextInjectorMessagesTransformHook(collector)
70
+ const sessionID = "ses_transform2"
71
+ const messages = [createMockMessage("user", "Hello world", sessionID)]
72
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
73
+ const output = { messages } as any
74
+
75
+ // #when
76
+ await hook["experimental.chat.messages.transform"]!({}, output)
77
+
78
+ // #then
79
+ expect(output.messages.length).toBe(1)
80
+ })
81
+
82
+ it("does nothing when no user messages", async () => {
83
+ // #given
84
+ const hook = createContextInjectorMessagesTransformHook(collector)
85
+ const sessionID = "ses_transform3"
86
+ collector.register(sessionID, {
87
+ id: "ctx",
88
+ source: "keyword-detector",
89
+ content: "Context",
90
+ })
91
+ const messages = [createMockMessage("assistant", "Response", sessionID)]
92
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
93
+ const output = { messages } as any
94
+
95
+ // #when
96
+ await hook["experimental.chat.messages.transform"]!({}, output)
97
+
98
+ // #then
99
+ expect(output.messages.length).toBe(1)
100
+ expect(collector.hasPending(sessionID)).toBe(true)
101
+ })
102
+
103
+ it("consumes context after injection", async () => {
104
+ // #given
105
+ const hook = createContextInjectorMessagesTransformHook(collector)
106
+ const sessionID = "ses_transform4"
107
+ collector.register(sessionID, {
108
+ id: "ctx",
109
+ source: "keyword-detector",
110
+ content: "Context",
111
+ })
112
+ const messages = [createMockMessage("user", "Message", sessionID)]
113
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114
+ const output = { messages } as any
115
+
116
+ // #when
117
+ await hook["experimental.chat.messages.transform"]!({}, output)
118
+
119
+ // #then
120
+ expect(collector.hasPending(sessionID)).toBe(false)
121
+ })
122
+ })
@@ -0,0 +1,167 @@
1
+ import type { ContextCollector } from "./collector"
2
+ import type { Message, Part } from "@opencode-ai/sdk"
3
+ import { log } from "../../shared"
4
+ import { getMainSessionID } from "../claude-code-session-state"
5
+
6
+ interface OutputPart {
7
+ type: string
8
+ text?: string
9
+ [key: string]: unknown
10
+ }
11
+
12
+ interface InjectionResult {
13
+ injected: boolean
14
+ contextLength: number
15
+ }
16
+
17
+ export function injectPendingContext(
18
+ collector: ContextCollector,
19
+ sessionID: string,
20
+ parts: OutputPart[]
21
+ ): InjectionResult {
22
+ if (!collector.hasPending(sessionID)) {
23
+ return { injected: false, contextLength: 0 }
24
+ }
25
+
26
+ const textPartIndex = parts.findIndex((p) => p.type === "text" && p.text !== undefined)
27
+ if (textPartIndex === -1) {
28
+ return { injected: false, contextLength: 0 }
29
+ }
30
+
31
+ const pending = collector.consume(sessionID)
32
+ const originalText = parts[textPartIndex].text ?? ""
33
+ parts[textPartIndex].text = `${pending.merged}\n\n---\n\n${originalText}`
34
+
35
+ return {
36
+ injected: true,
37
+ contextLength: pending.merged.length,
38
+ }
39
+ }
40
+
41
+ interface ChatMessageInput {
42
+ sessionID: string
43
+ agent?: string
44
+ model?: { providerID: string; modelID: string }
45
+ messageID?: string
46
+ }
47
+
48
+ interface ChatMessageOutput {
49
+ message: Record<string, unknown>
50
+ parts: OutputPart[]
51
+ }
52
+
53
+ export function createContextInjectorHook(collector: ContextCollector) {
54
+ return {
55
+ "chat.message": async (
56
+ input: ChatMessageInput,
57
+ output: ChatMessageOutput
58
+ ): Promise<void> => {
59
+ const result = injectPendingContext(collector, input.sessionID, output.parts)
60
+ if (result.injected) {
61
+ log("[context-injector] Injected pending context via chat.message", {
62
+ sessionID: input.sessionID,
63
+ contextLength: result.contextLength,
64
+ })
65
+ }
66
+ },
67
+ }
68
+ }
69
+
70
+ interface MessageWithParts {
71
+ info: Message
72
+ parts: Part[]
73
+ }
74
+
75
+ type MessagesTransformHook = {
76
+ "experimental.chat.messages.transform"?: (
77
+ input: Record<string, never>,
78
+ output: { messages: MessageWithParts[] }
79
+ ) => Promise<void>
80
+ }
81
+
82
+ export function createContextInjectorMessagesTransformHook(
83
+ collector: ContextCollector
84
+ ): MessagesTransformHook {
85
+ return {
86
+ "experimental.chat.messages.transform": async (_input, output) => {
87
+ const { messages } = output
88
+ log("[DEBUG] experimental.chat.messages.transform called", {
89
+ messageCount: messages.length,
90
+ })
91
+ if (messages.length === 0) {
92
+ return
93
+ }
94
+
95
+ let lastUserMessageIndex = -1
96
+ for (let i = messages.length - 1; i >= 0; i--) {
97
+ if (messages[i].info.role === "user") {
98
+ lastUserMessageIndex = i
99
+ break
100
+ }
101
+ }
102
+
103
+ if (lastUserMessageIndex === -1) {
104
+ log("[DEBUG] No user message found in messages")
105
+ return
106
+ }
107
+
108
+ const lastUserMessage = messages[lastUserMessageIndex]
109
+ // Try message.info.sessionID first, fallback to mainSessionID
110
+ const messageSessionID = (lastUserMessage.info as unknown as { sessionID?: string }).sessionID
111
+ const sessionID = messageSessionID ?? getMainSessionID()
112
+ log("[DEBUG] Extracted sessionID", {
113
+ messageSessionID,
114
+ mainSessionID: getMainSessionID(),
115
+ sessionID,
116
+ infoKeys: Object.keys(lastUserMessage.info),
117
+ })
118
+ if (!sessionID) {
119
+ log("[DEBUG] sessionID is undefined (both message.info and mainSessionID are empty)")
120
+ return
121
+ }
122
+
123
+ const hasPending = collector.hasPending(sessionID)
124
+ log("[DEBUG] Checking hasPending", {
125
+ sessionID,
126
+ hasPending,
127
+ })
128
+ if (!hasPending) {
129
+ return
130
+ }
131
+
132
+ const pending = collector.consume(sessionID)
133
+ if (!pending.hasContent) {
134
+ return
135
+ }
136
+
137
+ const textPartIndex = lastUserMessage.parts.findIndex(
138
+ (p) => p.type === "text" && (p as { text?: string }).text
139
+ )
140
+
141
+ if (textPartIndex === -1) {
142
+ log("[context-injector] No text part found in last user message, skipping injection", {
143
+ sessionID,
144
+ partsCount: lastUserMessage.parts.length,
145
+ })
146
+ return
147
+ }
148
+
149
+ // synthetic part 패턴 (minimal fields)
150
+ const syntheticPart = {
151
+ id: `synthetic_hook_${Date.now()}`,
152
+ messageID: lastUserMessage.info.id,
153
+ sessionID: (lastUserMessage.info as { sessionID?: string }).sessionID ?? "",
154
+ type: "text" as const,
155
+ text: pending.merged,
156
+ synthetic: true, // UI에서 숨겨짐
157
+ }
158
+
159
+ lastUserMessage.parts.splice(textPartIndex, 0, syntheticPart as Part)
160
+
161
+ log("[context-injector] Inserted synthetic part with hook content", {
162
+ sessionID,
163
+ contentLength: pending.merged.length,
164
+ })
165
+ },
166
+ }
167
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Source identifier for context injection
3
+ * Each source registers context that will be merged and injected together
4
+ */
5
+ export type ContextSourceType =
6
+ | "keyword-detector"
7
+ | "rules-injector"
8
+ | "directory-agents"
9
+ | "directory-readme"
10
+ | "custom"
11
+
12
+ /**
13
+ * Priority levels for context ordering
14
+ * Higher priority contexts appear first in the merged output
15
+ */
16
+ export type ContextPriority = "critical" | "high" | "normal" | "low"
17
+
18
+ /**
19
+ * A single context entry registered by a source
20
+ */
21
+ export interface ContextEntry {
22
+ /** Unique identifier for this entry within the source */
23
+ id: string
24
+ /** The source that registered this context */
25
+ source: ContextSourceType
26
+ /** The actual context content to inject */
27
+ content: string
28
+ /** Priority for ordering (default: normal) */
29
+ priority: ContextPriority
30
+ /** Timestamp when registered */
31
+ timestamp: number
32
+ /** Optional metadata for debugging/logging */
33
+ metadata?: Record<string, unknown>
34
+ }
35
+
36
+ /**
37
+ * Options for registering context
38
+ */
39
+ export interface RegisterContextOptions {
40
+ /** Unique ID for this context entry (used for deduplication) */
41
+ id: string
42
+ /** Source identifier */
43
+ source: ContextSourceType
44
+ /** The content to inject */
45
+ content: string
46
+ /** Priority for ordering (default: normal) */
47
+ priority?: ContextPriority
48
+ /** Optional metadata */
49
+ metadata?: Record<string, unknown>
50
+ }
51
+
52
+ /**
53
+ * Result of getting pending context for a session
54
+ */
55
+ export interface PendingContext {
56
+ /** Merged context string, ready for injection */
57
+ merged: string
58
+ /** Individual entries that were merged */
59
+ entries: ContextEntry[]
60
+ /** Whether there's any content to inject */
61
+ hasContent: boolean
62
+ }
63
+
64
+ /**
65
+ * Message context from the original user message
66
+ * Used when injecting to match the message format
67
+ */
68
+ export interface MessageContext {
69
+ agent?: string
70
+ model?: {
71
+ providerID?: string
72
+ modelID?: string
73
+ }
74
+ path?: {
75
+ cwd?: string
76
+ root?: string
77
+ }
78
+ tools?: Record<string, boolean>
79
+ }
80
+
81
+ /**
82
+ * Output parts from chat.message hook
83
+ */
84
+ export interface OutputParts {
85
+ parts: Array<{ type: string; text?: string; [key: string]: unknown }>
86
+ }
87
+
88
+ /**
89
+ * Injection strategy
90
+ */
91
+ export type InjectionStrategy = "prepend-parts" | "storage" | "auto"
@@ -0,0 +1,6 @@
1
+ import { join } from "node:path"
2
+ import { getOpenCodeStorageDir } from "../../shared/data-path"
3
+
4
+ export const OPENCODE_STORAGE = getOpenCodeStorageDir()
5
+ export const MESSAGE_STORAGE = join(OPENCODE_STORAGE, "message")
6
+ export const PART_STORAGE = join(OPENCODE_STORAGE, "part")
@@ -0,0 +1,4 @@
1
+ export { injectHookMessage, findNearestMessageWithFields, findFirstMessageWithAgent } from "./injector"
2
+ export type { StoredMessage } from "./injector"
3
+ export type { MessageMeta, OriginalMessageContext, TextPart, ToolPermission } from "./types"
4
+ export { MESSAGE_STORAGE } from "./constants"
@@ -0,0 +1,195 @@
1
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs"
2
+ import { join } from "node:path"
3
+ import { MESSAGE_STORAGE, PART_STORAGE } from "./constants"
4
+ import type { MessageMeta, OriginalMessageContext, TextPart, ToolPermission } from "./types"
5
+
6
+ export interface StoredMessage {
7
+ agent?: string
8
+ model?: { providerID?: string; modelID?: string }
9
+ tools?: Record<string, ToolPermission>
10
+ }
11
+
12
+ export function findNearestMessageWithFields(messageDir: string): StoredMessage | null {
13
+ try {
14
+ const files = readdirSync(messageDir)
15
+ .filter((f) => f.endsWith(".json"))
16
+ .sort()
17
+ .reverse()
18
+
19
+ // First pass: find message with ALL fields (ideal)
20
+ for (const file of files) {
21
+ try {
22
+ const content = readFileSync(join(messageDir, file), "utf-8")
23
+ const msg = JSON.parse(content) as StoredMessage
24
+ if (msg.agent && msg.model?.providerID && msg.model?.modelID) {
25
+ return msg
26
+ }
27
+ } catch {
28
+ continue
29
+ }
30
+ }
31
+
32
+ // Second pass: find message with ANY useful field (fallback)
33
+ // This ensures agent info isn't lost when model info is missing
34
+ for (const file of files) {
35
+ try {
36
+ const content = readFileSync(join(messageDir, file), "utf-8")
37
+ const msg = JSON.parse(content) as StoredMessage
38
+ if (msg.agent || (msg.model?.providerID && msg.model?.modelID)) {
39
+ return msg
40
+ }
41
+ } catch {
42
+ continue
43
+ }
44
+ }
45
+ } catch {
46
+ return null
47
+ }
48
+ return null
49
+ }
50
+
51
+ /**
52
+ * Finds the FIRST (oldest) message in the session with agent field.
53
+ * This is used to get the original agent that started the session,
54
+ * avoiding issues where newer messages may have a different agent
55
+ * due to OpenCode's internal agent switching.
56
+ */
57
+ export function findFirstMessageWithAgent(messageDir: string): string | null {
58
+ try {
59
+ const files = readdirSync(messageDir)
60
+ .filter((f) => f.endsWith(".json"))
61
+ .sort() // Oldest first (no reverse)
62
+
63
+ for (const file of files) {
64
+ try {
65
+ const content = readFileSync(join(messageDir, file), "utf-8")
66
+ const msg = JSON.parse(content) as StoredMessage
67
+ if (msg.agent) {
68
+ return msg.agent
69
+ }
70
+ } catch {
71
+ continue
72
+ }
73
+ }
74
+ } catch {
75
+ return null
76
+ }
77
+ return null
78
+ }
79
+
80
+ function generateMessageId(): string {
81
+ const timestamp = Date.now().toString(16)
82
+ const random = Math.random().toString(36).substring(2, 14)
83
+ return `msg_${timestamp}${random}`
84
+ }
85
+
86
+ function generatePartId(): string {
87
+ const timestamp = Date.now().toString(16)
88
+ const random = Math.random().toString(36).substring(2, 10)
89
+ return `prt_${timestamp}${random}`
90
+ }
91
+
92
+ function getOrCreateMessageDir(sessionID: string): string {
93
+ if (!existsSync(MESSAGE_STORAGE)) {
94
+ mkdirSync(MESSAGE_STORAGE, { recursive: true })
95
+ }
96
+
97
+ const directPath = join(MESSAGE_STORAGE, sessionID)
98
+ if (existsSync(directPath)) {
99
+ return directPath
100
+ }
101
+
102
+ for (const dir of readdirSync(MESSAGE_STORAGE)) {
103
+ const sessionPath = join(MESSAGE_STORAGE, dir, sessionID)
104
+ if (existsSync(sessionPath)) {
105
+ return sessionPath
106
+ }
107
+ }
108
+
109
+ mkdirSync(directPath, { recursive: true })
110
+ return directPath
111
+ }
112
+
113
+ export function injectHookMessage(
114
+ sessionID: string,
115
+ hookContent: string,
116
+ originalMessage: OriginalMessageContext
117
+ ): boolean {
118
+ // Validate hook content to prevent empty message injection
119
+ if (!hookContent || hookContent.trim().length === 0) {
120
+ console.warn("[hook-message-injector] Attempted to inject empty hook content, skipping injection", {
121
+ sessionID,
122
+ hasAgent: !!originalMessage.agent,
123
+ hasModel: !!(originalMessage.model?.providerID && originalMessage.model?.modelID)
124
+ })
125
+ return false
126
+ }
127
+
128
+ const messageDir = getOrCreateMessageDir(sessionID)
129
+
130
+ const needsFallback =
131
+ !originalMessage.agent ||
132
+ !originalMessage.model?.providerID ||
133
+ !originalMessage.model?.modelID
134
+
135
+ const fallback = needsFallback ? findNearestMessageWithFields(messageDir) : null
136
+
137
+ const now = Date.now()
138
+ const messageID = generateMessageId()
139
+ const partID = generatePartId()
140
+
141
+ const resolvedAgent = originalMessage.agent ?? fallback?.agent ?? "general"
142
+ const resolvedModel =
143
+ originalMessage.model?.providerID && originalMessage.model?.modelID
144
+ ? { providerID: originalMessage.model.providerID, modelID: originalMessage.model.modelID }
145
+ : fallback?.model?.providerID && fallback?.model?.modelID
146
+ ? { providerID: fallback.model.providerID, modelID: fallback.model.modelID }
147
+ : undefined
148
+ const resolvedTools = originalMessage.tools ?? fallback?.tools
149
+
150
+ const messageMeta: MessageMeta = {
151
+ id: messageID,
152
+ sessionID,
153
+ role: "user",
154
+ time: {
155
+ created: now,
156
+ },
157
+ agent: resolvedAgent,
158
+ model: resolvedModel,
159
+ path:
160
+ originalMessage.path?.cwd
161
+ ? {
162
+ cwd: originalMessage.path.cwd,
163
+ root: originalMessage.path.root ?? "/",
164
+ }
165
+ : undefined,
166
+ tools: resolvedTools,
167
+ }
168
+
169
+ const textPart: TextPart = {
170
+ id: partID,
171
+ type: "text",
172
+ text: hookContent,
173
+ synthetic: true,
174
+ time: {
175
+ start: now,
176
+ end: now,
177
+ },
178
+ messageID,
179
+ sessionID,
180
+ }
181
+
182
+ try {
183
+ writeFileSync(join(messageDir, `${messageID}.json`), JSON.stringify(messageMeta, null, 2))
184
+
185
+ const partDir = join(PART_STORAGE, messageID)
186
+ if (!existsSync(partDir)) {
187
+ mkdirSync(partDir, { recursive: true })
188
+ }
189
+ writeFileSync(join(partDir, `${partID}.json`), JSON.stringify(textPart, null, 2))
190
+
191
+ return true
192
+ } catch {
193
+ return false
194
+ }
195
+ }