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,316 @@
1
+ // Scheduler — persistent cron-driven Job creation.
2
+ //
3
+ // Schedules live in the same SQLite DB as Jobs (extra table). A 30s tick
4
+ // scans for due rows, creates a Job for each, and bumps `next_run`. When
5
+ // SQLite is unavailable (bun runtime / disk error) the scheduler exposes
6
+ // the same API but persists nothing — exact same fail-soft pattern as
7
+ // audit-log + job-board.
8
+ import { homedir } from 'os';
9
+ import { join } from 'path';
10
+ import { logger as rootLogger } from './logger.js';
11
+ import { parseCron, nextOccurrence } from './cron.js';
12
+ import { createJob, runJob } from './job-board.js';
13
+ import { registry } from './registry.js';
14
+ import { AgentBase } from './agent-base.js';
15
+ import { createSqliteHelper } from './sqlite-helper.js';
16
+ const SCHED_DB = join(homedir(), '.im-hub', 'schedules.db');
17
+ const TICK_INTERVAL_MS = 30 * 1000;
18
+ const NOTIFY_TIMEOUT_MS = 10 * 1000;
19
+ /** Hard cap on webhook response body — we only read a small ack. */
20
+ const NOTIFY_MAX_RESPONSE_BYTES = 64 * 1024;
21
+ const log = rootLogger.child({ component: 'schedule' });
22
+ let tickTimer = null;
23
+ /**
24
+ * Validate a user-supplied webhook URL before the scheduler POSTs to it.
25
+ * Defends against accidental misconfig and basic SSRF: no protocols other
26
+ * than http(s); no loopback / RFC1918 / link-local destinations unless the
27
+ * operator explicitly opts in via IMHUB_ALLOW_PRIVATE_WEBHOOKS=1.
28
+ *
29
+ * Note: this is a lexical check (IP literals + "localhost"). DNS-rebinding
30
+ * mitigation requires an at-fetch-time guard; tracked separately.
31
+ */
32
+ export function validateWebhookUrl(raw) {
33
+ let u;
34
+ try {
35
+ u = new URL(raw);
36
+ }
37
+ catch {
38
+ return { ok: false, reason: 'malformed URL' };
39
+ }
40
+ if (u.protocol !== 'http:' && u.protocol !== 'https:') {
41
+ return { ok: false, reason: `protocol ${u.protocol} not allowed (use http: or https:)` };
42
+ }
43
+ if (process.env.IMHUB_ALLOW_PRIVATE_WEBHOOKS === '1')
44
+ return { ok: true, url: u };
45
+ const host = u.hostname.toLowerCase();
46
+ if (host === 'localhost' || host === '0.0.0.0' || host === '::' || host === '[::]') {
47
+ return { ok: false, reason: `host ${host} not allowed` };
48
+ }
49
+ // IPv4 literals
50
+ const v4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
51
+ if (v4) {
52
+ const o = v4.slice(1, 5).map(Number);
53
+ if (o.some((b) => !Number.isFinite(b) || b < 0 || b > 255)) {
54
+ return { ok: false, reason: 'malformed IPv4 literal' };
55
+ }
56
+ const [a, b] = o;
57
+ // 127/8 loopback, 10/8, 192.168/16, 172.16-31/12, 169.254/16 link-local,
58
+ // 0/8 "this network", 100.64/10 CGNAT.
59
+ if (a === 127 || a === 10 || a === 0
60
+ || (a === 192 && b === 168)
61
+ || (a === 172 && b >= 16 && b <= 31)
62
+ || (a === 169 && b === 254)
63
+ || (a === 100 && b >= 64 && b <= 127)) {
64
+ return { ok: false, reason: `private/loopback IPv4 ${host} not allowed` };
65
+ }
66
+ }
67
+ // IPv6 literals come wrapped in [] in URL.hostname strips them; bracket
68
+ // forms still surface in u.host though. Block well-known unsafe prefixes.
69
+ if (host === '::1' || host.startsWith('fc') || host.startsWith('fd') || host.startsWith('fe80')) {
70
+ return { ok: false, reason: `private/loopback IPv6 ${host} not allowed` };
71
+ }
72
+ return { ok: true, url: u };
73
+ }
74
+ /**
75
+ * Idempotent column-add for ownership tracking. Same shape as job-board's
76
+ * migrateOwnership — survive multiple boots without breaking.
77
+ */
78
+ function migrateOwnership(d) {
79
+ for (const col of ['creator_id', 'workspace_id']) {
80
+ try {
81
+ d.prepare(`ALTER TABLE schedules ADD COLUMN ${col} TEXT NOT NULL DEFAULT ''`).run();
82
+ rootLogger.info({ component: 'schedule', column: col, event: 'schedule.migration' }, `Added ${col} column to schedules table`);
83
+ }
84
+ catch {
85
+ // duplicate column — already migrated
86
+ }
87
+ }
88
+ }
89
+ const helper = createSqliteHelper({
90
+ file: SCHED_DB,
91
+ component: 'schedule',
92
+ logger: rootLogger,
93
+ schema: `
94
+ CREATE TABLE IF NOT EXISTS schedules (
95
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
96
+ name TEXT NOT NULL,
97
+ agent TEXT NOT NULL,
98
+ prompt TEXT NOT NULL,
99
+ cron TEXT NOT NULL,
100
+ enabled INTEGER NOT NULL DEFAULT 1,
101
+ next_run TEXT NOT NULL,
102
+ last_run TEXT,
103
+ notify_url TEXT,
104
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
105
+ creator_id TEXT NOT NULL DEFAULT '',
106
+ workspace_id TEXT NOT NULL DEFAULT ''
107
+ );
108
+ CREATE INDEX IF NOT EXISTS idx_sched_next ON schedules(next_run, enabled);
109
+ CREATE INDEX IF NOT EXISTS idx_sched_creator ON schedules(creator_id);
110
+ `,
111
+ init: (d) => migrateOwnership(d),
112
+ });
113
+ function getDb() {
114
+ return helper.get();
115
+ }
116
+ export function createSchedule(input) {
117
+ const d = getDb();
118
+ if (!d)
119
+ throw new Error('Scheduler unavailable (SQLite not initialized)');
120
+ const spec = parseCron(input.cron); // throws on bad expr
121
+ const next = nextOccurrence(spec, new Date());
122
+ if (!next)
123
+ throw new Error('Cron expression has no future occurrence');
124
+ if (input.notifyUrl) {
125
+ const v = validateWebhookUrl(input.notifyUrl);
126
+ if (!v.ok)
127
+ throw new Error(`notifyUrl rejected: ${v.reason}`);
128
+ }
129
+ const info = d.prepare('INSERT INTO schedules (name, agent, prompt, cron, enabled, next_run, notify_url, creator_id, workspace_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)').run(input.name, input.agent, input.prompt, input.cron, input.enabled === false ? 0 : 1, next.toISOString(), input.notifyUrl || null, input.creatorId || '', input.workspaceId || '');
130
+ return Number(info.lastInsertRowid);
131
+ }
132
+ export function listSchedules(limit = 50, opts = {}) {
133
+ const d = getDb();
134
+ if (!d)
135
+ return [];
136
+ const conditions = [];
137
+ const params = [];
138
+ if (opts.creatorId !== undefined) {
139
+ conditions.push("(creator_id = ? OR creator_id = '')");
140
+ params.push(opts.creatorId);
141
+ }
142
+ if (opts.agent) {
143
+ conditions.push('agent = ?');
144
+ params.push(opts.agent);
145
+ }
146
+ const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
147
+ return d.prepare(`SELECT * FROM schedules ${where} ORDER BY id DESC LIMIT ?`)
148
+ .all(...params, limit);
149
+ }
150
+ export function getSchedule(id, opts = {}) {
151
+ const d = getDb();
152
+ if (!d)
153
+ return null;
154
+ const row = (opts.creatorId !== undefined
155
+ ? d.prepare("SELECT * FROM schedules WHERE id = ? AND (creator_id = ? OR creator_id = '')")
156
+ .get(id, opts.creatorId)
157
+ : d.prepare('SELECT * FROM schedules WHERE id = ?').get(id));
158
+ return row || null;
159
+ }
160
+ export function deleteSchedule(id, opts = {}) {
161
+ const d = getDb();
162
+ if (!d)
163
+ return false;
164
+ if (opts.creatorId !== undefined) {
165
+ return d.prepare("DELETE FROM schedules WHERE id = ? AND (creator_id = ? OR creator_id = '')")
166
+ .run(id, opts.creatorId).changes > 0;
167
+ }
168
+ return d.prepare('DELETE FROM schedules WHERE id = ?').run(id).changes > 0;
169
+ }
170
+ export function setEnabled(id, enabled, opts = {}) {
171
+ const d = getDb();
172
+ if (!d)
173
+ return false;
174
+ if (opts.creatorId !== undefined) {
175
+ return d.prepare("UPDATE schedules SET enabled = ? WHERE id = ? AND (creator_id = ? OR creator_id = '')").run(enabled ? 1 : 0, id, opts.creatorId).changes > 0;
176
+ }
177
+ return d.prepare('UPDATE schedules SET enabled = ? WHERE id = ?')
178
+ .run(enabled ? 1 : 0, id).changes > 0;
179
+ }
180
+ /** Find schedules whose next_run is at or before `now` and are enabled. */
181
+ function dueSchedules(d, now) {
182
+ return d.prepare("SELECT * FROM schedules WHERE enabled = 1 AND next_run <= ?").all(now.toISOString());
183
+ }
184
+ /**
185
+ * Execute a single scheduled run: create a Job, kick it off through Job
186
+ * Board (which honors the concurrency cap), bump next_run.
187
+ */
188
+ async function fireSchedule(s, logger) {
189
+ const d = getDb();
190
+ if (!d)
191
+ return;
192
+ const agent = registry.findAgent(s.agent);
193
+ if (!agent) {
194
+ logger.warn({ scheduleId: s.id, agent: s.agent }, 'Schedule agent not registered, skipping');
195
+ return;
196
+ }
197
+ const jobId = createJob(s.agent, s.prompt);
198
+ logger.info({ scheduleId: s.id, jobId, name: s.name }, 'Schedule fired');
199
+ // Compute next_run BEFORE running so even a long-running job doesn't
200
+ // block the next tick.
201
+ try {
202
+ const nextSpec = parseCron(s.cron);
203
+ const next = nextOccurrence(nextSpec, new Date());
204
+ if (next) {
205
+ d.prepare('UPDATE schedules SET last_run = ?, next_run = ? WHERE id = ?')
206
+ .run(new Date().toISOString(), next.toISOString(), s.id);
207
+ }
208
+ }
209
+ catch {
210
+ // Bad cron at this point would only happen if it was edited externally.
211
+ // Disable rather than re-fire forever.
212
+ d.prepare('UPDATE schedules SET enabled = 0 WHERE id = ?').run(s.id);
213
+ }
214
+ // Run via Job Board so the result is persisted + bounded concurrency.
215
+ void runJob(jobId, async function* (job, jobLogger, signal) {
216
+ if (agent instanceof AgentBase) {
217
+ const text = await agent.spawnAndCollect(job.prompt, signal);
218
+ if (text)
219
+ yield text;
220
+ }
221
+ else {
222
+ for await (const chunk of agent.sendPrompt(`schedule-${s.id}-${jobId}`, job.prompt, [])) {
223
+ if (signal.aborted)
224
+ break;
225
+ yield chunk;
226
+ }
227
+ }
228
+ }, logger).then(async () => {
229
+ if (!s.notify_url)
230
+ return;
231
+ // Defense in depth: re-validate at fire time in case the row was edited
232
+ // out-of-band (manual SQL, future v0.x importer) — never trust DB content
233
+ // as an authorization bypass for the SSRF gate.
234
+ const v = validateWebhookUrl(s.notify_url);
235
+ if (!v.ok) {
236
+ logger.warn({ scheduleId: s.id, reason: v.reason, event: 'schedule.notify.rejected' }, 'Schedule notify webhook rejected by URL validator');
237
+ return;
238
+ }
239
+ try {
240
+ const job = (await import('./job-board.js')).getJob(jobId);
241
+ if (!job)
242
+ return;
243
+ const ctrl = new AbortController();
244
+ const timer = setTimeout(() => ctrl.abort(), NOTIFY_TIMEOUT_MS);
245
+ try {
246
+ const resp = await fetch(v.url.toString(), {
247
+ method: 'POST',
248
+ headers: { 'content-type': 'application/json' },
249
+ body: JSON.stringify({
250
+ scheduleId: s.id,
251
+ jobId,
252
+ status: job.status,
253
+ result: job.result,
254
+ error: job.error,
255
+ }),
256
+ signal: ctrl.signal,
257
+ // Drop redirects so a 30x can't pivot us back to a private host.
258
+ redirect: 'manual',
259
+ });
260
+ // Drain (capped) so the connection can be reused / closed cleanly.
261
+ if (resp.body) {
262
+ let read = 0;
263
+ const reader = resp.body.getReader();
264
+ while (read < NOTIFY_MAX_RESPONSE_BYTES) {
265
+ const { done, value } = await reader.read();
266
+ if (done)
267
+ break;
268
+ read += value?.byteLength ?? 0;
269
+ }
270
+ await reader.cancel().catch(() => { });
271
+ }
272
+ }
273
+ finally {
274
+ clearTimeout(timer);
275
+ }
276
+ }
277
+ catch (err) {
278
+ logger.warn({ scheduleId: s.id, err: err instanceof Error ? err.message : String(err) }, 'Schedule notify webhook failed');
279
+ }
280
+ }).catch(() => { });
281
+ }
282
+ /** Run one tick — exposed for tests so they don't have to wait for setInterval. */
283
+ export async function tick(now = new Date()) {
284
+ const d = getDb();
285
+ if (!d)
286
+ return 0;
287
+ const due = dueSchedules(d, now);
288
+ for (const s of due) {
289
+ await fireSchedule(s, log).catch((err) => {
290
+ log.error({ scheduleId: s.id, err: err instanceof Error ? err.message : String(err) }, 'fireSchedule threw');
291
+ });
292
+ }
293
+ return due.length;
294
+ }
295
+ /** Start the periodic ticker. Idempotent. */
296
+ export function startScheduler() {
297
+ if (tickTimer)
298
+ return;
299
+ tickTimer = setInterval(() => { void tick(); }, TICK_INTERVAL_MS);
300
+ if (typeof tickTimer === 'object' && tickTimer && 'unref' in tickTimer) {
301
+ tickTimer.unref();
302
+ }
303
+ log.info({ tickMs: TICK_INTERVAL_MS }, 'Scheduler started');
304
+ }
305
+ export function stopScheduler() {
306
+ if (tickTimer) {
307
+ clearInterval(tickTimer);
308
+ tickTimer = null;
309
+ }
310
+ }
311
+ /** Close the underlying SQLite handle. Called by cli.ts on graceful
312
+ * shutdown so the WAL is checkpointed before exit (L2). */
313
+ export function closeScheduleDb() {
314
+ helper.close();
315
+ }
316
+ //# sourceMappingURL=schedule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schedule.js","sourceRoot":"","sources":["../../src/core/schedule.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=session-subtasks.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-subtasks.test.d.ts","sourceRoot":"","sources":["../../src/core/session-subtasks.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,88 @@
1
+ // listAllSubtasks — covers the dashboard's flattened-subtask view. We can't
2
+ // monkey-patch SESSIONS_DIR (it's a const), so this test goes via the real
3
+ // public API: nextSubtaskId + updateSubtask write to ~/.im-hub/sessions, and
4
+ // we then read them back and clean up.
5
+ //
6
+ // We use a unique platform/channelId/threadId triple so we don't collide with
7
+ // any real sessions and we delete them afterwards.
8
+ import { describe, it, expect, beforeAll, afterEach } from 'bun:test';
9
+ import { homedir } from 'os';
10
+ import { join } from 'path';
11
+ import { mkdir, unlink, readdir } from 'fs/promises';
12
+ import { sessionManager } from './session.js';
13
+ const PLATFORM = 'subtest';
14
+ const CHANNEL = 'c-' + Math.random().toString(36).slice(2, 8);
15
+ const THREAD = 't-' + Math.random().toString(36).slice(2, 8);
16
+ const SESSIONS_DIR = join(homedir(), '.im-hub', 'sessions');
17
+ // CI runs on a fresh runner where ~/.im-hub/sessions/ doesn't exist yet.
18
+ // Without this beforeAll, saveSessionMeta's atomicWrite hits ENOENT, the
19
+ // outer try/catch swallows it (correct for runtime — never crash the
20
+ // session manager on a disk hiccup) and the test sees 0 subtasks instead
21
+ // of 2. Local boxes happened to have the dir from prior runs, masking
22
+ // the issue until a clean CI environment surfaced it.
23
+ beforeAll(async () => {
24
+ await mkdir(SESSIONS_DIR, { recursive: true });
25
+ });
26
+ async function cleanupTestSessions() {
27
+ // The session manager sanitizes the key, so we can't compute the filename.
28
+ // Instead, scan and delete any file whose JSON has our test platform.
29
+ let names;
30
+ try {
31
+ names = await readdir(SESSIONS_DIR);
32
+ }
33
+ catch {
34
+ return;
35
+ }
36
+ const { readFile } = await import('fs/promises');
37
+ for (const name of names) {
38
+ if (!name.endsWith('.json'))
39
+ continue;
40
+ try {
41
+ const raw = await readFile(join(SESSIONS_DIR, name), 'utf-8');
42
+ const parsed = JSON.parse(raw);
43
+ if (parsed.platform === PLATFORM) {
44
+ await unlink(join(SESSIONS_DIR, name)).catch(() => { });
45
+ await unlink(join(SESSIONS_DIR, name.replace(/\.json$/, '.log'))).catch(() => { });
46
+ }
47
+ }
48
+ catch { /* ignore */ }
49
+ }
50
+ }
51
+ afterEach(async () => {
52
+ await cleanupTestSessions();
53
+ });
54
+ describe('sessionManager.listAllSubtasks', () => {
55
+ it('returns subtasks across all sessions, flattened with parent context', async () => {
56
+ // Spawn two subtasks in our test session.
57
+ const id1 = await sessionManager.nextSubtaskId(PLATFORM, CHANNEL, THREAD, 'claude-code');
58
+ await sessionManager.updateSubtask(PLATFORM, CHANNEL, THREAD, id1, {
59
+ id: id1, agent: 'claude-code', prompt: 'hello',
60
+ status: 'running', createdAt: new Date('2026-05-01T09:00:00Z'),
61
+ });
62
+ const id2 = await sessionManager.nextSubtaskId(PLATFORM, CHANNEL, THREAD, 'claude-code');
63
+ await sessionManager.updateSubtask(PLATFORM, CHANNEL, THREAD, id2, {
64
+ id: id2, agent: 'opencode', prompt: 'world',
65
+ status: 'completed', createdAt: new Date('2026-05-01T10:00:00Z'),
66
+ });
67
+ const all = await sessionManager.listAllSubtasks();
68
+ const ours = all.filter((s) => s.platform === PLATFORM);
69
+ expect(ours.length).toBe(2);
70
+ // newest first
71
+ expect(ours[0].createdAt.getTime()).toBeGreaterThan(ours[1].createdAt.getTime());
72
+ // parent context attached
73
+ for (const s of ours) {
74
+ expect(s.platform).toBe(PLATFORM);
75
+ expect(s.channelId).toBe(CHANNEL);
76
+ expect(s.threadId).toBe(THREAD);
77
+ expect(s.parentSessionId).toContain(PLATFORM);
78
+ }
79
+ expect(ours.find((s) => s.agent === 'opencode')?.status).toBe('completed');
80
+ });
81
+ it('returns empty array when no sessions on disk match', async () => {
82
+ // No subtasks written for our (unique) platform. Other real sessions
83
+ // may exist; our filter shows ours is zero.
84
+ const all = await sessionManager.listAllSubtasks();
85
+ expect(all.filter((s) => s.platform === PLATFORM).length).toBe(0);
86
+ });
87
+ });
88
+ //# sourceMappingURL=session-subtasks.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-subtasks.test.js","sourceRoot":"","sources":["../../src/core/session-subtasks.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,182 @@
1
+ import type { Session, ChatMessage, SubtaskMeta } from './types.js';
2
+ declare class SessionManager {
3
+ private sessions;
4
+ private cleanupTimer?;
5
+ /**
6
+ * Per-key promise chain used to serialize writes that perform a
7
+ * read-modify-write on the in-memory session + persistent JSONL log.
8
+ *
9
+ * Without this, two concurrent {@link addMessage} calls on the same key
10
+ * could interleave: A reads cached session, B reads cached session, both
11
+ * push a different message into the same array, then both write — the
12
+ * later write wins and the earlier message is lost from disk (the
13
+ * in-memory copy is fine because both pushes happened on the same object
14
+ * reference, but the JSONL log only reflects the most recent appendFile +
15
+ * meta save).
16
+ *
17
+ * The lock is keyed by `${platform}:${channelId}:${threadId}`; different
18
+ * threads still proceed in parallel.
19
+ */
20
+ private writeQueues;
21
+ start(): Promise<void>;
22
+ stop(): void;
23
+ /**
24
+ * Run `fn` while holding a per-key serial lock. Subsequent calls with the
25
+ * same key wait until prior ones settle (success OR failure). Returns the
26
+ * value of `fn`. Internal use only — keeps {@link addMessage} race-free
27
+ * without introducing an external dep like p-queue.
28
+ */
29
+ private withLock;
30
+ /**
31
+ * Get or create a session for a conversation
32
+ * Session key: `${platform}:${channelId}:${threadId}`
33
+ */
34
+ getOrCreateSession(platform: string, channelId: string, threadId: string, agent: string): Promise<Session>;
35
+ /**
36
+ * Get existing session without creating a new one
37
+ * Returns undefined if no session exists or it's expired
38
+ */
39
+ getExistingSession(platform: string, channelId: string, threadId: string): Promise<Session | undefined>;
40
+ /**
41
+ * Switch the agent for a session.
42
+ *
43
+ * Generates a new session id but preserves thread identity AND every
44
+ * thread-level field that isn't agent-specific:
45
+ * - usage (per-thread /stats roll-up)
46
+ * - subtasks/active (subtask state lives at thread level)
47
+ * - claudeSessionId (Claude UUID survives /oc → /cc round-trips so the
48
+ * underlying ~/.claude/projects jsonl keeps continuing
49
+ * when the user comes back to claude)
50
+ *
51
+ * `model` and `variant` are reset because they live in different namespaces
52
+ * across CLIs (`opencode` model ≠ `claude` model); carrying them across
53
+ * would just feed the new agent an unrecognized argument.
54
+ */
55
+ switchAgent(platform: string, channelId: string, threadId: string, newAgent: string): Promise<Session>;
56
+ /**
57
+ * Append a message to the session history.
58
+ *
59
+ * Performance: instead of re-serializing the entire session JSON every
60
+ * turn, the message body is appended to a JSONL log file alongside the
61
+ * metadata (which gets a tiny atomic update for `lastActivity`).
62
+ *
63
+ * Concurrency: the entire read-modify-write is serialized per session key
64
+ * via {@link withLock} so two concurrent calls on the same thread (e.g.
65
+ * the IM message landing event AND a tool-result event arriving within
66
+ * the same ms) cannot lose either message to a lost-update race.
67
+ */
68
+ addMessage(platform: string, channelId: string, threadId: string, message: ChatMessage): Promise<void>;
69
+ /**
70
+ * Persist `model` / `variant` / arbitrary patchable fields. Used by
71
+ * `/model`, `/think` etc so the change survives a restart between turns.
72
+ * Mutates the in-memory session in place AND writes metadata atomically.
73
+ */
74
+ patchSession(platform: string, channelId: string, threadId: string, patch: Partial<Pick<Session, 'model' | 'variant' | 'agent' | 'planMode'>>): Promise<Session | undefined>;
75
+ /**
76
+ * Persist claude-code resumable session bookkeeping (UUID + primed flag).
77
+ * Returns the updated session, or undefined if no session exists yet for
78
+ * this thread. Caller is expected to ensure the session exists first.
79
+ */
80
+ setClaudeSessionId(platform: string, channelId: string, threadId: string, claudeSessionId: string): Promise<Session | undefined>;
81
+ markClaudeSessionPrimed(platform: string, channelId: string, threadId: string): Promise<void>;
82
+ /**
83
+ * Persist opencode's native session id (`ses_…`) once we've seen it in the
84
+ * adapter's stream. Idempotent — calling with the same id is a no-op so
85
+ * the per-event callback can fire as many times as opencode sends events.
86
+ */
87
+ setOpencodeSessionId(platform: string, channelId: string, threadId: string, opencodeSessionId: string): Promise<Session | undefined>;
88
+ /**
89
+ * Persist codex's native thread id (UUID) once we've seen it in the
90
+ * adapter's `thread.started` event. Idempotent — same id may fire multiple
91
+ * times per spawn. Mirrors setOpencodeSessionId.
92
+ */
93
+ setCodexSessionId(platform: string, channelId: string, threadId: string, codexSessionId: string): Promise<Session | undefined>;
94
+ /**
95
+ * Increment the per-session usage roll-up after a successful agent
96
+ * invocation. Used by router.callAgentWithHistory to power /stats.
97
+ */
98
+ recordUsage(platform: string, channelId: string, threadId: string, delta: {
99
+ costUsd: number;
100
+ promptChars: number;
101
+ responseChars: number;
102
+ durationMs: number;
103
+ }): Promise<void>;
104
+ /**
105
+ * Reset conversation history (keep session but clear messages)
106
+ */
107
+ resetConversation(platform: string, channelId: string, threadId: string): Promise<Session | undefined>;
108
+ /**
109
+ * Get session with messages (convenience method)
110
+ */
111
+ getSessionWithHistory(platform: string, channelId: string, threadId: string): Promise<{
112
+ session: Session;
113
+ messages: ChatMessage[];
114
+ } | undefined>;
115
+ /**
116
+ * Create or get a subtask session (independent from parent).
117
+ */
118
+ getOrCreateSubSession(platform: string, channelId: string, threadId: string, subtaskId: number, agent: string): Promise<Session>;
119
+ /**
120
+ * Set active subtask id on parent session — subsequent messages route to the subtask.
121
+ */
122
+ setActiveSubtask(platform: string, channelId: string, threadId: string, taskId: number | null): Promise<void>;
123
+ /**
124
+ * Get subtask metadata list from parent session.
125
+ */
126
+ getSubtasks(platform: string, channelId: string, threadId: string): Promise<SubtaskMeta[]>;
127
+ /**
128
+ * Scan all session files on disk and return every subtask, flattened, with
129
+ * its parent platform/channelId/threadId/agent attached so the dashboard
130
+ * can render subtasks across all conversations.
131
+ *
132
+ * Session files live as `<sanitized-key>.json` under SESSIONS_DIR. The
133
+ * sanitized key is one-way (sha256-prefix per non-alnum char), so we
134
+ * cannot reverse it — but each session file preserves the original
135
+ * platform/channelId/threadId fields, which is what we need.
136
+ */
137
+ listAllSubtasks(opts?: {
138
+ agent?: string;
139
+ }): Promise<Array<SubtaskMeta & {
140
+ platform: string;
141
+ channelId: string;
142
+ threadId: string;
143
+ parentAgent: string;
144
+ parentSessionId: string;
145
+ }>>;
146
+ /**
147
+ * Update subtask metadata in parent session.
148
+ */
149
+ updateSubtask(platform: string, channelId: string, threadId: string, taskId: number, patch: Partial<SubtaskMeta>): Promise<void>;
150
+ /**
151
+ * Get next subtask id and persist the increment.
152
+ *
153
+ * Previously returned 1 when the parent session didn't exist yet, but
154
+ * never created one — second call returned 1 again, leading to subtask
155
+ * id collisions. Now we lazy-create the parent session so the counter
156
+ * increments durably from the first call.
157
+ */
158
+ nextSubtaskId(platform: string, channelId: string, threadId: string, agent?: string): Promise<number>;
159
+ /**
160
+ * Persist the full session (metadata + messages). Used for the legacy
161
+ * one-file format on resetConversation() and switchAgent() — anywhere
162
+ * the messages array itself was rewritten. Atomic via tmp+rename.
163
+ */
164
+ private saveSession;
165
+ /** Persist metadata only (no messages payload), atomically. */
166
+ private saveSessionMeta;
167
+ /** Crash-safe write: tmp file + atomic rename.
168
+ *
169
+ * Recovers from ENOENT (parent dir missing) by mkdir-recursive + retry
170
+ * exactly once. This keeps the manager robust to environments where
171
+ * start() wasn't called yet — tests in particular tend to import
172
+ * sessionManager without going through the start() lifecycle, and
173
+ * saveSessionMeta's outer try/catch would otherwise swallow the ENOENT
174
+ * and silently produce a no-op write (the bug that caused
175
+ * session-subtasks.test.ts to fail on a fresh CI runner). */
176
+ private atomicWrite;
177
+ private loadSession;
178
+ private cleanup;
179
+ }
180
+ export declare const sessionManager: SessionManager;
181
+ export {};
182
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/core/session.ts"],"names":[],"mappings":"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"}