im-hub-pro 0.2.29

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 (384) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +497 -0
  3. package/README.zh-CN.md +496 -0
  4. package/dist/cli.d.ts +3 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +921 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/core/acp-server.d.ts +8 -0
  9. package/dist/core/acp-server.d.ts.map +1 -0
  10. package/dist/core/acp-server.js +266 -0
  11. package/dist/core/acp-server.js.map +1 -0
  12. package/dist/core/agent-base.d.ts +94 -0
  13. package/dist/core/agent-base.d.ts.map +1 -0
  14. package/dist/core/agent-base.js +374 -0
  15. package/dist/core/agent-base.js.map +1 -0
  16. package/dist/core/agent-cwd.d.ts +45 -0
  17. package/dist/core/agent-cwd.d.ts.map +1 -0
  18. package/dist/core/agent-cwd.js +178 -0
  19. package/dist/core/agent-cwd.js.map +1 -0
  20. package/dist/core/agent-cwd.test.d.ts +2 -0
  21. package/dist/core/agent-cwd.test.d.ts.map +1 -0
  22. package/dist/core/agent-cwd.test.js +149 -0
  23. package/dist/core/agent-cwd.test.js.map +1 -0
  24. package/dist/core/approval-bus.d.ts +232 -0
  25. package/dist/core/approval-bus.d.ts.map +1 -0
  26. package/dist/core/approval-bus.js +703 -0
  27. package/dist/core/approval-bus.js.map +1 -0
  28. package/dist/core/approval-bus.synthetic.test.d.ts +2 -0
  29. package/dist/core/approval-bus.synthetic.test.d.ts.map +1 -0
  30. package/dist/core/approval-bus.synthetic.test.js +182 -0
  31. package/dist/core/approval-bus.synthetic.test.js.map +1 -0
  32. package/dist/core/approval-bus.test.d.ts +2 -0
  33. package/dist/core/approval-bus.test.d.ts.map +1 -0
  34. package/dist/core/approval-bus.test.js +537 -0
  35. package/dist/core/approval-bus.test.js.map +1 -0
  36. package/dist/core/approval-router.d.ts +95 -0
  37. package/dist/core/approval-router.d.ts.map +1 -0
  38. package/dist/core/approval-router.js +450 -0
  39. package/dist/core/approval-router.js.map +1 -0
  40. package/dist/core/approval-router.test.d.ts +2 -0
  41. package/dist/core/approval-router.test.d.ts.map +1 -0
  42. package/dist/core/approval-router.test.js +413 -0
  43. package/dist/core/approval-router.test.js.map +1 -0
  44. package/dist/core/audit-log.d.ts +55 -0
  45. package/dist/core/audit-log.d.ts.map +1 -0
  46. package/dist/core/audit-log.js +203 -0
  47. package/dist/core/audit-log.js.map +1 -0
  48. package/dist/core/bgjob-reader.d.ts +65 -0
  49. package/dist/core/bgjob-reader.d.ts.map +1 -0
  50. package/dist/core/bgjob-reader.js +212 -0
  51. package/dist/core/bgjob-reader.js.map +1 -0
  52. package/dist/core/bgjob-reader.test.d.ts +2 -0
  53. package/dist/core/bgjob-reader.test.d.ts.map +1 -0
  54. package/dist/core/bgjob-reader.test.js +178 -0
  55. package/dist/core/bgjob-reader.test.js.map +1 -0
  56. package/dist/core/circuit-breaker.d.ts +37 -0
  57. package/dist/core/circuit-breaker.d.ts.map +1 -0
  58. package/dist/core/circuit-breaker.js +115 -0
  59. package/dist/core/circuit-breaker.js.map +1 -0
  60. package/dist/core/commands/agent.d.ts +4 -0
  61. package/dist/core/commands/agent.d.ts.map +1 -0
  62. package/dist/core/commands/agent.js +21 -0
  63. package/dist/core/commands/agent.js.map +1 -0
  64. package/dist/core/commands/approval.d.ts +3 -0
  65. package/dist/core/commands/approval.d.ts.map +1 -0
  66. package/dist/core/commands/approval.js +44 -0
  67. package/dist/core/commands/approval.js.map +1 -0
  68. package/dist/core/commands/approval.test.d.ts +2 -0
  69. package/dist/core/commands/approval.test.d.ts.map +1 -0
  70. package/dist/core/commands/approval.test.js +85 -0
  71. package/dist/core/commands/approval.test.js.map +1 -0
  72. package/dist/core/commands/audit.d.ts +3 -0
  73. package/dist/core/commands/audit.d.ts.map +1 -0
  74. package/dist/core/commands/audit.js +84 -0
  75. package/dist/core/commands/audit.js.map +1 -0
  76. package/dist/core/commands/builtin.d.ts +3 -0
  77. package/dist/core/commands/builtin.d.ts.map +1 -0
  78. package/dist/core/commands/builtin.js +26 -0
  79. package/dist/core/commands/builtin.js.map +1 -0
  80. package/dist/core/commands/job.d.ts +3 -0
  81. package/dist/core/commands/job.d.ts.map +1 -0
  82. package/dist/core/commands/job.js +195 -0
  83. package/dist/core/commands/job.js.map +1 -0
  84. package/dist/core/commands/model.d.ts +9 -0
  85. package/dist/core/commands/model.d.ts.map +1 -0
  86. package/dist/core/commands/model.js +183 -0
  87. package/dist/core/commands/model.js.map +1 -0
  88. package/dist/core/commands/plan.d.ts +3 -0
  89. package/dist/core/commands/plan.d.ts.map +1 -0
  90. package/dist/core/commands/plan.js +75 -0
  91. package/dist/core/commands/plan.js.map +1 -0
  92. package/dist/core/commands/plan.test.d.ts +2 -0
  93. package/dist/core/commands/plan.test.d.ts.map +1 -0
  94. package/dist/core/commands/plan.test.js +122 -0
  95. package/dist/core/commands/plan.test.js.map +1 -0
  96. package/dist/core/commands/router.d.ts +3 -0
  97. package/dist/core/commands/router.d.ts.map +1 -0
  98. package/dist/core/commands/router.js +71 -0
  99. package/dist/core/commands/router.js.map +1 -0
  100. package/dist/core/commands/schedule.d.ts +3 -0
  101. package/dist/core/commands/schedule.d.ts.map +1 -0
  102. package/dist/core/commands/schedule.js +123 -0
  103. package/dist/core/commands/schedule.js.map +1 -0
  104. package/dist/core/commands/sessions.d.ts +3 -0
  105. package/dist/core/commands/sessions.d.ts.map +1 -0
  106. package/dist/core/commands/sessions.js +88 -0
  107. package/dist/core/commands/sessions.js.map +1 -0
  108. package/dist/core/commands/stats.d.ts +3 -0
  109. package/dist/core/commands/stats.d.ts.map +1 -0
  110. package/dist/core/commands/stats.js +73 -0
  111. package/dist/core/commands/stats.js.map +1 -0
  112. package/dist/core/commands/think.d.ts +3 -0
  113. package/dist/core/commands/think.d.ts.map +1 -0
  114. package/dist/core/commands/think.js +28 -0
  115. package/dist/core/commands/think.js.map +1 -0
  116. package/dist/core/commands/workspaces.d.ts +3 -0
  117. package/dist/core/commands/workspaces.d.ts.map +1 -0
  118. package/dist/core/commands/workspaces.js +47 -0
  119. package/dist/core/commands/workspaces.js.map +1 -0
  120. package/dist/core/config-schema.d.ts +58 -0
  121. package/dist/core/config-schema.d.ts.map +1 -0
  122. package/dist/core/config-schema.js +63 -0
  123. package/dist/core/config-schema.js.map +1 -0
  124. package/dist/core/cron.d.ts +29 -0
  125. package/dist/core/cron.d.ts.map +1 -0
  126. package/dist/core/cron.js +184 -0
  127. package/dist/core/cron.js.map +1 -0
  128. package/dist/core/event-bus.d.ts +80 -0
  129. package/dist/core/event-bus.d.ts.map +1 -0
  130. package/dist/core/event-bus.js +62 -0
  131. package/dist/core/event-bus.js.map +1 -0
  132. package/dist/core/intent-llm.d.ts +27 -0
  133. package/dist/core/intent-llm.d.ts.map +1 -0
  134. package/dist/core/intent-llm.js +170 -0
  135. package/dist/core/intent-llm.js.map +1 -0
  136. package/dist/core/intent.d.ts +12 -0
  137. package/dist/core/intent.d.ts.map +1 -0
  138. package/dist/core/intent.js +187 -0
  139. package/dist/core/intent.js.map +1 -0
  140. package/dist/core/job-board.d.ts +84 -0
  141. package/dist/core/job-board.d.ts.map +1 -0
  142. package/dist/core/job-board.js +379 -0
  143. package/dist/core/job-board.js.map +1 -0
  144. package/dist/core/logger.d.ts +6 -0
  145. package/dist/core/logger.d.ts.map +1 -0
  146. package/dist/core/logger.js +54 -0
  147. package/dist/core/logger.js.map +1 -0
  148. package/dist/core/metrics.d.ts +55 -0
  149. package/dist/core/metrics.d.ts.map +1 -0
  150. package/dist/core/metrics.js +291 -0
  151. package/dist/core/metrics.js.map +1 -0
  152. package/dist/core/onboarding.d.ts +94 -0
  153. package/dist/core/onboarding.d.ts.map +1 -0
  154. package/dist/core/onboarding.js +426 -0
  155. package/dist/core/onboarding.js.map +1 -0
  156. package/dist/core/onboarding.test.d.ts +2 -0
  157. package/dist/core/onboarding.test.d.ts.map +1 -0
  158. package/dist/core/onboarding.test.js +112 -0
  159. package/dist/core/onboarding.test.js.map +1 -0
  160. package/dist/core/rate-limiter.d.ts +44 -0
  161. package/dist/core/rate-limiter.d.ts.map +1 -0
  162. package/dist/core/rate-limiter.js +115 -0
  163. package/dist/core/rate-limiter.js.map +1 -0
  164. package/dist/core/registry.d.ts +32 -0
  165. package/dist/core/registry.d.ts.map +1 -0
  166. package/dist/core/registry.js +122 -0
  167. package/dist/core/registry.js.map +1 -0
  168. package/dist/core/router.d.ts +41 -0
  169. package/dist/core/router.d.ts.map +1 -0
  170. package/dist/core/router.js +431 -0
  171. package/dist/core/router.js.map +1 -0
  172. package/dist/core/schedule.d.ts +65 -0
  173. package/dist/core/schedule.d.ts.map +1 -0
  174. package/dist/core/schedule.js +316 -0
  175. package/dist/core/schedule.js.map +1 -0
  176. package/dist/core/session-subtasks.test.d.ts +2 -0
  177. package/dist/core/session-subtasks.test.d.ts.map +1 -0
  178. package/dist/core/session-subtasks.test.js +88 -0
  179. package/dist/core/session-subtasks.test.js.map +1 -0
  180. package/dist/core/session.d.ts +182 -0
  181. package/dist/core/session.d.ts.map +1 -0
  182. package/dist/core/session.js +774 -0
  183. package/dist/core/session.js.map +1 -0
  184. package/dist/core/sqlite-helper.d.ts +37 -0
  185. package/dist/core/sqlite-helper.d.ts.map +1 -0
  186. package/dist/core/sqlite-helper.js +79 -0
  187. package/dist/core/sqlite-helper.js.map +1 -0
  188. package/dist/core/transcribe.d.ts +25 -0
  189. package/dist/core/transcribe.d.ts.map +1 -0
  190. package/dist/core/transcribe.js +217 -0
  191. package/dist/core/transcribe.js.map +1 -0
  192. package/dist/core/transcribe.test.d.ts +2 -0
  193. package/dist/core/transcribe.test.d.ts.map +1 -0
  194. package/dist/core/transcribe.test.js +163 -0
  195. package/dist/core/transcribe.test.js.map +1 -0
  196. package/dist/core/types.d.ts +352 -0
  197. package/dist/core/types.d.ts.map +1 -0
  198. package/dist/core/types.js +3 -0
  199. package/dist/core/types.js.map +1 -0
  200. package/dist/core/workspace.d.ts +67 -0
  201. package/dist/core/workspace.d.ts.map +1 -0
  202. package/dist/core/workspace.js +113 -0
  203. package/dist/core/workspace.js.map +1 -0
  204. package/dist/index.d.ts +5 -0
  205. package/dist/index.d.ts.map +1 -0
  206. package/dist/index.js +6 -0
  207. package/dist/index.js.map +1 -0
  208. package/dist/plugins/agents/acp/acp-adapter.d.ts +16 -0
  209. package/dist/plugins/agents/acp/acp-adapter.d.ts.map +1 -0
  210. package/dist/plugins/agents/acp/acp-adapter.js +49 -0
  211. package/dist/plugins/agents/acp/acp-adapter.js.map +1 -0
  212. package/dist/plugins/agents/acp/acp-client.d.ts +32 -0
  213. package/dist/plugins/agents/acp/acp-client.d.ts.map +1 -0
  214. package/dist/plugins/agents/acp/acp-client.js +175 -0
  215. package/dist/plugins/agents/acp/acp-client.js.map +1 -0
  216. package/dist/plugins/agents/acp/discovery.d.ts +19 -0
  217. package/dist/plugins/agents/acp/discovery.d.ts.map +1 -0
  218. package/dist/plugins/agents/acp/discovery.js +109 -0
  219. package/dist/plugins/agents/acp/discovery.js.map +1 -0
  220. package/dist/plugins/agents/acp/index.d.ts +4 -0
  221. package/dist/plugins/agents/acp/index.d.ts.map +1 -0
  222. package/dist/plugins/agents/acp/index.js +4 -0
  223. package/dist/plugins/agents/acp/index.js.map +1 -0
  224. package/dist/plugins/agents/acp/types.d.ts +62 -0
  225. package/dist/plugins/agents/acp/types.d.ts.map +1 -0
  226. package/dist/plugins/agents/acp/types.js +5 -0
  227. package/dist/plugins/agents/acp/types.js.map +1 -0
  228. package/dist/plugins/agents/claude-code/adapter.test.d.ts +2 -0
  229. package/dist/plugins/agents/claude-code/adapter.test.d.ts.map +1 -0
  230. package/dist/plugins/agents/claude-code/adapter.test.js +195 -0
  231. package/dist/plugins/agents/claude-code/adapter.test.js.map +1 -0
  232. package/dist/plugins/agents/claude-code/index.d.ts +25 -0
  233. package/dist/plugins/agents/claude-code/index.d.ts.map +1 -0
  234. package/dist/plugins/agents/claude-code/index.js +184 -0
  235. package/dist/plugins/agents/claude-code/index.js.map +1 -0
  236. package/dist/plugins/agents/claude-code/mcp-approval-server.d.ts +42 -0
  237. package/dist/plugins/agents/claude-code/mcp-approval-server.d.ts.map +1 -0
  238. package/dist/plugins/agents/claude-code/mcp-approval-server.js +235 -0
  239. package/dist/plugins/agents/claude-code/mcp-approval-server.js.map +1 -0
  240. package/dist/plugins/agents/claude-code/mcp-approval-server.test.d.ts +2 -0
  241. package/dist/plugins/agents/claude-code/mcp-approval-server.test.d.ts.map +1 -0
  242. package/dist/plugins/agents/claude-code/mcp-approval-server.test.js +188 -0
  243. package/dist/plugins/agents/claude-code/mcp-approval-server.test.js.map +1 -0
  244. package/dist/plugins/agents/codex/adapter.test.d.ts +2 -0
  245. package/dist/plugins/agents/codex/adapter.test.d.ts.map +1 -0
  246. package/dist/plugins/agents/codex/adapter.test.js +192 -0
  247. package/dist/plugins/agents/codex/adapter.test.js.map +1 -0
  248. package/dist/plugins/agents/codex/index.d.ts +37 -0
  249. package/dist/plugins/agents/codex/index.d.ts.map +1 -0
  250. package/dist/plugins/agents/codex/index.js +254 -0
  251. package/dist/plugins/agents/codex/index.js.map +1 -0
  252. package/dist/plugins/agents/copilot/index.d.ts +35 -0
  253. package/dist/plugins/agents/copilot/index.d.ts.map +1 -0
  254. package/dist/plugins/agents/copilot/index.js +182 -0
  255. package/dist/plugins/agents/copilot/index.js.map +1 -0
  256. package/dist/plugins/agents/opencode/adapter.test.d.ts +2 -0
  257. package/dist/plugins/agents/opencode/adapter.test.d.ts.map +1 -0
  258. package/dist/plugins/agents/opencode/adapter.test.js +139 -0
  259. package/dist/plugins/agents/opencode/adapter.test.js.map +1 -0
  260. package/dist/plugins/agents/opencode/http-adapter.test.d.ts +2 -0
  261. package/dist/plugins/agents/opencode/http-adapter.test.d.ts.map +1 -0
  262. package/dist/plugins/agents/opencode/http-adapter.test.js +492 -0
  263. package/dist/plugins/agents/opencode/http-adapter.test.js.map +1 -0
  264. package/dist/plugins/agents/opencode/index.d.ts +5 -0
  265. package/dist/plugins/agents/opencode/index.d.ts.map +1 -0
  266. package/dist/plugins/agents/opencode/index.js +30 -0
  267. package/dist/plugins/agents/opencode/index.js.map +1 -0
  268. package/dist/plugins/agents/opencode/opencode-http-adapter.d.ts +138 -0
  269. package/dist/plugins/agents/opencode/opencode-http-adapter.d.ts.map +1 -0
  270. package/dist/plugins/agents/opencode/opencode-http-adapter.js +549 -0
  271. package/dist/plugins/agents/opencode/opencode-http-adapter.js.map +1 -0
  272. package/dist/plugins/agents/opencode/opencode-stdio-adapter.d.ts +24 -0
  273. package/dist/plugins/agents/opencode/opencode-stdio-adapter.d.ts.map +1 -0
  274. package/dist/plugins/agents/opencode/opencode-stdio-adapter.js +103 -0
  275. package/dist/plugins/agents/opencode/opencode-stdio-adapter.js.map +1 -0
  276. package/dist/plugins/agents/opencode/serve-manager.d.ts +27 -0
  277. package/dist/plugins/agents/opencode/serve-manager.d.ts.map +1 -0
  278. package/dist/plugins/agents/opencode/serve-manager.js +190 -0
  279. package/dist/plugins/agents/opencode/serve-manager.js.map +1 -0
  280. package/dist/plugins/messengers/discord/discord-adapter.d.ts +22 -0
  281. package/dist/plugins/messengers/discord/discord-adapter.d.ts.map +1 -0
  282. package/dist/plugins/messengers/discord/discord-adapter.js +241 -0
  283. package/dist/plugins/messengers/discord/discord-adapter.js.map +1 -0
  284. package/dist/plugins/messengers/discord/discord-adapter.test.d.ts +2 -0
  285. package/dist/plugins/messengers/discord/discord-adapter.test.d.ts.map +1 -0
  286. package/dist/plugins/messengers/discord/discord-adapter.test.js +332 -0
  287. package/dist/plugins/messengers/discord/discord-adapter.test.js.map +1 -0
  288. package/dist/plugins/messengers/discord/index.d.ts +4 -0
  289. package/dist/plugins/messengers/discord/index.d.ts.map +1 -0
  290. package/dist/plugins/messengers/discord/index.js +4 -0
  291. package/dist/plugins/messengers/discord/index.js.map +1 -0
  292. package/dist/plugins/messengers/discord/markdown-to-discord.d.ts +11 -0
  293. package/dist/plugins/messengers/discord/markdown-to-discord.d.ts.map +1 -0
  294. package/dist/plugins/messengers/discord/markdown-to-discord.js +59 -0
  295. package/dist/plugins/messengers/discord/markdown-to-discord.js.map +1 -0
  296. package/dist/plugins/messengers/discord/types.d.ts +9 -0
  297. package/dist/plugins/messengers/discord/types.d.ts.map +1 -0
  298. package/dist/plugins/messengers/discord/types.js +3 -0
  299. package/dist/plugins/messengers/discord/types.js.map +1 -0
  300. package/dist/plugins/messengers/feishu/card-builder.d.ts +23 -0
  301. package/dist/plugins/messengers/feishu/card-builder.d.ts.map +1 -0
  302. package/dist/plugins/messengers/feishu/card-builder.js +89 -0
  303. package/dist/plugins/messengers/feishu/card-builder.js.map +1 -0
  304. package/dist/plugins/messengers/feishu/feishu-adapter.d.ts +33 -0
  305. package/dist/plugins/messengers/feishu/feishu-adapter.d.ts.map +1 -0
  306. package/dist/plugins/messengers/feishu/feishu-adapter.js +195 -0
  307. package/dist/plugins/messengers/feishu/feishu-adapter.js.map +1 -0
  308. package/dist/plugins/messengers/feishu/feishu-client.d.ts +44 -0
  309. package/dist/plugins/messengers/feishu/feishu-client.d.ts.map +1 -0
  310. package/dist/plugins/messengers/feishu/feishu-client.js +120 -0
  311. package/dist/plugins/messengers/feishu/feishu-client.js.map +1 -0
  312. package/dist/plugins/messengers/feishu/feishu-dedup.test.d.ts +2 -0
  313. package/dist/plugins/messengers/feishu/feishu-dedup.test.d.ts.map +1 -0
  314. package/dist/plugins/messengers/feishu/feishu-dedup.test.js +70 -0
  315. package/dist/plugins/messengers/feishu/feishu-dedup.test.js.map +1 -0
  316. package/dist/plugins/messengers/feishu/index.d.ts +4 -0
  317. package/dist/plugins/messengers/feishu/index.d.ts.map +1 -0
  318. package/dist/plugins/messengers/feishu/index.js +4 -0
  319. package/dist/plugins/messengers/feishu/index.js.map +1 -0
  320. package/dist/plugins/messengers/feishu/types.d.ts +113 -0
  321. package/dist/plugins/messengers/feishu/types.d.ts.map +1 -0
  322. package/dist/plugins/messengers/feishu/types.js +4 -0
  323. package/dist/plugins/messengers/feishu/types.js.map +1 -0
  324. package/dist/plugins/messengers/telegram/index.d.ts +4 -0
  325. package/dist/plugins/messengers/telegram/index.d.ts.map +1 -0
  326. package/dist/plugins/messengers/telegram/index.js +4 -0
  327. package/dist/plugins/messengers/telegram/index.js.map +1 -0
  328. package/dist/plugins/messengers/telegram/markdown-to-html.d.ts +5 -0
  329. package/dist/plugins/messengers/telegram/markdown-to-html.d.ts.map +1 -0
  330. package/dist/plugins/messengers/telegram/markdown-to-html.js +186 -0
  331. package/dist/plugins/messengers/telegram/markdown-to-html.js.map +1 -0
  332. package/dist/plugins/messengers/telegram/media-download.d.ts +51 -0
  333. package/dist/plugins/messengers/telegram/media-download.d.ts.map +1 -0
  334. package/dist/plugins/messengers/telegram/media-download.js +224 -0
  335. package/dist/plugins/messengers/telegram/media-download.js.map +1 -0
  336. package/dist/plugins/messengers/telegram/media-download.test.d.ts +2 -0
  337. package/dist/plugins/messengers/telegram/media-download.test.d.ts.map +1 -0
  338. package/dist/plugins/messengers/telegram/media-download.test.js +125 -0
  339. package/dist/plugins/messengers/telegram/media-download.test.js.map +1 -0
  340. package/dist/plugins/messengers/telegram/telegram-adapter.d.ts +62 -0
  341. package/dist/plugins/messengers/telegram/telegram-adapter.d.ts.map +1 -0
  342. package/dist/plugins/messengers/telegram/telegram-adapter.js +653 -0
  343. package/dist/plugins/messengers/telegram/telegram-adapter.js.map +1 -0
  344. package/dist/plugins/messengers/telegram/types.d.ts +47 -0
  345. package/dist/plugins/messengers/telegram/types.d.ts.map +1 -0
  346. package/dist/plugins/messengers/telegram/types.js +3 -0
  347. package/dist/plugins/messengers/telegram/types.js.map +1 -0
  348. package/dist/plugins/messengers/wechat/ilink-adapter.d.ts +68 -0
  349. package/dist/plugins/messengers/wechat/ilink-adapter.d.ts.map +1 -0
  350. package/dist/plugins/messengers/wechat/ilink-adapter.js +483 -0
  351. package/dist/plugins/messengers/wechat/ilink-adapter.js.map +1 -0
  352. package/dist/plugins/messengers/wechat/ilink-client.d.ts +66 -0
  353. package/dist/plugins/messengers/wechat/ilink-client.d.ts.map +1 -0
  354. package/dist/plugins/messengers/wechat/ilink-client.js +288 -0
  355. package/dist/plugins/messengers/wechat/ilink-client.js.map +1 -0
  356. package/dist/plugins/messengers/wechat/ilink-types.d.ts +173 -0
  357. package/dist/plugins/messengers/wechat/ilink-types.d.ts.map +1 -0
  358. package/dist/plugins/messengers/wechat/ilink-types.js +12 -0
  359. package/dist/plugins/messengers/wechat/ilink-types.js.map +1 -0
  360. package/dist/utils/backoff.d.ts +35 -0
  361. package/dist/utils/backoff.d.ts.map +1 -0
  362. package/dist/utils/backoff.js +59 -0
  363. package/dist/utils/backoff.js.map +1 -0
  364. package/dist/utils/cross-platform.d.ts +26 -0
  365. package/dist/utils/cross-platform.d.ts.map +1 -0
  366. package/dist/utils/cross-platform.js +58 -0
  367. package/dist/utils/cross-platform.js.map +1 -0
  368. package/dist/utils/message-split.d.ts +14 -0
  369. package/dist/utils/message-split.d.ts.map +1 -0
  370. package/dist/utils/message-split.js +65 -0
  371. package/dist/utils/message-split.js.map +1 -0
  372. package/dist/utils/safe-equal.d.ts +2 -0
  373. package/dist/utils/safe-equal.d.ts.map +1 -0
  374. package/dist/utils/safe-equal.js +11 -0
  375. package/dist/utils/safe-equal.js.map +1 -0
  376. package/dist/web/public/_app.js +196 -0
  377. package/dist/web/public/index.html +935 -0
  378. package/dist/web/public/settings.html +1181 -0
  379. package/dist/web/public/tasks.html +1827 -0
  380. package/dist/web/server.d.ts +11 -0
  381. package/dist/web/server.d.ts.map +1 -0
  382. package/dist/web/server.js +1820 -0
  383. package/dist/web/server.js.map +1 -0
  384. package/package.json +73 -0
