@xopcai/xopc 0.0.109 → 0.0.111

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 (346) hide show
  1. package/README.md +46 -4
  2. package/README.zh-CN.md +46 -4
  3. package/dist/browser-ext/manifest.json +1 -1
  4. package/dist/extensions/feishu/src/pairing-config-resolver.d.ts +1 -0
  5. package/dist/extensions/feishu/src/pairing-config-resolver.js +30 -0
  6. package/dist/extensions/feishu/src/pairing-config-resolver.js.map +1 -0
  7. package/dist/extensions/feishu/src/plugin.d.ts +1 -0
  8. package/dist/extensions/feishu/src/plugin.js +1 -0
  9. package/dist/extensions/feishu/src/plugin.js.map +1 -1
  10. package/dist/extensions/telegram/src/access-control.d.ts +2 -1
  11. package/dist/extensions/telegram/src/access-control.js.map +1 -1
  12. package/dist/extensions/telegram/src/account-manager.d.ts +2 -1
  13. package/dist/extensions/telegram/src/account-manager.js.map +1 -1
  14. package/dist/extensions/telegram/src/adapters/gateway-setup.js +26 -26
  15. package/dist/extensions/telegram/src/adapters/gateway-setup.js.map +1 -1
  16. package/dist/extensions/telegram/src/adapters/onboard-cli.js +8 -4
  17. package/dist/extensions/telegram/src/adapters/onboard-cli.js.map +1 -1
  18. package/dist/extensions/telegram/src/config-schema.d.ts +88 -50
  19. package/dist/extensions/telegram/src/config-schema.js +40 -32
  20. package/dist/extensions/telegram/src/config-schema.js.map +1 -1
  21. package/dist/extensions/telegram/src/config-types.d.ts +54 -0
  22. package/dist/extensions/telegram/src/config-types.js +1 -0
  23. package/dist/extensions/telegram/src/doctor.js +17 -16
  24. package/dist/extensions/telegram/src/doctor.js.map +1 -1
  25. package/dist/extensions/telegram/src/exec-approvals.js +2 -3
  26. package/dist/extensions/telegram/src/exec-approvals.js.map +1 -1
  27. package/dist/extensions/telegram/src/group-config-resolver.d.ts +1 -1
  28. package/dist/extensions/telegram/src/group-config-resolver.js.map +1 -1
  29. package/dist/extensions/telegram/src/pairing-config-resolver.d.ts +1 -0
  30. package/dist/extensions/telegram/src/pairing-config-resolver.js +56 -0
  31. package/dist/extensions/telegram/src/pairing-config-resolver.js.map +1 -0
  32. package/dist/extensions/telegram/src/plugin.d.ts +1 -0
  33. package/dist/extensions/telegram/src/plugin.js +8 -9
  34. package/dist/extensions/telegram/src/plugin.js.map +1 -1
  35. package/dist/extensions/telegram/xopc.extension.json +156 -122
  36. package/dist/extensions/weixin/src/pairing-config-resolver.d.ts +1 -0
  37. package/dist/extensions/weixin/src/pairing-config-resolver.js +54 -0
  38. package/dist/extensions/weixin/src/pairing-config-resolver.js.map +1 -0
  39. package/dist/extensions/weixin/src/plugin.d.ts +1 -0
  40. package/dist/extensions/weixin/src/plugin.js +1 -0
  41. package/dist/extensions/weixin/src/plugin.js.map +1 -1
  42. package/dist/gateway/static/root/assets/Combination-BzaXOLd9.js +41 -0
  43. package/dist/gateway/static/root/assets/agent-avatar-dicebear-zlx9l5Yp.js +1 -0
  44. package/dist/gateway/static/root/assets/agent-browser-settings-page-B-0zGjt8.js +6 -0
  45. package/dist/gateway/static/root/assets/agent-defaults-tabbed-page-Bz7JWjRM.js +1 -0
  46. package/dist/gateway/static/root/assets/agents-DwDnJlXT.js +71 -0
  47. package/dist/gateway/static/root/assets/app-management-settings-panel-DaQwdeCq.js +1 -0
  48. package/dist/gateway/static/root/assets/appearance-settings-D2zv6r3P.js +1 -0
  49. package/dist/gateway/static/root/assets/apps-page-BIppe_jj.js +1 -0
  50. package/dist/gateway/static/root/assets/archive-plugin-DEmu8eH7.js +1 -0
  51. package/dist/gateway/static/root/assets/attachment-load-B98lJoN5.js +2 -0
  52. package/dist/gateway/static/root/assets/{attachment-preview-renderer-CybMSIoS.js → attachment-preview-renderer-DonnL2dL.js} +1 -1
  53. package/dist/gateway/static/root/assets/binary-plugins-CL9QKmyo.js +2 -0
  54. package/dist/gateway/static/root/assets/block-editor-BOAhMAhT.js +180 -0
  55. package/dist/gateway/static/root/assets/channels-settings-Br8iRnXp.js +1 -0
  56. package/dist/gateway/static/root/assets/cn-JdwAhgNB.js +1 -0
  57. package/dist/gateway/static/root/assets/code-editor-dZTY2dRW.js +1 -0
  58. package/dist/gateway/static/root/assets/config-api-0o1WYdz2.js +3 -0
  59. package/dist/gateway/static/root/assets/connectors-settings-DDvHo9in.js +2 -0
  60. package/dist/gateway/static/root/assets/cron-api-Dj_UZKST.js +1 -0
  61. package/dist/gateway/static/root/assets/cron-dreaming-jobs-nCMWCf0p.js +1 -0
  62. package/dist/gateway/static/root/assets/cron-page-Dv7GII9G.js +1 -0
  63. package/dist/gateway/static/root/assets/cron-schedule-picker-DH10KgD6.js +1 -0
  64. package/dist/gateway/static/root/assets/dist-By6Rh2ee.js +1 -0
  65. package/dist/gateway/static/root/assets/dist-CkobR7mk.js +1 -0
  66. package/dist/gateway/static/root/assets/dist-DokQNVMQ.js +1 -0
  67. package/dist/gateway/static/root/assets/dreaming-scene-BHMRVEUS.js +4585 -0
  68. package/dist/gateway/static/root/assets/dreaming-settings-L2rgZDQl.js +1 -0
  69. package/dist/gateway/static/root/assets/extension-debug-page-Cjx6pZVz.js +1 -0
  70. package/dist/gateway/static/root/assets/{extension-page-DNcTendk.js → extension-page-BSPNL90V.js} +1 -1
  71. package/dist/gateway/static/root/assets/extension-settings-page-m_UQYkVc.js +1 -0
  72. package/dist/gateway/static/root/assets/fetch-Bs7in_4F.js +1 -0
  73. package/dist/gateway/static/root/assets/format-interval-label-CftJ5uXu.js +1 -0
  74. package/dist/gateway/static/root/assets/gateway-config-api-Clfck-hW.js +1 -0
  75. package/dist/gateway/static/root/assets/gateway-settings-CS177xni.js +1 -0
  76. package/dist/gateway/static/root/assets/gateway-startup-retry-BkjVJc38.js +1 -0
  77. package/dist/gateway/static/root/assets/goal-detail-page-kqJue0sE.js +1 -0
  78. package/dist/gateway/static/root/assets/goals-page-C0FujUB5.js +2 -0
  79. package/dist/gateway/static/root/assets/goals-settings-CJnzNSDo.js +1 -0
  80. package/dist/gateway/static/root/assets/heartbeat-config-api-Cp7CPuIn.js +1 -0
  81. package/dist/gateway/static/root/assets/heartbeat-settings-ifSI1Wbh.js +1 -0
  82. package/dist/gateway/static/root/assets/html-workspace-editor-CQLWDiwE.js +1 -0
  83. package/dist/gateway/static/root/assets/index-COkF54xv.js +121 -0
  84. package/dist/gateway/static/root/assets/index-DIaSNGEO.css +1 -0
  85. package/dist/gateway/static/root/assets/interval-presets-B29Ss_2y.js +2 -0
  86. package/dist/gateway/static/root/assets/keyboard-shortcuts-settings--MI1rYl7.js +1 -0
  87. package/dist/gateway/static/root/assets/locale-store-DjbfSsfA.js +1 -0
  88. package/dist/gateway/static/root/assets/logs-page-BwrVNlyW.js +2 -0
  89. package/dist/gateway/static/root/assets/markdown-editor-AssmZFx4.js +2 -0
  90. package/dist/gateway/static/root/assets/markdown-split-B_oc9lJe.js +1 -0
  91. package/dist/gateway/static/root/assets/media-plugins-DLPvB5H3.js +1 -0
  92. package/dist/gateway/static/root/assets/mermaid-render-BbqDc_LQ.js +134 -0
  93. package/dist/gateway/static/root/assets/messages-Csef1aW7.js +3 -0
  94. package/dist/gateway/static/root/assets/middleware-Pd0e8Igk.js +1 -0
  95. package/dist/gateway/static/root/assets/models-hub-panel-DHGiTIvf.js +2 -0
  96. package/dist/gateway/static/root/assets/note-detail-page-BM7zlDiT.js +2 -0
  97. package/dist/gateway/static/root/assets/note-image-lightbox-rSqAgOi9.js +1 -0
  98. package/dist/gateway/static/root/assets/note-time-hm6JO_Wi.js +1 -0
  99. package/dist/gateway/static/root/assets/notes-page-C9ACGEty.js +1 -0
  100. package/dist/gateway/static/root/assets/preview-open-alternatives-Bw_N2oZi.js +1 -0
  101. package/dist/gateway/static/root/assets/remote-access-hub-BhcWIKuM.js +1 -0
  102. package/dist/gateway/static/root/assets/save-bar-controls-CNP2jmw0.js +1 -0
  103. package/dist/gateway/static/root/assets/schedule-field-BPAWui3P.js +1 -0
  104. package/dist/gateway/static/root/assets/schedule-summary-B-hdSRdR.js +1 -0
  105. package/dist/gateway/static/root/assets/schema-form-CDPzua9q.js +1 -0
  106. package/dist/gateway/static/root/assets/sessions-page-DzkpSDZr.js +1 -0
  107. package/dist/gateway/static/root/assets/settings-advanced-gate-BYS-rpzn.js +1 -0
  108. package/dist/gateway/static/root/assets/settings-collapsible-section-C9bff85B.js +1 -0
  109. package/dist/gateway/static/root/assets/settings-form-section-BsrK7TBF.js +1 -0
  110. package/dist/gateway/static/root/assets/settings-page-DWK4Zo7s.js +1 -0
  111. package/dist/gateway/static/root/assets/setup-status-panel-DJiksNu4.js +1 -0
  112. package/dist/gateway/static/root/assets/share-preview-page-DnutoY_S.js +2 -0
  113. package/dist/gateway/static/root/assets/shares-settings-6oRgJMpn.js +1 -0
  114. package/dist/gateway/static/root/assets/skill-reload-api-CU9i0nJV.js +1 -0
  115. package/dist/gateway/static/root/assets/skills-page-ERbdWENm.js +2 -0
  116. package/dist/gateway/static/root/assets/system-settings-panel-qP1JDtDd.js +1 -0
  117. package/dist/gateway/static/root/assets/theme-store-CD2wtgZV.js +1 -0
  118. package/dist/gateway/static/root/assets/use-agent-defaults-form-D3FGVLZb.js +1 -0
  119. package/dist/gateway/static/root/assets/use-blob-object-url-D4BFuzZB.js +1 -0
  120. package/dist/gateway/static/root/assets/use-channel-catalog-C4mg1hjD.js +1 -0
  121. package/dist/gateway/static/root/assets/use-settings-tab-guard-F6C0mpJt.js +1 -0
  122. package/dist/gateway/static/root/assets/vendor-codemirror-CNP1wPe7.js +45 -0
  123. package/dist/gateway/static/root/assets/voice-api-key-field-BnDDb6qO.js +1 -0
  124. package/dist/gateway/static/root/assets/voice-player-store-B2EwIhtY.js +1 -0
  125. package/dist/gateway/static/root/assets/{workflow-page.utils-BcZQmKBX.js → workflow-page.utils-CRB7WX9l.js} +1 -1
  126. package/dist/gateway/static/root/assets/workflows-page-2Fptr0je.js +18 -0
  127. package/dist/gateway/static/root/index.html +12 -10
  128. package/dist/package.js +1 -1
  129. package/dist/src/agent/goals/checklist-judge.d.ts +5 -0
  130. package/dist/src/agent/goals/checklist-judge.js +26 -2
  131. package/dist/src/agent/goals/checklist-judge.js.map +1 -1
  132. package/dist/src/agent/goals/evaluate-turn.d.ts +5 -1
  133. package/dist/src/agent/goals/evaluate-turn.js +29 -3
  134. package/dist/src/agent/goals/evaluate-turn.js.map +1 -1
  135. package/dist/src/agent/goals/goal-locale.d.ts +0 -2
  136. package/dist/src/agent/goals/goal-locale.js +2 -15
  137. package/dist/src/agent/goals/goal-locale.js.map +1 -1
  138. package/dist/src/agent/goals/persistent-goal-service.d.ts +9 -0
  139. package/dist/src/agent/goals/persistent-goal-service.js +2 -1
  140. package/dist/src/agent/goals/persistent-goal-service.js.map +1 -1
  141. package/dist/src/agent/goals/post-turn.d.ts +7 -0
  142. package/dist/src/agent/goals/post-turn.js +68 -23
  143. package/dist/src/agent/goals/post-turn.js.map +1 -1
  144. package/dist/src/agent/goals/state.d.ts +0 -5
  145. package/dist/src/agent/goals/state.js +1 -96
  146. package/dist/src/agent/goals/state.js.map +1 -1
  147. package/dist/src/agent/messaging/command-handler.d.ts +4 -0
  148. package/dist/src/agent/messaging/command-handler.js +6 -2
  149. package/dist/src/agent/messaging/command-handler.js.map +1 -1
  150. package/dist/src/agent/service/direct-turn-helpers.d.ts +2 -0
  151. package/dist/src/agent/service/direct-turn-helpers.js +3 -2
  152. package/dist/src/agent/service/direct-turn-helpers.js.map +1 -1
  153. package/dist/src/agent/service/process-direct-streaming.js +10 -1
  154. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  155. package/dist/src/agent/service.js +20 -1
  156. package/dist/src/agent/service.js.map +1 -1
  157. package/dist/src/agent/service.types.d.ts +8 -0
  158. package/dist/src/agent/tools/cronjob-tool.d.ts +2 -0
  159. package/dist/src/agent/tools/cronjob-tool.js +27 -4
  160. package/dist/src/agent/tools/cronjob-tool.js.map +1 -1
  161. package/dist/src/agent/tools/edit.d.ts +2 -0
  162. package/dist/src/agent/tools/edit.js +10 -0
  163. package/dist/src/agent/tools/edit.js.map +1 -1
  164. package/dist/src/agent/tools/factory.js +16 -3
  165. package/dist/src/agent/tools/factory.js.map +1 -1
  166. package/dist/src/agent/tools/goal-evidence-recorder.d.ts +6 -0
  167. package/dist/src/agent/tools/goal-evidence-recorder.js +34 -0
  168. package/dist/src/agent/tools/goal-evidence-recorder.js.map +1 -0
  169. package/dist/src/agent/tools/goal-tool.d.ts +19 -0
  170. package/dist/src/agent/tools/goal-tool.js +101 -0
  171. package/dist/src/agent/tools/goal-tool.js.map +1 -0
  172. package/dist/src/agent/tools/index.d.ts +1 -0
  173. package/dist/src/agent/tools/index.js +2 -1
  174. package/dist/src/agent/tools/shell.d.ts +2 -0
  175. package/dist/src/agent/tools/shell.js +17 -2
  176. package/dist/src/agent/tools/shell.js.map +1 -1
  177. package/dist/src/agent/tools/workflow-tool.d.ts +1 -0
  178. package/dist/src/agent/tools/workflow-tool.js +10 -2
  179. package/dist/src/agent/tools/workflow-tool.js.map +1 -1
  180. package/dist/src/agent/tools/write.d.ts +2 -0
  181. package/dist/src/agent/tools/write.js +11 -0
  182. package/dist/src/agent/tools/write.js.map +1 -1
  183. package/dist/src/channels/attachments/inbound-persist.d.ts +7 -0
  184. package/dist/src/channels/attachments/inbound-persist.js +20 -2
  185. package/dist/src/channels/attachments/inbound-persist.js.map +1 -1
  186. package/dist/src/channels/channel-domain.d.ts +1 -81
  187. package/dist/src/channels/pairing/index.d.ts +2 -2
  188. package/dist/src/channels/pairing/index.js +2 -2
  189. package/dist/src/channels/pairing/pairing-service.d.ts +13 -0
  190. package/dist/src/channels/pairing/pairing-service.js +78 -63
  191. package/dist/src/channels/pairing/pairing-service.js.map +1 -1
  192. package/dist/src/chat-commands/builtins/config.js +4 -11
  193. package/dist/src/chat-commands/builtins/config.js.map +1 -1
  194. package/dist/src/chat-commands/builtins/goal.d.ts +1 -1
  195. package/dist/src/chat-commands/builtins/goal.js +55 -97
  196. package/dist/src/chat-commands/builtins/goal.js.map +1 -1
  197. package/dist/src/chat-commands/builtins/subgoal.d.ts +1 -1
  198. package/dist/src/chat-commands/builtins/subgoal.js +49 -108
  199. package/dist/src/chat-commands/builtins/subgoal.js.map +1 -1
  200. package/dist/src/chat-commands/builtins/workflow.js +132 -8
  201. package/dist/src/chat-commands/builtins/workflow.js.map +1 -1
  202. package/dist/src/chat-commands/context.d.ts +3 -0
  203. package/dist/src/chat-commands/context.js +5 -0
  204. package/dist/src/chat-commands/context.js.map +1 -1
  205. package/dist/src/chat-commands/types.d.ts +5 -0
  206. package/dist/src/cli/command-catalog.js +43 -0
  207. package/dist/src/cli/command-catalog.js.map +1 -1
  208. package/dist/src/cli/command-loaders.js +1 -0
  209. package/dist/src/cli/command-loaders.js.map +1 -1
  210. package/dist/src/cli/command-manifest.js +4 -0
  211. package/dist/src/cli/command-manifest.js.map +1 -1
  212. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  213. package/dist/src/cli/commands/goal.d.ts +4 -0
  214. package/dist/src/cli/commands/goal.js +213 -0
  215. package/dist/src/cli/commands/goal.js.map +1 -0
  216. package/dist/src/cli/commands/onboard.js +11 -5
  217. package/dist/src/cli/commands/onboard.js.map +1 -1
  218. package/dist/src/config/index.js +2 -2
  219. package/dist/src/config/schema.d.ts +115 -0
  220. package/dist/src/config/schema.js +72 -18
  221. package/dist/src/config/schema.js.map +1 -1
  222. package/dist/src/config/web-patch.d.ts +14 -0
  223. package/dist/src/config/web-patch.js +45 -6
  224. package/dist/src/config/web-patch.js.map +1 -1
  225. package/dist/src/cron/executor.d.ts +2 -0
  226. package/dist/src/cron/executor.js +25 -0
  227. package/dist/src/cron/executor.js.map +1 -1
  228. package/dist/src/cron/job-content.js +1 -0
  229. package/dist/src/cron/job-content.js.map +1 -1
  230. package/dist/src/cron/types.d.ts +19 -1
  231. package/dist/src/cron/validation.d.ts +20 -0
  232. package/dist/src/cron/validation.js +7 -0
  233. package/dist/src/cron/validation.js.map +1 -1
  234. package/dist/src/gateway/hono/lib/config-payload.d.ts +14 -0
  235. package/dist/src/gateway/hono/routes/goals.d.ts +1 -1
  236. package/dist/src/gateway/hono/routes/goals.js +670 -173
  237. package/dist/src/gateway/hono/routes/goals.js.map +1 -1
  238. package/dist/src/gateway/hono/routes/home.d.ts +5 -0
  239. package/dist/src/gateway/hono/routes/home.js +88 -3
  240. package/dist/src/gateway/hono/routes/home.js.map +1 -1
  241. package/dist/src/gateway/hono/routes/notes.js +20 -14
  242. package/dist/src/gateway/hono/routes/notes.js.map +1 -1
  243. package/dist/src/gateway/hono/routes/workflows.js +6 -2
  244. package/dist/src/gateway/hono/routes/workflows.js.map +1 -1
  245. package/dist/src/gateway/hono/sse.js.map +1 -1
  246. package/dist/src/gateway/service/agent-runner.d.ts +2 -0
  247. package/dist/src/gateway/service/agent-runner.js +6 -2
  248. package/dist/src/gateway/service/agent-runner.js.map +1 -1
  249. package/dist/src/gateway/service/run-gateway-agent.d.ts +3 -0
  250. package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
  251. package/dist/src/gateway/service.d.ts +8 -0
  252. package/dist/src/gateway/service.js +70 -3
  253. package/dist/src/gateway/service.js.map +1 -1
  254. package/dist/src/goals/goal-notifications.d.ts +29 -0
  255. package/dist/src/goals/goal-notifications.js +154 -0
  256. package/dist/src/goals/goal-notifications.js.map +1 -0
  257. package/dist/src/goals/goal-queue-store.d.ts +16 -0
  258. package/dist/src/goals/goal-queue-store.js +131 -0
  259. package/dist/src/goals/goal-queue-store.js.map +1 -0
  260. package/dist/src/goals/goal-queue-types.d.ts +31 -0
  261. package/dist/src/goals/goal-queue-types.js +1 -0
  262. package/dist/src/goals/goal-runner.d.ts +17 -0
  263. package/dist/src/goals/goal-runner.js +127 -0
  264. package/dist/src/goals/goal-runner.js.map +1 -0
  265. package/dist/src/goals/goal-service.d.ts +77 -0
  266. package/dist/src/goals/goal-service.js +261 -0
  267. package/dist/src/goals/goal-service.js.map +1 -0
  268. package/dist/src/goals/goal-store.d.ts +38 -0
  269. package/dist/src/goals/goal-store.js +404 -0
  270. package/dist/src/goals/goal-store.js.map +1 -0
  271. package/dist/src/goals/goal-workflow-judge.d.ts +13 -0
  272. package/dist/src/goals/goal-workflow-judge.js +147 -0
  273. package/dist/src/goals/goal-workflow-judge.js.map +1 -0
  274. package/dist/src/goals/index.d.ts +10 -0
  275. package/dist/src/goals/index.js +7 -0
  276. package/dist/src/goals/types.d.ts +119 -0
  277. package/dist/src/goals/types.js +1 -0
  278. package/dist/src/storage/sqlite/migrations/006_add_goals.sql +108 -0
  279. package/dist/src/storage/sqlite/migrations/007_goal_run_judge_schema.sql +4 -0
  280. package/dist/src/storage/sqlite/migrations/008_goal_queue_workflow_runs.sql +53 -0
  281. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  282. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  283. package/dist/src/storage/sqlite/migrations/runner.js.map +1 -1
  284. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  285. package/dist/src/storage/sqlite/schema.js +1 -1
  286. package/dist/src/storage/sqlite/schema.sql +163 -0
  287. package/dist/src/utils/logger/stats.d.ts +1 -1
  288. package/dist/src/workflows/domain/run.d.ts +1 -0
  289. package/dist/src/workflows/domain/run.js.map +1 -1
  290. package/dist/src/workflows/service/workflow-run-service.d.ts +1 -0
  291. package/dist/src/workflows/service/workflow-run-service.js +5 -1
  292. package/dist/src/workflows/service/workflow-run-service.js.map +1 -1
  293. package/dist/src/workflows/service/workflow-run-service.types.d.ts +1 -0
  294. package/dist/src/workflows/service/workflow-session-bridge.js +7 -0
  295. package/dist/src/workflows/service/workflow-session-bridge.js.map +1 -1
  296. package/dist/src/workflows/store/run-index-store.d.ts +8 -0
  297. package/dist/src/workflows/store/run-index-store.js +58 -0
  298. package/dist/src/workflows/store/run-index-store.js.map +1 -0
  299. package/dist/src/workflows/store/run-store.d.ts +2 -0
  300. package/dist/src/workflows/store/run-store.js +14 -21
  301. package/dist/src/workflows/store/run-store.js.map +1 -1
  302. package/package.json +9 -3
  303. package/dist/gateway/static/root/assets/Combination-HAlzriaz.js +0 -41
  304. package/dist/gateway/static/root/assets/agents-D2VtykeA.js +0 -78
  305. package/dist/gateway/static/root/assets/apps-page-BPQ6ueG-.js +0 -1
  306. package/dist/gateway/static/root/assets/channels-settings-DgZc25cS.js +0 -1
  307. package/dist/gateway/static/root/assets/circle-check-C23XjkUj.js +0 -1
  308. package/dist/gateway/static/root/assets/copy-Dv6d4Dvw.js +0 -1
  309. package/dist/gateway/static/root/assets/cron-page-BQFw5O-8.js +0 -1
  310. package/dist/gateway/static/root/assets/dist-BLMShn4_.js +0 -1
  311. package/dist/gateway/static/root/assets/extension-debug-page-BbQ8uZY_.js +0 -1
  312. package/dist/gateway/static/root/assets/extension-settings-page-ClWJvwHK.js +0 -1
  313. package/dist/gateway/static/root/assets/fetch-CFdKBnE6.js +0 -1
  314. package/dist/gateway/static/root/assets/gateway-startup-retry-DdngdObI.js +0 -1
  315. package/dist/gateway/static/root/assets/heartbeat-config-api-4O1U_Oki.js +0 -1
  316. package/dist/gateway/static/root/assets/index-DI5_GsZt.css +0 -1
  317. package/dist/gateway/static/root/assets/index-S23p3aa0.js +0 -4845
  318. package/dist/gateway/static/root/assets/logs-page-BAIdbvSC.js +0 -2
  319. package/dist/gateway/static/root/assets/note-detail-page-BVDoLwec.js +0 -180
  320. package/dist/gateway/static/root/assets/note-time-COWUfBZO.js +0 -1
  321. package/dist/gateway/static/root/assets/notes-page-CI3aD7Tm.js +0 -1
  322. package/dist/gateway/static/root/assets/schema-form-CDpBshDP.js +0 -1
  323. package/dist/gateway/static/root/assets/sessions-page-DM1S4eso.js +0 -1
  324. package/dist/gateway/static/root/assets/settings-advanced-gate-Bz_nYzvB.js +0 -2
  325. package/dist/gateway/static/root/assets/settings-form-section-BxhPoWWH.js +0 -1
  326. package/dist/gateway/static/root/assets/settings-page-Ch_Sd28M.js +0 -3
  327. package/dist/gateway/static/root/assets/share-preview-page-D6u4llqD.js +0 -2
  328. package/dist/gateway/static/root/assets/skills-page-XMi76Nps.js +0 -2
  329. package/dist/gateway/static/root/assets/theme-store-BZozwAeK.js +0 -1
  330. package/dist/gateway/static/root/assets/url-DQRqHuL-.js +0 -3
  331. package/dist/gateway/static/root/assets/vendor-codemirror-DYoKfS8f.js +0 -45
  332. package/dist/gateway/static/root/assets/voice-api-key-field-CMt5Xc7_.js +0 -1
  333. package/dist/gateway/static/root/assets/workflows-page-CxFbPBed.js +0 -18
  334. package/dist/src/agent/goals/checklist-user.d.ts +0 -24
  335. package/dist/src/agent/goals/checklist-user.js +0 -92
  336. package/dist/src/agent/goals/checklist-user.js.map +0 -1
  337. package/dist/src/agent/goals/goal-run-store.d.ts +0 -19
  338. package/dist/src/agent/goals/goal-run-store.js +0 -138
  339. package/dist/src/agent/goals/goal-run-store.js.map +0 -1
  340. package/dist/src/agent/goals/goal-run-types.d.ts +0 -28
  341. package/dist/src/agent/goals/goal-run-types.js +0 -6
  342. package/dist/src/agent/goals/goal-run-types.js.map +0 -1
  343. package/dist/src/agent/goals/patch-from-user-action.d.ts +0 -20
  344. package/dist/src/agent/goals/patch-from-user-action.js +0 -103
  345. package/dist/src/agent/goals/patch-from-user-action.js.map +0 -1
  346. /package/dist/gateway/static/root/assets/{note-detail-page-D4ZIVQbk.css → block-editor-D4ZIVQbk.css} +0 -0
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  <p align="center">
9
9
  <strong>Goal Loop OS. Long-term AI in one loop.</strong><br />
