agim-cli 1.0.1

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 (453) hide show
  1. package/CHANGELOG.md +1234 -0
  2. package/LICENSE +21 -0
  3. package/README.md +422 -0
  4. package/README.zh-CN.md +414 -0
  5. package/dist/cli-ui/cmd-handlers.d.ts +11 -0
  6. package/dist/cli-ui/cmd-handlers.d.ts.map +1 -0
  7. package/dist/cli-ui/cmd-handlers.js +240 -0
  8. package/dist/cli-ui/cmd-handlers.js.map +1 -0
  9. package/dist/cli-ui/config-wizard.d.ts +3 -0
  10. package/dist/cli-ui/config-wizard.d.ts.map +1 -0
  11. package/dist/cli-ui/config-wizard.js +851 -0
  12. package/dist/cli-ui/config-wizard.js.map +1 -0
  13. package/dist/cli-ui/entry-menu.d.ts +28 -0
  14. package/dist/cli-ui/entry-menu.d.ts.map +1 -0
  15. package/dist/cli-ui/entry-menu.js +50 -0
  16. package/dist/cli-ui/entry-menu.js.map +1 -0
  17. package/dist/cli-ui/env-file.d.ts +35 -0
  18. package/dist/cli-ui/env-file.d.ts.map +1 -0
  19. package/dist/cli-ui/env-file.js +163 -0
  20. package/dist/cli-ui/env-file.js.map +1 -0
  21. package/dist/cli-ui/i18n.d.ts +204 -0
  22. package/dist/cli-ui/i18n.d.ts.map +1 -0
  23. package/dist/cli-ui/i18n.js +455 -0
  24. package/dist/cli-ui/i18n.js.map +1 -0
  25. package/dist/cli-ui/lang-picker.d.ts +10 -0
  26. package/dist/cli-ui/lang-picker.d.ts.map +1 -0
  27. package/dist/cli-ui/lang-picker.js +33 -0
  28. package/dist/cli-ui/lang-picker.js.map +1 -0
  29. package/dist/cli-ui/paths.d.ts +4 -0
  30. package/dist/cli-ui/paths.d.ts.map +1 -0
  31. package/dist/cli-ui/paths.js +11 -0
  32. package/dist/cli-ui/paths.js.map +1 -0
  33. package/dist/cli-ui/prompts.d.ts +65 -0
  34. package/dist/cli-ui/prompts.d.ts.map +1 -0
  35. package/dist/cli-ui/prompts.js +125 -0
  36. package/dist/cli-ui/prompts.js.map +1 -0
  37. package/dist/cli-ui/service.d.ts +41 -0
  38. package/dist/cli-ui/service.d.ts.map +1 -0
  39. package/dist/cli-ui/service.js +241 -0
  40. package/dist/cli-ui/service.js.map +1 -0
  41. package/dist/cli.d.ts +3 -0
  42. package/dist/cli.d.ts.map +1 -0
  43. package/dist/cli.js +1143 -0
  44. package/dist/cli.js.map +1 -0
  45. package/dist/core/acp-server.d.ts +8 -0
  46. package/dist/core/acp-server.d.ts.map +1 -0
  47. package/dist/core/acp-server.js +266 -0
  48. package/dist/core/acp-server.js.map +1 -0
  49. package/dist/core/agent-base.d.ts +94 -0
  50. package/dist/core/agent-base.d.ts.map +1 -0
  51. package/dist/core/agent-base.js +373 -0
  52. package/dist/core/agent-base.js.map +1 -0
  53. package/dist/core/agent-cwd.d.ts +48 -0
  54. package/dist/core/agent-cwd.d.ts.map +1 -0
  55. package/dist/core/agent-cwd.js +181 -0
  56. package/dist/core/agent-cwd.js.map +1 -0
  57. package/dist/core/agent-helper.d.ts +65 -0
  58. package/dist/core/agent-helper.d.ts.map +1 -0
  59. package/dist/core/agent-helper.js +150 -0
  60. package/dist/core/agent-helper.js.map +1 -0
  61. package/dist/core/agim-paths.d.ts +10 -0
  62. package/dist/core/agim-paths.d.ts.map +1 -0
  63. package/dist/core/agim-paths.js +64 -0
  64. package/dist/core/agim-paths.js.map +1 -0
  65. package/dist/core/approval-bus.d.ts +300 -0
  66. package/dist/core/approval-bus.d.ts.map +1 -0
  67. package/dist/core/approval-bus.js +990 -0
  68. package/dist/core/approval-bus.js.map +1 -0
  69. package/dist/core/approval-router.d.ts +101 -0
  70. package/dist/core/approval-router.d.ts.map +1 -0
  71. package/dist/core/approval-router.js +540 -0
  72. package/dist/core/approval-router.js.map +1 -0
  73. package/dist/core/audit-log.d.ts +55 -0
  74. package/dist/core/audit-log.d.ts.map +1 -0
  75. package/dist/core/audit-log.js +203 -0
  76. package/dist/core/audit-log.js.map +1 -0
  77. package/dist/core/bgjob-reader.d.ts +65 -0
  78. package/dist/core/bgjob-reader.d.ts.map +1 -0
  79. package/dist/core/bgjob-reader.js +212 -0
  80. package/dist/core/bgjob-reader.js.map +1 -0
  81. package/dist/core/circuit-breaker.d.ts +37 -0
  82. package/dist/core/circuit-breaker.d.ts.map +1 -0
  83. package/dist/core/circuit-breaker.js +115 -0
  84. package/dist/core/circuit-breaker.js.map +1 -0
  85. package/dist/core/commands/agent.d.ts +4 -0
  86. package/dist/core/commands/agent.d.ts.map +1 -0
  87. package/dist/core/commands/agent.js +40 -0
  88. package/dist/core/commands/agent.js.map +1 -0
  89. package/dist/core/commands/approval.d.ts +3 -0
  90. package/dist/core/commands/approval.d.ts.map +1 -0
  91. package/dist/core/commands/approval.js +85 -0
  92. package/dist/core/commands/approval.js.map +1 -0
  93. package/dist/core/commands/audit.d.ts +3 -0
  94. package/dist/core/commands/audit.d.ts.map +1 -0
  95. package/dist/core/commands/audit.js +84 -0
  96. package/dist/core/commands/audit.js.map +1 -0
  97. package/dist/core/commands/builtin.d.ts +3 -0
  98. package/dist/core/commands/builtin.d.ts.map +1 -0
  99. package/dist/core/commands/builtin.js +304 -0
  100. package/dist/core/commands/builtin.js.map +1 -0
  101. package/dist/core/commands/cron.d.ts +3 -0
  102. package/dist/core/commands/cron.d.ts.map +1 -0
  103. package/dist/core/commands/cron.js +128 -0
  104. package/dist/core/commands/cron.js.map +1 -0
  105. package/dist/core/commands/job.d.ts +3 -0
  106. package/dist/core/commands/job.d.ts.map +1 -0
  107. package/dist/core/commands/job.js +195 -0
  108. package/dist/core/commands/job.js.map +1 -0
  109. package/dist/core/commands/memo.d.ts +3 -0
  110. package/dist/core/commands/memo.d.ts.map +1 -0
  111. package/dist/core/commands/memo.js +151 -0
  112. package/dist/core/commands/memo.js.map +1 -0
  113. package/dist/core/commands/model.d.ts +9 -0
  114. package/dist/core/commands/model.d.ts.map +1 -0
  115. package/dist/core/commands/model.js +183 -0
  116. package/dist/core/commands/model.js.map +1 -0
  117. package/dist/core/commands/plan.d.ts +3 -0
  118. package/dist/core/commands/plan.d.ts.map +1 -0
  119. package/dist/core/commands/plan.js +75 -0
  120. package/dist/core/commands/plan.js.map +1 -0
  121. package/dist/core/commands/remind.d.ts +3 -0
  122. package/dist/core/commands/remind.d.ts.map +1 -0
  123. package/dist/core/commands/remind.js +271 -0
  124. package/dist/core/commands/remind.js.map +1 -0
  125. package/dist/core/commands/router.d.ts +3 -0
  126. package/dist/core/commands/router.d.ts.map +1 -0
  127. package/dist/core/commands/router.js +71 -0
  128. package/dist/core/commands/router.js.map +1 -0
  129. package/dist/core/commands/sessions.d.ts +3 -0
  130. package/dist/core/commands/sessions.d.ts.map +1 -0
  131. package/dist/core/commands/sessions.js +88 -0
  132. package/dist/core/commands/sessions.js.map +1 -0
  133. package/dist/core/commands/stats.d.ts +3 -0
  134. package/dist/core/commands/stats.d.ts.map +1 -0
  135. package/dist/core/commands/stats.js +73 -0
  136. package/dist/core/commands/stats.js.map +1 -0
  137. package/dist/core/commands/think.d.ts +3 -0
  138. package/dist/core/commands/think.d.ts.map +1 -0
  139. package/dist/core/commands/think.js +28 -0
  140. package/dist/core/commands/think.js.map +1 -0
  141. package/dist/core/commands/workspaces.d.ts +3 -0
  142. package/dist/core/commands/workspaces.d.ts.map +1 -0
  143. package/dist/core/commands/workspaces.js +47 -0
  144. package/dist/core/commands/workspaces.js.map +1 -0
  145. package/dist/core/config-schema.d.ts +60 -0
  146. package/dist/core/config-schema.d.ts.map +1 -0
  147. package/dist/core/config-schema.js +75 -0
  148. package/dist/core/config-schema.js.map +1 -0
  149. package/dist/core/coord-systems.d.ts +65 -0
  150. package/dist/core/coord-systems.d.ts.map +1 -0
  151. package/dist/core/coord-systems.js +229 -0
  152. package/dist/core/coord-systems.js.map +1 -0
  153. package/dist/core/cron.d.ts +29 -0
  154. package/dist/core/cron.d.ts.map +1 -0
  155. package/dist/core/cron.js +184 -0
  156. package/dist/core/cron.js.map +1 -0
  157. package/dist/core/event-bus.d.ts +80 -0
  158. package/dist/core/event-bus.d.ts.map +1 -0
  159. package/dist/core/event-bus.js +62 -0
  160. package/dist/core/event-bus.js.map +1 -0
  161. package/dist/core/intent-llm.d.ts +27 -0
  162. package/dist/core/intent-llm.d.ts.map +1 -0
  163. package/dist/core/intent-llm.js +170 -0
  164. package/dist/core/intent-llm.js.map +1 -0
  165. package/dist/core/intent.d.ts +12 -0
  166. package/dist/core/intent.d.ts.map +1 -0
  167. package/dist/core/intent.js +187 -0
  168. package/dist/core/intent.js.map +1 -0
  169. package/dist/core/job-board.d.ts +82 -0
  170. package/dist/core/job-board.d.ts.map +1 -0
  171. package/dist/core/job-board.js +379 -0
  172. package/dist/core/job-board.js.map +1 -0
  173. package/dist/core/location-context.d.ts +32 -0
  174. package/dist/core/location-context.d.ts.map +1 -0
  175. package/dist/core/location-context.js +69 -0
  176. package/dist/core/location-context.js.map +1 -0
  177. package/dist/core/location-token.d.ts +57 -0
  178. package/dist/core/location-token.d.ts.map +1 -0
  179. package/dist/core/location-token.js +128 -0
  180. package/dist/core/location-token.js.map +1 -0
  181. package/dist/core/logger.d.ts +6 -0
  182. package/dist/core/logger.d.ts.map +1 -0
  183. package/dist/core/logger.js +54 -0
  184. package/dist/core/logger.js.map +1 -0
  185. package/dist/core/memo-rpc.d.ts +13 -0
  186. package/dist/core/memo-rpc.d.ts.map +1 -0
  187. package/dist/core/memo-rpc.js +288 -0
  188. package/dist/core/memo-rpc.js.map +1 -0
  189. package/dist/core/memos.d.ts +163 -0
  190. package/dist/core/memos.d.ts.map +1 -0
  191. package/dist/core/memos.js +502 -0
  192. package/dist/core/memos.js.map +1 -0
  193. package/dist/core/metrics.d.ts +55 -0
  194. package/dist/core/metrics.d.ts.map +1 -0
  195. package/dist/core/metrics.js +291 -0
  196. package/dist/core/metrics.js.map +1 -0
  197. package/dist/core/onboarding.d.ts +99 -0
  198. package/dist/core/onboarding.d.ts.map +1 -0
  199. package/dist/core/onboarding.js +426 -0
  200. package/dist/core/onboarding.js.map +1 -0
  201. package/dist/core/pending-reminder.d.ts +25 -0
  202. package/dist/core/pending-reminder.d.ts.map +1 -0
  203. package/dist/core/pending-reminder.js +53 -0
  204. package/dist/core/pending-reminder.js.map +1 -0
  205. package/dist/core/rate-limiter.d.ts +44 -0
  206. package/dist/core/rate-limiter.d.ts.map +1 -0
  207. package/dist/core/rate-limiter.js +115 -0
  208. package/dist/core/rate-limiter.js.map +1 -0
  209. package/dist/core/registry.d.ts +32 -0
  210. package/dist/core/registry.d.ts.map +1 -0
  211. package/dist/core/registry.js +126 -0
  212. package/dist/core/registry.js.map +1 -0
  213. package/dist/core/remind-intent.d.ts +25 -0
  214. package/dist/core/remind-intent.d.ts.map +1 -0
  215. package/dist/core/remind-intent.js +196 -0
  216. package/dist/core/remind-intent.js.map +1 -0
  217. package/dist/core/reminder-rpc.d.ts +17 -0
  218. package/dist/core/reminder-rpc.d.ts.map +1 -0
  219. package/dist/core/reminder-rpc.js +169 -0
  220. package/dist/core/reminder-rpc.js.map +1 -0
  221. package/dist/core/reminders.d.ts +159 -0
  222. package/dist/core/reminders.d.ts.map +1 -0
  223. package/dist/core/reminders.js +977 -0
  224. package/dist/core/reminders.js.map +1 -0
  225. package/dist/core/router.d.ts +55 -0
  226. package/dist/core/router.d.ts.map +1 -0
  227. package/dist/core/router.js +497 -0
  228. package/dist/core/router.js.map +1 -0
  229. package/dist/core/schedule.d.ts +65 -0
  230. package/dist/core/schedule.d.ts.map +1 -0
  231. package/dist/core/schedule.js +323 -0
  232. package/dist/core/schedule.js.map +1 -0
  233. package/dist/core/session.d.ts +182 -0
  234. package/dist/core/session.d.ts.map +1 -0
  235. package/dist/core/session.js +807 -0
  236. package/dist/core/session.js.map +1 -0
  237. package/dist/core/sqlite-helper.d.ts +37 -0
  238. package/dist/core/sqlite-helper.d.ts.map +1 -0
  239. package/dist/core/sqlite-helper.js +79 -0
  240. package/dist/core/sqlite-helper.js.map +1 -0
  241. package/dist/core/transcribe.d.ts +25 -0
  242. package/dist/core/transcribe.d.ts.map +1 -0
  243. package/dist/core/transcribe.js +217 -0
  244. package/dist/core/transcribe.js.map +1 -0
  245. package/dist/core/types.d.ts +360 -0
  246. package/dist/core/types.d.ts.map +1 -0
  247. package/dist/core/types.js +3 -0
  248. package/dist/core/types.js.map +1 -0
  249. package/dist/core/workspace.d.ts +67 -0
  250. package/dist/core/workspace.d.ts.map +1 -0
  251. package/dist/core/workspace.js +113 -0
  252. package/dist/core/workspace.js.map +1 -0
  253. package/dist/index.d.ts +5 -0
  254. package/dist/index.d.ts.map +1 -0
  255. package/dist/index.js +6 -0
  256. package/dist/index.js.map +1 -0
  257. package/dist/plugins/agents/acp/acp-adapter.d.ts +16 -0
  258. package/dist/plugins/agents/acp/acp-adapter.d.ts.map +1 -0
  259. package/dist/plugins/agents/acp/acp-adapter.js +49 -0
  260. package/dist/plugins/agents/acp/acp-adapter.js.map +1 -0
  261. package/dist/plugins/agents/acp/acp-client.d.ts +32 -0
  262. package/dist/plugins/agents/acp/acp-client.d.ts.map +1 -0
  263. package/dist/plugins/agents/acp/acp-client.js +177 -0
  264. package/dist/plugins/agents/acp/acp-client.js.map +1 -0
  265. package/dist/plugins/agents/acp/discovery.d.ts +19 -0
  266. package/dist/plugins/agents/acp/discovery.d.ts.map +1 -0
  267. package/dist/plugins/agents/acp/discovery.js +111 -0
  268. package/dist/plugins/agents/acp/discovery.js.map +1 -0
  269. package/dist/plugins/agents/acp/index.d.ts +4 -0
  270. package/dist/plugins/agents/acp/index.d.ts.map +1 -0
  271. package/dist/plugins/agents/acp/index.js +4 -0
  272. package/dist/plugins/agents/acp/index.js.map +1 -0
  273. package/dist/plugins/agents/acp/types.d.ts +62 -0
  274. package/dist/plugins/agents/acp/types.d.ts.map +1 -0
  275. package/dist/plugins/agents/acp/types.js +5 -0
  276. package/dist/plugins/agents/acp/types.js.map +1 -0
  277. package/dist/plugins/agents/claude-code/index.d.ts +25 -0
  278. package/dist/plugins/agents/claude-code/index.d.ts.map +1 -0
  279. package/dist/plugins/agents/claude-code/index.js +184 -0
  280. package/dist/plugins/agents/claude-code/index.js.map +1 -0
  281. package/dist/plugins/agents/claude-code/mcp-approval-server.d.ts +59 -0
  282. package/dist/plugins/agents/claude-code/mcp-approval-server.d.ts.map +1 -0
  283. package/dist/plugins/agents/claude-code/mcp-approval-server.js +645 -0
  284. package/dist/plugins/agents/claude-code/mcp-approval-server.js.map +1 -0
  285. package/dist/plugins/agents/codex/build-mcp-cli-args.d.ts +28 -0
  286. package/dist/plugins/agents/codex/build-mcp-cli-args.d.ts.map +1 -0
  287. package/dist/plugins/agents/codex/build-mcp-cli-args.js +74 -0
  288. package/dist/plugins/agents/codex/build-mcp-cli-args.js.map +1 -0
  289. package/dist/plugins/agents/codex/index.d.ts +53 -0
  290. package/dist/plugins/agents/codex/index.d.ts.map +1 -0
  291. package/dist/plugins/agents/codex/index.js +341 -0
  292. package/dist/plugins/agents/codex/index.js.map +1 -0
  293. package/dist/plugins/agents/copilot/index.d.ts +35 -0
  294. package/dist/plugins/agents/copilot/index.d.ts.map +1 -0
  295. package/dist/plugins/agents/copilot/index.js +182 -0
  296. package/dist/plugins/agents/copilot/index.js.map +1 -0
  297. package/dist/plugins/agents/opencode/ensure-mcp-config.d.ts +11 -0
  298. package/dist/plugins/agents/opencode/ensure-mcp-config.d.ts.map +1 -0
  299. package/dist/plugins/agents/opencode/ensure-mcp-config.js +100 -0
  300. package/dist/plugins/agents/opencode/ensure-mcp-config.js.map +1 -0
  301. package/dist/plugins/agents/opencode/index.d.ts +5 -0
  302. package/dist/plugins/agents/opencode/index.d.ts.map +1 -0
  303. package/dist/plugins/agents/opencode/index.js +30 -0
  304. package/dist/plugins/agents/opencode/index.js.map +1 -0
  305. package/dist/plugins/agents/opencode/opencode-http-adapter.d.ts +166 -0
  306. package/dist/plugins/agents/opencode/opencode-http-adapter.d.ts.map +1 -0
  307. package/dist/plugins/agents/opencode/opencode-http-adapter.js +682 -0
  308. package/dist/plugins/agents/opencode/opencode-http-adapter.js.map +1 -0
  309. package/dist/plugins/agents/opencode/opencode-stdio-adapter.d.ts +32 -0
  310. package/dist/plugins/agents/opencode/opencode-stdio-adapter.d.ts.map +1 -0
  311. package/dist/plugins/agents/opencode/opencode-stdio-adapter.js +137 -0
  312. package/dist/plugins/agents/opencode/opencode-stdio-adapter.js.map +1 -0
  313. package/dist/plugins/agents/opencode/serve-manager.d.ts +27 -0
  314. package/dist/plugins/agents/opencode/serve-manager.d.ts.map +1 -0
  315. package/dist/plugins/agents/opencode/serve-manager.js +194 -0
  316. package/dist/plugins/agents/opencode/serve-manager.js.map +1 -0
  317. package/dist/plugins/messengers/dingtalk/dingtalk-adapter.d.ts +57 -0
  318. package/dist/plugins/messengers/dingtalk/dingtalk-adapter.d.ts.map +1 -0
  319. package/dist/plugins/messengers/dingtalk/dingtalk-adapter.js +409 -0
  320. package/dist/plugins/messengers/dingtalk/dingtalk-adapter.js.map +1 -0
  321. package/dist/plugins/messengers/dingtalk/dingtalk-client.d.ts +48 -0
  322. package/dist/plugins/messengers/dingtalk/dingtalk-client.d.ts.map +1 -0
  323. package/dist/plugins/messengers/dingtalk/dingtalk-client.js +236 -0
  324. package/dist/plugins/messengers/dingtalk/dingtalk-client.js.map +1 -0
  325. package/dist/plugins/messengers/dingtalk/index.d.ts +3 -0
  326. package/dist/plugins/messengers/dingtalk/index.d.ts.map +1 -0
  327. package/dist/plugins/messengers/dingtalk/index.js +3 -0
  328. package/dist/plugins/messengers/dingtalk/index.js.map +1 -0
  329. package/dist/plugins/messengers/dingtalk/link-coords.d.ts +23 -0
  330. package/dist/plugins/messengers/dingtalk/link-coords.d.ts.map +1 -0
  331. package/dist/plugins/messengers/dingtalk/link-coords.js +89 -0
  332. package/dist/plugins/messengers/dingtalk/link-coords.js.map +1 -0
  333. package/dist/plugins/messengers/dingtalk/media-store.d.ts +16 -0
  334. package/dist/plugins/messengers/dingtalk/media-store.d.ts.map +1 -0
  335. package/dist/plugins/messengers/dingtalk/media-store.js +77 -0
  336. package/dist/plugins/messengers/dingtalk/media-store.js.map +1 -0
  337. package/dist/plugins/messengers/dingtalk/types.d.ts +82 -0
  338. package/dist/plugins/messengers/dingtalk/types.d.ts.map +1 -0
  339. package/dist/plugins/messengers/dingtalk/types.js +14 -0
  340. package/dist/plugins/messengers/dingtalk/types.js.map +1 -0
  341. package/dist/plugins/messengers/discord/discord-adapter.d.ts +21 -0
  342. package/dist/plugins/messengers/discord/discord-adapter.d.ts.map +1 -0
  343. package/dist/plugins/messengers/discord/discord-adapter.js +238 -0
  344. package/dist/plugins/messengers/discord/discord-adapter.js.map +1 -0
  345. package/dist/plugins/messengers/discord/index.d.ts +4 -0
  346. package/dist/plugins/messengers/discord/index.d.ts.map +1 -0
  347. package/dist/plugins/messengers/discord/index.js +4 -0
  348. package/dist/plugins/messengers/discord/index.js.map +1 -0
  349. package/dist/plugins/messengers/discord/markdown-to-discord.d.ts +11 -0
  350. package/dist/plugins/messengers/discord/markdown-to-discord.d.ts.map +1 -0
  351. package/dist/plugins/messengers/discord/markdown-to-discord.js +59 -0
  352. package/dist/plugins/messengers/discord/markdown-to-discord.js.map +1 -0
  353. package/dist/plugins/messengers/discord/types.d.ts +9 -0
  354. package/dist/plugins/messengers/discord/types.d.ts.map +1 -0
  355. package/dist/plugins/messengers/discord/types.js +3 -0
  356. package/dist/plugins/messengers/discord/types.js.map +1 -0
  357. package/dist/plugins/messengers/email/email-adapter.d.ts +33 -0
  358. package/dist/plugins/messengers/email/email-adapter.d.ts.map +1 -0
  359. package/dist/plugins/messengers/email/email-adapter.js +137 -0
  360. package/dist/plugins/messengers/email/email-adapter.js.map +1 -0
  361. package/dist/plugins/messengers/feishu/card-builder.d.ts +23 -0
  362. package/dist/plugins/messengers/feishu/card-builder.d.ts.map +1 -0
  363. package/dist/plugins/messengers/feishu/card-builder.js +89 -0
  364. package/dist/plugins/messengers/feishu/card-builder.js.map +1 -0
  365. package/dist/plugins/messengers/feishu/feishu-adapter.d.ts +23 -0
  366. package/dist/plugins/messengers/feishu/feishu-adapter.d.ts.map +1 -0
  367. package/dist/plugins/messengers/feishu/feishu-adapter.js +250 -0
  368. package/dist/plugins/messengers/feishu/feishu-adapter.js.map +1 -0
  369. package/dist/plugins/messengers/feishu/feishu-client.d.ts +43 -0
  370. package/dist/plugins/messengers/feishu/feishu-client.d.ts.map +1 -0
  371. package/dist/plugins/messengers/feishu/feishu-client.js +118 -0
  372. package/dist/plugins/messengers/feishu/feishu-client.js.map +1 -0
  373. package/dist/plugins/messengers/feishu/index.d.ts +4 -0
  374. package/dist/plugins/messengers/feishu/index.d.ts.map +1 -0
  375. package/dist/plugins/messengers/feishu/index.js +4 -0
  376. package/dist/plugins/messengers/feishu/index.js.map +1 -0
  377. package/dist/plugins/messengers/feishu/types.d.ts +113 -0
  378. package/dist/plugins/messengers/feishu/types.d.ts.map +1 -0
  379. package/dist/plugins/messengers/feishu/types.js +4 -0
  380. package/dist/plugins/messengers/feishu/types.js.map +1 -0
  381. package/dist/plugins/messengers/telegram/index.d.ts +4 -0
  382. package/dist/plugins/messengers/telegram/index.d.ts.map +1 -0
  383. package/dist/plugins/messengers/telegram/index.js +4 -0
  384. package/dist/plugins/messengers/telegram/index.js.map +1 -0
  385. package/dist/plugins/messengers/telegram/markdown-to-html.d.ts +5 -0
  386. package/dist/plugins/messengers/telegram/markdown-to-html.d.ts.map +1 -0
  387. package/dist/plugins/messengers/telegram/markdown-to-html.js +186 -0
  388. package/dist/plugins/messengers/telegram/markdown-to-html.js.map +1 -0
  389. package/dist/plugins/messengers/telegram/media-download.d.ts +59 -0
  390. package/dist/plugins/messengers/telegram/media-download.d.ts.map +1 -0
  391. package/dist/plugins/messengers/telegram/media-download.js +228 -0
  392. package/dist/plugins/messengers/telegram/media-download.js.map +1 -0
  393. package/dist/plugins/messengers/telegram/telegram-adapter.d.ts +77 -0
  394. package/dist/plugins/messengers/telegram/telegram-adapter.d.ts.map +1 -0
  395. package/dist/plugins/messengers/telegram/telegram-adapter.js +880 -0
  396. package/dist/plugins/messengers/telegram/telegram-adapter.js.map +1 -0
  397. package/dist/plugins/messengers/telegram/types.d.ts +47 -0
  398. package/dist/plugins/messengers/telegram/types.d.ts.map +1 -0
  399. package/dist/plugins/messengers/telegram/types.js +3 -0
  400. package/dist/plugins/messengers/telegram/types.js.map +1 -0
  401. package/dist/plugins/messengers/wechat/context-store.d.ts +18 -0
  402. package/dist/plugins/messengers/wechat/context-store.d.ts.map +1 -0
  403. package/dist/plugins/messengers/wechat/context-store.js +105 -0
  404. package/dist/plugins/messengers/wechat/context-store.js.map +1 -0
  405. package/dist/plugins/messengers/wechat/ilink-adapter.d.ts +71 -0
  406. package/dist/plugins/messengers/wechat/ilink-adapter.d.ts.map +1 -0
  407. package/dist/plugins/messengers/wechat/ilink-adapter.js +664 -0
  408. package/dist/plugins/messengers/wechat/ilink-adapter.js.map +1 -0
  409. package/dist/plugins/messengers/wechat/ilink-client.d.ts +75 -0
  410. package/dist/plugins/messengers/wechat/ilink-client.d.ts.map +1 -0
  411. package/dist/plugins/messengers/wechat/ilink-client.js +331 -0
  412. package/dist/plugins/messengers/wechat/ilink-client.js.map +1 -0
  413. package/dist/plugins/messengers/wechat/ilink-types.d.ts +181 -0
  414. package/dist/plugins/messengers/wechat/ilink-types.d.ts.map +1 -0
  415. package/dist/plugins/messengers/wechat/ilink-types.js +22 -0
  416. package/dist/plugins/messengers/wechat/ilink-types.js.map +1 -0
  417. package/dist/plugins/messengers/wechat/media-download.d.ts +32 -0
  418. package/dist/plugins/messengers/wechat/media-download.d.ts.map +1 -0
  419. package/dist/plugins/messengers/wechat/media-download.js +78 -0
  420. package/dist/plugins/messengers/wechat/media-download.js.map +1 -0
  421. package/dist/scripts/migrate-gcj02-to-wgs84.d.ts +3 -0
  422. package/dist/scripts/migrate-gcj02-to-wgs84.d.ts.map +1 -0
  423. package/dist/scripts/migrate-gcj02-to-wgs84.js +52 -0
  424. package/dist/scripts/migrate-gcj02-to-wgs84.js.map +1 -0
  425. package/dist/utils/backoff.d.ts +35 -0
  426. package/dist/utils/backoff.d.ts.map +1 -0
  427. package/dist/utils/backoff.js +59 -0
  428. package/dist/utils/backoff.js.map +1 -0
  429. package/dist/utils/cross-platform.d.ts +26 -0
  430. package/dist/utils/cross-platform.d.ts.map +1 -0
  431. package/dist/utils/cross-platform.js +58 -0
  432. package/dist/utils/cross-platform.js.map +1 -0
  433. package/dist/utils/message-split.d.ts +14 -0
  434. package/dist/utils/message-split.d.ts.map +1 -0
  435. package/dist/utils/message-split.js +65 -0
  436. package/dist/utils/message-split.js.map +1 -0
  437. package/dist/utils/safe-equal.d.ts +2 -0
  438. package/dist/utils/safe-equal.d.ts.map +1 -0
  439. package/dist/utils/safe-equal.js +11 -0
  440. package/dist/utils/safe-equal.js.map +1 -0
  441. package/dist/web/public/_app.js +196 -0
  442. package/dist/web/public/index.html +936 -0
  443. package/dist/web/public/loc.html +305 -0
  444. package/dist/web/public/login.html +106 -0
  445. package/dist/web/public/memos.html +271 -0
  446. package/dist/web/public/reminders.html +234 -0
  447. package/dist/web/public/settings.html +1355 -0
  448. package/dist/web/public/tasks.html +1835 -0
  449. package/dist/web/server.d.ts +12 -0
  450. package/dist/web/server.d.ts.map +1 -0
  451. package/dist/web/server.js +2399 -0
  452. package/dist/web/server.js.map +1 -0
  453. package/package.json +92 -0
