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
package/CHANGELOG.md ADDED
@@ -0,0 +1,1234 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [1.0.1] - 2026-05-12
8
+
9
+ ### Changed — config / workspace dir auto-detect (~/.agim/ vs ~/.im-hub/)
10
+
11
+ Follow-up to the v1.0.0 rebrand: the on-disk root is now resolved at
12
+ startup rather than hard-coded to `~/.im-hub/`. New module
13
+ `src/core/agim-paths.ts` exports `AGIM_HOME` and `AGIM_WORKSPACES`,
14
+ computed once with this priority:
15
+
16
+ 1. `$AGIM_HOME` / `$AGIM_WORKSPACES` env override (legacy `$IMHUB_HOME`
17
+ spelling also honored).
18
+ 2. `~/.agim/` (or `~/.agim-workspaces/`) if the directory exists — new
19
+ canonical name in v1.0+.
20
+ 3. `~/.im-hub/` (or `~/.im-hub-workspaces/`) if the directory exists —
21
+ legacy 0.x layout. Picked up automatically so upgraded users keep
22
+ their data in place with **zero migration**.
23
+ 4. Default to `~/.agim/` (or `~/.agim-workspaces/`) for fresh installs.
24
+
25
+ That means:
26
+ - **Upgraded 0.x user** → `agim` finds the existing `~/.im-hub/`, uses
27
+ it. memos.db / reminders.db / sessions/ / wechat-credentials.json all
28
+ stay where they are; nothing is moved.
29
+ - **Fresh installer on a clean box** → `agim` creates `~/.agim/`.
30
+ - **User who manually moves data** to `~/.agim/` later → `agim` switches
31
+ to it automatically on next start.
32
+
33
+ Every hard-coded `join(homedir(), '.im-hub', ...)` and
34
+ `'.im-hub-workspaces'` literal across the codebase (22 files,
35
+ ~30 call sites) was replaced with the resolved root. Modules touched:
36
+ `cli.ts`, `web/server.ts`, `core/onboarding.ts`, `core/audit-log.ts`,
37
+ `core/job-board.ts`, `core/schedule.ts`, `core/session.ts`,
38
+ `core/memos.ts`, `core/reminders.ts`, `core/acp-server.ts`,
39
+ `core/agent-cwd.ts`, `cli-ui/{paths,env-file,service,i18n}.ts`,
40
+ every messenger plugin, `scripts/migrate-gcj02-to-wgs84.ts`.
41
+
42
+ User-facing wizard strings (uninstall confirm, SMTP subtitle, workspaces
43
+ kept) now interpolate the resolved paths so the prompt accurately
44
+ reflects the actual on-disk layout instead of hard-coding the legacy
45
+ literal.
46
+
47
+ ## [1.0.0] - 2026-05-12
48
+
49
+ ### Changed — Brand rename: `im-hub-pro` → **Agim · 阿吉姆**
50
+
51
+ Product upgrade to v1.0 ships under a new brand:
52
+
53
+ - **English name**: `Agim` (Agent + IM, with a hint of "agile")
54
+ - **Chinese name**: `阿吉姆`
55
+ - **CLI command**: `agim` (was `im-hub-pro`)
56
+ - **npm package**: `agim-cli` (was `im-hub-pro`). npm's anti-typosquat
57
+ policy rejected the unscoped name `agim` (too similar to existing
58
+ `ai`/`aegir`/`gm`), so we ship as `agim-cli` and the installed binary
59
+ is still `agim` — the package name only affects the install command
60
+ `npm install -g agim-cli`.
61
+
62
+ Cross-cutting policy:
63
+
64
+ | Surface | New | Status |
65
+ |------------------------------------------------------|--------------------|--------|
66
+ | npm package name | `agim` | renamed |
67
+ | CLI bin command | `agim` | renamed |
68
+ | Old CLI bin command | `im-hub-pro` | **kept as alias** in the same package — existing systemd units, shell aliases, scripts continue to work |
69
+ | Console banner / help text / wizard titles | `Agim` / `阿吉姆` | renamed |
70
+ | Config dir | `~/.im-hub/` | **unchanged** for backwards compat with all 0.x installs |
71
+ | Workspace dir | `~/.im-hub-workspaces/` | **unchanged** |
72
+ | Env vars | `IMHUB_*` | **unchanged** |
73
+ | HTTP header | `X-IM-Hub-Token` | **unchanged** |
74
+ | systemd unit name | `agim.service` preferred | **legacy `im-hub.service` is still recognised** by detection / restart / stop / uninstall code paths |
75
+
76
+ What this means for users:
77
+ - New install: `npm install -g agim` → use `agim …` commands.
78
+ - Existing 0.x install: stays working unchanged. Optional upgrade is just
79
+ `npm uninstall -g im-hub-pro && npm install -g agim`; everything else
80
+ (config, env, data) is untouched. The old `im-hub-pro` bin command
81
+ continues to be installed by the new package, so your systemd unit
82
+ `ExecStart=/usr/local/bin/im-hub-pro start` keeps running.
83
+
84
+ Files touched (non-exhaustive):
85
+ - `package.json` — name + bin (with `im-hub-pro` alias) + version 1.0.0 + description + keywords
86
+ - `src/cli.ts` — banner, program name, help text, comments
87
+ - `src/cli-ui/i18n.ts` — every wizard title / status / uninstall string in
88
+ both EN and 中文
89
+ - `src/cli-ui/service.ts` — accepts either `agim.service` or
90
+ `im-hub.service`; `pgrep` matches both bin names
91
+ - `src/cli-ui/cmd-handlers.ts` — restart / uninstall walk both unit paths
92
+ - `README.md` + `README.zh-CN.md` — full top-section rewrite, install
93
+ commands, migration guide, version history
94
+ - imhub-site repo — separate brand rollout for the marketing site
95
+
96
+ ## [0.6.3] - 2026-05-12
97
+
98
+ ### Fixed — per-platform coordinate-system handling for native location messages
99
+
100
+ User-reported regression: GPS captured via WeChat's `/memo here` H5 link
101
+ landed ~500m–1km off in both Baidu Maps and Amap. Root cause: the old code
102
+ applied `gcj02ToWgs84` to every incoming lat/lng on the assumption that any
103
+ inside-China coord came from Apple Core Location or Google's China-region
104
+ GMS (both of which serve GCJ-02). Reality is messier: WeChat's X5 webview
105
+ and Android Chrome without GMS hand `navigator.geolocation` raw WGS-84, so
106
+ the inverse GCJ offset got applied to coords that were already WGS-84 and
107
+ the stored value was shifted ~500m in the wrong direction.
108
+
109
+ New module **`src/core/coord-systems.ts`** centralises every conversion +
110
+ the per-platform "what coord system did the payload arrive in?" policy:
111
+
112
+ | Source | Default | Override |
113
+ |---------------------------------------|-------------------------|----------|
114
+ | Telegram native location | WGS-84 pass-through | `IMHUB_TELEGRAM_COORDS_GCJ02=1` (iOS-zh-CN users) |
115
+ | Feishu native location | GCJ-02 → WGS-84 | (server-mandated, no override) |
116
+ | DingTalk `link` msgtype, amap.com URL | GCJ-02 → WGS-84 | — |
117
+ | DingTalk `link` msgtype, qq.com URL | GCJ-02 → WGS-84 | — |
118
+ | DingTalk `link` msgtype, baidu.com URL| **BD-09 → WGS-84** (new)| — |
119
+ | DingTalk `link` msgtype, unknown host | WGS-84 pass-through | — |
120
+ | H5 `/memo here` browser geolocation | WGS-84 pass-through | `IMHUB_H5_COORDS_GCJ02=1` (iOS Safari users) |
121
+
122
+ Two **default flips** vs. the old behavior:
123
+ 1. Telegram: was always GCJ→WGS, now pass-through. Telegram Desktop +
124
+ Android-without-China-GMS + non-China users all stop getting a phantom
125
+ inverse offset. Apple-zh-CN-in-China users opt back in via env flag.
126
+ 2. H5: was always GCJ→WGS, now pass-through. Fixes the WeChat X5 case the
127
+ user reported. iOS Safari users opt back in via env flag.
128
+
129
+ New: **BD-09 ↔ GCJ-02 ↔ WGS-84** support. DingTalk users sharing a Baidu
130
+ Maps link no longer get a 1km-off marker (chained GCJ + BD-09 offsets).
131
+
132
+ Refactor:
133
+ - `src/plugins/messengers/{telegram,feishu,dingtalk}/*.ts` — all coord
134
+ normalization now routes through `normalizeIncomingCoords(source, ...)`
135
+ rather than calling `gcj02ToWgs84` directly. Each call site logs
136
+ `event: coord.normalized, source, raw_*, wgs_*, applied` for post-mortems.
137
+ - `src/web/server.ts` — H5 `/loc` handler likewise uses
138
+ `normalizeIncomingCoords('h5-browser-geolocation', ...)`.
139
+ - `src/plugins/messengers/dingtalk/dingtalk-adapter.ts:tryExtractLatLng`
140
+ now also returns `source` based on URL host (amap / qq / baidu / unknown);
141
+ this is what lets the per-source policy fire correctly.
142
+
143
+ Tests:
144
+ - `src/core/coord-systems.test.ts` — 17 cases covering round-trip math
145
+ (WGS↔GCJ, GCJ↔BD-09, BD-09↔WGS-84) + per-source policy + env-flag
146
+ override + out-of-China bypass + NaN guard.
147
+ - `src/plugins/messengers/dingtalk/dingtalk-link-coords.test.ts` —
148
+ 6 cases over the host classifier.
149
+
150
+ ## [0.6.0] - 2026-05-12
151
+
152
+ > **核心主题**:DingTalk 钉钉作为第六个 IM 适配器加入,图片 + 语音双向支持,
153
+ > 语音直接走钉钉服务端 ASR(零延迟、中文准确率好),图片通过
154
+ > `messageFiles/download` 端点拉到本地后让 claude-code 多模态 Read 工具看。
155
+
156
+ ### Added — DingTalk Stream-mode adapter (text + media)
157
+
158
+ **Connection model** — WebSocket Stream via `dingtalk-stream` v2.1.5
159
+ (official SDK). No public webhook required: SDK opens a long-lived WS to
160
+ `api.dingtalk.com/v1.0/gateway/connections/open`, handles reconnect /
161
+ heartbeat / access-token rotation internally. Bot only receives when the
162
+ user @-mentions in groups or sends 1:1 (DingTalk hard limit, same as
163
+ Feishu).
164
+
165
+ **Outbound** — REST OpenAPI under the bot's auto-rotated access token:
166
+ - 1:1 → `POST /v1.0/robot/oToMessages/batchSend`
167
+ - group → `POST /v1.0/robot/groupMessages/send`
168
+
169
+ The per-message `sessionWebhook` is NOT used — its 30 s expiry doesn't
170
+ survive multi-minute agent runs. OpenAPI works for any send.
171
+
172
+ **Inbound msgtypes** — empirically confirmed via real payload dumps
173
+ (community docs were inconsistent):
174
+
175
+ | msgtype on wire | Payload location | Adapter behavior |
176
+ |---|---|---|
177
+ | `text` | `text.content` | Pass through to agent (no change) |
178
+ | `picture` | `content.downloadCode` | Download via `/v1.0/robot/messageFiles/download` (the `pictureDownloadCode` field looks tempting but returns 500 — reserved for a different endpoint we don't use). Save under `~/.im-hub/media/dingtalk/<thread>/<msgId>.<ext>`. Append `[图片附件:<path>]` to user text. claude-code's Read tool sees the JPG. |
179
+ | `audio` | `content.downloadCode` + `content.recognition` | **Prefer the server-side ASR transcript in `recognition`** (zero latency, better Mandarin accuracy than whisper.cpp base model). Still download the source AMR for archive/replay. Fall back to whisper.cpp only when `recognition` is missing. |
180
+ | `link` | `link.messageUrl` | Try `tryExtractLatLng()` on the URL (handles Amap, Tencent QMap, generic `?lat=&lng=`, `?location=lat,lng`). On hit → stash in `location-context`, next user text gets the GPS prefix injected. |
181
+ | `file` | `content.downloadCode` | Acknowledge only (PDF / Word handling is a separate effort) |
182
+ | anything else | full payload | WARN-level dump to journalctl so unknown shapes surface for the next iteration |
183
+
184
+ **Filename hygiene** — DingTalk msgIds are base64-shaped
185
+ (`msgAB2pLyIgOgo6bJXw9mWAtA==`) and contain `/+=`. `safeFilenameStem()`
186
+ sanitises every non-`[A-Za-z0-9_-]` char to `_` before write, so the
187
+ files land at `msgAB2pLyIgOgo6bJXw9mWAtA__.jpg` and the path-injection
188
+ guard in `saveDingTalkMedia()` doesn't reject them.
189
+
190
+ **Config** — `{ dingtalk: { clientId, clientSecret } }` in
191
+ `~/.im-hub/config.json`. `clientId` doubles as `robotCode` for
192
+ stream-mode internal apps; we also cache the most-recent `robotCode`
193
+ from inbound messages for robustness against multi-bot setups.
194
+
195
+ **Wizard** — full per-component flow under `Configure → Messengers →
196
+ DingTalk` (bilingual). Docs walkthrough (Stream-mode bot creation in
197
+ `open.dingtalk.com`), prefilled ClientID on re-config, masked "Enter =
198
+ keep existing" for ClientSecret.
199
+
200
+ **Known platform limitations**:
201
+ - DingTalk does NOT deliver native location messages to bots (verified
202
+ empirically — zero inbound when the user shares a location).
203
+ Workaround: `/memo here <what>` capture link, or type the address and
204
+ let the agent geocode via Baidu.
205
+ - No typing indicator API for stream bots. The 1:1 client UI shows a
206
+ native "正在输入" hint; groups get nothing until first chunk.
207
+
208
+ **Files**:
209
+ - `src/plugins/messengers/dingtalk/{types,dingtalk-client,dingtalk-adapter,media-store,index}.ts` (new)
210
+ - `src/core/registry.ts` — registered
211
+ - `src/core/onboarding.ts` — `Config.dingtalk` shape added
212
+ - `src/cli-ui/{config-wizard,i18n}.ts` — wizard + EN/中文 strings
213
+ - `package.json` — added `dingtalk-stream@^2.1.5`
214
+
215
+ ## [0.5.2] - 2026-05-11
216
+
217
+ > 0.5.0 introduced the bilingual wizard but 0.5.1 hands-on showed it had
218
+ > regressed real functionality vs. the old `im-hub-pro config <component>`
219
+ > flows — checkbox-only selection couldn't issue WeChat QR codes, prompt
220
+ > for Telegram tokens, probe agent CLI installs, etc. 0.5.2 restores all
221
+ > of that work inside the new arrow-key UI, plus fixes a pile of paper-
222
+ > cuts surfaced from real TTY sessions.
223
+
224
+ ### Changed — wizard restructured as list-of-per-component menus (matches legacy `config <component>`)
225
+
226
+ **IM 渠道** is no longer a checkbox. Each messenger gets its own sub-menu
227
+ with the real configuration work the legacy flow did:
228
+
229
+ - **WeChat (iLink)** — `Configure` scans QR via
230
+ `ILinkWeChatAdapter.startQRLogin()` + `waitForQRLogin()`; on success the
231
+ credentials persist to `~/.im-hub/wechat-credentials.json` (existing
232
+ adapter path) and `wechat-ilink` is added to `cfg.messengers`. Re-running
233
+ re-issues a fresh QR.
234
+ - **Telegram** — prompts for bot token (hidden) + channel id. On
235
+ re-config the existing token is shown masked (`••••••XXXX`) and Enter
236
+ keeps it; channel id prefills.
237
+ - **Feishu / Lark** — prompts for App ID + App Secret with the same
238
+ "Enter = keep existing" behavior for the secret.
239
+ - **Discord** — prompts for token + channel + allowed guilds/channels
240
+ (comma-separated). Existing values prefill except the token (masked).
241
+ - **Email** — pure toggle since SMTP credentials live one section down.
242
+ Enabling prints a reminder to also configure SMTP.
243
+
244
+ Each per-component menu has its own `Configure / Re-configure / Remove /
245
+ ← Back` choices; status row in the list above shows
246
+ `enabled / enabled (credentials missing) / not configured` per item.
247
+
248
+ **AI agents** is also per-component now, with live CLI install detection
249
+ (`<cmd> --version` probe, copilot uses the plugin's own multi-path
250
+ `isAvailable()`):
251
+
252
+ ```
253
+ Claude Code — enabled · CLI installed
254
+ OpenCode — not enabled · CLI installed
255
+ Codex — not enabled · CLI NOT installed
256
+ GitHub Copilot CLI — not enabled · CLI NOT installed
257
+ ```
258
+
259
+ Per-agent sub-menu offers `Add to enabled list / Set as default /
260
+ Remove / Show install instructions / Re-check CLI installation / ← Back`.
261
+ Install instructions and `auth login` hints are localized; the legacy
262
+ flow's bash-only install text is preserved. First agent enabled when no
263
+ default exists is auto-promoted to default.
264
+
265
+ ### Added — env reads now find values wherever they actually live
266
+
267
+ The wizard previously only read `~/.im-hub/env` for SMTP / Baidu prefill,
268
+ so machines where those keys were set in the systemd unit's
269
+ `Environment=` lines (the common install pattern from before the wizard
270
+ existed) showed `(unset)` even though everything was working at runtime.
271
+ New `readEffectiveEnv()` falls back through three sources for read:
272
+
273
+ 1. `~/.im-hub/env` (canonical, what the wizard writes)
274
+ 2. `process.env` (shell-exported)
275
+ 3. `/etc/systemd/system/im-hub.service` — parsed `Environment=` lines
276
+ (file is world-readable on standard installs, so works for the user's
277
+ shell-invoked `im-hub-pro config`)
278
+
279
+ Writes still go to the env file only. When `hasProcessEnvOverride(key)`
280
+ detects a wizard-managed key that's set somewhere outside the env file,
281
+ the SMTP / Baidu save path prints a one-line warning:
282
+
283
+ ```
284
+ ⚠️ IMHUB_BAIDU_MAP_AK is also set by the running environment (systemd
285
+ unit / shell export). Saving to ~/.im-hub/env alone will NOT change the
286
+ active value — update the unit file or `unset` the shell export first.
287
+ ```
288
+
289
+ ### Fixed — four paper-cuts from real TTY use
290
+
291
+ 1. **Duplicate language prompt** when entering `config` from the entry
292
+ menu. `runEntryMenu()` now returns `{ action, lang }`, and the
293
+ dispatcher in `cli.ts` reuses the picked lang. (`src/cli-ui/entry-menu.ts`)
294
+ 2. **No `← Back` inside sub-sections.** Every menu and sub-menu (and
295
+ sub-sub-menu) now has an explicit Back row — non-technical users
296
+ should never need to discover Ctrl-C.
297
+ 3. **Existing values not prefilled.** SMTP host / port / user / from now
298
+ prefer the stored value over preset defaults. The password prompt
299
+ accepts empty input as "keep existing" with a new "Enter = keep
300
+ existing" hint. Baidu AK uses the same effective-env read.
301
+ 4. **Agent default-agent drift was invisible.** Configs with
302
+ `agents: []` + `defaultAgent: "opencode"` now treat the union as the
303
+ effective enabled set, so the working default shows correctly. When
304
+ the user explicitly disables every agent, `defaultAgent` is also
305
+ cleared.
306
+
307
+ ### Notes
308
+ - No behavior change for first-time setup; everything new is in the
309
+ re-entry / drift-state / prefill paths.
310
+ - Verified via `tsc --noEmit` + manual TTY round-trip on the SMTP /
311
+ Baidu / per-messenger / per-agent flows.
312
+
313
+ ## [0.5.1] - 2026-05-11
314
+
315
+ ### Fixed — `require() is not defined` crash entering the wizard
316
+ 0.5.0 shipped three `require()` calls inside ESM modules (`package.json` is
317
+ `"type": "module"`), so the wizard crashed the moment the user picked a
318
+ language:
319
+
320
+ ```
321
+ ReferenceError: require is not defined
322
+ at menu (file:///.../cli-ui/prompts.js:37:40)
323
+ ```
324
+
325
+ Fixed all three:
326
+ - `src/cli-ui/prompts.ts` — `Separator` now statically imported from
327
+ `@inquirer/prompts`
328
+ - `src/cli-ui/service.ts` — `openSync`/`closeSync` added to the existing
329
+ `node:fs` import
330
+ - `src/cli.ts` — replaced `require('node:child_process')` with
331
+ `await import('node:child_process')` in the entry-menu's foreground-start
332
+ dispatch path
333
+
334
+ No other behavior changes. v0.5.0 was tagged but never npm-published, so
335
+ this is the actual first published 0.5.x release.
336
+
337
+ ## [0.5.0] - 2026-05-11
338
+
339
+ > **核心主题**:`im-hub-pro` CLI 升级成"运维主入口"——双语 + 上下键交互向导 + 完整服务生命周期管理(status / start --bg / restart / stop / uninstall)。SMTP 和百度地图 AK 终于进入 `config` 一站式配置。
340
+
341
+ ### Added — 双语交互式 `config` 向导
342
+ 之前 `im-hub-pro config` 是 per-component 的命令行参数式配置(`config wechat` / `config telegram` / ...),界面是纯 `readline.question` 文本提示,新用户摸不着头脑。
343
+
344
+ 新增基于 `@inquirer/prompts` 的交互向导:
345
+ - **语言选择**:首次进入弹出 EN / 中文 选择器,记忆到 `~/.im-hub/cli-lang`,下次进入回车即可
346
+ - **5 个分类**(每个分类内部支持 ← Back 回根菜单):
347
+ 1. **IM 渠道** — 多选 checkbox:微信 / Telegram / 飞书 / Discord / 邮件
348
+ 2. **AI Agent** — 多选 + 选默认 agent(Claude Code / OpenCode / Codex / Copilot)
349
+ 3. **远程 ACP agent** — 列表 + 新增 / 编辑 / 删除,支持 Bearer token 认证(输入隐藏)
350
+ 4. **SMTP**(新增)— 4 个预设(Gmail / Outlook / QQ / 163)+ Custom + Skip;写到 `~/.im-hub/env`(chmod 600)
351
+ 5. **百度地图 AK**(新增)— 输入 / 清除,写到 `~/.im-hub/env`
352
+ - **Per-spawn 持久化**:SMTP 密码、Baidu AK 一输入就落盘,wizard 半途崩了不丢
353
+ - 旧的 `im-hub-pro config <component>` 路径保留兼容
354
+
355
+ ### Added — bare `im-hub-pro` → 运维入口菜单
356
+ 直接敲 `im-hub-pro`(无参)不再 dump help。改成弹出 8 项菜单:
357
+ - Configure
358
+ - Show status
359
+ - Start (foreground / background)
360
+ - Restart
361
+ - Stop
362
+ - Uninstall
363
+ - Quit
364
+
365
+ 非 TTY 环境(pipe / cron)仍回退到 commander 的 `--help`。
366
+
367
+ ### Added — 5 个服务管理命令
368
+
369
+ | Command | 行为 |
370
+ |---|---|
371
+ | `im-hub-pro status` | 探测 systemd / background daemon / foreground;显示 pid + uptime + Web URL |
372
+ | `im-hub-pro start --bg` | 后台启动(nohup + setsid + pid 文件 + 日志到 `~/.im-hub/im-hub.log`);裸 `start` 仍前台 |
373
+ | `im-hub-pro restart` | 自动选 systemctl restart 或 bg daemon 重启 |
374
+ | `im-hub-pro stop` | 自动选 systemctl stop 或 SIGTERM bg pid + cleanup pid 文件 |
375
+ | `im-hub-pro uninstall` | 双重确认后:停服务 → 卸载 npm 包 → 删 config.json + env + pid + systemd 单元;**保留 `~/.im-hub-workspaces/`** |
376
+
377
+ **跨平台后台启动**:Linux / macOS / WSL2 都走 `nohup`+detach 路径。macOS 上想真正开机自启仍要自己写 launchd plist(CHANGELOG 后续版本可加)。
378
+
379
+ ### Added — 新模块
380
+ - `src/cli-ui/i18n.ts` — 100+ 键的 `Record<Key, string>` × `{en, zh}`,TS 编译期保证两侧同步
381
+ - `src/cli-ui/prompts.ts` — `@inquirer/prompts` 的薄包装,统一 BACK 哨兵 + UserAbortError 处理
382
+ - `src/cli-ui/lang-picker.ts` — 语言选择 + 持久化
383
+ - `src/cli-ui/config-wizard.ts` — 5 个分类的向导主体
384
+ - `src/cli-ui/service.ts` — `detectService()` / `spawnBackground()` / `stopService()` / `formatUptime()`
385
+ - `src/cli-ui/env-file.ts` — 增量更新 `~/.im-hub/env`(merge-update + chmod 600)
386
+ - `src/cli-ui/cmd-handlers.ts` — status / start / stop / restart / uninstall 实现
387
+ - `src/cli-ui/entry-menu.ts` — bare invocation 用的入口菜单
388
+ - `src/cli-ui/paths.ts` — 共享路径常量
389
+
390
+ ### Changed — Feishu adapter: 移除 "🤔 思考中…" placeholder
391
+ 飞书原生有打字状态指示,bot 自己再发占位文本只会把对话框搞乱(飞书还不支持 bot 消息撤回)。注释保留为什么。
392
+
393
+ ### Changed — 消息回复不再加 `[opencode]` / `[codex]` / `[claude-code]` 前缀
394
+ 原本为了让用户看清哪个 agent 在答,但 sticky session 下 99% 的消息都是同一个 agent,每条都加一个 `[xxx]\n\n` 反而噪音。要看当前 agent 跑 `/status` 或 `/router status`。
395
+
396
+ ### New dependency
397
+ - `@inquirer/prompts ^8.4.3` — modern interactive prompts (上下键 / Space / 输入隐藏)。~80KB 加到 dist。
398
+
399
+ ### Migration notes
400
+ - 老用户升级:`npm install -g im-hub-pro@0.5.0`。已有 `~/.im-hub/config.json` 全部兼容,新增 SMTP / Baidu 配置可以走新向导补充
401
+ - 想跳过新向导:`im-hub-pro config wechat` / `config telegram` 等老路径仍然能用
402
+ - 后台启动取代了之前文档里推荐的 `nohup im-hub-pro start &` 写法
403
+
404
+ ## [0.4.0] - 2026-05-11
405
+
406
+ > **核心主题(两条)**:
407
+ >
408
+ > 1. **codex 也接 imhub MCP**——/remind 4 工具 + /memo 5 工具,9 个全部对它开放。三大 agent(claude-code / opencode / codex)功能对等。
409
+ > 2. **位置消息架构统一化**——Telegram / 飞书的原生位置消息原本走「pending-reply + 启发式拆词」决策链,bug 频出。新版改成 agent-driven:adapter 只缓存坐标,cli.ts 注入结构化 annotation 到用户下一条文本,agent 用 LLM 决策。删除 ~400 行启发式代码,4 个 IM 渠道行为完全一致。
410
+
411
+ ### Refactored — Telegram + 飞书位置消息走 agent-driven 决策(对齐 WeChat H5 流程)
412
+ **问题**:旧流程 adapter 拿到原生 pin → setPending → ask "回 y/n/名字" → cli.ts 跑 `memo-what-extract` 启发式(剥前缀 / cut at verb / `的`-split / LLM 兜底 / slice 兜底)→ save_memo。这条链跑出过的 bug 包括:
413
+ - "记下,这是一个好吃的日料店" → what="记下"(前缀没剥)
414
+ - "我车暂时停这里,晚点过来取" → what="车暂时"(state 词没识别)
415
+ - "这是我公司地址,记录下" → what="这"(demonstrative 没剥)
416
+ - "一个好吃的日料店" → what="一个好吃的日"(slice 截断在「的」前)
417
+
418
+ 每修一个 case 又冒出新的,根因是决策者错位——adapter 用 deterministic 代码做语言理解。
419
+
420
+ **重构**:
421
+ - 新增 `src/core/location-context.ts`:~80 行纯 TTL 缓存(5min, single-shot consume)+ `formatLocationAnnotation()`
422
+ - 删除 `pending-memo-location.ts` + `memo-what-extract.ts`(共 ~400 行)+ 它们的 test 文件
423
+ - Telegram adapter:`message:location` / `message:venue` 改成 `setLocationContext` + 简短 ack("📍 位置已收到 (lat, lng)。告诉我要做什么。")
424
+ - 飞书 adapter:`message_type:'location'` 同上
425
+ - cli.ts:agent dispatch 前如果有缓存的位置,把 `[user just shared location: lat=…, lng=…, name="X", address="Y", accuracy_m=N]` 前缀拼到 `ctx.message.text`,单次 consume 后清除
426
+ - `save_memo` MCP tool description 新增 "📍 LOCATION ANNOTATION" 段,教 agent 识别这个 tag、直接用 lat/lng、不要再调 `request_location_capture`
427
+
428
+ **用户感知变化**:
429
+ - bot 不再问 "回 y/n"——直接看用户下一条说什么,agent 用 LLM + 整段上下文决策
430
+ - 任何长句、短词、混合表达都不再走启发式:「这是我公司」「记下日料店」「不用记了」「这是哪里」都自然处理
431
+ - 每个 pin 后续消息额外付出一次 agent 调用(旧版是 deterministic 直存);用稳定性换费用
432
+
433
+ ### Added — codex 也能用 imhub MCP 工具
434
+ (这一段已在 0.4.0-rc1 时落地,下面是 rc1 的内容)
435
+
436
+ 之前 codex 是唯一一个 reminder/memo 工具完全无法触达的 agent,因为 codex CLI 没有 `--mcp-config` flag。验证发现 codex 0.130.0+ 支持通过 `-c mcp_servers.<name>.{command,args,env}=...` 的 TOML 内联 override 在调用层注册 MCP server——per-spawn、不污染全局 `~/.codex/config.toml`。
437
+
438
+ 实现:
439
+
440
+ - **`src/plugins/agents/codex/build-mcp-cli-args.ts`**:纯函数,输入 `{sockPath, runId}`,输出 6 个 argv entries(3 对 `-c key=value`)。TOML 转义走 `JSON.stringify` 兼容 basic-string 语法。共享 claude-code 那个 `mcp-approval-server.js`(同一个二进制读 `IMHUB_APPROVAL_SOCK + IMHUB_RUN_ID`,已经 channel-agnostic)。
441
+ - **`codex/index.ts` 的 `prepareCommand`**:fallback 三道闸——`IMHUB_APPROVAL_DISABLED=1` / `approvalBus.getSocketPath()` 空 / 无 IM context 任一不满足就降级到无 MCP 模式,codex 仍能正常调用。命中时生成 UUID runId → `approvalBus.registerRun(runId, ...)` → MCP `-c` 参数前置到 argv 头部(codex 的 clap 要求全局参数在 `exec` 之前)→ `cleanup` 在 spawn 退出后 `unregisterRun`。
442
+ - **`codex exec resume` 不再接 `-s`**(codex 0.130 改的):resume 路径继承原会话 sandbox,传 `-s` 直接 `unexpected argument` 报错。修复:sandbox flag 仅在 fresh `exec` 加。
443
+ - **`--dangerously-bypass-approvals-and-sandbox`**:codex 在 `exec --json` 非交互模式下默认会 auto-cancel 所有 MCP 工具调用(找不到 TUI 让用户点 approve)。这个 flag 是 codex 0.130 目前唯一让 MCP 工具在非交互模式下能实际运行的方式。代价是 codex 自己的 bash/edit 也失去 sandbox。提供 `IMHUB_CODEX_SAFE_MODE=1` opt-out(代价:MCP 工具不可用)。
444
+ - **AGENTS.md 写明命名差异**:claude-code/opencode 暴露 `mcp__imhub__save_memo`;codex 暴露 bare name `save_memo`。模型按错名调用会静默失败。
445
+
446
+ **版本下限**:codex CLI ≥ 0.130.0(`mcp` 子命令 + `-c mcp_servers.*` TOML override 同时存在的最早稳定版)。
447
+
448
+ **不在范围**:codex 自己的 Read/Edit/Bash 走 approval-bus——需要把 codex 跑成 `codex mcp-server` 让 im-hub 当 MCP client,是 v0.5.x 级别的大重构。
449
+
450
+ ## [0.3.1] - 2026-05-11
451
+
452
+ ### Added — codex 也能用 imhub MCP 工具
453
+ 之前 codex 是唯一一个 reminder/memo 工具完全无法触达的 agent,因为 codex CLI 没有 `--mcp-config` flag。验证发现 codex 0.130.0+ 支持通过 `-c mcp_servers.<name>.{command,args,env}=...` 的 TOML 内联 override 在调用层注册 MCP server——per-spawn、不污染全局 `~/.codex/config.toml`。
454
+
455
+ 实现:
456
+
457
+ - **`src/plugins/agents/codex/build-mcp-cli-args.ts`**:纯函数,输入 `{sockPath, runId}`,输出 6 个 argv entries(3 对 `-c key=value`)。TOML 转义走 `JSON.stringify` 兼容 basic-string 语法。共享 claude-code 那个 `mcp-approval-server.js`(同一个二进制读 `IMHUB_APPROVAL_SOCK + IMHUB_RUN_ID`,已经 channel-agnostic)。
458
+ - **`codex/index.ts` 的 `prepareCommand`**:fallback 三道闸——`IMHUB_APPROVAL_DISABLED=1` / `approvalBus.getSocketPath()` 空 / 无 IM context(threadId+platform+channelId)任一不满足就降级到无 MCP 模式,codex 仍能正常调用。命中时生成 UUID runId → `approvalBus.registerRun(runId, ...)` → MCP `-c` 参数前置到 argv 头部(codex 的 clap 要求全局参数在 `exec` 之前)→ `cleanup` 在 spawn 退出后 `unregisterRun`。
459
+ - **测试**:39 个新单测覆盖 builder(argv shape + TOML 双引号/反斜杠/UUID/空格路径 escaping)+ adapter(fallback 三道、happy path、runId 嵌入、cleanup、cwd+planMode+resume 与 MCP -c 共存、并发产生不同 runId)。
460
+
461
+ **仍然不做**:
462
+
463
+ - codex 自己的 Read/Edit/Bash 走 approval-bus——CLI 没 hook,需要换成 `codex mcp-server` mode + 我们当 MCP client,是 v0.5.x 级别的大改造
464
+ - 全局 `~/.codex/config.toml` 写入——这次坚决走 per-spawn -c 路径,不修改用户配置
465
+
466
+ **版本下限**:codex CLI ≥ 0.130.0(`mcp` 子命令 + `-c mcp_servers.*` TOML override 同时存在的最早稳定版)。adapter 不主动探测版本——老版本 codex 看到不认识的 `-c` key 会忽略,所以最坏情况是 reminder/memo 工具不可见但 codex 正常跑。
467
+
468
+ ## [0.3.1] - 2026-05-11
469
+
470
+ > **核心主题**:把 `/memo` 的位置感知从「只有 H5 抓 GPS」一条路扩成「**Telegram / 飞书原生位置消息 + H5**」三条路。顺带把所有渠道的中国坐标系偏移问题彻底修了——iOS/Android Core Location 在境内吐 GCJ-02,再走 WGS→GCJ 的 amap 链接就成双重偏移,~500m-1km 翻车。
471
+
472
+ ### Added — Telegram 原生 `message:location` + `message:venue` 消息支持
473
+ 之前 telegram adapter 只接 text/photo/document/voice/audio/video/video_note/animation 八种事件,**完全忽略**用户点📎→Location 发的 pin。现在加上 grammy 的 `bot.on('message:location')` + `bot.on('message:venue')` 双 handler——
474
+
475
+ - 收到 pin → 通过新建的 `pending-memo-location.ts`(TTL 5min,threadKey-scoped,结构对齐 `pending-reminder.ts`)暂存 `{lat, lng, accuracy?, venueLabel?, venueAddress?}`
476
+ - adapter 立刻 reply 「📍 收到位置 (lat, lng),要记一笔吗?回个名字 / `n` 跳过」
477
+ - 用户下条 text 消息走 cli.ts 新加的 `tryConsumePendingMemoLocationReply` 拦截(位置:在 reminder-confirm 之前、agent dispatch 之前),cancel 模式直接放弃,否则用回复文本当 `what` 调 `createMemo({where_lat, where_lng, where_label, what})`
478
+ - Venue 模式额外支持 `y` 确认(直接用 venue title 当 `what`,省去重打字),改名也支持
479
+ - 20 单测覆盖 store + reply-consumption 矩阵
480
+
481
+ ### Added — 飞书原生 `message_type: 'location'` 消息支持
482
+ 飞书 IM 协议本身有结构化位置消息,content 是 `{ name?, address?, latitude, longitude }`(注意 lat/lng 是字符串需要 parseFloat)。adapter 之前是纯文本,只解 `content.text`。现在 BEFORE 那一步加分支,命中 location 类型就走同一条 pending-memo-location 链路,跟 telegram 完全对齐。Venue 形态(带 name)支持 `y` 确认,无 name 时退到自由输入。
483
+
484
+ ### Added — `memos.memo` 列:保存用户原话,不丢长信息
485
+ 之前用户回 telegram pending prompt 时说一整句话("我车暂时停这里,晚点过来取"),整句话就成了 `what`,搜索很难找。新增:
486
+
487
+ - **schema 加 `memo TEXT` 列**——idempotent ALTER TABLE 迁移(CREATE TABLE 里也加,新老 DB 都覆盖)
488
+ - **新模块 `memo-what-extract.ts`** 4 级提取策略(cheap → rich):
489
+ 1. **short**——文本 ≤6 CJK / ≤16 ASCII + 无句号逗号 + 无主语前缀,原样当 `what`
490
+ 2. **heuristic**——剥 `我[的]?` / `本人`,找句中最早出现的状态词(暂时/今天/刚刚)或安置类动词(停/放/在/找)作为切点,前面那段当 `what`。能确定性 handle "我车暂时停这里" → "车"
491
+ 3. **llm**——`callHelperForJson` 调用户当前活跃 agent,给个 strict JSON-only prompt,6s 超时
492
+ 4. **fallback**——前 6 字符切片,保证 save 不会因为提取失败而阻塞
493
+ - **`search_memos` query 加上 memo 字段**:现在搜全文也能命中
494
+ - **`save_memo` / `update_memo` MCP 工具加可选 `memo` 参数**:agent 直接落库时也能用
495
+ - **`summarizeMemo` 返回带 memo 字段**——搜索结果让 agent 能引用原话
496
+ - 用户可见:保存回复多一行「记录原文:...」当提取出的 `what` 跟用户原话不一样时
497
+ - 17 单测覆盖 short/heuristic/fallback 各路径 + emoji / 中英混合 / 长句不卷死等 adversarial 输入
498
+
499
+ ### Fixed — 中国境内坐标双重偏移(影响 Telegram + 飞书 + H5 三条路径)
500
+ **根因**:iOS Core Location(Apple,iOS 7+)和 Android Google Play Services 都会在中国境内**主动**把 WGS-84 偏移成 GCJ-02 才交给应用层(合规要求)。Telegram / 飞书 / 浏览器 `navigator.geolocation` 都吃这个偏移,传给 im-hub 时已经是 GCJ-02。我们当 WGS-84 存入库,构 amap URL 时再做一次 WGS→GCJ-02 = 双重偏移,~500m-1km。
501
+
502
+ **修复**:
503
+
504
+ - **新函数 `gcj02ToWgs84()`**:已有 `wgs84ToGcj02()` 的 Newton-iteration 逆运算,3 趟收敛到亚米级(< 1e-6 度 ≈ 0.1m)。境外(lng < 72 / > 137.83 OR lat < 0.83 / > 55.83)自动 no-op
505
+ - **Telegram adapter**:`message:location` + `message:venue` handler 都先 GCJ→WGS 再 setPending
506
+ - **飞书 adapter**:`message_type: 'location'` handler 同样转
507
+ - **`/api/loc` POST**(H5 抓 GPS 入口):同样转
508
+ - **`src/scripts/migrate-gcj02-to-wgs84.ts`**:一次性回填脚本,dry-run + write 模式,幂等(境外行跳过)。本机部署时已对 DB 里 7 行历史 memo 跑过——所有坐标平均西南偏移 ~500m,回到真实物理位置
509
+ - 6 个新单测:6 城(北京/杭州/上海/广州/乌鲁木齐/兰州)round-trip < 1e-6 度,境外 no-op,方向校验(GCJ→WGS 应向西南偏移)
510
+
511
+ ### Added — 部署脚本自动迁移坐标
512
+ 没加。该迁移是 one-shot 性质,加在 startup 路径上反而每次重启重跑(虽然 outOfChina 已 no-op,但 1e-7 浮点抖动还是会触发"已变更"日志)。保持手动 `node dist/scripts/migrate-gcj02-to-wgs84.js [--dry-run]` 触发。
513
+
514
+ ### Fixed — Venue title 在 where_label 中保留
515
+ 之前 cli.ts pending consumer 写 `where_label` 时是 `venueAddress ?? venueLabel`——`??` 让 venueAddress 优先,title 就被丢了。Telegram venue 通常两者都有("黄龙国际中心" + "学院路77号"),title 是用户最容易认出的那条。改成 `[venueLabel, venueAddress].filter(Boolean).join(' · ')`,两者都存,搜任一 token 都能命中。回复卡片同步加一行 `📍 <title> · <address>` 让用户看见保留。
516
+
517
+ ### Site (separate repo: imhub-site)
518
+ 不直接绑 npm 包,但同期 imhub-site 跑了一波 optimization pass:
519
+
520
+ - 一键安装命令分 Linux / macOS / Windows 三 tab(同一份 install.sh,uname 自检;Windows 走 WSL2)
521
+ - `curl ... | bash` 改 `bash -c "$(curl ...)"`:macOS bash 3.2 + /dev/tty 重连不稳的 pipe 形式换成 Homebrew 同款
522
+ - install.sh:macOS 友好的依赖缺失提示(`brew install node` / `xcode-select --install`)+ 纯 bash JSON encoder(新版 macOS 不再预装 python3)+ launchd 提示
523
+ - SEO:robots.txt + sitemap-index.xml + og.svg
524
+ - 404 页面 + 双语支持
525
+ - 代码 DRY:install 命令抽到 `src/data/install.ts` 单一真源;JS 初始化抽到 `src/lib/install-ui.ts`
526
+ - 可达性:`role="tablist"` / `aria-selected` / keyboard navigation 完整
527
+
528
+ ## [0.3.0] - 2026-05-11
529
+
530
+ > **核心主题**:`/location` 升级为通用 `/memo`(5W1H 持久记忆库),地图服务从腾讯切到百度,opencode HTTP driver 修复 SSE-broken 回归。这是个有意义的里程碑——位置只是 5W1H 中的一个轴。
531
+
532
+ ### Fixed — opencode HTTP driver: SSE 不发事件时从 POST body 拿文本
533
+ opencode 1.14.45+ 在新的 `node-http-server` runtime 下 `/event` SSE 只发首个 `server.connected` 然后再不广播任何 bus 事件(`message.part.updated` / `session.idle` 全丢),导致 im-hub 这边收 0 字节,用户在微信里看到「opencode无回复」。
534
+
535
+ 直 curl 复现:POST `/session/:id/message` 这个 endpoint 现在改成阻塞直至 LLM 完成,把整条 assistant message 同步以 JSON 还回来。我们就拿这个:
536
+
537
+ - `OpenCodeHttpAdapter.postPrompt` 不再 `Promise<void>`,改回 `Promise<OpencodeMessageResponse>`,把 body 解析出来
538
+ - `inspectHttpEvent` 多返一个 `partId`,SSE 路径走过的 part 都进 `yieldedPartIds`,不会被 POST 兜底重复 yield
539
+ - 新增 `drainResponseBody(body, alreadyYielded, opts)`:从 POST body 抽完成态 text part(time.end 已设、文本非空、非 reasoning / step-* 类)+ 把 cost / tokens 喂回 `opts.onUsage`
540
+ - `promptPromise.finally` 内挂 500ms grace 计时器调 `sse.close()`:旧版 opencode SSE 正常时仍走原路径;新版 SSE 一直空时让 for-await 退出,然后排掉 POST body
541
+ - 命中走 fallback 路径会记一行 `opencode.http.drained_post_body` 日志,方便排查
542
+
543
+ 新增 3 单测覆盖:drained-only / SSE+POST 同 part 的 dedupe / `drainResponseBody` 的过滤逻辑。35/35 adapter 测试全过。
544
+
545
+ ### Added — 部署脚本自动重启 opencode
546
+ `/root/.claude/bgjobs/scripts/imhub-deploy.sh` 末尾加一段:im-hub 重启完成后如果检测到 `opencode serve --port 14199` 还在跑,调 `opencode-restart.sh` 把它弹一遍,让新 daemon 继承当前 shell 的 `IMHUB_APPROVAL_SOCK`(im-hub 每次重启会换 socket hash,旧 daemon 还指向旧 socket → MCP 工具调用会回「后台消息服务不可用」)。一行钩子,一劳永逸。
547
+
548
+ ### Added — 给 place-like memo 主动追问坐标
549
+ agent 落库一条「老孙家」「公司新址」这种**明显是地点但用户没给坐标**的 memo 后,会反问一句「要不要顺带补个坐标?」给三个选项(现在抓 GPS / 告地址解析 / 不用)。`save_memo` tool description 里写死的 PROACTIVE FOLLOW-UP 段。同一段还硬塞一份 TOOL INVENTORY 列表(5 个 memo 工具),并明令「不准跟用户说工具下线了」——之前 agent 在 location→memo 重命名后偶尔会胡说「位置工具已下线」。
550
+
551
+ ### Added — `update_memo` 接受 `address` 参数走 geocoding 路径
552
+ 之前 update_memo 只能直接给 `where_lat/where_lng/where_label`。现在传 `address` 字符串会自动调百度地理编码补全坐标 + 把 formatted address 当 label。配合 augment-mode token 流(POST `/api/loc` 带 memoId),用户也能在 H5 上直接补充「我现在就在那」的当前 GPS 给已存的 memo。
553
+
554
+ ### Fixed — memo 时区 + 永久 location 判断
555
+ 两个用户反馈直接修:
556
+
557
+ 1. **不应该按"有没有 location"判断 transient/permanent**。我朋友常去的茶馆、公司新址、家庭住址这些**有 location 但应该永久**记的,之前 prompt 里把"含位置类"暗示成 24h 临时,会丢数据。
558
+ - `save_memo` tool description 重写 EXPIRY 段:判断条件改为**内容语义**("刚刚 / 今天 / 暂时 / 临时" → 临时;"我朋友常去 / 老地方 / 我家 / 我公司 / 重要 / 永久" → 永久),不再以 has_location 作为信号
559
+ - 给了双向 examples:transient 案例 vs permanent location 案例,让 agent 看清差异
560
+ - 强化 "When ambiguous, ASK" 指引
561
+
562
+ 2. **时间没有时区,agent 按 UTC 计算 "明天下午3点" 会偏 8 小时**。统一按 **Asia/Shanghai (UTC+8)** 存储,bare 本地格式 `YYYY-MM-DD HH:MM:SS`:
563
+ - 新 helpers `nowSqliteLocal()` / `toSqliteLocal(ms)` / `normalizeWhenAt(s)`
564
+ - `createMemo` / `updateMemo`:JS 显式设 `created_at` / `updated_at` (UTC+8);agent 传的 `when_at` / `expires_at` 走 `normalizeWhenAt` 兜底(含 `Z` / `+00:00` / `+08:00` 都正确归一)
565
+ - `searchMemos` 的 expiry 过滤 + `whenAfter` / `whenBefore` 都用 `datetime('now', '+8 hours')` 与存储 shape 对齐
566
+ - `sweepExpired` 同理
567
+ - `expiresAtFrom(hours)` 改用 `toSqliteLocal(now + ms)` 而不是 `.toISOString()`
568
+ - schema 里 `created_at` / `updated_at` 默认值改为 `datetime('now', '+8 hours')`(兜底,正常路径 JS 会显式设)
569
+ - tool description 里 `when_at` / `when_after` / `when_before` 都说明:**Asia/Shanghai (UTC+8),"YYYY-MM-DD HH:MM:SS",不要带 T 也不要 Z**
570
+ - 11 新增单测覆盖:nowSqliteLocal 形状 / toSqliteLocal +8h shift / normalizeWhenAt 各路径(Z / +08:00 / 已 local / null / unparseable)/ created_at 实际写入 UTC+8 / sweep 用 UTC+8 比较
571
+ - 时区可调:`IMHUB_TZ_OFFSET_HOURS` env,默认 8(其他时区部署只改这一个就行)
572
+
573
+ ### Changed — opencode agent 系统提示也加上 memo 工具
574
+ opencode http adapter 的 `buildImContextInstruction` system prompt 之前只提了 reminder 那 4 个工具,现在加上 memo 那 5 个 (`save_memo` / `search_memos` / `update_memo` / `delete_memo` / `request_location_capture`) 一起列出,并补一段 timezone 说明。这样 opencode 通过 HTTP daemon mode 跑时也知道要带 `_im_context` + 用 UTC+8。
575
+
576
+
577
+ ### Changed — `/location` 重命名为 `/memo` + 数据模型升级到 5W1H 持久记忆库
578
+ **这是个产品级别的概念升级**:从"位置备忘"扩展到通用的"持久记忆数据库"。位置只是其中一种类型——同一个工具也能记 "爸爸的生日是5月8日"、"明天下午3点开会"、"伞放前台了"、"苹果发了 AVP2"。
579
+
580
+ - **数据模型**:`location_memos` 表整体替换为 `memos` 表,每行携带 5W1H 可选字段 — `what` (必填,核心事项) / `who` / `when_at` / `when_text` / `where_lat` / `where_lng` / `where_label` / `how` / `why`,加 `expires_at` 生命周期列(NULL = 永久)+ `source` + `created_at` + `updated_at`
581
+ - **slash 命令**:`/location` 整体下线,改为 `/memo`(别名 `/记` `/note`)。`/memo list/show/delete/here/help` 子命令保留,新增 `/memo search <query>` 直接搜,无子命令的纯文本也走 search
582
+ - **MCP 工具组**(agent-driven):4 个旧 location 工具替换为 5 个 memo 工具——
583
+ - `save_memo({what, who?, when_at?, when_text?, where_*?, address?, how?, why?, expires_in_hours?})` — 通用落库;agent 从用户自然语言抽 5W1H
584
+ - `request_location_capture({what?, memo_id?})` — 一次性 GPS 链接。可选 `memo_id` 触发 UPDATE 已有 memo 的 `where_*`(之前只能 CREATE)
585
+ - `search_memos({query?, who?, what?, has_location?, when_after?, when_before?, include_expired?, limit?})` — 多字段 AND-combined 检索
586
+ - `update_memo({id, ...fields})` — patch 模式,null 清空 / undefined 不动
587
+ - `delete_memo({id})`
588
+ - **Tool description prompt 工程**:每个工具描述给 3-5 个具体例子("我爸爸的生日是5月8日" → save_memo 拆出 what/who/when_text);明确 `expires_in_hours` 的判断指引(含"暂时/临时/今天"等关键词→24h,"永久/记住/总是"等关键词→不传);明确告诉 agent 用 markdown 链接 `[百度地图](url)` 格式回复
589
+ - **生命周期分层**:默认所有 memo 永久;agent 判定为临时性内容(停车、当天会议)才 set `expires_in_hours`。后台每 5 分钟跑一次 `sweepExpired()` DELETE `expires_at <= now` 的行
590
+ - **token meta 升级**:`note` → `what`,新增 `memoId` 字段。POST `/api/loc` 拿 token 时根据 memoId 决定 CREATE 新 memo 还是 UPDATE 已有 memo 的 `where_*`
591
+ - **H5 页**:根据 token 是 create 还是 augment 模式显示"正在记录: <what>"或"正在补充'<existing what>'的位置"
592
+ - **wire-protocol 升级**:approval-bus 上 `type: 'location'` / `'location.result'` → `type: 'memo'` / `'memo.result'`;ApprovalClient 的 `requestLocation` → `requestMemo`
593
+ - **数据迁移**:用户决定不迁移老 `location_memos` 表数据(从 0 重新开始)。老 `locations.db` 文件留在磁盘上但不再被打开
594
+ - 13 单测 `memos.test.ts` (含 SQLite-skipped CRUD 9 条) + 7 单测 `memo-rpc.test.ts` (含 9 个 SQLite-skipped agent-flow case)
595
+
596
+
597
+ ### Added — H5 地图预览 + 拖拽微调
598
+ - `loc.html` 加载 Leaflet + OpenStreetMap 瓦片(无需密钥、免商业授权)。授权 GPS 后展示当前坐标的地图视图,初始 marker 放在浏览器返回的位置上,外圈叠一层蓝色 accuracy 圆圈让用户直观感受 ±N米的不确定性。
599
+ - **微调操作**:长按 marker 拖动、或点击地图任意位置 marker 跳过去;坐标框实时更新。
600
+ - 新两按钮:`✅ 确认提交这个位置`(POST 当前 marker 坐标)/ `🔄 重新获取定位`(清除 marker,回到第 1 步重抽 GPS——某些情况第二次精度更高)。
601
+ - **优雅降级**:CDN 屏蔽 / Leaflet 加载失败时(`typeof L === 'undefined'`),地图区隐藏 + 提示文案改成"网络限制?无法微调",但同一个"确认"按钮仍可直接提交浏览器抓的原始坐标。
602
+ - 三步卡片视觉:`获取位置` → `地图微调` → `已发送`,布局清晰,每一步只露一个 primary action。
603
+
604
+ ### Changed — Token 短链
605
+ - Token 从 32 hex char (16 bytes) 缩到 12 char base64url (9 bytes random,~10^16 keyspace;single-shot + 10min TTL 下绰绰有余)
606
+ - URL 路径从 `/loc?t=<32hex>` 改为 `/l/<12base64url>`,总长度 70+ char → ~38 char。微信里展开占 1 行而非 2 行
607
+ - `/loc?t=` 老路径同时保留——已发出去尚未消费的 token 不会断
608
+ - `loc.html` 同时识别两种 URL shape,自动降级
609
+
610
+ ### Added — 三家地图任选(百度 / 高德 / Google)
611
+ - 用户在 IM 里收到的位置消息现在同时给出 **百度 / 高德 / Google** 三条地图链接,根据自己习惯任选——之前默认只挂百度
612
+ - 新增 `mapUrls(lat, lng, label?, addr?)` 工具:返回 `{ baidu, amap, google }`,三家分别对接:
613
+ - 百度:`api.map.baidu.com/marker` + `coord_type=wgs84`(继承之前的实现)
614
+ - 高德:`uri.amap.com/marker` —— **必须把 WGS-84 → GCJ-02 转换后再发送**(高德内部用 GCJ-02,参数 `coordinate=gaode` 提示)
615
+ - Google:`google.com/maps?q=lat,lng`(WGS-84 原生,直接透传)
616
+ - 新增 `wgs84ToGcj02(lat, lng)`:标准的"中国加密偏移"转换公式(A=6378245.0, EE=0.00669342162296594323),中国大陆境内偏移 ~300-700m,境外(HK/MO/TW/海外)原样返回
617
+ - 调整三处用户可见输出:
618
+ - `POST /api/loc` 推回的"✅ 已记录" 消息列出 3 条链接
619
+ - `/location show <id>` 命令详情列出 3 条
620
+ - `mcp__imhub__save_location` 和 `list_locations` 工具返回结构加 `mapUrls: { baidu, amap, google }` 字段;旧 `mapUrl`(百度)保留向后兼容
621
+ - MCP 工具描述告诉 agent 优先把三条都展示给用户挑
622
+
623
+ ### Changed — 地图服务从腾讯切到百度
624
+ - **地理编码**:`apis.map.qq.com/ws/geocoder/v1/` → `api.map.baidu.com/geocoding/v3/`,强制 `ret_coordtype=wgs84ll`(百度默认返 BD-09,必须显式拉 WGS-84 才和浏览器 `navigator.geolocation` 一致——否则存库坐标 vs 浏览器坐标 50–500m 飘移)
625
+ - **凭据 env**:`IMHUB_TENCENT_MAP_KEY` / `IMHUB_TENCENT_MAP_SK` → `IMHUB_BAIDU_MAP_AK`(默认 key 流程不需要 SN 签名;如以后开启 SN 校验,文档说明在 `src/core/locations.ts` 头注释里)
626
+ - **地图 URI scheme**:默认从 `apis.map.qq.com/uri/v1/marker?...` 改为 `api.map.baidu.com/marker?location=lat,lng&coord_type=wgs84&...`(注意 `coord_type=wgs84` 关键,否则百度按 BD-09 解读飘 50–500m)
627
+ - 错误码分类重写:百度状态码体系不同(200/201/202 = 签名 / Referer / IP;401-405 = 配额;1-4 = 找不到/参数错;5/210/211/220/240 = 各种权限 / 配额家族)
628
+ - **腾讯 SK 签名代码移除**:`signTencent` 函数 + 4 条相关单测删除。Baidu 默认 key 流程不需要签名;如以后开启 SN 校验,算法是 `MD5(URLencode(path?qs+sk))`(与腾讯的 `MD5(path?raw_qs+sk)` 不同 —— 多一步整体 URL 编码),实现可参考头注释
629
+ - 用户消息文案更新:错误提示里 `lbs.qq.com` → `lbsyun.baidu.com`
630
+ - **不影响**:已有的 `location_memos` 数据(坐标统一是 WGS-84,不需要迁移);坐标输入路径(`/location 39.9,116.4`);H5 浏览器 geolocation 流程
631
+
632
+ ### Added — Location MCP 工具(agent-driven 自然语言交互)
633
+ - 新增 4 个 MCP 工具,agent(claude-code / opencode)按需调用——**不加 detector,不拦截消息流**。直接利用 agent 已有的语义理解:
634
+ - `request_location_capture(note?)`:用户想记当前 GPS("我把车停这"),返回一次性 H5 URL 给 agent 念给用户
635
+ - `save_location({ lat, lng } | { address }, note?)`:agent 已有坐标或地址时直接落库(绕过 H5);address 路径走腾讯地图地理编码
636
+ - `list_locations(query?, limit?)`:用户问"我的车在哪"时调,模糊匹配 label + note,按时间倒序返回
637
+ - `delete_location(id)`:用户说"删了车的记录"
638
+ - 数据流(NL 记录 + NL 查询):
639
+ ```
640
+ 用户 "帮我记下车停这了" → claude 看到 4 个工具
641
+ → claude 调 request_location_capture({note:'车'})
642
+ → bus → location-rpc → 发 token + 返 URL
643
+ → claude 回用户 "好的,点开链接授权: <url>"
644
+ → 用户点 → H5 显示 "正在记录: 车" → 授权 → POST → 写库
645
+ → im-hub fire-and-forget 推回 "✅ 已记录'车' lat,lng [地图]"
646
+ ```
647
+ ```
648
+ 用户 "我的车在哪" → claude 调 list_locations({query:'车'})
649
+ → bus → location-rpc → SQL LIKE → 返结果
650
+ → claude 自然语言回 "🚗 你 5 小时前记录的车: ..."
651
+ ```
652
+ - 实现:
653
+ - 新建 `src/core/location-rpc.ts` — 4 op handlers,与 `reminder-rpc.ts` 结构对齐
654
+ - `src/core/locations.ts` 加 `searchMemos(opts)`,SQL LIKE ESCAPE 处理 `%` `_` 字面量
655
+ - `src/core/location-token.ts` 加 `note` 字段 + `peekToken()`(只读不消费)
656
+ - `src/web/server.ts` 加 `GET /api/loc/info?t=…` 让 H5 拿 server-stored note 显示
657
+ - `src/web/public/loc.html` 加 "正在记录: <note>" 蓝色信息条
658
+ - `src/core/approval-bus.ts` 加 `type:'location'` dispatcher,复用 runId/`_im_context` 双路径
659
+ - `src/plugins/agents/claude-code/mcp-approval-server.ts` 加 `requestLocation` client + 4 工具注册(claude-code 和 opencode 共享同一个 sidecar 二进制,自动可见)
660
+ - 同时把 `POST /api/loc` 改为读 token 中的 server-side note 作为 memo label(防 URL 篡改)
661
+ - 18 单测 `location-rpc.test.ts`(含 9 个 SQLite-skipped CRUD case)+ 现有 `location-token.test.ts` 7 条全过。
662
+ - **架构权衡**:放弃了 `remind-intent.ts` 那种"消息流前置正则探测器"模式。reminder 的 detector 是历史遗留,每条消息都跑一次额外 LLM 调用(就是修过 80s→3s 那个性能问题的祸根)。location 直接让 agent 看工具描述自己判断,零额外 LLM 调用、零误触代价、对话上下文连续。
663
+
664
+ ### Added — `/location here` 浏览器 GPS 捕获
665
+ - 新子命令 `/location here [备注]` 或 `/location 当前 [备注]`:bot 回一个一次性 HTTPS 链接,用户在 WeChat 内置浏览器点开,授权后 H5 页面调 `navigator.geolocation.getCurrentPosition` 把坐标 POST 回 im-hub,im-hub 写库 + 推回 IM 线程。
666
+ - 数据流:`/location here` → 内存 token store 发 32 hex token (10 min TTL,single-shot) → 用户点 URL → `GET /loc?t=...` 渲 H5 → 用户授权 → `POST /api/loc { t, lat, lng, accuracy }` → 验 token → 写 `location_memos`(source=`browser`)→ messenger.sendMessage 把"✅ 位置已收到 + 地图链接"回推到原线程。
667
+ - **默认 base URL**:`https://agent.iclaw.host`(公共托管入口)。自托管用户可设 `IMHUB_LOC_BASE_URL=https://your-host` 覆盖。OSS 用户没有 HTTPS endpoint 时可继续用坐标输入路径。
668
+ - 新建 `src/core/location-token.ts`:内存 Map,60s 一次的 sweep timer 清过期;test/diagnostic 钩子 `_pendingCount` `_clearAll` `_stopSweep`。
669
+ - 新建 `src/web/public/loc.html`:单页 H5(无构建依赖、纯内联 CSS+JS),蓝主题与 imhub-site 视觉一致。错误分支覆盖:缺 token / 浏览器不支持 / 用户拒绝授权 / 网络错误。
670
+ - 新增 `src/web/server.ts` 公开路由 `GET /loc` + `POST /api/loc`,放在 auth gate 之前(token 即凭证)。`POST /api/loc` 失败时分类回 410(链接过期 / 已使用)/ 400(参数错);写库/dispatch 是 fail-soft,不阻塞 H5 端 200 响应。
671
+ - **agent.iclaw.host 部署**:路由复用现有 :3000,CDN 必须配路径白名单只放行 `/loc` 与 `/api/loc`,其他路径(`/tasks` `/reminders` `/api/*`)必须在 CDN 层 404,否则操作员控制台会被外网暴露。
672
+ - 7 单测 `location-token.test.ts`:issue/consume 圆环、单次消费、过期回收(Date.now stub)、多 token 独立、跨用户隔离。
673
+
674
+ ### Added — `/location` 位置备忘(基础版)
675
+ - 新 slash 命令 `/location`(别名 `/loc` `/位置`):记录"在 X 位置干了 Y 事"型备忘。
676
+ - **零依赖坐标输入**:`/location 39.908,116.397 [备注]` —— 支持 `,` `,` 或空格分隔,负数支持。
677
+ - **可选地址解析**:`/location 国贸三期 [备注]`,需配置 `IMHUB_TENCENT_MAP_KEY`([lbs.qq.com](https://lbs.qq.com) 注册免费 key)。无 key 时给友好错误提示,引导用户用坐标路径。
678
+ - **腾讯地图 SK 签名支持**:开启了"WebServiceAPI 强制签名校验"的 key 配 `IMHUB_TENCENT_MAP_SK`,自动 MD5 签名。算法:参数按 key 字典序排序、用未 URL-encode 的原值拼 `k=v&...`、与 `path?qs+SK` 整体 MD5 → 32 位 lowercase hex。已对照官方 API 实测验证。
679
+ - **细分错误反馈**:地理编码失败按状态码分类(`signature`/`quota`/`not_found`/`http`/`unknown`),命令直接告诉用户是配额满了还是签名错了或地址不对。
680
+ - 子命令:`list [N]` / `show <id>` / `delete <id>` / `help`,全部 per-user owner-scoped(同 `/remind` `/cron` 模式)。
681
+ - **地图链接回显**:每条记录附 `https://apis.map.qq.com/uri/v1/marker?...` URI,WeChat 内置浏览器/地图 App 可直接唤起。
682
+ - 数据:`~/.im-hub/locations.db`(路径可通过 `IMHUB_LOCATIONS_DB` 覆盖),fail-soft via 现有 `sqlite-helper`。
683
+ - 18 单测:坐标解析(中文逗号 / 负数 / 范围校验)、地图 URL 构建、地理编码 env 探测、SK 签名(含 reference fixture)、CRUD 全流程 + owner scoping。
684
+ - **不包含**:浏览器 geolocation 链接(Phase 2,要求 HTTPS endpoint),LLM 自然语言识别(Phase 3)。
685
+
686
+ ## [0.2.41] - 2026-05-10 — 移除 WeChat 思考中占位气泡
687
+
688
+ ### Removed
689
+ - **WeChat `🤔 思考中…` 占位气泡**:原本在 cli.ts:591 调 `messenger.sendThinking` 给 wechat 发的 placeholder。理由:iLink 不支持 recall,气泡永久留在聊天里污染历史;原生 typing 指示器(`sendTyping`)已 1.5 个月稳定运行;remind-intent perf 修复后平均回复延迟 80s → 3s,"以为机器人挂了"的窗口几乎消失;CRLF 修复也让真实回复的视觉强度足够。改动:删 `wechat/ilink-adapter.ts:sendThinking`,cli.ts 已用 `if (messenger.sendThinking)` 自动 fallback。Feishu 保留(其 sendTyping 是 no-op,气泡是唯一信号)。
690
+
691
+ ## [0.2.40] - 2026-05-10 — 只读工具自动放行 + WeChat 换行修复
692
+
693
+ ### Added — 只读工具自动放行(默认开启)
694
+ - 新增 `READONLY_TOOLS` 短路:`Read` / `Grep` / `Glob` / `LS` / `NotebookRead` 这 5 个 Claude Code 原生只读工具的审批请求在 `approval-bus._registerPending` 入口被直接放行——不建 pending、不通知、不写 IM 卡片。零 IM 往返,给 LLM 自由探索代码而不打扰用户。
695
+ - 全局开关:`IMHUB_AUTOALLOW_READONLY=0`(默认 1)关闭。
696
+ - per-thread 开关:`/approval readonly off|on`(别名 `/approval ro`)。仅本会话内存生效,重启后回到全局默认。
697
+ - 审计:每次短路写一行 `approval.bus.readonly_auto_allow` info log,并在 metrics 新增 `totalReadonlyAutoAllowed` 计数。
698
+ - `/approval`(无参)和 `/approval readonly` 都会显示当前状态行 `🛡 只读自动放行:开启 ...`。
699
+ - 保守白名单:**不**包含 `Bash`(无法静态判断 `cat` vs `rm`)、`WebFetch`/`WebSearch`(出站 HTTP)、任何 `mcp__*`(第三方 MCP 工具语义未知)。这些走原审批流程不变。
700
+ - 测试:`approval-bus.readonly.test.ts` 11 条(每个 readonly 工具 + 5 条边界)+ `approval.test.ts` 4 条新 case。
701
+
702
+ ### Fixed
703
+ - **WeChat 文本换行渲染**:`ILinkClient.sendMessage` 把 outbound 文本里的 `\n` 替换为 `\r\n`。iLink Bot API 要求 CRLF;只发 LF 时部分微信客户端会吞换行,多行消息渲染成单段。
704
+
705
+ ## [0.2.39] - 2026-05-10 — WeChat 回复延迟修复 + `/help` alias 解析
706
+
707
+ ### Fixed
708
+ - **WeChat 回复延迟(remind-intent 误触)**:v0.2.37 引入的 reminder 意图识别在 cli.ts 上每条非 slash 消息都跑一次预过滤。预过滤正则里有一组独立的"强意图动词"分支 `(提醒|通知|叫我|喊我|告诉我|记得|别忘了|提醒我)` —— **不要求带时间词**。日常会话里 `告诉我XXX`/`通知XXX`/`记得XXX` 都中招,触发一次 5–10s 的 helper-LLM 调用 `await` 在真正的 agent 回复之前,最终用户感知"每条消息都慢 5–10s"。修复:删掉这个动词分支。下游 LLM 没有时间词也产不出 `fire_at_iso`(remind-intent.ts:164 会拒),所以这一分支本来就是 100% 浪费。真正想设提醒的自然表达都带时间词(`明天9点提醒我开会`),仍能命中其他分支。新增 5+4 条测试覆盖。**实测**:WeChat 平均回复 ~80s/avg → 3s/avg。
709
+
710
+ ### Changed
711
+ - **`/help <alias>` 自动解析**:`/help tasks` `/help check` `/help cancel` `/help switch` `/help collect` 都解析到 `/help job`;`/help auto` → `approval`;`/help models` → `model`;`/help ws` → `workspaces`;`/help reminder` → `remind`。alias 表 `TOPIC_ALIAS` 与 dict 解耦,避免维护重定向字符串。
712
+ - 同时清理 `EN_TOKENS` / `ZH_TOKENS` 里早期防御代码遗留的 `'english:'` `'cn:'` 噪音 token。
713
+
714
+ ## [0.2.38] - 2026-05-10 — `/schedule` → `/cron` 命名空间清理 + `/help` 改造
715
+
716
+ ### Changed — `/help` / `/start` i18n + 重写
717
+ - **默认中文**(与 `/remind` `/cron` `/audit` 等其它命令的中文文案保持一致),`/help en` / `/start en` 切英文。
718
+ - **补全所有遗漏命令**:原本只有 ~10 条,现在覆盖 `/cron` `/remind` `/job` `/tasks` `/check` `/cancel` `/switch` `/collect` `/model` `/models` `/think` `/plan` `/approval` `/auto` `/audit` `/stats` `/sessions` `/workspaces` `/router` 等。
719
+ - **按分类分区块**:会话 / 提醒 · 定时 / 任务 / Agent 控制 / 工具审批 / 运维 / Agent 子命令。
720
+ - **`/help <topic>` 子命令**:`/help remind`、`/help cron`、`/help job`、`/help approval`、`/help workspaces` 等都返回该命令的详细帮助;`/help remind` 直接复用 `/remind help`。
721
+ - **`/?` 别名 `/help`**。
722
+ - 实现:`ParsedMessage` 的 `command` 变种新增可选 `args`,router 把 `/help` `/start` 后面的内容当 args 透传。
723
+
724
+
725
+ ### Changed
726
+ - **`/schedule` 重命名为 `/cron`**——避免和 Claude Code 内置 `/schedule` skill(Anthropic 云端 routines)撞名导致 LLM 误解。
727
+ - 主名:`/cron list|create|delete|enable|disable|check`,语义和老 `/schedule` 完全相同。
728
+ - 老 `/schedule` 仍能用,但首行返回 `⚠️ 已重命名为 /cron` 迁移提示。计划在 **v0.4.0** 移除 `/schedule` 别名。
729
+ - 引擎模块名(`src/core/schedule.ts`)和 REST 端点(`/api/schedules`)保持不变——纯命令面 rename,不破坏历史数据 / 外部集成。
730
+ - 文件移动:`src/core/commands/schedule.ts` → `src/core/commands/cron.ts`;导出函数 `handleScheduleCommand` → `handleCronCommand`;`ParsedMessage` 联合类型 `{ type: 'schedule' }` → `{ type: 'cron'; legacy?: boolean }`。
731
+ - README / Web tasks 页文案同步更新。
732
+
733
+ ### Migration
734
+ - 用户层:把 `/schedule …` 改成 `/cron …` 即可;脚本里如果硬编码 `/schedule` 也能继续工作直到 v0.4.0。
735
+ - 集成层:`/api/schedules` REST 接口、`schedules` SQLite 表、`schedules.db` 文件路径均不变。
736
+
737
+ ### Fixed
738
+ - **schedules.db 自举顺序**:`idx_sched_creator` 索引从初始化 schema 字符串里挪到 `migrateOwnership()` 之后。原先在 0.2.0 之前建的库升级到 0.2.x 时,`db.exec(schema)` 会因为 `creator_id` 列不存在直接抛错,被 helper 标记为 broken,导致后续的 `ALTER TABLE` 迁移永远跑不到——`/cron` / `/schedule` 整个子系统于是默默失效。修了这次老库会自动补列再建索引。
739
+
740
+ ## [0.2.37] - 2026-05-10 — Reminders + Email + Multi-agent MCP
741
+
742
+ > 设计文档:`docs/architecture/reminders.md`
743
+
744
+ ### Added — `/remind` 子系统
745
+
746
+ **核心**
747
+ - `/remind` slash command(一次性 + 定期 + 邮件 + 字面/LLM 模式)
748
+ - 时间解析:中文 NL 时长 / 时点 / 循环表达式(每天 N 点 / 每周 X / 工作日 / 周末 / 每隔 N),英文 / HH:MM / ISO
749
+ - 5 秒 tick 调度器;崩溃恢复(`firing → pending`);7 天 fireAt 上限;50 条 / 用户配额
750
+
751
+ **LLM 增强(默认开启)**
752
+ - **F1 LLM 意图识别**:非 slash 消息走 detector,时间词正则预过滤后调当前 thread 的 active agent 抽 JSON。置信度 ≥ 0.85 弹"要不要为这件事设个提醒?"卡片,y/n 确认。可 `/remind aiwatch off` 关
753
+ - **F2 LLM 投递润色**:fire 时把字面文本作为 prompt 喂给当前 agent 重写成自然投递语(系统约束:"不献媚、不过度幽默、不夸大",≤ 80 字)。20s 超时 fallback 字面。`/remind literal …` 强制字面投递。每 N 分钟(< 1h)+ LLM 模式被拒绝(防止高频 LLM 调用)
754
+ - **Agent context snippet**:每次 agent 调用前在 prompt 前注入 ≤ 5 条 pending 提醒摘要,agent 不用主动 list_reminders 就能看到现状
755
+
756
+ **Agent MCP 工具(提醒兜底创建)**
757
+ - 4 个工具:`create_reminder` / `list_reminders` / `cancel_reminder` / `snooze_reminder`
758
+ - claude-code:复用现有 `--mcp-config` per-spawn 注入
759
+ - opencode (stdio):`prepareCommand` 注入 `IMHUB_RUN_ID` extraEnv,全局 `mcp.imhub` config 自动注册
760
+ - opencode (http):常驻 daemon 共享 MCP server,agent-asserted `_im_context` 兜底(**单用户限定**——agent 可伪造身份)
761
+ - codex:未实施(`codex exec` 不支持外部 MCP)
762
+
763
+ **Web 管理**
764
+ - `/reminders` 页面(dark mode、状态筛选、取消、延后)
765
+ - 3 个 REST endpoint:`GET /api/reminders`、`POST /api/reminders/:id/cancel`、`POST /api/reminders/:id/snooze`
766
+
767
+ ### Added — 邮件投递通道
768
+ - 注册为 `email` messenger(`threadId` 即邮箱地址)
769
+ - nodemailer + Gmail / QQ / 163 / 任意 SMTP(env:`IMHUB_SMTP_HOST/PORT/USER/PASS/FROM/SECURE`)
770
+ - `/remind email <addr> <time> <text>` 内联 / `/remind bindemail` 绑定默认 / `/remind email <time> <text>` 走默认
771
+ - 邮件 + 循环 + LLM 润色组合自由
772
+
773
+ ### Added — WeChat context_token 持久化
774
+ - iLink Bot API 30 分钟 context_token TTL 之前只存内存,重启即丢,导致循环提醒在 wechat 端高概率失败
775
+ - 新 SQLite 表(`~/.im-hub/wechat-context.db`),收消息时写穿,启动时 warm 内存 Map,cleanup 同步清 DB
776
+
777
+ ### Schema migrations
778
+ 新建 / 扩展三张表(在 `reminders.db` 内):
779
+ - `reminders` 主表:`recurrence` `last_fired_at` `prompt_mode` 三列追加(idempotent ALTER)
780
+ - `user_email_bindings`
781
+ - `user_aiwatch_settings`
782
+
783
+ 新独立 SQLite:`wechat-context.db`
784
+
785
+ ### Tests
786
+ 新增 7 个测试文件,~50 个新 case:`agent-helper`、`pending-reminder`、`remind-intent`、`reminder-rpc`、`email-adapter`、`wechat-context-store`、扩展的 `reminders.test.ts`。bun test 1033 pass / 34 skip / 0 new fail。
787
+
788
+ ### Dependencies
789
+ - `nodemailer ^8.0.7`
790
+
791
+ ### Known limitations
792
+ - opencode http 多用户场景不安全(`_im_context` 可伪造);多用户部署应选 stdio 或 claude-code
793
+ - `/api/reminders` 无 per-user owner-scoping(单运营者部署)
794
+ - codex 暂无 MCP reminder 工具(重写需 ~5h,下一轮专项)
795
+ - F1 detector 时区跟随 server `toLocaleString()`
796
+
797
+ ---
798
+
799
+ ## [0.2.35] - 2026-05-09
800
+
801
+ ### Added — WeChat & Telegram rich media support
802
+
803
+ - **WeChat 图片 / 文件 / 语音 / 视频接收**:`handleIncomingMessage` 现在处理 iLink `item_list` 的全部 5 种类型(TEXT=1, IMAGE=2, VOICE=3, FILE=4, VIDEO=5),此前只处理 TEXT。
804
+ - **图片**:通过 iLink CDN 下载到 `~/.im-hub/media/wechat/<userId>/`,转为 `[图片附件:/path]` 标记传递给 Agent
805
+ - **语音**:优先使用微信自带转写(`voice_item.text`),无转写时下载音频后走 OpenAI Whisper 或 whisper.cpp(复用 `src/core/transcribe.ts`)
806
+ - **文件**:下载并保留原始文件名,显示大小,转为 `[文件附件:/path (name, size)]`
807
+ - **视频**:下载并显示时长和大小,转为 `[视频附件:/path (duration, size)]`
808
+ - **混合消息**:同一条消息中的文本 + 媒体项按顺序拼接
809
+ - **Telegram 图片 / 语音增强**:Telegram 适配器的图片下载和语音转写能力在本轮中同步维护和 bugfix。
810
+ - **新文件**:`src/plugins/messengers/wechat/media-download.ts` — 媒体下载辅助,含路径安全检查(拒绝 `../` / `/`)和 20 MB 大小上限
811
+ - **新类型**:`ilink-types.ts` 新增 `ITEM_TYPE` 常量和 `DownloadMediaRequest`
812
+ - **新方法**:`ILinkClient.downloadMedia(cdnMedia)` — 通过 iLink CDN 端点下载二进制媒体
813
+
814
+ ### Tests
815
+
816
+ - 53 个新测试:
817
+ - 31 集成测试(`test/integration/wechat-media-integration.test.ts`)— 端到端验证 `handleIncomingMessage` 对所有媒体类型的处理,含成功 / 失败 / 边界情况
818
+ - 15 单元测试(`test/unit/wechat-media-download.test.ts`)— 下载辅助函数、扩展名推断、CDN 提取、安全防护
819
+ - 8 单元测试(`test/unit/wechat-media-handling.test.ts`)— 类型常量和消息结构验证
820
+ - 全量 950 pass / 0 fail
821
+
822
+ ---
823
+
824
+ ## [0.2.33] - 2026-05-09
825
+
826
+ ### Fixed — audit-fixes-7950 follow-up
827
+
828
+ 稳定 0.2.32(v0.2.32 的 10 项审计修复在合入时引入 37 个失败测试 + 一处运行时回归,0.2.33 把这些都修掉,建议直接从 0.2.31 升到 0.2.33)。
829
+
830
+ - **审批总线 composite key 测试同步**:P0 #4 把 `ApprovalBus` 的内部 keying 从裸 threadId 改成 `platform:channelId:threadId`,但单元测试仍然按裸 `'thread-A'` 调 `hasPendingFor` / `resolvePending`,导致 `bun test` 红 37 项。所有相关测试(approval-bus、synthetic、router、approval 命令、mcp-approval、opencode-http、discord)改用 `threadKey()` 显式构造 composite,注释中误导性的 "backward compat" 承诺一并删除。
831
+ - **Claude session 标记顺序回归**:P1 #5 把 `markClaudeSessionPrimed` 从 `routeMessage` 之前挪到了之后;如果 claude 已写出 jsonl 但 run 中途出错,下一轮就会用 `--session-id` 而不是 `--resume`,触发 "session already exists"。0.2.33 把 markPrimed 移回 `onAgentResolved` 内部、紧跟 `setClaudeSessionId`,恢复"先 mark 再调 adapter"的语义。
832
+ - **Cookie 加固**:`web.public_bind_warning` 改在 server 启动时打一次(之前每次登录都打);`Set-Cookie` 在 public bind 或 `X-Forwarded-Proto: https` 时自动附加 `Secure`,纯 localhost dev 不加。
833
+ - **静态页测试改写**:原 `/tasks` 用例还在断言 `IMHUB_TOKEN` 注入到 HTML,改成走真实流程:未登录 302 到 `/login`,POST `/api/auth/login` 拿 cookie 后 200,断言 HTML 中不再含 token。
834
+ - **死代码清理**:`editCardOnThreadResolution` 的 thread-scan fallback 在 composite key 下永远 miss,删掉;签名从 `(threadId, info, decision, by)` 缩为 `(info, decision, by)`。
835
+ - **小项**:`web/server.ts` 删未使用的 `lstat` import;`settings.html` toast 调用末尾被误删的 `;` 补回。
836
+ - **测试 flake 修复**:`test/integration/acp-server.test.ts` 在全套并发下被 `intent.test.ts` 注册的空-yield 子代理污染(router intent fallback 到 opencode → 返回 '')。ACP 测试在 `beforeAll` 重新注册 4 个 profile 名同名 stub 让其也产出内容。
837
+
838
+ ### Result
839
+
840
+ - `bun test`:895 / 895 pass(之前 857 / 37)
841
+ - `tsc --noEmit` / `biome` 干净
842
+
843
+ ---
844
+
845
+ ## [0.2.31] - 2026-05-08
846
+
847
+ ### Release
848
+
849
+ - 重新发布生产硬化修复到 npm,确保生产环境通过 `npm install -g im-hub-pro@0.2.31` 可以拿到 v0.2.30 严格代码审查后的全部修复。
850
+ - 包含子任务会话隔离、WebSocket 入站串行化、session 元数据写锁、安全默认值加固、release/CI 门禁收紧,以及 `im-hub-pro --version` 跟随 `package.json` 的修复。
851
+
852
+ ---
853
+
854
+ ## [0.2.30] - 2026-05-08
855
+
856
+ ### Fixed — strict production code review follow-up
857
+
858
+ - **子任务会话隔离**:active subtask 下的普通消息与 `/test` / `/review` / `/diff` 等 agentCommand 现在都会写入 `${threadId}:sub:${jobId}` 子会话,不再把用户消息、assistant 回复、usage 和 adapter-native session id 混写到父会话。
859
+ - **native Agent session 隔离**:Claude Code / OpenCode / Codex 在子任务内使用子会话自己的 `claudeSessionId` / `opencodeSessionId` / `codexSessionId`;避免主会话与子任务在 CLI 原生会话层串线。
860
+ - **WebSocket 入站串行化**:Web Chat 同一浏览器连接上的多条消息按顺序逐条进入 `routeMessage`,降低快速连发时 Agent 子进程并行、历史乱序和流式回复交错风险。
861
+ - **Session 元数据写锁**:`patchSession`、usage roll-up、Claude/OpenCode/Codex session id setters、subtask counter/update、agent switch/reset 等读改写路径纳入 per-key lock;`nextSubtaskId` 并发调用保持唯一递增。
862
+ - **安全默认值**:
863
+ - Web 控制台默认监听 `127.0.0.1`;需要对外暴露时显式设置 `IMHUB_WEB_BIND=0.0.0.0`,建议放在 HTTPS 反代后。
864
+ - `~/.im-hub/config.json` 写入时强制 `0600`,目录创建使用 `0700`。
865
+ - Telegram 媒体下载的 `curl` 保留 `--location` 但加 `--max-redirs 0`,拒绝跟随重定向到任意主机。
866
+ - **CLI 版本号**:`im-hub-pro --version` 改为读取包内 `package.json`,不再硬编码旧值(之前可能输出 `0.2.18`)。
867
+
868
+ ### Build / release
869
+
870
+ - Release workflow 中 `npm test` 失败不再 `continue-on-error`,避免测试红仍发布 npm。
871
+ - CI 增加 `npm run lint`;补齐 `@biomejs/biome` devDependency。
872
+ - `lint` 当前只检查相对 `main` 的变更文件,避免仓库既有 Biome 基线问题一次性阻断所有 PR。
873
+
874
+ ### Tests
875
+
876
+ - 新增/补齐回归覆盖:active subtask 默认消息与 agentCommand 路由、并发 `nextSubtaskId`、Telegram curl redirect 参数、`config.json` 权限、WebSocket serial queue。
877
+
878
+ ---
879
+
880
+ ## [0.2.26] - 2026-05-08
881
+
882
+ ### Changed — Rebrand to `im-hub-pro`
883
+
884
+ - **npm 包名**:`im-hub` → `im-hub-pro`(产品化分支)。
885
+ - **CLI 命令**:`im-hub` → `im-hub-pro`(`bin` 字段同步更新)。
886
+ - **仓库 URL**:`package.json.repository.url` 从原作者 `ceociocto/im-hub` 切到当前仓库 `benking007/imhub`,新增 `homepage` / `bugs` / `publishConfig{access:public,provenance:true}` 字段。
887
+ - **Web 控制台品牌**:`/`、`/tasks`、`/settings` 三个静态页的浏览器标题、欢迎语、左上角 logo、Settings 卡里的 `im-hub config wechat` 提示文案,全部改为 `im-hub-pro`。
888
+ - **README + README.zh-CN**:`What's new` / 安装命令 / Quick Start / CLI 命令表 / Project Structure ASCII 树标签,全部改为新品牌;新增「Migrating from `im-hub`」回退指引。
889
+ - **LICENSE 文件**:补齐 MIT 全文(之前 `package.json` 声明 MIT 但根目录无 LICENSE 文件),版权归属保持 `Jerry`。
890
+ - **`THEME_KEY`**:localStorage 主题 key 从 `im-hub-theme` → `im-hub-pro-theme`(用户在升级后第一次访问会丢失主题偏好;其他 localStorage key `im-hub-lang` / `im-hub-history` 保留以保住语言与对话历史)。
891
+
892
+ ### Unchanged — drop-in compat
893
+
894
+ 为了让老用户 `npm uninstall -g im-hub && npm install -g im-hub-pro` 后零迁移成本运行:
895
+ - **配置目录**:`~/.im-hub/`、`~/.im-hub-workspaces/<agent>/` 路径不变
896
+ - **环境变量**:60+ 处 `IMHUB_*` 前缀不变
897
+ - **HTTP 头**:`X-IM-Hub-Token` / `x-im-hub-token` 不变
898
+ - **Window 全局**:`window.IMHUB_TOKEN` 不变
899
+ - **ACP 服务名**:`im-hub-gateway` 不变(线上 ACP 客户端通过这个名字 discover)
900
+ - **MCP 测试 fixture**:`mcpServers.imhub` 不变
901
+ - **内部 client ID**:`im-hub:${ts}` 格式不变
902
+
903
+ ### Notes
904
+
905
+ - 发布走 npm OIDC trusted publishing(`release.yml` 不需要改),首次发布前需在 npmjs.com 上为 `im-hub-pro` 配置 trusted publisher 指向 `benking007/imhub` 仓库 + `release.yml` workflow。
906
+ - 老 `im-hub@0.2.25` 包在 npmjs.com 上保持不动,可继续安装。
907
+
908
+ ---
909
+
910
+ ## [0.2.23] - 2026-05-07
911
+
912
+ ### Added — Web console PR-D
913
+ - **Agent workspace 文件浏览器**:`/tasks` 新增 **Files** tab,只读浏览 `~/.im-hub-workspaces/<agent>/`(CLAUDE.md / AGENTS.md / 临时笔记等)。左侧目录树 + 右侧文件预览,点目录进入、点文件预览,文件 1 MiB 上限 + 二进制(首 8 KB NUL 探测)走 base64。后端 `GET /api/workspace-files?agent=&path=` 双重防 traversal:agent 必须在 `registry.listAgents()` 白名单,路径必须等于或位于 `defaultAgentCwd(agent)` base 之下,否则 400。
914
+ - **Job 批量操作**:Jobs tab 加多选 checkbox + 全选表头 + 隐藏式批量工具栏(Run selected / Cancel selected);选中态跨刷新保留——按可见行 reconcile,不会被 list mutation 抹掉。后端新 `POST /api/jobs/batch-cancel` / `batch-run` 接受 `{ ids: number[] }`(最多 100),per-id 结果不互相阻断。
915
+
916
+ ### Changed
917
+ - **Settings 页布局**:header 用 `justify-content: space-between` + 4 个独立 flex 子项导致语言 `<select>` 被拉伸成满屏宽,标题/链接全部换行。重写为 `.brand`(左侧链接 + 标题)+ `.controls`(右侧 lang/theme,`margin-left: auto`)双分组,控件 `width: auto` + `white-space: nowrap`。容器 max-width 720→880,标题字号字距微调,卡片圆角 8→12 + 浅色态加 1px 阴影,header sticky。
918
+ - **修复主题污染**:toast `.success` / `.error` 和 `.btn-danger:hover` 之前用了硬编码深色背景(`#0a2a0a` / `#2a2a0a` / `#1a0a0a`),浅色主题下渲染成近黑色,已改为主题变量。
919
+
920
+ ## [0.2.22] - 2026-05-07
921
+
922
+ ### Added — Web console PR-C
923
+ - **SSE 实时事件流**:新建 `src/core/event-bus.ts`——进程内 publish/subscribe,4 类事件(`audit` / `approval` / `job` / `metrics`),200 条 ring buffer。新连接 replay 一次避免空白等待。
924
+ - **`GET /events` 端点**:token 走 `?token=...`(EventSource 不支持自定义 header),25s 心跳防 nginx idle close,listener 错误在 `publish()` 内吞掉避免拖坏起源动作。
925
+ - **Dashboard 实时刷新**:`tasks.html` 用 `EventSource('/events?token=...')`,事件触发 `refreshIfVisible(paneId, loader)` 只刷可见 tab;轮询保留作为 fallback。`approval requested` 事件让 Approvals tab 标签闪一下高亮(动态注入的 CSS keyframe)。
926
+ - **Workspace 完整 CRUD UI**:`settings.html` 新增 Workspaces 卡片,列表 + 新增 / 编辑 / 删除表单(id 锁定防误改、default 行不可删)。后端 `GET /api/workspaces?full=1` / `POST` upsert / `PATCH /:id` / `DELETE /:id`,每次 mutation persist 回 `~/.im-hub/config.json`。
927
+ - `WorkspaceRegistry.remove(id)` 拒绝 `'default'`,`listFull()` 返回完整 `WorkspaceConfig[]`(含 member ids)。
928
+
929
+ ## [0.2.21] - 2026-05-07
930
+
931
+ ### Added — Web console PR-B
932
+ - **Health tab**:`/tasks` 新增 Agent 健康面板,每个 agent 一行展示熔断器状态(closed / open / half-open)、限流余量、p50/p95/p99 延迟、调用次数 / 成功率 / 累计成本、半开冷却。最近 60 次轮询的 p95 延迟用内联 SVG sparkline 画出。
933
+ - **Approvals tab**:列出所有 pending HITL 审批(reqId / threadId / toolName / 等待时长 / 注册时间),支持网页端直接 Allow / Deny / Allow + Auto。后端新 3 路由:`GET /api/agent-health` / `GET /api/approvals` / `POST /api/approvals/:reqId/resolve`。
934
+ - `approval-bus.PendingApproval` 增 `input`(原始入参)+ `registeredAt`,`listPending()` 返回脱敏快照;register / cancel 时 emit `'approval'` 事件,给 SSE 用。
935
+
936
+ ## [0.2.20] - 2026-05-07
937
+
938
+ ### Added — Web console PR-A
939
+ - **三态主题**:light / dark / system 三档循环切换,按钮在 head 加载完前同步应用 `:root[data-theme]`,避免主题闪烁。`prefers-color-scheme: dark` 仅在 `data-theme` 缺失(system 模式)时生效。语言 + 主题偏好都落在 localStorage。
940
+ - **错误边界**:新建 `src/web/public/_app.js`——`window.imhub` 命名空间提供 `theme` / `i18n` / `api` / `showError`,自动安装 `window.onerror` + `unhandledrejection` 监听,让脚本错误以浮层形式可见,不再静默死掉。
941
+ - **In-chat 审批卡**:`/` 聊天界面收到工具审批请求时弹出 inline 卡片(Allow / Deny / Allow + Auto),点击通过 WS `approval-action` 走回 `approvalBus.resolvePending()`,跟 Telegram inline-button 同一条路径。
942
+ - `approval-router` 默认 `buttonCallbackPlatforms` 加 `'discord'` / `'web'`;新导出 `bindButtonHandlerForPlatform(platform)` 给延迟注册的 messenger(web 是其中之一)调用,避免 install-before-register 时序问题。
943
+
944
+ ### Fixed
945
+ - `tasks.html` 第二段 `<script>` 顶层 `const T` 与第一段 IIFE 外的 `const T` 冲突("Identifier 'T' has already been declared")。把首段包进 IIFE 隔离作用域。
946
+ - WS `approval-action` 处理路径增日志(每次点击的 receipt + handler-bound 状态),handler 未绑定 / 处理失败时把"approval handler not bound" / "click failed"以聊天错误提示给用户;客户端用 soft-disable,避免服务端报错时按钮卡死。
947
+
948
+ ## [0.2.19] - 2026-05-07
949
+
950
+ ### Added
951
+ - **Codex sandbox-mode plan 支持**:codex 接入 `session.planMode`,默认 sandbox 从 `--full-auto` 改为显式的 `-s workspace-write`,关闭 auto-approval。
952
+ - **Dashboard agent 过滤 + Audit tab**:Jobs / Subtasks / Schedules 三个 tab 加 agent 下拉过滤(之前看得到但分不清归属),新增 Audit tab 把 SQLite 审计日志拉到 UI(之前只有 IM `/audit` 命令)。后端配套 `OwnerOpts.agent?` 字段下沉到 `listJobs` / `listSchedules` / `listAllSubtasks`,新 `GET /api/audit?agent=&days=&user=&intent=`。
953
+
954
+ ### Docs
955
+ - README "What's new" + Roadmap 补到 v0.2.18。
956
+
957
+ ---
958
+
959
+ ## [0.2.18] - 2026-05-07
960
+
961
+ ### Fixed
962
+ - **M9 — IM 重连退避**:`src/utils/backoff.ts` 新增 `Backoff` 助手(指数 + ±jitter,参数自钳位,RNG 可注入做确定性测试)。
963
+ - **Telegram** `runPollingLoop`:两个 fixed `setTimeout(2000)` / `setTimeout(5000)` → `Backoff(2_000, 60_000, 0.5)`;新增 `HEALTHY_RUN_THRESHOLD_MS=30_000`,`bot.start()` 跑健康 ≥ 30s 后再失败会重置 backoff,避免长会话第一次错被推到累计尾端。日志带 `attempt` + `delayMs`。
964
+ - **WeChat ilink** `pollLoop`:inline `1000 * Math.pow(2, n-1)` → `Backoff(2_000, 30_000, 0.5)`;保留首次失败"零等待"(`consecutiveFailures > 1` 才生效);`getUpdates` 成功路径加 `backoff.reset()`。
965
+
966
+ ### Notes
967
+ - Feishu (Lark SDK) / Discord (discord.js) 的 WebSocket 重连在 SDK 内部,没有 app 层注入点,本次未改。
968
+ - WeChat `tryRefreshSession` 的两次线性 `attempt*5000` 保留原状(套 Backoff 反而扭曲)。
969
+
970
+ ### Tests
971
+ - `test/unit/backoff.test.ts` 新建 10 用例:指数调度 / cap / reset / pinned RNG 下 jitter 边界 / 200 随机样本落 `[exp*0.5, exp*1.5]` / 默认值 / `baseMs=0` 边界 / `capMs<baseMs` 自纠正。
972
+
973
+ ---
974
+
975
+ ## [0.2.17] - 2026-05-07
976
+
977
+ 收尾 P2 + P3,配 `docs/code-review-2026-05-06-main.md` 的 punch list。
978
+
979
+ ### Fixed — P2 web/HTTP hardening
980
+ - **M1** 静态页响应头:`X-Frame-Options: DENY` / `X-Content-Type-Options: nosniff` / `Referrer-Policy: no-referrer` / Content-Type 加 `charset=utf-8` / CSP 锁第三方资源(`script-src` / `style-src` 暂留 `'unsafe-inline'`,nonce 化留 P3)。
981
+ - **M2** Prometheus 标签基数白名单:`KNOWN_INTENTS = {default,explicit,fallback,llm,topic,keyword,sticky}`、`KNOWN_PLATFORMS = {telegram,feishu,wechat,discord,web,acp,acp-server,rest,rest-msg}`,命中外的桶到 `'other'`。
982
+ - **M3** WS 连接上限:默认 100,env `IMHUB_MAX_WS_CLIENTS` 覆盖;超限时 `1013 Try Again Later`。
983
+ - **M4** `/api/health` 改为 token gate 之前公开(k8s liveness 友好)。
984
+ - **M11** ACP 测试入口的 `JSON.parse` 包 `try/catch` + endpoint 类型校验,错误 body 落 400 而非 500。
985
+
986
+ ### Fixed — P2 observability
987
+ - **M7** audit 30 天修剪失败暴露 `im_hub_audit_prune_failed_total` counter。
988
+ - **M14** approval-bus 五个生命周期 counter(`pending` / `requests_total` / `resolved_total{result=allow|deny|timeout}`),lazy provider 注入避免循环依赖。
989
+ - **M15** intent-llm cache key:候选列表用 sha256 16-hex,prompt 截断到 256 字符,防 100KB prompt × 多 ACP 候选爆 LRU。
990
+ - **bug-fix**:approval result 三类桶做成互斥(timeout / deny / allow),原本 timeout-deny 双计可能让 Prom counter 倒退。
991
+
992
+ ### Fixed — P2 IM 文本边界
993
+ - **M10** `splitMessage` 强制 `maxLength` 切分加 `safeSplitPoint`,避免切到 UTF-16 surrogate pair(emoji 半切渲染成 `□`,或破坏 Telegram HTML 解析)。
994
+ - **M12** Telegram `escapeHtml` 补 `'` / `"` —— approval card 模板里的 `<a href="...">` 属性场景必要。
995
+ - **M13** approval auto-allow 前缀指纹 5 → 10:原来 `git s` 同时匹配 `git status`/`git stash`/`git submodule`,太宽。
996
+
997
+ ### Fixed — P3 low-priority (10 L items)
998
+ - **L1** WS chunk 循环检查 `bufferedAmount`(4 MiB highwater,50 ms poll,5 s 预算)。
999
+ - **L2** SIGINT 关停链接通 `helper.close()` / `closeAuditDb` / `closeJobBoardDb` / `closeScheduleDb`,WAL 清洁 checkpoint。
1000
+ - **L5** `traceId` 12 hex (~2^48) → 16 hex (~2^64)。
1001
+ - **L6** WeChat ilink-client 4 处遗漏的 `fetch` 加 `AbortSignal.timeout()`。
1002
+ - **L7** Discord adapter `typingIntervals` 配 `AbortController`,`stop()` 先 abort 再 `clearInterval`,避免与 `client.destroy()` 竞速。
1003
+ - **L10** approval-bus buffer 溢出改为写 `{v:1,type:"fatal"}` + `socket.end()`,不再静默 destroy。
1004
+ - **L12** `classifyIntent` 失败日志带 `err.message` + stack。
1005
+ - **L13** 新增 `im_hub_agent_cleanup_failed_total` counter,`plan.cleanup()` catch 计数。
1006
+ - **L14** WeChat ilink `cleanupExpiredContextTokens` 搭 1/min 心跳 tick —— 静默用户不再留 30 min 残余。
1007
+ - **L15** onboarding `perAgentCache` 注释对齐实际查询顺序。
1008
+
1009
+ ### Build / infra
1010
+ - `dist/cli.js` 编译后 `chmod +x`(534fb55)。
1011
+ - `package-lock.json` 94 处 `resolved` URL 由 Tencent 内网镜像改回 `https://registry.npmjs.org/`(content-addressed integrity 一致),CI 解锁。
1012
+ - `session.ts atomicWrite` 加 ENOENT defense(`mkdir` + 重试一次);同步修了 `session-subtasks.test.ts` 在 fresh runner 上的 0 vs 2 翻车。
1013
+
1014
+ ### Skipped from CR (理由记 PR body)
1015
+ - L3 / L4 / L8 / L9 / L11:false positive 或纯 cosmetic 或过度侵入。
1016
+
1017
+ ---
1018
+
1019
+ ## [0.2.16] - 2026-05-06
1020
+
1021
+ P0 + P1 安全收口,配 `docs/code-review-2026-05-06-main.md`。
1022
+
1023
+ ### Fixed — P0 hotfix
1024
+ - **H1** Web REST/WS token 比较切到 `crypto.timingSafeEqual`,抽 `src/utils/safe-equal.ts` 与 `acp-server.ts` 共用。
1025
+ - **H2** `serveIndexHtml` 用 `JSON.stringify(token)` 注入 `window.IMHUB_TOKEN`,防 token 含 `'` / `</script>` 破坏 JS 字面量。
1026
+ - **H3** Schedule `notify_url` SSRF 防护:`validateWebhookUrl` 白名单 `http(s)`、黑名单 RFC1918 / loopback / link-local / CGNAT / IPv6 ULA / fe80::;`IMHUB_ALLOW_PRIVATE_WEBHOOKS=1` 显式 opt-in;fetch 加 10s `AbortSignal` + `redirect: 'manual'` + 64 KiB 响应上限;`createSchedule` 与 `fireSchedule` 双层校验。
1027
+ - **H8** `settings.html` ACP 测试按钮:原代码 `const res` 重复声明 + 引用未定义 `endpoint`/`auth`,整页脚本 SyntaxError,功能必坏。改读 `agent.endpoint` / `agent.auth`。
1028
+ - **H10** Telegram bot token 不再随错误日志泄露:`logger.ts` 加 `redact.paths`(`*.token` / `*.botToken` / `*.apiKey` / `headers.authorization` / `x-im-hub-token` 等);图片 / 语音下载 catch 路径用本地 `scrub()` 把 token 字面量替换 `[REDACTED]`,覆盖 curl stderr 带回 URL 的场景。
1029
+
1030
+ ### Fixed — P1 runtime hardening
1031
+ - **H4** `RateLimiter` bucket 无界:`allow()` 内置 `maybeSweep`,每 30 min 机会式触发 cleanup(不引入 `setInterval` timer);新增 `size()` 诊断访问器。
1032
+ - **H6** Job result 大小:`resolveMaxResultBytes()` 默认 1 MiB,env `IMHUB_JOB_RESULT_MAX_BYTES` 覆盖;超过 cap 整 chunk 丢弃(边界落在 UTF-8/JSON 事件分隔上),尾部拼 truncation footer,落 warn `event: 'job.result.truncated'`。
1033
+ - **H9** WeChat 凭证文件权限:`saveCredentials` 用 `mkdir(.., {mode:0o700})` + `writeFile(.., {mode:0o600})`;`loadCredentials` 读时 `stat`,松权落 warn 提示用户 `chmod 600`(不主动改老文件)。
1034
+ - **H11** Approval socket 路径熵 + 权限:`defaultSocketPath` 改用 `randomBytes(16).toString('hex')`(128 位熵),listen 后 `chmod 0o600` + `stat` 验证;防同主机非特权用户预占 socket 的 TOCTOU。
1035
+ - **H12** `Session.addMessage` 并发:`SessionManager` 加 `writeQueues<key, Promise>` + `withLock<T>(key, fn)`,per-key 串行化 RMW,跨 thread 仍并行。
1036
+
1037
+ ### Fixed — P1 multi-tenant ACL
1038
+ - **H5** `/job` `/schedule` `/audit` 命令补 owner 隔离:`jobs` / `schedules` 表加 `creator_id` / `workspace_id`(幂等 `ALTER TABLE`,老库无痛升级);`createJob` / `createSchedule` 接 `{ creatorId, workspaceId }` 由 handler 注入;查询/取消/删/启停加 `OwnerOpts`,`creator_id = ? OR creator_id = ''`(老库 `''` 对所有 owner 可见,避免升级当天列表看似消失);`/audit user=<other>` 强制 overwrite 到 `ctx.userId`,结果末尾追加忽略提示。
1039
+ - **H7** Job 表 retention:`resolveRetentionDays()` 默认 30 天,env `IM_HUB_JOB_RETENTION_DAYS` 覆盖;`pruneOldJobs()` 删 `completed`/`failed`/`cancelled` 超期行,`pending`/`running` 永不修剪;启动跑一次 + 每 6h `setInterval(..unref())`;新增 `idx_jobs_created` / `idx_jobs_creator` / `idx_jobs_status_created` 索引。
1040
+ - **M6** Workspace 白名单从路由层下沉到命令层:`/job create <agent>` 与 `/schedule create <agent>` 在 `createJob` / `createSchedule` 之前调 `workspaceRegistry.resolve(ctx.userId).hasAgent(agent.name)`。
1041
+
1042
+ ### Tests
1043
+ - `test/unit/schedule-webhook.test.ts` 新增 22 用例锁定 SSRF 防护行为。
1044
+ - `test/unit/rate-limiter.test.ts` +4 用例:size 计数、强制清空、刚活跃 bucket spare、连续 allow 不误触发 sweep。
1045
+ - `test/unit/session-real.test.ts` +1 用例:50 个并发 `addMessage` 同 key,JSONL 行数与 in-memory messages 严格 = N,content 集合无丢失。
1046
+
1047
+ ### Notes
1048
+ - 历史 `/job list`(无 `ctx.userId` 注入)仍能看全部,REST `/api/jobs` 同。
1049
+ - 历史调用方(无 `OwnerOpts`)TS 仍可编译通过。
1050
+ - M5(cron 时钟跳变)经复核当前 `dueSchedules → fireSchedule` 路径不会双触发,留到后续 UTC 重构一并处理。
1051
+
1052
+ ---
1053
+
1054
+ ## [0.2.15] - 2026-05-01
1055
+
1056
+ ### Added
1057
+ - **Discord messenger adapter** — full IM bridge for Discord (Gateway WebSocket via `discord.js`)
1058
+ - `im-hub config discord` interactive setup wizard
1059
+ - Typing indicator (10s TTL, 8s refresh)
1060
+ - Markdown → Discord-flavored format conversion
1061
+ - Guild / Channel whitelist filtering
1062
+ - Setup guide: [`docs/discord-setup.md`](docs/discord-setup.md)
1063
+ - **Tasks dashboard upgrades** — `/tasks` page now surfaces background work
1064
+ - **Background tab**: lists `~/.claude/bgjobs` + `~/.config/opencode/bgjobs` jobs (override via `IMHUB_BGJOB_ROOTS`); per-root selector, 5s auto-refresh, detail modal with `cmd` / `workdir` / `log_tail`
1065
+ - **Subtasks tab**: flattens every subtask in every session file, with parent platform / threadId / agent attached
1066
+ - Bilingual labels (EN + ZH)
1067
+ - **REST endpoints (read-only)**:
1068
+ - `GET /api/bgjobs[?root=ID]` / `GET /api/bgjobs/:id[?root=ID&tail=N]`
1069
+ - `GET /api/subtasks`
1070
+
1071
+ ### Changed
1072
+ - Web Chat / Tasks `index.html` served with `Cache-Control: no-cache, must-revalidate` so dashboard updates land without a hard refresh.
1073
+ - **Feishu adapter** dedupes `message_id` in a 10-min TTL set — WebSocket long-poll was replaying the same event on reconnect and double-firing Claude runs.
1074
+
1075
+ ### Tests
1076
+ - 26 new tests: bgjob-reader (15), session subtasks (2), web server integration (9)
1077
+ - Discord adapter: mock-client driven offline e2e (sendMessage, messageCreate, whitelist / bot filtering, message splitting)
1078
+ - IM approval ↔ Discord end-to-end loop (sidecar → ApprovalBus → approval-router → Discord channel → reply → decision back)
1079
+
1080
+ ---
1081
+
1082
+ ## [0.2.14] - 2026-05-01
1083
+
1084
+ ### Added
1085
+ - **Human-in-the-loop tool approval** for IM-launched Claude runs.
1086
+ - Replaces the legacy `--permission-mode dontAsk + blanket-allow PreToolUse hook` shortcut with a real approval flow over IM.
1087
+ - Architecture:
1088
+ ```
1089
+ claude --permission-prompt-tool mcp__imhub__request --mcp-config <tmp>
1090
+ └─> MCP sidecar (mcp-approval-server.ts) ── unix socket ──> im-hub
1091
+ └─> approval-bus
1092
+ └─> approval-router → messenger.sendMessage
1093
+
1094
+ user replies y / n / 批准 / 拒绝 in the same IM thread
1095
+ ```
1096
+ - cli intercepts approval replies *before* the agent router; unrecognized replies during a pending request auto-deny so the sidecar (and Claude) don't hang.
1097
+ - Per-spawn state lives in the `SpawnPlan` returned by `AgentBase.prepareCommand` (closure-local, not `this.*`) — fixes a singleton race where parallel IM threads running claude clobbered each other's `mcp-config` and the second run died with *MCP config file not found*.
1098
+ - Graceful fallbacks: `IMHUB_APPROVAL_DISABLED=1`, missing IM context, approval-bus not started, or `mkdtemp/writeFile` failure all degrade to the legacy `--permission-mode dontAsk` path.
1099
+
1100
+ ### Notes
1101
+ - Approvals are platform-agnostic — the same chain works for WeChat / Telegram / Feishu / Discord with no per-platform changes.
1102
+
1103
+ ---
1104
+
1105
+ ## [0.2.13] - 2026-04-30
1106
+
1107
+ A large multi-phase release: structured logging, observability, multi-tenant routing, persistent jobs, ACP server-mode, and a Web tasks panel. Versioned together because the wiring is interdependent.
1108
+
1109
+ ### Phase 1 — Foundations (security, logging, schema, agent base)
1110
+
1111
+ #### Added
1112
+ - **Structured logging** with `pino` and request-scoped `traceId` propagated through every layer (router → agent → audit). Pretty in TTY, JSON in production. ADR: [`docs/adr/0002-structured-logging-trace-id.md`](docs/adr/0002-structured-logging-trace-id.md).
1113
+ - **Zod config schema validation** at startup and PUT `/api/config` — invalid configs reject with a useful error instead of crashing the bridge mid-run.
1114
+ - **`AgentBase` abstraction** for CLI-based adapters (claude-code / codex / copilot / opencode) — shared spawn-stream, abort/timeout, line buffer, error formatting, healthCheck. ADR: [`docs/adr/0001-agent-base-abstraction.md`](docs/adr/0001-agent-base-abstraction.md).
1115
+ - **Agent availability TTL cache** on top of `healthCheck` — avoids spawning a probe process on every `/<agent>` switch.
1116
+ - **Audit log** with SQLite (`~/.im-hub/audit.db`, 30-day retention) + `/audit [n]` chat command.
1117
+
1118
+ #### Fixed
1119
+ - P0 batch: WebSocket auth, nested config-mask leaks, timeout coercion, `/api/notify` token validation, agent-name prefix collisions, session path traversal (ADR [`0003`](docs/adr/0003-session-path-safety.md)).
1120
+
1121
+ ### Phase 2 — Routing & resilience
1122
+
1123
+ #### Added
1124
+ - **Intent classifier** (`src/core/intent.ts`) — topic regex (CJK + ASCII), per-agent keyword profile, sticky-session bias, optional LLM judge fallback with LRU cache. `/router status|policy|explain|reset` for inspection.
1125
+ - **Circuit breaker** for agent invocations (3 failures → 5-minute cool-down).
1126
+ - **Per-user token-bucket rate limiter**, applied before every agent dispatch.
1127
+
1128
+ #### Fixed
1129
+ - Workspace whitelist now applies to both `/<agent>` *and* default routing.
1130
+ - `workspace.rateLimit` actually enforced (was inert before).
1131
+ - Intent classifier matches CJK keywords (was `\b` regex, all dead).
1132
+ - Profile-less ACP custom agents participate via a `DEFAULT_WEIGHT` floor.
1133
+
1134
+ ### Phase 3 — ACP server, workspaces, persistent jobs
1135
+
1136
+ #### Added
1137
+ - **ACP server mode** — im-hub itself is now an ACP-compatible agent at `POST /tasks` (sync + SSE) with timing-safe auth and a 1 MiB body cap.
1138
+ - **`/.well-known/acp` discovery** for ACP custom agents (A-1).
1139
+ - **Multi-tenant workspace registry** (`src/core/workspace.ts`) with per-workspace agent whitelist + rate limits + member lists.
1140
+ - **Persistent Job Board** with SQLite (`~/.im-hub/jobs.db`) and `/job` chat commands — survives restarts; ACP-server tasks become durable jobs.
1141
+ - **Subtask sessions** + `/task` aliases for backward compatibility.
1142
+ - **`AbortController` signal** plumbed through job board for real cancellation (Phase 3.5).
1143
+ - **Cron scheduler** (`src/core/schedule.ts`) — 30-second tick, fires registered job specs.
1144
+
1145
+ ### Phase 4 — Observability & Web (W-1)
1146
+
1147
+ #### Added
1148
+ - **Web `/tasks` panel** for jobs, schedules, workspaces.
1149
+ - **REST jobs API** (`/api/jobs`, `/api/schedules`, `/api/workspaces`).
1150
+ - **Prometheus metrics** at `/api/metrics` (pure quickselect quantiles, no extra dep).
1151
+ - Deployment guide: [`docs/deployment.md`](docs/deployment.md).
1152
+
1153
+ ### Restored / overhauled commands
1154
+ - `/model`, `/models`, `/think`, `/stats`, `/sessions` — all returned and overhauled. Session model selection now persists across restarts.
1155
+
1156
+ ### Performance
1157
+ - LRU cache for LLM intent judge.
1158
+ - Shared SQLite helper (single `prepare`-cache, single PRAGMA bootstrap).
1159
+ - LineBuffer indexOf walk (avoids quadratic scans on long stdout chunks).
1160
+ - Metrics quickselect quantiles (no `.sort()` per scrape).
1161
+ - Cron `nextOccurrence` field-level fast-forward (skip-ahead instead of minute-loop).
1162
+
1163
+ ### Stability fixes (CR round)
1164
+ - 11 follow-up findings from the code-review pass (see [`docs/code-review-2026-04-30-main.md`](docs/code-review-2026-04-30-main.md)).
1165
+ - WeChat `getUpdates` now has a `FETCH_TIMEOUT` to prevent event-loop blocking.
1166
+ - `AgentBase.sendPrompt` is true streaming with multi-byte UTF-8 safety.
1167
+ - `session.addMessage` is append-only JSONL (was full rewrite per turn).
1168
+
1169
+ ### Architecture docs
1170
+ - [`docs/architecture/current.md`](docs/architecture/current.md) — system overview at v0.2.13.
1171
+ - [`docs/architecture/target.md`](docs/architecture/target.md) — multi-tenant target.
1172
+ - ADRs 0001 / 0002 / 0003.
1173
+
1174
+ ---
1175
+
1176
+ ## [0.2.7] - 2026-03-27
1177
+
1178
+ ### Added
1179
+ - **Conversation history support** — agents now remember context across messages
1180
+ - Session stores message history (`ChatMessage[]`)
1181
+ - History is passed to agents with each prompt for context awareness
1182
+ - `/new` command to start a fresh conversation (clears history)
1183
+ - **ChatMessage type** — `{ role: 'user' | 'assistant', content: string, timestamp: Date }`
1184
+ - **Session history management** in SessionManager:
1185
+ - `addMessage()` — add message to conversation history
1186
+ - `resetConversation()` — clear history, start new session
1187
+ - `getSessionWithHistory()` — retrieve session with messages
1188
+
1189
+ ### Changed
1190
+ - **AgentAdapter interface** — `sendPrompt()` now accepts optional `history?: ChatMessage[]`
1191
+ - **All agent adapters** (claude-code, codex, copilot, opencode) now:
1192
+ - Accept conversation history
1193
+ - Build contextual prompts with previous messages
1194
+ - **Router** — automatically saves user messages and agent responses to history
1195
+ - **Help text** — updated to include `/new` command
1196
+
1197
+ ### Fixed
1198
+ - Context loss issue in channel-based conversations — agents now maintain conversation memory
1199
+
1200
+ ## [0.2.2.0] - 2026-03-27
1201
+
1202
+ ### Added
1203
+ - **Onboarding module** (`src/core/onboarding.ts`) with friendly first-run experience
1204
+ - `checkMessengerConfig()` — detect if messengers are configured
1205
+ - `checkAgentAvailability()` — async check with session-level caching
1206
+ - `runMessengerOnboarding()` — interactive messenger setup wizard
1207
+ - `formatAgentInstallHint()` — friendly install messages for missing agents
1208
+ - `formatAgentNotAvailableError()` — chat-friendly runtime error messages
1209
+ - `formatMessengerStartError()` — actionable hints for startup failures
1210
+
1211
+ ### Changed
1212
+ - **CLI start command** now runs onboarding checks before starting messengers
1213
+ - Detects unconfigured messengers and launches interactive setup
1214
+ - Warns about missing agents with install instructions
1215
+ - Shows friendly error messages instead of stack traces
1216
+ - **Router** now checks agent availability at runtime
1217
+ - Returns helpful chat message if requested agent isn't installed
1218
+ - Uses cached availability check to avoid repeated process spawns
1219
+
1220
+ ### Fixed
1221
+ - Critical bug where onboarding never triggered because `config.messengers` was auto-filled with default
1222
+ - Ugly stack traces shown to users when messenger fails to start
1223
+
1224
+ ## [0.0.1.0] - 2026-03-25
1225
+
1226
+ ### Added
1227
+ - Initial project scaffold with TypeScript + Bun
1228
+ - Core types: `Message`, `ParsedMessage`, `Session`, `MessengerAdapter`, `AgentAdapter`
1229
+ - Plugin registry for static imports
1230
+ - Message router with command parsing (`/status`, `/help`, `/agents`, `/<agent>`)
1231
+ - Session manager with file-based persistence
1232
+ - WeChat adapter stub (wechaty-puppet-wechat)
1233
+ - Claude Code adapter stub (stream-json mode)
1234
+ - CLI commands: `start`, `config`, `agents`, `messengers`