10
- Local-first · Multi-surface · Workflows + Multi-agent + Extensible.
10
+ A local-first AI assistant that keeps long-term goals moving across terminal, web, desktop, mobile app, and messengers.
11
11
  </p>
12
12
 
13
13
  <p align="center">
@@ -20,6 +20,7 @@
20
20
 
21
21
  <p align="center">
22
22
  <a href="https://xopc.ai"><strong>xopc.ai</strong></a> ·
23
+ <a href="https://github.com/xopcai/xopc"><strong>GitHub</strong></a> ·
23
24
  <a href="https://xopcai.github.io/xopc/">Documentation</a> ·
24
25
  <a href="#get-started">Get started</a> ·
25
26
  <a href="https://github.com/xopcai/xopc/releases">Releases</a>
@@ -31,11 +32,29 @@
31
32
 
32
33
  ---
33
34
 
35
+ ## Built for
36
+
37
+ - **Long-running projects** — keep direction, next actions, feedback, and recalibration in one loop instead of losing context between chats.
38
+ - **One-person companies and solo builders** — use the same assistant from CLI, TUI, Web, Desktop, Telegram, WeChat, and Feishu/Lark.
39
+ - **Local-first AI workflows** — bring your own keys, mix cloud/local models, add skills/extensions, and keep data under **`~/.xopc/`**.
40
+
41
+ ---
42
+
34
43
  <a id="get-started"></a>
35
44
  <a id="quick-start"></a>
36
45
 
37
46
  ## Get started
38
47
 
48
+ ### Try in 3 minutes
49
+
50
+ ```bash
51
+ curl -fsSL https://xopc.ai/install.sh | bash
52
+ xopc onboard --quick
53
+ xopc tui --local
54
+ ```
55
+
56
+ This path starts the embedded terminal UI: no gateway, desktop app, or messenger setup required.
57
+
39
58
  ### One-liner (recommended)
40
59
 
41
60
  **Linux, macOS, WSL2, Termux**
@@ -67,6 +86,8 @@ xopc tui --local
67
86
 
68
87
  > **New here?** Start with **`xopc tui --local`** (embedded agent, no gateway). Run **`xopc gateway`** when you want the **web console** or **messengers** (Telegram, WeChat, Feishu).