@@ -0,0 +1,682 @@
1
+ // OpenCode HTTP driver.
2
+ //
3
+ // Why this exists:
4
+ // `opencode run --format json` (the stdio driver in opencode-stdio-adapter.ts)
5
+ // does NOT emit `permission.asked` events on its stdout stream, and its
6
+ // inline auto-reject logic occasionally fails to release the prompt
7
+ // deferred — the process then hangs until im-hub's 30-min hard SIGTERM.
8
+ // Audit confirmed this on 2026-05-04 (rows id=283/284, exact 1,800,000 ms).
9
+ //
10
+ // What this driver does instead:
11
+ // 1. Lazily starts a long-lived `opencode serve` daemon on 127.0.0.1
12
+ // (via OpencodeServeManager). Probes-and-reuses any healthy daemon
13
+ // already on the configured port so back-to-back im-hub restarts
14
+ // don't bind-fail on a still-running child.
15
+ // 2. Subscribes to /event SSE so the adapter — not the run-CLI — owns the
16
+ // authoritative event stream.
17
+ // 3. Creates sessions via POST /session and submits prompts via
18
+ // POST /session/:id/message. Resumes existing sessions when
19
+ // opts.agentSessionId is set; on first turn of a resumed session this
20
+ // process lifetime, PATCHes the session ruleset to apply the current
21
+ // gate policy.
22
+ // 4. Routes `permission.asked` SSE events through the IM approval bus
23
+ // when an IM context (threadId + platform) and notifier are both
24
+ // available. Decisions translate: allow → opencode `once`, deny →
25
+ // opencode `reject` (with optional message). Without an IM channel
26
+ // the adapter falls back to auto-`once` so non-IM call paths (web,
27
+ // scheduler) keep working.
28
+ // 5. Exits cleanly on `session.status: idle` for the right session,
29
+ // surfaces `session.error`, and is bounded by OPENCODE_TIMEOUT_MS
30
+ // (default 30 min).
31
+ //
32
+ // Selection: env IMHUB_OPENCODE_DRIVER=http picks this; anything else (incl
33
+ // unset) picks the stdio adapter. See ./index.ts factory.
34
+ //
35
+ // Gate policy: env IMHUB_OPENCODE_GATE controls which permissions surface
36
+ // as IM cards. See buildSessionRuleset() for the strict / medium / loose /
37
+ // none levels — medium is the default and gates edit / write / patch.
38
+ //
39
+ // Compatibility: extends OpenCodeAdapter so registry / tests that probe
40
+ // `instanceof OpenCodeAdapter` stay green. The base class's spawnStream is
41
+ // not used — sendPrompt is overridden end-to-end.
42
+ import { OpenCodeAdapter } from './opencode-stdio-adapter.js';
43
+ import { resolveAgentCwd } from '../../../core/agent-cwd.js';
44
+ import { logger as rootLogger } from '../../../core/logger.js';
45
+ import { opencodeServe as defaultServe } from './serve-manager.js';
46
+ import { approvalBus as defaultApprovalBus } from '../../../core/approval-bus.js';
47
+ const log = rootLogger.child({ component: 'agent.opencode.http' });
48
+ const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
49
+ export class OpenCodeHttpAdapter extends OpenCodeAdapter {
50
+ serve;
51
+ fetchImpl;
52
+ /** null when the bridge is intentionally disabled. */
53
+ approvalBus;
54
+ /** Set of opencode session ids whose ruleset we've already PATCHed in this
55
+ * process lifetime. Lets us re-apply the gate policy when an im-hub
56
+ * restart resurfaces an old session, without duplicating rules every
57
+ * turn. Cleared only by im-hub restart — that's fine since the rules
58
+ * the previous run wrote are still in opencode's session DB. */
59
+ rulesetApplied = new Set();
60
+ constructor(opts = {}) {
61
+ super();
62
+ this.serve = opts.serve ?? defaultServe;
63
+ this.fetchImpl = opts.fetchImpl ?? fetch;
64
+ this.approvalBus = opts.approvalBus === null
65
+ ? null
66
+ : (opts.approvalBus ?? defaultApprovalBus);
67
+ log.info({ event: 'opencode.http.driver_selected', bridge: this.approvalBus !== null }, '[opencode] HTTP driver active (real serve + SSE; IM approval bridge when bus has notifier)');
68
+ }
69
+ /**
70
+ * Translate a single SSE event into adapter-side effects:
71
+ * - sessionID capture → opts.onAgentSessionId
72
+ * - step-finish cost/tokens → opts.onUsage
73
+ * - text-part with finished `time.end` → returned as a chunk (with
74
+ * part id when known, so the caller can dedupe against the POST-body
75
+ * fallback for SSE-broken builds — see drainResponseBody).
76
+ * Pure: tests drive this directly without spinning up a daemon.
77
+ */
78
+ inspectHttpEvent(event, sessionID, opts) {
79
+ const props = event.properties ?? {};
80
+ const part = props.part;
81
+ // sessionID can appear on the event root, on properties, on properties.info
82
+ // or inside part. Bubble up the first one we see — setOpencodeSessionId
83
+ // (the receiver) is idempotent.
84
+ const sid = props.sessionID || part?.sessionID || props.info?.sessionID;
85
+ if (sid && opts.onAgentSessionId) {
86
+ try {
87
+ opts.onAgentSessionId(sid);
88
+ }
89
+ catch { /* don't let userland callbacks kill the stream */ }
90
+ }
91
+ if (event.type === 'message.part.updated' && part) {
92
+ if (part.type === 'step-finish') {
93
+ const delta = {};
94
+ if (typeof part.cost === 'number' && Number.isFinite(part.cost))
95
+ delta.costUsd = part.cost;
96
+ if (typeof part.tokens?.input === 'number')
97
+ delta.tokensInput = part.tokens.input;
98
+ if (typeof part.tokens?.output === 'number')
99
+ delta.tokensOutput = part.tokens.output;
100
+ if (delta.costUsd !== undefined || delta.tokensInput !== undefined || delta.tokensOutput !== undefined) {
101
+ if (opts.onUsage) {
102
+ try {
103
+ opts.onUsage(delta);
104
+ }
105
+ catch { /* same — user callback safety */ }
106
+ }
107
+ }
108
+ }
109
+ // Match the stdio adapter: only yield text once the part is fully
110
+ // emitted (time.end set). Avoids streaming partial fragments and keeps
111
+ // /stats response_len aligned across drivers.
112
+ const eventSid = props.sessionID || props.info?.sessionID || part.sessionID;
113
+ if (part.type === 'text' &&
114
+ part.text &&
115
+ part.time?.end &&
116
+ eventSid === sessionID) {
117
+ return { text: part.text, partId: part.id };
118
+ }
119
+ }
120
+ return {};
121
+ }
122
+ /**
123
+ * `session.status` event with status.type === 'idle' for our session is
124
+ * the canonical "stop" signal in opencode (cf. run.ts:532).
125
+ */
126
+ isIdleEvent(event, sessionID) {
127
+ if (event.type !== 'session.status')
128
+ return false;
129
+ const props = event.properties ?? {};
130
+ const sid = props.sessionID || props.info?.sessionID;
131
+ if (sid !== sessionID)
132
+ return false;
133
+ return props.status?.type === 'idle' || props.info?.status?.type === 'idle';
134
+ }
135
+ isErrorEvent(event, sessionID) {
136
+ if (event.type !== 'session.error')
137
+ return null;
138
+ const props = event.properties ?? {};
139
+ if (props.sessionID !== sessionID)
140
+ return null;
141
+ const e = props.error;
142
+ let msg = 'session error';
143
+ if (e && typeof e === 'object') {
144
+ if (typeof e.name === 'string')
145
+ msg = e.name;
146
+ const data = e.data;
147
+ if (data && typeof data.message === 'string')
148
+ msg = data.message;
149
+ }
150
+ return { error: msg };
151
+ }
152
+ buildHttpContextualPrompt(prompt, history) {
153
+ if (!history || history.length === 0)
154
+ return prompt;
155
+ const historyText = history
156
+ .map(msg => `[${msg.role === 'user' ? 'User' : 'Assistant'}]: ${msg.content}`)
157
+ .join('\n\n');
158
+ return `Previous conversation context:\n${historyText}\n\nCurrent request: ${prompt}`;
159
+ }
160
+ async *sendPrompt(_sessionId, prompt, history, opts) {
161
+ const callOpts = opts ?? {};
162
+ const baseUrl = await this.serve.ensureRunning();
163
+ let sessionID = callOpts.agentSessionId;
164
+ if (!sessionID) {
165
+ sessionID = await this.createSession(baseUrl, callOpts);
166
+ if (callOpts.onAgentSessionId) {
167
+ try {
168
+ callOpts.onAgentSessionId(sessionID);
169
+ }
170
+ catch { /* ignore */ }
171
+ }
172
+ // Newly-created sessions already received the ruleset in createSession's
173
+ // POST body — no need to PATCH again. Mark applied so we skip the dup.
174
+ this.rulesetApplied.add(sessionID);
175
+ }
176
+ else if (callOpts.planMode) {
177
+ // Plan mode: opencode's `plan` primary agent already ships with its own
178
+ // stricter ruleset (edit denied except .opencode/plans/*.md). Stacking
179
+ // the medium-gate edit/write/patch=ask rules on top would just create
180
+ // redundant ask prompts during read-only planning. Skip the PATCH and
181
+ // let plan agent's defaults govern this turn. We intentionally do NOT
182
+ // mark rulesetApplied — once the user /plan off's and we resume normal
183
+ // turns, the next non-plan turn should re-apply the medium gate.
184
+ }
185
+ else if (!this.rulesetApplied.has(sessionID)) {
186
+ // Resumed session: opencode loaded its previously-stored ruleset,
187
+ // which may predate the current gate policy (older sessions were
188
+ // created with no override at all). PATCH the gate ruleset onto it
189
+ // so edit/write/patch correctly route through the IM bridge for the
190
+ // rest of this process's lifetime.
191
+ //
192
+ // Claim the slot synchronously so concurrent prompts on the same
193
+ // session don't fire duplicate PATCHes; remove on failure so the
194
+ // next turn retries instead of leaving the session permanently
195
+ // unguarded if this attempt fails transiently.
196
+ const targetSession = sessionID;
197
+ this.rulesetApplied.add(targetSession);
198
+ this.applyRuleset(baseUrl, targetSession).catch((err) => {
199
+ this.rulesetApplied.delete(targetSession);
200
+ log.warn({ event: 'opencode.http.apply_ruleset_failed', err: String(err), sessionID: targetSession });
201
+ });
202
+ }
203
+ log.info({ event: 'opencode.http.send', sessionID, historyLen: history?.length || 0, hasResume: !!callOpts.agentSessionResume }, 'sendPrompt');
204
+ // When resuming, opencode already has the conversation in its DB — feeding
205
+ // history again would duplicate every prior turn. (Same invariant as the
206
+ // stdio driver — see router.callAgentWithHistory zeroing effectiveHistory.)
207
+ const contextualPrompt = callOpts.agentSessionResume
208
+ ? prompt
209
+ : this.buildHttpContextualPrompt(prompt, history);
210
+ // HTTP mode quirk: the imhub MCP server is shared across all opencode
211
+ // sessions because `opencode serve` is one daemon. The MCP server's
212
+ // env was set ONCE at daemon startup, so the runId there is stale for
213
+ // any given turn. Workaround: ask the agent to pass `_im_context` in
214
+ // every imhub-tool call; the bus accepts it as a runId fallback. This
215
+ // is a single-user-only path (agent could lie in multi-tenant) — see
216
+ // approval-bus.ts:handleReminder for the trust note.
217
+ const imCtxInstruction = buildImContextInstruction(callOpts);
218
+ const finalPrompt = imCtxInstruction
219
+ ? `${imCtxInstruction}\n\n${contextualPrompt}`
220
+ : contextualPrompt;
221
+ // Subscribe BEFORE posting so we don't miss early events.
222
+ //
223
+ // SSE-broken fallback (opencode 1.14.45+ with node-http-server runtime):
224
+ // the /event stream only emits the initial server.connected and never
225
+ // broadcasts bus events. The POST /session/:id/message endpoint blocks
226
+ // until the message is complete and returns the full assistant body in
227
+ // JSON, so we use that as the authoritative text source. We still keep
228
+ // the SSE loop for older opencode versions that DO stream — yielded
229
+ // text-part ids are tracked so we don't emit them twice when draining
230
+ // the POST body. Once POST settles we schedule a brief grace then close
231
+ // SSE, which exits the for-await loop on the broken-stream case.
232
+ const sse = await this.openEventStream(baseUrl);
233
+ const promptPromise = this.postPrompt(baseUrl, sessionID, finalPrompt, callOpts)
234
+ .catch((err) => ({ error: err instanceof Error ? err : new Error(String(err)) }));
235
+ // 500ms after POST resolves, force-close SSE so the for-await exits even
236
+ // when the daemon never emits session.idle. Short enough that latency
237
+ // stays low; long enough to flush any final events that did stream.
238
+ promptPromise.finally(() => {
239
+ const t = setTimeout(() => { sse.close(); }, 500);
240
+ t.unref?.();
241
+ });
242
+ const timeoutMs = resolveTimeout();
243
+ const timeoutAt = Date.now() + timeoutMs;
244
+ let surfacedError = null;
245
+ const yieldedPartIds = new Set();
246
+ let sseYieldedAnyText = false;
247
+ try {
248
+ for await (const event of sse) {
249
+ if (Date.now() > timeoutAt) {
250
+ log.warn({ event: 'opencode.http.timeout', sessionID, timeoutMs }, 'http adapter timed out');
251
+ yield `\n\n⚠️ 处理超时(已超过 ${Math.round(timeoutMs / 60000)} 分钟)`;
252
+ return;
253
+ }
254
+ if (event.type === 'permission.asked') {
255
+ this.routeAsk(event, baseUrl, sessionID, callOpts);
256
+ continue;
257
+ }
258
+ const errEvent = this.isErrorEvent(event, sessionID);
259
+ if (errEvent) {
260
+ surfacedError = errEvent.error;
261
+ continue;
262
+ }
263
+ const { text, partId } = this.inspectHttpEvent(event, sessionID, callOpts);
264
+ if (text) {
265
+ if (partId)
266
+ yieldedPartIds.add(partId);
267
+ sseYieldedAnyText = true;
268
+ yield text;
269
+ }
270
+ if (this.isIdleEvent(event, sessionID)) {
271
+ break;
272
+ }
273
+ }
274
+ }
275
+ finally {
276
+ sse.close();
277
+ }
278
+ const result = await promptPromise;
279
+ if (result && typeof result === 'object' && 'error' in result) {
280
+ log.warn({ event: 'opencode.http.prompt_failed', err: String(result.error) }, 'prompt POST failed');
281
+ yield `opencode failed: ${result.error.message}`;
282
+ return;
283
+ }
284
+ // SSE-broken fallback: drain text + usage from the POST response body.
285
+ // No-op when SSE already streamed everything (yieldedPartIds covers it).
286
+ if (result && typeof result === 'object' && !('error' in result)) {
287
+ const drained = this.drainResponseBody(result, yieldedPartIds, callOpts);
288
+ if (drained.length > 0) {
289
+ if (!sseYieldedAnyText) {
290
+ log.info({ event: 'opencode.http.drained_post_body', sessionID, parts: drained.length }, 'SSE yielded no text; recovered assistant message from POST body');
291
+ }
292
+ for (const text of drained)
293
+ yield text;
294
+ }
295
+ }
296
+ if (surfacedError) {
297
+ yield `opencode session error: ${surfacedError}`;
298
+ }
299
+ }
300
+ /**
301
+ * Extract assistant text + usage from the POST /session/:id/message response
302
+ * body, skipping any text part already emitted via SSE (matched by part id).
303
+ * Used as a fallback for opencode builds whose /event stream is broken.
304
+ */
305
+ drainResponseBody(body, alreadyYielded, opts) {
306
+ const parts = Array.isArray(body.parts) ? body.parts : [];
307
+ if (opts.onUsage && body.info) {
308
+ const delta = {};
309
+ if (typeof body.info.cost === 'number' && Number.isFinite(body.info.cost)) {
310
+ delta.costUsd = body.info.cost;
311
+ }
312
+ if (typeof body.info.tokens?.input === 'number')
313
+ delta.tokensInput = body.info.tokens.input;
314
+ if (typeof body.info.tokens?.output === 'number')
315
+ delta.tokensOutput = body.info.tokens.output;
316
+ if (delta.costUsd !== undefined || delta.tokensInput !== undefined || delta.tokensOutput !== undefined) {
317
+ try {
318
+ opts.onUsage(delta);
319
+ }
320
+ catch { /* ignore */ }
321
+ }
322
+ }
323
+ const texts = [];
324
+ for (const part of parts) {
325
+ if (part.type !== 'text')
326
+ continue;
327
+ if (typeof part.text !== 'string' || part.text.length === 0)
328
+ continue;
329
+ if (!part.time?.end)
330
+ continue;
331
+ if (part.id && alreadyYielded.has(part.id))
332
+ continue;
333
+ texts.push(part.text);
334
+ }
335
+ return texts;
336
+ }
337
+ // ─── permission bridge ────────────────────────────────────────────────
338
+ /**
339
+ * Decide how to handle a `permission.asked` SSE event:
340
+ * - If `this.approvalBus` is set AND has a notifier installed AND the
341
+ * call carries enough IM context (threadId + platform) → register a
342
+ * synthetic pending so the user gets an IM card; the bus's resolve
343
+ * callback POSTs the decision back to opencode via /permission/.../reply.
344
+ * - Otherwise → fall back to `once`. Without an IM channel there's no
345
+ * human to ask, and a plain `reject` would surface as a tool-call
346
+ * failure mid-conversation which is worse UX than allowing.
347
+ *
348
+ * Fire-and-forget: the SSE loop must keep draining other events while the
349
+ * bridge waits for the user. The bus enforces its own timeout (5 min by
350
+ * default) so we never deadlock the SSE loop.
351
+ */
352
+ routeAsk(event, baseUrl, ocSessionID, opts) {
353
+ const props = event.properties ?? {};
354
+ const reqId = props.id;
355
+ if (typeof reqId !== 'string')
356
+ return;
357
+ const fallback = () => {
358
+ this.replyPermission(baseUrl, reqId, 'once').catch((err) => {
359
+ log.warn({ event: 'opencode.http.ask_fallback_failed', err: String(err), reqId });
360
+ });
361
+ };
362
+ const bus = this.approvalBus;
363
+ const canBridge = bus?.hasNotifier()
364
+ && typeof opts.threadId === 'string' && opts.threadId.length > 0
365
+ && typeof opts.platform === 'string' && opts.platform.length > 0;
366
+ if (!canBridge) {
367
+ fallback();
368
+ return;
369
+ }
370
+ const ctx = {
371
+ threadId: opts.threadId,
372
+ platform: opts.platform,
373
+ userId: opts.userId ?? '',
374
+ channelId: opts.channelId ?? '',
375
+ };
376
+ const dispatch = (decision) => {
377
+ const wire = decisionToOpencodeReply(decision);
378
+ this.replyPermission(baseUrl, reqId, wire.reply, wire.message).catch((err) => {
379
+ log.warn({ event: 'opencode.http.ask_reply_failed', err: String(err), reqId });
380
+ });
381
+ };
382
+ bus?.registerSyntheticPending({
383
+ runId: ocSessionID, // opencode session id IS the run id from the bus's POV
384
+ reqId,
385
+ toolName: typeof props.permission === 'string' ? props.permission : 'permission',
386
+ input: {
387
+ ...(Array.isArray(props.patterns) ? { patterns: props.patterns } : {}),
388
+ ...(props.metadata && typeof props.metadata === 'object' ? props.metadata : {}),
389
+ },
390
+ ctx,
391
+ dispatch,
392
+ }).catch((err) => {
393
+ // Bus rejected (e.g. notifier removed mid-flight). Safety: fall back.
394
+ log.warn({ event: 'opencode.http.bridge_register_failed', err: String(err), reqId });
395
+ fallback();
396
+ });
397
+ }
398
+ /**
399
+ * Build the session-level permission ruleset to append to opencode's
400
+ * agent defaults. See createSession's docstring for policy rationale.
401
+ *
402
+ * The returned rules are flat `{permission, pattern, action}` objects
403
+ * matching opencode's `Permission.Rule` schema.
404
+ */
405
+ buildSessionRuleset() {
406
+ const gate = (process.env.IMHUB_OPENCODE_GATE || 'medium').toLowerCase();
407
+ const ask = (permission) => ({ permission, pattern: '*', action: 'ask' });
408
+ if (gate === 'none')
409
+ return [];
410
+ if (gate === 'strict')
411
+ return [ask('edit'), ask('write'), ask('patch'), ask('bash')];
412
+ if (gate === 'loose')
413
+ return [ask('edit'), ask('write')];
414
+ return [ask('edit'), ask('write'), ask('patch')]; // medium (default)
415
+ }
416
+ /**
417
+ * PATCH a resumed session with our gate ruleset so subsequent tool calls
418
+ * surface as `permission.asked` events for the bridge. opencode's update
419
+ * endpoint merges (current + payload) — payload comes last and wins under
420
+ * findLast semantics. Idempotent across im-hub restarts but NOT within a
421
+ * single process: the caller (sendPrompt) gates on rulesetApplied.
422
+ */
423
+ async applyRuleset(baseUrl, sessionID) {
424
+ const ruleset = this.buildSessionRuleset();
425
+ if (ruleset.length === 0)
426
+ return; // gate=none
427
+ const res = await this.fetchImpl(`${baseUrl}/session/${sessionID}`, {
428
+ method: 'PATCH',
429
+ headers: { 'Content-Type': 'application/json' },
430
+ body: JSON.stringify({ permission: ruleset }),
431
+ });
432
+ if (!res.ok) {
433
+ throw new Error(`opencode session update failed: HTTP ${res.status}`);
434
+ }
435
+ await res.text().catch(() => '');
436
+ log.info({ event: 'opencode.http.ruleset_applied', sessionID, gate: process.env.IMHUB_OPENCODE_GATE || 'medium' }, 'applied gate ruleset to resumed session');
437
+ }
438
+ // ─── HTTP plumbing ──────────────────────────────────────────────────────
439
+ async createSession(baseUrl, opts) {
440
+ // Session-level permission overrides for IM-launched runs.
441
+ //
442
+ // Why we override at all:
443
+ // opencode's built-in `build` agent ships with a `*: allow` catch-all
444
+ // (agent.ts:86-103 in the upstream repo). With findLast semantics,
445
+ // that swallows mutating tools like edit/write/patch unless we
446
+ // *append* stricter rules whose specificity wins.
447
+ //
448
+ // The "medium" policy (default):
449
+ // - edit / write / patch → ask: any time the assistant wants to
450
+ // mutate files, surface an IM card. These are the operations a
451
+ // user wants to "拍板".
452
+ // - bash is intentionally NOT gated: IM-driven exploration involves
453
+ // a flood of `ls` / `cat` / `git status` and asking on every one
454
+ // would drown the user. Mutations the LLM does via bash (rm,
455
+ // npm install, git commit) aren't caught here — that's a known
456
+ // gap; revisit if it bites in practice.
457
+ // - external_directory is already `ask` by default for paths
458
+ // outside the cwd / skill whitelist; we don't need to repeat it
459
+ // and it would be surprising to override.
460
+ //
461
+ // Override path: env IMHUB_OPENCODE_GATE=strict|loose|none flips this
462
+ // policy without redeploy:
463
+ // strict → bash also asks
464
+ // loose → only write+edit (drop patch)
465
+ // none → no override (mostly for debugging)
466
+ const body = {
467
+ title: 'im-hub session',
468
+ };
469
+ // Plan mode: route through opencode's built-in `plan` agent and skip our
470
+ // medium-gate ruleset entirely (plan agent's edit-deny-except-plans
471
+ // policy is already stricter than the IM gate would impose).
472
+ if (opts.planMode) {
473
+ body.agent = 'plan';
474
+ }
475
+ else {
476
+ body.permission = this.buildSessionRuleset();
477
+ }
478
+ const res = await this.fetchImpl(`${baseUrl}/session`, {
479
+ method: 'POST',
480
+ headers: { 'Content-Type': 'application/json' },
481
+ body: JSON.stringify(body),
482
+ });
483
+ if (!res.ok) {
484
+ throw new Error(`opencode session create failed: HTTP ${res.status} ${await safeText(res)}`);
485
+ }
486
+ const json = await res.json();
487
+ if (!json.id)
488
+ throw new Error('opencode session create returned no id');
489
+ log.info({
490
+ event: 'opencode.http.session_created',
491
+ sessionID: json.id,
492
+ cwd: resolveAgentCwd('opencode', opts),
493
+ }, 'session created');
494
+ return json.id;
495
+ }
496
+ async postPrompt(baseUrl, sessionID, prompt, opts) {
497
+ const body = {
498
+ parts: [{ type: 'text', text: prompt }],
499
+ };
500
+ if (opts.model)
501
+ body.model = opts.model;
502
+ if (opts.variant)
503
+ body.variant = opts.variant;
504
+ // planMode forces the plan agent on every turn, including resumed sessions
505
+ // that were originally created under build. opencode's per-message `agent`
506
+ // field overrides the session's default agent for this single turn.
507
+ if (opts.planMode)
508
+ body.agent = 'plan';
509
+ const res = await this.fetchImpl(`${baseUrl}/session/${sessionID}/message`, {
510
+ method: 'POST',
511
+ headers: { 'Content-Type': 'application/json' },
512
+ body: JSON.stringify(body),
513
+ });
514
+ if (!res.ok) {
515
+ throw new Error(`opencode prompt failed: HTTP ${res.status} ${await safeText(res)}`);
516
+ }
517
+ // On opencode 1.14.45+ the POST blocks until the assistant message is
518
+ // complete and returns the full body. We parse it (best-effort) so the
519
+ // caller can recover text + usage when /event SSE doesn't broadcast.
520
+ try {
521
+ return await res.json();
522
+ }
523
+ catch {
524
+ return {};
525
+ }
526
+ }
527
+ async replyPermission(baseUrl, requestId, reply, message) {
528
+ const body = { reply };
529
+ if (reply === 'reject' && message)
530
+ body.message = message;
531
+ const res = await this.fetchImpl(`${baseUrl}/permission/${requestId}/reply`, {
532
+ method: 'POST',
533
+ headers: { 'Content-Type': 'application/json' },
534
+ body: JSON.stringify(body),
535
+ });
536
+ if (!res.ok) {
537
+ throw new Error(`opencode permission reply HTTP ${res.status}`);
538
+ }
539
+ }
540
+ async openEventStream(baseUrl) {
541
+ const res = await this.fetchImpl(`${baseUrl}/event`, {
542
+ headers: { Accept: 'text/event-stream' },
543
+ });
544
+ if (!res.ok || !res.body) {
545
+ throw new Error(`opencode event stream failed: HTTP ${res.status}`);
546
+ }
547
+ return new EventStream(res.body);
548
+ }
549
+ }
550
+ /**
551
+ * Map a bus Decision to opencode's permission.reply schema.
552
+ *
553
+ * Mapping rules:
554
+ * - allow → once (one-shot grant; matches what TUI sends)
555
+ * - allow + autoAllowFurther → once on the wire; the bus has already
556
+ * registered an im-side auto-allow rule by side effect (see
557
+ * ApprovalBus.cancelPending). We deliberately do NOT promote to
558
+ * opencode's `always` because that would persist a ruleset addition on
559
+ * the opencode session, which is heavier scope than the IM rule.
560
+ * - deny + message → reject (+ message). opencode surfaces this as a
561
+ * CorrectedError ("user rejected … with feedback"), giving the LLM a
562
+ * useful hint instead of a bare reject.
563
+ * - deny → reject
564
+ */
565
+ /**
566
+ * Compose the system instruction telling the agent how to identify the
567
+ * IM thread when it calls imhub MCP tools. Empty string when we don't
568
+ * have full IM context (web/scheduler/intent-llm calls) — those won't
569
+ * have reminder tools wired anyway.
570
+ *
571
+ * Single-user trust model: this prompt asks the agent to assert the
572
+ * caller's identity. A malicious or confused agent could pass another
573
+ * user's IDs. Acceptable for personal deployments; multi-tenant operators
574
+ * should use claude-code (per-spawn env via --mcp-config) or opencode
575
+ * stdio (per-spawn extraEnv) instead.
576
+ */
577
+ function buildImContextInstruction(opts) {
578
+ const platform = opts.platform;
579
+ const threadId = opts.threadId;
580
+ const channelId = opts.channelId;
581
+ if (!platform || !threadId || !channelId)
582
+ return '';
583
+ const userId = opts.userId ?? '';
584
+ const ctx = JSON.stringify({ platform, threadId, channelId, userId });
585
+ return [
586
+ '[im-hub MCP routing — important]',
587
+ 'When you call any imhub tool — reminder family (create_reminder /',
588
+ 'list_reminders / cancel_reminder / snooze_reminder) or memo family',
589
+ '(save_memo / search_memos / update_memo / delete_memo /',
590
+ 'request_location_capture) — you MUST include this exact `_im_context`',
591
+ 'field in the tool input so the call routes to the correct IM thread:',
592
+ '',
593
+ ` "_im_context": ${ctx}`,
594
+ '',
595
+ 'Pass these values verbatim — do not invent or modify them.',
596
+ '',
597
+ '[memo timezone] All times in memo tools are Asia/Shanghai (UTC+8).',
598
+ 'When the user says relative dates ("明天" / "下周三" / "下午3点"),',
599
+ 'compute the absolute datetime in UTC+8 and emit "YYYY-MM-DD HH:MM:SS"',
600
+ '(no T, no offset suffix). The server normalizes if you slip an offset',
601
+ 'in, but emit local directly to keep the chat consistent.',
602
+ ].join('\n');
603
+ }
604
+ function decisionToOpencodeReply(decision) {
605
+ if (decision.behavior === 'allow')
606
+ return { reply: 'once' };
607
+ return decision.message
608
+ ? { reply: 'reject', message: decision.message }
609
+ : { reply: 'reject' };
610
+ }
611
+ function resolveTimeout() {
612
+ const raw = process.env.OPENCODE_TIMEOUT_MS;
613
+ if (raw) {
614
+ const n = parseInt(raw, 10);
615
+ if (Number.isFinite(n) && n > 0)
616
+ return n;
617
+ }
618
+ return DEFAULT_TIMEOUT_MS;
619
+ }
620
+ async function safeText(res) {
621
+ try {
622
+ return (await res.text()).slice(0, 500);
623
+ }
624
+ catch {
625
+ return '';
626
+ }
627
+ }
628
+ export class EventStream {
629
+ reader;
630
+ decoder = new TextDecoder('utf-8');
631
+ buf = '';
632
+ closed = false;
633
+ constructor(body) {
634
+ this.reader = body.getReader();
635
+ }
636
+ close() {
637
+ if (this.closed)
638
+ return;
639
+ this.closed = true;
640
+ try {
641
+ void this.reader.cancel();
642
+ }
643
+ catch { /* ignore */ }
644
+ }
645
+ async *[Symbol.asyncIterator]() {
646
+ while (!this.closed) {
647
+ const { value, done } = await this.reader.read();
648
+ if (done)
649
+ return;
650
+ if (!value)
651
+ continue;
652
+ this.buf += this.decoder.decode(value, { stream: true });
653
+ while (true) {
654
+ const idx = this.buf.indexOf('\n\n');
655
+ if (idx === -1)
656
+ break;
657
+ const frame = this.buf.slice(0, idx);
658
+ this.buf = this.buf.slice(idx + 2);
659
+ const evt = parseSseFrame(frame);
660
+ if (evt)
661
+ yield evt;
662
+ }
663
+ }
664
+ }
665
+ }
666
+ function parseSseFrame(frame) {
667
+ let payload = '';
668
+ for (const line of frame.split('\n')) {
669
+ if (line.startsWith('data:')) {
670
+ payload += line.slice(5).trimStart();
671
+ }
672
+ }
673
+ if (!payload)
674
+ return null;
675
+ try {
676
+ return JSON.parse(payload);
677
+ }
678
+ catch {
679
+ return null;
680
+ }
681
+ }
682
+ //# sourceMappingURL=opencode-http-adapter.js.map