@xopcai/xopc 0.0.86 → 0.0.88

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 (658) 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/feishu/src/outbound/media-load.js +1 -1
  5. package/dist/extensions/feishu/src/workflow-progress.js +1 -1
  6. package/dist/extensions/telegram/src/delivery-chat-id.d.ts +1 -1
  7. package/dist/extensions/telegram/src/delivery-chat-id.js +1 -1
  8. package/dist/extensions/telegram/src/delivery-chat-id.js.map +1 -1
  9. package/dist/extensions/telegram/src/plugin.js +1 -1
  10. package/dist/extensions/telegram/src/routing-integration.js +3 -2
  11. package/dist/extensions/telegram/src/routing-integration.js.map +1 -1
  12. package/dist/extensions/telegram/src/workflow-progress.js +1 -1
  13. package/dist/extensions/telegram/xopc.extension.json +1 -1
  14. package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js +2 -2
  15. package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js.map +1 -1
  16. package/dist/extensions/weixin/src/api/api.js +3 -3
  17. package/dist/extensions/weixin/src/api/api.js.map +1 -1
  18. package/dist/extensions/weixin/src/auth/accounts.js +12 -12
  19. package/dist/extensions/weixin/src/auth/accounts.js.map +1 -1
  20. package/dist/extensions/weixin/src/cdn/upload.js +1 -1
  21. package/dist/extensions/weixin/src/delivery-to.js +2 -2
  22. package/dist/extensions/weixin/src/delivery-to.js.map +1 -1
  23. package/dist/extensions/weixin/src/media/data-url.js +1 -1
  24. package/dist/extensions/weixin/src/messaging/debug-mode.js +5 -5
  25. package/dist/extensions/weixin/src/messaging/debug-mode.js.map +1 -1
  26. package/dist/extensions/weixin/src/messaging/inbound.js +11 -11
  27. package/dist/extensions/weixin/src/messaging/inbound.js.map +1 -1
  28. package/dist/extensions/weixin/src/messaging/process-message.js +1 -1
  29. package/dist/extensions/weixin/src/plugin.js +1 -1
  30. package/dist/extensions/weixin/src/storage/sync-buf.js +4 -4
  31. package/dist/extensions/weixin/src/storage/sync-buf.js.map +1 -1
  32. package/dist/extensions/weixin/src/workflow-progress.d.ts +1 -1
  33. package/dist/extensions/weixin/src/workflow-progress.js +1 -1
  34. package/dist/extensions/weixin/src/workflow-progress.js.map +1 -1
  35. package/dist/gateway/static/root/assets/agents-CRxETUZx.js +222 -0
  36. package/dist/gateway/static/root/assets/{apps-page-DrfytjOb.js → apps-page-wKWf3l57.js} +1 -1
  37. package/dist/gateway/static/root/assets/channels-settings-DDbqVNkx.js +1 -0
  38. package/dist/gateway/static/root/assets/{channels-status-swr-Bs5kMCMI.js → channels-status-swr-DIsl75Y3.js} +1 -1
  39. package/dist/gateway/static/root/assets/copy-SxMW6Xpc.js +1 -0
  40. package/dist/gateway/static/root/assets/{cron-api-BuVcZ5zR.js → cron-api-N9hvuRrn.js} +1 -1
  41. package/dist/gateway/static/root/assets/{cron-page-BMrloeFH.js → cron-page-tlNGNxhP.js} +1 -1
  42. package/dist/gateway/static/root/assets/{dist-CKU1OOTf.js → dist-CJwfHYvT.js} +1 -1
  43. package/dist/gateway/static/root/assets/{extension-debug-page-BdW_46sN.js → extension-debug-page-BVJohZoZ.js} +1 -1
  44. package/dist/gateway/static/root/assets/{extension-page-DW47KI82.js → extension-page-BT2tmElC.js} +1 -1
  45. package/dist/gateway/static/root/assets/extension-settings-page-BSS47c2j.js +1 -0
  46. package/dist/gateway/static/root/assets/{fetch-B2MYHbWg.js → fetch-BaFNUtkE.js} +1 -1
  47. package/dist/gateway/static/root/assets/{field-primitives-DPG-oJmx.js → field-primitives-QwYEq6Hz.js} +1 -1
  48. package/dist/gateway/static/root/assets/{heartbeat-config-api-C8dNts9i.js → heartbeat-config-api-BVSidEDJ.js} +1 -1
  49. package/dist/gateway/static/root/assets/index-CqZzHNEg.css +1 -0
  50. package/dist/gateway/static/root/assets/{index-BmVYculr.js → index-qNrVJp-y.js} +97 -95
  51. package/dist/gateway/static/root/assets/{logs-page-sTsVWz0X.js → logs-page-DDonPVLn.js} +1 -1
  52. package/dist/gateway/static/root/assets/sessions-page-DKt-Wmib.js +1 -0
  53. package/dist/gateway/static/root/assets/{settings-form-section-DuvRQW--.js → settings-form-section-B8N3A3Zo.js} +1 -1
  54. package/dist/gateway/static/root/assets/settings-page-DcJjvvw4.js +3 -0
  55. package/dist/gateway/static/root/assets/{share-preview-page-BtG2kLDh.js → share-preview-page-Q7KqkO-u.js} +1 -1
  56. package/dist/gateway/static/root/assets/skills-page-DuJ4BTO3.js +2 -0
  57. package/dist/gateway/static/root/assets/{theme-store-DryYl3qD.js → theme-store-BbRc5ugR.js} +1 -1
  58. package/dist/gateway/static/root/assets/url-D6jvVYIA.js +7 -0
  59. package/dist/gateway/static/root/assets/{utils-BY7bU1DT.js → utils-CxDGduqK.js} +1 -1
  60. package/dist/gateway/static/root/assets/voice-api-key-field-CTyHz7L_.js +1 -0
  61. package/dist/gateway/static/root/assets/workflows-page-GacJ41Fv.js +27 -0
  62. package/dist/gateway/static/root/index.html +6 -5
  63. package/dist/package.js +1 -1
  64. package/dist/src/agent/agent-manager.js +7 -7
  65. package/dist/src/agent/agent-scope.d.ts +4 -0
  66. package/dist/src/agent/agent-scope.js +53 -10
  67. package/dist/src/agent/agent-scope.js.map +1 -1
  68. package/dist/src/agent/bootstrap/filter-bootstrap-files.js +2 -1
  69. package/dist/src/agent/bootstrap/filter-bootstrap-files.js.map +1 -1
  70. package/dist/src/agent/bootstrap/load-bootstrap-files.js +1 -1
  71. package/dist/src/agent/child-agent-factory.d.ts +15 -0
  72. package/dist/src/agent/child-agent-factory.js +35 -2
  73. package/dist/src/agent/child-agent-factory.js.map +1 -1
  74. package/dist/src/agent/client-error-format.d.ts +20 -0
  75. package/dist/src/agent/client-error-format.js +97 -0
  76. package/dist/src/agent/client-error-format.js.map +1 -0
  77. package/dist/src/agent/context/workspace-seed.js +2 -2
  78. package/dist/src/agent/embedded/run-turn.js +23 -4
  79. package/dist/src/agent/embedded/run-turn.js.map +1 -1
  80. package/dist/src/agent/embedded/session-tool-result-guard.js +2 -1
  81. package/dist/src/agent/embedded/session-tool-result-guard.js.map +1 -1
  82. package/dist/src/agent/embedded/tool-result-truncation.js +2 -1
  83. package/dist/src/agent/embedded/tool-result-truncation.js.map +1 -1
  84. package/dist/src/agent/fallback/candidates.js +2 -2
  85. package/dist/src/agent/fallback/candidates.js.map +1 -1
  86. package/dist/src/agent/goals/goal-locale.d.ts +1 -1
  87. package/dist/src/agent/goals/goal-run-store.js +4 -4
  88. package/dist/src/agent/goals/persistent-goal-apis.d.ts +0 -2
  89. package/dist/src/agent/goals/persistent-goal-service.js +1 -2
  90. package/dist/src/agent/goals/persistent-goal-service.js.map +1 -1
  91. package/dist/src/agent/goals/post-turn.js +2 -2
  92. package/dist/src/agent/image/generation/normalization.js +2 -12
  93. package/dist/src/agent/image/generation/normalization.js.map +1 -1
  94. package/dist/src/agent/image/generation/provider-registry.d.ts +4 -8
  95. package/dist/src/agent/image/generation/provider-registry.js.map +1 -1
  96. package/dist/src/agent/image/generation/runtime.d.ts +2 -2
  97. package/dist/src/agent/image/generation/runtime.js.map +1 -1
  98. package/dist/src/agent/image/generation/types.d.ts +0 -18
  99. package/dist/src/agent/image/image-helpers.js +6 -1
  100. package/dist/src/agent/image/image-helpers.js.map +1 -1
  101. package/dist/src/agent/image/index.d.ts +1 -1
  102. package/dist/src/agent/image/load-image-media.js +2 -2
  103. package/dist/src/agent/inbound/inbound-loop.d.ts +5 -0
  104. package/dist/src/agent/inbound/inbound-loop.js +41 -10
  105. package/dist/src/agent/inbound/inbound-loop.js.map +1 -1
  106. package/dist/src/agent/inbound/turn-dispatcher.d.ts +4 -0
  107. package/dist/src/agent/inbound/turn-dispatcher.js +7 -5
  108. package/dist/src/agent/inbound/turn-dispatcher.js.map +1 -1
  109. package/dist/src/agent/ipc/bus.js +1 -1
  110. package/dist/src/agent/ipc/inbox.js +2 -2
  111. package/dist/src/agent/ipc/socket.js +1 -1
  112. package/dist/src/agent/mcp/bundle-mcp-materialize.js +2 -1
  113. package/dist/src/agent/mcp/bundle-mcp-materialize.js.map +1 -1
  114. package/dist/src/agent/mcp/bundle-mcp-names.js +2 -1
  115. package/dist/src/agent/mcp/bundle-mcp-names.js.map +1 -1
  116. package/dist/src/agent/mcp/bundle-mcp-runtime.js +2 -1
  117. package/dist/src/agent/mcp/bundle-mcp-runtime.js.map +1 -1
  118. package/dist/src/agent/mcp/mcp-transport-config.js +2 -1
  119. package/dist/src/agent/mcp/mcp-transport-config.js.map +1 -1
  120. package/dist/src/agent/mcp/mcp-transport.js +2 -1
  121. package/dist/src/agent/mcp/mcp-transport.js.map +1 -1
  122. package/dist/src/agent/media-generation/runtime-shared.js +2 -9
  123. package/dist/src/agent/media-generation/runtime-shared.js.map +1 -1
  124. package/dist/src/agent/memory/builtin-memory-store.js +1 -1
  125. package/dist/src/agent/memory/dreaming/deep-promotion.js +1 -1
  126. package/dist/src/agent/memory/dreaming/events.js +1 -1
  127. package/dist/src/agent/memory/dreaming/last-run.js +1 -1
  128. package/dist/src/agent/memory/dreaming/light-sweep.js +1 -1
  129. package/dist/src/agent/memory/dreaming/preview.js +1 -1
  130. package/dist/src/agent/memory/dreaming/rem-patterns.js +1 -1
  131. package/dist/src/agent/memory/dreaming/short-term-store.js +1 -1
  132. package/dist/src/agent/memory/dreaming/utils.js +1 -1
  133. package/dist/src/agent/memory/plugin-discovery.js +1 -1
  134. package/dist/src/agent/messaging/command-handler.d.ts +6 -0
  135. package/dist/src/agent/messaging/command-handler.js +5 -0
  136. package/dist/src/agent/messaging/command-handler.js.map +1 -1
  137. package/dist/src/agent/models/manager.js +1 -1
  138. package/dist/src/agent/orchestration/llm-turn-retry.d.ts +2 -0
  139. package/dist/src/agent/orchestration/llm-turn-retry.js +9 -1
  140. package/dist/src/agent/orchestration/llm-turn-retry.js.map +1 -1
  141. package/dist/src/agent/prompt/safety.d.ts +0 -7
  142. package/dist/src/agent/prompt/safety.js +1 -20
  143. package/dist/src/agent/prompt/safety.js.map +1 -1
  144. package/dist/src/agent/prompt/service-prompt-builder.js +2 -2
  145. package/dist/src/agent/reply/post-compaction-context.js +1 -1
  146. package/dist/src/agent/reply/workspace-boundary-read.js +1 -1
  147. package/dist/src/agent/sandbox/path-policy.js +2 -2
  148. package/dist/src/agent/service/build-direct-message-content.js +2 -2
  149. package/dist/src/agent/service/build-direct-message-content.js.map +1 -1
  150. package/dist/src/agent/service/direct-turn-helpers.d.ts +3 -1
  151. package/dist/src/agent/service/direct-turn-helpers.js +6 -1
  152. package/dist/src/agent/service/direct-turn-helpers.js.map +1 -1
  153. package/dist/src/agent/service/process-direct-one-shot.d.ts +4 -0
  154. package/dist/src/agent/service/process-direct-one-shot.js +15 -2
  155. package/dist/src/agent/service/process-direct-one-shot.js.map +1 -1
  156. package/dist/src/agent/service/process-direct-streaming.d.ts +4 -0
  157. package/dist/src/agent/service/process-direct-streaming.js +53 -7
  158. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  159. package/dist/src/agent/service/webchat-tts.d.ts +1 -2
  160. package/dist/src/agent/service/webchat-tts.js +2 -2
  161. package/dist/src/agent/service/webchat-tts.js.map +1 -1
  162. package/dist/src/agent/service.d.ts +8 -0
  163. package/dist/src/agent/service.js +25 -5
  164. package/dist/src/agent/service.js.map +1 -1
  165. package/dist/src/agent/session/session-inspector.js +1 -1
  166. package/dist/src/agent/skills/config.js +1 -1
  167. package/dist/src/agent/skills/hub-hash.js +2 -2
  168. package/dist/src/agent/skills/hub-lock.js +1 -1
  169. package/dist/src/agent/skills/hub-pull.js +2 -2
  170. package/dist/src/agent/skills/index.js +1 -1
  171. package/dist/src/agent/skills/managed-store.js +1 -1
  172. package/dist/src/agent/skills/scanner.js +1 -1
  173. package/dist/src/agent/skills/skill-manage-ops.js +1 -1
  174. package/dist/src/agent/skills/skill-manager.js +1 -1
  175. package/dist/src/agent/tools/create-share-tool.js +27 -20
  176. package/dist/src/agent/tools/create-share-tool.js.map +1 -1
  177. package/dist/src/agent/tools/dreaming-tool.js +1 -1
  178. package/dist/src/agent/tools/factory.js +2 -2
  179. package/dist/src/agent/tools/image-generate-tool.js +1 -1
  180. package/dist/src/agent/tools/index.d.ts +0 -1
  181. package/dist/src/agent/tools/index.js +4 -5
  182. package/dist/src/agent/tools/send-media.js +1 -1
  183. package/dist/src/agent/tools/shell.js +0 -13
  184. package/dist/src/agent/tools/shell.js.map +1 -1
  185. package/dist/src/agent/tools/skill-manage-tool.js +1 -1
  186. package/dist/src/agent/tools/workflow-tool.js +70 -16
  187. package/dist/src/agent/tools/workflow-tool.js.map +1 -1
  188. package/dist/src/agent/tools/write.js +1 -1
  189. package/dist/src/agent/workflow/agent-progress.d.ts +5 -0
  190. package/dist/src/agent/workflow/agent-progress.js +65 -0
  191. package/dist/src/agent/workflow/agent-progress.js.map +1 -0
  192. package/dist/src/agent/workflow/builtins/audit-repo.d.ts +1 -1
  193. package/dist/src/agent/workflow/builtins/audit-repo.js +14 -0
  194. package/dist/src/agent/workflow/builtins/audit-repo.js.map +1 -1
  195. package/dist/src/agent/workflow/builtins/debug-incident.d.ts +1 -1
  196. package/dist/src/agent/workflow/builtins/debug-incident.js +14 -0
  197. package/dist/src/agent/workflow/builtins/debug-incident.js.map +1 -1
  198. package/dist/src/agent/workflow/builtins/implementation-plan.d.ts +12 -0
  199. package/dist/src/agent/workflow/builtins/implementation-plan.js +175 -0
  200. package/dist/src/agent/workflow/builtins/implementation-plan.js.map +1 -0
  201. package/dist/src/agent/workflow/builtins/index.d.ts +3 -1
  202. package/dist/src/agent/workflow/builtins/index.js +11 -1
  203. package/dist/src/agent/workflow/builtins/index.js.map +1 -1
  204. package/dist/src/agent/workflow/builtins/multi-perspective-review.d.ts +1 -1
  205. package/dist/src/agent/workflow/builtins/multi-perspective-review.js +14 -0
  206. package/dist/src/agent/workflow/builtins/multi-perspective-review.js.map +1 -1
  207. package/dist/src/agent/workflow/builtins/pr-review.d.ts +1 -1
  208. package/dist/src/agent/workflow/builtins/pr-review.js +14 -0
  209. package/dist/src/agent/workflow/builtins/pr-review.js.map +1 -1
  210. package/dist/src/agent/workflow/builtins/release-check.d.ts +11 -0
  211. package/dist/src/agent/workflow/builtins/release-check.js +165 -0
  212. package/dist/src/agent/workflow/builtins/release-check.js.map +1 -0
  213. package/dist/src/agent/workflow/builtins/research.d.ts +1 -1
  214. package/dist/src/agent/workflow/builtins/research.js +14 -0
  215. package/dist/src/agent/workflow/builtins/research.js.map +1 -1
  216. package/dist/src/agent/workflow/catalog.js +1 -1
  217. package/dist/src/agent/workflow/channel-capability.d.ts +3 -3
  218. package/dist/src/agent/workflow/index.d.ts +2 -1
  219. package/dist/src/agent/workflow/index.js +3 -2
  220. package/dist/src/agent/workflow/lint.d.ts +38 -0
  221. package/dist/src/agent/workflow/lint.js +74 -0
  222. package/dist/src/agent/workflow/lint.js.map +1 -0
  223. package/dist/src/agent/workflow/meta-locale.d.ts +12 -0
  224. package/dist/src/agent/workflow/meta-locale.js +62 -0
  225. package/dist/src/agent/workflow/meta-locale.js.map +1 -0
  226. package/dist/src/agent/workflow/parser.js +7 -1
  227. package/dist/src/agent/workflow/parser.js.map +1 -1
  228. package/dist/src/agent/workflow/runtime.d.ts +4 -1
  229. package/dist/src/agent/workflow/runtime.js +88 -8
  230. package/dist/src/agent/workflow/runtime.js.map +1 -1
  231. package/dist/src/agent/workflow/snapshot.js +2 -12
  232. package/dist/src/agent/workflow/snapshot.js.map +1 -1
  233. package/dist/src/agent/workflow/step-labels.d.ts +8 -0
  234. package/dist/src/agent/workflow/step-labels.js +48 -0
  235. package/dist/src/agent/workflow/step-labels.js.map +1 -0
  236. package/dist/src/agent/workflow/subagent-runner.js +46 -1
  237. package/dist/src/agent/workflow/subagent-runner.js.map +1 -1
  238. package/dist/src/agent/workflow/types.d.ts +76 -1
  239. package/dist/src/auth/credentials.d.ts +5 -0
  240. package/dist/src/auth/credentials.js +12 -3
  241. package/dist/src/auth/credentials.js.map +1 -1
  242. package/dist/src/auth/profiles/store.js +1 -1
  243. package/dist/src/auth/sync-provider-auth.js +1 -1
  244. package/dist/src/browser/cache-dir-policy.js +1 -1
  245. package/dist/src/browser/cdp-local-launcher.js +2 -2
  246. package/dist/src/browser/index.js +4 -4
  247. package/dist/src/browser/manager.d.ts +1 -3
  248. package/dist/src/browser/manager.js +0 -6
  249. package/dist/src/browser/manager.js.map +1 -1
  250. package/dist/src/browser/providers/browser-ext-install.d.ts +4 -4
  251. package/dist/src/browser/providers/browser-ext-install.js +41 -88
  252. package/dist/src/browser/providers/browser-ext-install.js.map +1 -1
  253. package/dist/src/browser/providers/cloakbrowser.d.ts +0 -5
  254. package/dist/src/browser/providers/cloakbrowser.js +6 -59
  255. package/dist/src/browser/providers/cloakbrowser.js.map +1 -1
  256. package/dist/src/browser/providers/playwright-doctor.js +1 -1
  257. package/dist/src/browser/stealth.js +1 -1
  258. package/dist/src/channels/attachments/inbound-persist.js +1 -1
  259. package/dist/src/channels/attachments/outbound-tts-persist.js +1 -1
  260. package/dist/src/channels/attachments/voice-stt-webchat.js +10 -8
  261. package/dist/src/channels/attachments/voice-stt-webchat.js.map +1 -1
  262. package/dist/src/channels/outbound/persist-store.js +1 -1
  263. package/dist/src/channels/pairing/allow-from-file.js +9 -9
  264. package/dist/src/channels/pairing/allow-from-file.js.map +1 -1
  265. package/dist/src/channels/pairing/pairing-store.js +7 -7
  266. package/dist/src/channels/pairing/pairing-store.js.map +1 -1
  267. package/dist/src/chat-commands/builtins/config.js +2 -2
  268. package/dist/src/chat-commands/builtins/session.js +1 -1
  269. package/dist/src/chat-commands/builtins/session.js.map +1 -1
  270. package/dist/src/chat-commands/builtins/tts.js +2 -2
  271. package/dist/src/chat-commands/builtins/tts.js.map +1 -1
  272. package/dist/src/chat-commands/context.d.ts +3 -0
  273. package/dist/src/chat-commands/context.js +22 -4
  274. package/dist/src/chat-commands/context.js.map +1 -1
  275. package/dist/src/chat-commands/session-key.d.ts +4 -37
  276. package/dist/src/chat-commands/session-key.js +49 -85
  277. package/dist/src/chat-commands/session-key.js.map +1 -1
  278. package/dist/src/chat-commands/types.d.ts +2 -0
  279. package/dist/src/cli/commands/agent/interactive.js +2 -2
  280. package/dist/src/cli/commands/agent/interactive.js.map +1 -1
  281. package/dist/src/cli/commands/agent/sessions.js +2 -2
  282. package/dist/src/cli/commands/agent/sessions.js.map +1 -1
  283. package/dist/src/cli/commands/agent.js +4 -5
  284. package/dist/src/cli/commands/agent.js.map +1 -1
  285. package/dist/src/cli/commands/channels.js +1 -5
  286. package/dist/src/cli/commands/channels.js.map +1 -1
  287. package/dist/src/cli/commands/config.js +1 -1
  288. package/dist/src/cli/commands/doctor/checks/config-health.js +1 -1
  289. package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
  290. package/dist/src/cli/commands/doctor/checks/session-integrity.js +1 -1
  291. package/dist/src/cli/commands/doctor/checks/state-integrity.js +1 -1
  292. package/dist/src/cli/commands/doctor/checks/workspace-status.js +1 -1
  293. package/dist/src/cli/commands/extension-dev.js +1 -1
  294. package/dist/src/cli/commands/extension-marketplace.js +1 -1
  295. package/dist/src/cli/commands/extension-pack.js +1 -1
  296. package/dist/src/cli/commands/gateway/lifecycle-core.js +1 -1
  297. package/dist/src/cli/commands/gateway/lifecycle-core.js.map +1 -1
  298. package/dist/src/cli/commands/gateway/logs.d.ts +9 -0
  299. package/dist/src/cli/commands/gateway/logs.js +50 -17
  300. package/dist/src/cli/commands/gateway/logs.js.map +1 -1
  301. package/dist/src/cli/commands/image.js +23 -22
  302. package/dist/src/cli/commands/image.js.map +1 -1
  303. package/dist/src/cli/commands/init.js +4 -4
  304. package/dist/src/cli/commands/onboard.js +1 -1
  305. package/dist/src/cli/commands/session/utils.js +2 -2
  306. package/dist/src/cli/commands/session/utils.js.map +1 -1
  307. package/dist/src/cli/commands/update.js +26 -46
  308. package/dist/src/cli/commands/update.js.map +1 -1
  309. package/dist/src/cli/utils/init-workspace-core.js +2 -2
  310. package/dist/src/cli/utils/session.d.ts +0 -5
  311. package/dist/src/cli/utils/session.js +1 -6
  312. package/dist/src/cli/utils/session.js.map +1 -1
  313. package/dist/src/commands/agents.config.js +1 -1
  314. package/dist/src/commands/agents.config.js.map +1 -1
  315. package/dist/src/config/agent-profile.js +6 -28
  316. package/dist/src/config/agent-profile.js.map +1 -1
  317. package/dist/src/config/agent-typed-models.d.ts +18 -0
  318. package/dist/src/config/agent-typed-models.js +53 -0
  319. package/dist/src/config/agent-typed-models.js.map +1 -0
  320. package/dist/src/config/gateway-bind.js +1 -1
  321. package/dist/src/config/index.js +6 -6
  322. package/dist/src/config/loader.js +2 -2
  323. package/dist/src/config/model-input.js +2 -5
  324. package/dist/src/config/model-input.js.map +1 -1
  325. package/dist/src/config/models-json.js +2 -2
  326. package/dist/src/config/paths-state.js +1 -1
  327. package/dist/src/config/profile.js +2 -2
  328. package/dist/src/config/schema.d.ts +253 -217
  329. package/dist/src/config/schema.js +91 -40
  330. package/dist/src/config/schema.js.map +1 -1
  331. package/dist/src/config/voice.d.ts +3 -28
  332. package/dist/src/config/voice.js +27 -261
  333. package/dist/src/config/voice.js.map +1 -1
  334. package/dist/src/config/workspace-path-helpers.d.ts +1 -2
  335. package/dist/src/config/workspace-path-helpers.js.map +1 -1
  336. package/dist/src/config/workspace-path.js +1 -1
  337. package/dist/src/cron/executor.js +2 -2
  338. package/dist/src/cron/persistence.js +1 -1
  339. package/dist/src/cron/run-log-store.js +1 -1
  340. package/dist/src/daemon/constants.js +1 -1
  341. package/dist/src/daemon/install-plan.js +27 -3
  342. package/dist/src/daemon/install-plan.js.map +1 -1
  343. package/dist/src/daemon/launchd.d.ts +8 -0
  344. package/dist/src/daemon/launchd.js +7 -14
  345. package/dist/src/daemon/launchd.js.map +1 -1
  346. package/dist/src/daemon/schtasks.d.ts +25 -0
  347. package/dist/src/daemon/schtasks.js +168 -48
  348. package/dist/src/daemon/schtasks.js.map +1 -1
  349. package/dist/src/daemon/service.js +5 -4
  350. package/dist/src/daemon/service.js.map +1 -1
  351. package/dist/src/daemon/systemd.d.ts +6 -0
  352. package/dist/src/daemon/systemd.js +20 -5
  353. package/dist/src/daemon/systemd.js.map +1 -1
  354. package/dist/src/extensions/activation-context.js +0 -1
  355. package/dist/src/extensions/activation-context.js.map +1 -1
  356. package/dist/src/extensions/bundle-mcp.js +1 -1
  357. package/dist/src/extensions/discover-extensions.js +1 -1
  358. package/dist/src/extensions/health.js +1 -1
  359. package/dist/src/extensions/loader.js +1 -1
  360. package/dist/src/extensions/lockfile.js +2 -2
  361. package/dist/src/extensions/normalize-manifest.js +0 -1
  362. package/dist/src/extensions/normalize-manifest.js.map +1 -1
  363. package/dist/src/extensions/types/manifest.d.ts +0 -2
  364. package/dist/src/gateway/agent-builtin-tools.d.ts +1 -1
  365. package/dist/src/gateway/agent-builtin-tools.js +1 -0
  366. package/dist/src/gateway/agent-builtin-tools.js.map +1 -1
  367. package/dist/src/gateway/agents-admin.d.ts +9 -0
  368. package/dist/src/gateway/agents-admin.js +28 -4
  369. package/dist/src/gateway/agents-admin.js.map +1 -1
  370. package/dist/src/gateway/config-tools-web.js +3 -2
  371. package/dist/src/gateway/config-tools-web.js.map +1 -1
  372. package/dist/src/gateway/file-path-classifier.js +2 -2
  373. package/dist/src/gateway/heartbeat/service.js +2 -2
  374. package/dist/src/gateway/heartbeat/service.js.map +1 -1
  375. package/dist/src/gateway/hono/app.js +1 -1
  376. package/dist/src/gateway/hono/lib/agent-model.d.ts +25 -10
  377. package/dist/src/gateway/hono/lib/agent-model.js +60 -36
  378. package/dist/src/gateway/hono/lib/agent-model.js.map +1 -1
  379. package/dist/src/gateway/hono/lib/config-payload.js +29 -6
  380. package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
  381. package/dist/src/gateway/hono/lib/extension-store.js +2 -2
  382. package/dist/src/gateway/hono/lib/mask-secret-length.d.ts +6 -0
  383. package/dist/src/gateway/hono/lib/mask-secret-length.js +16 -0
  384. package/dist/src/gateway/hono/lib/mask-secret-length.js.map +1 -0
  385. package/dist/src/gateway/hono/lib/safe-providers-config.d.ts +1 -1
  386. package/dist/src/gateway/hono/lib/safe-providers-config.js +2 -1
  387. package/dist/src/gateway/hono/lib/safe-providers-config.js.map +1 -1
  388. package/dist/src/gateway/hono/lib/safe-voice-config.js +16 -54
  389. package/dist/src/gateway/hono/lib/safe-voice-config.js.map +1 -1
  390. package/dist/src/gateway/hono/lib/static-ui.js +2 -2
  391. package/dist/src/gateway/hono/oauth.js +1 -1
  392. package/dist/src/gateway/hono/routes/agents.js +2 -2
  393. package/dist/src/gateway/hono/routes/auth-registry-extensions.js +1 -1
  394. package/dist/src/gateway/hono/routes/config-patch/agents.js +25 -7
  395. package/dist/src/gateway/hono/routes/config-patch/agents.js.map +1 -1
  396. package/dist/src/gateway/hono/routes/config-patch/channels.js +0 -11
  397. package/dist/src/gateway/hono/routes/config-patch/channels.js.map +1 -1
  398. package/dist/src/gateway/hono/routes/config-patch/gateway.js +3 -2
  399. package/dist/src/gateway/hono/routes/config-patch/gateway.js.map +1 -1
  400. package/dist/src/gateway/hono/routes/config-patch/misc.js +8 -3
  401. package/dist/src/gateway/hono/routes/config-patch/misc.js.map +1 -1
  402. package/dist/src/gateway/hono/routes/config.js +59 -0
  403. package/dist/src/gateway/hono/routes/config.js.map +1 -1
  404. package/dist/src/gateway/hono/routes/dreaming.js +1 -1
  405. package/dist/src/gateway/hono/routes/goals.js +1 -1
  406. package/dist/src/gateway/hono/routes/goals.js.map +1 -1
  407. package/dist/src/gateway/hono/routes/host-fs.js +2 -2
  408. package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
  409. package/dist/src/gateway/hono/routes/lazy-bundles.js.map +1 -1
  410. package/dist/src/gateway/hono/routes/models.js +75 -12
  411. package/dist/src/gateway/hono/routes/models.js.map +1 -1
  412. package/dist/src/gateway/hono/routes/sessions.js +28 -7
  413. package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
  414. package/dist/src/gateway/hono/routes/shares.js +15 -13
  415. package/dist/src/gateway/hono/routes/shares.js.map +1 -1
  416. package/dist/src/gateway/hono/routes/tunnel.js +1 -1
  417. package/dist/src/gateway/hono/routes/update.js +4 -2
  418. package/dist/src/gateway/hono/routes/update.js.map +1 -1
  419. package/dist/src/gateway/hono/routes/voice.js +75 -0
  420. package/dist/src/gateway/hono/routes/voice.js.map +1 -1
  421. package/dist/src/gateway/hono/routes/workflows.d.ts +3 -0
  422. package/dist/src/gateway/hono/routes/workflows.js +347 -0
  423. package/dist/src/gateway/hono/routes/workflows.js.map +1 -0
  424. package/dist/src/gateway/hono/routes/workspace.js +4 -4
  425. package/dist/src/gateway/hono/sse.js +16 -33
  426. package/dist/src/gateway/hono/sse.js.map +1 -1
  427. package/dist/src/gateway/lock.js +11 -11
  428. package/dist/src/gateway/lock.js.map +1 -1
  429. package/dist/src/gateway/ports.js +6 -6
  430. package/dist/src/gateway/ports.js.map +1 -1
  431. package/dist/src/gateway/resolve-webchat-session-key.d.ts +19 -0
  432. package/dist/src/gateway/resolve-webchat-session-key.js +46 -0
  433. package/dist/src/gateway/resolve-webchat-session-key.js.map +1 -0
  434. package/dist/src/gateway/service/agent-runner.js +2 -2
  435. package/dist/src/gateway/service/marketplace-service.js +2 -2
  436. package/dist/src/gateway/service/run-gateway-agent.js +9 -11
  437. package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
  438. package/dist/src/gateway/service/sessions-api.d.ts +3 -0
  439. package/dist/src/gateway/service/sessions-api.js +8 -0
  440. package/dist/src/gateway/service/sessions-api.js.map +1 -1
  441. package/dist/src/gateway/service.d.ts +3 -2
  442. package/dist/src/gateway/service.js +9 -8
  443. package/dist/src/gateway/service.js.map +1 -1
  444. package/dist/src/gateway/session-reset-service.d.ts +20 -0
  445. package/dist/src/gateway/session-reset-service.js +54 -0
  446. package/dist/src/gateway/session-reset-service.js.map +1 -0
  447. package/dist/src/gateway/startup-readiness.d.ts +1 -1
  448. package/dist/src/gateway/startup-readiness.js +1 -0
  449. package/dist/src/gateway/startup-readiness.js.map +1 -1
  450. package/dist/src/gateway/workspace-fs-file-list.js +1 -1
  451. package/dist/src/heartbeat/index.js +1 -1
  452. package/dist/src/infra/gateway-processes.js +2 -2
  453. package/dist/src/infra/gateway-processes.js.map +1 -1
  454. package/dist/src/infra/restart.js +2 -2
  455. package/dist/src/infra/run-command.d.ts +16 -0
  456. package/dist/src/infra/run-command.js +67 -0
  457. package/dist/src/infra/run-command.js.map +1 -0
  458. package/dist/src/infra/update-check.js +1 -1
  459. package/dist/src/infra/update-global.d.ts +45 -0
  460. package/dist/src/infra/update-global.js +224 -0
  461. package/dist/src/infra/update-global.js.map +1 -0
  462. package/dist/src/infra/update-lock.js +3 -3
  463. package/dist/src/infra/update-runner.js +1 -1
  464. package/dist/src/infra/update-startup.js +2 -2
  465. package/dist/src/infra/write-file-atomic.js +2 -2
  466. package/dist/src/mcp/channel-shared.js +2 -1
  467. package/dist/src/mcp/channel-shared.js.map +1 -1
  468. package/dist/src/providers/auth-runtime/auth-profile-store.js +2 -2
  469. package/dist/src/providers/auth-runtime/auth-profile-store.js.map +1 -1
  470. package/dist/src/providers/auth-runtime/resolve-auth.js +1 -12
  471. package/dist/src/providers/auth-runtime/resolve-auth.js.map +1 -1
  472. package/dist/src/providers/auth-runtime/types.d.ts +6 -12
  473. package/dist/src/providers/index.js +2 -2
  474. package/dist/src/providers/model-registry.js +1 -1
  475. package/dist/src/routing/agent-session-key.d.ts +58 -0
  476. package/dist/src/routing/agent-session-key.js +164 -0
  477. package/dist/src/routing/agent-session-key.js.map +1 -0
  478. package/dist/src/routing/index.d.ts +1 -1
  479. package/dist/src/routing/index.js +4 -2
  480. package/dist/src/routing/index.js.map +1 -1
  481. package/dist/src/routing/resolve-route.d.ts +15 -0
  482. package/dist/src/routing/resolve-route.js +41 -20
  483. package/dist/src/routing/resolve-route.js.map +1 -1
  484. package/dist/src/routing/resolve-tui-session-key.d.ts +25 -0
  485. package/dist/src/routing/resolve-tui-session-key.js +54 -0
  486. package/dist/src/routing/resolve-tui-session-key.js.map +1 -0
  487. package/dist/src/routing/session-key-utils.d.ts +24 -0
  488. package/dist/src/routing/session-key-utils.js +92 -0
  489. package/dist/src/routing/session-key-utils.js.map +1 -0
  490. package/dist/src/routing/session-key.d.ts +19 -49
  491. package/dist/src/routing/session-key.js +143 -116
  492. package/dist/src/routing/session-key.js.map +1 -1
  493. package/dist/src/session/config-store.js +2 -2
  494. package/dist/src/session/index.d.ts +6 -0
  495. package/dist/src/session/index.js +7 -1
  496. package/dist/src/session/init-session-turn.d.ts +30 -0
  497. package/dist/src/session/init-session-turn.js +102 -0
  498. package/dist/src/session/init-session-turn.js.map +1 -0
  499. package/dist/src/session/lifecycle-timestamps.d.ts +8 -0
  500. package/dist/src/session/lifecycle-timestamps.js +16 -0
  501. package/dist/src/session/lifecycle-timestamps.js.map +1 -0
  502. package/dist/src/session/manager.d.ts +7 -1
  503. package/dist/src/session/manager.js +8 -1
  504. package/dist/src/session/manager.js.map +1 -1
  505. package/dist/src/session/parity/jsonl-transcript-io.js +2 -2
  506. package/dist/src/session/parity/sessions-json-file.js +1 -1
  507. package/dist/src/session/parity/transcript-file-lock.js +2 -2
  508. package/dist/src/session/parity/transcript-paths.js +2 -2
  509. package/dist/src/session/parity/transcript-paths.js.map +1 -1
  510. package/dist/src/session/parity/xopc-session-disk-entry.d.ts +6 -0
  511. package/dist/src/session/reset-policy.d.ts +32 -0
  512. package/dist/src/session/reset-policy.js +65 -0
  513. package/dist/src/session/reset-policy.js.map +1 -0
  514. package/dist/src/session/reset-triggers.d.ts +20 -0
  515. package/dist/src/session/reset-triggers.js +63 -0
  516. package/dist/src/session/reset-triggers.js.map +1 -0
  517. package/dist/src/session/reset-type.d.ts +12 -0
  518. package/dist/src/session/reset-type.js +25 -0
  519. package/dist/src/session/reset-type.js.map +1 -0
  520. package/dist/src/session/resolve-session.d.ts +30 -0
  521. package/dist/src/session/resolve-session.js +93 -0
  522. package/dist/src/session/resolve-session.js.map +1 -0
  523. package/dist/src/session/search-index-cache.js +1 -1
  524. package/dist/src/session/search-index.js +1 -1
  525. package/dist/src/session/session-title.js +3 -2
  526. package/dist/src/session/session-title.js.map +1 -1
  527. package/dist/src/session/store.d.ts +11 -4
  528. package/dist/src/session/store.js +62 -11
  529. package/dist/src/session/store.js.map +1 -1
  530. package/dist/src/session/transcript-events.js +2 -1
  531. package/dist/src/session/transcript-events.js.map +1 -1
  532. package/dist/src/share/share-auto.js +2 -2
  533. package/dist/src/share/share-store.js +3 -3
  534. package/dist/src/share/share-thumbnail.js +2 -2
  535. package/dist/src/share/share-url.d.ts +33 -0
  536. package/dist/src/share/share-url.js +56 -14
  537. package/dist/src/share/share-url.js.map +1 -1
  538. package/dist/src/share/share-zip.js +1 -1
  539. package/dist/src/share/site-share-store.js +3 -3
  540. package/dist/src/share/site-static-serve.js +1 -1
  541. package/dist/src/tui/backends/embedded-backend.js +4 -9
  542. package/dist/src/tui/backends/embedded-backend.js.map +1 -1
  543. package/dist/src/tui/backends/gateway-sse-backend.js +1 -1
  544. package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
  545. package/dist/src/tui/clipboard-image.js +3 -3
  546. package/dist/src/tui/components/chat-log.js +3 -3
  547. package/dist/src/tui/components/chat-log.js.map +1 -1
  548. package/dist/src/tui/theme-manager.js +1 -1
  549. package/dist/src/tui/theme.d.ts +0 -2
  550. package/dist/src/tui/theme.js +1 -3
  551. package/dist/src/tui/theme.js.map +1 -1
  552. package/dist/src/tui/tui-agent-events.js +2 -1
  553. package/dist/src/tui/tui-agent-events.js.map +1 -1
  554. package/dist/src/tui/tui-commands.d.ts +3 -0
  555. package/dist/src/tui/tui-commands.js +45 -10
  556. package/dist/src/tui/tui-commands.js.map +1 -1
  557. package/dist/src/tui/tui-keybindings-file.js +2 -22
  558. package/dist/src/tui/tui-keybindings-file.js.map +1 -1
  559. package/dist/src/tui/tui-scoped-models.js +2 -2
  560. package/dist/src/tui/tui-session-actions.d.ts +28 -0
  561. package/dist/src/tui/tui-session-actions.js +88 -0
  562. package/dist/src/tui/tui-session-actions.js.map +1 -0
  563. package/dist/src/tui/tui-settings.js +1 -1
  564. package/dist/src/tui/tui.js +54 -49
  565. package/dist/src/tui/tui.js.map +1 -1
  566. package/dist/src/tunnel/frpc-binary.js +3 -3
  567. package/dist/src/tunnel/frpc-config.js +1 -1
  568. package/dist/src/tunnel/frpc-extract.js +1 -1
  569. package/dist/src/tunnel/tunnel-state.js +1 -1
  570. package/dist/src/utils/logger/audit.js +1 -1
  571. package/dist/src/utils/logger/log-store.js +1 -1
  572. package/dist/src/utils/logger/rotation.js +1 -1
  573. package/dist/src/utils/string-coerce.d.ts +2 -0
  574. package/dist/src/utils/string-coerce.js +10 -1
  575. package/dist/src/utils/string-coerce.js.map +1 -1
  576. package/dist/src/voice/metadata/builtin.d.ts +2 -0
  577. package/dist/src/voice/metadata/builtin.js +420 -0
  578. package/dist/src/voice/metadata/builtin.js.map +1 -0
  579. package/dist/src/voice/metadata/index.d.ts +4 -0
  580. package/dist/src/voice/metadata/index.js +3 -0
  581. package/dist/src/voice/metadata/registry.d.ts +5 -0
  582. package/dist/src/voice/metadata/registry.js +34 -0
  583. package/dist/src/voice/metadata/registry.js.map +1 -0
  584. package/dist/src/voice/metadata/types.d.ts +41 -0
  585. package/dist/src/voice/metadata/types.js +1 -0
  586. package/dist/src/voice/stt/config-slice.d.ts +2 -5
  587. package/dist/src/voice/stt/config-slice.js +5 -26
  588. package/dist/src/voice/stt/config-slice.js.map +1 -1
  589. package/dist/src/voice/stt/list-providers.d.ts +3 -3
  590. package/dist/src/voice/stt/list-providers.js +41 -6
  591. package/dist/src/voice/stt/list-providers.js.map +1 -1
  592. package/dist/src/voice/stt/types.d.ts +1 -18
  593. package/dist/src/voice/stt/types.js +4 -2
  594. package/dist/src/voice/stt/types.js.map +1 -1
  595. package/dist/src/voice/tts/audio.js +1 -1
  596. package/dist/src/voice/tts/config-slice.d.ts +3 -7
  597. package/dist/src/voice/tts/config-slice.js +7 -38
  598. package/dist/src/voice/tts/config-slice.js.map +1 -1
  599. package/dist/src/voice/tts/list-providers.d.ts +3 -3
  600. package/dist/src/voice/tts/list-providers.js +41 -6
  601. package/dist/src/voice/tts/list-providers.js.map +1 -1
  602. package/dist/src/voice/tts/merge-config.js +2 -48
  603. package/dist/src/voice/tts/merge-config.js.map +1 -1
  604. package/dist/src/voice/tts/providers/alibaba-speech.js +1 -1
  605. package/dist/src/voice/tts/providers/alibaba-speech.js.map +1 -1
  606. package/dist/src/voice/tts/providers/edge-speech.js +2 -2
  607. package/dist/src/voice/tts/types.d.ts +1 -29
  608. package/dist/src/voice/tts/types.js +19 -17
  609. package/dist/src/voice/tts/types.js.map +1 -1
  610. package/dist/src/workflows/domain/command.d.ts +18 -0
  611. package/dist/src/workflows/domain/command.js +1 -0
  612. package/dist/src/workflows/domain/definition.d.ts +62 -0
  613. package/dist/src/workflows/domain/definition.js +1 -0
  614. package/dist/src/workflows/domain/event.d.ts +67 -0
  615. package/dist/src/workflows/domain/event.js +1 -0
  616. package/dist/src/workflows/domain/index.d.ts +5 -0
  617. package/dist/src/workflows/domain/index.js +2 -0
  618. package/dist/src/workflows/domain/result.d.ts +65 -0
  619. package/dist/src/workflows/domain/result.js +1 -0
  620. package/dist/src/workflows/domain/run.d.ts +120 -0
  621. package/dist/src/workflows/domain/run.js +14 -0
  622. package/dist/src/workflows/domain/run.js.map +1 -0
  623. package/dist/src/workflows/engine/index.d.ts +2 -0
  624. package/dist/src/workflows/engine/index.js +3 -0
  625. package/dist/src/workflows/engine/projector.d.ts +3 -0
  626. package/dist/src/workflows/engine/projector.js +205 -0
  627. package/dist/src/workflows/engine/projector.js.map +1 -0
  628. package/dist/src/workflows/engine/workflow-engine.d.ts +31 -0
  629. package/dist/src/workflows/engine/workflow-engine.js +188 -0
  630. package/dist/src/workflows/engine/workflow-engine.js.map +1 -0
  631. package/dist/src/workflows/index.d.ts +6 -0
  632. package/dist/src/workflows/index.js +11 -0
  633. package/dist/src/workflows/runtime/index.d.ts +1 -0
  634. package/dist/src/workflows/runtime/index.js +4 -0
  635. package/dist/src/workflows/runtime/script-runtime.d.ts +3 -0
  636. package/dist/src/workflows/runtime/script-runtime.js +3 -0
  637. package/dist/src/workflows/store/event-store.d.ts +17 -0
  638. package/dist/src/workflows/store/event-store.js +83 -0
  639. package/dist/src/workflows/store/event-store.js.map +1 -0
  640. package/dist/src/workflows/store/paths.d.ts +7 -0
  641. package/dist/src/workflows/store/paths.js +26 -0
  642. package/dist/src/workflows/store/paths.js.map +1 -0
  643. package/dist/src/workflows/store/run-store.d.ts +13 -0
  644. package/dist/src/workflows/store/run-store.js +68 -0
  645. package/dist/src/workflows/store/run-store.js.map +1 -0
  646. package/package.json +5 -8
  647. package/dist/gateway/static/root/assets/agents-mS3_HpRI.js +0 -222
  648. package/dist/gateway/static/root/assets/channels-settings-BG6b9KrW.js +0 -1
  649. package/dist/gateway/static/root/assets/extension-settings-page-B-W4x2xP.js +0 -1
  650. package/dist/gateway/static/root/assets/index-ew_2L2We.css +0 -1
  651. package/dist/gateway/static/root/assets/sessions-page-FaG_Vlkb.js +0 -1
  652. package/dist/gateway/static/root/assets/settings-page-Bet1OerL.js +0 -3
  653. package/dist/gateway/static/root/assets/skills-page-DhUO235y.js +0 -2
  654. package/dist/gateway/static/root/assets/url-BwNL6Rgk.js +0 -3
  655. package/dist/gateway/static/root/assets/voice-api-key-field-CGEydndO.js +0 -1
  656. package/dist/src/agent/tools/browser-legacy-tools.d.ts +0 -17
  657. package/dist/src/agent/tools/browser-legacy-tools.js +0 -766
  658. package/dist/src/agent/tools/browser-legacy-tools.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "xopc Browser Bridge",