69
88
 
89
+ If the first run works for you, please **[star the repo](https://github.com/xopcai/xopc)** so others can discover it.
90
+
70
91
  ### npm (already have Node.js 22+)
71
92
 
72
93
  ```bash
@@ -111,7 +132,7 @@ Windows git install: `& ([scriptblock]::Create((irm https://xopc.ai/install.ps1)
111
132
 
112
133
  - 🏠 **Your machine** — Data and config under **`~/.xopc/`**. No mandatory cloud or surprise bills.
113
134
  - 🔑 **Bring your own keys** — OpenAI, Anthropic, Google, DeepSeek, Ollama, LM Studio, vLLM, and **20+** providers. Mix cloud and local; switch catalog models in one config line. See **[Models](https://xopcai.github.io/xopc/models)**.
114
- - 📱 **One brain, every screen** — Same assistant in terminal, browser, desktop, phone, and IM. No sync layer — one system.
135
+ - 📱 **One brain, every screen** — Same assistant in terminal, browser, desktop, [mobile app](https://github.com/xopcai/xopc-app), and IM. No sync layer — one system.
115
136
  - 🧩 **Grows with you** — **`xopc skills install`** · **`xopc extensions install`** for tools, channels, and UI panels; multi-agent routing per context.
116
137
  - ⏰ **Proactive** — **Cron** for scheduled summaries and reminders; **workflows** for fan-out subagent tasks; **multi-agent** routing with isolated workspaces, tools, and prompts.
117
138
 
@@ -125,7 +146,7 @@ Windows git install: `& ([scriptblock]::Create((irm https://xopc.ai/install.ps1)
125
146
  | **CLI** | `xopc agent -i` / `xopc agent -m "…"` | Scripts and minimal TTY |
126
147
  | **Web** | `xopc gateway` → open console URL | Chat, settings, logs in the browser |
127
148
  | **Desktop** | [GitHub Releases](#desktop-app) or `pnpm run electron:build` | Native app (macOS, Windows, Linux) |
128
- | **Mobile** | Pair with your gateway ([remote access](https://xopcai.github.io/xopc/remote-access)) | Continue your loop on the move |
149
+ | **Mobile** | [xopc-app](https://github.com/xopcai/xopc-app) + gateway pairing ([mobile app](https://xopcai.github.io/xopc/mobile-app), [remote access](https://xopcai.github.io/xopc/remote-access)) | Continue your loop on iOS/Android |
129
150
  | **Messengers** | `channels.*` + gateway | Telegram, WeChat, Feishu/Lark |
130
151
 
131
152
  ---
@@ -192,7 +213,28 @@ Optional tools (e.g. browser) stay **off until enabled**; use Playwright Chromiu
192
213
  | [Architecture](https://xopcai.github.io/xopc/architecture) | How pieces fit together |
193
214
  | [Workflows](https://xopcai.github.io/xopc/workflows) | Fan-out subagents, board UI, scripts |
194
215
 
195
- Also: [Tools](https://xopcai.github.io/xopc/tools) · [Voice](https://xopcai.github.io/xopc/voice) · [Remote access](https://xopcai.github.io/xopc/remote-access)
216
+ Also: [Tools](https://xopcai.github.io/xopc/tools) · [Mobile app](https://xopcai.github.io/xopc/mobile-app) · [Voice](https://xopcai.github.io/xopc/voice) · [Remote access](https://xopcai.github.io/xopc/remote-access)
217
+
218
+ ---
219
+
220
+ ## FAQ
221
+
222
+ **Is xopc a hosted service?**
223
+ No. xopc runs on your machine. Config, workspace files, credentials, and local state live under **`~/.xopc/`** by default.
224
+
225
+ **Do I need a paid cloud model?**
226
+ No. Bring your own keys for cloud providers, or use local/model-server options such as Ollama, LM Studio, and vLLM.
227
+
228
+ **What is the fastest way to try it?**
229
+ Use **`xopc onboard --quick`** and **`xopc tui --local`**. That path avoids gateway, desktop, and messenger setup.
230
+
231
+ **How is this different from another chat UI?**
232
+ xopc is organized around goal loops: direction, next action, feedback, and recalibration across multiple surfaces.
233
+
234
+ **Can I use it from my phone or messengers?**
235
+ Yes. Start with local TUI first; when ready, run the gateway and use [xopc-app](https://github.com/xopcai/xopc-app) on iOS/Android, or configure Telegram, WeChat, or Feishu/Lark.
236
+
237
+ If this answers your concerns, please **[star xopc](https://github.com/xopcai/xopc)** so more developers can find it.
196
238
 
197
239
  ---
198
240
 
package/README.zh-CN.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  <p align="center">
9
9
  <strong>Goal Loop OS,目标持续循环。</strong><br />
10
- 本地优先 · 多端联动 · 工作流 + 多 Agent + 可扩展。
10
+ 一个本地优先的长期目标 AI 助手:在终端、网页、桌面、移动端和 IM 里持续推进目标、行动与反馈。
11
11
  </p>
12
12
 
13
13
  <p align="center">
@@ -20,6 +20,7 @@
20
20
 
21
21
  <p align="center">
22
22
  <a href="https://xopc.ai"><strong>xopc.ai</strong></a> ·
23
+ <a href="https://github.com/xopcai/xopc"><strong>GitHub</strong></a> ·
23
24
  <a href="https://xopcai.github.io/xopc/zh/">中文文档</a> ·
24
25
  <a href="#get-started">快速开始</a> ·
25
26
  <a href="https://github.com/xopcai/xopc/releases">Releases</a>
@@ -31,11 +32,29 @@
31
32
 
32
33
  ---
33
34
 
35
+ ## 适合谁
36
+
37
+ - **长期项目** —— 把方向、下一步行动、反馈和重新校准放进同一个循环,不让目标断在一次次聊天里。
38
+ - **独立开发者 / One Person Company** —— 同一个助手覆盖 CLI、TUI、网页、桌面、Telegram、微信和飞书/Lark。
39
+ - **本地优先 AI 工作流** —— 自带 API Key,云端/本地模型混用,支持 skills/extensions,数据默认在 **`~/.xopc/`**。
40
+
41
+ ---
42
+
34
43
  <a id="get-started"></a>
35
44
  <a id="quick-start"></a>
36
45
 
37
46
  ## 快速开始
38
47
 
48
+ ### 3 分钟试用
49
+
50
+ ```bash
51
+ curl -fsSL https://xopc.ai/install.sh | bash
52
+ xopc onboard --quick
53
+ xopc tui --local
54
+ ```
55
+
56
+ 这条路径会启动本地嵌入式 TUI,不需要先配置网关、桌面端或 IM。
57
+
39
58
  ### 一键安装(推荐)
40
59
 
41
60
  **Linux、macOS、WSL2、Termux**
@@ -67,6 +86,8 @@ xopc tui --local
67
86
 
68
87
  > **第一次用?** 先跑 **`xopc tui --local`**(本地嵌入式对话,不用网关)。需要 **网页控制台** 或 **Telegram / 微信 / 飞书** 时再执行 **`xopc gateway`**。
69
88
 
89
+ 如果首次运行顺利,欢迎 **[给仓库点个 Star](https://github.com/xopcai/xopc)**,帮助更多人发现这个项目。
90
+
70
91
  ### npm(已具备 Node.js 22+)
71
92
 
72
93
  ```bash
@@ -111,7 +132,7 @@ Windows 源码安装:`& ([scriptblock]::Create((irm https://xopc.ai/install.ps
111
132
 
112
133
  - 🏠 **你的机器** — 配置与数据在 **`~/.xopc/`**,无强制云端、无意外账单。
113
134
  - 🔑 **自带钥匙** — OpenAI、Anthropic、Google、DeepSeek、Ollama、LM Studio、vLLM 等 **20+** 厂商;云端本地可混用,一行配置切换目录模型。详见 **[模型](https://xopcai.github.io/xopc/zh/models)**。
114
- - 📱 **一个大脑,处处可用** — 终端、浏览器、桌面、手机、IM 同一套助手,无需另做同步。
135
+ - 📱 **一个大脑,处处可用** — 终端、浏览器、桌面、[移动端 app](https://github.com/xopcai/xopc-app)、IM 同一套助手,无需另做同步。
115
136
  - 🧩 **随你长大** — **`xopc skills install`** · **`xopc extensions install`** 扩展工具、频道与 UI;多 Agent 按场景隔离。
116
137
  - ⏰ **能主动干活** — **Cron** 定时摘要与提醒;**工作流**扇出多子 Agent 任务;**多 Agent** 各自工作区、工具与系统提示词。
117
138
 
@@ -125,7 +146,7 @@ Windows 源码安装:`& ([scriptblock]::Create((irm https://xopc.ai/install.ps
125
146
  | **CLI** | `xopc agent -i` / `xopc agent -m "…"` | 脚本、最小终端环境 |
126
147
  | **网页** | `xopc gateway` → 打开控制台地址 | 聊天、设置、日志 |
127
148
  | **桌面** | [GitHub Releases](#desktop-app) 或 `pnpm run electron:build` | 原生应用(macOS / Windows / Linux) |
128
- | **手机** | 与网关配对([远程访问](https://xopcai.github.io/xopc/zh/remote-access)) | 在路上也能继续推进循环 |
149
+ | **手机** | [xopc-app](https://github.com/xopcai/xopc-app) + 网关配对([移动端 app](https://xopcai.github.io/xopc/zh/mobile-app)、[远程访问](https://xopcai.github.io/xopc/zh/remote-access)) | iOS/Android 上继续推进循环 |
129
150
  | **即时通讯** | 配置 `channels.*` 并启动网关 | Telegram、微信、飞书/Lark |
130
151
 
131
152
  ---
@@ -192,7 +213,28 @@ xopc extensions dev ./my-extension
192
213
  | [架构](https://xopcai.github.io/xopc/zh/architecture) | 整体结构 |
193
214
  | [工作流](https://xopcai.github.io/xopc/zh/workflows) | 扇出子 Agent、看板、脚本 |
194
215
 
195
- 更多:[工具](https://xopcai.github.io/xopc/zh/tools) · [语音](https://xopcai.github.io/xopc/zh/voice) · [远程访问](https://xopcai.github.io/xopc/zh/remote-access)
216
+ 更多:[工具](https://xopcai.github.io/xopc/zh/tools) · [移动端 app](https://xopcai.github.io/xopc/zh/mobile-app) · [语音](https://xopcai.github.io/xopc/zh/voice) · [远程访问](https://xopcai.github.io/xopc/zh/remote-access)
217
+
218
+ ---
219
+
220
+ ## 常见问题
221
+
222
+ **xopc 是云服务吗?**
223
+ 不是。xopc 跑在你的机器上,配置、工作区文件、凭据和本地状态默认都在 **`~/.xopc/`**。
224
+
225
+ **必须使用付费云端模型吗?**
226
+ 不必须。你可以自带云厂商 API Key,也可以使用 Ollama、LM Studio、vLLM 等本地/自部署模型服务。
227
+
228
+ **最快怎么试用?**
229
+ 执行 **`xopc onboard --quick`** 和 **`xopc tui --local`**。这条路径不会先要求你配置网关、桌面端或 IM。
230
+
231
+ **它和普通聊天 UI 有什么区别?**
232
+ xopc 围绕目标循环组织:方向、下一步、反馈、重新校准,并且同一个助手可以跨多个入口继续工作。
233
+
234
+ **能在手机或 IM 里用吗?**
235
+ 可以。建议先用本地 TUI 试用;确认方向后,再启动 gateway,用 [xopc-app](https://github.com/xopcai/xopc-app) 连接 iOS/Android,或配置 Telegram、微信、飞书/Lark。
236
+
237
+ 如果这些回答解决了你的顾虑,欢迎 **[给 xopc 点个 Star](https://github.com/xopcai/xopc)**,帮助更多开发者看到它。
196
238
 
197
239
  ---
198
240
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "xopc Browser Bridge",
4
- "version": "0.0.109",
4
+ "version": "0.0.111",
5
5
  "description": "Browser automation bridge for xopc — executes commands in Chrome via the local xopc daemon.",
6
6
  "permissions": [
7
7
  "debugger",
@@ -0,0 +1,30 @@
1
+ import { resolveStandardAllowFromPath, resolveStandardPairingPath } from "../../../src/channels/pairing/paths.js";
2
+ import { registerPairingChannelResolver } from "../../../src/channels/pairing/pairing-service.js";
3
+ //#region extensions/feishu/src/pairing-config-resolver.ts
4
+ function feishuBlock(config) {
5
+ const ch = config?.channels?.feishu;
6
+ return ch && typeof ch === "object" && !Array.isArray(ch) ? ch : null;
7
+ }
8
+ registerPairingChannelResolver("feishu", {
9
+ resolvePaths(accountId) {
10
+ return {
11
+ pairingPath: resolveStandardPairingPath("feishu", accountId),
12
+ allowPath: resolveStandardAllowFromPath("feishu", accountId)
13
+ };
14
+ },
15
+ resolveAccountIds(config) {
16
+ const block = feishuBlock(config);
17
+ const accounts = block?.accounts;
18
+ if (accounts && typeof accounts === "object" && !Array.isArray(accounts)) {
19
+ const keys = Object.keys(accounts).sort();
20
+ if (keys.length > 0) return keys;
21
+ }
22
+ const appId = typeof block?.appId === "string" ? block.appId.trim() : "";
23
+ const appSecret = typeof block?.appSecret === "string" ? block.appSecret.trim() : "";
24
+ return appId && appSecret ? ["default"] : [];
25
+ }
26
+ });
27
+ //#endregion
28
+ export {};
29
+
30
+ //# sourceMappingURL=pairing-config-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pairing-config-resolver.js","names":[],"sources":["../../../../extensions/feishu/src/pairing-config-resolver.ts"],"sourcesContent":["import type { Config } from '@xopcai/xopc/config/schema.js';\nimport {\n registerPairingChannelResolver,\n resolveStandardAllowFromPath,\n resolveStandardPairingPath,\n} from '@xopcai/xopc/channels/pairing/index.js';\n\nfunction feishuBlock(config: Config | undefined): Record<string, unknown> | null {\n const ch = config?.channels?.feishu;\n return ch && typeof ch === 'object' && !Array.isArray(ch) ? (ch as Record<string, unknown>) : null;\n}\n\nregisterPairingChannelResolver('feishu', {\n resolvePaths(accountId) {\n return {\n pairingPath: resolveStandardPairingPath('feishu', accountId),\n allowPath: resolveStandardAllowFromPath('feishu', accountId),\n };\n },\n resolveAccountIds(config) {\n const block = feishuBlock(config);\n const accounts = block?.accounts;\n if (accounts && typeof accounts === 'object' && !Array.isArray(accounts)) {\n const keys = Object.keys(accounts as Record<string, unknown>).sort();\n if (keys.length > 0) return keys;\n }\n const appId = typeof block?.appId === 'string' ? block.appId.trim() : '';\n const appSecret = typeof block?.appSecret === 'string' ? block.appSecret.trim() : '';\n return appId && appSecret ? ['default'] : [];\n },\n});\n"],"mappings":";;;AAOA,SAAS,YAAY,QAA4D;CAC/E,MAAM,KAAK,QAAQ,UAAU;AAC7B,QAAO,MAAM,OAAO,OAAO,YAAY,CAAC,MAAM,QAAQ,GAAG,GAAI,KAAiC;;AAGhG,+BAA+B,UAAU;CACvC,aAAa,WAAW;AACtB,SAAO;GACL,aAAa,2BAA2B,UAAU,UAAU;GAC5D,WAAW,6BAA6B,UAAU,UAAU;GAC7D;;CAEH,kBAAkB,QAAQ;EACxB,MAAM,QAAQ,YAAY,OAAO;EACjC,MAAM,WAAW,OAAO;AACxB,MAAI,YAAY,OAAO,aAAa,YAAY,CAAC,MAAM,QAAQ,SAAS,EAAE;GACxE,MAAM,OAAO,OAAO,KAAK,SAAoC,CAAC,MAAM;AACpE,OAAI,KAAK,SAAS,EAAG,QAAO;;EAE9B,MAAM,QAAQ,OAAO,OAAO,UAAU,WAAW,MAAM,MAAM,MAAM,GAAG;EACtE,MAAM,YAAY,OAAO,OAAO,cAAc,WAAW,MAAM,UAAU,MAAM,GAAG;AAClF,SAAO,SAAS,YAAY,CAAC,UAAU,GAAG,EAAE;;CAE/C,CAAC"}
@@ -4,6 +4,7 @@
4
4
  * This is intentionally decomposed into small modules so we can grow toward
5
5
  * openclaw `extensions/feishu` parity without turning `plugin.ts` into a monolith.
6
6
  */
7
+ import './pairing-config-resolver.js';
7
8
  import type { Config } from '@xopcai/xopc/config/schema.js';
8
9
  import type { ChannelCapabilities, ChannelDoctorAdapter, ChannelMessageActionAdapter, ChannelOutboundAdapter, ChannelPlugin, ChannelPluginDefaults, ChannelPluginInitOptions, ChannelPluginReloadMeta, ChannelPluginStartOptions, ChannelSecurityAdapter, ChannelStatusAdapter } from '@xopcai/xopc/channels/plugin-types.js';
9
10
  import type { ChannelCliLoginAdapter, ChannelRuntimeActionAdapter, ChannelOnboardAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';
@@ -2,6 +2,7 @@ import { createLogger } from "../../../src/utils/logger/index.js";
2
2
  import { init_logger } from "../../../src/utils/logger.js";
3
3
  import { evaluateAccess, resolveDmPolicy, resolveGroupPolicy } from "../../../src/channels/security.js";
4
4
  import { createStandardPairingAdapter } from "../../../src/channels/pairing/pairing-store-adapter.js";
5
+ import "./pairing-config-resolver.js";
5
6
  import { FeishuConfigSchema } from "./schema/config-schema.js";
6
7
  import { listFeishuAccountIds, resolveFeishuAccount } from "./state/accounts.js";
7
8
  import { createFeishuOutboundAdapter } from "./outbound/outbound-adapter.js";
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","names":[],"sources":["../../../../extensions/feishu/src/plugin.ts"],"sourcesContent":["/**\n * Feishu/Lark channel plugin (Socket Mode first).\n *\n * This is intentionally decomposed into small modules so we can grow toward\n * openclaw `extensions/feishu` parity without turning `plugin.ts` into a monolith.\n */\n\nimport { isDeepStrictEqual } from 'node:util';\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport type { MessageBus } from '@xopcai/xopc/infra/bus/index.js';\nimport type {\n ChannelCapabilities,\n ChannelDoctorAdapter,\n ChannelMessageActionAdapter,\n ChannelOutboundAdapter,\n ChannelPlugin,\n ChannelPluginDefaults,\n ChannelPluginInitOptions,\n ChannelPluginReloadMeta,\n ChannelPluginStartOptions,\n ChannelSecurityAdapter,\n ChannelSecurityContext,\n ChannelStatusAdapter,\n ChatType,\n} from '@xopcai/xopc/channels/plugin-types.js';\nimport type {\n ChannelCliLoginAdapter,\n ChannelRuntimeActionAdapter,\n ChannelOnboardAdapter,\n} from '@xopcai/xopc/channels/plugins/types.adapters.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\nimport { evaluateAccess, resolveDmPolicy, resolveGroupPolicy } from '@xopcai/xopc/channels/security.js';\nimport { createStandardPairingAdapter } from '@xopcai/xopc/channels/pairing/pairing-store-adapter.js';\n\nimport { FeishuConfigSchema, type FeishuConfig } from './schema/config-schema.js';\nimport { listFeishuAccountIds, resolveFeishuAccount, type ResolvedFeishuAccount } from './state/accounts.js';\nimport { createFeishuOutboundAdapter } from './outbound/outbound-adapter.js';\nimport { createFeishuStatusAdapter } from './status/status-adapter.js';\nimport { createFeishuDoctorAdapter } from './status/doctor.js';\nimport { feishuConfigSurface } from './ui/config-surface.js';\nimport { createFeishuStreamingAdapter } from './streaming/streaming-adapter.js';\nimport { readFrameworkAllowFromList } from './auth/pairing.js';\nimport { feishuGatewaySetupActions } from './adapters/gateway-setup.js';\nimport { handleFeishuChannelMessageAction } from './actions/message-action-handler.js';\nimport { createFeishuInboundPipeline, type FeishuInboundWork } from './transport/reliability/inbound-pipeline.js';\n\nconst log = createLogger('FeishuPlugin');\n\nexport class FeishuChannelPlugin implements ChannelPlugin<ResolvedFeishuAccount> {\n readonly id = 'feishu' as const;\n\n readonly reload: ChannelPluginReloadMeta = {\n configPrefixes: ['channels.feishu'],\n };\n\n readonly meta = {\n id: 'feishu',\n label: 'Feishu',\n selectionLabel: 'Feishu/Lark (飞书)',\n docsPath: '/channels/feishu',\n blurb: 'Feishu/Lark enterprise messaging (Socket Mode).',\n order: 4,\n deferConnectUntilAfterListen: true,\n } as const;\n\n readonly capabilities: ChannelCapabilities = {\n chatTypes: ['direct', 'channel'] as ChatType[],\n reactions: true,\n threads: true,\n media: true,\n polls: false,\n nativeCommands: false,\n blockStreaming: false,\n edit: true,\n reply: true,\n } as any;\n\n readonly defaults: ChannelPluginDefaults = {\n queue: { debounceMs: 350 },\n outbound: { textChunkLimit: 4000 },\n streaming: {\n blockStreamingCoalesce: {\n minChars: 200,\n idleMs: 2500,\n },\n },\n };\n\n readonly configSchema = {\n schema: {},\n validate: (raw: unknown) => {\n const r = FeishuConfigSchema.safeParse(raw);\n return r.success ? { ok: true as const } : { ok: false as const, errors: [r.error.message] };\n },\n };\n\n readonly configSurface = feishuConfigSurface;\n\n readonly pairing = createStandardPairingAdapter('feishu');\n\n onboard: ChannelOnboardAdapter = {\n isConfigured: (config) => {\n const feishu = config.channels?.feishu as Record<string, unknown> | undefined;\n const appId = typeof feishu?.appId === 'string' ? feishu.appId.trim() : '';\n const appSecret = typeof feishu?.appSecret === 'string' ? feishu.appSecret.trim() : '';\n return feishu?.enabled === true && Boolean(appId && appSecret);\n },\n configure: async (config) => {\n const { feishuOnboardAdapter } = await import('./adapters/onboard-cli.js');\n return feishuOnboardAdapter.configure(config);\n },\n };\n\n readonly cliLogin: ChannelCliLoginAdapter = {\n runLogin: async (params) => {\n const { feishuCliLoginAdapter } = await import('./adapters/cli-login.js');\n return feishuCliLoginAdapter.runLogin(params);\n },\n };\n\n readonly runtimeActions: ChannelRuntimeActionAdapter = feishuGatewaySetupActions;\n\n private bus!: MessageBus;\n private cfg!: Config;\n private abortControllers = new Map<string, AbortController>();\n private inboundPipeline?: ReturnType<typeof createFeishuInboundPipeline>;\n /** Unregister fn for the workflow-progress capability registered against the global broker. */\n private workflowProgressUnregister: (() => void) | null = null;\n\n config = {\n listAccountIds: (cfg: Config) => listFeishuAccountIds(cfg),\n resolveAccount: (cfg: Config, accountId?: string | null) => resolveFeishuAccount(cfg, accountId),\n isConfigured: async (account: ResolvedFeishuAccount) => account.configured,\n describeAccount: (account: ResolvedFeishuAccount) => ({\n accountId: account.accountId,\n channelId: 'feishu',\n enabled: account.enabled,\n configured: account.configured,\n status: account.configured ? undefined : 'unconfigured',\n }),\n };\n\n security: ChannelSecurityAdapter<ResolvedFeishuAccount> = {\n resolveDmPolicy: ({ account }: { account: ResolvedFeishuAccount }) =>\n resolveDmPolicy(account.dmPolicy, 'open'),\n resolveGroupPolicy: ({ account }: { account: ResolvedFeishuAccount }) =>\n resolveGroupPolicy(account.groupPolicy, 'allowlist'),\n checkAccess: (ctx: ChannelSecurityContext, account: ResolvedFeishuAccount, _cfg: Config) => {\n const isDm = !ctx.isGroup;\n const frameworkAllowFrom = readFrameworkAllowFromList(account.accountId);\n const baseAllowFrom = isDm ? account.allowFrom : account.groupAllowFrom ?? account.allowFrom;\n const allowFrom = [...(baseAllowFrom ?? []), ...frameworkAllowFrom];\n if (isDm) {\n return evaluateAccess({\n context: {\n channel: 'feishu',\n accountId: account.accountId,\n chatId: ctx.chatId,\n senderId: ctx.senderId,\n senderName: ctx.senderName,\n isGroup: false,\n isDm: true,\n },\n dmPolicy: account.dmPolicy,\n allowFrom,\n });\n }\n return evaluateAccess({\n context: {\n channel: 'feishu',\n accountId: account.accountId,\n chatId: ctx.chatId,\n senderId: ctx.senderId,\n senderName: ctx.senderName,\n isGroup: true,\n isDm: false,\n },\n groupPolicy: account.groupPolicy,\n allowFrom,\n });\n },\n };\n\n outbound: ChannelOutboundAdapter = createFeishuOutboundAdapter();\n\n streaming = createFeishuStreamingAdapter(() => this.cfg);\n\n status: ChannelStatusAdapter<ResolvedFeishuAccount> = createFeishuStatusAdapter();\n\n doctor: ChannelDoctorAdapter = createFeishuDoctorAdapter();\n\n directory = {\n resolveDisplayName: async (params) => {\n const { createFeishuDirectoryAdapter } = await import('./directory/directory-adapter.js');\n return createFeishuDirectoryAdapter().resolveDisplayName?.(params);\n },\n };\n\n actions: ChannelMessageActionAdapter = {\n handleAction: handleFeishuChannelMessageAction,\n };\n\n agentTools = [\n {\n name: 'feishu_read',\n description: 'Read a Feishu message by messageId (debug/utility).',\n execute: async (toolCtx, args) => {\n const messageId = typeof (args as any)?.messageId === 'string' ? (args as any).messageId : toolCtx.messageId;\n if (!messageId) throw new Error('feishu_read requires messageId');\n const { getMessageFeishu } = await import('./outbound/actions.js');\n return await getMessageFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId });\n },\n },\n {\n name: 'feishu_edit',\n description: 'Edit a Feishu message by messageId.',\n execute: async (toolCtx, args) => {\n const messageId = typeof (args as any)?.messageId === 'string' ? (args as any).messageId : toolCtx.messageId;\n const text = typeof (args as any)?.text === 'string' ? (args as any).text : '';\n if (!messageId) throw new Error('feishu_edit requires messageId');\n if (!text.trim()) throw new Error('feishu_edit requires text');\n const { editMessageFeishu } = await import('./outbound/actions.js');\n return await editMessageFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId, text });\n },\n },\n {\n name: 'feishu_scopes_probe',\n description: 'Probe Feishu credentials/scopes (placeholder for full docs/wiki/drive tools).',\n execute: async (toolCtx) => {\n const { feishuWhoAmI } = await import('./tools/tools.js');\n return await feishuWhoAmI({ cfg: this.cfg, accountId: toolCtx.accountId });\n },\n },\n {\n name: 'feishu_react',\n description: 'Add/remove/list reactions for a message.',\n execute: async (toolCtx, args) => {\n const a = args as any;\n const messageId = typeof a?.messageId === 'string' ? a.messageId : toolCtx.messageId;\n if (!messageId) throw new Error('feishu_react requires messageId');\n\n const account = resolveFeishuAccount(this.cfg, toolCtx.accountId ?? 'default');\n const enabled = (account.actions as any)?.reactions !== false;\n if (!enabled) {\n throw new Error('Feishu reactions are disabled via channels.feishu.actions.reactions');\n }\n\n if (a?.list === true) {\n const { listReactionsFeishu } = await import('./outbound/actions.js');\n return await listReactionsFeishu({\n cfg: this.cfg,\n accountId: toolCtx.accountId,\n messageId,\n emojiType: typeof a?.emojiType === 'string' ? a.emojiType : undefined,\n });\n }\n\n if (a?.remove === true) {\n const reactionId = typeof a?.reactionId === 'string' ? a.reactionId : '';\n if (!reactionId) throw new Error('feishu_react remove requires reactionId');\n const { removeReactionFeishu } = await import('./outbound/actions.js');\n return await removeReactionFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId, reactionId });\n }\n\n const emojiType = typeof a?.emojiType === 'string' ? a.emojiType : '';\n if (!emojiType) throw new Error('feishu_react requires emojiType');\n const { addReactionFeishu } = await import('./outbound/actions.js');\n return await addReactionFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId, emojiType });\n },\n },\n {\n name: 'feishu_pins',\n description: 'Pin/unpin/list pins for a chat.',\n execute: async (toolCtx, args) => {\n const a = args as any;\n const action = typeof a?.action === 'string' ? a.action : '';\n if (action === 'list') {\n const chatId = typeof a?.chatId === 'string' ? a.chatId : toolCtx.chatId;\n if (!chatId) throw new Error('feishu_pins list requires chatId');\n const { listPinsFeishu } = await import('./outbound/actions.js');\n return await listPinsFeishu({\n cfg: this.cfg,\n accountId: toolCtx.accountId,\n chatId,\n startTime: typeof a?.startTime === 'string' ? a.startTime : undefined,\n endTime: typeof a?.endTime === 'string' ? a.endTime : undefined,\n pageSize: typeof a?.pageSize === 'number' ? a.pageSize : undefined,\n pageToken: typeof a?.pageToken === 'string' ? a.pageToken : undefined,\n });\n }\n const messageId = typeof a?.messageId === 'string' ? a.messageId : toolCtx.messageId;\n if (!messageId) throw new Error('feishu_pins requires messageId');\n if (action === 'pin') {\n const { pinMessageFeishu } = await import('./outbound/actions.js');\n return await pinMessageFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId });\n }\n if (action === 'unpin') {\n const { unpinMessageFeishu } = await import('./outbound/actions.js');\n return await unpinMessageFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId });\n }\n throw new Error('feishu_pins requires action: pin | unpin | list');\n },\n },\n ];\n\n async init(options: ChannelPluginInitOptions): Promise<void> {\n this.bus = options.bus;\n this.cfg = options.config;\n const defaultDebounce = this.defaults.queue?.debounceMs ?? 350;\n this.inboundPipeline = createFeishuInboundPipeline({\n bus: this.bus,\n defaultDebounceMs: defaultDebounce,\n onError: (err, items) => {\n log.error({ err, count: items.length }, 'Feishu inbound pipeline flush failed');\n },\n });\n\n await this.registerWorkflowProgressCapability();\n\n log.debug('Feishu plugin initialized');\n }\n\n private async registerWorkflowProgressCapability(): Promise<void> {\n // Lazy-load workflow progress so Feishu runtime startup does not pull the\n // agent/workflow/provider graph into Electron's dynamic extension path.\n const [{ getWorkflowProgressBroker }, { createFeishuWorkflowProgressCapability }] = await Promise.all([\n import('@xopcai/xopc/agent/workflow/progress-broker.js'),\n import('./workflow-progress.js'),\n ]);\n this.workflowProgressUnregister = getWorkflowProgressBroker().registerChannel(\n createFeishuWorkflowProgressCapability({ getConfig: () => this.cfg }),\n );\n }\n\n async start(options?: ChannelPluginStartOptions): Promise<void> {\n const section = this.cfg.channels?.feishu as FeishuConfig | undefined;\n if (!section || section.enabled !== true) {\n return;\n }\n\n const ids = options?.accountId ? [options.accountId] : listFeishuAccountIds(this.cfg);\n for (const accountId of ids) {\n const account = resolveFeishuAccount(this.cfg, accountId);\n if (!account.enabled || !account.configured) continue;\n if (this.abortControllers.has(accountId)) continue;\n\n const ac = new AbortController();\n this.abortControllers.set(accountId, ac);\n\n const defaultDebounce = this.defaults.queue?.debounceMs ?? 350;\n const enqueueInbound = (work: FeishuInboundWork) => {\n const p = this.inboundPipeline;\n if (!p) {\n return this.bus.publishInbound(work.inbound);\n }\n return p.enqueue(work);\n };\n\n const monitorDeps = {\n account,\n config: this.cfg,\n enqueueInbound,\n inboundDebounceDefaultMs: defaultDebounce,\n abortSignal: ac.signal,\n security: {\n checkAccess: (ctx: ChannelSecurityContext) => this.security.checkAccess?.(ctx, account, this.cfg),\n },\n };\n const runner =\n account.connectionMode === 'webhook'\n ? (await import('./transport/webhook/monitor.js')).createFeishuWebhookMonitor(monitorDeps)\n : (await import('./transport/socket-mode/monitor.js')).createFeishuSocketModeMonitor(monitorDeps);\n\n void runner.run().catch((err) => {\n if ((err as { name?: string } | undefined)?.name === 'AbortError') {\n log.debug({ accountId }, 'Feishu monitor stopped');\n return;\n }\n log.error({ err, accountId }, 'Feishu monitor exited with error');\n });\n\n log.info({ accountId, mode: account.connectionMode }, 'Feishu monitor started');\n }\n }\n\n async stop(accountId?: string): Promise<void> {\n try {\n await this.inboundPipeline?.flushAll();\n } catch (err) {\n log.warn({ err }, 'Feishu inbound pipeline flushAll failed');\n }\n const ids = accountId ? [accountId] : [...this.abortControllers.keys()];\n for (const id of ids) {\n const ac = this.abortControllers.get(id);\n if (ac) {\n ac.abort();\n this.abortControllers.delete(id);\n }\n }\n // Only unregister the broker capability on a full stop. Per-account stops\n // still leave other accounts capable of delivering progress through the\n // same registered cap.\n if (!accountId) {\n this.workflowProgressUnregister?.();\n this.workflowProgressUnregister = null;\n }\n }\n\n channelIsRunning(cfg: Config): boolean {\n const ids = listFeishuAccountIds(cfg);\n return ids.some((id) => {\n const a = resolveFeishuAccount(cfg, id);\n return a.enabled !== false && a.configured && this.abortControllers.has(id);\n });\n }\n\n async onConfigUpdated(cfg: Config): Promise<void> {\n const prev = this.cfg.channels?.feishu as unknown;\n const next = cfg.channels?.feishu as { enabled?: boolean } | undefined;\n const channelOff = !next || next.enabled !== true;\n\n if (channelOff) {\n this.cfg = cfg;\n await this.stop();\n return;\n }\n\n this.cfg = cfg;\n\n if (isDeepStrictEqual(prev, next) && this.channelIsRunning(cfg)) {\n return;\n }\n\n await this.stop();\n await this.start();\n }\n}\n\nexport const feishuPlugin = new FeishuChannelPlugin();\n"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.js","names":[],"sources":["../../../../extensions/feishu/src/plugin.ts"],"sourcesContent":["/**\n * Feishu/Lark channel plugin (Socket Mode first).\n *\n * This is intentionally decomposed into small modules so we can grow toward\n * openclaw `extensions/feishu` parity without turning `plugin.ts` into a monolith.\n */\n\nimport { isDeepStrictEqual } from 'node:util';\n\nimport './pairing-config-resolver.js';\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport type { MessageBus } from '@xopcai/xopc/infra/bus/index.js';\nimport type {\n ChannelCapabilities,\n ChannelDoctorAdapter,\n ChannelMessageActionAdapter,\n ChannelOutboundAdapter,\n ChannelPlugin,\n ChannelPluginDefaults,\n ChannelPluginInitOptions,\n ChannelPluginReloadMeta,\n ChannelPluginStartOptions,\n ChannelSecurityAdapter,\n ChannelSecurityContext,\n ChannelStatusAdapter,\n ChatType,\n} from '@xopcai/xopc/channels/plugin-types.js';\nimport type {\n ChannelCliLoginAdapter,\n ChannelRuntimeActionAdapter,\n ChannelOnboardAdapter,\n} from '@xopcai/xopc/channels/plugins/types.adapters.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\nimport { evaluateAccess, resolveDmPolicy, resolveGroupPolicy } from '@xopcai/xopc/channels/security.js';\nimport { createStandardPairingAdapter } from '@xopcai/xopc/channels/pairing/pairing-store-adapter.js';\n\nimport { FeishuConfigSchema, type FeishuConfig } from './schema/config-schema.js';\nimport { listFeishuAccountIds, resolveFeishuAccount, type ResolvedFeishuAccount } from './state/accounts.js';\nimport { createFeishuOutboundAdapter } from './outbound/outbound-adapter.js';\nimport { createFeishuStatusAdapter } from './status/status-adapter.js';\nimport { createFeishuDoctorAdapter } from './status/doctor.js';\nimport { feishuConfigSurface } from './ui/config-surface.js';\nimport { createFeishuStreamingAdapter } from './streaming/streaming-adapter.js';\nimport { readFrameworkAllowFromList } from './auth/pairing.js';\nimport { feishuGatewaySetupActions } from './adapters/gateway-setup.js';\nimport { handleFeishuChannelMessageAction } from './actions/message-action-handler.js';\nimport { createFeishuInboundPipeline, type FeishuInboundWork } from './transport/reliability/inbound-pipeline.js';\n\nconst log = createLogger('FeishuPlugin');\n\nexport class FeishuChannelPlugin implements ChannelPlugin<ResolvedFeishuAccount> {\n readonly id = 'feishu' as const;\n\n readonly reload: ChannelPluginReloadMeta = {\n configPrefixes: ['channels.feishu'],\n };\n\n readonly meta = {\n id: 'feishu',\n label: 'Feishu',\n selectionLabel: 'Feishu/Lark (飞书)',\n docsPath: '/channels/feishu',\n blurb: 'Feishu/Lark enterprise messaging (Socket Mode).',\n order: 4,\n deferConnectUntilAfterListen: true,\n } as const;\n\n readonly capabilities: ChannelCapabilities = {\n chatTypes: ['direct', 'channel'] as ChatType[],\n reactions: true,\n threads: true,\n media: true,\n polls: false,\n nativeCommands: false,\n blockStreaming: false,\n edit: true,\n reply: true,\n } as any;\n\n readonly defaults: ChannelPluginDefaults = {\n queue: { debounceMs: 350 },\n outbound: { textChunkLimit: 4000 },\n streaming: {\n blockStreamingCoalesce: {\n minChars: 200,\n idleMs: 2500,\n },\n },\n };\n\n readonly configSchema = {\n schema: {},\n validate: (raw: unknown) => {\n const r = FeishuConfigSchema.safeParse(raw);\n return r.success ? { ok: true as const } : { ok: false as const, errors: [r.error.message] };\n },\n };\n\n readonly configSurface = feishuConfigSurface;\n\n readonly pairing = createStandardPairingAdapter('feishu');\n\n onboard: ChannelOnboardAdapter = {\n isConfigured: (config) => {\n const feishu = config.channels?.feishu as Record<string, unknown> | undefined;\n const appId = typeof feishu?.appId === 'string' ? feishu.appId.trim() : '';\n const appSecret = typeof feishu?.appSecret === 'string' ? feishu.appSecret.trim() : '';\n return feishu?.enabled === true && Boolean(appId && appSecret);\n },\n configure: async (config) => {\n const { feishuOnboardAdapter } = await import('./adapters/onboard-cli.js');\n return feishuOnboardAdapter.configure(config);\n },\n };\n\n readonly cliLogin: ChannelCliLoginAdapter = {\n runLogin: async (params) => {\n const { feishuCliLoginAdapter } = await import('./adapters/cli-login.js');\n return feishuCliLoginAdapter.runLogin(params);\n },\n };\n\n readonly runtimeActions: ChannelRuntimeActionAdapter = feishuGatewaySetupActions;\n\n private bus!: MessageBus;\n private cfg!: Config;\n private abortControllers = new Map<string, AbortController>();\n private inboundPipeline?: ReturnType<typeof createFeishuInboundPipeline>;\n /** Unregister fn for the workflow-progress capability registered against the global broker. */\n private workflowProgressUnregister: (() => void) | null = null;\n\n config = {\n listAccountIds: (cfg: Config) => listFeishuAccountIds(cfg),\n resolveAccount: (cfg: Config, accountId?: string | null) => resolveFeishuAccount(cfg, accountId),\n isConfigured: async (account: ResolvedFeishuAccount) => account.configured,\n describeAccount: (account: ResolvedFeishuAccount) => ({\n accountId: account.accountId,\n channelId: 'feishu',\n enabled: account.enabled,\n configured: account.configured,\n status: account.configured ? undefined : 'unconfigured',\n }),\n };\n\n security: ChannelSecurityAdapter<ResolvedFeishuAccount> = {\n resolveDmPolicy: ({ account }: { account: ResolvedFeishuAccount }) =>\n resolveDmPolicy(account.dmPolicy, 'open'),\n resolveGroupPolicy: ({ account }: { account: ResolvedFeishuAccount }) =>\n resolveGroupPolicy(account.groupPolicy, 'allowlist'),\n checkAccess: (ctx: ChannelSecurityContext, account: ResolvedFeishuAccount, _cfg: Config) => {\n const isDm = !ctx.isGroup;\n const frameworkAllowFrom = readFrameworkAllowFromList(account.accountId);\n const baseAllowFrom = isDm ? account.allowFrom : account.groupAllowFrom ?? account.allowFrom;\n const allowFrom = [...(baseAllowFrom ?? []), ...frameworkAllowFrom];\n if (isDm) {\n return evaluateAccess({\n context: {\n channel: 'feishu',\n accountId: account.accountId,\n chatId: ctx.chatId,\n senderId: ctx.senderId,\n senderName: ctx.senderName,\n isGroup: false,\n isDm: true,\n },\n dmPolicy: account.dmPolicy,\n allowFrom,\n });\n }\n return evaluateAccess({\n context: {\n channel: 'feishu',\n accountId: account.accountId,\n chatId: ctx.chatId,\n senderId: ctx.senderId,\n senderName: ctx.senderName,\n isGroup: true,\n isDm: false,\n },\n groupPolicy: account.groupPolicy,\n allowFrom,\n });\n },\n };\n\n outbound: ChannelOutboundAdapter = createFeishuOutboundAdapter();\n\n streaming = createFeishuStreamingAdapter(() => this.cfg);\n\n status: ChannelStatusAdapter<ResolvedFeishuAccount> = createFeishuStatusAdapter();\n\n doctor: ChannelDoctorAdapter = createFeishuDoctorAdapter();\n\n directory = {\n resolveDisplayName: async (params) => {\n const { createFeishuDirectoryAdapter } = await import('./directory/directory-adapter.js');\n return createFeishuDirectoryAdapter().resolveDisplayName?.(params);\n },\n };\n\n actions: ChannelMessageActionAdapter = {\n handleAction: handleFeishuChannelMessageAction,\n };\n\n agentTools = [\n {\n name: 'feishu_read',\n description: 'Read a Feishu message by messageId (debug/utility).',\n execute: async (toolCtx, args) => {\n const messageId = typeof (args as any)?.messageId === 'string' ? (args as any).messageId : toolCtx.messageId;\n if (!messageId) throw new Error('feishu_read requires messageId');\n const { getMessageFeishu } = await import('./outbound/actions.js');\n return await getMessageFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId });\n },\n },\n {\n name: 'feishu_edit',\n description: 'Edit a Feishu message by messageId.',\n execute: async (toolCtx, args) => {\n const messageId = typeof (args as any)?.messageId === 'string' ? (args as any).messageId : toolCtx.messageId;\n const text = typeof (args as any)?.text === 'string' ? (args as any).text : '';\n if (!messageId) throw new Error('feishu_edit requires messageId');\n if (!text.trim()) throw new Error('feishu_edit requires text');\n const { editMessageFeishu } = await import('./outbound/actions.js');\n return await editMessageFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId, text });\n },\n },\n {\n name: 'feishu_scopes_probe',\n description: 'Probe Feishu credentials/scopes (placeholder for full docs/wiki/drive tools).',\n execute: async (toolCtx) => {\n const { feishuWhoAmI } = await import('./tools/tools.js');\n return await feishuWhoAmI({ cfg: this.cfg, accountId: toolCtx.accountId });\n },\n },\n {\n name: 'feishu_react',\n description: 'Add/remove/list reactions for a message.',\n execute: async (toolCtx, args) => {\n const a = args as any;\n const messageId = typeof a?.messageId === 'string' ? a.messageId : toolCtx.messageId;\n if (!messageId) throw new Error('feishu_react requires messageId');\n\n const account = resolveFeishuAccount(this.cfg, toolCtx.accountId ?? 'default');\n const enabled = (account.actions as any)?.reactions !== false;\n if (!enabled) {\n throw new Error('Feishu reactions are disabled via channels.feishu.actions.reactions');\n }\n\n if (a?.list === true) {\n const { listReactionsFeishu } = await import('./outbound/actions.js');\n return await listReactionsFeishu({\n cfg: this.cfg,\n accountId: toolCtx.accountId,\n messageId,\n emojiType: typeof a?.emojiType === 'string' ? a.emojiType : undefined,\n });\n }\n\n if (a?.remove === true) {\n const reactionId = typeof a?.reactionId === 'string' ? a.reactionId : '';\n if (!reactionId) throw new Error('feishu_react remove requires reactionId');\n const { removeReactionFeishu } = await import('./outbound/actions.js');\n return await removeReactionFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId, reactionId });\n }\n\n const emojiType = typeof a?.emojiType === 'string' ? a.emojiType : '';\n if (!emojiType) throw new Error('feishu_react requires emojiType');\n const { addReactionFeishu } = await import('./outbound/actions.js');\n return await addReactionFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId, emojiType });\n },\n },\n {\n name: 'feishu_pins',\n description: 'Pin/unpin/list pins for a chat.',\n execute: async (toolCtx, args) => {\n const a = args as any;\n const action = typeof a?.action === 'string' ? a.action : '';\n if (action === 'list') {\n const chatId = typeof a?.chatId === 'string' ? a.chatId : toolCtx.chatId;\n if (!chatId) throw new Error('feishu_pins list requires chatId');\n const { listPinsFeishu } = await import('./outbound/actions.js');\n return await listPinsFeishu({\n cfg: this.cfg,\n accountId: toolCtx.accountId,\n chatId,\n startTime: typeof a?.startTime === 'string' ? a.startTime : undefined,\n endTime: typeof a?.endTime === 'string' ? a.endTime : undefined,\n pageSize: typeof a?.pageSize === 'number' ? a.pageSize : undefined,\n pageToken: typeof a?.pageToken === 'string' ? a.pageToken : undefined,\n });\n }\n const messageId = typeof a?.messageId === 'string' ? a.messageId : toolCtx.messageId;\n if (!messageId) throw new Error('feishu_pins requires messageId');\n if (action === 'pin') {\n const { pinMessageFeishu } = await import('./outbound/actions.js');\n return await pinMessageFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId });\n }\n if (action === 'unpin') {\n const { unpinMessageFeishu } = await import('./outbound/actions.js');\n return await unpinMessageFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId });\n }\n throw new Error('feishu_pins requires action: pin | unpin | list');\n },\n },\n ];\n\n async init(options: ChannelPluginInitOptions): Promise<void> {\n this.bus = options.bus;\n this.cfg = options.config;\n const defaultDebounce = this.defaults.queue?.debounceMs ?? 350;\n this.inboundPipeline = createFeishuInboundPipeline({\n bus: this.bus,\n defaultDebounceMs: defaultDebounce,\n onError: (err, items) => {\n log.error({ err, count: items.length }, 'Feishu inbound pipeline flush failed');\n },\n });\n\n await this.registerWorkflowProgressCapability();\n\n log.debug('Feishu plugin initialized');\n }\n\n private async registerWorkflowProgressCapability(): Promise<void> {\n // Lazy-load workflow progress so Feishu runtime startup does not pull the\n // agent/workflow/provider graph into Electron's dynamic extension path.\n const [{ getWorkflowProgressBroker }, { createFeishuWorkflowProgressCapability }] = await Promise.all([\n import('@xopcai/xopc/agent/workflow/progress-broker.js'),\n import('./workflow-progress.js'),\n ]);\n this.workflowProgressUnregister = getWorkflowProgressBroker().registerChannel(\n createFeishuWorkflowProgressCapability({ getConfig: () => this.cfg }),\n );\n }\n\n async start(options?: ChannelPluginStartOptions): Promise<void> {\n const section = this.cfg.channels?.feishu as FeishuConfig | undefined;\n if (!section || section.enabled !== true) {\n return;\n }\n\n const ids = options?.accountId ? [options.accountId] : listFeishuAccountIds(this.cfg);\n for (const accountId of ids) {\n const account = resolveFeishuAccount(this.cfg, accountId);\n if (!account.enabled || !account.configured) continue;\n if (this.abortControllers.has(accountId)) continue;\n\n const ac = new AbortController();\n this.abortControllers.set(accountId, ac);\n\n const defaultDebounce = this.defaults.queue?.debounceMs ?? 350;\n const enqueueInbound = (work: FeishuInboundWork) => {\n const p = this.inboundPipeline;\n if (!p) {\n return this.bus.publishInbound(work.inbound);\n }\n return p.enqueue(work);\n };\n\n const monitorDeps = {\n account,\n config: this.cfg,\n enqueueInbound,\n inboundDebounceDefaultMs: defaultDebounce,\n abortSignal: ac.signal,\n security: {\n checkAccess: (ctx: ChannelSecurityContext) => this.security.checkAccess?.(ctx, account, this.cfg),\n },\n };\n const runner =\n account.connectionMode === 'webhook'\n ? (await import('./transport/webhook/monitor.js')).createFeishuWebhookMonitor(monitorDeps)\n : (await import('./transport/socket-mode/monitor.js')).createFeishuSocketModeMonitor(monitorDeps);\n\n void runner.run().catch((err) => {\n if ((err as { name?: string } | undefined)?.name === 'AbortError') {\n log.debug({ accountId }, 'Feishu monitor stopped');\n return;\n }\n log.error({ err, accountId }, 'Feishu monitor exited with error');\n });\n\n log.info({ accountId, mode: account.connectionMode }, 'Feishu monitor started');\n }\n }\n\n async stop(accountId?: string): Promise<void> {\n try {\n await this.inboundPipeline?.flushAll();\n } catch (err) {\n log.warn({ err }, 'Feishu inbound pipeline flushAll failed');\n }\n const ids = accountId ? [accountId] : [...this.abortControllers.keys()];\n for (const id of ids) {\n const ac = this.abortControllers.get(id);\n if (ac) {\n ac.abort();\n this.abortControllers.delete(id);\n }\n }\n // Only unregister the broker capability on a full stop. Per-account stops\n // still leave other accounts capable of delivering progress through the\n // same registered cap.\n if (!accountId) {\n this.workflowProgressUnregister?.();\n this.workflowProgressUnregister = null;\n }\n }\n\n channelIsRunning(cfg: Config): boolean {\n const ids = listFeishuAccountIds(cfg);\n return ids.some((id) => {\n const a = resolveFeishuAccount(cfg, id);\n return a.enabled !== false && a.configured && this.abortControllers.has(id);\n });\n }\n\n async onConfigUpdated(cfg: Config): Promise<void> {\n const prev = this.cfg.channels?.feishu as unknown;\n const next = cfg.channels?.feishu as { enabled?: boolean } | undefined;\n const channelOff = !next || next.enabled !== true;\n\n if (channelOff) {\n this.cfg = cfg;\n await this.stop();\n return;\n }\n\n this.cfg = cfg;\n\n if (isDeepStrictEqual(prev, next) && this.channelIsRunning(cfg)) {\n return;\n }\n\n await this.stop();\n await this.start();\n }\n}\n\nexport const feishuPlugin = new FeishuChannelPlugin();\n"],"mappings":"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"}
@@ -4,7 +4,8 @@
4
4
  * Implements hierarchical access control for groups and topics
5
5
  *
6
6
  */
7
- import type { NormalizedAllowFrom, AllowFromMatch, GroupAccessResult, TelegramGroupConfig, TelegramTopicConfig, GroupPolicy } from '@xopcai/xopc/channels/channel-domain.js';
7
+ import type { NormalizedAllowFrom, AllowFromMatch, GroupAccessResult, GroupPolicy } from '@xopcai/xopc/channels/channel-domain.js';
8
+ import type { TelegramGroupConfig, TelegramTopicConfig } from './config-types.js';
8
9
  /**
9
10
  * Normalize allowFrom list to structured format
10
11
  */
@@ -1 +1 @@
1
- {"version":3,"file":"access-control.js","names":[],"sources":["../../../../extensions/telegram/src/access-control.ts"],"sourcesContent":["/**\n * Channel Access Control Utilities\n * \n * Implements hierarchical access control for groups and topics\n *\n */\n\nimport type {\n NormalizedAllowFrom,\n AllowFromMatch,\n GroupAccessResult,\n TelegramGroupConfig,\n TelegramTopicConfig,\n GroupPolicy,\n} from '@xopcai/xopc/channels/channel-domain.js';\n\n// ============================================\n// AllowFrom Normalization\n// ============================================\n\n/**\n * Normalize allowFrom list to structured format\n */\nexport function normalizeAllowFrom(list?: Array<string | number>): NormalizedAllowFrom {\n const entries = (list ?? []).map((value) => String(value).trim()).filter(Boolean);\n const hasWildcard = entries.includes('*');\n const normalized = entries\n .filter((value) => value !== '*')\n .map((value) => value.replace(/^(telegram|tg):/i, ''));\n const ids = normalized.filter((value) => /^\\d+$/.test(value));\n \n return {\n entries: ids,\n hasWildcard,\n hasEntries: ids.length > 0,\n };\n}\n\n/**\n * Normalize allowFrom combining config and store\n */\nexport function normalizeAllowFromWithStore(params: {\n allowFrom?: Array<string | number>;\n storeAllowFrom?: string[];\n}): NormalizedAllowFrom {\n const combined = [\n ...(params.allowFrom ?? []),\n ...(params.storeAllowFrom ?? []),\n ]\n .map((value) => String(value).trim())\n .filter(Boolean);\n return normalizeAllowFrom(combined);\n}\n\n/**\n * Check if sender is allowed based on normalized allowFrom\n */\nexport function isSenderAllowed(params: {\n allow: NormalizedAllowFrom;\n senderId?: string;\n senderUsername?: string;\n}): boolean {\n const { allow, senderId } = params;\n \n // No entries = allow all\n if (!allow.hasEntries) {\n return true;\n }\n \n // Wildcard = allow all\n if (allow.hasWildcard) {\n return true;\n }\n \n // Check if sender ID is in allowlist\n if (senderId && allow.entries.includes(senderId)) {\n return true;\n }\n \n return false;\n}\n\n/**\n * Resolve sender allowlist match\n */\nexport function resolveSenderAllowMatch(params: {\n allow: NormalizedAllowFrom;\n senderId?: string;\n senderUsername?: string;\n}): AllowFromMatch {\n const { allow, senderId } = params;\n \n if (allow.hasWildcard) {\n return { allowed: true, matchKey: '*', matchSource: 'wildcard' };\n }\n \n if (!allow.hasEntries) {\n return { allowed: false };\n }\n \n if (senderId && allow.entries.includes(senderId)) {\n return { allowed: true, matchKey: senderId, matchSource: 'id' };\n }\n \n return { allowed: false };\n}\n\n// ============================================\n// Group Access Control\n// ============================================\n\n/**\n * Evaluate base group access (group/topic enabled, allowFrom override)\n */\nexport function evaluateGroupBaseAccess(params: {\n isGroup: boolean;\n groupConfig?: TelegramGroupConfig;\n topicConfig?: TelegramTopicConfig;\n hasGroupAllowOverride: boolean;\n effectiveGroupAllow: NormalizedAllowFrom;\n senderId?: string;\n senderUsername?: string;\n}): GroupAccessResult {\n // Not a group = always allowed\n if (!params.isGroup) {\n return { allowed: true };\n }\n \n // Check if group is disabled\n if (params.groupConfig?.enabled === false) {\n return { allowed: false, reason: 'group-disabled' };\n }\n \n // Check if topic is disabled\n if (params.topicConfig?.enabled === false) {\n return { allowed: false, reason: 'topic-disabled' };\n }\n \n // Check allowFrom override if present\n if (params.hasGroupAllowOverride) {\n const senderId = params.senderId ?? '';\n if (!senderId) {\n return { allowed: false, reason: 'unauthorized' };\n }\n \n const allowed = isSenderAllowed({\n allow: params.effectiveGroupAllow,\n senderId,\n senderUsername: params.senderUsername,\n });\n \n if (!allowed) {\n return { allowed: false, reason: 'unauthorized' };\n }\n }\n \n return { allowed: true };\n}\n\n/**\n * Evaluate group policy access (open/disabled/allowlist)\n */\nexport function evaluateGroupPolicyAccess(params: {\n isGroup: boolean;\n groupPolicy: GroupPolicy;\n effectiveGroupAllow: NormalizedAllowFrom;\n senderId?: string;\n senderUsername?: string;\n}): GroupAccessResult {\n const { isGroup, groupPolicy, effectiveGroupAllow, senderId, senderUsername } = params;\n \n // Not a group or policy not enforced = always allowed\n if (!isGroup) {\n return { allowed: true, groupPolicy };\n }\n \n // Disabled policy = block all\n if (groupPolicy === 'disabled') {\n return { allowed: false, reason: 'policy-blocked', groupPolicy };\n }\n \n // Open policy = allow all (mention gating handled separately)\n if (groupPolicy === 'open') {\n return { allowed: true, groupPolicy };\n }\n \n // Allowlist policy = check allowFrom\n if (groupPolicy === 'allowlist') {\n if (!effectiveGroupAllow.hasEntries && !effectiveGroupAllow.hasWildcard) {\n return { allowed: false, reason: 'policy-blocked', groupPolicy };\n }\n \n const allowed = isSenderAllowed({\n allow: effectiveGroupAllow,\n senderId,\n senderUsername,\n });\n \n if (!allowed) {\n return { allowed: false, reason: 'policy-blocked', groupPolicy };\n }\n }\n \n return { allowed: true, groupPolicy };\n}\n\n/**\n * Resolve group policy from config hierarchy\n */\nexport function resolveGroupPolicy(params: {\n topicConfig?: TelegramTopicConfig;\n groupConfig?: TelegramGroupConfig;\n accountGroupPolicy?: GroupPolicy;\n defaultGroupPolicy?: GroupPolicy;\n}): GroupPolicy {\n // Priority: topic > group > account > default\n return (\n params.topicConfig?.groupPolicy ??\n params.groupConfig?.groupPolicy ??\n params.accountGroupPolicy ??\n params.defaultGroupPolicy ??\n 'open'\n );\n}\n\n/**\n * Resolve requireMention from config hierarchy\n */\nexport function resolveRequireMention(params: {\n topicConfig?: TelegramTopicConfig;\n groupConfig?: TelegramGroupConfig;\n defaultRequireMention?: boolean;\n}): boolean {\n return (\n params.topicConfig?.requireMention ??\n params.groupConfig?.requireMention ??\n params.defaultRequireMention ??\n true\n );\n}\n\n// ============================================\n// Mention Detection\n// ============================================\n\n/**\n * Build mention regexes for bot username\n */\nexport function buildMentionRegex(botUsername: string): RegExp {\n const escaped = botUsername.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n return new RegExp(`@${escaped}`, 'i');\n}\n\n/**\n * Check if message has bot mention (text or entity)\n */\nexport function hasBotMention(params: {\n botUsername: string;\n text?: string;\n entities?: Array<{ type: string; offset: number; length: number }>;\n}): boolean {\n const { botUsername, text = '', entities = [] } = params;\n const botUsernameLower = botUsername.toLowerCase();\n const textLower = text.toLowerCase();\n \n // Check text mention\n if (textLower.includes(`@${botUsernameLower}`)) {\n return true;\n }\n \n // Check entity mentions\n for (const ent of entities) {\n if (ent.type === 'mention') {\n const mentionText = text.slice(ent.offset, ent.offset + ent.length);\n if (mentionText.toLowerCase() === `@${botUsernameLower}`) {\n return true;\n }\n }\n }\n \n return false;\n}\n\n/**\n * Remove bot mention from text\n */\nexport function removeBotMention(text: string, botUsername: string): string {\n const botUsernameLower = botUsername.toLowerCase();\n return text.replace(new RegExp(`@${botUsernameLower}\\\\s*`, 'gi'), '').trim();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"access-control.js","names":[],"sources":["../../../../extensions/telegram/src/access-control.ts"],"sourcesContent":["/**\n * Channel Access Control Utilities\n * \n * Implements hierarchical access control for groups and topics\n *\n */\n\nimport type {\n NormalizedAllowFrom,\n AllowFromMatch,\n GroupAccessResult,\n GroupPolicy,\n} from '@xopcai/xopc/channels/channel-domain.js';\nimport type { TelegramGroupConfig, TelegramTopicConfig } from './config-types.js';\n\n// ============================================\n// AllowFrom Normalization\n// ============================================\n\n/**\n * Normalize allowFrom list to structured format\n */\nexport function normalizeAllowFrom(list?: Array<string | number>): NormalizedAllowFrom {\n const entries = (list ?? []).map((value) => String(value).trim()).filter(Boolean);\n const hasWildcard = entries.includes('*');\n const normalized = entries\n .filter((value) => value !== '*')\n .map((value) => value.replace(/^(telegram|tg):/i, ''));\n const ids = normalized.filter((value) => /^\\d+$/.test(value));\n \n return {\n entries: ids,\n hasWildcard,\n hasEntries: ids.length > 0,\n };\n}\n\n/**\n * Normalize allowFrom combining config and store\n */\nexport function normalizeAllowFromWithStore(params: {\n allowFrom?: Array<string | number>;\n storeAllowFrom?: string[];\n}): NormalizedAllowFrom {\n const combined = [\n ...(params.allowFrom ?? []),\n ...(params.storeAllowFrom ?? []),\n ]\n .map((value) => String(value).trim())\n .filter(Boolean);\n return normalizeAllowFrom(combined);\n}\n\n/**\n * Check if sender is allowed based on normalized allowFrom\n */\nexport function isSenderAllowed(params: {\n allow: NormalizedAllowFrom;\n senderId?: string;\n senderUsername?: string;\n}): boolean {\n const { allow, senderId } = params;\n \n // No entries = allow all\n if (!allow.hasEntries) {\n return true;\n }\n \n // Wildcard = allow all\n if (allow.hasWildcard) {\n return true;\n }\n \n // Check if sender ID is in allowlist\n if (senderId && allow.entries.includes(senderId)) {\n return true;\n }\n \n return false;\n}\n\n/**\n * Resolve sender allowlist match\n */\nexport function resolveSenderAllowMatch(params: {\n allow: NormalizedAllowFrom;\n senderId?: string;\n senderUsername?: string;\n}): AllowFromMatch {\n const { allow, senderId } = params;\n \n if (allow.hasWildcard) {\n return { allowed: true, matchKey: '*', matchSource: 'wildcard' };\n }\n \n if (!allow.hasEntries) {\n return { allowed: false };\n }\n \n if (senderId && allow.entries.includes(senderId)) {\n return { allowed: true, matchKey: senderId, matchSource: 'id' };\n }\n \n return { allowed: false };\n}\n\n// ============================================\n// Group Access Control\n// ============================================\n\n/**\n * Evaluate base group access (group/topic enabled, allowFrom override)\n */\nexport function evaluateGroupBaseAccess(params: {\n isGroup: boolean;\n groupConfig?: TelegramGroupConfig;\n topicConfig?: TelegramTopicConfig;\n hasGroupAllowOverride: boolean;\n effectiveGroupAllow: NormalizedAllowFrom;\n senderId?: string;\n senderUsername?: string;\n}): GroupAccessResult {\n // Not a group = always allowed\n if (!params.isGroup) {\n return { allowed: true };\n }\n \n // Check if group is disabled\n if (params.groupConfig?.enabled === false) {\n return { allowed: false, reason: 'group-disabled' };\n }\n \n // Check if topic is disabled\n if (params.topicConfig?.enabled === false) {\n return { allowed: false, reason: 'topic-disabled' };\n }\n \n // Check allowFrom override if present\n if (params.hasGroupAllowOverride) {\n const senderId = params.senderId ?? '';\n if (!senderId) {\n return { allowed: false, reason: 'unauthorized' };\n }\n \n const allowed = isSenderAllowed({\n allow: params.effectiveGroupAllow,\n senderId,\n senderUsername: params.senderUsername,\n });\n \n if (!allowed) {\n return { allowed: false, reason: 'unauthorized' };\n }\n }\n \n return { allowed: true };\n}\n\n/**\n * Evaluate group policy access (open/disabled/allowlist)\n */\nexport function evaluateGroupPolicyAccess(params: {\n isGroup: boolean;\n groupPolicy: GroupPolicy;\n effectiveGroupAllow: NormalizedAllowFrom;\n senderId?: string;\n senderUsername?: string;\n}): GroupAccessResult {\n const { isGroup, groupPolicy, effectiveGroupAllow, senderId, senderUsername } = params;\n \n // Not a group or policy not enforced = always allowed\n if (!isGroup) {\n return { allowed: true, groupPolicy };\n }\n \n // Disabled policy = block all\n if (groupPolicy === 'disabled') {\n return { allowed: false, reason: 'policy-blocked', groupPolicy };\n }\n \n // Open policy = allow all (mention gating handled separately)\n if (groupPolicy === 'open') {\n return { allowed: true, groupPolicy };\n }\n \n // Allowlist policy = check allowFrom\n if (groupPolicy === 'allowlist') {\n if (!effectiveGroupAllow.hasEntries && !effectiveGroupAllow.hasWildcard) {\n return { allowed: false, reason: 'policy-blocked', groupPolicy };\n }\n \n const allowed = isSenderAllowed({\n allow: effectiveGroupAllow,\n senderId,\n senderUsername,\n });\n \n if (!allowed) {\n return { allowed: false, reason: 'policy-blocked', groupPolicy };\n }\n }\n \n return { allowed: true, groupPolicy };\n}\n\n/**\n * Resolve group policy from config hierarchy\n */\nexport function resolveGroupPolicy(params: {\n topicConfig?: TelegramTopicConfig;\n groupConfig?: TelegramGroupConfig;\n accountGroupPolicy?: GroupPolicy;\n defaultGroupPolicy?: GroupPolicy;\n}): GroupPolicy {\n // Priority: topic > group > account > default\n return (\n params.topicConfig?.groupPolicy ??\n params.groupConfig?.groupPolicy ??\n params.accountGroupPolicy ??\n params.defaultGroupPolicy ??\n 'open'\n );\n}\n\n/**\n * Resolve requireMention from config hierarchy\n */\nexport function resolveRequireMention(params: {\n topicConfig?: TelegramTopicConfig;\n groupConfig?: TelegramGroupConfig;\n defaultRequireMention?: boolean;\n}): boolean {\n return (\n params.topicConfig?.requireMention ??\n params.groupConfig?.requireMention ??\n params.defaultRequireMention ??\n true\n );\n}\n\n// ============================================\n// Mention Detection\n// ============================================\n\n/**\n * Build mention regexes for bot username\n */\nexport function buildMentionRegex(botUsername: string): RegExp {\n const escaped = botUsername.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n return new RegExp(`@${escaped}`, 'i');\n}\n\n/**\n * Check if message has bot mention (text or entity)\n */\nexport function hasBotMention(params: {\n botUsername: string;\n text?: string;\n entities?: Array<{ type: string; offset: number; length: number }>;\n}): boolean {\n const { botUsername, text = '', entities = [] } = params;\n const botUsernameLower = botUsername.toLowerCase();\n const textLower = text.toLowerCase();\n \n // Check text mention\n if (textLower.includes(`@${botUsernameLower}`)) {\n return true;\n }\n \n // Check entity mentions\n for (const ent of entities) {\n if (ent.type === 'mention') {\n const mentionText = text.slice(ent.offset, ent.offset + ent.length);\n if (mentionText.toLowerCase() === `@${botUsernameLower}`) {\n return true;\n }\n }\n }\n \n return false;\n}\n\n/**\n * Remove bot mention from text\n */\nexport function removeBotMention(text: string, botUsername: string): string {\n const botUsernameLower = botUsername.toLowerCase();\n return text.replace(new RegExp(`@${botUsernameLower}\\\\s*`, 'gi'), '').trim();\n}\n"],"mappings":"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"}
@@ -9,8 +9,9 @@
9
9
  * - Bot username mapping
10
10
  */
11
11
  import type { Bot, Context } from 'grammy';
12
- import type { TelegramAccountConfig, ChannelStatus } from '@xopcai/xopc/channels/channel-domain.js';
12
+ import type { ChannelStatus } from '@xopcai/xopc/channels/channel-domain.js';
13
13
  import type { TelegramPollingSession } from './polling-session.js';
14
+ import type { TelegramAccountConfig } from './config-types.js';
14
15
  export declare class TelegramAccountManager {
15
16
  private accounts;
16
17
  private bots;
@@ -1 +1 @@
1
- {"version":3,"file":"account-manager.js","names":[],"sources":["../../../../extensions/telegram/src/account-manager.ts"],"sourcesContent":["/**\n * Telegram Account Manager\n *\n * Manages multiple Telegram bot accounts, including:\n * - Account configuration storage\n * - Bot instance management\n * - Runner lifecycle\n * - Status tracking\n * - Bot username mapping\n */\n\nimport type { Bot, Context } from 'grammy';\nimport type { TelegramAccountConfig, ChannelStatus } from '@xopcai/xopc/channels/channel-domain.js';\nimport type { TelegramPollingSession } from './polling-session.js';\n\nexport class TelegramAccountManager {\n private accounts = new Map<string, TelegramAccountConfig>();\n private bots = new Map<string, Bot>();\n private pollingSessions = new Map<string, TelegramPollingSession>();\n private statuses = new Map<string, ChannelStatus>();\n private botUsernames = new Map<string, string>();\n private startingAccounts = new Set<string>();\n\n registerAccount(account: TelegramAccountConfig): void {\n this.accounts.set(account.accountId, account);\n this.statuses.set(account.accountId, {\n accountId: account.accountId,\n running: false,\n mode: 'stopped',\n });\n }\n\n getAccount(accountId: string): TelegramAccountConfig | undefined {\n return this.accounts.get(accountId);\n }\n\n getAllAccounts(): TelegramAccountConfig[] {\n return Array.from(this.accounts.values());\n }\n\n /** Clear all accounts and runtime state (used when reloading channel config). */\n reset(): void {\n this.accounts.clear();\n this.bots.clear();\n this.pollingSessions.clear();\n this.statuses.clear();\n this.botUsernames.clear();\n this.startingAccounts.clear();\n }\n\n registerBot(accountId: string, bot: Bot): void {\n this.bots.set(accountId, bot);\n }\n\n getBot(accountId: string): Bot | undefined {\n return this.bots.get(accountId);\n }\n\n registerPollingSession(accountId: string, session: TelegramPollingSession): void {\n this.pollingSessions.set(accountId, session);\n }\n\n async stopRunner(accountId: string): Promise<void> {\n const session = this.pollingSessions.get(accountId);\n const bot = this.bots.get(accountId);\n\n if (session) {\n await session.stop();\n this.pollingSessions.delete(accountId);\n }\n\n if (bot) {\n bot.stop();\n this.bots.delete(accountId);\n }\n \n this.startingAccounts.delete(accountId);\n \n // Update status to stopped\n this.updateStatus({\n accountId,\n running: false,\n mode: 'stopped',\n });\n }\n\n isStarting(accountId: string): boolean {\n return this.startingAccounts.has(accountId);\n }\n\n markStarting(accountId: string): void {\n this.startingAccounts.add(accountId);\n }\n\n markStartComplete(accountId: string): void {\n this.startingAccounts.delete(accountId);\n }\n\n isRunning(accountId: string): boolean {\n const status = this.statuses.get(accountId);\n return status?.running ?? false;\n }\n\n updateStatus(status: ChannelStatus): void {\n this.statuses.set(status.accountId, status);\n }\n\n getStatus(accountId: string): ChannelStatus | undefined {\n return this.statuses.get(accountId);\n }\n\n setBotUsername(accountId: string, username: string): void {\n this.botUsernames.set(accountId, username);\n }\n\n getBotUsername(accountId: string): string | undefined {\n return this.botUsernames.get(accountId);\n }\n\n /** Map the bot handling this update to a configured account id (session keys must match inbound). */\n resolveAccountIdFromContext(ctx: Context): string {\n const botId = ctx.me?.id;\n if (botId == null) {\n const accounts = this.getAllAccounts();\n return accounts[0]?.accountId ?? 'default';\n }\n for (const [accountId, bot] of this.bots) {\n if (bot.botInfo?.id === botId) return accountId;\n }\n const accounts = this.getAllAccounts();\n if (accounts.length === 1) return accounts[0]!.accountId;\n return 'default';\n }\n}\n"],"mappings":";AAeA,IAAa,yBAAb,MAAoC;CAClC,2BAAmB,IAAI,KAAoC;CAC3D,uBAAe,IAAI,KAAkB;CACrC,kCAA0B,IAAI,KAAqC;CACnE,2BAAmB,IAAI,KAA4B;CACnD,+BAAuB,IAAI,KAAqB;CAChD,mCAA2B,IAAI,KAAa;CAE5C,gBAAgB,SAAsC;AACpD,OAAK,SAAS,IAAI,QAAQ,WAAW,QAAQ;AAC7C,OAAK,SAAS,IAAI,QAAQ,WAAW;GACnC,WAAW,QAAQ;GACnB,SAAS;GACT,MAAM;GACP,CAAC;;CAGJ,WAAW,WAAsD;AAC/D,SAAO,KAAK,SAAS,IAAI,UAAU;;CAGrC,iBAA0C;AACxC,SAAO,MAAM,KAAK,KAAK,SAAS,QAAQ,CAAC;;;CAI3C,QAAc;AACZ,OAAK,SAAS,OAAO;AACrB,OAAK,KAAK,OAAO;AACjB,OAAK,gBAAgB,OAAO;AAC5B,OAAK,SAAS,OAAO;AACrB,OAAK,aAAa,OAAO;AACzB,OAAK,iBAAiB,OAAO;;CAG/B,YAAY,WAAmB,KAAgB;AAC7C,OAAK,KAAK,IAAI,WAAW,IAAI;;CAG/B,OAAO,WAAoC;AACzC,SAAO,KAAK,KAAK,IAAI,UAAU;;CAGjC,uBAAuB,WAAmB,SAAuC;AAC/E,OAAK,gBAAgB,IAAI,WAAW,QAAQ;;CAG9C,MAAM,WAAW,WAAkC;EACjD,MAAM,UAAU,KAAK,gBAAgB,IAAI,UAAU;EACnD,MAAM,MAAM,KAAK,KAAK,IAAI,UAAU;AAEpC,MAAI,SAAS;AACX,SAAM,QAAQ,MAAM;AACpB,QAAK,gBAAgB,OAAO,UAAU;;AAGxC,MAAI,KAAK;AACP,OAAI,MAAM;AACV,QAAK,KAAK,OAAO,UAAU;;AAG7B,OAAK,iBAAiB,OAAO,UAAU;AAGvC,OAAK,aAAa;GAChB;GACA,SAAS;GACT,MAAM;GACP,CAAC;;CAGJ,WAAW,WAA4B;AACrC,SAAO,KAAK,iBAAiB,IAAI,UAAU;;CAG7C,aAAa,WAAyB;AACpC,OAAK,iBAAiB,IAAI,UAAU;;CAGtC,kBAAkB,WAAyB;AACzC,OAAK,iBAAiB,OAAO,UAAU;;CAGzC,UAAU,WAA4B;AAEpC,SADe,KAAK,SAAS,IAAI,UACpB,EAAE,WAAW;;CAG5B,aAAa,QAA6B;AACxC,OAAK,SAAS,IAAI,OAAO,WAAW,OAAO;;CAG7C,UAAU,WAA8C;AACtD,SAAO,KAAK,SAAS,IAAI,UAAU;;CAGrC,eAAe,WAAmB,UAAwB;AACxD,OAAK,aAAa,IAAI,WAAW,SAAS;;CAG5C,eAAe,WAAuC;AACpD,SAAO,KAAK,aAAa,IAAI,UAAU;;;CAIzC,4BAA4B,KAAsB;EAChD,MAAM,QAAQ,IAAI,IAAI;AACtB,MAAI,SAAS,KAEX,QADiB,KAAK,gBACP,CAAC,IAAI,aAAa;AAEnC,OAAK,MAAM,CAAC,WAAW,QAAQ,KAAK,KAClC,KAAI,IAAI,SAAS,OAAO,MAAO,QAAO;EAExC,MAAM,WAAW,KAAK,gBAAgB;AACtC,MAAI,SAAS,WAAW,EAAG,QAAO,SAAS,GAAI;AAC/C,SAAO"}
1
+ {"version":3,"file":"account-manager.js","names":[],"sources":["../../../../extensions/telegram/src/account-manager.ts"],"sourcesContent":["/**\n * Telegram Account Manager\n *\n * Manages multiple Telegram bot accounts, including:\n * - Account configuration storage\n * - Bot instance management\n * - Runner lifecycle\n * - Status tracking\n * - Bot username mapping\n */\n\nimport type { Bot, Context } from 'grammy';\nimport type { ChannelStatus } from '@xopcai/xopc/channels/channel-domain.js';\nimport type { TelegramPollingSession } from './polling-session.js';\nimport type { TelegramAccountConfig } from './config-types.js';\n\nexport class TelegramAccountManager {\n private accounts = new Map<string, TelegramAccountConfig>();\n private bots = new Map<string, Bot>();\n private pollingSessions = new Map<string, TelegramPollingSession>();\n private statuses = new Map<string, ChannelStatus>();\n private botUsernames = new Map<string, string>();\n private startingAccounts = new Set<string>();\n\n registerAccount(account: TelegramAccountConfig): void {\n this.accounts.set(account.accountId, account);\n this.statuses.set(account.accountId, {\n accountId: account.accountId,\n running: false,\n mode: 'stopped',\n });\n }\n\n getAccount(accountId: string): TelegramAccountConfig | undefined {\n return this.accounts.get(accountId);\n }\n\n getAllAccounts(): TelegramAccountConfig[] {\n return Array.from(this.accounts.values());\n }\n\n /** Clear all accounts and runtime state (used when reloading channel config). */\n reset(): void {\n this.accounts.clear();\n this.bots.clear();\n this.pollingSessions.clear();\n this.statuses.clear();\n this.botUsernames.clear();\n this.startingAccounts.clear();\n }\n\n registerBot(accountId: string, bot: Bot): void {\n this.bots.set(accountId, bot);\n }\n\n getBot(accountId: string): Bot | undefined {\n return this.bots.get(accountId);\n }\n\n registerPollingSession(accountId: string, session: TelegramPollingSession): void {\n this.pollingSessions.set(accountId, session);\n }\n\n async stopRunner(accountId: string): Promise<void> {\n const session = this.pollingSessions.get(accountId);\n const bot = this.bots.get(accountId);\n\n if (session) {\n await session.stop();\n this.pollingSessions.delete(accountId);\n }\n\n if (bot) {\n bot.stop();\n this.bots.delete(accountId);\n }\n \n this.startingAccounts.delete(accountId);\n \n // Update status to stopped\n this.updateStatus({\n accountId,\n running: false,\n mode: 'stopped',\n });\n }\n\n isStarting(accountId: string): boolean {\n return this.startingAccounts.has(accountId);\n }\n\n markStarting(accountId: string): void {\n this.startingAccounts.add(accountId);\n }\n\n markStartComplete(accountId: string): void {\n this.startingAccounts.delete(accountId);\n }\n\n isRunning(accountId: string): boolean {\n const status = this.statuses.get(accountId);\n return status?.running ?? false;\n }\n\n updateStatus(status: ChannelStatus): void {\n this.statuses.set(status.accountId, status);\n }\n\n getStatus(accountId: string): ChannelStatus | undefined {\n return this.statuses.get(accountId);\n }\n\n setBotUsername(accountId: string, username: string): void {\n this.botUsernames.set(accountId, username);\n }\n\n getBotUsername(accountId: string): string | undefined {\n return this.botUsernames.get(accountId);\n }\n\n /** Map the bot handling this update to a configured account id (session keys must match inbound). */\n resolveAccountIdFromContext(ctx: Context): string {\n const botId = ctx.me?.id;\n if (botId == null) {\n const accounts = this.getAllAccounts();\n return accounts[0]?.accountId ?? 'default';\n }\n for (const [accountId, bot] of this.bots) {\n if (bot.botInfo?.id === botId) return accountId;\n }\n const accounts = this.getAllAccounts();\n if (accounts.length === 1) return accounts[0]!.accountId;\n return 'default';\n }\n}\n"],"mappings":";AAgBA,IAAa,yBAAb,MAAoC;CAClC,2BAAmB,IAAI,KAAoC;CAC3D,uBAAe,IAAI,KAAkB;CACrC,kCAA0B,IAAI,KAAqC;CACnE,2BAAmB,IAAI,KAA4B;CACnD,+BAAuB,IAAI,KAAqB;CAChD,mCAA2B,IAAI,KAAa;CAE5C,gBAAgB,SAAsC;AACpD,OAAK,SAAS,IAAI,QAAQ,WAAW,QAAQ;AAC7C,OAAK,SAAS,IAAI,QAAQ,WAAW;GACnC,WAAW,QAAQ;GACnB,SAAS;GACT,MAAM;GACP,CAAC;;CAGJ,WAAW,WAAsD;AAC/D,SAAO,KAAK,SAAS,IAAI,UAAU;;CAGrC,iBAA0C;AACxC,SAAO,MAAM,KAAK,KAAK,SAAS,QAAQ,CAAC;;;CAI3C,QAAc;AACZ,OAAK,SAAS,OAAO;AACrB,OAAK,KAAK,OAAO;AACjB,OAAK,gBAAgB,OAAO;AAC5B,OAAK,SAAS,OAAO;AACrB,OAAK,aAAa,OAAO;AACzB,OAAK,iBAAiB,OAAO;;CAG/B,YAAY,WAAmB,KAAgB;AAC7C,OAAK,KAAK,IAAI,WAAW,IAAI;;CAG/B,OAAO,WAAoC;AACzC,SAAO,KAAK,KAAK,IAAI,UAAU;;CAGjC,uBAAuB,WAAmB,SAAuC;AAC/E,OAAK,gBAAgB,IAAI,WAAW,QAAQ;;CAG9C,MAAM,WAAW,WAAkC;EACjD,MAAM,UAAU,KAAK,gBAAgB,IAAI,UAAU;EACnD,MAAM,MAAM,KAAK,KAAK,IAAI,UAAU;AAEpC,MAAI,SAAS;AACX,SAAM,QAAQ,MAAM;AACpB,QAAK,gBAAgB,OAAO,UAAU;;AAGxC,MAAI,KAAK;AACP,OAAI,MAAM;AACV,QAAK,KAAK,OAAO,UAAU;;AAG7B,OAAK,iBAAiB,OAAO,UAAU;AAGvC,OAAK,aAAa;GAChB;GACA,SAAS;GACT,MAAM;GACP,CAAC;;CAGJ,WAAW,WAA4B;AACrC,SAAO,KAAK,iBAAiB,IAAI,UAAU;;CAG7C,aAAa,WAAyB;AACpC,OAAK,iBAAiB,IAAI,UAAU;;CAGtC,kBAAkB,WAAyB;AACzC,OAAK,iBAAiB,OAAO,UAAU;;CAGzC,UAAU,WAA4B;AAEpC,SADe,KAAK,SAAS,IAAI,UACpB,EAAE,WAAW;;CAG5B,aAAa,QAA6B;AACxC,OAAK,SAAS,IAAI,OAAO,WAAW,OAAO;;CAG7C,UAAU,WAA8C;AACtD,SAAO,KAAK,SAAS,IAAI,UAAU;;CAGrC,eAAe,WAAmB,UAAwB;AACxD,OAAK,aAAa,IAAI,WAAW,SAAS;;CAG5C,eAAe,WAAuC;AACpD,SAAO,KAAK,aAAa,IAAI,UAAU;;;CAIzC,4BAA4B,KAAsB;EAChD,MAAM,QAAQ,IAAI,IAAI;AACtB,MAAI,SAAS,KAEX,QADiB,KAAK,gBACP,CAAC,IAAI,aAAa;AAEnC,OAAK,MAAM,CAAC,WAAW,QAAQ,KAAK,KAClC,KAAI,IAAI,SAAS,OAAO,MAAO,QAAO;EAExC,MAAM,WAAW,KAAK,gBAAgB;AACtC,MAAI,SAAS,WAAW,EAAG,QAAO,SAAS,GAAI;AAC/C,SAAO"}
@@ -9,36 +9,44 @@ function pickString(input) {
9
9
  return typeof input === "string" ? input.trim() : "";
10
10
  }
11
11
  function pickDmPolicy(input) {
12
- return input === "pairing" || input === "allowlist" || input === "disabled" ? input : "open";
12
+ return input === "open" || input === "pairing" || input === "allowlist" || input === "disabled" ? input : "pairing";
13
13
  }
14
14
  function existingDefaultAccount(config) {
15
15
  const account = ((config.channels?.telegram)?.accounts)?.default;
16
16
  return account && typeof account === "object" && !Array.isArray(account) ? account : {};
17
17
  }
18
+ function existingDefaults(config) {
19
+ const defaults = (config.channels?.telegram)?.defaults;
20
+ return defaults && typeof defaults === "object" && !Array.isArray(defaults) ? defaults : {};
21
+ }
18
22
  function buildTelegramConfig(config, input) {
19
23
  const existing = config.channels?.telegram ?? {};
20
24
  const existingAccounts = existing.accounts ?? {};
21
25
  const previousDefault = existingDefaultAccount(config);
26
+ const previousDefaults = existingDefaults(config);
22
27
  const botToken = pickString(input.botToken) || pickString(previousDefault.botToken);
23
28
  const apiRoot = pickString(input.apiRoot);
24
29
  const proxy = pickString(input.proxy);
25
- const dmPolicy = pickDmPolicy(input.dmPolicy ?? previousDefault.dmPolicy ?? existing.dmPolicy);
30
+ const dmPolicy = pickDmPolicy(input.dmPolicy ?? previousDefault.dmPolicy ?? previousDefaults.dmPolicy);
31
+ const defaults = {
32
+ ...previousDefaults,
33
+ dmPolicy: previousDefaults.dmPolicy ?? "pairing",
34
+ groupPolicy: previousDefaults.groupPolicy ?? "open",
35
+ replyToMode: previousDefaults.replyToMode ?? "off",
36
+ historyLimit: previousDefaults.historyLimit ?? 50,
37
+ textChunkLimit: previousDefaults.textChunkLimit ?? 4e3,
38
+ streaming: previousDefaults.streaming ?? { mode: "partial" }
39
+ };
40
+ if (apiRoot) defaults.apiRoot = apiRoot;
41
+ if (proxy) defaults.proxy = proxy;
26
42
  const defaultAccount = {
27
43
  ...previousDefault,
28
44
  accountId: "default",
29
45
  enabled: true,
30
46
  botToken,
31
47
  dmPolicy,
32
- allowFrom: dmPolicy === "open" ? ["*"] : previousDefault.allowFrom ?? [],
33
- groupPolicy: previousDefault.groupPolicy ?? existing.groupPolicy ?? "open",
34
- replyToMode: previousDefault.replyToMode ?? existing.replyToMode ?? "off",
35
- historyLimit: previousDefault.historyLimit ?? existing.historyLimit ?? 50,
36
- textChunkLimit: previousDefault.textChunkLimit ?? existing.textChunkLimit ?? 4e3,
37
- streaming: previousDefault.streaming ?? existing.streaming ?? { mode: "partial" }
48
+ allowFrom: dmPolicy === "open" ? ["*"] : previousDefault.allowFrom ?? []
38
49
  };
39
- if (apiRoot) defaultAccount.apiRoot = apiRoot;
40
- if (proxy) defaultAccount.proxy = proxy;
41
- const topAllowFrom = dmPolicy === "open" ? ["*"] : existing.allowFrom ?? [];
42
50
  return {
43
51
  ...config,
44
52
  channels: {
@@ -46,15 +54,7 @@ function buildTelegramConfig(config, input) {
46
54
  telegram: {
47
55
  ...existing,
48
56
  enabled: true,
49
- dmPolicy,
50
- allowFrom: topAllowFrom,
51
- groupPolicy: existing.groupPolicy ?? "open",
52
- replyToMode: existing.replyToMode ?? "off",
53
- streaming: existing.streaming ?? { mode: "partial" },
54
- historyLimit: existing.historyLimit ?? 50,
55
- textChunkLimit: existing.textChunkLimit ?? 4e3,
56
- ...apiRoot ? { apiRoot } : {},
57
- ...proxy ? { proxy } : {},
57
+ defaults,
58
58
  accounts: {
59
59
  ...existingAccounts,
60
60
  default: defaultAccount
@@ -65,12 +65,12 @@ function buildTelegramConfig(config, input) {
65
65
  }
66
66
  function formPayload(config, locale) {
67
67
  const zh = isZh(locale);
68
- const telegram = config.channels?.telegram ?? {};
69
68
  const account = existingDefaultAccount(config);
69
+ const defaults = existingDefaults(config);
70
70
  return {
71
71
  type: "form",
72
72
  submitAction: "setup.save",
73
- message: zh ? "填写 Bot Token 后保存即可启用 Telegram 私聊。群聊、多个账号、allowlist 等放在高级配置。" : "Enter a Bot Token and save. Private chat works by default; groups, multi-account, and allowlists live in Advanced configuration.",
73
+ message: zh ? "填写 Bot Token 后保存即可启用 Telegram。默认私聊策略为 pairing;允许列表在账号配置里维护。" : "Enter a Bot Token and save. The default DM policy is pairing; manage allow lists on the account configuration.",
74
74
  schema: {
75
75
  type: "object",
76
76
  properties: {
@@ -105,9 +105,9 @@ function formPayload(config, locale) {
105
105
  },
106
106
  values: {
107
107
  botToken: pickString(account.botToken),
108
- dmPolicy: pickDmPolicy(account.dmPolicy ?? telegram.dmPolicy ?? "open"),
109
- apiRoot: pickString(account.apiRoot ?? telegram.apiRoot),
110
- proxy: pickString(account.proxy ?? telegram.proxy)
108
+ dmPolicy: pickDmPolicy(account.dmPolicy ?? defaults.dmPolicy ?? "pairing"),
109
+ apiRoot: pickString(account.apiRoot ?? defaults.apiRoot),
110
+ proxy: pickString(account.proxy ?? defaults.proxy)
111
111
  }
112
112
  };
113
113
  }
@@ -132,7 +132,7 @@ const telegramGatewaySetupActions = { async runAction({ actionId, input, locale
132
132
  ok: true,
133
133
  payload: {
134
134
  type: "ok",
135
- message: zh ? "Telegram 配置已保存。私聊现在默认可用。" : "Telegram configuration saved. Private chat is enabled by default.",
135
+ message: zh ? "Telegram 配置已保存。私聊将按 pairing/允许列表策略放行。" : "Telegram configuration saved. Private chat now follows the pairing/allow-list policy.",
136
136
  configChanged: true
137
137
  }
138
138
  };