@xopcai/xopc 0.0.85 → 0.0.87

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 (407) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/feishu/src/adapters/cli-login.js +3 -3
  3. package/dist/extensions/feishu/src/adapters/cli-login.js.map +1 -1
  4. package/dist/extensions/telegram/src/delivery-chat-id.d.ts +1 -1
  5. package/dist/extensions/telegram/src/delivery-chat-id.js +1 -1
  6. package/dist/extensions/telegram/src/delivery-chat-id.js.map +1 -1
  7. package/dist/extensions/telegram/src/routing-integration.js +1 -0
  8. package/dist/extensions/telegram/src/routing-integration.js.map +1 -1
  9. package/dist/extensions/telegram/xopc.extension.json +1 -1
  10. package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js +2 -2
  11. package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js.map +1 -1
  12. package/dist/extensions/weixin/src/api/api.js +2 -2
  13. package/dist/extensions/weixin/src/api/api.js.map +1 -1
  14. package/dist/extensions/weixin/src/auth/accounts.js +12 -12
  15. package/dist/extensions/weixin/src/auth/accounts.js.map +1 -1
  16. package/dist/extensions/weixin/src/delivery-to.js +2 -2
  17. package/dist/extensions/weixin/src/delivery-to.js.map +1 -1
  18. package/dist/extensions/weixin/src/messaging/debug-mode.js +5 -5
  19. package/dist/extensions/weixin/src/messaging/debug-mode.js.map +1 -1
  20. package/dist/extensions/weixin/src/messaging/inbound.js +11 -11
  21. package/dist/extensions/weixin/src/messaging/inbound.js.map +1 -1
  22. package/dist/extensions/weixin/src/storage/sync-buf.js +4 -4
  23. package/dist/extensions/weixin/src/storage/sync-buf.js.map +1 -1
  24. package/dist/extensions/weixin/src/workflow-progress.d.ts +1 -1
  25. package/dist/extensions/weixin/src/workflow-progress.js.map +1 -1
  26. package/dist/gateway/static/root/assets/agents-BEAbXpuP.js +222 -0
  27. package/dist/gateway/static/root/assets/{apps-page-D7v7649T.js → apps-page-Dg8R-Szf.js} +1 -1
  28. package/dist/gateway/static/root/assets/{channels-settings-nCaMb0a7.js → channels-settings-yohw9YSu.js} +1 -1
  29. package/dist/gateway/static/root/assets/{channels-status-swr-C1gZBcJV.js → channels-status-swr-BSHqqCF1.js} +1 -1
  30. package/dist/gateway/static/root/assets/{cron-api-CoYK0hlm.js → cron-api-0h_QT8U3.js} +1 -1
  31. package/dist/gateway/static/root/assets/{cron-page-DeGo-Vjc.js → cron-page-BkfKFfFk.js} +1 -1
  32. package/dist/gateway/static/root/assets/{dist-DaK4dsss.js → dist-Cmjp2APP.js} +1 -1
  33. package/dist/gateway/static/root/assets/{extension-debug-page-BZngZWbO.js → extension-debug-page-CFa9z_1N.js} +1 -1
  34. package/dist/gateway/static/root/assets/{extension-page-D6JSyV27.js → extension-page-BI8eaTPq.js} +1 -1
  35. package/dist/gateway/static/root/assets/extension-settings-page-x4BB7q1X.js +1 -0
  36. package/dist/gateway/static/root/assets/{fetch-B2MYHbWg.js → fetch-DRqwef_Q.js} +1 -1
  37. package/dist/gateway/static/root/assets/{field-primitives-Zzl22MvN.js → field-primitives-BiNHBo2Y.js} +1 -1
  38. package/dist/gateway/static/root/assets/{heartbeat-config-api-BtIcpG0O.js → heartbeat-config-api-ZRb8qhuz.js} +1 -1
  39. package/dist/gateway/static/root/assets/{index-D4vM3-P7.js → index-Cu7bKuUi.js} +96 -94
  40. package/dist/gateway/static/root/assets/index-a5gWIdZQ.css +1 -0
  41. package/dist/gateway/static/root/assets/{logs-page-_d4UJ-qQ.js → logs-page-BFZ8GgCv.js} +1 -1
  42. package/dist/gateway/static/root/assets/{sessions-page-5N4aF2Wk.js → sessions-page-CD7AfB-2.js} +1 -1
  43. package/dist/gateway/static/root/assets/settings-form-section-DiqqVs6m.js +1 -0
  44. package/dist/gateway/static/root/assets/settings-page-BBOjEQW3.js +3 -0
  45. package/dist/gateway/static/root/assets/{share-preview-page-D4EG_vM1.js → share-preview-page-n1Gprylk.js} +1 -1
  46. package/dist/gateway/static/root/assets/{skills-page-sPAXhh8w.js → skills-page-CcN_gj--.js} +1 -1
  47. package/dist/gateway/static/root/assets/{theme-store-DryYl3qD.js → theme-store-CZOh1nT3.js} +1 -1
  48. package/dist/gateway/static/root/assets/url-Dd8Q7kZZ.js +3 -0
  49. package/dist/gateway/static/root/assets/{utils-CYO9eTCM.js → utils-CkWBfxs4.js} +1 -1
  50. package/dist/gateway/static/root/assets/{voice-api-key-field-Ds51havm.js → voice-api-key-field-O6awz9hi.js} +1 -1
  51. package/dist/gateway/static/root/index.html +5 -5
  52. package/dist/package.js +1 -1
  53. package/dist/src/agent/agent-scope.d.ts +4 -0
  54. package/dist/src/agent/agent-scope.js +53 -10
  55. package/dist/src/agent/agent-scope.js.map +1 -1
  56. package/dist/src/agent/bootstrap/filter-bootstrap-files.js +2 -1
  57. package/dist/src/agent/bootstrap/filter-bootstrap-files.js.map +1 -1
  58. package/dist/src/agent/embedded/session-tool-result-guard.js +2 -1
  59. package/dist/src/agent/embedded/session-tool-result-guard.js.map +1 -1
  60. package/dist/src/agent/embedded/tool-result-truncation.js +2 -1
  61. package/dist/src/agent/embedded/tool-result-truncation.js.map +1 -1
  62. package/dist/src/agent/fallback/candidates.js +2 -2
  63. package/dist/src/agent/fallback/candidates.js.map +1 -1
  64. package/dist/src/agent/goals/persistent-goal-apis.d.ts +0 -2
  65. package/dist/src/agent/goals/persistent-goal-service.js +0 -1
  66. package/dist/src/agent/goals/persistent-goal-service.js.map +1 -1
  67. package/dist/src/agent/image/generation/normalization.js +2 -12
  68. package/dist/src/agent/image/generation/normalization.js.map +1 -1
  69. package/dist/src/agent/image/generation/provider-registry.d.ts +4 -8
  70. package/dist/src/agent/image/generation/provider-registry.js.map +1 -1
  71. package/dist/src/agent/image/generation/runtime.d.ts +2 -2
  72. package/dist/src/agent/image/generation/runtime.js.map +1 -1
  73. package/dist/src/agent/image/generation/types.d.ts +0 -18
  74. package/dist/src/agent/image/image-helpers.js +6 -1
  75. package/dist/src/agent/image/image-helpers.js.map +1 -1
  76. package/dist/src/agent/image/index.d.ts +1 -1
  77. package/dist/src/agent/inbound/inbound-loop.d.ts +5 -0
  78. package/dist/src/agent/inbound/inbound-loop.js +41 -10
  79. package/dist/src/agent/inbound/inbound-loop.js.map +1 -1
  80. package/dist/src/agent/inbound/turn-dispatcher.d.ts +4 -0
  81. package/dist/src/agent/inbound/turn-dispatcher.js +6 -4
  82. package/dist/src/agent/inbound/turn-dispatcher.js.map +1 -1
  83. package/dist/src/agent/mcp/bundle-mcp-materialize.js +2 -1
  84. package/dist/src/agent/mcp/bundle-mcp-materialize.js.map +1 -1
  85. package/dist/src/agent/mcp/bundle-mcp-names.js +2 -1
  86. package/dist/src/agent/mcp/bundle-mcp-names.js.map +1 -1
  87. package/dist/src/agent/mcp/bundle-mcp-runtime.js +2 -1
  88. package/dist/src/agent/mcp/bundle-mcp-runtime.js.map +1 -1
  89. package/dist/src/agent/mcp/mcp-transport-config.js +2 -1
  90. package/dist/src/agent/mcp/mcp-transport-config.js.map +1 -1
  91. package/dist/src/agent/mcp/mcp-transport.js +2 -1
  92. package/dist/src/agent/mcp/mcp-transport.js.map +1 -1
  93. package/dist/src/agent/media-generation/runtime-shared.js +2 -9
  94. package/dist/src/agent/media-generation/runtime-shared.js.map +1 -1
  95. package/dist/src/agent/messaging/command-handler.d.ts +6 -0
  96. package/dist/src/agent/messaging/command-handler.js +5 -0
  97. package/dist/src/agent/messaging/command-handler.js.map +1 -1
  98. package/dist/src/agent/prompt/safety.d.ts +0 -7
  99. package/dist/src/agent/prompt/safety.js +1 -20
  100. package/dist/src/agent/prompt/safety.js.map +1 -1
  101. package/dist/src/agent/service/build-direct-message-content.js +1 -1
  102. package/dist/src/agent/service/build-direct-message-content.js.map +1 -1
  103. package/dist/src/agent/service/direct-turn-helpers.d.ts +3 -1
  104. package/dist/src/agent/service/direct-turn-helpers.js +6 -1
  105. package/dist/src/agent/service/direct-turn-helpers.js.map +1 -1
  106. package/dist/src/agent/service/process-direct-one-shot.d.ts +4 -0
  107. package/dist/src/agent/service/process-direct-one-shot.js +15 -2
  108. package/dist/src/agent/service/process-direct-one-shot.js.map +1 -1
  109. package/dist/src/agent/service/process-direct-streaming.d.ts +4 -0
  110. package/dist/src/agent/service/process-direct-streaming.js +34 -4
  111. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  112. package/dist/src/agent/service/webchat-tts.js +1 -1
  113. package/dist/src/agent/service/webchat-tts.js.map +1 -1
  114. package/dist/src/agent/service.d.ts +8 -0
  115. package/dist/src/agent/service.js +21 -1
  116. package/dist/src/agent/service.js.map +1 -1
  117. package/dist/src/agent/tools/create-share-tool.js +27 -20
  118. package/dist/src/agent/tools/create-share-tool.js.map +1 -1
  119. package/dist/src/agent/tools/factory.js +1 -1
  120. package/dist/src/agent/tools/index.d.ts +0 -1
  121. package/dist/src/agent/tools/index.js +4 -5
  122. package/dist/src/agent/tools/shell.js +0 -13
  123. package/dist/src/agent/tools/shell.js.map +1 -1
  124. package/dist/src/agent/tools/workflow-tool.js +10 -4
  125. package/dist/src/agent/tools/workflow-tool.js.map +1 -1
  126. package/dist/src/agent/workflow/builtins/audit-repo.d.ts +5 -1
  127. package/dist/src/agent/workflow/builtins/audit-repo.js +52 -11
  128. package/dist/src/agent/workflow/builtins/audit-repo.js.map +1 -1
  129. package/dist/src/agent/workflow/builtins/debug-incident.d.ts +13 -0
  130. package/dist/src/agent/workflow/builtins/debug-incident.js +155 -0
  131. package/dist/src/agent/workflow/builtins/debug-incident.js.map +1 -0
  132. package/dist/src/agent/workflow/builtins/index.d.ts +3 -1
  133. package/dist/src/agent/workflow/builtins/index.js +11 -1
  134. package/dist/src/agent/workflow/builtins/index.js.map +1 -1
  135. package/dist/src/agent/workflow/builtins/multi-perspective-review.d.ts +6 -1
  136. package/dist/src/agent/workflow/builtins/multi-perspective-review.js +66 -30
  137. package/dist/src/agent/workflow/builtins/multi-perspective-review.js.map +1 -1
  138. package/dist/src/agent/workflow/builtins/pr-review.d.ts +12 -0
  139. package/dist/src/agent/workflow/builtins/pr-review.js +156 -0
  140. package/dist/src/agent/workflow/builtins/pr-review.js.map +1 -0
  141. package/dist/src/agent/workflow/builtins/research.d.ts +5 -1
  142. package/dist/src/agent/workflow/builtins/research.js +37 -6
  143. package/dist/src/agent/workflow/builtins/research.js.map +1 -1
  144. package/dist/src/agent/workflow/catalog.d.ts +5 -0
  145. package/dist/src/agent/workflow/catalog.js +6 -2
  146. package/dist/src/agent/workflow/catalog.js.map +1 -1
  147. package/dist/src/agent/workflow/channel-capability.d.ts +3 -3
  148. package/dist/src/agent/workflow/index.d.ts +1 -1
  149. package/dist/src/agent/workflow/lint.d.ts +38 -0
  150. package/dist/src/agent/workflow/lint.js +74 -0
  151. package/dist/src/agent/workflow/lint.js.map +1 -0
  152. package/dist/src/agent/workflow/parser.js +13 -1
  153. package/dist/src/agent/workflow/parser.js.map +1 -1
  154. package/dist/src/agent/workflow/runtime.d.ts +3 -0
  155. package/dist/src/agent/workflow/runtime.js +76 -3
  156. package/dist/src/agent/workflow/runtime.js.map +1 -1
  157. package/dist/src/agent/workflow/types.d.ts +11 -1
  158. package/dist/src/browser/index.js +4 -4
  159. package/dist/src/browser/manager.d.ts +1 -3
  160. package/dist/src/browser/manager.js +0 -6
  161. package/dist/src/browser/manager.js.map +1 -1
  162. package/dist/src/browser/providers/browser-ext-install.d.ts +4 -4
  163. package/dist/src/browser/providers/browser-ext-install.js +38 -85
  164. package/dist/src/browser/providers/browser-ext-install.js.map +1 -1
  165. package/dist/src/browser/providers/cloakbrowser.d.ts +0 -5
  166. package/dist/src/browser/providers/cloakbrowser.js +2 -55
  167. package/dist/src/browser/providers/cloakbrowser.js.map +1 -1
  168. package/dist/src/channels/attachments/voice-stt-webchat.js +10 -8
  169. package/dist/src/channels/attachments/voice-stt-webchat.js.map +1 -1
  170. package/dist/src/channels/pairing/allow-from-file.js +9 -9
  171. package/dist/src/channels/pairing/allow-from-file.js.map +1 -1
  172. package/dist/src/channels/pairing/pairing-store.js +6 -6
  173. package/dist/src/channels/pairing/pairing-store.js.map +1 -1
  174. package/dist/src/chat-commands/builtins/session.js +1 -1
  175. package/dist/src/chat-commands/builtins/session.js.map +1 -1
  176. package/dist/src/chat-commands/builtins/tts.js +2 -2
  177. package/dist/src/chat-commands/builtins/tts.js.map +1 -1
  178. package/dist/src/chat-commands/builtins/workflow.js +7 -2
  179. package/dist/src/chat-commands/builtins/workflow.js.map +1 -1
  180. package/dist/src/chat-commands/context.d.ts +3 -0
  181. package/dist/src/chat-commands/context.js +21 -3
  182. package/dist/src/chat-commands/context.js.map +1 -1
  183. package/dist/src/chat-commands/session-key.d.ts +4 -37
  184. package/dist/src/chat-commands/session-key.js +49 -85
  185. package/dist/src/chat-commands/session-key.js.map +1 -1
  186. package/dist/src/chat-commands/types.d.ts +2 -0
  187. package/dist/src/cli/commands/agent/interactive.js +2 -2
  188. package/dist/src/cli/commands/agent/interactive.js.map +1 -1
  189. package/dist/src/cli/commands/agent/sessions.js +2 -2
  190. package/dist/src/cli/commands/agent/sessions.js.map +1 -1
  191. package/dist/src/cli/commands/agent.js +4 -5
  192. package/dist/src/cli/commands/agent.js.map +1 -1
  193. package/dist/src/cli/commands/channels.js +1 -5
  194. package/dist/src/cli/commands/channels.js.map +1 -1
  195. package/dist/src/cli/commands/gateway/lifecycle-core.js +1 -1
  196. package/dist/src/cli/commands/gateway/lifecycle-core.js.map +1 -1
  197. package/dist/src/cli/commands/gateway/logs.d.ts +9 -0
  198. package/dist/src/cli/commands/gateway/logs.js +50 -17
  199. package/dist/src/cli/commands/gateway/logs.js.map +1 -1
  200. package/dist/src/cli/commands/image.js +22 -21
  201. package/dist/src/cli/commands/image.js.map +1 -1
  202. package/dist/src/cli/commands/session/utils.js +2 -2
  203. package/dist/src/cli/commands/session/utils.js.map +1 -1
  204. package/dist/src/cli/commands/update.js +26 -46
  205. package/dist/src/cli/commands/update.js.map +1 -1
  206. package/dist/src/cli/utils/session.d.ts +0 -5
  207. package/dist/src/cli/utils/session.js +1 -6
  208. package/dist/src/cli/utils/session.js.map +1 -1
  209. package/dist/src/commands/agents.config.js +1 -1
  210. package/dist/src/commands/agents.config.js.map +1 -1
  211. package/dist/src/config/agent-profile.js +5 -27
  212. package/dist/src/config/agent-profile.js.map +1 -1
  213. package/dist/src/config/index.js +2 -2
  214. package/dist/src/config/model-input.js +2 -5
  215. package/dist/src/config/model-input.js.map +1 -1
  216. package/dist/src/config/schema.d.ts +201 -217
  217. package/dist/src/config/schema.js +54 -39
  218. package/dist/src/config/schema.js.map +1 -1
  219. package/dist/src/config/workspace-path-helpers.d.ts +1 -2
  220. package/dist/src/config/workspace-path-helpers.js.map +1 -1
  221. package/dist/src/daemon/install-plan.js +25 -1
  222. package/dist/src/daemon/install-plan.js.map +1 -1
  223. package/dist/src/daemon/launchd.d.ts +8 -0
  224. package/dist/src/daemon/launchd.js +5 -12
  225. package/dist/src/daemon/launchd.js.map +1 -1
  226. package/dist/src/daemon/schtasks.d.ts +25 -0
  227. package/dist/src/daemon/schtasks.js +166 -46
  228. package/dist/src/daemon/schtasks.js.map +1 -1
  229. package/dist/src/daemon/service.js +5 -4
  230. package/dist/src/daemon/service.js.map +1 -1
  231. package/dist/src/daemon/systemd.d.ts +6 -0
  232. package/dist/src/daemon/systemd.js +18 -3
  233. package/dist/src/daemon/systemd.js.map +1 -1
  234. package/dist/src/extensions/activation-context.js +0 -1
  235. package/dist/src/extensions/activation-context.js.map +1 -1
  236. package/dist/src/extensions/normalize-manifest.js +0 -1
  237. package/dist/src/extensions/normalize-manifest.js.map +1 -1
  238. package/dist/src/extensions/types/manifest.d.ts +0 -2
  239. package/dist/src/gateway/agent-builtin-tools.d.ts +1 -1
  240. package/dist/src/gateway/agent-builtin-tools.js +1 -0
  241. package/dist/src/gateway/agent-builtin-tools.js.map +1 -1
  242. package/dist/src/gateway/agents-admin.js +10 -2
  243. package/dist/src/gateway/agents-admin.js.map +1 -1
  244. package/dist/src/gateway/heartbeat/service.js +1 -1
  245. package/dist/src/gateway/heartbeat/service.js.map +1 -1
  246. package/dist/src/gateway/hono/app.js +1 -1
  247. package/dist/src/gateway/hono/lib/agent-model.d.ts +18 -10
  248. package/dist/src/gateway/hono/lib/agent-model.js +24 -35
  249. package/dist/src/gateway/hono/lib/agent-model.js.map +1 -1
  250. package/dist/src/gateway/hono/lib/config-payload.js +1 -1
  251. package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
  252. package/dist/src/gateway/hono/lib/safe-voice-config.js +14 -53
  253. package/dist/src/gateway/hono/lib/safe-voice-config.js.map +1 -1
  254. package/dist/src/gateway/hono/routes/config-patch/agents.js +17 -5
  255. package/dist/src/gateway/hono/routes/config-patch/agents.js.map +1 -1
  256. package/dist/src/gateway/hono/routes/config-patch/channels.js +0 -11
  257. package/dist/src/gateway/hono/routes/config-patch/channels.js.map +1 -1
  258. package/dist/src/gateway/hono/routes/goals.js +1 -1
  259. package/dist/src/gateway/hono/routes/goals.js.map +1 -1
  260. package/dist/src/gateway/hono/routes/sessions.js +28 -7
  261. package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
  262. package/dist/src/gateway/hono/routes/shares.js +14 -12
  263. package/dist/src/gateway/hono/routes/shares.js.map +1 -1
  264. package/dist/src/gateway/hono/routes/tunnel.js +1 -1
  265. package/dist/src/gateway/hono/routes/update.js +4 -2
  266. package/dist/src/gateway/hono/routes/update.js.map +1 -1
  267. package/dist/src/gateway/hono/sse.js +16 -33
  268. package/dist/src/gateway/hono/sse.js.map +1 -1
  269. package/dist/src/gateway/lock.js +10 -10
  270. package/dist/src/gateway/lock.js.map +1 -1
  271. package/dist/src/gateway/ports.js +6 -6
  272. package/dist/src/gateway/ports.js.map +1 -1
  273. package/dist/src/gateway/resolve-webchat-session-key.d.ts +19 -0
  274. package/dist/src/gateway/resolve-webchat-session-key.js +46 -0
  275. package/dist/src/gateway/resolve-webchat-session-key.js.map +1 -0
  276. package/dist/src/gateway/service/run-gateway-agent.js +27 -11
  277. package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
  278. package/dist/src/gateway/service/sessions-api.d.ts +3 -0
  279. package/dist/src/gateway/service/sessions-api.js +8 -0
  280. package/dist/src/gateway/service/sessions-api.js.map +1 -1
  281. package/dist/src/gateway/service.d.ts +0 -2
  282. package/dist/src/gateway/service.js +2 -7
  283. package/dist/src/gateway/service.js.map +1 -1
  284. package/dist/src/gateway/session-reset-service.d.ts +20 -0
  285. package/dist/src/gateway/session-reset-service.js +54 -0
  286. package/dist/src/gateway/session-reset-service.js.map +1 -0
  287. package/dist/src/gateway/startup-readiness.d.ts +1 -1
  288. package/dist/src/gateway/startup-readiness.js +1 -0
  289. package/dist/src/gateway/startup-readiness.js.map +1 -1
  290. package/dist/src/infra/gateway-processes.js +2 -2
  291. package/dist/src/infra/gateway-processes.js.map +1 -1
  292. package/dist/src/infra/run-command.d.ts +16 -0
  293. package/dist/src/infra/run-command.js +67 -0
  294. package/dist/src/infra/run-command.js.map +1 -0
  295. package/dist/src/infra/update-global.d.ts +45 -0
  296. package/dist/src/infra/update-global.js +224 -0
  297. package/dist/src/infra/update-global.js.map +1 -0
  298. package/dist/src/mcp/channel-bridge.js +1 -1
  299. package/dist/src/mcp/channel-shared.js +2 -1
  300. package/dist/src/mcp/channel-shared.js.map +1 -1
  301. package/dist/src/providers/auth-runtime/auth-profile-store.js +1 -1
  302. package/dist/src/providers/auth-runtime/auth-profile-store.js.map +1 -1
  303. package/dist/src/providers/auth-runtime/resolve-auth.js +1 -12
  304. package/dist/src/providers/auth-runtime/resolve-auth.js.map +1 -1
  305. package/dist/src/providers/auth-runtime/types.d.ts +6 -12
  306. package/dist/src/routing/agent-session-key.d.ts +58 -0
  307. package/dist/src/routing/agent-session-key.js +164 -0
  308. package/dist/src/routing/agent-session-key.js.map +1 -0
  309. package/dist/src/routing/index.d.ts +1 -1
  310. package/dist/src/routing/index.js +4 -2
  311. package/dist/src/routing/index.js.map +1 -1
  312. package/dist/src/routing/resolve-route.d.ts +15 -0
  313. package/dist/src/routing/resolve-route.js +41 -20
  314. package/dist/src/routing/resolve-route.js.map +1 -1
  315. package/dist/src/routing/resolve-tui-session-key.d.ts +25 -0
  316. package/dist/src/routing/resolve-tui-session-key.js +54 -0
  317. package/dist/src/routing/resolve-tui-session-key.js.map +1 -0
  318. package/dist/src/routing/session-key-utils.d.ts +24 -0
  319. package/dist/src/routing/session-key-utils.js +92 -0
  320. package/dist/src/routing/session-key-utils.js.map +1 -0
  321. package/dist/src/routing/session-key.d.ts +19 -49
  322. package/dist/src/routing/session-key.js +143 -116
  323. package/dist/src/routing/session-key.js.map +1 -1
  324. package/dist/src/session/index.d.ts +6 -0
  325. package/dist/src/session/index.js +7 -1
  326. package/dist/src/session/init-session-turn.d.ts +30 -0
  327. package/dist/src/session/init-session-turn.js +102 -0
  328. package/dist/src/session/init-session-turn.js.map +1 -0
  329. package/dist/src/session/lifecycle-timestamps.d.ts +8 -0
  330. package/dist/src/session/lifecycle-timestamps.js +16 -0
  331. package/dist/src/session/lifecycle-timestamps.js.map +1 -0
  332. package/dist/src/session/manager.d.ts +7 -1
  333. package/dist/src/session/manager.js +8 -1
  334. package/dist/src/session/manager.js.map +1 -1
  335. package/dist/src/session/parity/transcript-paths.js +2 -2
  336. package/dist/src/session/parity/transcript-paths.js.map +1 -1
  337. package/dist/src/session/parity/xopc-session-disk-entry.d.ts +6 -0
  338. package/dist/src/session/reset-policy.d.ts +32 -0
  339. package/dist/src/session/reset-policy.js +65 -0
  340. package/dist/src/session/reset-policy.js.map +1 -0
  341. package/dist/src/session/reset-triggers.d.ts +20 -0
  342. package/dist/src/session/reset-triggers.js +63 -0
  343. package/dist/src/session/reset-triggers.js.map +1 -0
  344. package/dist/src/session/reset-type.d.ts +12 -0
  345. package/dist/src/session/reset-type.js +25 -0
  346. package/dist/src/session/reset-type.js.map +1 -0
  347. package/dist/src/session/resolve-session.d.ts +30 -0
  348. package/dist/src/session/resolve-session.js +93 -0
  349. package/dist/src/session/resolve-session.js.map +1 -0
  350. package/dist/src/session/session-title.js +3 -2
  351. package/dist/src/session/session-title.js.map +1 -1
  352. package/dist/src/session/store.d.ts +11 -4
  353. package/dist/src/session/store.js +57 -6
  354. package/dist/src/session/store.js.map +1 -1
  355. package/dist/src/session/transcript-events.js +2 -1
  356. package/dist/src/session/transcript-events.js.map +1 -1
  357. package/dist/src/share/share-url.d.ts +33 -0
  358. package/dist/src/share/share-url.js +56 -14
  359. package/dist/src/share/share-url.js.map +1 -1
  360. package/dist/src/tui/backends/embedded-backend.js +4 -9
  361. package/dist/src/tui/backends/embedded-backend.js.map +1 -1
  362. package/dist/src/tui/backends/gateway-sse-backend.js +1 -1
  363. package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
  364. package/dist/src/tui/components/chat-log.js +3 -3
  365. package/dist/src/tui/components/chat-log.js.map +1 -1
  366. package/dist/src/tui/theme.d.ts +0 -2
  367. package/dist/src/tui/theme.js +1 -3
  368. package/dist/src/tui/theme.js.map +1 -1
  369. package/dist/src/tui/tui-commands.d.ts +3 -0
  370. package/dist/src/tui/tui-commands.js +45 -10
  371. package/dist/src/tui/tui-commands.js.map +1 -1
  372. package/dist/src/tui/tui-keybindings-file.js +1 -21
  373. package/dist/src/tui/tui-keybindings-file.js.map +1 -1
  374. package/dist/src/tui/tui-session-actions.d.ts +28 -0
  375. package/dist/src/tui/tui-session-actions.js +88 -0
  376. package/dist/src/tui/tui-session-actions.js.map +1 -0
  377. package/dist/src/tui/tui.js +52 -47
  378. package/dist/src/tui/tui.js.map +1 -1
  379. package/dist/src/utils/string-coerce.d.ts +2 -0
  380. package/dist/src/utils/string-coerce.js +10 -1
  381. package/dist/src/utils/string-coerce.js.map +1 -1
  382. package/dist/src/voice/stt/config-slice.d.ts +2 -5
  383. package/dist/src/voice/stt/config-slice.js +5 -26
  384. package/dist/src/voice/stt/config-slice.js.map +1 -1
  385. package/dist/src/voice/stt/types.d.ts +1 -18
  386. package/dist/src/voice/stt/types.js +4 -2
  387. package/dist/src/voice/stt/types.js.map +1 -1
  388. package/dist/src/voice/tts/config-slice.d.ts +3 -7
  389. package/dist/src/voice/tts/config-slice.js +7 -38
  390. package/dist/src/voice/tts/config-slice.js.map +1 -1
  391. package/dist/src/voice/tts/merge-config.js +2 -48
  392. package/dist/src/voice/tts/merge-config.js.map +1 -1
  393. package/dist/src/voice/tts/providers/alibaba-speech.js +1 -1
  394. package/dist/src/voice/tts/providers/alibaba-speech.js.map +1 -1
  395. package/dist/src/voice/tts/types.d.ts +1 -29
  396. package/dist/src/voice/tts/types.js +19 -17
  397. package/dist/src/voice/tts/types.js.map +1 -1
  398. package/package.json +1 -4
  399. package/dist/gateway/static/root/assets/agents-D3_-kNlZ.js +0 -222
  400. package/dist/gateway/static/root/assets/extension-settings-page-8PZcmWI7.js +0 -1
  401. package/dist/gateway/static/root/assets/index-ew_2L2We.css +0 -1
  402. package/dist/gateway/static/root/assets/settings-form-section-D_tgb8r2.js +0 -1
  403. package/dist/gateway/static/root/assets/settings-page-C18xBt4X.js +0 -3
  404. package/dist/gateway/static/root/assets/url-BwNL6Rgk.js +0 -3
  405. package/dist/src/agent/tools/browser-legacy-tools.d.ts +0 -17
  406. package/dist/src/agent/tools/browser-legacy-tools.js +0 -766
  407. package/dist/src/agent/tools/browser-legacy-tools.js.map +0 -1