@@ -0,0 +1,139 @@
1
+ // OpenCodeAdapter — verify session-id capture, --session injection, and
2
+ // usage callbacks. The adapter is the only piece in the v2 chain that turns
3
+ // opencode's `--format json` event stream into the per-agent session row in
4
+ // im-hub, so this is the regression net for the 2026-05-02 wire-up.
5
+ import { describe, it, expect } from 'bun:test';
6
+ import { OpenCodeAdapter } from './index.js';
7
+ // Reach past the protected modifier to drive the adapter directly without
8
+ // spawning real opencode.
9
+ function inspect(adapter, event, opts) {
10
+ // @ts-expect-error — protected hook, intentional reach for tests
11
+ adapter.inspectEvent(event, opts);
12
+ }
13
+ function buildArgs(adapter, prompt, opts) {
14
+ // @ts-expect-error — protected
15
+ return adapter.buildArgs(prompt, opts);
16
+ }
17
+ describe('OpenCodeAdapter buildArgs', () => {
18
+ it('starts a fresh session when no agentSessionId provided', () => {
19
+ const adapter = new OpenCodeAdapter();
20
+ const args = buildArgs(adapter, 'hello', {});
21
+ expect(args).not.toContain('--session');
22
+ expect(args[args.length - 1]).toBe('hello');
23
+ });
24
+ it('injects --session <id> when opts.agentSessionId is set', () => {
25
+ const adapter = new OpenCodeAdapter();
26
+ const args = buildArgs(adapter, 'hello', { agentSessionId: 'ses_abc' });
27
+ const idx = args.indexOf('--session');
28
+ expect(idx).toBeGreaterThanOrEqual(0);
29
+ expect(args[idx + 1]).toBe('ses_abc');
30
+ });
31
+ it('still passes model and variant alongside --session', () => {
32
+ const adapter = new OpenCodeAdapter();
33
+ const args = buildArgs(adapter, 'p', {
34
+ agentSessionId: 'ses_abc', model: 'anthropic/claude-sonnet-4-6', variant: 'high',
35
+ });
36
+ expect(args).toContain('--session');
37
+ expect(args).toContain('--model');
38
+ expect(args).toContain('--variant');
39
+ });
40
+ it('injects --agent plan when planMode is true', () => {
41
+ const adapter = new OpenCodeAdapter();
42
+ const args = buildArgs(adapter, 'design X', { planMode: true });
43
+ const idx = args.indexOf('--agent');
44
+ expect(idx).toBeGreaterThanOrEqual(0);
45
+ expect(args[idx + 1]).toBe('plan');
46
+ // Prompt is still last positional.
47
+ expect(args[args.length - 1]).toBe('design X');
48
+ });
49
+ it('does not pass --agent when planMode is false / unset', () => {
50
+ const adapter = new OpenCodeAdapter();
51
+ expect(buildArgs(adapter, 'p', {})).not.toContain('--agent');
52
+ expect(buildArgs(adapter, 'p', { planMode: false })).not.toContain('--agent');
53
+ });
54
+ });
55
+ describe('OpenCodeAdapter inspectEvent — session id capture', () => {
56
+ it('forwards sessionID via opts.onAgentSessionId', () => {
57
+ const adapter = new OpenCodeAdapter();
58
+ const ids = [];
59
+ inspect(adapter, { type: 'step_start', sessionID: 'ses_x' }, {
60
+ onAgentSessionId: (id) => ids.push(id),
61
+ });
62
+ expect(ids).toEqual(['ses_x']);
63
+ });
64
+ it('fires every time it sees sessionID — caller dedupes', () => {
65
+ const adapter = new OpenCodeAdapter();
66
+ const ids = [];
67
+ const opts = { onAgentSessionId: (id) => ids.push(id) };
68
+ inspect(adapter, { type: 'step_start', sessionID: 'ses_x' }, opts);
69
+ inspect(adapter, { type: 'text', sessionID: 'ses_x', part: { type: 'text', text: 'hi' } }, opts);
70
+ inspect(adapter, { type: 'step_finish', sessionID: 'ses_x' }, opts);
71
+ // 3 events, all carry ses_x. Adapter forwards each — setOpencodeSessionId
72
+ // is itself idempotent so this is safe.
73
+ expect(ids.length).toBe(3);
74
+ expect(new Set(ids).size).toBe(1);
75
+ });
76
+ it('does nothing when callback is absent', () => {
77
+ const adapter = new OpenCodeAdapter();
78
+ // Should not throw
79
+ inspect(adapter, { type: 'step_start', sessionID: 'ses_x' }, {});
80
+ });
81
+ it('survives a callback that throws', () => {
82
+ const adapter = new OpenCodeAdapter();
83
+ inspect(adapter, { type: 'step_start', sessionID: 'ses_x' }, {
84
+ onAgentSessionId: () => { throw new Error('boom'); },
85
+ });
86
+ // No assertion — the absence of an unhandled throw is the assertion.
87
+ expect(true).toBe(true);
88
+ });
89
+ });
90
+ describe('OpenCodeAdapter inspectEvent — usage capture', () => {
91
+ // Reproduces the exact shape opencode 1.14.x emits — cost and tokens
92
+ // are nested under `part`, not on the event root. The first cut of this
93
+ // adapter read them from the root, silently zeroing /stats.
94
+ it('forwards cost + tokens from step_finish (event.part.cost / .tokens)', () => {
95
+ const adapter = new OpenCodeAdapter();
96
+ const deltas = [];
97
+ inspect(adapter, {
98
+ type: 'step_finish',
99
+ sessionID: 'ses_x',
100
+ part: {
101
+ type: 'step-finish',
102
+ tokens: { input: 100, output: 50, total: 150 },
103
+ cost: 0.0123,
104
+ },
105
+ }, { onUsage: (d) => deltas.push(d) });
106
+ expect(deltas.length).toBe(1);
107
+ expect(deltas[0].costUsd).toBe(0.0123);
108
+ expect(deltas[0].tokensInput).toBe(100);
109
+ expect(deltas[0].tokensOutput).toBe(50);
110
+ });
111
+ it('does NOT misread cost/tokens placed at event root (regression guard)', () => {
112
+ const adapter = new OpenCodeAdapter();
113
+ const deltas = [];
114
+ // Some hypothetical future format that flattens to root — should NOT
115
+ // accidentally match. This locks in the part-nested expectation.
116
+ inspect(adapter, {
117
+ type: 'step_finish',
118
+ sessionID: 'ses_x',
119
+ // Purposely misshapen — properties at root instead of nested under part.
120
+ cost: 99,
121
+ tokens: { input: 999 },
122
+ }, { onUsage: (d) => deltas.push(d) });
123
+ expect(deltas.length).toBe(0);
124
+ });
125
+ it('does NOT fire onUsage for non-step_finish events', () => {
126
+ const adapter = new OpenCodeAdapter();
127
+ const deltas = [];
128
+ inspect(adapter, { type: 'text', sessionID: 'ses_x', part: { type: 'text', text: 'hi' } }, { onUsage: (d) => deltas.push(d) });
129
+ inspect(adapter, { type: 'step_start', sessionID: 'ses_x' }, { onUsage: (d) => deltas.push(d) });
130
+ expect(deltas.length).toBe(0);
131
+ });
132
+ it('skips onUsage when step_finish carries no cost/tokens', () => {
133
+ const adapter = new OpenCodeAdapter();
134
+ const deltas = [];
135
+ inspect(adapter, { type: 'step_finish', sessionID: 'ses_x' }, { onUsage: (d) => deltas.push(d) });
136
+ expect(deltas.length).toBe(0);
137
+ });
138
+ });
139
+ //# sourceMappingURL=adapter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.test.js","sourceRoot":"","sources":["../../../../src/plugins/agents/opencode/adapter.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=http-adapter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-adapter.test.d.ts","sourceRoot":"","sources":["../../../../src/plugins/agents/opencode/http-adapter.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,492 @@
1
+ // OpenCodeHttpAdapter tests.
2
+ //
3
+ // Coverage layers:
4
+ // 1. Driver-selection contract (factory) — protects the
5
+ // IMHUB_OPENCODE_DRIVER env toggle.
6
+ // 2. Pure event-mapping helpers (inspectHttpEvent / isIdleEvent /
7
+ // isErrorEvent) — locks in the SSE → adapter-effects contract without
8
+ // spinning up `opencode serve`.
9
+ // 3. End-to-end sendPrompt with a fully-mocked fetch + serve — proves the
10
+ // happy path (subscribes SSE, posts prompt, captures sessionID, yields
11
+ // text on time.end, breaks on session.idle) and the four permission
12
+ // paths: no bus → fallback once, bus without IM ctx → fallback once,
13
+ // bridge allow → POST reply once, bridge deny+message → POST reply
14
+ // reject + message.
15
+ import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
16
+ import { OpenCodeAdapter, OpenCodeHttpAdapter } from './index.js';
17
+ describe('OpenCodeHttpAdapter — identity', () => {
18
+ it('inherits from OpenCodeAdapter so registry sees the same name/aliases', () => {
19
+ const a = new OpenCodeHttpAdapter();
20
+ expect(a).toBeInstanceOf(OpenCodeAdapter);
21
+ expect(a.name).toBe('opencode');
22
+ expect(a.aliases).toEqual(['oc', 'opencodeai']);
23
+ });
24
+ });
25
+ describe('opencode driver factory (env IMHUB_OPENCODE_DRIVER)', () => {
26
+ let original;
27
+ beforeEach(() => { original = process.env.IMHUB_OPENCODE_DRIVER; });
28
+ afterEach(() => {
29
+ if (original === undefined)
30
+ delete process.env.IMHUB_OPENCODE_DRIVER;
31
+ else
32
+ process.env.IMHUB_OPENCODE_DRIVER = original;
33
+ });
34
+ // The factory is invoked at module-load time — to test selection without
35
+ // re-importing the module on every case, we re-construct the same factory
36
+ // logic locally. Mirrors index.ts and locks in the contract.
37
+ function pick(envValue) {
38
+ if (envValue === undefined)
39
+ delete process.env.IMHUB_OPENCODE_DRIVER;
40
+ else
41
+ process.env.IMHUB_OPENCODE_DRIVER = envValue;
42
+ const driver = (process.env.IMHUB_OPENCODE_DRIVER || '').toLowerCase();
43
+ return driver === 'http' ? new OpenCodeHttpAdapter() : new OpenCodeAdapter();
44
+ }
45
+ it('defaults to stdio when env is unset', () => {
46
+ const a = pick(undefined);
47
+ expect(a).toBeInstanceOf(OpenCodeAdapter);
48
+ expect(a).not.toBeInstanceOf(OpenCodeHttpAdapter);
49
+ });
50
+ it('defaults to stdio when env is empty string', () => {
51
+ const a = pick('');
52
+ expect(a).not.toBeInstanceOf(OpenCodeHttpAdapter);
53
+ });
54
+ it('selects http when env is "http"', () => {
55
+ const a = pick('http');
56
+ expect(a).toBeInstanceOf(OpenCodeHttpAdapter);
57
+ });
58
+ it('selects http case-insensitively', () => {
59
+ const a = pick('HTTP');
60
+ expect(a).toBeInstanceOf(OpenCodeHttpAdapter);
61
+ });
62
+ it('falls back to stdio for unknown values (no throw)', () => {
63
+ const a = pick('grpc-someday');
64
+ expect(a).not.toBeInstanceOf(OpenCodeHttpAdapter);
65
+ expect(a).toBeInstanceOf(OpenCodeAdapter);
66
+ });
67
+ });
68
+ describe('OpenCodeHttpAdapter.inspectHttpEvent — sessionID + usage capture', () => {
69
+ it('forwards sessionID via opts.onAgentSessionId (event.properties.sessionID)', () => {
70
+ const a = new OpenCodeHttpAdapter();
71
+ const ids = [];
72
+ a.inspectHttpEvent({ type: 'message.part.updated', properties: { sessionID: 'ses_x', part: { type: 'step-start' } } }, 'ses_x', { onAgentSessionId: (id) => ids.push(id) });
73
+ expect(ids).toEqual(['ses_x']);
74
+ });
75
+ it('forwards sessionID from properties.info.sessionID when properties.sessionID missing', () => {
76
+ const a = new OpenCodeHttpAdapter();
77
+ const ids = [];
78
+ a.inspectHttpEvent({ type: 'session.status', properties: { info: { sessionID: 'ses_x', status: { type: 'running' } } } }, 'ses_x', { onAgentSessionId: (id) => ids.push(id) });
79
+ expect(ids).toEqual(['ses_x']);
80
+ });
81
+ it('captures cost + tokens from message.part.updated of type step-finish', () => {
82
+ const a = new OpenCodeHttpAdapter();
83
+ const deltas = [];
84
+ a.inspectHttpEvent({
85
+ type: 'message.part.updated',
86
+ properties: {
87
+ sessionID: 'ses_x',
88
+ part: { type: 'step-finish', cost: 0.0123, tokens: { input: 100, output: 50, total: 150 } },
89
+ },
90
+ }, 'ses_x', { onUsage: (d) => deltas.push(d) });
91
+ expect(deltas.length).toBe(1);
92
+ expect(deltas[0].costUsd).toBe(0.0123);
93
+ expect(deltas[0].tokensInput).toBe(100);
94
+ expect(deltas[0].tokensOutput).toBe(50);
95
+ });
96
+ it('does not fire onUsage when step-finish carries no cost/tokens', () => {
97
+ const a = new OpenCodeHttpAdapter();
98
+ const deltas = [];
99
+ a.inspectHttpEvent({ type: 'message.part.updated', properties: { sessionID: 'ses_x', part: { type: 'step-finish' } } }, 'ses_x', { onUsage: (d) => deltas.push(d) });
100
+ expect(deltas.length).toBe(0);
101
+ });
102
+ it('does not fire onUsage for non-step-finish events', () => {
103
+ const a = new OpenCodeHttpAdapter();
104
+ const deltas = [];
105
+ a.inspectHttpEvent({ type: 'message.part.updated', properties: { sessionID: 'ses_x', part: { type: 'text', text: 'hi' } } }, 'ses_x', { onUsage: (d) => deltas.push(d) });
106
+ expect(deltas.length).toBe(0);
107
+ });
108
+ it('survives onAgentSessionId throwing (callback safety)', () => {
109
+ const a = new OpenCodeHttpAdapter();
110
+ a.inspectHttpEvent({ type: 'message.part.updated', properties: { sessionID: 'ses_x', part: { type: 'step-start' } } }, 'ses_x', { onAgentSessionId: () => { throw new Error('boom'); } });
111
+ expect(true).toBe(true);
112
+ });
113
+ });
114
+ describe('OpenCodeHttpAdapter.inspectHttpEvent — text emission', () => {
115
+ it('yields text only when part.time.end is set (matches stdio adapter)', () => {
116
+ const a = new OpenCodeHttpAdapter();
117
+ const opts = {};
118
+ const streaming = a.inspectHttpEvent({
119
+ type: 'message.part.updated',
120
+ properties: { sessionID: 'ses_x', part: { type: 'text', text: 'hel', time: { start: 1 } } },
121
+ }, 'ses_x', opts);
122
+ expect(streaming.text).toBeUndefined();
123
+ const finished = a.inspectHttpEvent({
124
+ type: 'message.part.updated',
125
+ properties: { sessionID: 'ses_x', part: { type: 'text', text: 'hello', time: { start: 1, end: 2 } } },
126
+ }, 'ses_x', opts);
127
+ expect(finished.text).toBe('hello');
128
+ });
129
+ it('ignores text from a different sessionID (cross-talk guard)', () => {
130
+ const a = new OpenCodeHttpAdapter();
131
+ const out = a.inspectHttpEvent({
132
+ type: 'message.part.updated',
133
+ properties: { sessionID: 'ses_other', part: { type: 'text', text: 'hi', time: { end: 1 } } },
134
+ }, 'ses_x', {});
135
+ expect(out.text).toBeUndefined();
136
+ });
137
+ });
138
+ describe('OpenCodeHttpAdapter.isIdleEvent', () => {
139
+ it('matches session.status idle for our session', () => {
140
+ const a = new OpenCodeHttpAdapter();
141
+ expect(a.isIdleEvent({ type: 'session.status', properties: { sessionID: 'ses_x', status: { type: 'idle' } } }, 'ses_x')).toBe(true);
142
+ });
143
+ it('matches when status comes nested under properties.info', () => {
144
+ const a = new OpenCodeHttpAdapter();
145
+ expect(a.isIdleEvent({ type: 'session.status', properties: { info: { sessionID: 'ses_x', status: { type: 'idle' } } } }, 'ses_x')).toBe(true);
146
+ });
147
+ it('does not match running status', () => {
148
+ const a = new OpenCodeHttpAdapter();
149
+ expect(a.isIdleEvent({ type: 'session.status', properties: { sessionID: 'ses_x', status: { type: 'running' } } }, 'ses_x')).toBe(false);
150
+ });
151
+ it('does not match idle for a different session', () => {
152
+ const a = new OpenCodeHttpAdapter();
153
+ expect(a.isIdleEvent({ type: 'session.status', properties: { sessionID: 'ses_other', status: { type: 'idle' } } }, 'ses_x')).toBe(false);
154
+ });
155
+ it('does not match non-status events', () => {
156
+ const a = new OpenCodeHttpAdapter();
157
+ expect(a.isIdleEvent({ type: 'message.part.updated', properties: { sessionID: 'ses_x' } }, 'ses_x')).toBe(false);
158
+ });
159
+ });
160
+ describe('OpenCodeHttpAdapter.isErrorEvent', () => {
161
+ it('extracts error message from properties.error.data.message', () => {
162
+ const a = new OpenCodeHttpAdapter();
163
+ const out = a.isErrorEvent({
164
+ type: 'session.error',
165
+ properties: {
166
+ sessionID: 'ses_x',
167
+ error: { name: 'ProviderError', data: { message: 'rate limited' } },
168
+ },
169
+ }, 'ses_x');
170
+ expect(out).toEqual({ error: 'rate limited' });
171
+ });
172
+ it('falls back to error.name when data.message is absent', () => {
173
+ const a = new OpenCodeHttpAdapter();
174
+ const out = a.isErrorEvent({ type: 'session.error', properties: { sessionID: 'ses_x', error: { name: 'BoomError' } } }, 'ses_x');
175
+ expect(out).toEqual({ error: 'BoomError' });
176
+ });
177
+ it('returns null for non-error events', () => {
178
+ const a = new OpenCodeHttpAdapter();
179
+ expect(a.isErrorEvent({ type: 'message.part.updated', properties: { sessionID: 'ses_x' } }, 'ses_x')).toBeNull();
180
+ });
181
+ it('returns null for errors on a different session', () => {
182
+ const a = new OpenCodeHttpAdapter();
183
+ expect(a.isErrorEvent({ type: 'session.error', properties: { sessionID: 'ses_other', error: { name: 'X' } } }, 'ses_x')).toBeNull();
184
+ });
185
+ });
186
+ function makeServeStub(baseUrl = 'http://127.0.0.1:14199') {
187
+ // Just enough surface for sendPrompt — the manager's real lifecycle is
188
+ // tested separately if/when it grows.
189
+ return {
190
+ ensureRunning: async () => baseUrl,
191
+ isRunning: () => true,
192
+ getBaseUrl: () => baseUrl,
193
+ stop: async () => { },
194
+ };
195
+ }
196
+ /** Build a fake fetch that returns a programmable stream of SSE frames for
197
+ * /event and JSON for the other endpoints. */
198
+ function makeFakeFetch(events, opts = {}) {
199
+ const hits = opts.hits ?? [];
200
+ return (async (input, init) => {
201
+ const url = typeof input === 'string'
202
+ ? input
203
+ : input instanceof URL ? input.toString() : input.url;
204
+ const method = init?.method ?? 'GET';
205
+ let parsedBody;
206
+ if (init?.body && typeof init.body === 'string') {
207
+ try {
208
+ parsedBody = JSON.parse(init.body);
209
+ }
210
+ catch {
211
+ parsedBody = init.body;
212
+ }
213
+ }
214
+ hits.push({ url, method, body: parsedBody });
215
+ if (url.endsWith('/session') && method === 'POST') {
216
+ return new Response(JSON.stringify({ id: opts.sessionId ?? 'ses_test' }), {
217
+ status: 200, headers: { 'Content-Type': 'application/json' },
218
+ });
219
+ }
220
+ if (url.includes('/session/') && url.endsWith('/message') && method === 'POST') {
221
+ return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } });
222
+ }
223
+ if (url.endsWith('/permission') === false && url.includes('/permission/') && url.endsWith('/reply')) {
224
+ return new Response('true', { status: 200 });
225
+ }
226
+ if (url.endsWith('/event')) {
227
+ const stream = new ReadableStream({
228
+ start(controller) {
229
+ const enc = new TextEncoder();
230
+ for (const ev of events) {
231
+ controller.enqueue(enc.encode(`data: ${JSON.stringify(ev)}\n\n`));
232
+ }
233
+ // Leave the stream open — the adapter cancels via close() on idle.
234
+ },
235
+ });
236
+ return new Response(stream, {
237
+ status: 200, headers: { 'Content-Type': 'text/event-stream' },
238
+ });
239
+ }
240
+ return new Response('not mocked', { status: 404 });
241
+ });
242
+ }
243
+ async function collect(gen) {
244
+ const out = [];
245
+ for await (const chunk of gen)
246
+ out.push(chunk);
247
+ return out;
248
+ }
249
+ describe('OpenCodeHttpAdapter.sendPrompt — happy path', () => {
250
+ it('creates a session, captures sessionID, yields finished text, breaks on idle', async () => {
251
+ const sid = 'ses_happy';
252
+ const events = [
253
+ { type: 'server.connected', properties: {} },
254
+ { type: 'message.part.updated', properties: { sessionID: sid, part: { type: 'step-start' } } },
255
+ { type: 'message.part.updated', properties: {
256
+ sessionID: sid,
257
+ part: { type: 'step-finish', cost: 0.01, tokens: { input: 10, output: 5 } },
258
+ } },
259
+ { type: 'message.part.updated', properties: {
260
+ sessionID: sid,
261
+ part: { type: 'text', text: 'hello world', time: { start: 1, end: 2 } },
262
+ } },
263
+ { type: 'session.status', properties: { sessionID: sid, status: { type: 'idle' } } },
264
+ ];
265
+ const hits = [];
266
+ const fetchImpl = makeFakeFetch(events, { sessionId: sid, hits });
267
+ const serve = makeServeStub('http://test.local');
268
+ const ids = [];
269
+ const usage = [];
270
+ const adapter = new OpenCodeHttpAdapter({ serve, fetchImpl });
271
+ const chunks = await collect(adapter.sendPrompt('imhub-1', 'hi', [], {
272
+ onAgentSessionId: (id) => ids.push(id),
273
+ onUsage: (d) => usage.push(d),
274
+ }));
275
+ expect(chunks).toEqual(['hello world']);
276
+ expect(ids[0]).toBe(sid); // Captured both at create-time and from events
277
+ expect(usage[0].costUsd).toBe(0.01);
278
+ expect(usage[0].tokensInput).toBe(10);
279
+ expect(usage[0].tokensOutput).toBe(5);
280
+ // Must have hit /event, then POST /session, POST /session/:id/message
281
+ const urls = hits.map(h => `${h.method} ${h.url}`);
282
+ expect(urls.some(u => u.startsWith('GET ') && u.endsWith('/event'))).toBe(true);
283
+ expect(urls.some(u => u === 'POST http://test.local/session')).toBe(true);
284
+ expect(urls.some(u => u === `POST http://test.local/session/${sid}/message`)).toBe(true);
285
+ });
286
+ it('skips session create and reuses agentSessionId on resume', async () => {
287
+ const sid = 'ses_resumed';
288
+ const events = [
289
+ { type: 'message.part.updated', properties: {
290
+ sessionID: sid,
291
+ part: { type: 'text', text: 'second turn', time: { end: 1 } },
292
+ } },
293
+ { type: 'session.status', properties: { sessionID: sid, status: { type: 'idle' } } },
294
+ ];
295
+ const hits = [];
296
+ const fetchImpl = makeFakeFetch(events, { hits });
297
+ const serve = makeServeStub('http://test.local');
298
+ const adapter = new OpenCodeHttpAdapter({ serve, fetchImpl });
299
+ const chunks = await collect(adapter.sendPrompt('imhub-1', 'next', [], {
300
+ agentSessionId: sid,
301
+ agentSessionResume: true,
302
+ }));
303
+ expect(chunks).toEqual(['second turn']);
304
+ const urls = hits.map(h => `${h.method} ${h.url}`);
305
+ // No POST /session — we skipped create
306
+ expect(urls.some(u => u === 'POST http://test.local/session')).toBe(false);
307
+ // But we DID submit the prompt to the right session
308
+ expect(urls.some(u => u === `POST http://test.local/session/${sid}/message`)).toBe(true);
309
+ });
310
+ it('falls back to "once" when no approval bus is wired', async () => {
311
+ // Explicit `approvalBus: null` to lock in: when the bridge is
312
+ // unavailable, the adapter falls back to auto-`once`.
313
+ const sid = 'ses_perm';
314
+ const events = [
315
+ { type: 'permission.asked', properties: { id: 'perm_1', sessionID: sid } },
316
+ { type: 'message.part.updated', properties: {
317
+ sessionID: sid,
318
+ part: { type: 'text', text: 'done', time: { end: 1 } },
319
+ } },
320
+ { type: 'session.status', properties: { sessionID: sid, status: { type: 'idle' } } },
321
+ ];
322
+ const hits = [];
323
+ const fetchImpl = makeFakeFetch(events, { sessionId: sid, hits });
324
+ const serve = makeServeStub('http://test.local');
325
+ const adapter = new OpenCodeHttpAdapter({ serve, fetchImpl, approvalBus: null });
326
+ await collect(adapter.sendPrompt('imhub-1', 'go', [], {}));
327
+ const replyHit = hits.find(h => h.url === 'http://test.local/permission/perm_1/reply' && h.method === 'POST');
328
+ expect(replyHit).toBeDefined();
329
+ expect(replyHit?.body?.reply).toBe('once');
330
+ });
331
+ it('falls back to "once" when bus is wired but IM ctx is missing', async () => {
332
+ // When the prompt comes from a non-IM caller (web UI, scheduler),
333
+ // there's no thread to surface a card to even though the bus is alive.
334
+ const { ApprovalBus } = await import('../../../core/approval-bus.js');
335
+ const bus = new ApprovalBus({ approvalTimeoutMs: 200 });
336
+ bus.setNotifier(async () => { });
337
+ const sid = 'ses_perm_noctx';
338
+ const events = [
339
+ { type: 'permission.asked', properties: { id: 'perm_x', sessionID: sid } },
340
+ { type: 'session.status', properties: { sessionID: sid, status: { type: 'idle' } } },
341
+ ];
342
+ const hits = [];
343
+ const fetchImpl = makeFakeFetch(events, { sessionId: sid, hits });
344
+ const serve = makeServeStub('http://test.local');
345
+ const adapter = new OpenCodeHttpAdapter({ serve, fetchImpl, approvalBus: bus });
346
+ // No threadId / platform → bridge declines, fallback fires.
347
+ await collect(adapter.sendPrompt('imhub-1', 'go', [], {}));
348
+ const replyHit = hits.find(h => h.url === 'http://test.local/permission/perm_x/reply');
349
+ expect(replyHit).toBeDefined();
350
+ expect(replyHit?.body?.reply).toBe('once');
351
+ expect(bus.hasPendingFor('any')).toBe(false);
352
+ });
353
+ it('routes permission.asked through the IM bridge: allow → POST reply once', async () => {
354
+ const { ApprovalBus } = await import('../../../core/approval-bus.js');
355
+ const bus = new ApprovalBus({ approvalTimeoutMs: 5000 });
356
+ let notified = null;
357
+ bus.setNotifier(async (n) => {
358
+ notified = { reqId: n.reqId, toolName: n.toolName, threadId: n.ctx.threadId };
359
+ });
360
+ const sid = 'ses_bridge';
361
+ const events = [
362
+ { type: 'permission.asked', properties: {
363
+ id: 'perm_b1', sessionID: sid,
364
+ permission: 'external_directory',
365
+ patterns: ['/root/workspace/im-hub/*'],
366
+ } },
367
+ { type: 'message.part.updated', properties: {
368
+ sessionID: sid,
369
+ part: { type: 'text', text: 'after approval', time: { end: 1 } },
370
+ } },
371
+ { type: 'session.status', properties: { sessionID: sid, status: { type: 'idle' } } },
372
+ ];
373
+ const hits = [];
374
+ const fetchImpl = makeFakeFetch(events, { sessionId: sid, hits });
375
+ const serve = makeServeStub('http://test.local');
376
+ const adapter = new OpenCodeHttpAdapter({ serve, fetchImpl, approvalBus: bus });
377
+ // Drive sendPrompt + simulate the user replying "y" once the bus has
378
+ // surfaced the request. We can't easily await mid-stream, so kick the
379
+ // resolve after a tick.
380
+ const drained = collect(adapter.sendPrompt('imhub-1', 'go', [], {
381
+ threadId: 'thread-X', platform: 'wechat', userId: 'u', channelId: 'c',
382
+ }));
383
+ // Wait for the notifier to fire (bridge registered its synthetic pending).
384
+ for (let i = 0; i < 50 && !notified; i++)
385
+ await new Promise(r => setTimeout(r, 5));
386
+ expect(notified).not.toBeNull();
387
+ expect(notified.reqId).toBe('perm_b1');
388
+ expect(notified.toolName).toBe('external_directory');
389
+ expect(notified.threadId).toBe('thread-X');
390
+ // User clicks "approve" → bus.resolvePending → adapter's dispatch → POST.
391
+ bus.resolvePending('thread-X', { behavior: 'allow' });
392
+ await drained;
393
+ const replyHit = hits.find(h => h.url === 'http://test.local/permission/perm_b1/reply' && h.method === 'POST');
394
+ expect(replyHit).toBeDefined();
395
+ expect(replyHit?.body?.reply).toBe('once');
396
+ });
397
+ it('routes permission.asked through the IM bridge: deny → POST reply reject + message', async () => {
398
+ const { ApprovalBus } = await import('../../../core/approval-bus.js');
399
+ const bus = new ApprovalBus({ approvalTimeoutMs: 5000 });
400
+ bus.setNotifier(async () => { });
401
+ const sid = 'ses_bridge_deny';
402
+ const events = [
403
+ { type: 'permission.asked', properties: { id: 'perm_d1', sessionID: sid, permission: 'bash' } },
404
+ { type: 'session.status', properties: { sessionID: sid, status: { type: 'idle' } } },
405
+ ];
406
+ const hits = [];
407
+ const fetchImpl = makeFakeFetch(events, { sessionId: sid, hits });
408
+ const serve = makeServeStub('http://test.local');
409
+ const adapter = new OpenCodeHttpAdapter({ serve, fetchImpl, approvalBus: bus });
410
+ const drained = collect(adapter.sendPrompt('imhub-1', 'go', [], {
411
+ threadId: 'thread-Y', platform: 'wechat',
412
+ }));
413
+ for (let i = 0; i < 50 && !bus.hasPendingFor('thread-Y'); i++)
414
+ await new Promise(r => setTimeout(r, 5));
415
+ expect(bus.hasPendingFor('thread-Y')).toBe(true);
416
+ bus.resolvePending('thread-Y', { behavior: 'deny', message: 'user said no' });
417
+ await drained;
418
+ const replyHit = hits.find(h => h.url === 'http://test.local/permission/perm_d1/reply');
419
+ expect(replyHit).toBeDefined();
420
+ const body = replyHit.body;
421
+ expect(body.reply).toBe('reject');
422
+ expect(body.message).toBe('user said no');
423
+ });
424
+ it('surfaces session.error message as a final chunk', async () => {
425
+ const sid = 'ses_err';
426
+ const events = [
427
+ { type: 'session.error', properties: {
428
+ sessionID: sid,
429
+ error: { name: 'ProviderError', data: { message: 'rate limited' } },
430
+ } },
431
+ { type: 'session.status', properties: { sessionID: sid, status: { type: 'idle' } } },
432
+ ];
433
+ const fetchImpl = makeFakeFetch(events, { sessionId: sid });
434
+ const serve = makeServeStub('http://test.local');
435
+ const adapter = new OpenCodeHttpAdapter({ serve, fetchImpl });
436
+ const chunks = await collect(adapter.sendPrompt('imhub-1', 'oops', [], {}));
437
+ expect(chunks.some(c => c.includes('rate limited'))).toBe(true);
438
+ });
439
+ });
440
+ describe('OpenCodeHttpAdapter.sendPrompt — plan mode', () => {
441
+ it('creates a session with agent=plan and no permission ruleset', async () => {
442
+ const sid = 'ses_plan_new';
443
+ const events = [
444
+ { type: 'message.part.updated', properties: {
445
+ sessionID: sid,
446
+ part: { type: 'text', text: 'plan ready', time: { end: 1 } },
447
+ } },
448
+ { type: 'session.status', properties: { sessionID: sid, status: { type: 'idle' } } },
449
+ ];
450
+ const hits = [];
451
+ const fetchImpl = makeFakeFetch(events, { sessionId: sid, hits });
452
+ const serve = makeServeStub('http://test.local');
453
+ const adapter = new OpenCodeHttpAdapter({ serve, fetchImpl });
454
+ await collect(adapter.sendPrompt('imhub-1', 'design X', [], { planMode: true }));
455
+ const createHit = hits.find(h => h.url === 'http://test.local/session' && h.method === 'POST');
456
+ expect(createHit).toBeDefined();
457
+ const createBody = createHit.body;
458
+ expect(createBody.agent).toBe('plan');
459
+ // plan agent's own ruleset governs — we MUST NOT stack our medium gate.
460
+ expect(createBody.permission).toBeUndefined();
461
+ const messageHit = hits.find(h => h.url === `http://test.local/session/${sid}/message` && h.method === 'POST');
462
+ expect(messageHit).toBeDefined();
463
+ const messageBody = messageHit.body;
464
+ expect(messageBody.agent).toBe('plan');
465
+ });
466
+ it('on resume, posts agent=plan per message and skips ruleset PATCH', async () => {
467
+ const sid = 'ses_plan_resume';
468
+ const events = [
469
+ { type: 'message.part.updated', properties: {
470
+ sessionID: sid,
471
+ part: { type: 'text', text: 'replanning', time: { end: 1 } },
472
+ } },
473
+ { type: 'session.status', properties: { sessionID: sid, status: { type: 'idle' } } },
474
+ ];
475
+ const hits = [];
476
+ const fetchImpl = makeFakeFetch(events, { hits });
477
+ const serve = makeServeStub('http://test.local');
478
+ const adapter = new OpenCodeHttpAdapter({ serve, fetchImpl });
479
+ await collect(adapter.sendPrompt('imhub-1', 'redo plan', [], {
480
+ agentSessionId: sid,
481
+ agentSessionResume: true,
482
+ planMode: true,
483
+ }));
484
+ const urls = hits.map(h => `${h.method} ${h.url}`);
485
+ // Plan-mode resume must NOT PATCH the session — plan agent's own rules win.
486
+ expect(urls.some(u => u === `PATCH http://test.local/session/${sid}`)).toBe(false);
487
+ // But it MUST submit the prompt with agent=plan to override session default.
488
+ const messageHit = hits.find(h => h.url === `http://test.local/session/${sid}/message` && h.method === 'POST');
489
+ expect(messageHit.body.agent).toBe('plan');
490
+ });
491
+ });
492
+ //# sourceMappingURL=http-adapter.test.js.map