4
- "version": "0.0.86",
4
+ "version": "0.0.88",
5
5
  "description": "Browser automation bridge for xopc — executes commands in Chrome via the local xopc daemon.",
6
6
  "permissions": [
7
7
  "debugger",
@@ -1,6 +1,6 @@
1
1
  import { mergeDistinctSenderIds } from "../../../../src/channels/pairing/preseed-allow-from.js";
2
2
  import { beginAppRegistration, initAppRegistration, pollAppRegistration, printQrCode } from "../auth/app-registration.js";
3
- import fsSync from "node:fs";
3
+ import fs from "node:fs";
4
4
  import { confirm, input, select } from "@inquirer/prompts";
5
5
  //#region extensions/feishu/src/adapters/cli-login.ts
6
6
  /**
@@ -11,14 +11,14 @@ import { confirm, input, select } from "@inquirer/prompts";
11
11
  */
12
12
  function loadConfigFromPath(configPath) {
13
13
  try {
14
- const raw = fsSync.readFileSync(configPath, "utf8");
14
+ const raw = fs.readFileSync(configPath, "utf8");
15
15
  return JSON.parse(raw);
16
16
  } catch {
17
17
  return {};
18
18
  }
19
19
  }
20
20
  function writeConfigToPath(configPath, config) {
21
- fsSync.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
21
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
22
22
  }
23
23
  function isFeishuConfigured(config) {
24
24
  const feishu = config.channels?.feishu;
@@ -1 +1 @@
1
- {"version":3,"file":"cli-login.js","names":["fs"],"sources":["../../../../../extensions/feishu/src/adapters/cli-login.ts"],"sourcesContent":["/**\n * Feishu CLI Login adapter — implements `ChannelCliLoginAdapter`.\n *\n * Provides `xopc channels login --channel feishu` interactive credential setup.\n * Supports QR scan-to-create and manual credential input.\n */\n\nimport fs from 'node:fs';\n\nimport { confirm, input, select } from '@inquirer/prompts';\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport { mergeDistinctSenderIds } from '@xopcai/xopc/channels/pairing/index.js';\nimport type { ChannelCliLoginAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';\n\nimport {\n initAppRegistration,\n beginAppRegistration,\n pollAppRegistration,\n printQrCode,\n type FeishuDomain,\n} from '../auth/app-registration.js';\n\ntype DmPolicy = 'pairing' | 'allowlist' | 'open' | 'disabled';\ntype GroupPolicy = 'open' | 'disabled' | 'allowlist';\n\nfunction loadConfigFromPath(configPath: string): Config {\n try {\n const raw = fs.readFileSync(configPath, 'utf8');\n return JSON.parse(raw) as Config;\n } catch {\n return {} as Config;\n }\n}\n\nfunction writeConfigToPath(configPath: string, config: Config): void {\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n', 'utf8');\n}\n\nfunction isFeishuConfigured(config: Config): boolean {\n const feishu = config.channels?.feishu as Record<string, unknown> | undefined;\n if (!feishu) return false;\n const appId = typeof feishu.appId === 'string' ? feishu.appId.trim() : '';\n const appSecret = typeof feishu.appSecret === 'string' ? feishu.appSecret.trim() : '';\n return Boolean(appId && appSecret);\n}\n\nfunction parseAllowlistRaw(raw: string): Array<string | number> {\n if (!raw.trim()) return [];\n return raw\n .split(/[,\\s\\n]+/)\n .map((entry) => entry.trim())\n .filter(Boolean)\n .map((entry) => {\n const asNumber = parseInt(entry, 10);\n return !isNaN(asNumber) && String(asNumber) === entry ? asNumber : entry;\n });\n}\n\nasync function acquireCredentials(params: {\n existingAppId: string;\n timeoutMs: number;\n}): Promise<{\n appId: string;\n appSecret: string;\n domain: FeishuDomain;\n ownerOpenId?: string;\n}> {\n const domain = await select<FeishuDomain>({\n message: 'Feishu/Lark domain:',\n choices: [\n { value: 'feishu', name: 'feishu (open.feishu.cn)', description: 'China / Feishu' },\n { value: 'lark', name: 'lark (open.larksuite.com)', description: 'International / Lark' },\n ],\n default: 'feishu',\n });\n\n const canScan = await initAppRegistration(domain);\n const useScan = canScan\n ? await confirm({\n message: 'Create an app by scanning a QR code (recommended)?',\n default: true,\n })\n : false;\n\n if (useScan) {\n console.log('\\nScan this QR code with Feishu/Lark to create an app:\\n');\n const begin = await beginAppRegistration(domain);\n await printQrCode(begin.qrUrl);\n console.log('\\nWaiting for confirmation...\\n');\n\n const expireInSec = Math.min(begin.expireInSec, Math.floor(params.timeoutMs / 1000));\n const outcome = await pollAppRegistration({\n deviceCode: begin.deviceCode,\n intervalSec: begin.intervalSec,\n expireInSec,\n initialDomain: domain,\n });\n\n if (outcome.status === 'success') {\n console.log(`✅ App created. Domain: \"${outcome.result.domain}\".\\n`);\n return {\n appId: outcome.result.appId,\n appSecret: outcome.result.appSecret,\n domain: outcome.result.domain,\n ownerOpenId: outcome.result.openId,\n };\n }\n\n const reason =\n outcome.status === 'access_denied'\n ? 'User denied authorization.'\n : outcome.status === 'expired'\n ? 'Session expired.'\n : outcome.status === 'timeout'\n ? 'Scan timed out.'\n : `Error: ${'message' in outcome ? outcome.message : 'unknown'}`;\n console.log(`${reason} Falling back to manual input.\\n`);\n }\n\n console.log('📝 Enter Feishu app credentials (from Feishu Open Platform developer console):\\n');\n\n const appId = (\n await input({\n message: 'App ID (cli_xxx):',\n default: params.existingAppId || undefined,\n validate: (value) => value.trim().length > 0 || 'App ID cannot be empty',\n })\n ).trim();\n\n const appSecret = (\n await input({\n message: 'App Secret:',\n validate: (value) => value.trim().length > 0 || 'App Secret cannot be empty',\n })\n ).trim();\n\n return { appId, appSecret, domain };\n}\n\nasync function promptSecurityPolicies(ownerOpenId?: string): Promise<{\n dmPolicy: DmPolicy;\n groupPolicy: GroupPolicy;\n allowFrom: Array<string | number>;\n groupAllowFrom: Array<string | number>;\n requireMention: boolean;\n}> {\n const dmPolicy = await select<DmPolicy>({\n message: 'DM (private chat) policy:',\n choices: [\n { value: 'open', name: 'open [default]', description: 'Anyone can DM the bot after setup' },\n {\n value: 'pairing',\n name: 'pairing',\n description: 'New users need `xopc channels pairing approve`',\n },\n { value: 'allowlist', name: 'allowlist', description: 'Only allowlisted users' },\n { value: 'disabled', name: 'disabled', description: 'Disable DMs' },\n ],\n default: 'open',\n });\n\n let allowFrom: Array<string | number> = [];\n if (dmPolicy === 'allowlist') {\n const defaultAllow = ownerOpenId ?? '';\n const raw = await input({\n message: 'Allowed user open_id / union_id (comma-separated):',\n default: defaultAllow,\n });\n allowFrom = parseAllowlistRaw(raw || defaultAllow);\n }\n\n const groupPolicy = await select<GroupPolicy>({\n message: 'Group chat policy:',\n choices: [\n { value: 'allowlist', name: 'allowlist [recommended]', description: 'Only allowlisted groups' },\n { value: 'open', name: 'open', description: 'All groups allowed' },\n { value: 'disabled', name: 'disabled', description: 'Disable groups' },\n ],\n default: 'allowlist',\n });\n\n let groupAllowFrom: Array<string | number> = [];\n if (groupPolicy === 'allowlist') {\n const raw = await input({\n message: 'Allowed group chat IDs (comma-separated, e.g. oc_xxx):',\n default: '',\n });\n groupAllowFrom = parseAllowlistRaw(raw);\n }\n\n const requireMention = await confirm({\n message: 'Require @mention in groups?',\n default: true,\n });\n\n const extras =\n ownerOpenId?.trim() && (dmPolicy === 'pairing' || dmPolicy === 'allowlist') ? [ownerOpenId.trim()] : [];\n const mergedAllowFrom = mergeDistinctSenderIds(allowFrom, extras);\n\n return { dmPolicy, groupPolicy, allowFrom: mergedAllowFrom, groupAllowFrom, requireMention };\n}\n\nexport const feishuCliLoginAdapter: ChannelCliLoginAdapter = {\n async runLogin(params) {\n const { configPath, verbose, timeoutMs = 480_000, accountId, writeConfig = true } = params;\n\n if (verbose) {\n console.log(`[feishu-login] configPath=${configPath}, timeoutMs=${timeoutMs}`);\n }\n\n const config = loadConfigFromPath(configPath);\n const existingFeishu = config.channels?.feishu as Record<string, unknown> | undefined;\n const existingAppId = typeof existingFeishu?.appId === 'string' ? existingFeishu.appId : '';\n const alreadyConfigured = isFeishuConfigured(config);\n\n console.log(`\\n${'='.repeat(50)}`);\n console.log('📱 Feishu / Lark login');\n console.log(`${'='.repeat(50)}\\n`);\n\n if (alreadyConfigured) {\n const reconfigure = await confirm({\n message: `Feishu is already configured (App ID: ${existingAppId}). Reconfigure?`,\n default: false,\n });\n if (!reconfigure) {\n console.log('Keeping existing configuration.\\n');\n return { ok: true, message: 'Existing configuration kept.', accountId };\n }\n }\n\n const credentials = await acquireCredentials({\n existingAppId,\n timeoutMs,\n });\n\n const policies = await promptSecurityPolicies(credentials.ownerOpenId);\n\n const nextFeishu: Record<string, unknown> = {\n ...(existingFeishu ?? {}),\n enabled: true,\n appId: credentials.appId,\n appSecret: credentials.appSecret,\n domain: credentials.domain,\n connectionMode: (existingFeishu?.connectionMode as string) || 'websocket',\n dmPolicy: policies.dmPolicy,\n groupPolicy: policies.groupPolicy,\n allowFrom: policies.allowFrom,\n groupAllowFrom: policies.groupAllowFrom,\n requireMention: policies.requireMention,\n };\n\n const nextConfig: Config = {\n ...config,\n channels: {\n ...config.channels,\n feishu: nextFeishu,\n },\n };\n\n if (writeConfig) {\n writeConfigToPath(configPath, nextConfig);\n console.log(`✅ Feishu configuration saved to ${configPath}\\n`);\n } else {\n console.log('✅ Feishu credentials acquired (--credentials-only: config not updated).\\n');\n }\n\n return {\n ok: true,\n message: `Feishu login complete (App ID: ${credentials.appId}).`,\n accountId: accountId ?? undefined,\n };\n },\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli-login.js","names":[],"sources":["../../../../../extensions/feishu/src/adapters/cli-login.ts"],"sourcesContent":["/**\n * Feishu CLI Login adapter — implements `ChannelCliLoginAdapter`.\n *\n * Provides `xopc channels login --channel feishu` interactive credential setup.\n * Supports QR scan-to-create and manual credential input.\n */\n\nimport fs from 'node:fs';\n\nimport { confirm, input, select } from '@inquirer/prompts';\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport { mergeDistinctSenderIds } from '@xopcai/xopc/channels/pairing/index.js';\nimport type { ChannelCliLoginAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';\n\nimport {\n initAppRegistration,\n beginAppRegistration,\n pollAppRegistration,\n printQrCode,\n type FeishuDomain,\n} from '../auth/app-registration.js';\n\ntype DmPolicy = 'pairing' | 'allowlist' | 'open' | 'disabled';\ntype GroupPolicy = 'open' | 'disabled' | 'allowlist';\n\nfunction loadConfigFromPath(configPath: string): Config {\n try {\n const raw = fs.readFileSync(configPath, 'utf8');\n return JSON.parse(raw) as Config;\n } catch {\n return {} as Config;\n }\n}\n\nfunction writeConfigToPath(configPath: string, config: Config): void {\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n', 'utf8');\n}\n\nfunction isFeishuConfigured(config: Config): boolean {\n const feishu = config.channels?.feishu as Record<string, unknown> | undefined;\n if (!feishu) return false;\n const appId = typeof feishu.appId === 'string' ? feishu.appId.trim() : '';\n const appSecret = typeof feishu.appSecret === 'string' ? feishu.appSecret.trim() : '';\n return Boolean(appId && appSecret);\n}\n\nfunction parseAllowlistRaw(raw: string): Array<string | number> {\n if (!raw.trim()) return [];\n return raw\n .split(/[,\\s\\n]+/)\n .map((entry) => entry.trim())\n .filter(Boolean)\n .map((entry) => {\n const asNumber = parseInt(entry, 10);\n return !isNaN(asNumber) && String(asNumber) === entry ? asNumber : entry;\n });\n}\n\nasync function acquireCredentials(params: {\n existingAppId: string;\n timeoutMs: number;\n}): Promise<{\n appId: string;\n appSecret: string;\n domain: FeishuDomain;\n ownerOpenId?: string;\n}> {\n const domain = await select<FeishuDomain>({\n message: 'Feishu/Lark domain:',\n choices: [\n { value: 'feishu', name: 'feishu (open.feishu.cn)', description: 'China / Feishu' },\n { value: 'lark', name: 'lark (open.larksuite.com)', description: 'International / Lark' },\n ],\n default: 'feishu',\n });\n\n const canScan = await initAppRegistration(domain);\n const useScan = canScan\n ? await confirm({\n message: 'Create an app by scanning a QR code (recommended)?',\n default: true,\n })\n : false;\n\n if (useScan) {\n console.log('\\nScan this QR code with Feishu/Lark to create an app:\\n');\n const begin = await beginAppRegistration(domain);\n await printQrCode(begin.qrUrl);\n console.log('\\nWaiting for confirmation...\\n');\n\n const expireInSec = Math.min(begin.expireInSec, Math.floor(params.timeoutMs / 1000));\n const outcome = await pollAppRegistration({\n deviceCode: begin.deviceCode,\n intervalSec: begin.intervalSec,\n expireInSec,\n initialDomain: domain,\n });\n\n if (outcome.status === 'success') {\n console.log(`✅ App created. Domain: \"${outcome.result.domain}\".\\n`);\n return {\n appId: outcome.result.appId,\n appSecret: outcome.result.appSecret,\n domain: outcome.result.domain,\n ownerOpenId: outcome.result.openId,\n };\n }\n\n const reason =\n outcome.status === 'access_denied'\n ? 'User denied authorization.'\n : outcome.status === 'expired'\n ? 'Session expired.'\n : outcome.status === 'timeout'\n ? 'Scan timed out.'\n : `Error: ${'message' in outcome ? outcome.message : 'unknown'}`;\n console.log(`${reason} Falling back to manual input.\\n`);\n }\n\n console.log('📝 Enter Feishu app credentials (from Feishu Open Platform developer console):\\n');\n\n const appId = (\n await input({\n message: 'App ID (cli_xxx):',\n default: params.existingAppId || undefined,\n validate: (value) => value.trim().length > 0 || 'App ID cannot be empty',\n })\n ).trim();\n\n const appSecret = (\n await input({\n message: 'App Secret:',\n validate: (value) => value.trim().length > 0 || 'App Secret cannot be empty',\n })\n ).trim();\n\n return { appId, appSecret, domain };\n}\n\nasync function promptSecurityPolicies(ownerOpenId?: string): Promise<{\n dmPolicy: DmPolicy;\n groupPolicy: GroupPolicy;\n allowFrom: Array<string | number>;\n groupAllowFrom: Array<string | number>;\n requireMention: boolean;\n}> {\n const dmPolicy = await select<DmPolicy>({\n message: 'DM (private chat) policy:',\n choices: [\n { value: 'open', name: 'open [default]', description: 'Anyone can DM the bot after setup' },\n {\n value: 'pairing',\n name: 'pairing',\n description: 'New users need `xopc channels pairing approve`',\n },\n { value: 'allowlist', name: 'allowlist', description: 'Only allowlisted users' },\n { value: 'disabled', name: 'disabled', description: 'Disable DMs' },\n ],\n default: 'open',\n });\n\n let allowFrom: Array<string | number> = [];\n if (dmPolicy === 'allowlist') {\n const defaultAllow = ownerOpenId ?? '';\n const raw = await input({\n message: 'Allowed user open_id / union_id (comma-separated):',\n default: defaultAllow,\n });\n allowFrom = parseAllowlistRaw(raw || defaultAllow);\n }\n\n const groupPolicy = await select<GroupPolicy>({\n message: 'Group chat policy:',\n choices: [\n { value: 'allowlist', name: 'allowlist [recommended]', description: 'Only allowlisted groups' },\n { value: 'open', name: 'open', description: 'All groups allowed' },\n { value: 'disabled', name: 'disabled', description: 'Disable groups' },\n ],\n default: 'allowlist',\n });\n\n let groupAllowFrom: Array<string | number> = [];\n if (groupPolicy === 'allowlist') {\n const raw = await input({\n message: 'Allowed group chat IDs (comma-separated, e.g. oc_xxx):',\n default: '',\n });\n groupAllowFrom = parseAllowlistRaw(raw);\n }\n\n const requireMention = await confirm({\n message: 'Require @mention in groups?',\n default: true,\n });\n\n const extras =\n ownerOpenId?.trim() && (dmPolicy === 'pairing' || dmPolicy === 'allowlist') ? [ownerOpenId.trim()] : [];\n const mergedAllowFrom = mergeDistinctSenderIds(allowFrom, extras);\n\n return { dmPolicy, groupPolicy, allowFrom: mergedAllowFrom, groupAllowFrom, requireMention };\n}\n\nexport const feishuCliLoginAdapter: ChannelCliLoginAdapter = {\n async runLogin(params) {\n const { configPath, verbose, timeoutMs = 480_000, accountId, writeConfig = true } = params;\n\n if (verbose) {\n console.log(`[feishu-login] configPath=${configPath}, timeoutMs=${timeoutMs}`);\n }\n\n const config = loadConfigFromPath(configPath);\n const existingFeishu = config.channels?.feishu as Record<string, unknown> | undefined;\n const existingAppId = typeof existingFeishu?.appId === 'string' ? existingFeishu.appId : '';\n const alreadyConfigured = isFeishuConfigured(config);\n\n console.log(`\\n${'='.repeat(50)}`);\n console.log('📱 Feishu / Lark login');\n console.log(`${'='.repeat(50)}\\n`);\n\n if (alreadyConfigured) {\n const reconfigure = await confirm({\n message: `Feishu is already configured (App ID: ${existingAppId}). Reconfigure?`,\n default: false,\n });\n if (!reconfigure) {\n console.log('Keeping existing configuration.\\n');\n return { ok: true, message: 'Existing configuration kept.', accountId };\n }\n }\n\n const credentials = await acquireCredentials({\n existingAppId,\n timeoutMs,\n });\n\n const policies = await promptSecurityPolicies(credentials.ownerOpenId);\n\n const nextFeishu: Record<string, unknown> = {\n ...(existingFeishu ?? {}),\n enabled: true,\n appId: credentials.appId,\n appSecret: credentials.appSecret,\n domain: credentials.domain,\n connectionMode: (existingFeishu?.connectionMode as string) || 'websocket',\n dmPolicy: policies.dmPolicy,\n groupPolicy: policies.groupPolicy,\n allowFrom: policies.allowFrom,\n groupAllowFrom: policies.groupAllowFrom,\n requireMention: policies.requireMention,\n };\n\n const nextConfig: Config = {\n ...config,\n channels: {\n ...config.channels,\n feishu: nextFeishu,\n },\n };\n\n if (writeConfig) {\n writeConfigToPath(configPath, nextConfig);\n console.log(`✅ Feishu configuration saved to ${configPath}\\n`);\n } else {\n console.log('✅ Feishu credentials acquired (--credentials-only: config not updated).\\n');\n }\n\n return {\n ok: true,\n message: `Feishu login complete (App ID: ${credentials.appId}).`,\n accountId: accountId ?? undefined,\n };\n },\n};\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { checkFileSafety } from "../../../../src/agent/prompt/safety.js";
2
2
  import { getMimeType } from "../../../../src/channels/media.js";
3
3
  import { getWorkspacePath } from "../../../../src/config/workspace-path-helpers.js";
4
- import path from "node:path";
5
4
  import { promises } from "node:fs";
5
+ import path from "node:path";
6
6
  import { Readable } from "node:stream";
7
7
  //#region extensions/feishu/src/outbound/media-load.ts
8
8
  function isPathUnderRoots(resolved, roots) {
@@ -1,6 +1,6 @@
1
- import { init_session_key, parseSessionKey } from "../../../src/routing/session-key.js";
2
1
  import { createLogger } from "../../../src/utils/logger/index.js";
3
2
  import { init_logger } from "../../../src/utils/logger.js";
3
+ import { init_session_key, parseSessionKey } from "../../../src/routing/session-key.js";
4
4
  import { resolveFeishuAccount } from "./state/accounts.js";
5
5
  import { createFeishuClient } from "./transport/client/client.js";
6
6
  import { editMessageFeishu } from "./outbound/actions.js";
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Resolves Telegram Bot API `chat_id` from config/UI `to` / `targetChatId`.
3
- * Accepts numeric ids, full session keys (`main:telegram:...`), or
3
+ * Accepts numeric ids, full session keys (`agent:main:telegram:...`), or
4
4
  * routing suffixes (`account:dm:peerId` / `account:group:peerId`).
5
5
  */
6
6
  export declare function normalizeTelegramDeliveryChatId(to: string): string;
@@ -3,7 +3,7 @@ import { init_session_key, parseSessionKey } from "../../../src/routing/session-
3
3
  init_session_key();
4
4
  /**
5
5
  * Resolves Telegram Bot API `chat_id` from config/UI `to` / `targetChatId`.
6
- * Accepts numeric ids, full session keys (`main:telegram:...`), or
6
+ * Accepts numeric ids, full session keys (`agent:main:telegram:...`), or
7
7
  * routing suffixes (`account:dm:peerId` / `account:group:peerId`).
8
8
  */
9
9
  function normalizeTelegramDeliveryChatId(to) {
@@ -1 +1 @@
1
- {"version":3,"file":"delivery-chat-id.js","names":[],"sources":["../../../../extensions/telegram/src/delivery-chat-id.ts"],"sourcesContent":["import { parseSessionKey } from '@xopcai/xopc/routing/session-key.js';\n\n/**\n * Resolves Telegram Bot API `chat_id` from config/UI `to` / `targetChatId`.\n * Accepts numeric ids, full session keys (`main:telegram:...`), or\n * routing suffixes (`account:dm:peerId` / `account:group:peerId`).\n */\nexport function normalizeTelegramDeliveryChatId(to: string): string {\n const trimmed = to.trim();\n if (!trimmed) {\n return trimmed;\n }\n\n const parsed = parseSessionKey(trimmed);\n if (parsed?.source === 'telegram') {\n return parsed.peerId;\n }\n\n const parts = trimmed.split(':');\n if (parts.length === 3 && (parts[1] === 'dm' || parts[1] === 'group') && parts[2] !== '') {\n return parts[2];\n }\n\n return trimmed;\n}\n"],"mappings":";;kBAAsE;;;;;;AAOtE,SAAgB,gCAAgC,IAAoB;CAClE,MAAM,UAAU,GAAG,MAAM;AACzB,KAAI,CAAC,QACH,QAAO;CAGT,MAAM,SAAS,gBAAgB,QAAQ;AACvC,KAAI,QAAQ,WAAW,WACrB,QAAO,OAAO;CAGhB,MAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,KAAI,MAAM,WAAW,MAAM,MAAM,OAAO,QAAQ,MAAM,OAAO,YAAY,MAAM,OAAO,GACpF,QAAO,MAAM;AAGf,QAAO"}
1
+ {"version":3,"file":"delivery-chat-id.js","names":[],"sources":["../../../../extensions/telegram/src/delivery-chat-id.ts"],"sourcesContent":["import { parseSessionKey } from '@xopcai/xopc/routing/session-key.js';\n\n/**\n * Resolves Telegram Bot API `chat_id` from config/UI `to` / `targetChatId`.\n * Accepts numeric ids, full session keys (`agent:main:telegram:...`), or\n * routing suffixes (`account:dm:peerId` / `account:group:peerId`).\n */\nexport function normalizeTelegramDeliveryChatId(to: string): string {\n const trimmed = to.trim();\n if (!trimmed) {\n return trimmed;\n }\n\n const parsed = parseSessionKey(trimmed);\n if (parsed?.source === 'telegram') {\n return parsed.peerId;\n }\n\n const parts = trimmed.split(':');\n if (parts.length === 3 && (parts[1] === 'dm' || parts[1] === 'group') && parts[2] !== '') {\n return parts[2];\n }\n\n return trimmed;\n}\n"],"mappings":";;kBAAsE;;;;;;AAOtE,SAAgB,gCAAgC,IAAoB;CAClE,MAAM,UAAU,GAAG,MAAM;AACzB,KAAI,CAAC,QACH,QAAO;CAGT,MAAM,SAAS,gBAAgB,QAAQ;AACvC,KAAI,QAAQ,WAAW,WACrB,QAAO,OAAO;CAGhB,MAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,KAAI,MAAM,WAAW,MAAM,MAAM,OAAO,QAAQ,MAAM,OAAO,YAAY,MAAM,OAAO,GACpF,QAAO,MAAM;AAGf,QAAO"}
@@ -1,6 +1,6 @@
1
- import { TelegramConfigSchema, init_config_schema } from "./config-schema.js";
2
1
  import { createLogger } from "../../../src/utils/logger/index.js";
3
2
  import { init_logger } from "../../../src/utils/logger.js";
3
+ import { TelegramConfigSchema, init_config_schema } from "./config-schema.js";
4
4
  import { getWorkflowProgressBroker } from "../../../src/agent/workflow/progress-broker.js";
5
5
  import "../../../src/agent/workflow/index.js";
6
6
  import { transcribe } from "../../../src/voice/stt/transcribe-core.js";
@@ -1,7 +1,8 @@
1
- import { buildSessionKey } from "../../../src/routing/session-key.js";
2
- import { applyIdentityLinks, resolveRoute } from "../../../src/routing/resolve-route.js";
3
1
  import { createLogger } from "../../../src/utils/logger/index.js";
4
2
  import { init_logger } from "../../../src/utils/logger.js";
3
+ import { buildSessionKey } from "../../../src/routing/session-key.js";
4
+ import { applyIdentityLinks, resolveRoute } from "../../../src/routing/resolve-route.js";
5
+ import "../../../src/routing/index.js";
5
6
  //#region extensions/telegram/src/routing-integration.ts
6
7
  init_logger();
7
8
  const log = createLogger("TelegramRouting");
@@ -1 +1 @@
1
- {"version":3,"file":"routing-integration.js","names":[],"sources":["../../../../extensions/telegram/src/routing-integration.ts"],"sourcesContent":["/**\n * Telegram Routing Integration\n * \n * Integrates Telegram channel with the new session routing system.\n * Provides session key generation with routing context.\n */\n\nimport type { Context } from 'grammy';\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport {\n buildSessionKey,\n resolveRoute,\n applyIdentityLinks,\n type RouteContext,\n} from '@xopcai/xopc/routing/index.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\n\nconst log = createLogger('TelegramRouting');\n\nexport interface TelegramRoutingContext {\n accountId: string;\n chatId: string;\n senderId: string;\n senderUsername?: string;\n isGroup: boolean;\n threadId?: string;\n guildId?: string;\n memberRoleIds?: string[];\n /**\n * Channel type for identity links.\n * @default 'telegram'\n */\n channel?: string;\n}\n\n/**\n * Generate session key with routing integration\n */\nexport function generateSessionKeyWithRouting(\n ctx: TelegramRoutingContext,\n config: Config\n): string {\n const channel = ctx.channel ?? 'telegram';\n \n // Build route context for resolveRoute\n const routeInput: RouteContext = {\n channel,\n accountId: ctx.accountId,\n peerKind: ctx.isGroup ? 'group' : 'dm',\n peerId: ctx.isGroup ? ctx.chatId : ctx.senderId,\n guildId: ctx.guildId ?? null,\n teamId: null,\n memberRoleIds: ctx.memberRoleIds ?? [],\n };\n\n // Resolve route using bindings\n const route = resolveRoute({\n config,\n ...routeInput,\n threadId: ctx.threadId,\n });\n\n // Apply identity links for cross-platform user merging\n const identityLinks = config.session?.identityLinks ?? {};\n const originalPeerId = ctx.isGroup ? ctx.chatId : ctx.senderId;\n const finalPeerId = applyIdentityLinks(\n originalPeerId,\n channel,\n identityLinks\n );\n \n // Rebuild session key with final peerId\n const finalSessionKey = buildSessionKey({\n agentId: route.agentId,\n source: channel,\n accountId: route.accountId,\n peerKind: ctx.isGroup ? 'group' : 'dm',\n peerId: finalPeerId,\n threadId: ctx.threadId,\n });\n\n log.debug({\n accountId: ctx.accountId,\n chatId: ctx.chatId,\n senderId: ctx.senderId,\n sessionKey: finalSessionKey,\n agentId: route.agentId,\n }, 'Generated session key with routing');\n\n return finalSessionKey;\n}\n\n/**\n * Extract member role IDs from Telegram chat member\n * (Currently returns empty array as Telegram doesn't have direct role mapping)\n */\nexport function extractMemberRoleIds(ctx: Context): string[] {\n // Telegram doesn't expose a first-class role id list like some other chat APIs\n // Could implement custom role mapping based on admin status\n const chatMember = ctx.chatMember;\n if (!chatMember?.new_chat_member) {\n return [];\n }\n\n const roles: string[] = [];\n const memberStatus = chatMember.new_chat_member.status;\n \n if (memberStatus === 'creator') {\n roles.push('telegram:creator');\n } else if (memberStatus === 'administrator') {\n roles.push('telegram:admin');\n }\n \n return roles;\n}\n"],"mappings":";;;;;aAe4D;AAE5D,MAAM,MAAM,aAAa,kBAAkB;;;;AAqB3C,SAAgB,8BACd,KACA,QACQ;CACR,MAAM,UAAU,IAAI,WAAW;CAc/B,MAAM,QAAQ,aAAa;EACzB;EAXA;EACA,WAAW,IAAI;EACf,UAAU,IAAI,UAAU,UAAU;EAClC,QAAQ,IAAI,UAAU,IAAI,SAAS,IAAI;EACvC,SAAS,IAAI,WAAW;EACxB,QAAQ;EACR,eAAe,IAAI,iBAAiB,EAAE;EAOtC,UAAU,IAAI;EACf,CAAC;CAGF,MAAM,gBAAgB,OAAO,SAAS,iBAAiB,EAAE;CAEzD,MAAM,cAAc,mBADG,IAAI,UAAU,IAAI,SAAS,IAAI,UAGpD,SACA,cACD;CAGD,MAAM,kBAAkB,gBAAgB;EACtC,SAAS,MAAM;EACf,QAAQ;EACR,WAAW,MAAM;EACjB,UAAU,IAAI,UAAU,UAAU;EAClC,QAAQ;EACR,UAAU,IAAI;EACf,CAAC;AAEF,KAAI,MAAM;EACR,WAAW,IAAI;EACf,QAAQ,IAAI;EACZ,UAAU,IAAI;EACd,YAAY;EACZ,SAAS,MAAM;EAChB,EAAE,qCAAqC;AAExC,QAAO;;;;;;AAOT,SAAgB,qBAAqB,KAAwB;CAG3D,MAAM,aAAa,IAAI;AACvB,KAAI,CAAC,YAAY,gBACf,QAAO,EAAE;CAGX,MAAM,QAAkB,EAAE;CAC1B,MAAM,eAAe,WAAW,gBAAgB;AAEhD,KAAI,iBAAiB,UACnB,OAAM,KAAK,mBAAmB;UACrB,iBAAiB,gBAC1B,OAAM,KAAK,iBAAiB;AAG9B,QAAO"}
1
+ {"version":3,"file":"routing-integration.js","names":[],"sources":["../../../../extensions/telegram/src/routing-integration.ts"],"sourcesContent":["/**\n * Telegram Routing Integration\n * \n * Integrates Telegram channel with the new session routing system.\n * Provides session key generation with routing context.\n */\n\nimport type { Context } from 'grammy';\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport {\n buildSessionKey,\n resolveRoute,\n applyIdentityLinks,\n type RouteContext,\n} from '@xopcai/xopc/routing/index.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\n\nconst log = createLogger('TelegramRouting');\n\nexport interface TelegramRoutingContext {\n accountId: string;\n chatId: string;\n senderId: string;\n senderUsername?: string;\n isGroup: boolean;\n threadId?: string;\n guildId?: string;\n memberRoleIds?: string[];\n /**\n * Channel type for identity links.\n * @default 'telegram'\n */\n channel?: string;\n}\n\n/**\n * Generate session key with routing integration\n */\nexport function generateSessionKeyWithRouting(\n ctx: TelegramRoutingContext,\n config: Config\n): string {\n const channel = ctx.channel ?? 'telegram';\n \n // Build route context for resolveRoute\n const routeInput: RouteContext = {\n channel,\n accountId: ctx.accountId,\n peerKind: ctx.isGroup ? 'group' : 'dm',\n peerId: ctx.isGroup ? ctx.chatId : ctx.senderId,\n guildId: ctx.guildId ?? null,\n teamId: null,\n memberRoleIds: ctx.memberRoleIds ?? [],\n };\n\n // Resolve route using bindings\n const route = resolveRoute({\n config,\n ...routeInput,\n threadId: ctx.threadId,\n });\n\n // Apply identity links for cross-platform user merging\n const identityLinks = config.session?.identityLinks ?? {};\n const originalPeerId = ctx.isGroup ? ctx.chatId : ctx.senderId;\n const finalPeerId = applyIdentityLinks(\n originalPeerId,\n channel,\n identityLinks\n );\n \n // Rebuild session key with final peerId\n const finalSessionKey = buildSessionKey({\n agentId: route.agentId,\n source: channel,\n accountId: route.accountId,\n peerKind: ctx.isGroup ? 'group' : 'dm',\n peerId: finalPeerId,\n threadId: ctx.threadId,\n });\n\n log.debug({\n accountId: ctx.accountId,\n chatId: ctx.chatId,\n senderId: ctx.senderId,\n sessionKey: finalSessionKey,\n agentId: route.agentId,\n }, 'Generated session key with routing');\n\n return finalSessionKey;\n}\n\n/**\n * Extract member role IDs from Telegram chat member\n * (Currently returns empty array as Telegram doesn't have direct role mapping)\n */\nexport function extractMemberRoleIds(ctx: Context): string[] {\n // Telegram doesn't expose a first-class role id list like some other chat APIs\n // Could implement custom role mapping based on admin status\n const chatMember = ctx.chatMember;\n if (!chatMember?.new_chat_member) {\n return [];\n }\n\n const roles: string[] = [];\n const memberStatus = chatMember.new_chat_member.status;\n \n if (memberStatus === 'creator') {\n roles.push('telegram:creator');\n } else if (memberStatus === 'administrator') {\n roles.push('telegram:admin');\n }\n \n return roles;\n}\n"],"mappings":";;;;;;aAe4D;AAE5D,MAAM,MAAM,aAAa,kBAAkB;;;;AAqB3C,SAAgB,8BACd,KACA,QACQ;CACR,MAAM,UAAU,IAAI,WAAW;CAc/B,MAAM,QAAQ,aAAa;EACzB;EAXA;EACA,WAAW,IAAI;EACf,UAAU,IAAI,UAAU,UAAU;EAClC,QAAQ,IAAI,UAAU,IAAI,SAAS,IAAI;EACvC,SAAS,IAAI,WAAW;EACxB,QAAQ;EACR,eAAe,IAAI,iBAAiB,EAAE;EAOtC,UAAU,IAAI;EACf,CAAC;CAGF,MAAM,gBAAgB,OAAO,SAAS,iBAAiB,EAAE;CAEzD,MAAM,cAAc,mBADG,IAAI,UAAU,IAAI,SAAS,IAAI,UAGpD,SACA,cACD;CAGD,MAAM,kBAAkB,gBAAgB;EACtC,SAAS,MAAM;EACf,QAAQ;EACR,WAAW,MAAM;EACjB,UAAU,IAAI,UAAU,UAAU;EAClC,QAAQ;EACR,UAAU,IAAI;EACf,CAAC;AAEF,KAAI,MAAM;EACR,WAAW,IAAI;EACf,QAAQ,IAAI;EACZ,UAAU,IAAI;EACd,YAAY;EACZ,SAAS,MAAM;EAChB,EAAE,qCAAqC;AAExC,QAAO;;;;;;AAOT,SAAgB,qBAAqB,KAAwB;CAG3D,MAAM,aAAa,IAAI;AACvB,KAAI,CAAC,YAAY,gBACf,QAAO,EAAE;CAGX,MAAM,QAAkB,EAAE;CAC1B,MAAM,eAAe,WAAW,gBAAgB;AAEhD,KAAI,iBAAiB,UACnB,OAAM,KAAK,mBAAmB;UACrB,iBAAiB,gBAC1B,OAAM,KAAK,iBAAiB;AAG9B,QAAO"}
@@ -1,6 +1,6 @@
1
- import { init_session_key, parseSessionKey } from "../../../src/routing/session-key.js";
2
1
  import { createLogger } from "../../../src/utils/logger/index.js";
3
2
  import { init_logger } from "../../../src/utils/logger.js";
3
+ import { init_session_key, parseSessionKey } from "../../../src/routing/session-key.js";
4
4
  //#region extensions/telegram/src/workflow-progress.ts
5
5
  init_session_key();
6
6
  init_logger();
@@ -2,7 +2,7 @@
2
2
  "id": "telegram",
3
3
  "name": "Telegram Channel",
4
4
  "description": "Bundled Telegram Bot channel (private workspace sources; ships inside @xopcai/xopc dist/)",
5
- "version": "0.0.86",
5
+ "version": "0.0.88",
6
6
  "kind": "channel",
7
7
  "main": "src/index.ts",
8
8
  "channels": ["telegram"],
@@ -26,7 +26,7 @@ vi.mock("../messaging/send.js", async () => {
26
26
  const getCtxMock = vi.mocked(getContextToken);
27
27
  const ensureMock = vi.mocked(ensureWeixinContextTokenForOutbound);
28
28
  const sendMock = vi.mocked(sendMessageWeixin);
29
- const SESSION = "main:weixin:default:dm:ilink_user_abc";
29
+ const SESSION = "agent:main:weixin:default:direct:ilink_user_abc";
30
30
  function mkCap() {
31
31
  return createWeixinWorkflowProgressCapability({ getConfig: () => ({}) });
32
32
  }
@@ -82,7 +82,7 @@ describe("weixin workflow progress capability", () => {
82
82
  });
83
83
  it("throws on unroutable sessionKey", async () => {
84
84
  await expect(mkCap().postProgress({
85
- sessionKey: "main:telegram:default:dm:123",
85
+ sessionKey: "agent:main:telegram:default:direct:123",
86
86
  text: "x",
87
87
  isFinal: true
88
88
  })).rejects.toThrow(/cannot route/);
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-progress.test.js","names":[],"sources":["../../../../../extensions/weixin/src/__tests__/workflow-progress.test.ts"],"sourcesContent":["import { describe, expect, it, vi } from 'vitest';\n\nimport { createWeixinWorkflowProgressCapability } from '../workflow-progress.js';\n\nvi.mock('../auth/accounts.js', () => ({\n resolveWeixinAccount: () => ({\n accountId: 'default',\n enabled: true,\n configured: true,\n token: 'tok-x',\n baseUrl: 'https://example/weixin',\n routeTag: undefined,\n }),\n}));\n\nvi.mock('../messaging/inbound.js', () => ({\n getContextToken: vi.fn(),\n restoreContextTokens: vi.fn(),\n}));\n\nvi.mock('../messaging/context-token-init.js', () => ({\n ensureWeixinContextTokenForOutbound: vi.fn(),\n}));\n\nvi.mock('../messaging/send.js', async () => {\n const actual = await vi.importActual<typeof import('../messaging/send.js')>('../messaging/send.js');\n return {\n ...actual,\n sendMessageWeixin: vi.fn().mockResolvedValue({ messageId: 'weixin-1' }),\n };\n});\n\nimport { getContextToken } from '../messaging/inbound.js';\nimport { ensureWeixinContextTokenForOutbound } from '../messaging/context-token-init.js';\nimport { sendMessageWeixin } from '../messaging/send.js';\n\nconst getCtxMock = vi.mocked(getContextToken);\nconst ensureMock = vi.mocked(ensureWeixinContextTokenForOutbound);\nconst sendMock = vi.mocked(sendMessageWeixin);\n\nconst SESSION = 'main:weixin:default:dm:ilink_user_abc';\n\nfunction mkCap() {\n return createWeixinWorkflowProgressCapability({\n getConfig: () => ({}) as never,\n });\n}\n\ndescribe('weixin workflow progress capability', () => {\n it('declares final-only defaults (no editMessage on WeChat)', () => {\n const cap = mkCap();\n expect(cap.channelId).toBe('weixin');\n expect(cap.supportsEdit).toBe(false);\n expect(cap.defaultMode).toBe('final-only');\n expect(cap.defaultThrottleMs).toBeGreaterThanOrEqual(30_000);\n });\n\n it('sends the final message when a cached context token exists', async () => {\n getCtxMock.mockReturnValue('tok-cached');\n sendMock.mockClear();\n ensureMock.mockClear();\n const cap = mkCap();\n const r = await cap.postProgress({ sessionKey: SESSION, text: 'done', isFinal: true });\n expect(ensureMock).not.toHaveBeenCalled();\n expect(sendMock).toHaveBeenCalledWith(\n expect.objectContaining({\n to: 'ilink_user_abc',\n text: 'done',\n opts: expect.objectContaining({ contextToken: 'tok-cached' }),\n }),\n );\n expect(r.messageId).toBe('weixin-1');\n });\n\n it('hydrates a context token on demand when cache is empty', async () => {\n getCtxMock.mockReturnValue(undefined);\n ensureMock.mockResolvedValue('tok-hydrated');\n sendMock.mockClear();\n const cap = mkCap();\n await cap.postProgress({ sessionKey: SESSION, text: 'done', isFinal: true });\n expect(ensureMock).toHaveBeenCalled();\n const sendArg = sendMock.mock.calls[0][0];\n expect(sendArg.opts.contextToken).toBe('tok-hydrated');\n });\n\n it('returns an empty messageId without sending when no context token can be found', async () => {\n getCtxMock.mockReturnValue(undefined);\n ensureMock.mockResolvedValue(undefined);\n sendMock.mockClear();\n const cap = mkCap();\n const r = await cap.postProgress({ sessionKey: SESSION, text: 'done', isFinal: true });\n expect(sendMock).not.toHaveBeenCalled();\n expect(r.messageId).toBe('');\n });\n\n it('throws on unroutable sessionKey', async () => {\n const cap = mkCap();\n await expect(\n cap.postProgress({\n sessionKey: 'main:telegram:default:dm:123',\n text: 'x',\n isFinal: true,\n }),\n ).rejects.toThrow(/cannot route/);\n });\n\n it('clamps oversized text to WeChat limit', async () => {\n getCtxMock.mockReturnValue('tok');\n sendMock.mockClear();\n const cap = mkCap();\n await cap.postProgress({ sessionKey: SESSION, text: 'x'.repeat(10_000), isFinal: true });\n const text = sendMock.mock.calls[0][0].text;\n expect(text.length).toBeLessThanOrEqual(4_000);\n expect(text.endsWith('…')).toBe(true);\n });\n\n describe('append-mode message decoration', () => {\n it('prefixes mid-run append messages with \"工作流进展\" header', async () => {\n getCtxMock.mockReturnValue('tok');\n sendMock.mockClear();\n const cap = mkCap();\n await cap.postProgress({\n sessionKey: SESSION,\n text: 'Inventory done · Review running',\n isFinal: false,\n mode: 'append',\n });\n const text = sendMock.mock.calls[0][0].text;\n expect(text.startsWith('▾ 工作流进展\\n')).toBe(true);\n expect(text).toContain('Inventory done · Review running');\n });\n\n it('prefixes final append message with \"工作流完成\" header', async () => {\n getCtxMock.mockReturnValue('tok');\n sendMock.mockClear();\n const cap = mkCap();\n await cap.postProgress({\n sessionKey: SESSION,\n text: 'Top findings (3): …',\n isFinal: true,\n mode: 'append',\n });\n const text = sendMock.mock.calls[0][0].text;\n expect(text.startsWith('✓ 工作流完成\\n')).toBe(true);\n expect(text).toContain('Top findings (3): …');\n });\n\n it('does NOT add a header in final-only mode (existing behaviour preserved)', async () => {\n getCtxMock.mockReturnValue('tok');\n sendMock.mockClear();\n const cap = mkCap();\n await cap.postProgress({\n sessionKey: SESSION,\n text: 'Top findings (3): …',\n isFinal: true,\n mode: 'final-only',\n });\n const text = sendMock.mock.calls[0][0].text;\n expect(text).not.toContain('工作流');\n expect(text).toBe('Top findings (3): …');\n });\n\n it('does NOT add a header when mode is omitted (back-compat)', async () => {\n getCtxMock.mockReturnValue('tok');\n sendMock.mockClear();\n const cap = mkCap();\n await cap.postProgress({ sessionKey: SESSION, text: 'plain', isFinal: true });\n const text = sendMock.mock.calls[0][0].text;\n expect(text).toBe('plain');\n });\n });\n});\n"],"mappings":";;;;;;AAIA,GAAG,KAAK,8BAA8B,EACpC,6BAA6B;CAC3B,WAAW;CACX,SAAS;CACT,YAAY;CACZ,OAAO;CACP,SAAS;CACT,UAAU,KAAA;CACX,GACF,EAAE;AAEH,GAAG,KAAK,kCAAkC;CACxC,iBAAiB,GAAG,IAAI;CACxB,sBAAsB,GAAG,IAAI;CAC9B,EAAE;AAEH,GAAG,KAAK,6CAA6C,EACnD,qCAAqC,GAAG,IAAI,EAC7C,EAAE;AAEH,GAAG,KAAK,wBAAwB,YAAY;AAE1C,QAAO;EACL,GAAG,MAFgB,GAAG,aAAoD,uBAAuB;EAGjG,mBAAmB,GAAG,IAAI,CAAC,kBAAkB,EAAE,WAAW,YAAY,CAAC;EACxE;EACD;AAMF,MAAM,aAAa,GAAG,OAAO,gBAAgB;AAC7C,MAAM,aAAa,GAAG,OAAO,oCAAoC;AACjE,MAAM,WAAW,GAAG,OAAO,kBAAkB;AAE7C,MAAM,UAAU;AAEhB,SAAS,QAAQ;AACf,QAAO,uCAAuC,EAC5C,kBAAkB,EAAE,GACrB,CAAC;;AAGJ,SAAS,6CAA6C;AACpD,IAAG,iEAAiE;EAClE,MAAM,MAAM,OAAO;AACnB,SAAO,IAAI,UAAU,CAAC,KAAK,SAAS;AACpC,SAAO,IAAI,aAAa,CAAC,KAAK,MAAM;AACpC,SAAO,IAAI,YAAY,CAAC,KAAK,aAAa;AAC1C,SAAO,IAAI,kBAAkB,CAAC,uBAAuB,IAAO;GAC5D;AAEF,IAAG,8DAA8D,YAAY;AAC3E,aAAW,gBAAgB,aAAa;AACxC,WAAS,WAAW;AACpB,aAAW,WAAW;EAEtB,MAAM,IAAI,MADE,OACO,CAAC,aAAa;GAAE,YAAY;GAAS,MAAM;GAAQ,SAAS;GAAM,CAAC;AACtF,SAAO,WAAW,CAAC,IAAI,kBAAkB;AACzC,SAAO,SAAS,CAAC,qBACf,OAAO,iBAAiB;GACtB,IAAI;GACJ,MAAM;GACN,MAAM,OAAO,iBAAiB,EAAE,cAAc,cAAc,CAAC;GAC9D,CAAC,CACH;AACD,SAAO,EAAE,UAAU,CAAC,KAAK,WAAW;GACpC;AAEF,IAAG,0DAA0D,YAAY;AACvE,aAAW,gBAAgB,KAAA,EAAU;AACrC,aAAW,kBAAkB,eAAe;AAC5C,WAAS,WAAW;AAEpB,QADY,OACH,CAAC,aAAa;GAAE,YAAY;GAAS,MAAM;GAAQ,SAAS;GAAM,CAAC;AAC5E,SAAO,WAAW,CAAC,kBAAkB;EACrC,MAAM,UAAU,SAAS,KAAK,MAAM,GAAG;AACvC,SAAO,QAAQ,KAAK,aAAa,CAAC,KAAK,eAAe;GACtD;AAEF,IAAG,iFAAiF,YAAY;AAC9F,aAAW,gBAAgB,KAAA,EAAU;AACrC,aAAW,kBAAkB,KAAA,EAAU;AACvC,WAAS,WAAW;EAEpB,MAAM,IAAI,MADE,OACO,CAAC,aAAa;GAAE,YAAY;GAAS,MAAM;GAAQ,SAAS;GAAM,CAAC;AACtF,SAAO,SAAS,CAAC,IAAI,kBAAkB;AACvC,SAAO,EAAE,UAAU,CAAC,KAAK,GAAG;GAC5B;AAEF,IAAG,mCAAmC,YAAY;AAEhD,QAAM,OADM,OAEP,CAAC,aAAa;GACf,YAAY;GACZ,MAAM;GACN,SAAS;GACV,CAAC,CACH,CAAC,QAAQ,QAAQ,eAAe;GACjC;AAEF,IAAG,yCAAyC,YAAY;AACtD,aAAW,gBAAgB,MAAM;AACjC,WAAS,WAAW;AAEpB,QADY,OACH,CAAC,aAAa;GAAE,YAAY;GAAS,MAAM,IAAI,OAAO,IAAO;GAAE,SAAS;GAAM,CAAC;EACxF,MAAM,OAAO,SAAS,KAAK,MAAM,GAAG,GAAG;AACvC,SAAO,KAAK,OAAO,CAAC,oBAAoB,IAAM;AAC9C,SAAO,KAAK,SAAS,IAAI,CAAC,CAAC,KAAK,KAAK;GACrC;AAEF,UAAS,wCAAwC;AAC/C,KAAG,0DAAwD,YAAY;AACrE,cAAW,gBAAgB,MAAM;AACjC,YAAS,WAAW;AAEpB,SADY,OACH,CAAC,aAAa;IACrB,YAAY;IACZ,MAAM;IACN,SAAS;IACT,MAAM;IACP,CAAC;GACF,MAAM,OAAO,SAAS,KAAK,MAAM,GAAG,GAAG;AACvC,UAAO,KAAK,WAAW,YAAY,CAAC,CAAC,KAAK,KAAK;AAC/C,UAAO,KAAK,CAAC,UAAU,kCAAkC;IACzD;AAEF,KAAG,uDAAqD,YAAY;AAClE,cAAW,gBAAgB,MAAM;AACjC,YAAS,WAAW;AAEpB,SADY,OACH,CAAC,aAAa;IACrB,YAAY;IACZ,MAAM;IACN,SAAS;IACT,MAAM;IACP,CAAC;GACF,MAAM,OAAO,SAAS,KAAK,MAAM,GAAG,GAAG;AACvC,UAAO,KAAK,WAAW,YAAY,CAAC,CAAC,KAAK,KAAK;AAC/C,UAAO,KAAK,CAAC,UAAU,sBAAsB;IAC7C;AAEF,KAAG,2EAA2E,YAAY;AACxF,cAAW,gBAAgB,MAAM;AACjC,YAAS,WAAW;AAEpB,SADY,OACH,CAAC,aAAa;IACrB,YAAY;IACZ,MAAM;IACN,SAAS;IACT,MAAM;IACP,CAAC;GACF,MAAM,OAAO,SAAS,KAAK,MAAM,GAAG,GAAG;AACvC,UAAO,KAAK,CAAC,IAAI,UAAU,MAAM;AACjC,UAAO,KAAK,CAAC,KAAK,sBAAsB;IACxC;AAEF,KAAG,4DAA4D,YAAY;AACzE,cAAW,gBAAgB,MAAM;AACjC,YAAS,WAAW;AAEpB,SADY,OACH,CAAC,aAAa;IAAE,YAAY;IAAS,MAAM;IAAS,SAAS;IAAM,CAAC;GAC7E,MAAM,OAAO,SAAS,KAAK,MAAM,GAAG,GAAG;AACvC,UAAO,KAAK,CAAC,KAAK,QAAQ;IAC1B;GACF;EACF"}
1
+ {"version":3,"file":"workflow-progress.test.js","names":[],"sources":["../../../../../extensions/weixin/src/__tests__/workflow-progress.test.ts"],"sourcesContent":["import { describe, expect, it, vi } from 'vitest';\n\nimport { createWeixinWorkflowProgressCapability } from '../workflow-progress.js';\n\nvi.mock('../auth/accounts.js', () => ({\n resolveWeixinAccount: () => ({\n accountId: 'default',\n enabled: true,\n configured: true,\n token: 'tok-x',\n baseUrl: 'https://example/weixin',\n routeTag: undefined,\n }),\n}));\n\nvi.mock('../messaging/inbound.js', () => ({\n getContextToken: vi.fn(),\n restoreContextTokens: vi.fn(),\n}));\n\nvi.mock('../messaging/context-token-init.js', () => ({\n ensureWeixinContextTokenForOutbound: vi.fn(),\n}));\n\nvi.mock('../messaging/send.js', async () => {\n const actual = await vi.importActual<typeof import('../messaging/send.js')>('../messaging/send.js');\n return {\n ...actual,\n sendMessageWeixin: vi.fn().mockResolvedValue({ messageId: 'weixin-1' }),\n };\n});\n\nimport { getContextToken } from '../messaging/inbound.js';\nimport { ensureWeixinContextTokenForOutbound } from '../messaging/context-token-init.js';\nimport { sendMessageWeixin } from '../messaging/send.js';\n\nconst getCtxMock = vi.mocked(getContextToken);\nconst ensureMock = vi.mocked(ensureWeixinContextTokenForOutbound);\nconst sendMock = vi.mocked(sendMessageWeixin);\n\nconst SESSION = 'agent:main:weixin:default:direct:ilink_user_abc';\n\nfunction mkCap() {\n return createWeixinWorkflowProgressCapability({\n getConfig: () => ({}) as never,\n });\n}\n\ndescribe('weixin workflow progress capability', () => {\n it('declares final-only defaults (no editMessage on WeChat)', () => {\n const cap = mkCap();\n expect(cap.channelId).toBe('weixin');\n expect(cap.supportsEdit).toBe(false);\n expect(cap.defaultMode).toBe('final-only');\n expect(cap.defaultThrottleMs).toBeGreaterThanOrEqual(30_000);\n });\n\n it('sends the final message when a cached context token exists', async () => {\n getCtxMock.mockReturnValue('tok-cached');\n sendMock.mockClear();\n ensureMock.mockClear();\n const cap = mkCap();\n const r = await cap.postProgress({ sessionKey: SESSION, text: 'done', isFinal: true });\n expect(ensureMock).not.toHaveBeenCalled();\n expect(sendMock).toHaveBeenCalledWith(\n expect.objectContaining({\n to: 'ilink_user_abc',\n text: 'done',\n opts: expect.objectContaining({ contextToken: 'tok-cached' }),\n }),\n );\n expect(r.messageId).toBe('weixin-1');\n });\n\n it('hydrates a context token on demand when cache is empty', async () => {\n getCtxMock.mockReturnValue(undefined);\n ensureMock.mockResolvedValue('tok-hydrated');\n sendMock.mockClear();\n const cap = mkCap();\n await cap.postProgress({ sessionKey: SESSION, text: 'done', isFinal: true });\n expect(ensureMock).toHaveBeenCalled();\n const sendArg = sendMock.mock.calls[0][0];\n expect(sendArg.opts.contextToken).toBe('tok-hydrated');\n });\n\n it('returns an empty messageId without sending when no context token can be found', async () => {\n getCtxMock.mockReturnValue(undefined);\n ensureMock.mockResolvedValue(undefined);\n sendMock.mockClear();\n const cap = mkCap();\n const r = await cap.postProgress({ sessionKey: SESSION, text: 'done', isFinal: true });\n expect(sendMock).not.toHaveBeenCalled();\n expect(r.messageId).toBe('');\n });\n\n it('throws on unroutable sessionKey', async () => {\n const cap = mkCap();\n await expect(\n cap.postProgress({\n sessionKey: 'agent:main:telegram:default:direct:123',\n text: 'x',\n isFinal: true,\n }),\n ).rejects.toThrow(/cannot route/);\n });\n\n it('clamps oversized text to WeChat limit', async () => {\n getCtxMock.mockReturnValue('tok');\n sendMock.mockClear();\n const cap = mkCap();\n await cap.postProgress({ sessionKey: SESSION, text: 'x'.repeat(10_000), isFinal: true });\n const text = sendMock.mock.calls[0][0].text;\n expect(text.length).toBeLessThanOrEqual(4_000);\n expect(text.endsWith('…')).toBe(true);\n });\n\n describe('append-mode message decoration', () => {\n it('prefixes mid-run append messages with \"工作流进展\" header', async () => {\n getCtxMock.mockReturnValue('tok');\n sendMock.mockClear();\n const cap = mkCap();\n await cap.postProgress({\n sessionKey: SESSION,\n text: 'Inventory done · Review running',\n isFinal: false,\n mode: 'append',\n });\n const text = sendMock.mock.calls[0][0].text;\n expect(text.startsWith('▾ 工作流进展\\n')).toBe(true);\n expect(text).toContain('Inventory done · Review running');\n });\n\n it('prefixes final append message with \"工作流完成\" header', async () => {\n getCtxMock.mockReturnValue('tok');\n sendMock.mockClear();\n const cap = mkCap();\n await cap.postProgress({\n sessionKey: SESSION,\n text: 'Top findings (3): …',\n isFinal: true,\n mode: 'append',\n });\n const text = sendMock.mock.calls[0][0].text;\n expect(text.startsWith('✓ 工作流完成\\n')).toBe(true);\n expect(text).toContain('Top findings (3): …');\n });\n\n it('does NOT add a header in final-only mode (existing behaviour preserved)', async () => {\n getCtxMock.mockReturnValue('tok');\n sendMock.mockClear();\n const cap = mkCap();\n await cap.postProgress({\n sessionKey: SESSION,\n text: 'Top findings (3): …',\n isFinal: true,\n mode: 'final-only',\n });\n const text = sendMock.mock.calls[0][0].text;\n expect(text).not.toContain('工作流');\n expect(text).toBe('Top findings (3): …');\n });\n\n it('does NOT add a header when mode is omitted (back-compat)', async () => {\n getCtxMock.mockReturnValue('tok');\n sendMock.mockClear();\n const cap = mkCap();\n await cap.postProgress({ sessionKey: SESSION, text: 'plain', isFinal: true });\n const text = sendMock.mock.calls[0][0].text;\n expect(text).toBe('plain');\n });\n });\n});\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { logger } from "../util/logger.js";
2
2
  import { redactBody, redactUrl } from "../util/redact.js";
3
- import path from "node:path";
4
- import fsSync from "node:fs";
5
3
  import crypto from "node:crypto";
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { Agent, fetch as fetch$1 } from "undici";
8
8
  //#region extensions/weixin/src/api/api.ts
@@ -10,7 +10,7 @@ function readPackageJson() {
10
10
  try {
11
11
  const dir = path.dirname(fileURLToPath(import.meta.url));
12
12
  const pkgPath = path.resolve(dir, "..", "..", "package.json");
13
- return JSON.parse(fsSync.readFileSync(pkgPath, "utf-8"));
13
+ return JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
14
14
  } catch {
15
15
  return {};
16
16
  }
@@ -1 +1 @@
1
- {"version":3,"file":"api.js","names":["fs","undiciFetch"],"sources":["../../../../../extensions/weixin/src/api/api.ts"],"sourcesContent":["import crypto from \"node:crypto\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { Agent, type Dispatcher, fetch as undiciFetch } from \"undici\";\n\nimport { logger } from \"../util/logger.js\";\nimport { redactBody, redactUrl } from \"../util/redact.js\";\n\nimport type {\n BaseInfo,\n GetUploadUrlReq,\n GetUploadUrlResp,\n GetUpdatesReq,\n GetUpdatesResp,\n SendMessageReq,\n SendMessageResp,\n SendTypingReq,\n GetConfigResp,\n} from \"./types.js\";\n\nexport type WeixinApiOptions = {\n baseUrl: string;\n token?: string;\n timeoutMs?: number;\n /** Long-poll timeout for getUpdates (server may hold the request up to this). */\n longPollTimeoutMs?: number;\n /** Optional ilink SKRouteTag (from config / account). */\n routeTag?: string;\n};\n\n// ---------------------------------------------------------------------------\n// BaseInfo — attached to every outgoing CGI request\n// ---------------------------------------------------------------------------\n\ninterface PackageJson {\n name?: string;\n version?: string;\n ilink_appid?: string;\n}\n\nfunction readPackageJson(): PackageJson {\n try {\n const dir = path.dirname(fileURLToPath(import.meta.url));\n const pkgPath = path.resolve(dir, \"..\", \"..\", \"package.json\");\n return JSON.parse(fs.readFileSync(pkgPath, \"utf-8\")) as PackageJson;\n } catch {\n return {};\n }\n}\n\nconst pkg = readPackageJson();\n\nconst CHANNEL_VERSION = pkg.version ?? \"unknown\";\n\n/** iLink-App-Id: from package.json `ilink_appid`. */\nconst ILINK_APP_ID: string = pkg.ilink_appid ?? \"\";\n\n/**\n * iLink-App-ClientVersion: uint32 encoded as 0x00MMNNPP\n * High 8 bits fixed to 0; remaining bits: major<<16 | minor<<8 | patch.\n */\nfunction buildClientVersion(version: string): number {\n const parts = version.split(\".\").map((p) => parseInt(p, 10));\n const major = parts[0] ?? 0;\n const minor = parts[1] ?? 0;\n const patch = parts[2] ?? 0;\n return ((major & 0xff) << 16) | ((minor & 0xff) << 8) | (patch & 0xff);\n}\n\nconst ILINK_APP_CLIENT_VERSION: number = buildClientVersion(pkg.version ?? \"0.0.0\");\n\n/** Build the `base_info` payload included in every API request. */\nexport function buildBaseInfo(): BaseInfo {\n return { channel_version: CHANNEL_VERSION };\n}\n\n/** Default timeout for long-poll getUpdates requests. */\nconst DEFAULT_LONG_POLL_TIMEOUT_MS = 35_000;\n/** Extra time beyond client long-poll timeout for undici `headersTimeout` (server may idle before first byte). */\nconst LONG_POLL_HEADERS_SLACK_MS = 15_000;\n/** Default timeout for regular API requests (sendMessage, getUploadUrl). */\nconst DEFAULT_API_TIMEOUT_MS = 15_000;\n/** Default timeout for lightweight API requests (getConfig, sendTyping). */\nconst DEFAULT_CONFIG_TIMEOUT_MS = 10_000;\n\nfunction ensureTrailingSlash(url: string): string {\n return url.endsWith(\"/\") ? url : `${url}/`;\n}\n\nfunction isUndiciHeadersTimeout(err: unknown): boolean {\n return (\n typeof err === \"object\" &&\n err !== null &&\n \"code\" in err &&\n (err as { code?: string }).code === \"UND_ERR_HEADERS_TIMEOUT\"\n );\n}\n\n/** X-WECHAT-UIN header: random uint32 -> decimal string -> base64. */\nfunction randomWechatUin(): string {\n const uint32 = crypto.randomBytes(4).readUInt32BE(0);\n return Buffer.from(String(uint32), \"utf-8\").toString(\"base64\");\n}\n\n/** Build headers shared by GET and POST requests. */\nfunction buildCommonHeaders(routeTag?: string): Record<string, string> {\n const headers: Record<string, string> = {\n \"iLink-App-Id\": ILINK_APP_ID,\n \"iLink-App-ClientVersion\": String(ILINK_APP_CLIENT_VERSION),\n };\n const tag = routeTag?.trim();\n if (tag) {\n headers.SKRouteTag = tag;\n }\n return headers;\n}\n\nfunction buildHeaders(opts: { token?: string; body: string; routeTag?: string }): Record<string, string> {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n AuthorizationType: \"ilink_bot_token\",\n \"Content-Length\": String(Buffer.byteLength(opts.body, \"utf-8\")),\n \"X-WECHAT-UIN\": randomWechatUin(),\n ...buildCommonHeaders(opts.routeTag),\n };\n if (opts.token?.trim()) {\n headers.Authorization = `Bearer ${opts.token.trim()}`;\n }\n logger.debug(\n `requestHeaders: ${JSON.stringify({ ...headers, Authorization: headers.Authorization ? \"Bearer ***\" : undefined })}`,\n );\n return headers;\n}\n\n/**\n * GET fetch wrapper (QR login and other GET endpoints).\n */\nexport async function apiGetFetch(params: {\n baseUrl: string;\n endpoint: string;\n timeoutMs?: number;\n label: string;\n routeTag?: string;\n}): Promise<string> {\n const base = ensureTrailingSlash(params.baseUrl);\n const url = new URL(params.endpoint, base);\n const hdrs = buildCommonHeaders(params.routeTag);\n logger.debug(`GET ${redactUrl(url.toString())}`);\n\n const timeoutMs = params.timeoutMs;\n const controller =\n timeoutMs != null && timeoutMs > 0 ? new AbortController() : undefined;\n const t =\n controller != null && timeoutMs != null\n ? setTimeout(() => controller.abort(), timeoutMs)\n : undefined;\n try {\n const res = await fetch(url.toString(), {\n method: \"GET\",\n headers: hdrs,\n ...(controller ? { signal: controller.signal } : {}),\n // 复用连接,避免 VPN/网络不稳定时卡住\n keepalive: true,\n });\n if (t !== undefined) clearTimeout(t);\n const rawText = await res.text();\n logger.debug(`${params.label} status=${res.status} raw=${redactBody(rawText)}`);\n if (!res.ok) {\n throw new Error(`${params.label} ${res.status}: ${rawText}`);\n }\n return rawText;\n } catch (err) {\n if (t !== undefined) clearTimeout(t);\n throw err;\n }\n}\n\n/**\n * Common POST fetch wrapper: POST JSON to a Weixin API endpoint with timeout + abort.\n */\nasync function apiPostFetch(params: {\n baseUrl: string;\n endpoint: string;\n body: string;\n token?: string;\n timeoutMs: number;\n label: string;\n routeTag?: string;\n dispatcher?: Dispatcher;\n keepalive?: boolean;\n abortSignal?: AbortSignal;\n}): Promise<string> {\n const base = ensureTrailingSlash(params.baseUrl);\n const url = new URL(params.endpoint, base);\n const hdrs = buildHeaders({ token: params.token, body: params.body, routeTag: params.routeTag });\n logger.debug(`POST ${redactUrl(url.toString())} body=${redactBody(params.body)}`);\n\n const controller = new AbortController();\n const t = setTimeout(() => controller.abort(), params.timeoutMs);\n const signal =\n params.abortSignal != null\n ? AbortSignal.any([controller.signal, params.abortSignal])\n : controller.signal;\n const keepalive = params.keepalive ?? true;\n try {\n const init: RequestInit = {\n method: \"POST\",\n headers: hdrs,\n body: params.body,\n signal,\n keepalive,\n };\n // `dispatcher` is an undici extension. Node's `fetch` types come from `undici-types`,\n // which may not match the installed undici version's `Dispatcher` type.\n if (params.dispatcher) (init as any).dispatcher = params.dispatcher;\n\n // Use undici's fetch implementation so `dispatcher` is interpreted consistently.\n const res = await undiciFetch(url.toString(), init as any);\n clearTimeout(t);\n const rawText = await res.text();\n logger.debug(`${params.label} status=${res.status} raw=${redactBody(rawText)}`);\n if (!res.ok) {\n throw new Error(`${params.label} ${res.status}: ${rawText}`);\n }\n return rawText;\n } catch (err) {\n clearTimeout(t);\n throw err;\n }\n}\n\n/**\n * Long-poll getUpdates. Server should hold the request until new messages or timeout.\n *\n * On client-side timeout (no server response within timeoutMs), returns an empty response\n * with ret=0 so the caller can simply retry. This is normal for long-poll.\n */\nexport async function getUpdates(\n params: GetUpdatesReq & {\n baseUrl: string;\n token?: string;\n timeoutMs?: number;\n routeTag?: string;\n abortSignal?: AbortSignal;\n },\n): Promise<GetUpdatesResp> {\n const timeout = params.timeoutMs ?? DEFAULT_LONG_POLL_TIMEOUT_MS;\n const headersTimeout = timeout + LONG_POLL_HEADERS_SLACK_MS;\n const agent = new Agent({\n connectTimeout: 60_000,\n headersTimeout,\n bodyTimeout: headersTimeout,\n });\n try {\n const rawText = await apiPostFetch({\n baseUrl: params.baseUrl,\n endpoint: \"ilink/bot/getupdates\",\n body: JSON.stringify({\n get_updates_buf: params.get_updates_buf ?? \"\",\n base_info: buildBaseInfo(),\n }),\n token: params.token,\n timeoutMs: timeout,\n label: \"getUpdates\",\n routeTag: params.routeTag,\n dispatcher: agent,\n keepalive: true,\n abortSignal: params.abortSignal,\n });\n const resp: GetUpdatesResp = JSON.parse(rawText);\n return resp;\n } catch (err) {\n if (params.abortSignal?.aborted && err instanceof Error && err.name === \"AbortError\") {\n throw err;\n }\n if (err instanceof Error && err.name === \"AbortError\") {\n logger.debug(`getUpdates: client-side timeout after ${timeout}ms, returning empty response`);\n return { ret: 0, msgs: [], get_updates_buf: params.get_updates_buf };\n }\n if (isUndiciHeadersTimeout(err)) {\n logger.debug(\n `getUpdates: undici headers timeout (UND_ERR_HEADERS_TIMEOUT), returning empty response`,\n );\n return { ret: 0, msgs: [], get_updates_buf: params.get_updates_buf ?? \"\" };\n }\n throw err;\n } finally {\n try {\n await agent.close();\n } catch {\n // ignore\n }\n }\n}\n\n/** Get a pre-signed CDN upload URL for a file. */\nexport async function getUploadUrl(\n params: GetUploadUrlReq & WeixinApiOptions,\n): Promise<GetUploadUrlResp> {\n const rawText = await apiPostFetch({\n baseUrl: params.baseUrl,\n endpoint: \"ilink/bot/getuploadurl\",\n body: JSON.stringify({\n filekey: params.filekey,\n media_type: params.media_type,\n to_user_id: params.to_user_id,\n rawsize: params.rawsize,\n rawfilemd5: params.rawfilemd5,\n filesize: params.filesize,\n thumb_rawsize: params.thumb_rawsize,\n thumb_rawfilemd5: params.thumb_rawfilemd5,\n thumb_filesize: params.thumb_filesize,\n no_need_thumb: params.no_need_thumb,\n aeskey: params.aeskey,\n base_info: buildBaseInfo(),\n }),\n token: params.token,\n timeoutMs: params.timeoutMs ?? DEFAULT_API_TIMEOUT_MS,\n label: \"getUploadUrl\",\n routeTag: params.routeTag,\n });\n const resp: GetUploadUrlResp = JSON.parse(rawText);\n return resp;\n}\n\nfunction assertSendMessageOk(resp: SendMessageResp): void {\n if (typeof resp.ret === \"number\" && resp.ret !== 0) {\n throw new Error(`sendMessage ret=${resp.ret} errmsg=${resp.errmsg ?? \"\"}`);\n }\n if (typeof resp.errcode === \"number\" && resp.errcode !== 0) {\n throw new Error(`sendMessage errcode=${resp.errcode} errmsg=${resp.errmsg ?? \"\"}`);\n }\n}\n\n/** Send a single message downstream. Returns parsed body (incl. refreshed `context_token` when present). */\nexport async function sendMessage(\n params: WeixinApiOptions & { body: SendMessageReq },\n): Promise<SendMessageResp> {\n const rawText = await apiPostFetch({\n baseUrl: params.baseUrl,\n endpoint: \"ilink/bot/sendmessage\",\n body: JSON.stringify({ ...params.body, base_info: buildBaseInfo() }),\n token: params.token,\n timeoutMs: params.timeoutMs ?? DEFAULT_API_TIMEOUT_MS,\n label: \"sendMessage\",\n routeTag: params.routeTag,\n });\n const t = rawText.trim();\n if (!t.startsWith(\"{\")) {\n return {};\n }\n let resp: SendMessageResp;\n try {\n resp = JSON.parse(t) as SendMessageResp;\n } catch (e) {\n throw new Error(`sendMessage invalid JSON: ${String(e)}`);\n }\n assertSendMessageOk(resp);\n return resp;\n}\n\n/** Fetch bot config (includes typing_ticket) for a given user. */\nexport async function getConfig(\n params: WeixinApiOptions & { ilinkUserId: string; contextToken?: string },\n): Promise<GetConfigResp> {\n const rawText = await apiPostFetch({\n baseUrl: params.baseUrl,\n endpoint: \"ilink/bot/getconfig\",\n body: JSON.stringify({\n ilink_user_id: params.ilinkUserId,\n context_token: params.contextToken,\n base_info: buildBaseInfo(),\n }),\n token: params.token,\n timeoutMs: params.timeoutMs ?? DEFAULT_CONFIG_TIMEOUT_MS,\n label: \"getConfig\",\n routeTag: params.routeTag,\n });\n const resp: GetConfigResp = JSON.parse(rawText);\n return resp;\n}\n\n/** Send a typing indicator to a user. */\nexport async function sendTyping(\n params: WeixinApiOptions & { body: SendTypingReq },\n): Promise<void> {\n await apiPostFetch({\n baseUrl: params.baseUrl,\n endpoint: \"ilink/bot/sendtyping\",\n body: JSON.stringify({ ...params.body, base_info: buildBaseInfo() }),\n token: params.token,\n timeoutMs: params.timeoutMs ?? DEFAULT_CONFIG_TIMEOUT_MS,\n label: \"sendTyping\",\n routeTag: params.routeTag,\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"api.js","names":["undiciFetch"],"sources":["../../../../../extensions/weixin/src/api/api.ts"],"sourcesContent":["import crypto from \"node:crypto\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { Agent, type Dispatcher, fetch as undiciFetch } from \"undici\";\n\nimport { logger } from \"../util/logger.js\";\nimport { redactBody, redactUrl } from \"../util/redact.js\";\n\nimport type {\n BaseInfo,\n GetUploadUrlReq,\n GetUploadUrlResp,\n GetUpdatesReq,\n GetUpdatesResp,\n SendMessageReq,\n SendMessageResp,\n SendTypingReq,\n GetConfigResp,\n} from \"./types.js\";\n\nexport type WeixinApiOptions = {\n baseUrl: string;\n token?: string;\n timeoutMs?: number;\n /** Long-poll timeout for getUpdates (server may hold the request up to this). */\n longPollTimeoutMs?: number;\n /** Optional ilink SKRouteTag (from config / account). */\n routeTag?: string;\n};\n\n// ---------------------------------------------------------------------------\n// BaseInfo — attached to every outgoing CGI request\n// ---------------------------------------------------------------------------\n\ninterface PackageJson {\n name?: string;\n version?: string;\n ilink_appid?: string;\n}\n\nfunction readPackageJson(): PackageJson {\n try {\n const dir = path.dirname(fileURLToPath(import.meta.url));\n const pkgPath = path.resolve(dir, \"..\", \"..\", \"package.json\");\n return JSON.parse(fs.readFileSync(pkgPath, \"utf-8\")) as PackageJson;\n } catch {\n return {};\n }\n}\n\nconst pkg = readPackageJson();\n\nconst CHANNEL_VERSION = pkg.version ?? \"unknown\";\n\n/** iLink-App-Id: from package.json `ilink_appid`. */\nconst ILINK_APP_ID: string = pkg.ilink_appid ?? \"\";\n\n/**\n * iLink-App-ClientVersion: uint32 encoded as 0x00MMNNPP\n * High 8 bits fixed to 0; remaining bits: major<<16 | minor<<8 | patch.\n */\nfunction buildClientVersion(version: string): number {\n const parts = version.split(\".\").map((p) => parseInt(p, 10));\n const major = parts[0] ?? 0;\n const minor = parts[1] ?? 0;\n const patch = parts[2] ?? 0;\n return ((major & 0xff) << 16) | ((minor & 0xff) << 8) | (patch & 0xff);\n}\n\nconst ILINK_APP_CLIENT_VERSION: number = buildClientVersion(pkg.version ?? \"0.0.0\");\n\n/** Build the `base_info` payload included in every API request. */\nexport function buildBaseInfo(): BaseInfo {\n return { channel_version: CHANNEL_VERSION };\n}\n\n/** Default timeout for long-poll getUpdates requests. */\nconst DEFAULT_LONG_POLL_TIMEOUT_MS = 35_000;\n/** Extra time beyond client long-poll timeout for undici `headersTimeout` (server may idle before first byte). */\nconst LONG_POLL_HEADERS_SLACK_MS = 15_000;\n/** Default timeout for regular API requests (sendMessage, getUploadUrl). */\nconst DEFAULT_API_TIMEOUT_MS = 15_000;\n/** Default timeout for lightweight API requests (getConfig, sendTyping). */\nconst DEFAULT_CONFIG_TIMEOUT_MS = 10_000;\n\nfunction ensureTrailingSlash(url: string): string {\n return url.endsWith(\"/\") ? url : `${url}/`;\n}\n\nfunction isUndiciHeadersTimeout(err: unknown): boolean {\n return (\n typeof err === \"object\" &&\n err !== null &&\n \"code\" in err &&\n (err as { code?: string }).code === \"UND_ERR_HEADERS_TIMEOUT\"\n );\n}\n\n/** X-WECHAT-UIN header: random uint32 -> decimal string -> base64. */\nfunction randomWechatUin(): string {\n const uint32 = crypto.randomBytes(4).readUInt32BE(0);\n return Buffer.from(String(uint32), \"utf-8\").toString(\"base64\");\n}\n\n/** Build headers shared by GET and POST requests. */\nfunction buildCommonHeaders(routeTag?: string): Record<string, string> {\n const headers: Record<string, string> = {\n \"iLink-App-Id\": ILINK_APP_ID,\n \"iLink-App-ClientVersion\": String(ILINK_APP_CLIENT_VERSION),\n };\n const tag = routeTag?.trim();\n if (tag) {\n headers.SKRouteTag = tag;\n }\n return headers;\n}\n\nfunction buildHeaders(opts: { token?: string; body: string; routeTag?: string }): Record<string, string> {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n AuthorizationType: \"ilink_bot_token\",\n \"Content-Length\": String(Buffer.byteLength(opts.body, \"utf-8\")),\n \"X-WECHAT-UIN\": randomWechatUin(),\n ...buildCommonHeaders(opts.routeTag),\n };\n if (opts.token?.trim()) {\n headers.Authorization = `Bearer ${opts.token.trim()}`;\n }\n logger.debug(\n `requestHeaders: ${JSON.stringify({ ...headers, Authorization: headers.Authorization ? \"Bearer ***\" : undefined })}`,\n );\n return headers;\n}\n\n/**\n * GET fetch wrapper (QR login and other GET endpoints).\n */\nexport async function apiGetFetch(params: {\n baseUrl: string;\n endpoint: string;\n timeoutMs?: number;\n label: string;\n routeTag?: string;\n}): Promise<string> {\n const base = ensureTrailingSlash(params.baseUrl);\n const url = new URL(params.endpoint, base);\n const hdrs = buildCommonHeaders(params.routeTag);\n logger.debug(`GET ${redactUrl(url.toString())}`);\n\n const timeoutMs = params.timeoutMs;\n const controller =\n timeoutMs != null && timeoutMs > 0 ? new AbortController() : undefined;\n const t =\n controller != null && timeoutMs != null\n ? setTimeout(() => controller.abort(), timeoutMs)\n : undefined;\n try {\n const res = await fetch(url.toString(), {\n method: \"GET\",\n headers: hdrs,\n ...(controller ? { signal: controller.signal } : {}),\n // 复用连接,避免 VPN/网络不稳定时卡住\n keepalive: true,\n });\n if (t !== undefined) clearTimeout(t);\n const rawText = await res.text();\n logger.debug(`${params.label} status=${res.status} raw=${redactBody(rawText)}`);\n if (!res.ok) {\n throw new Error(`${params.label} ${res.status}: ${rawText}`);\n }\n return rawText;\n } catch (err) {\n if (t !== undefined) clearTimeout(t);\n throw err;\n }\n}\n\n/**\n * Common POST fetch wrapper: POST JSON to a Weixin API endpoint with timeout + abort.\n */\nasync function apiPostFetch(params: {\n baseUrl: string;\n endpoint: string;\n body: string;\n token?: string;\n timeoutMs: number;\n label: string;\n routeTag?: string;\n dispatcher?: Dispatcher;\n keepalive?: boolean;\n abortSignal?: AbortSignal;\n}): Promise<string> {\n const base = ensureTrailingSlash(params.baseUrl);\n const url = new URL(params.endpoint, base);\n const hdrs = buildHeaders({ token: params.token, body: params.body, routeTag: params.routeTag });\n logger.debug(`POST ${redactUrl(url.toString())} body=${redactBody(params.body)}`);\n\n const controller = new AbortController();\n const t = setTimeout(() => controller.abort(), params.timeoutMs);\n const signal =\n params.abortSignal != null\n ? AbortSignal.any([controller.signal, params.abortSignal])\n : controller.signal;\n const keepalive = params.keepalive ?? true;\n try {\n const init: RequestInit = {\n method: \"POST\",\n headers: hdrs,\n body: params.body,\n signal,\n keepalive,\n };\n // `dispatcher` is an undici extension. Node's `fetch` types come from `undici-types`,\n // which may not match the installed undici version's `Dispatcher` type.\n if (params.dispatcher) (init as any).dispatcher = params.dispatcher;\n\n // Use undici's fetch implementation so `dispatcher` is interpreted consistently.\n const res = await undiciFetch(url.toString(), init as any);\n clearTimeout(t);\n const rawText = await res.text();\n logger.debug(`${params.label} status=${res.status} raw=${redactBody(rawText)}`);\n if (!res.ok) {\n throw new Error(`${params.label} ${res.status}: ${rawText}`);\n }\n return rawText;\n } catch (err) {\n clearTimeout(t);\n throw err;\n }\n}\n\n/**\n * Long-poll getUpdates. Server should hold the request until new messages or timeout.\n *\n * On client-side timeout (no server response within timeoutMs), returns an empty response\n * with ret=0 so the caller can simply retry. This is normal for long-poll.\n */\nexport async function getUpdates(\n params: GetUpdatesReq & {\n baseUrl: string;\n token?: string;\n timeoutMs?: number;\n routeTag?: string;\n abortSignal?: AbortSignal;\n },\n): Promise<GetUpdatesResp> {\n const timeout = params.timeoutMs ?? DEFAULT_LONG_POLL_TIMEOUT_MS;\n const headersTimeout = timeout + LONG_POLL_HEADERS_SLACK_MS;\n const agent = new Agent({\n connectTimeout: 60_000,\n headersTimeout,\n bodyTimeout: headersTimeout,\n });\n try {\n const rawText = await apiPostFetch({\n baseUrl: params.baseUrl,\n endpoint: \"ilink/bot/getupdates\",\n body: JSON.stringify({\n get_updates_buf: params.get_updates_buf ?? \"\",\n base_info: buildBaseInfo(),\n }),\n token: params.token,\n timeoutMs: timeout,\n label: \"getUpdates\",\n routeTag: params.routeTag,\n dispatcher: agent,\n keepalive: true,\n abortSignal: params.abortSignal,\n });\n const resp: GetUpdatesResp = JSON.parse(rawText);\n return resp;\n } catch (err) {\n if (params.abortSignal?.aborted && err instanceof Error && err.name === \"AbortError\") {\n throw err;\n }\n if (err instanceof Error && err.name === \"AbortError\") {\n logger.debug(`getUpdates: client-side timeout after ${timeout}ms, returning empty response`);\n return { ret: 0, msgs: [], get_updates_buf: params.get_updates_buf };\n }\n if (isUndiciHeadersTimeout(err)) {\n logger.debug(\n `getUpdates: undici headers timeout (UND_ERR_HEADERS_TIMEOUT), returning empty response`,\n );\n return { ret: 0, msgs: [], get_updates_buf: params.get_updates_buf ?? \"\" };\n }\n throw err;\n } finally {\n try {\n await agent.close();\n } catch {\n // ignore\n }\n }\n}\n\n/** Get a pre-signed CDN upload URL for a file. */\nexport async function getUploadUrl(\n params: GetUploadUrlReq & WeixinApiOptions,\n): Promise<GetUploadUrlResp> {\n const rawText = await apiPostFetch({\n baseUrl: params.baseUrl,\n endpoint: \"ilink/bot/getuploadurl\",\n body: JSON.stringify({\n filekey: params.filekey,\n media_type: params.media_type,\n to_user_id: params.to_user_id,\n rawsize: params.rawsize,\n rawfilemd5: params.rawfilemd5,\n filesize: params.filesize,\n thumb_rawsize: params.thumb_rawsize,\n thumb_rawfilemd5: params.thumb_rawfilemd5,\n thumb_filesize: params.thumb_filesize,\n no_need_thumb: params.no_need_thumb,\n aeskey: params.aeskey,\n base_info: buildBaseInfo(),\n }),\n token: params.token,\n timeoutMs: params.timeoutMs ?? DEFAULT_API_TIMEOUT_MS,\n label: \"getUploadUrl\",\n routeTag: params.routeTag,\n });\n const resp: GetUploadUrlResp = JSON.parse(rawText);\n return resp;\n}\n\nfunction assertSendMessageOk(resp: SendMessageResp): void {\n if (typeof resp.ret === \"number\" && resp.ret !== 0) {\n throw new Error(`sendMessage ret=${resp.ret} errmsg=${resp.errmsg ?? \"\"}`);\n }\n if (typeof resp.errcode === \"number\" && resp.errcode !== 0) {\n throw new Error(`sendMessage errcode=${resp.errcode} errmsg=${resp.errmsg ?? \"\"}`);\n }\n}\n\n/** Send a single message downstream. Returns parsed body (incl. refreshed `context_token` when present). */\nexport async function sendMessage(\n params: WeixinApiOptions & { body: SendMessageReq },\n): Promise<SendMessageResp> {\n const rawText = await apiPostFetch({\n baseUrl: params.baseUrl,\n endpoint: \"ilink/bot/sendmessage\",\n body: JSON.stringify({ ...params.body, base_info: buildBaseInfo() }),\n token: params.token,\n timeoutMs: params.timeoutMs ?? DEFAULT_API_TIMEOUT_MS,\n label: \"sendMessage\",\n routeTag: params.routeTag,\n });\n const t = rawText.trim();\n if (!t.startsWith(\"{\")) {\n return {};\n }\n let resp: SendMessageResp;\n try {\n resp = JSON.parse(t) as SendMessageResp;\n } catch (e) {\n throw new Error(`sendMessage invalid JSON: ${String(e)}`);\n }\n assertSendMessageOk(resp);\n return resp;\n}\n\n/** Fetch bot config (includes typing_ticket) for a given user. */\nexport async function getConfig(\n params: WeixinApiOptions & { ilinkUserId: string; contextToken?: string },\n): Promise<GetConfigResp> {\n const rawText = await apiPostFetch({\n baseUrl: params.baseUrl,\n endpoint: \"ilink/bot/getconfig\",\n body: JSON.stringify({\n ilink_user_id: params.ilinkUserId,\n context_token: params.contextToken,\n base_info: buildBaseInfo(),\n }),\n token: params.token,\n timeoutMs: params.timeoutMs ?? DEFAULT_CONFIG_TIMEOUT_MS,\n label: \"getConfig\",\n routeTag: params.routeTag,\n });\n const resp: GetConfigResp = JSON.parse(rawText);\n return resp;\n}\n\n/** Send a typing indicator to a user. */\nexport async function sendTyping(\n params: WeixinApiOptions & { body: SendTypingReq },\n): Promise<void> {\n await apiPostFetch({\n baseUrl: params.baseUrl,\n endpoint: \"ilink/bot/sendtyping\",\n body: JSON.stringify({ ...params.body, base_info: buildBaseInfo() }),\n token: params.token,\n timeoutMs: params.timeoutMs ?? DEFAULT_CONFIG_TIMEOUT_MS,\n label: \"sendTyping\",\n routeTag: params.routeTag,\n });\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { logger } from "../util/logger.js";
2
2
  import { normalizeWeixinAccountId } from "./weixin-account-id.js";
3
3
  import { resolveWeixinRootDir } from "../storage/state-dir.js";
4
+ import fs from "node:fs";
4
5
  import path from "node:path";
5
- import fsSync from "node:fs";
6
6
  //#region extensions/weixin/src/auth/accounts.ts
7
7
  const DEFAULT_BASE_URL = "https://ilinkai.weixin.qq.com";
8
8
  const CDN_BASE_URL = "https://novac2c.cdn.weixin.qq.com/c2c";
@@ -15,8 +15,8 @@ function resolveAccountIndexPath() {
15
15
  function listIndexedWeixinAccountIds() {
16
16
  const filePath = resolveAccountIndexPath();
17
17
  try {
18
- if (!fsSync.existsSync(filePath)) return [];
19
- const raw = fsSync.readFileSync(filePath, "utf-8");
18
+ if (!fs.existsSync(filePath)) return [];
19
+ const raw = fs.readFileSync(filePath, "utf-8");
20
20
  const parsed = JSON.parse(raw);
21
21
  if (!Array.isArray(parsed)) return [];
22
22
  return parsed.filter((id) => typeof id === "string" && id.trim() !== "");
@@ -26,16 +26,16 @@ function listIndexedWeixinAccountIds() {
26
26
  }
27
27
  function registerWeixinAccountId(accountId) {
28
28
  const dir = resolveWeixinStateDir();
29
- fsSync.mkdirSync(dir, { recursive: true });
29
+ fs.mkdirSync(dir, { recursive: true });
30
30
  const existing = listIndexedWeixinAccountIds();
31
31
  if (existing.includes(accountId)) return;
32
32
  const updated = [...existing, accountId];
33
- fsSync.writeFileSync(resolveAccountIndexPath(), JSON.stringify(updated, null, 2), "utf-8");
33
+ fs.writeFileSync(resolveAccountIndexPath(), JSON.stringify(updated, null, 2), "utf-8");
34
34
  }
35
35
  function unregisterWeixinAccountId(accountId) {
36
36
  const existing = listIndexedWeixinAccountIds();
37
37
  const updated = existing.filter((id) => id !== accountId);
38
- if (updated.length !== existing.length) fsSync.writeFileSync(resolveAccountIndexPath(), JSON.stringify(updated, null, 2), "utf-8");
38
+ if (updated.length !== existing.length) fs.writeFileSync(resolveAccountIndexPath(), JSON.stringify(updated, null, 2), "utf-8");
39
39
  }
40
40
  function clearStaleAccountsForUserId(currentAccountId, userId, onClearContextTokens) {
41
41
  if (!userId) return;
@@ -58,7 +58,7 @@ function resolveAccountPath(accountId) {
58
58
  }
59
59
  function readAccountFile(filePath) {
60
60
  try {
61
- if (fsSync.existsSync(filePath)) return JSON.parse(fsSync.readFileSync(filePath, "utf-8"));
61
+ if (fs.existsSync(filePath)) return JSON.parse(fs.readFileSync(filePath, "utf-8"));
62
62
  } catch {}
63
63
  return null;
64
64
  }
@@ -67,7 +67,7 @@ function loadWeixinAccount(accountId) {
67
67
  }
68
68
  function saveWeixinAccount(accountId, update) {
69
69
  const dir = resolveAccountsDir();
70
- fsSync.mkdirSync(dir, { recursive: true });
70
+ fs.mkdirSync(dir, { recursive: true });
71
71
  const existing = loadWeixinAccount(accountId) ?? {};
72
72
  const token = update.token?.trim() || existing.token;
73
73
  const baseUrl = update.baseUrl?.trim() || existing.baseUrl;
@@ -81,9 +81,9 @@ function saveWeixinAccount(accountId, update) {
81
81
  ...userId ? { userId } : {}
82
82
  };
83
83
  const filePath = resolveAccountPath(accountId);
84
- fsSync.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
84
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
85
85
  try {
86
- fsSync.chmodSync(filePath, 384);
86
+ fs.chmodSync(filePath, 384);
87
87
  } catch {}
88
88
  }
89
89
  function clearWeixinAccount(accountId) {
@@ -94,10 +94,10 @@ function clearWeixinAccount(accountId) {
94
94
  `${accountId}.context-tokens.json`
95
95
  ];
96
96
  for (const file of accountFiles) try {
97
- fsSync.unlinkSync(path.join(dir, file));
97
+ fs.unlinkSync(path.join(dir, file));
98
98
  } catch {}
99
99
  try {
100
- fsSync.unlinkSync(resolveFrameworkAllowFromPath(accountId));
100
+ fs.unlinkSync(resolveFrameworkAllowFromPath(accountId));
101
101
  } catch {}
102
102
  }
103
103
  function weixinSection(cfg) {
@@ -1 +1 @@
1
- {"version":3,"file":"accounts.js","names":["fs"],"sources":["../../../../../extensions/weixin/src/auth/accounts.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\n\nimport { clearContextTokensForAccount } from '../messaging/inbound.js';\nimport { resolveWeixinRootDir } from '../storage/state-dir.js';\nimport { logger } from '../util/logger.js';\n\nimport { normalizeWeixinAccountId } from './weixin-account-id.js';\n\nexport { normalizeWeixinAccountId };\n\nexport const DEFAULT_BASE_URL = 'https://ilinkai.weixin.qq.com';\nexport const CDN_BASE_URL = 'https://novac2c.cdn.weixin.qq.com/c2c';\n\nfunction resolveWeixinStateDir(): string {\n return resolveWeixinRootDir();\n}\n\nfunction resolveAccountIndexPath(): string {\n return path.join(resolveWeixinStateDir(), 'accounts.json');\n}\n\nexport function listIndexedWeixinAccountIds(): string[] {\n const filePath = resolveAccountIndexPath();\n try {\n if (!fs.existsSync(filePath)) return [];\n const raw = fs.readFileSync(filePath, 'utf-8');\n const parsed = JSON.parse(raw) as unknown;\n if (!Array.isArray(parsed)) return [];\n return parsed.filter((id): id is string => typeof id === 'string' && id.trim() !== '');\n } catch {\n return [];\n }\n}\n\nexport function registerWeixinAccountId(accountId: string): void {\n const dir = resolveWeixinStateDir();\n fs.mkdirSync(dir, { recursive: true });\n\n const existing = listIndexedWeixinAccountIds();\n if (existing.includes(accountId)) return;\n\n const updated = [...existing, accountId];\n fs.writeFileSync(resolveAccountIndexPath(), JSON.stringify(updated, null, 2), 'utf-8');\n}\n\nexport function unregisterWeixinAccountId(accountId: string): void {\n const existing = listIndexedWeixinAccountIds();\n const updated = existing.filter((id) => id !== accountId);\n if (updated.length !== existing.length) {\n fs.writeFileSync(resolveAccountIndexPath(), JSON.stringify(updated, null, 2), 'utf-8');\n }\n}\n\nexport function clearStaleAccountsForUserId(\n currentAccountId: string,\n userId: string,\n onClearContextTokens?: (accountId: string) => void,\n): void {\n if (!userId) return;\n const allIds = listIndexedWeixinAccountIds();\n for (const id of allIds) {\n if (id === currentAccountId) continue;\n const data = loadWeixinAccount(id);\n if (data?.userId?.trim() === userId) {\n logger.info(`clearStaleAccountsForUserId: removing stale account=${id} (same userId=${userId})`);\n onClearContextTokens?.(id);\n clearWeixinAccount(id);\n unregisterWeixinAccountId(id);\n }\n }\n}\n\nexport type WeixinAccountData = {\n token?: string;\n savedAt?: string;\n baseUrl?: string;\n userId?: string;\n};\n\nfunction resolveAccountsDir(): string {\n return path.join(resolveWeixinStateDir(), 'accounts');\n}\n\nfunction resolveAccountPath(accountId: string): string {\n return path.join(resolveAccountsDir(), `${accountId}.json`);\n}\n\nfunction readAccountFile(filePath: string): WeixinAccountData | null {\n try {\n if (fs.existsSync(filePath)) {\n return JSON.parse(fs.readFileSync(filePath, 'utf-8')) as WeixinAccountData;\n }\n } catch {\n // ignore\n }\n return null;\n}\n\nexport function loadWeixinAccount(accountId: string): WeixinAccountData | null {\n return readAccountFile(resolveAccountPath(accountId));\n}\n\nexport function saveWeixinAccount(\n accountId: string,\n update: { token?: string; baseUrl?: string; userId?: string },\n): void {\n const dir = resolveAccountsDir();\n fs.mkdirSync(dir, { recursive: true });\n\n const existing = loadWeixinAccount(accountId) ?? {};\n\n const token = update.token?.trim() || existing.token;\n const baseUrl = update.baseUrl?.trim() || existing.baseUrl;\n const userId =\n update.userId !== undefined ? update.userId.trim() || undefined : existing.userId?.trim() || undefined;\n\n const data: WeixinAccountData = {\n ...(token ? { token, savedAt: new Date().toISOString() } : {}),\n ...(baseUrl ? { baseUrl } : {}),\n ...(userId ? { userId } : {}),\n };\n\n const filePath = resolveAccountPath(accountId);\n fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');\n try {\n fs.chmodSync(filePath, 0o600);\n } catch {\n // best-effort\n }\n}\n\nexport function clearWeixinAccount(accountId: string): void {\n const dir = resolveAccountsDir();\n const accountFiles = [\n `${accountId}.json`,\n `${accountId}.sync.json`,\n `${accountId}.context-tokens.json`,\n ];\n for (const file of accountFiles) {\n try {\n fs.unlinkSync(path.join(dir, file));\n } catch {\n // ignore\n }\n }\n try {\n fs.unlinkSync(resolveFrameworkAllowFromPath(accountId));\n } catch {\n // ignore\n }\n}\n\nexport type ResolvedWeixinAccount = {\n accountId: string;\n baseUrl: string;\n cdnBaseUrl: string;\n token?: string;\n enabled: boolean;\n configured: boolean;\n name?: string;\n dmPolicy: 'pairing' | 'allowlist' | 'open' | 'disabled';\n allowFrom: string[];\n streamMode?: 'off' | 'partial' | 'block';\n routeTag?: string;\n debug?: boolean;\n};\n\ntype WeixinAccountCfg = {\n name?: string;\n enabled?: boolean;\n cdnBaseUrl?: string;\n routeTag?: number | string;\n dmPolicy?: ResolvedWeixinAccount['dmPolicy'];\n allowFrom?: string[];\n streamMode?: ResolvedWeixinAccount['streamMode'];\n debug?: boolean;\n accounts?: Record<string, WeixinAccountCfg>;\n};\n\nfunction weixinSection(cfg: Config): (WeixinAccountCfg & { accounts?: Record<string, WeixinAccountCfg> }) | undefined {\n return cfg.channels?.weixin as\n | (WeixinAccountCfg & { accounts?: Record<string, WeixinAccountCfg> })\n | undefined;\n}\n\nexport function listWeixinAccountIds(cfg: Config): string[] {\n const section = weixinSection(cfg);\n const fromConfig = section?.accounts ? Object.keys(section.accounts) : [];\n // Only merge disk index when the channel is explicitly enabled. Otherwise removed / disabled\n // config still leaves token files + accounts.json on disk and monitors would keep running.\n if (!section || section.enabled !== true) {\n return fromConfig;\n }\n const indexed = listIndexedWeixinAccountIds();\n return [...new Set([...indexed, ...fromConfig])];\n}\n\nexport function resolveWeixinAccount(cfg: Config, accountId?: string | null): ResolvedWeixinAccount {\n const raw = accountId?.trim();\n if (!raw) {\n throw new Error('weixin: accountId is required');\n }\n const id = normalizeWeixinAccountId(raw);\n const section = weixinSection(cfg);\n const fromAccount = section?.accounts?.[id];\n const { accounts: _omit, ...sectionRest } = section ?? {};\n void _omit;\n const accountCfg: WeixinAccountCfg = {\n ...sectionRest,\n ...(fromAccount ?? {}),\n };\n\n const accountData = loadWeixinAccount(id);\n const token = accountData?.token?.trim() || undefined;\n const stateBaseUrl = accountData?.baseUrl?.trim() || '';\n\n const routeTagRaw = accountCfg.routeTag;\n const routeTag =\n typeof routeTagRaw === 'number'\n ? String(routeTagRaw)\n : typeof routeTagRaw === 'string' && routeTagRaw.trim()\n ? routeTagRaw.trim()\n : undefined;\n\n const channelEnabled = section?.enabled === true;\n\n return {\n accountId: id,\n baseUrl: stateBaseUrl || DEFAULT_BASE_URL,\n cdnBaseUrl: accountCfg.cdnBaseUrl?.trim() || CDN_BASE_URL,\n token,\n enabled: channelEnabled && accountCfg.enabled !== false,\n configured: Boolean(token),\n name: accountCfg.name?.trim() || undefined,\n dmPolicy: accountCfg.dmPolicy ?? 'open',\n allowFrom: accountCfg.allowFrom ?? [],\n streamMode: accountCfg.streamMode,\n routeTag,\n debug: accountCfg.debug ?? false,\n };\n}\n\nexport function resolveFrameworkAllowFromPath(accountId: string): string {\n const base = 'xopc-weixin'.replace(/[\\\\/:*?\"<>|]/g, '_');\n const safeAccount = accountId.trim().toLowerCase().replace(/[\\\\/:*?\"<>|]/g, '_').replace(/\\.\\./g, '_');\n const { join } = path;\n const credRoot = process.env.XOPC_CREDENTIALS_DIR?.trim()\n ? process.env.XOPC_CREDENTIALS_DIR!\n : join(resolveWeixinRootDir(), 'credentials');\n return join(credRoot, `${base}-${safeAccount}-allowFrom.json`);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"accounts.js","names":[],"sources":["../../../../../extensions/weixin/src/auth/accounts.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\n\nimport { clearContextTokensForAccount } from '../messaging/inbound.js';\nimport { resolveWeixinRootDir } from '../storage/state-dir.js';\nimport { logger } from '../util/logger.js';\n\nimport { normalizeWeixinAccountId } from './weixin-account-id.js';\n\nexport { normalizeWeixinAccountId };\n\nexport const DEFAULT_BASE_URL = 'https://ilinkai.weixin.qq.com';\nexport const CDN_BASE_URL = 'https://novac2c.cdn.weixin.qq.com/c2c';\n\nfunction resolveWeixinStateDir(): string {\n return resolveWeixinRootDir();\n}\n\nfunction resolveAccountIndexPath(): string {\n return path.join(resolveWeixinStateDir(), 'accounts.json');\n}\n\nexport function listIndexedWeixinAccountIds(): string[] {\n const filePath = resolveAccountIndexPath();\n try {\n if (!fs.existsSync(filePath)) return [];\n const raw = fs.readFileSync(filePath, 'utf-8');\n const parsed = JSON.parse(raw) as unknown;\n if (!Array.isArray(parsed)) return [];\n return parsed.filter((id): id is string => typeof id === 'string' && id.trim() !== '');\n } catch {\n return [];\n }\n}\n\nexport function registerWeixinAccountId(accountId: string): void {\n const dir = resolveWeixinStateDir();\n fs.mkdirSync(dir, { recursive: true });\n\n const existing = listIndexedWeixinAccountIds();\n if (existing.includes(accountId)) return;\n\n const updated = [...existing, accountId];\n fs.writeFileSync(resolveAccountIndexPath(), JSON.stringify(updated, null, 2), 'utf-8');\n}\n\nexport function unregisterWeixinAccountId(accountId: string): void {\n const existing = listIndexedWeixinAccountIds();\n const updated = existing.filter((id) => id !== accountId);\n if (updated.length !== existing.length) {\n fs.writeFileSync(resolveAccountIndexPath(), JSON.stringify(updated, null, 2), 'utf-8');\n }\n}\n\nexport function clearStaleAccountsForUserId(\n currentAccountId: string,\n userId: string,\n onClearContextTokens?: (accountId: string) => void,\n): void {\n if (!userId) return;\n const allIds = listIndexedWeixinAccountIds();\n for (const id of allIds) {\n if (id === currentAccountId) continue;\n const data = loadWeixinAccount(id);\n if (data?.userId?.trim() === userId) {\n logger.info(`clearStaleAccountsForUserId: removing stale account=${id} (same userId=${userId})`);\n onClearContextTokens?.(id);\n clearWeixinAccount(id);\n unregisterWeixinAccountId(id);\n }\n }\n}\n\nexport type WeixinAccountData = {\n token?: string;\n savedAt?: string;\n baseUrl?: string;\n userId?: string;\n};\n\nfunction resolveAccountsDir(): string {\n return path.join(resolveWeixinStateDir(), 'accounts');\n}\n\nfunction resolveAccountPath(accountId: string): string {\n return path.join(resolveAccountsDir(), `${accountId}.json`);\n}\n\nfunction readAccountFile(filePath: string): WeixinAccountData | null {\n try {\n if (fs.existsSync(filePath)) {\n return JSON.parse(fs.readFileSync(filePath, 'utf-8')) as WeixinAccountData;\n }\n } catch {\n // ignore\n }\n return null;\n}\n\nexport function loadWeixinAccount(accountId: string): WeixinAccountData | null {\n return readAccountFile(resolveAccountPath(accountId));\n}\n\nexport function saveWeixinAccount(\n accountId: string,\n update: { token?: string; baseUrl?: string; userId?: string },\n): void {\n const dir = resolveAccountsDir();\n fs.mkdirSync(dir, { recursive: true });\n\n const existing = loadWeixinAccount(accountId) ?? {};\n\n const token = update.token?.trim() || existing.token;\n const baseUrl = update.baseUrl?.trim() || existing.baseUrl;\n const userId =\n update.userId !== undefined ? update.userId.trim() || undefined : existing.userId?.trim() || undefined;\n\n const data: WeixinAccountData = {\n ...(token ? { token, savedAt: new Date().toISOString() } : {}),\n ...(baseUrl ? { baseUrl } : {}),\n ...(userId ? { userId } : {}),\n };\n\n const filePath = resolveAccountPath(accountId);\n fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');\n try {\n fs.chmodSync(filePath, 0o600);\n } catch {\n // best-effort\n }\n}\n\nexport function clearWeixinAccount(accountId: string): void {\n const dir = resolveAccountsDir();\n const accountFiles = [\n `${accountId}.json`,\n `${accountId}.sync.json`,\n `${accountId}.context-tokens.json`,\n ];\n for (const file of accountFiles) {\n try {\n fs.unlinkSync(path.join(dir, file));\n } catch {\n // ignore\n }\n }\n try {\n fs.unlinkSync(resolveFrameworkAllowFromPath(accountId));\n } catch {\n // ignore\n }\n}\n\nexport type ResolvedWeixinAccount = {\n accountId: string;\n baseUrl: string;\n cdnBaseUrl: string;\n token?: string;\n enabled: boolean;\n configured: boolean;\n name?: string;\n dmPolicy: 'pairing' | 'allowlist' | 'open' | 'disabled';\n allowFrom: string[];\n streamMode?: 'off' | 'partial' | 'block';\n routeTag?: string;\n debug?: boolean;\n};\n\ntype WeixinAccountCfg = {\n name?: string;\n enabled?: boolean;\n cdnBaseUrl?: string;\n routeTag?: number | string;\n dmPolicy?: ResolvedWeixinAccount['dmPolicy'];\n allowFrom?: string[];\n streamMode?: ResolvedWeixinAccount['streamMode'];\n debug?: boolean;\n accounts?: Record<string, WeixinAccountCfg>;\n};\n\nfunction weixinSection(cfg: Config): (WeixinAccountCfg & { accounts?: Record<string, WeixinAccountCfg> }) | undefined {\n return cfg.channels?.weixin as\n | (WeixinAccountCfg & { accounts?: Record<string, WeixinAccountCfg> })\n | undefined;\n}\n\nexport function listWeixinAccountIds(cfg: Config): string[] {\n const section = weixinSection(cfg);\n const fromConfig = section?.accounts ? Object.keys(section.accounts) : [];\n // Only merge disk index when the channel is explicitly enabled. Otherwise removed / disabled\n // config still leaves token files + accounts.json on disk and monitors would keep running.\n if (!section || section.enabled !== true) {\n return fromConfig;\n }\n const indexed = listIndexedWeixinAccountIds();\n return [...new Set([...indexed, ...fromConfig])];\n}\n\nexport function resolveWeixinAccount(cfg: Config, accountId?: string | null): ResolvedWeixinAccount {\n const raw = accountId?.trim();\n if (!raw) {\n throw new Error('weixin: accountId is required');\n }\n const id = normalizeWeixinAccountId(raw);\n const section = weixinSection(cfg);\n const fromAccount = section?.accounts?.[id];\n const { accounts: _omit, ...sectionRest } = section ?? {};\n void _omit;\n const accountCfg: WeixinAccountCfg = {\n ...sectionRest,\n ...(fromAccount ?? {}),\n };\n\n const accountData = loadWeixinAccount(id);\n const token = accountData?.token?.trim() || undefined;\n const stateBaseUrl = accountData?.baseUrl?.trim() || '';\n\n const routeTagRaw = accountCfg.routeTag;\n const routeTag =\n typeof routeTagRaw === 'number'\n ? String(routeTagRaw)\n : typeof routeTagRaw === 'string' && routeTagRaw.trim()\n ? routeTagRaw.trim()\n : undefined;\n\n const channelEnabled = section?.enabled === true;\n\n return {\n accountId: id,\n baseUrl: stateBaseUrl || DEFAULT_BASE_URL,\n cdnBaseUrl: accountCfg.cdnBaseUrl?.trim() || CDN_BASE_URL,\n token,\n enabled: channelEnabled && accountCfg.enabled !== false,\n configured: Boolean(token),\n name: accountCfg.name?.trim() || undefined,\n dmPolicy: accountCfg.dmPolicy ?? 'open',\n allowFrom: accountCfg.allowFrom ?? [],\n streamMode: accountCfg.streamMode,\n routeTag,\n debug: accountCfg.debug ?? false,\n };\n}\n\nexport function resolveFrameworkAllowFromPath(accountId: string): string {\n const base = 'xopc-weixin'.replace(/[\\\\/:*?\"<>|]/g, '_');\n const safeAccount = accountId.trim().toLowerCase().replace(/[\\\\/:*?\"<>|]/g, '_').replace(/\\.\\./g, '_');\n const { join } = path;\n const credRoot = process.env.XOPC_CREDENTIALS_DIR?.trim()\n ? process.env.XOPC_CREDENTIALS_DIR!\n : join(resolveWeixinRootDir(), 'credentials');\n return join(credRoot, `${base}-${safeAccount}-allowFrom.json`);\n}\n"],"mappings":"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"}
@@ -6,9 +6,9 @@ import { getUploadUrl } from "../api/api.js";
6
6
  import { getExtensionFromContentTypeOrUrl } from "../media/mime.js";
7
7
  import { aesEcbPaddedSize } from "./aes-ecb.js";
8
8
  import { uploadBufferToCdn } from "./cdn-upload.js";
9
- import path from "node:path";
10
9
  import crypto from "node:crypto";
11
10
  import fs from "node:fs/promises";
11
+ import path from "node:path";
12
12
  //#region extensions/weixin/src/cdn/upload.ts
13
13
  /**
14
14
  * Download a remote media URL (image, video, file) to a local temp file in destDir.
@@ -8,7 +8,7 @@ function normalizeWeixinCronDeliveryTo(to) {
8
8
  const trimmed = to.trim();
9
9
  if (!trimmed) return { chatId: trimmed };
10
10
  const parsed = parseSessionKey(trimmed);
11
- if (parsed?.source === "weixin" && parsed.peerKind === "dm" && parsed.peerId) {
11
+ if (parsed?.source === "weixin" && (parsed.peerKind === "direct" || parsed.peerKind === "dm") && parsed.peerId) {
12
12
  const accountId = parsed.accountId && parsed.accountId !== "_" ? parsed.accountId : void 0;
13
13
  return {
14
14
  chatId: parsed.peerId,
@@ -16,7 +16,7 @@ function normalizeWeixinCronDeliveryTo(to) {
16
16
  };
17
17
  }
18
18
  const parts = trimmed.split(":");
19
- if (parts.length === 3 && parts[1]?.toLowerCase() === "dm" && parts[0] && parts[2]) return {
19
+ if (parts.length === 3 && parts[1]?.toLowerCase() === "direct" && parts[0] && parts[2]) return {
20
20
  chatId: parts[2],
21
21
  accountId: parts[0]
22
22
  };
@@ -1 +1 @@
1
- {"version":3,"file":"delivery-to.js","names":[],"sources":["../../../../extensions/weixin/src/delivery-to.ts"],"sourcesContent":["import { parseSessionKey } from '@xopcai/xopc/routing/session-key.js';\nimport type { SessionStore } from '@xopcai/xopc/session/store.js';\n\nexport type NormalizedWeixinCronDelivery = {\n chatId: string;\n /** Set on OutboundMessage.metadata for multi-account Weixin */\n accountId?: string;\n};\n\n/**\n * Resolves Weixin ilink `chat_id` and optional `accountId` from cron / UI `delivery.to`.\n */\nexport function normalizeWeixinCronDeliveryTo(to: string): NormalizedWeixinCronDelivery {\n const trimmed = to.trim();\n if (!trimmed) {\n return { chatId: trimmed };\n }\n\n const parsed = parseSessionKey(trimmed);\n if (parsed?.source === 'weixin' && parsed.peerKind === 'dm' && parsed.peerId) {\n const accountId = parsed.accountId && parsed.accountId !== '_' ? parsed.accountId : undefined;\n return { chatId: parsed.peerId, accountId };\n }\n\n const parts = trimmed.split(':');\n if (parts.length === 3 && parts[1]?.toLowerCase() === 'dm' && parts[0] && parts[2]) {\n return { chatId: parts[2], accountId: parts[0] };\n }\n\n return { chatId: trimmed };\n}\n\n/**\n * If exactly one indexed weixin DM session matches `ilinkPeerId`, return that session's accountId.\n */\nexport async function resolveWeixinAccountIdFromSessions(\n store: SessionStore,\n ilinkPeerId: string,\n): Promise<string | undefined> {\n const normalized = ilinkPeerId.trim().toLowerCase();\n if (!normalized) return undefined;\n\n const accountIds = new Set<string>();\n let offset = 0;\n const pageSize = 2000;\n\n for (;;) {\n const batch = await store.list({ channel: 'weixin', limit: pageSize, offset });\n for (const s of batch.items) {\n const rp = s.routing?.peerId?.toLowerCase();\n if (rp === normalized && s.routing?.accountId) {\n accountIds.add(s.routing.accountId);\n }\n }\n if (!batch.hasMore) break;\n offset += pageSize;\n }\n\n if (accountIds.size === 1) return [...accountIds][0];\n return undefined;\n}\n\n/** Like {@link normalizeWeixinCronDeliveryTo}, plus session-index lookup for bare ilink user ids. */\nexport async function normalizeWeixinCronDeliveryToResolved(\n to: string,\n sessionStore?: SessionStore,\n): Promise<NormalizedWeixinCronDelivery> {\n const base = normalizeWeixinCronDeliveryTo(to);\n if (base.accountId || !sessionStore) return base;\n if (!to.trim().includes(':')) {\n const accountId = await resolveWeixinAccountIdFromSessions(sessionStore, base.chatId);\n if (accountId) return { chatId: base.chatId, accountId };\n }\n return base;\n}\n"],"mappings":";;kBAAsE;;;;AAYtE,SAAgB,8BAA8B,IAA0C;CACtF,MAAM,UAAU,GAAG,MAAM;AACzB,KAAI,CAAC,QACH,QAAO,EAAE,QAAQ,SAAS;CAG5B,MAAM,SAAS,gBAAgB,QAAQ;AACvC,KAAI,QAAQ,WAAW,YAAY,OAAO,aAAa,QAAQ,OAAO,QAAQ;EAC5E,MAAM,YAAY,OAAO,aAAa,OAAO,cAAc,MAAM,OAAO,YAAY,KAAA;AACpF,SAAO;GAAE,QAAQ,OAAO;GAAQ;GAAW;;CAG7C,MAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,KAAI,MAAM,WAAW,KAAK,MAAM,IAAI,aAAa,KAAK,QAAQ,MAAM,MAAM,MAAM,GAC9E,QAAO;EAAE,QAAQ,MAAM;EAAI,WAAW,MAAM;EAAI;AAGlD,QAAO,EAAE,QAAQ,SAAS;;;;;AAM5B,eAAsB,mCACpB,OACA,aAC6B;CAC7B,MAAM,aAAa,YAAY,MAAM,CAAC,aAAa;AACnD,KAAI,CAAC,WAAY,QAAO,KAAA;CAExB,MAAM,6BAAa,IAAI,KAAa;CACpC,IAAI,SAAS;CACb,MAAM,WAAW;AAEjB,UAAS;EACP,MAAM,QAAQ,MAAM,MAAM,KAAK;GAAE,SAAS;GAAU,OAAO;GAAU;GAAQ,CAAC;AAC9E,OAAK,MAAM,KAAK,MAAM,MAEpB,KADW,EAAE,SAAS,QAAQ,aAAa,KAChC,cAAc,EAAE,SAAS,UAClC,YAAW,IAAI,EAAE,QAAQ,UAAU;AAGvC,MAAI,CAAC,MAAM,QAAS;AACpB,YAAU;;AAGZ,KAAI,WAAW,SAAS,EAAG,QAAO,CAAC,GAAG,WAAW,CAAC;;;AAKpD,eAAsB,sCACpB,IACA,cACuC;CACvC,MAAM,OAAO,8BAA8B,GAAG;AAC9C,KAAI,KAAK,aAAa,CAAC,aAAc,QAAO;AAC5C,KAAI,CAAC,GAAG,MAAM,CAAC,SAAS,IAAI,EAAE;EAC5B,MAAM,YAAY,MAAM,mCAAmC,cAAc,KAAK,OAAO;AACrF,MAAI,UAAW,QAAO;GAAE,QAAQ,KAAK;GAAQ;GAAW;;AAE1D,QAAO"}
1
+ {"version":3,"file":"delivery-to.js","names":[],"sources":["../../../../extensions/weixin/src/delivery-to.ts"],"sourcesContent":["import { parseSessionKey } from '@xopcai/xopc/routing/session-key.js';\nimport type { SessionStore } from '@xopcai/xopc/session/store.js';\n\nexport type NormalizedWeixinCronDelivery = {\n chatId: string;\n /** Set on OutboundMessage.metadata for multi-account Weixin */\n accountId?: string;\n};\n\n/**\n * Resolves Weixin ilink `chat_id` and optional `accountId` from cron / UI `delivery.to`.\n */\nexport function normalizeWeixinCronDeliveryTo(to: string): NormalizedWeixinCronDelivery {\n const trimmed = to.trim();\n if (!trimmed) {\n return { chatId: trimmed };\n }\n\n const parsed = parseSessionKey(trimmed);\n if (parsed?.source === 'weixin' && (parsed.peerKind === 'direct' || parsed.peerKind === 'dm') && parsed.peerId) {\n const accountId = parsed.accountId && parsed.accountId !== '_' ? parsed.accountId : undefined;\n return { chatId: parsed.peerId, accountId };\n }\n\n const parts = trimmed.split(':');\n if (parts.length === 3 && parts[1]?.toLowerCase() === 'direct' && parts[0] && parts[2]) {\n return { chatId: parts[2], accountId: parts[0] };\n }\n\n return { chatId: trimmed };\n}\n\n/**\n * If exactly one indexed weixin DM session matches `ilinkPeerId`, return that session's accountId.\n */\nexport async function resolveWeixinAccountIdFromSessions(\n store: SessionStore,\n ilinkPeerId: string,\n): Promise<string | undefined> {\n const normalized = ilinkPeerId.trim().toLowerCase();\n if (!normalized) return undefined;\n\n const accountIds = new Set<string>();\n let offset = 0;\n const pageSize = 2000;\n\n for (;;) {\n const batch = await store.list({ channel: 'weixin', limit: pageSize, offset });\n for (const s of batch.items) {\n const rp = s.routing?.peerId?.toLowerCase();\n if (rp === normalized && s.routing?.accountId) {\n accountIds.add(s.routing.accountId);\n }\n }\n if (!batch.hasMore) break;\n offset += pageSize;\n }\n\n if (accountIds.size === 1) return [...accountIds][0];\n return undefined;\n}\n\n/** Like {@link normalizeWeixinCronDeliveryTo}, plus session-index lookup for bare ilink user ids. */\nexport async function normalizeWeixinCronDeliveryToResolved(\n to: string,\n sessionStore?: SessionStore,\n): Promise<NormalizedWeixinCronDelivery> {\n const base = normalizeWeixinCronDeliveryTo(to);\n if (base.accountId || !sessionStore) return base;\n if (!to.trim().includes(':')) {\n const accountId = await resolveWeixinAccountIdFromSessions(sessionStore, base.chatId);\n if (accountId) return { chatId: base.chatId, accountId };\n }\n return base;\n}\n"],"mappings":";;kBAAsE;;;;AAYtE,SAAgB,8BAA8B,IAA0C;CACtF,MAAM,UAAU,GAAG,MAAM;AACzB,KAAI,CAAC,QACH,QAAO,EAAE,QAAQ,SAAS;CAG5B,MAAM,SAAS,gBAAgB,QAAQ;AACvC,KAAI,QAAQ,WAAW,aAAa,OAAO,aAAa,YAAY,OAAO,aAAa,SAAS,OAAO,QAAQ;EAC9G,MAAM,YAAY,OAAO,aAAa,OAAO,cAAc,MAAM,OAAO,YAAY,KAAA;AACpF,SAAO;GAAE,QAAQ,OAAO;GAAQ;GAAW;;CAG7C,MAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,KAAI,MAAM,WAAW,KAAK,MAAM,IAAI,aAAa,KAAK,YAAY,MAAM,MAAM,MAAM,GAClF,QAAO;EAAE,QAAQ,MAAM;EAAI,WAAW,MAAM;EAAI;AAGlD,QAAO,EAAE,QAAQ,SAAS;;;;;AAM5B,eAAsB,mCACpB,OACA,aAC6B;CAC7B,MAAM,aAAa,YAAY,MAAM,CAAC,aAAa;AACnD,KAAI,CAAC,WAAY,QAAO,KAAA;CAExB,MAAM,6BAAa,IAAI,KAAa;CACpC,IAAI,SAAS;CACb,MAAM,WAAW;AAEjB,UAAS;EACP,MAAM,QAAQ,MAAM,MAAM,KAAK;GAAE,SAAS;GAAU,OAAO;GAAU;GAAQ,CAAC;AAC9E,OAAK,MAAM,KAAK,MAAM,MAEpB,KADW,EAAE,SAAS,QAAQ,aAAa,KAChC,cAAc,EAAE,SAAS,UAClC,YAAW,IAAI,EAAE,QAAQ,UAAU;AAGvC,MAAI,CAAC,MAAM,QAAS;AACpB,YAAU;;AAGZ,KAAI,WAAW,SAAS,EAAG,QAAO,CAAC,GAAG,WAAW,CAAC;;;AAKpD,eAAsB,sCACpB,IACA,cACuC;CACvC,MAAM,OAAO,8BAA8B,GAAG;AAC9C,KAAI,KAAK,aAAa,CAAC,aAAc,QAAO;AAC5C,KAAI,CAAC,GAAG,MAAM,CAAC,SAAS,IAAI,EAAE;EAC5B,MAAM,YAAY,MAAM,mCAAmC,cAAc,KAAK,OAAO;AACrF,MAAI,UAAW,QAAO;GAAE,QAAQ,KAAK;GAAQ;GAAW;;AAE1D,QAAO"}