@@ -4,7 +4,7 @@ import { MessageItemType } from "../api/types.js";
4
4
  import { canonicalWeixinPeerId, normalizeWeixinAccountId } from "../auth/weixin-account-id.js";
5
5
  import { resolveWeixinRootDir } from "../storage/state-dir.js";
6
6
  import path from "node:path";
7
- import fsSync from "node:fs";
7
+ import fs from "node:fs";
8
8
  //#region extensions/weixin/src/messaging/inbound.ts
9
9
  /**
10
10
  * contextToken is issued per-message by the Weixin getupdates API and must
@@ -66,8 +66,8 @@ function persistContextTokens(accountId) {
66
66
  const filePath = canonicalContextTokenFilePath(accountId);
67
67
  try {
68
68
  const dir = path.dirname(filePath);
69
- fsSync.mkdirSync(dir, { recursive: true });
70
- fsSync.writeFileSync(filePath, JSON.stringify(tokens, null, 0), "utf-8");
69
+ fs.mkdirSync(dir, { recursive: true });
70
+ fs.writeFileSync(filePath, JSON.stringify(tokens, null, 0), "utf-8");
71
71
  } catch (err) {
72
72
  logger.warn(`persistContextTokens: failed to write ${filePath}: ${String(err)}`);
73
73
  }
@@ -81,8 +81,8 @@ function restoreContextTokens(accountId) {
81
81
  const norm = normalizeWeixinAccountId(accountId);
82
82
  const filePath = canonicalContextTokenFilePath(accountId);
83
83
  try {
84
- if (fsSync.existsSync(filePath)) {
85
- const raw = fsSync.readFileSync(filePath, "utf-8");
84
+ if (fs.existsSync(filePath)) {
85
+ const raw = fs.readFileSync(filePath, "utf-8");
86
86
  const tokens = JSON.parse(raw);
87
87
  for (const [userId, val] of Object.entries(tokens)) {
88
88
  const entry = parsePersistedPeerVal(val);
@@ -104,7 +104,7 @@ function clearContextTokensForAccount(accountId) {
104
104
  for (const k of [...contextTokenStore.keys()]) if (k.startsWith(prefix)) contextTokenStore.delete(k);
105
105
  try {
106
106
  const filePath = canonicalContextTokenFilePath(accountId);
107
- if (fsSync.existsSync(filePath)) fsSync.unlinkSync(filePath);
107
+ if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
108
108
  } catch (err) {
109
109
  logger.warn(`clearContextTokensForAccount: failed to remove context tokens file: ${String(err)}`);
110
110
  }
@@ -114,9 +114,9 @@ function tryHydratePeerEntryFromDisk(accountId, userId) {
114
114
  const peer = normalizeIlinkUserIdForContext(userId);
115
115
  const norm = normalizeWeixinAccountId(accountId);
116
116
  const filePath = canonicalContextTokenFilePath(accountId);
117
- if (!fsSync.existsSync(filePath)) return void 0;
117
+ if (!fs.existsSync(filePath)) return void 0;
118
118
  try {
119
- const raw = fsSync.readFileSync(filePath, "utf-8");
119
+ const raw = fs.readFileSync(filePath, "utf-8");
120
120
  const tokens = JSON.parse(raw);
121
121
  for (const [jsonKey, val] of Object.entries(tokens)) {
122
122
  const entry = parsePersistedPeerVal(val);
@@ -198,11 +198,11 @@ function findContextTokenEntriesByPeer(peerUserId) {
198
198
  function tryHydrateAnyAccountContextTokenFromDisk(peerUserId) {
199
199
  const peer = normalizeIlinkUserIdForContext(peerUserId);
200
200
  const accountsDir = path.join(resolveWeixinRootDir(), "accounts");
201
- if (!fsSync.existsSync(accountsDir)) return void 0;
201
+ if (!fs.existsSync(accountsDir)) return void 0;
202
202
  const hits = [];
203
203
  let names;
204
204
  try {
205
- names = fsSync.readdirSync(accountsDir);
205
+ names = fs.readdirSync(accountsDir);
206
206
  } catch {
207
207
  return;
208
208
  }
@@ -212,7 +212,7 @@ function tryHydrateAnyAccountContextTokenFromDisk(peerUserId) {
212
212
  const accountFromFile = name.slice(0, -20);
213
213
  const filePath = path.join(accountsDir, name);
214
214
  try {
215
- const raw = fsSync.readFileSync(filePath, "utf-8");
215
+ const raw = fs.readFileSync(filePath, "utf-8");
216
216
  const tokens = JSON.parse(raw);
217
217
  for (const [jsonKey, val] of Object.entries(tokens)) {
218
218
  const entry = parsePersistedPeerVal(val);
@@ -1 +1 @@
1
- {"version":3,"file":"inbound.js","names":["fs"],"sources":["../../../../../extensions/weixin/src/messaging/inbound.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { logger } from \"../util/logger.js\";\nimport { generateId } from \"../util/random.js\";\nimport type { WeixinMessage, MessageItem } from \"../api/types.js\";\nimport { MessageItemType } from \"../api/types.js\";\nimport { canonicalWeixinPeerId, normalizeWeixinAccountId } from \"../auth/weixin-account-id.js\";\nimport { resolveWeixinRootDir } from \"../storage/state-dir.js\";\n\n// ---------------------------------------------------------------------------\n// Context token store (in-process cache + disk persistence)\n// ---------------------------------------------------------------------------\n\ntype WeixinContextTokenEntry = { token: string; sendTo?: string };\n\n/**\n * contextToken is issued per-message by the Weixin getupdates API and must\n * be echoed verbatim in every outbound send. The in-memory map is the primary\n * lookup; a disk-backed file per account ensures tokens survive gateway restarts.\n *\n * `sendTo` is the last inbound `from_user_id` verbatim. ilink often requires `to_user_id`\n * in sendmessage to match that string exactly (session/cron peer ids are sanitized).\n */\nconst contextTokenStore = new Map<string, WeixinContextTokenEntry>();\n\nfunction parsePersistedPeerVal(val: unknown): WeixinContextTokenEntry | null {\n if (typeof val === \"string\" && val.trim()) {\n return { token: val.trim() };\n }\n if (val && typeof val === \"object\" && \"token\" in val) {\n const o = val as { token: unknown; sendTo?: unknown };\n if (typeof o.token !== \"string\" || !o.token.trim()) return null;\n const sendTo = typeof o.sendTo === \"string\" && o.sendTo.trim() ? o.sendTo.trim() : undefined;\n return { token: o.token.trim(), ...(sendTo ? { sendTo } : {}) };\n }\n return null;\n}\n\n/**\n * Session keys use {@link canonicalWeixinPeerId} (same as `buildSessionKey`); cron `delivery.to`\n * and outbound `ctx.to` use that shape. ilink `from_user_id` is often `…@im.wechat` while the\n * session peer is `…-im-wechat`. Normalize so context_token cache keys match lookups.\n */\nfunction normalizeIlinkUserIdForContext(userId: string): string {\n return canonicalWeixinPeerId(userId);\n}\n\n/** ilink may use a shorter openid in one path and a suffixed id in another; treat as same peer when unambiguous. */\nfunction ilinkPeerKeysLikelySame(storedKey: string, queryPeer: string): boolean {\n const a = normalizeIlinkUserIdForContext(storedKey);\n const b = normalizeIlinkUserIdForContext(queryPeer);\n if (a === b) return true;\n if (a.length < 12 || b.length < 12) return false;\n return a.startsWith(b) || b.startsWith(a);\n}\n\nfunction contextTokenKey(accountId: string, userId: string): string {\n return `${normalizeWeixinAccountId(accountId)}:${normalizeIlinkUserIdForContext(userId)}`;\n}\n\n// ---------------------------------------------------------------------------\n// Disk persistence helpers\n// ---------------------------------------------------------------------------\n\nfunction canonicalContextTokenFilePath(accountId: string): string {\n const norm = normalizeWeixinAccountId(accountId);\n return path.join(resolveWeixinRootDir(), \"accounts\", `${norm}.context-tokens.json`);\n}\n\n/** Persist all context tokens for a given account to disk. */\nfunction persistContextTokens(accountId: string): void {\n const norm = normalizeWeixinAccountId(accountId);\n const prefix = `${norm}:`;\n const tokens: Record<string, string | { token: string; sendTo?: string }> = {};\n for (const [k, v] of contextTokenStore) {\n if (k.startsWith(prefix)) {\n const peerSuffix = k.slice(prefix.length);\n if (v.sendTo?.trim()) {\n tokens[peerSuffix] = { token: v.token, sendTo: v.sendTo.trim() };\n } else {\n tokens[peerSuffix] = v.token;\n }\n }\n }\n const filePath = canonicalContextTokenFilePath(accountId);\n try {\n const dir = path.dirname(filePath);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(filePath, JSON.stringify(tokens, null, 0), \"utf-8\");\n } catch (err) {\n logger.warn(`persistContextTokens: failed to write ${filePath}: ${String(err)}`);\n }\n}\n\n/**\n * Restore persisted context tokens for an account into the in-memory map.\n * Called once during gateway startAccount to survive restarts.\n */\nexport function restoreContextTokens(accountId: string): void {\n let count = 0;\n const norm = normalizeWeixinAccountId(accountId);\n const filePath = canonicalContextTokenFilePath(accountId);\n try {\n if (fs.existsSync(filePath)) {\n const raw = fs.readFileSync(filePath, \"utf-8\");\n const tokens = JSON.parse(raw) as Record<string, unknown>;\n for (const [userId, val] of Object.entries(tokens)) {\n const entry = parsePersistedPeerVal(val);\n if (entry?.token) {\n contextTokenStore.set(contextTokenKey(norm, userId), entry);\n count++;\n }\n }\n }\n } catch (err) {\n logger.warn(`restoreContextTokens: failed to read ${filePath}: ${String(err)}`);\n }\n if (count > 0) {\n logger.info(`restoreContextTokens: restored ${count} tokens for account=${norm}`);\n }\n}\n\n/** Remove all context tokens for a given account (memory + disk). */\nexport function clearContextTokensForAccount(accountId: string): void {\n const norm = normalizeWeixinAccountId(accountId);\n const prefix = `${norm}:`;\n for (const k of [...contextTokenStore.keys()]) {\n if (k.startsWith(prefix)) {\n contextTokenStore.delete(k);\n }\n }\n try {\n const filePath = canonicalContextTokenFilePath(accountId);\n if (fs.existsSync(filePath)) fs.unlinkSync(filePath);\n } catch (err) {\n logger.warn(`clearContextTokensForAccount: failed to remove context tokens file: ${String(err)}`);\n }\n logger.info(`clearContextTokensForAccount: cleared tokens for account=${norm}`);\n}\n\nfunction tryHydratePeerEntryFromDisk(accountId: string, userId: string): WeixinContextTokenEntry | undefined {\n const peer = normalizeIlinkUserIdForContext(userId);\n const norm = normalizeWeixinAccountId(accountId);\n const filePath = canonicalContextTokenFilePath(accountId);\n if (!fs.existsSync(filePath)) return undefined;\n try {\n const raw = fs.readFileSync(filePath, \"utf-8\");\n const tokens = JSON.parse(raw) as Record<string, unknown>;\n for (const [jsonKey, val] of Object.entries(tokens)) {\n const entry = parsePersistedPeerVal(val);\n if (!entry?.token) continue;\n if (!ilinkPeerKeysLikelySame(jsonKey, peer)) continue;\n contextTokenStore.set(contextTokenKey(norm, peer), entry);\n persistContextTokens(accountId);\n return entry;\n }\n } catch (err) {\n logger.debug(`tryHydratePeerEntryFromDisk: ${filePath}: ${String(err)}`);\n }\n return undefined;\n}\n\nfunction getWeixinContextPeerEntry(\n accountId: string,\n userId: string,\n): WeixinContextTokenEntry | undefined {\n const k = contextTokenKey(accountId, userId);\n let e = contextTokenStore.get(k);\n if (e?.token) {\n return e;\n }\n const hydrated = tryHydratePeerEntryFromDisk(accountId, userId);\n if (hydrated) return hydrated;\n return contextTokenStore.get(k);\n}\n\n/** Last inbound `from_user_id` for API `to_user_id` (verbatim), when known. */\nexport function getWeixinOutboundSendUserId(accountId: string, userId: string): string | undefined {\n return getWeixinContextPeerEntry(accountId, userId)?.sendTo?.trim() || undefined;\n}\n\n/** Store a context token for a given account+user pair (memory + disk). */\nexport function setContextToken(\n accountId: string,\n userId: string,\n token: string,\n options?: { sendToUserId?: string },\n): void {\n const k = contextTokenKey(accountId, userId);\n const prev = contextTokenStore.get(k);\n const sendTo =\n options && options.sendToUserId !== undefined\n ? options.sendToUserId.trim() || undefined\n : prev?.sendTo;\n const next: WeixinContextTokenEntry = { token, ...(sendTo ? { sendTo } : {}) };\n logger.debug(`setContextToken: key=${k} sendTo=${sendTo ? \"yes\" : \"no\"}`);\n contextTokenStore.set(k, next);\n persistContextTokens(accountId);\n}\n\n/** Retrieve the cached context token for a given account+user pair. */\nexport function getContextToken(accountId: string, userId: string): string | undefined {\n const k = contextTokenKey(accountId, userId);\n const e = getWeixinContextPeerEntry(accountId, userId);\n logger.debug(\n `getContextToken: key=${k} found=${e?.token !== undefined} storeSize=${contextTokenStore.size}`,\n );\n return e?.token;\n}\n\n/**\n * Find all accountIds that have an active contextToken for the given userId.\n * Used to infer the sending bot account from the recipient address when\n * accountId is not explicitly provided (e.g. cron delivery).\n *\n * Returns all matching accountIds (not just the first) so the caller can\n * detect ambiguity when multiple accounts have sessions with the same user.\n */\nexport function findAccountIdsByContextToken(\n accountIds: string[],\n userId: string,\n): string[] {\n return accountIds.filter((id) => contextTokenStore.has(contextTokenKey(id, userId)));\n}\n\n/** In-memory entries whose peer id matches (exact or ilink prefix/suffix variant). */\nexport function findContextTokenEntriesByPeer(\n peerUserId: string,\n): Array<{ accountId: string; token: string; sendTo?: string }> {\n const peer = normalizeIlinkUserIdForContext(peerUserId);\n const out: Array<{ accountId: string; token: string; sendTo?: string }> = [];\n for (const [k, entry] of contextTokenStore) {\n if (!entry?.token?.trim()) continue;\n const idx = k.indexOf(':');\n if (idx <= 0 || idx >= k.length - 1) continue;\n const accountId = k.slice(0, idx);\n const storedPeer = k.slice(idx + 1);\n if (!ilinkPeerKeysLikelySame(storedPeer, peer)) continue;\n if (accountId) {\n out.push({\n accountId,\n token: entry.token,\n ...(entry.sendTo?.trim() ? { sendTo: entry.sendTo.trim() } : {}),\n });\n }\n }\n return out;\n}\n\n/**\n * When the preferred account id does not match the token filename, scan every\n * `accounts/*.context-tokens.json` for this peer (case-insensitive user key).\n */\nexport function tryHydrateAnyAccountContextTokenFromDisk(\n peerUserId: string,\n): { accountId: string; token: string; sendTo?: string } | undefined {\n const peer = normalizeIlinkUserIdForContext(peerUserId);\n const accountsDir = path.join(resolveWeixinRootDir(), 'accounts');\n if (!fs.existsSync(accountsDir)) return undefined;\n const hits: Array<{ accountId: string; token: string; sendTo?: string }> = [];\n let names: string[];\n try {\n names = fs.readdirSync(accountsDir);\n } catch {\n return undefined;\n }\n const suffix = '.context-tokens.json';\n for (const name of names) {\n if (!name.endsWith(suffix)) continue;\n const accountFromFile = name.slice(0, -suffix.length);\n const filePath = path.join(accountsDir, name);\n try {\n const raw = fs.readFileSync(filePath, 'utf-8');\n const tokens = JSON.parse(raw) as Record<string, unknown>;\n for (const [jsonKey, val] of Object.entries(tokens)) {\n const entry = parsePersistedPeerVal(val);\n if (!entry?.token) continue;\n if (!ilinkPeerKeysLikelySame(jsonKey, peer)) continue;\n hits.push({\n accountId: normalizeWeixinAccountId(accountFromFile),\n token: entry.token,\n ...(entry.sendTo?.trim() ? { sendTo: entry.sendTo.trim() } : {}),\n });\n }\n } catch {\n /* skip corrupt file */\n }\n }\n if (hits.length === 0) return undefined;\n const byAccount = new Map<string, { token: string; sendTo?: string }>();\n for (const h of hits) {\n if (!byAccount.has(h.accountId)) byAccount.set(h.accountId, { token: h.token, sendTo: h.sendTo });\n }\n if (byAccount.size > 1) return undefined;\n const [accountId, hit] = [...byAccount.entries()][0]!;\n setContextToken(accountId, peer, hit.token, hit.sendTo ? { sendToUserId: hit.sendTo } : undefined);\n return { accountId, token: hit.token, sendTo: hit.sendTo };\n}\n\n// ---------------------------------------------------------------------------\n// Message ID generation\n// ---------------------------------------------------------------------------\n\nfunction generateMessageSid(): string {\n return generateId(\"weixin\");\n}\n\n/** Inbound context for the core message pipeline (matches MsgContext shape). */\nexport type WeixinMsgContext = {\n Body: string;\n From: string;\n To: string;\n AccountId: string;\n OriginatingChannel: \"weixin\";\n OriginatingTo: string;\n MessageSid: string;\n Timestamp?: number;\n Provider: \"weixin\";\n ChatType: \"direct\";\n /** Set by monitor after resolveAgentRoute so dispatchReplyFromConfig uses the correct session. */\n SessionKey?: string;\n context_token?: string;\n MediaUrl?: string;\n MediaPath?: string;\n MediaType?: string;\n /** Raw message body for framework command authorization. */\n CommandBody?: string;\n /** Whether the sender is authorized to execute slash commands. */\n CommandAuthorized?: boolean;\n};\n\n/** Returns true if the message item is a media type (image, video, file, or voice). */\nexport function isMediaItem(item: MessageItem): boolean {\n return (\n item.type === MessageItemType.IMAGE ||\n item.type === MessageItemType.VIDEO ||\n item.type === MessageItemType.FILE ||\n item.type === MessageItemType.VOICE\n );\n}\n\nfunction bodyFromItemList(itemList?: MessageItem[]): string {\n if (!itemList?.length) return \"\";\n for (const item of itemList) {\n if (item.type === MessageItemType.TEXT && item.text_item?.text != null) {\n const text = String(item.text_item.text);\n const ref = item.ref_msg;\n if (!ref) return text;\n // Quoted media is passed as MediaPath; only include the current text as body.\n if (ref.message_item && isMediaItem(ref.message_item)) return text;\n // Build quoted context from both title and message_item content.\n const parts: string[] = [];\n if (ref.title) parts.push(ref.title);\n if (ref.message_item) {\n const refBody = bodyFromItemList([ref.message_item]);\n if (refBody) parts.push(refBody);\n }\n if (!parts.length) return text;\n return `[引用: ${parts.join(\" | \")}]\\n${text}`;\n }\n // 语音转文字:如果语音消息有 text 字段,直接使用文字内容\n if (item.type === MessageItemType.VOICE && item.voice_item?.text) {\n return item.voice_item.text;\n }\n }\n return \"\";\n}\n\nexport type WeixinInboundMediaOpts = {\n /** Local path to decrypted image file. */\n decryptedPicPath?: string;\n /** Local path to transcoded/raw voice file (.wav or .silk). */\n decryptedVoicePath?: string;\n /** MIME type for the voice file (e.g. \"audio/wav\" or \"audio/silk\"). */\n voiceMediaType?: string;\n /** Local path to decrypted file attachment. */\n decryptedFilePath?: string;\n /** MIME type for the file attachment (guessed from file_name). */\n fileMediaType?: string;\n /** Local path to decrypted video file. */\n decryptedVideoPath?: string;\n};\n\n/**\n * Convert a WeixinMessage from getUpdates to the inbound MsgContext for the core pipeline.\n * Media: only pass MediaPath (local file, after CDN download + decrypt).\n * We never pass MediaUrl — the upstream CDN URL is encrypted/auth-only.\n * Priority when multiple media types present: image > video > file > voice.\n */\nexport function weixinMessageToMsgContext(\n msg: WeixinMessage,\n accountId: string,\n opts?: WeixinInboundMediaOpts,\n): WeixinMsgContext {\n const from_user_id = msg.from_user_id ?? \"\";\n const ctx: WeixinMsgContext = {\n Body: bodyFromItemList(msg.item_list),\n From: from_user_id,\n To: from_user_id,\n AccountId: accountId,\n OriginatingChannel: \"weixin\",\n OriginatingTo: from_user_id,\n MessageSid: generateMessageSid(),\n Timestamp: msg.create_time_ms,\n Provider: \"weixin\",\n ChatType: \"direct\",\n };\n if (msg.context_token) {\n ctx.context_token = msg.context_token;\n }\n\n if (opts?.decryptedPicPath) {\n ctx.MediaPath = opts.decryptedPicPath;\n ctx.MediaType = \"image/*\";\n } else if (opts?.decryptedVideoPath) {\n ctx.MediaPath = opts.decryptedVideoPath;\n ctx.MediaType = \"video/mp4\";\n } else if (opts?.decryptedFilePath) {\n ctx.MediaPath = opts.decryptedFilePath;\n ctx.MediaType = opts.fileMediaType ?? \"application/octet-stream\";\n } else if (opts?.decryptedVoicePath) {\n ctx.MediaPath = opts.decryptedVoicePath;\n ctx.MediaType = opts.voiceMediaType ?? \"audio/wav\";\n }\n\n return ctx;\n}\n\n/** Extract the context_token from an inbound WeixinMsgContext. */\nexport function getContextTokenFromMsgContext(ctx: WeixinMsgContext): string | undefined {\n return ctx.context_token;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"inbound.js","names":[],"sources":["../../../../../extensions/weixin/src/messaging/inbound.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { logger } from \"../util/logger.js\";\nimport { generateId } from \"../util/random.js\";\nimport type { WeixinMessage, MessageItem } from \"../api/types.js\";\nimport { MessageItemType } from \"../api/types.js\";\nimport { canonicalWeixinPeerId, normalizeWeixinAccountId } from \"../auth/weixin-account-id.js\";\nimport { resolveWeixinRootDir } from \"../storage/state-dir.js\";\n\n// ---------------------------------------------------------------------------\n// Context token store (in-process cache + disk persistence)\n// ---------------------------------------------------------------------------\n\ntype WeixinContextTokenEntry = { token: string; sendTo?: string };\n\n/**\n * contextToken is issued per-message by the Weixin getupdates API and must\n * be echoed verbatim in every outbound send. The in-memory map is the primary\n * lookup; a disk-backed file per account ensures tokens survive gateway restarts.\n *\n * `sendTo` is the last inbound `from_user_id` verbatim. ilink often requires `to_user_id`\n * in sendmessage to match that string exactly (session/cron peer ids are sanitized).\n */\nconst contextTokenStore = new Map<string, WeixinContextTokenEntry>();\n\nfunction parsePersistedPeerVal(val: unknown): WeixinContextTokenEntry | null {\n if (typeof val === \"string\" && val.trim()) {\n return { token: val.trim() };\n }\n if (val && typeof val === \"object\" && \"token\" in val) {\n const o = val as { token: unknown; sendTo?: unknown };\n if (typeof o.token !== \"string\" || !o.token.trim()) return null;\n const sendTo = typeof o.sendTo === \"string\" && o.sendTo.trim() ? o.sendTo.trim() : undefined;\n return { token: o.token.trim(), ...(sendTo ? { sendTo } : {}) };\n }\n return null;\n}\n\n/**\n * Session keys use {@link canonicalWeixinPeerId} (same as `buildSessionKey`); cron `delivery.to`\n * and outbound `ctx.to` use that shape. ilink `from_user_id` is often `…@im.wechat` while the\n * session peer is `…-im-wechat`. Normalize so context_token cache keys match lookups.\n */\nfunction normalizeIlinkUserIdForContext(userId: string): string {\n return canonicalWeixinPeerId(userId);\n}\n\n/** ilink may use a shorter openid in one path and a suffixed id in another; treat as same peer when unambiguous. */\nfunction ilinkPeerKeysLikelySame(storedKey: string, queryPeer: string): boolean {\n const a = normalizeIlinkUserIdForContext(storedKey);\n const b = normalizeIlinkUserIdForContext(queryPeer);\n if (a === b) return true;\n if (a.length < 12 || b.length < 12) return false;\n return a.startsWith(b) || b.startsWith(a);\n}\n\nfunction contextTokenKey(accountId: string, userId: string): string {\n return `${normalizeWeixinAccountId(accountId)}:${normalizeIlinkUserIdForContext(userId)}`;\n}\n\n// ---------------------------------------------------------------------------\n// Disk persistence helpers\n// ---------------------------------------------------------------------------\n\nfunction canonicalContextTokenFilePath(accountId: string): string {\n const norm = normalizeWeixinAccountId(accountId);\n return path.join(resolveWeixinRootDir(), \"accounts\", `${norm}.context-tokens.json`);\n}\n\n/** Persist all context tokens for a given account to disk. */\nfunction persistContextTokens(accountId: string): void {\n const norm = normalizeWeixinAccountId(accountId);\n const prefix = `${norm}:`;\n const tokens: Record<string, string | { token: string; sendTo?: string }> = {};\n for (const [k, v] of contextTokenStore) {\n if (k.startsWith(prefix)) {\n const peerSuffix = k.slice(prefix.length);\n if (v.sendTo?.trim()) {\n tokens[peerSuffix] = { token: v.token, sendTo: v.sendTo.trim() };\n } else {\n tokens[peerSuffix] = v.token;\n }\n }\n }\n const filePath = canonicalContextTokenFilePath(accountId);\n try {\n const dir = path.dirname(filePath);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(filePath, JSON.stringify(tokens, null, 0), \"utf-8\");\n } catch (err) {\n logger.warn(`persistContextTokens: failed to write ${filePath}: ${String(err)}`);\n }\n}\n\n/**\n * Restore persisted context tokens for an account into the in-memory map.\n * Called once during gateway startAccount to survive restarts.\n */\nexport function restoreContextTokens(accountId: string): void {\n let count = 0;\n const norm = normalizeWeixinAccountId(accountId);\n const filePath = canonicalContextTokenFilePath(accountId);\n try {\n if (fs.existsSync(filePath)) {\n const raw = fs.readFileSync(filePath, \"utf-8\");\n const tokens = JSON.parse(raw) as Record<string, unknown>;\n for (const [userId, val] of Object.entries(tokens)) {\n const entry = parsePersistedPeerVal(val);\n if (entry?.token) {\n contextTokenStore.set(contextTokenKey(norm, userId), entry);\n count++;\n }\n }\n }\n } catch (err) {\n logger.warn(`restoreContextTokens: failed to read ${filePath}: ${String(err)}`);\n }\n if (count > 0) {\n logger.info(`restoreContextTokens: restored ${count} tokens for account=${norm}`);\n }\n}\n\n/** Remove all context tokens for a given account (memory + disk). */\nexport function clearContextTokensForAccount(accountId: string): void {\n const norm = normalizeWeixinAccountId(accountId);\n const prefix = `${norm}:`;\n for (const k of [...contextTokenStore.keys()]) {\n if (k.startsWith(prefix)) {\n contextTokenStore.delete(k);\n }\n }\n try {\n const filePath = canonicalContextTokenFilePath(accountId);\n if (fs.existsSync(filePath)) fs.unlinkSync(filePath);\n } catch (err) {\n logger.warn(`clearContextTokensForAccount: failed to remove context tokens file: ${String(err)}`);\n }\n logger.info(`clearContextTokensForAccount: cleared tokens for account=${norm}`);\n}\n\nfunction tryHydratePeerEntryFromDisk(accountId: string, userId: string): WeixinContextTokenEntry | undefined {\n const peer = normalizeIlinkUserIdForContext(userId);\n const norm = normalizeWeixinAccountId(accountId);\n const filePath = canonicalContextTokenFilePath(accountId);\n if (!fs.existsSync(filePath)) return undefined;\n try {\n const raw = fs.readFileSync(filePath, \"utf-8\");\n const tokens = JSON.parse(raw) as Record<string, unknown>;\n for (const [jsonKey, val] of Object.entries(tokens)) {\n const entry = parsePersistedPeerVal(val);\n if (!entry?.token) continue;\n if (!ilinkPeerKeysLikelySame(jsonKey, peer)) continue;\n contextTokenStore.set(contextTokenKey(norm, peer), entry);\n persistContextTokens(accountId);\n return entry;\n }\n } catch (err) {\n logger.debug(`tryHydratePeerEntryFromDisk: ${filePath}: ${String(err)}`);\n }\n return undefined;\n}\n\nfunction getWeixinContextPeerEntry(\n accountId: string,\n userId: string,\n): WeixinContextTokenEntry | undefined {\n const k = contextTokenKey(accountId, userId);\n let e = contextTokenStore.get(k);\n if (e?.token) {\n return e;\n }\n const hydrated = tryHydratePeerEntryFromDisk(accountId, userId);\n if (hydrated) return hydrated;\n return contextTokenStore.get(k);\n}\n\n/** Last inbound `from_user_id` for API `to_user_id` (verbatim), when known. */\nexport function getWeixinOutboundSendUserId(accountId: string, userId: string): string | undefined {\n return getWeixinContextPeerEntry(accountId, userId)?.sendTo?.trim() || undefined;\n}\n\n/** Store a context token for a given account+user pair (memory + disk). */\nexport function setContextToken(\n accountId: string,\n userId: string,\n token: string,\n options?: { sendToUserId?: string },\n): void {\n const k = contextTokenKey(accountId, userId);\n const prev = contextTokenStore.get(k);\n const sendTo =\n options && options.sendToUserId !== undefined\n ? options.sendToUserId.trim() || undefined\n : prev?.sendTo;\n const next: WeixinContextTokenEntry = { token, ...(sendTo ? { sendTo } : {}) };\n logger.debug(`setContextToken: key=${k} sendTo=${sendTo ? \"yes\" : \"no\"}`);\n contextTokenStore.set(k, next);\n persistContextTokens(accountId);\n}\n\n/** Retrieve the cached context token for a given account+user pair. */\nexport function getContextToken(accountId: string, userId: string): string | undefined {\n const k = contextTokenKey(accountId, userId);\n const e = getWeixinContextPeerEntry(accountId, userId);\n logger.debug(\n `getContextToken: key=${k} found=${e?.token !== undefined} storeSize=${contextTokenStore.size}`,\n );\n return e?.token;\n}\n\n/**\n * Find all accountIds that have an active contextToken for the given userId.\n * Used to infer the sending bot account from the recipient address when\n * accountId is not explicitly provided (e.g. cron delivery).\n *\n * Returns all matching accountIds (not just the first) so the caller can\n * detect ambiguity when multiple accounts have sessions with the same user.\n */\nexport function findAccountIdsByContextToken(\n accountIds: string[],\n userId: string,\n): string[] {\n return accountIds.filter((id) => contextTokenStore.has(contextTokenKey(id, userId)));\n}\n\n/** In-memory entries whose peer id matches (exact or ilink prefix/suffix variant). */\nexport function findContextTokenEntriesByPeer(\n peerUserId: string,\n): Array<{ accountId: string; token: string; sendTo?: string }> {\n const peer = normalizeIlinkUserIdForContext(peerUserId);\n const out: Array<{ accountId: string; token: string; sendTo?: string }> = [];\n for (const [k, entry] of contextTokenStore) {\n if (!entry?.token?.trim()) continue;\n const idx = k.indexOf(':');\n if (idx <= 0 || idx >= k.length - 1) continue;\n const accountId = k.slice(0, idx);\n const storedPeer = k.slice(idx + 1);\n if (!ilinkPeerKeysLikelySame(storedPeer, peer)) continue;\n if (accountId) {\n out.push({\n accountId,\n token: entry.token,\n ...(entry.sendTo?.trim() ? { sendTo: entry.sendTo.trim() } : {}),\n });\n }\n }\n return out;\n}\n\n/**\n * When the preferred account id does not match the token filename, scan every\n * `accounts/*.context-tokens.json` for this peer (case-insensitive user key).\n */\nexport function tryHydrateAnyAccountContextTokenFromDisk(\n peerUserId: string,\n): { accountId: string; token: string; sendTo?: string } | undefined {\n const peer = normalizeIlinkUserIdForContext(peerUserId);\n const accountsDir = path.join(resolveWeixinRootDir(), 'accounts');\n if (!fs.existsSync(accountsDir)) return undefined;\n const hits: Array<{ accountId: string; token: string; sendTo?: string }> = [];\n let names: string[];\n try {\n names = fs.readdirSync(accountsDir);\n } catch {\n return undefined;\n }\n const suffix = '.context-tokens.json';\n for (const name of names) {\n if (!name.endsWith(suffix)) continue;\n const accountFromFile = name.slice(0, -suffix.length);\n const filePath = path.join(accountsDir, name);\n try {\n const raw = fs.readFileSync(filePath, 'utf-8');\n const tokens = JSON.parse(raw) as Record<string, unknown>;\n for (const [jsonKey, val] of Object.entries(tokens)) {\n const entry = parsePersistedPeerVal(val);\n if (!entry?.token) continue;\n if (!ilinkPeerKeysLikelySame(jsonKey, peer)) continue;\n hits.push({\n accountId: normalizeWeixinAccountId(accountFromFile),\n token: entry.token,\n ...(entry.sendTo?.trim() ? { sendTo: entry.sendTo.trim() } : {}),\n });\n }\n } catch {\n /* skip corrupt file */\n }\n }\n if (hits.length === 0) return undefined;\n const byAccount = new Map<string, { token: string; sendTo?: string }>();\n for (const h of hits) {\n if (!byAccount.has(h.accountId)) byAccount.set(h.accountId, { token: h.token, sendTo: h.sendTo });\n }\n if (byAccount.size > 1) return undefined;\n const [accountId, hit] = [...byAccount.entries()][0]!;\n setContextToken(accountId, peer, hit.token, hit.sendTo ? { sendToUserId: hit.sendTo } : undefined);\n return { accountId, token: hit.token, sendTo: hit.sendTo };\n}\n\n// ---------------------------------------------------------------------------\n// Message ID generation\n// ---------------------------------------------------------------------------\n\nfunction generateMessageSid(): string {\n return generateId(\"weixin\");\n}\n\n/** Inbound context for the core message pipeline (matches MsgContext shape). */\nexport type WeixinMsgContext = {\n Body: string;\n From: string;\n To: string;\n AccountId: string;\n OriginatingChannel: \"weixin\";\n OriginatingTo: string;\n MessageSid: string;\n Timestamp?: number;\n Provider: \"weixin\";\n ChatType: \"direct\";\n /** Set by monitor after resolveAgentRoute so dispatchReplyFromConfig uses the correct session. */\n SessionKey?: string;\n context_token?: string;\n MediaUrl?: string;\n MediaPath?: string;\n MediaType?: string;\n /** Raw message body for framework command authorization. */\n CommandBody?: string;\n /** Whether the sender is authorized to execute slash commands. */\n CommandAuthorized?: boolean;\n};\n\n/** Returns true if the message item is a media type (image, video, file, or voice). */\nexport function isMediaItem(item: MessageItem): boolean {\n return (\n item.type === MessageItemType.IMAGE ||\n item.type === MessageItemType.VIDEO ||\n item.type === MessageItemType.FILE ||\n item.type === MessageItemType.VOICE\n );\n}\n\nfunction bodyFromItemList(itemList?: MessageItem[]): string {\n if (!itemList?.length) return \"\";\n for (const item of itemList) {\n if (item.type === MessageItemType.TEXT && item.text_item?.text != null) {\n const text = String(item.text_item.text);\n const ref = item.ref_msg;\n if (!ref) return text;\n // Quoted media is passed as MediaPath; only include the current text as body.\n if (ref.message_item && isMediaItem(ref.message_item)) return text;\n // Build quoted context from both title and message_item content.\n const parts: string[] = [];\n if (ref.title) parts.push(ref.title);\n if (ref.message_item) {\n const refBody = bodyFromItemList([ref.message_item]);\n if (refBody) parts.push(refBody);\n }\n if (!parts.length) return text;\n return `[引用: ${parts.join(\" | \")}]\\n${text}`;\n }\n // 语音转文字:如果语音消息有 text 字段,直接使用文字内容\n if (item.type === MessageItemType.VOICE && item.voice_item?.text) {\n return item.voice_item.text;\n }\n }\n return \"\";\n}\n\nexport type WeixinInboundMediaOpts = {\n /** Local path to decrypted image file. */\n decryptedPicPath?: string;\n /** Local path to transcoded/raw voice file (.wav or .silk). */\n decryptedVoicePath?: string;\n /** MIME type for the voice file (e.g. \"audio/wav\" or \"audio/silk\"). */\n voiceMediaType?: string;\n /** Local path to decrypted file attachment. */\n decryptedFilePath?: string;\n /** MIME type for the file attachment (guessed from file_name). */\n fileMediaType?: string;\n /** Local path to decrypted video file. */\n decryptedVideoPath?: string;\n};\n\n/**\n * Convert a WeixinMessage from getUpdates to the inbound MsgContext for the core pipeline.\n * Media: only pass MediaPath (local file, after CDN download + decrypt).\n * We never pass MediaUrl — the upstream CDN URL is encrypted/auth-only.\n * Priority when multiple media types present: image > video > file > voice.\n */\nexport function weixinMessageToMsgContext(\n msg: WeixinMessage,\n accountId: string,\n opts?: WeixinInboundMediaOpts,\n): WeixinMsgContext {\n const from_user_id = msg.from_user_id ?? \"\";\n const ctx: WeixinMsgContext = {\n Body: bodyFromItemList(msg.item_list),\n From: from_user_id,\n To: from_user_id,\n AccountId: accountId,\n OriginatingChannel: \"weixin\",\n OriginatingTo: from_user_id,\n MessageSid: generateMessageSid(),\n Timestamp: msg.create_time_ms,\n Provider: \"weixin\",\n ChatType: \"direct\",\n };\n if (msg.context_token) {\n ctx.context_token = msg.context_token;\n }\n\n if (opts?.decryptedPicPath) {\n ctx.MediaPath = opts.decryptedPicPath;\n ctx.MediaType = \"image/*\";\n } else if (opts?.decryptedVideoPath) {\n ctx.MediaPath = opts.decryptedVideoPath;\n ctx.MediaType = \"video/mp4\";\n } else if (opts?.decryptedFilePath) {\n ctx.MediaPath = opts.decryptedFilePath;\n ctx.MediaType = opts.fileMediaType ?? \"application/octet-stream\";\n } else if (opts?.decryptedVoicePath) {\n ctx.MediaPath = opts.decryptedVoicePath;\n ctx.MediaType = opts.voiceMediaType ?? \"audio/wav\";\n }\n\n return ctx;\n}\n\n/** Extract the context_token from an inbound WeixinMsgContext. */\nexport function getContextTokenFromMsgContext(ctx: WeixinMsgContext): string | undefined {\n return ctx.context_token;\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { resolveWeixinRootDir } from "./state-dir.js";
2
2
  import path from "node:path";
3
- import fsSync from "node:fs";
3
+ import fs from "node:fs";
4
4
  //#region extensions/weixin/src/storage/sync-buf.ts
5
5
  function resolveAccountsDir() {
6
6
  return path.join(resolveWeixinRootDir(), "accounts");
@@ -10,7 +10,7 @@ function getSyncBufFilePath(accountId) {
10
10
  }
11
11
  function readSyncBufFile(filePath) {
12
12
  try {
13
- const raw = fsSync.readFileSync(filePath, "utf-8");
13
+ const raw = fs.readFileSync(filePath, "utf-8");
14
14
  const data = JSON.parse(raw);
15
15
  if (typeof data.get_updates_buf === "string") return data.get_updates_buf;
16
16
  } catch {}
@@ -20,8 +20,8 @@ function loadGetUpdatesBuf(filePath) {
20
20
  }
21
21
  function saveGetUpdatesBuf(filePath, getUpdatesBuf) {
22
22
  const dir = path.dirname(filePath);
23
- fsSync.mkdirSync(dir, { recursive: true });
24
- fsSync.writeFileSync(filePath, JSON.stringify({ get_updates_buf: getUpdatesBuf }, null, 0), "utf-8");
23
+ fs.mkdirSync(dir, { recursive: true });
24
+ fs.writeFileSync(filePath, JSON.stringify({ get_updates_buf: getUpdatesBuf }, null, 0), "utf-8");
25
25
  }
26
26
  //#endregion
27
27
  export { getSyncBufFilePath, loadGetUpdatesBuf, saveGetUpdatesBuf };
@@ -1 +1 @@
1
- {"version":3,"file":"sync-buf.js","names":["fs"],"sources":["../../../../../extensions/weixin/src/storage/sync-buf.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { resolveWeixinRootDir } from './state-dir.js';\n\nfunction resolveAccountsDir(): string {\n return path.join(resolveWeixinRootDir(), 'accounts');\n}\n\nexport function getSyncBufFilePath(accountId: string): string {\n return path.join(resolveAccountsDir(), `${accountId}.sync.json`);\n}\n\nexport type SyncBufData = {\n get_updates_buf: string;\n};\n\nfunction readSyncBufFile(filePath: string): string | undefined {\n try {\n const raw = fs.readFileSync(filePath, 'utf-8');\n const data = JSON.parse(raw) as { get_updates_buf?: string };\n if (typeof data.get_updates_buf === 'string') {\n return data.get_updates_buf;\n }\n } catch {\n // ignore\n }\n return undefined;\n}\n\nexport function loadGetUpdatesBuf(filePath: string): string | undefined {\n return readSyncBufFile(filePath);\n}\n\nexport function saveGetUpdatesBuf(filePath: string, getUpdatesBuf: string): void {\n const dir = path.dirname(filePath);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(filePath, JSON.stringify({ get_updates_buf: getUpdatesBuf }, null, 0), 'utf-8');\n}\n"],"mappings":";;;;AAKA,SAAS,qBAA6B;AACpC,QAAO,KAAK,KAAK,sBAAsB,EAAE,WAAW;;AAGtD,SAAgB,mBAAmB,WAA2B;AAC5D,QAAO,KAAK,KAAK,oBAAoB,EAAE,GAAG,UAAU,YAAY;;AAOlE,SAAS,gBAAgB,UAAsC;AAC7D,KAAI;EACF,MAAM,MAAMA,OAAG,aAAa,UAAU,QAAQ;EAC9C,MAAM,OAAO,KAAK,MAAM,IAAI;AAC5B,MAAI,OAAO,KAAK,oBAAoB,SAClC,QAAO,KAAK;SAER;;AAMV,SAAgB,kBAAkB,UAAsC;AACtE,QAAO,gBAAgB,SAAS;;AAGlC,SAAgB,kBAAkB,UAAkB,eAA6B;CAC/E,MAAM,MAAM,KAAK,QAAQ,SAAS;AAClC,QAAG,UAAU,KAAK,EAAE,WAAW,MAAM,CAAC;AACtC,QAAG,cAAc,UAAU,KAAK,UAAU,EAAE,iBAAiB,eAAe,EAAE,MAAM,EAAE,EAAE,QAAQ"}
1
+ {"version":3,"file":"sync-buf.js","names":[],"sources":["../../../../../extensions/weixin/src/storage/sync-buf.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { resolveWeixinRootDir } from './state-dir.js';\n\nfunction resolveAccountsDir(): string {\n return path.join(resolveWeixinRootDir(), 'accounts');\n}\n\nexport function getSyncBufFilePath(accountId: string): string {\n return path.join(resolveAccountsDir(), `${accountId}.sync.json`);\n}\n\nexport type SyncBufData = {\n get_updates_buf: string;\n};\n\nfunction readSyncBufFile(filePath: string): string | undefined {\n try {\n const raw = fs.readFileSync(filePath, 'utf-8');\n const data = JSON.parse(raw) as { get_updates_buf?: string };\n if (typeof data.get_updates_buf === 'string') {\n return data.get_updates_buf;\n }\n } catch {\n // ignore\n }\n return undefined;\n}\n\nexport function loadGetUpdatesBuf(filePath: string): string | undefined {\n return readSyncBufFile(filePath);\n}\n\nexport function saveGetUpdatesBuf(filePath: string, getUpdatesBuf: string): void {\n const dir = path.dirname(filePath);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(filePath, JSON.stringify({ get_updates_buf: getUpdatesBuf }, null, 0), 'utf-8');\n}\n"],"mappings":";;;;AAKA,SAAS,qBAA6B;AACpC,QAAO,KAAK,KAAK,sBAAsB,EAAE,WAAW;;AAGtD,SAAgB,mBAAmB,WAA2B;AAC5D,QAAO,KAAK,KAAK,oBAAoB,EAAE,GAAG,UAAU,YAAY;;AAOlE,SAAS,gBAAgB,UAAsC;AAC7D,KAAI;EACF,MAAM,MAAM,GAAG,aAAa,UAAU,QAAQ;EAC9C,MAAM,OAAO,KAAK,MAAM,IAAI;AAC5B,MAAI,OAAO,KAAK,oBAAoB,SAClC,QAAO,KAAK;SAER;;AAMV,SAAgB,kBAAkB,UAAsC;AACtE,QAAO,gBAAgB,SAAS;;AAGlC,SAAgB,kBAAkB,UAAkB,eAA6B;CAC/E,MAAM,MAAM,KAAK,QAAQ,SAAS;AAClC,IAAG,UAAU,KAAK,EAAE,WAAW,MAAM,CAAC;AACtC,IAAG,cAAc,UAAU,KAAK,UAAU,EAAE,iBAAiB,eAAe,EAAE,MAAM,EAAE,EAAE,QAAQ"}
@@ -12,7 +12,7 @@
12
12
  * the full final snapshot rendered as text. The user sees a single
13
13
  * tasteful summary at the end.
14
14
  *
15
- * Routing: sessionKey `main:weixin:<accountId>:dm:<ilinkUserId>`. We need a
15
+ * Routing: sessionKey `agent:main:weixin:<accountId>:direct:<ilinkUserId>`. We need a
16
16
  * valid `contextToken` for the recipient — which requires the user to have
17
17
  * recently messaged the bot (the token is harvested from inbound). If
18
18
  * missing, we throw and the broker logs; the run still completes, just
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-progress.js","names":[],"sources":["../../../../extensions/weixin/src/workflow-progress.ts"],"sourcesContent":["/**\n * WeChat capability for the workflow progress broker.\n *\n * WeChat (ilink/personal account) does not expose an \"edit message\" API for\n * bot replies, so live edit-in-place — what Telegram and Feishu do — is\n * structurally impossible. Trying to fake it with a `append` mode would\n * spam the chat with one message per tick, which violates the social\n * properties of the surface (\"Don't @ me twenty times\").\n *\n * Strategy: **final-only**. The broker silently drops every mid-run update;\n * when `tool_end` fires, the broker calls us once with `isFinal: true` and\n * the full final snapshot rendered as text. The user sees a single\n * tasteful summary at the end.\n *\n * Routing: sessionKey `main:weixin:<accountId>:dm:<ilinkUserId>`. We need a\n * valid `contextToken` for the recipient — which requires the user to have\n * recently messaged the bot (the token is harvested from inbound). If\n * missing, we throw and the broker logs; the run still completes, just\n * without a WeChat notification. This is consistent with how other WeChat\n * outbound paths behave.\n */\n\nimport type {\n ChannelProgressCapability,\n WorkflowProgressPostInput,\n} from '@xopcai/xopc/agent/workflow/index.js';\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport { parseSessionKey } from '@xopcai/xopc/routing/session-key.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\n\nimport { resolveWeixinAccount } from './auth/accounts.js';\nimport { ensureWeixinContextTokenForOutbound } from './messaging/context-token-init.js';\nimport { getContextToken } from './messaging/inbound.js';\nimport { sendMessageWeixin } from './messaging/send.js';\n\nconst log = createLogger('WeixinWorkflowProgress');\n\nconst WEIXIN_TEXT_MAX = 4_000;\n/**\n * Conservative throttle — only enforced when the user overrides the default\n * `final-only` mode to `append`. Set so back-to-back workflows can't spam.\n */\nconst DEFAULT_THROTTLE_MS = 60_000;\n\nexport function createWeixinWorkflowProgressCapability(opts: {\n getConfig: () => Config | undefined;\n}): ChannelProgressCapability {\n return {\n channelId: 'weixin',\n supportsEdit: false,\n defaultThrottleMs: DEFAULT_THROTTLE_MS,\n defaultMode: 'final-only',\n\n async postProgress(input: WorkflowProgressPostInput) {\n const cfg = opts.getConfig();\n if (!cfg) {\n throw new Error('weixin workflow progress: no config loaded');\n }\n const target = resolveTarget(input.sessionKey);\n if (!target) {\n throw new Error(`weixin workflow progress: cannot route sessionKey \"${input.sessionKey}\"`);\n }\n\n let account;\n try {\n account = resolveWeixinAccount(cfg, target.accountId);\n } catch (err) {\n throw new Error(\n `weixin workflow progress: cannot resolve account \"${target.accountId}\": ${errorMessage(err)}`,\n );\n }\n if (!account.configured || !account.token) {\n throw new Error(\n `weixin workflow progress: account \"${target.accountId}\" not configured / logged in`,\n );\n }\n\n let ctxTok = getContextToken(account.accountId, target.to)?.trim();\n if (!ctxTok) {\n ctxTok = (await ensureWeixinContextTokenForOutbound(account.accountId, target.to, account))?.trim();\n }\n if (!ctxTok) {\n // No usable context token — without one WeChat refuses outbound. Better\n // to drop the progress notice than to spam an error; the parent agent\n // still surfaces the result through its normal reply path.\n log.debug(\n { sessionKey: input.sessionKey, accountId: account.accountId },\n 'no context token for recipient; skipping workflow progress send',\n );\n return { messageId: '' };\n }\n\n const text = clampForWeixin(decorateForWeixin(input.text, input.mode, input.isFinal));\n const r = await sendMessageWeixin({\n to: target.to,\n text,\n opts: {\n baseUrl: account.baseUrl,\n token: account.token,\n routeTag: account.routeTag,\n contextToken: ctxTok,\n },\n });\n return { messageId: r.messageId };\n },\n };\n}\n\ninterface ResolvedTarget {\n accountId: string;\n to: string;\n}\n\nfunction resolveTarget(sessionKey: string): ResolvedTarget | null {\n const parsed = parseSessionKey(sessionKey);\n if (!parsed) return null;\n if (parsed.source !== 'weixin') return null;\n if (!parsed.peerId) return null;\n return {\n accountId: parsed.accountId || 'default',\n to: parsed.peerId,\n };\n}\n\nfunction clampForWeixin(text: string): string {\n if (text.length <= WEIXIN_TEXT_MAX) return text;\n return `${text.slice(0, WEIXIN_TEXT_MAX - 1)}…`;\n}\n\n/**\n * Add a one-line header on append-mode messages so the user can tell mid-run\n * snapshots apart from the final summary — WeChat has no editMessage, so they\n * pile up as separate messages and look identical without a marker.\n *\n * - `final-only` mode: no header (default; one summary message per run).\n * - `append` mode + mid-run: \"▾ 工作流进展\" header so the user knows more\n * updates are coming.\n * - `append` mode + final: \"✓ 工作流完成\" header to mark the conclusion.\n *\n * `edit` mode is never reached on WeChat (`supportsEdit: false`), so we don't\n * branch on it. Unknown / missing mode falls through to no header — safe\n * default for hand-rolled callers and tests.\n */\nfunction decorateForWeixin(\n text: string,\n mode: string | undefined,\n isFinal: boolean,\n): string {\n if (mode !== 'append') return text;\n const header = isFinal ? '✓ 工作流完成' : '▾ 工作流进展';\n return `${header}\\n${text}`;\n}\n\nfunction errorMessage(err: unknown): string {\n if (!err) return '';\n if (err instanceof Error) return err.message;\n return String(err);\n}\n"],"mappings":";;;;;;;;kBA2BsE;aACV;AAO5D,MAAM,MAAM,aAAa,yBAAyB;AAElD,MAAM,kBAAkB;;;;;AAKxB,MAAM,sBAAsB;AAE5B,SAAgB,uCAAuC,MAEzB;AAC5B,QAAO;EACL,WAAW;EACX,cAAc;EACd,mBAAmB;EACnB,aAAa;EAEb,MAAM,aAAa,OAAkC;GACnD,MAAM,MAAM,KAAK,WAAW;AAC5B,OAAI,CAAC,IACH,OAAM,IAAI,MAAM,6CAA6C;GAE/D,MAAM,SAAS,cAAc,MAAM,WAAW;AAC9C,OAAI,CAAC,OACH,OAAM,IAAI,MAAM,sDAAsD,MAAM,WAAW,GAAG;GAG5F,IAAI;AACJ,OAAI;AACF,cAAU,qBAAqB,KAAK,OAAO,UAAU;YAC9C,KAAK;AACZ,UAAM,IAAI,MACR,qDAAqD,OAAO,UAAU,KAAK,aAAa,IAAI,GAC7F;;AAEH,OAAI,CAAC,QAAQ,cAAc,CAAC,QAAQ,MAClC,OAAM,IAAI,MACR,sCAAsC,OAAO,UAAU,8BACxD;GAGH,IAAI,SAAS,gBAAgB,QAAQ,WAAW,OAAO,GAAG,EAAE,MAAM;AAClE,OAAI,CAAC,OACH,WAAU,MAAM,oCAAoC,QAAQ,WAAW,OAAO,IAAI,QAAQ,GAAG,MAAM;AAErG,OAAI,CAAC,QAAQ;AAIX,QAAI,MACF;KAAE,YAAY,MAAM;KAAY,WAAW,QAAQ;KAAW,EAC9D,kEACD;AACD,WAAO,EAAE,WAAW,IAAI;;GAG1B,MAAM,OAAO,eAAe,kBAAkB,MAAM,MAAM,MAAM,MAAM,MAAM,QAAQ,CAAC;AAWrF,UAAO,EAAE,YAAW,MAVJ,kBAAkB;IAChC,IAAI,OAAO;IACX;IACA,MAAM;KACJ,SAAS,QAAQ;KACjB,OAAO,QAAQ;KACf,UAAU,QAAQ;KAClB,cAAc;KACf;IACF,CAAC,EACoB,WAAW;;EAEpC;;AAQH,SAAS,cAAc,YAA2C;CAChE,MAAM,SAAS,gBAAgB,WAAW;AAC1C,KAAI,CAAC,OAAQ,QAAO;AACpB,KAAI,OAAO,WAAW,SAAU,QAAO;AACvC,KAAI,CAAC,OAAO,OAAQ,QAAO;AAC3B,QAAO;EACL,WAAW,OAAO,aAAa;EAC/B,IAAI,OAAO;EACZ;;AAGH,SAAS,eAAe,MAAsB;AAC5C,KAAI,KAAK,UAAU,gBAAiB,QAAO;AAC3C,QAAO,GAAG,KAAK,MAAM,GAAG,kBAAkB,EAAE,CAAC;;;;;;;;;;;;;;;;AAiB/C,SAAS,kBACP,MACA,MACA,SACQ;AACR,KAAI,SAAS,SAAU,QAAO;AAE9B,QAAO,GADQ,UAAU,YAAY,UACpB,IAAI;;AAGvB,SAAS,aAAa,KAAsB;AAC1C,KAAI,CAAC,IAAK,QAAO;AACjB,KAAI,eAAe,MAAO,QAAO,IAAI;AACrC,QAAO,OAAO,IAAI"}
1
+ {"version":3,"file":"workflow-progress.js","names":[],"sources":["../../../../extensions/weixin/src/workflow-progress.ts"],"sourcesContent":["/**\n * WeChat capability for the workflow progress broker.\n *\n * WeChat (ilink/personal account) does not expose an \"edit message\" API for\n * bot replies, so live edit-in-place — what Telegram and Feishu do — is\n * structurally impossible. Trying to fake it with a `append` mode would\n * spam the chat with one message per tick, which violates the social\n * properties of the surface (\"Don't @ me twenty times\").\n *\n * Strategy: **final-only**. The broker silently drops every mid-run update;\n * when `tool_end` fires, the broker calls us once with `isFinal: true` and\n * the full final snapshot rendered as text. The user sees a single\n * tasteful summary at the end.\n *\n * Routing: sessionKey `agent:main:weixin:<accountId>:direct:<ilinkUserId>`. We need a\n * valid `contextToken` for the recipient — which requires the user to have\n * recently messaged the bot (the token is harvested from inbound). If\n * missing, we throw and the broker logs; the run still completes, just\n * without a WeChat notification. This is consistent with how other WeChat\n * outbound paths behave.\n */\n\nimport type {\n ChannelProgressCapability,\n WorkflowProgressPostInput,\n} from '@xopcai/xopc/agent/workflow/index.js';\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport { parseSessionKey } from '@xopcai/xopc/routing/session-key.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\n\nimport { resolveWeixinAccount } from './auth/accounts.js';\nimport { ensureWeixinContextTokenForOutbound } from './messaging/context-token-init.js';\nimport { getContextToken } from './messaging/inbound.js';\nimport { sendMessageWeixin } from './messaging/send.js';\n\nconst log = createLogger('WeixinWorkflowProgress');\n\nconst WEIXIN_TEXT_MAX = 4_000;\n/**\n * Conservative throttle — only enforced when the user overrides the default\n * `final-only` mode to `append`. Set so back-to-back workflows can't spam.\n */\nconst DEFAULT_THROTTLE_MS = 60_000;\n\nexport function createWeixinWorkflowProgressCapability(opts: {\n getConfig: () => Config | undefined;\n}): ChannelProgressCapability {\n return {\n channelId: 'weixin',\n supportsEdit: false,\n defaultThrottleMs: DEFAULT_THROTTLE_MS,\n defaultMode: 'final-only',\n\n async postProgress(input: WorkflowProgressPostInput) {\n const cfg = opts.getConfig();\n if (!cfg) {\n throw new Error('weixin workflow progress: no config loaded');\n }\n const target = resolveTarget(input.sessionKey);\n if (!target) {\n throw new Error(`weixin workflow progress: cannot route sessionKey \"${input.sessionKey}\"`);\n }\n\n let account;\n try {\n account = resolveWeixinAccount(cfg, target.accountId);\n } catch (err) {\n throw new Error(\n `weixin workflow progress: cannot resolve account \"${target.accountId}\": ${errorMessage(err)}`,\n );\n }\n if (!account.configured || !account.token) {\n throw new Error(\n `weixin workflow progress: account \"${target.accountId}\" not configured / logged in`,\n );\n }\n\n let ctxTok = getContextToken(account.accountId, target.to)?.trim();\n if (!ctxTok) {\n ctxTok = (await ensureWeixinContextTokenForOutbound(account.accountId, target.to, account))?.trim();\n }\n if (!ctxTok) {\n // No usable context token — without one WeChat refuses outbound. Better\n // to drop the progress notice than to spam an error; the parent agent\n // still surfaces the result through its normal reply path.\n log.debug(\n { sessionKey: input.sessionKey, accountId: account.accountId },\n 'no context token for recipient; skipping workflow progress send',\n );\n return { messageId: '' };\n }\n\n const text = clampForWeixin(decorateForWeixin(input.text, input.mode, input.isFinal));\n const r = await sendMessageWeixin({\n to: target.to,\n text,\n opts: {\n baseUrl: account.baseUrl,\n token: account.token,\n routeTag: account.routeTag,\n contextToken: ctxTok,\n },\n });\n return { messageId: r.messageId };\n },\n };\n}\n\ninterface ResolvedTarget {\n accountId: string;\n to: string;\n}\n\nfunction resolveTarget(sessionKey: string): ResolvedTarget | null {\n const parsed = parseSessionKey(sessionKey);\n if (!parsed) return null;\n if (parsed.source !== 'weixin') return null;\n if (!parsed.peerId) return null;\n return {\n accountId: parsed.accountId || 'default',\n to: parsed.peerId,\n };\n}\n\nfunction clampForWeixin(text: string): string {\n if (text.length <= WEIXIN_TEXT_MAX) return text;\n return `${text.slice(0, WEIXIN_TEXT_MAX - 1)}…`;\n}\n\n/**\n * Add a one-line header on append-mode messages so the user can tell mid-run\n * snapshots apart from the final summary — WeChat has no editMessage, so they\n * pile up as separate messages and look identical without a marker.\n *\n * - `final-only` mode: no header (default; one summary message per run).\n * - `append` mode + mid-run: \"▾ 工作流进展\" header so the user knows more\n * updates are coming.\n * - `append` mode + final: \"✓ 工作流完成\" header to mark the conclusion.\n *\n * `edit` mode is never reached on WeChat (`supportsEdit: false`), so we don't\n * branch on it. Unknown / missing mode falls through to no header — safe\n * default for hand-rolled callers and tests.\n */\nfunction decorateForWeixin(\n text: string,\n mode: string | undefined,\n isFinal: boolean,\n): string {\n if (mode !== 'append') return text;\n const header = isFinal ? '✓ 工作流完成' : '▾ 工作流进展';\n return `${header}\\n${text}`;\n}\n\nfunction errorMessage(err: unknown): string {\n if (!err) return '';\n if (err instanceof Error) return err.message;\n return String(err);\n}\n"],"mappings":";;;;;;;;kBA2BsE;aACV;AAO5D,MAAM,MAAM,aAAa,yBAAyB;AAElD,MAAM,kBAAkB;;;;;AAKxB,MAAM,sBAAsB;AAE5B,SAAgB,uCAAuC,MAEzB;AAC5B,QAAO;EACL,WAAW;EACX,cAAc;EACd,mBAAmB;EACnB,aAAa;EAEb,MAAM,aAAa,OAAkC;GACnD,MAAM,MAAM,KAAK,WAAW;AAC5B,OAAI,CAAC,IACH,OAAM,IAAI,MAAM,6CAA6C;GAE/D,MAAM,SAAS,cAAc,MAAM,WAAW;AAC9C,OAAI,CAAC,OACH,OAAM,IAAI,MAAM,sDAAsD,MAAM,WAAW,GAAG;GAG5F,IAAI;AACJ,OAAI;AACF,cAAU,qBAAqB,KAAK,OAAO,UAAU;YAC9C,KAAK;AACZ,UAAM,IAAI,MACR,qDAAqD,OAAO,UAAU,KAAK,aAAa,IAAI,GAC7F;;AAEH,OAAI,CAAC,QAAQ,cAAc,CAAC,QAAQ,MAClC,OAAM,IAAI,MACR,sCAAsC,OAAO,UAAU,8BACxD;GAGH,IAAI,SAAS,gBAAgB,QAAQ,WAAW,OAAO,GAAG,EAAE,MAAM;AAClE,OAAI,CAAC,OACH,WAAU,MAAM,oCAAoC,QAAQ,WAAW,OAAO,IAAI,QAAQ,GAAG,MAAM;AAErG,OAAI,CAAC,QAAQ;AAIX,QAAI,MACF;KAAE,YAAY,MAAM;KAAY,WAAW,QAAQ;KAAW,EAC9D,kEACD;AACD,WAAO,EAAE,WAAW,IAAI;;GAG1B,MAAM,OAAO,eAAe,kBAAkB,MAAM,MAAM,MAAM,MAAM,MAAM,QAAQ,CAAC;AAWrF,UAAO,EAAE,YAAW,MAVJ,kBAAkB;IAChC,IAAI,OAAO;IACX;IACA,MAAM;KACJ,SAAS,QAAQ;KACjB,OAAO,QAAQ;KACf,UAAU,QAAQ;KAClB,cAAc;KACf;IACF,CAAC,EACoB,WAAW;;EAEpC;;AAQH,SAAS,cAAc,YAA2C;CAChE,MAAM,SAAS,gBAAgB,WAAW;AAC1C,KAAI,CAAC,OAAQ,QAAO;AACpB,KAAI,OAAO,WAAW,SAAU,QAAO;AACvC,KAAI,CAAC,OAAO,OAAQ,QAAO;AAC3B,QAAO;EACL,WAAW,OAAO,aAAa;EAC/B,IAAI,OAAO;EACZ;;AAGH,SAAS,eAAe,MAAsB;AAC5C,KAAI,KAAK,UAAU,gBAAiB,QAAO;AAC3C,QAAO,GAAG,KAAK,MAAM,GAAG,kBAAkB,EAAE,CAAC;;;;;;;;;;;;;;;;AAiB/C,SAAS,kBACP,MACA,MACA,SACQ;AACR,KAAI,SAAS,SAAU,QAAO;AAE9B,QAAO,GADQ,UAAU,YAAY,UACpB,IAAI;;AAGvB,SAAS,aAAa,KAAsB;AAC1C,KAAI,CAAC,IAAK,QAAO;AACjB,KAAI,eAAe,MAAO,QAAO,IAAI;AACrC,QAAO,OAAO,IAAI"}