@xopcai/xopc 0.0.130 → 0.0.132

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 (198) hide show
  1. package/README.md +23 -26
  2. package/README.zh-CN.md +25 -28
  3. package/dist/browser-ext/icons/icon-128.png +0 -0
  4. package/dist/browser-ext/icons/icon-16.png +0 -0
  5. package/dist/browser-ext/icons/icon-32.png +0 -0
  6. package/dist/browser-ext/icons/icon-48.png +0 -0
  7. package/dist/browser-ext/manifest.json +1 -1
  8. package/dist/extensions/telegram/xopc.extension.json +1 -1
  9. package/dist/extensions/weixin/src/messaging/process-message.js +5 -8
  10. package/dist/extensions/weixin/src/messaging/process-message.js.map +1 -1
  11. package/dist/extensions/weixin/src/plugin.js +11 -1
  12. package/dist/extensions/weixin/src/plugin.js.map +1 -1
  13. package/dist/extensions/weixin/src/routing-integration.d.ts +7 -0
  14. package/dist/extensions/weixin/src/routing-integration.js +17 -0
  15. package/dist/extensions/weixin/src/routing-integration.js.map +1 -0
  16. package/dist/gateway/static/root/apple-touch-icon.png +0 -0
  17. package/dist/gateway/static/root/assets/{agent-avatar-dicebear--iz8s0k-.js → agent-avatar-dicebear-CnZIdUyv.js} +1 -1
  18. package/dist/gateway/static/root/assets/agents-C-rdLOrb.js +2 -0
  19. package/dist/gateway/static/root/assets/app-management-settings-panel-BcvaXb7T.js +1 -0
  20. package/dist/gateway/static/root/assets/{appearance-settings-9Y9ulFHw.js → appearance-settings-CuxzNz-I.js} +1 -1
  21. package/dist/gateway/static/root/assets/apps-page-_VJm0o44.js +1 -0
  22. package/dist/gateway/static/root/assets/{archive-plugin-DLWkp7gy.js → archive-plugin-CcXXh0X9.js} +1 -1
  23. package/dist/gateway/static/root/assets/{attachment-load-aDP5GIUn.js → attachment-load-BYhYBw17.js} +1 -1
  24. package/dist/gateway/static/root/assets/{automation-suggestion-card-B1cByea5.js → automation-suggestion-card-ByjDtlOw.js} +1 -1
  25. package/dist/gateway/static/root/assets/automations-page-DUFct5t3.js +4 -0
  26. package/dist/gateway/static/root/assets/{binary-plugins-DUisfrnF.js → binary-plugins-B1A5KhWX.js} +1 -1
  27. package/dist/gateway/static/root/assets/{block-editor-D9aGvHMF.js → block-editor-DWpXiVTf.js} +4 -4
  28. package/dist/gateway/static/root/assets/browser-settings-page-Cff75b5Z.js +8 -0
  29. package/dist/gateway/static/root/assets/capability-presets-api-DFWL6PJ0.js +1 -0
  30. package/dist/gateway/static/root/assets/{capability-presets-settings-panel-BXDIjaXI.js → capability-presets-settings-panel-CBT89nI-.js} +1 -1
  31. package/dist/gateway/static/root/assets/channel-recipient-api-RANYrzfa.js +1 -0
  32. package/dist/gateway/static/root/assets/channels-settings-DHBjZ2fa.js +1 -0
  33. package/dist/gateway/static/root/assets/channels-status-swr-BG_j4Ci-.js +1 -0
  34. package/dist/gateway/static/root/assets/connectors-page-B4CGNErH.js +2 -0
  35. package/dist/gateway/static/root/assets/desktop-pet-CDD29oYv.js +1 -0
  36. package/dist/gateway/static/root/assets/{desktop-pet-settings-iLgj-52b.js → desktop-pet-settings-CEkfvSnb.js} +1 -1
  37. package/dist/gateway/static/root/assets/{directory-picker-path-field-RtmB1V_x.js → directory-picker-path-field-JbVQ2_md.js} +1 -1
  38. package/dist/gateway/static/root/assets/dreaming-settings-C-fzh8SN.js +3 -0
  39. package/dist/gateway/static/root/assets/{extension-debug-page-5-InZoH2.js → extension-debug-page-B4jZ0Yim.js} +1 -1
  40. package/dist/gateway/static/root/assets/{extension-page-Bsai0Lgm.js → extension-page-Bi0GmOZ2.js} +1 -1
  41. package/dist/gateway/static/root/assets/extension-settings-page-BJpxz8Dq.js +1 -0
  42. package/dist/gateway/static/root/assets/fetch-6HXXdO77.js +1 -0
  43. package/dist/gateway/static/root/assets/gateway-config-api-Ci1tJtJ4.js +1 -0
  44. package/dist/gateway/static/root/assets/gateway-settings-C-YBJHLX.js +1 -0
  45. package/dist/gateway/static/root/assets/{gateway-startup-retry-CY5W2Pp4.js → gateway-startup-retry-BE1M9fH_.js} +1 -1
  46. package/dist/gateway/static/root/assets/goal-create-dialog-DhfNsglx.js +1 -0
  47. package/dist/gateway/static/root/assets/goal-detail-page-9WeYgD-u.js +4 -0
  48. package/dist/gateway/static/root/assets/goals-config-api-DRJGqa_W.js +1 -0
  49. package/dist/gateway/static/root/assets/goals-page-DXEQqzEX.js +2 -0
  50. package/dist/gateway/static/root/assets/goals-settings-5pFGRkD6.js +1 -0
  51. package/dist/gateway/static/root/assets/heartbeat-settings-CGX78-fM.js +1 -0
  52. package/dist/gateway/static/root/assets/index-BjGnv8uJ.js +262 -0
  53. package/dist/gateway/static/root/assets/index-W4ogAiHt.css +1 -0
  54. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BHyCbDmv.js → keyboard-shortcuts-settings-D23eHsli.js} +1 -1
  55. package/dist/gateway/static/root/assets/logs-page-jMLLOxWk.js +7 -0
  56. package/dist/gateway/static/root/assets/{markdown-split-Cfv4fIaA.js → markdown-split-Dn7rJV-v.js} +1 -1
  57. package/dist/gateway/static/root/assets/{media-plugins-DHIecUIi.js → media-plugins-DOnTRGyE.js} +1 -1
  58. package/dist/gateway/static/root/assets/{messages-YzjnURPJ.js → messages-DR9ODkb8.js} +3 -3
  59. package/dist/gateway/static/root/assets/models-hub-panel-Br6l-Di9.js +2 -0
  60. package/dist/gateway/static/root/assets/notes-page-BiXfX1he.js +1 -0
  61. package/dist/gateway/static/root/assets/notes-workbench-DjaTvXj3.js +2 -0
  62. package/dist/gateway/static/root/assets/{page-tabs-xL5C9yyM.js → page-tabs-CYEVap7D.js} +1 -1
  63. package/dist/gateway/static/root/assets/{preview-open-alternatives-Dlzt9LTH.js → preview-open-alternatives-Dk7Cjd6E.js} +1 -1
  64. package/dist/gateway/static/root/assets/project-detail-page-B6UccBNl.js +1 -0
  65. package/dist/gateway/static/root/assets/projects-page-B56s8p-Q.js +1 -0
  66. package/dist/gateway/static/root/assets/remote-access-hub-D3ekwIaB.js +1 -0
  67. package/dist/gateway/static/root/assets/{save-bar-controls-CxMWRml4.js → save-bar-controls-D7FzAPOc.js} +1 -1
  68. package/dist/gateway/static/root/assets/{schedule-field-XCWPTiWa.js → schedule-field-BZP1-U9M.js} +1 -1
  69. package/dist/gateway/static/root/assets/{schema-form-BaQCsnUq.js → schema-form-1NNFTrvK.js} +1 -1
  70. package/dist/gateway/static/root/assets/sessions-page-08vT4R9X.js +1 -0
  71. package/dist/gateway/static/root/assets/settings-advanced-gate-IsaFJ6ZC.js +1 -0
  72. package/dist/gateway/static/root/assets/{settings-collapsible-section-D1SE9kRq.js → settings-collapsible-section-CX5IUGk1.js} +1 -1
  73. package/dist/gateway/static/root/assets/{settings-form-section-DnEPGUgO.js → settings-form-section-a71ujXN4.js} +1 -1
  74. package/dist/gateway/static/root/assets/{settings-loading-skeleton-DOvWEjHg.js → settings-loading-skeleton-D4mq628A.js} +1 -1
  75. package/dist/gateway/static/root/assets/settings-page-DSQWZsZt.js +1 -0
  76. package/dist/gateway/static/root/assets/{settings-page-layout-CCjhhWJI.js → settings-page-layout-BG-k3_CF.js} +1 -1
  77. package/dist/gateway/static/root/assets/setup-status-panel-CFBgxLKm.js +1 -0
  78. package/dist/gateway/static/root/assets/share-preview-page-Cz4X3Vcf.js +2 -0
  79. package/dist/gateway/static/root/assets/shares-settings-BHjEkA9t.js +1 -0
  80. package/dist/gateway/static/root/assets/skill-reload-api-CfHG_ekm.js +1 -0
  81. package/dist/gateway/static/root/assets/skills-page-C0KVNC0o.js +2 -0
  82. package/dist/gateway/static/root/assets/{system-settings-panel-CMMpS4gB.js → system-settings-panel-Bo9mLewC.js} +1 -1
  83. package/dist/gateway/static/root/assets/{typed-models-lib-JlIzyeQ_.js → typed-models-lib-Bp6wXydR.js} +1 -1
  84. package/dist/gateway/static/root/assets/use-channel-catalog-BCBfSoHx.js +1 -0
  85. package/dist/gateway/static/root/assets/{user-profile-settings-panel-CZVtp4He.js → user-profile-settings-panel-NcG1KFUf.js} +1 -1
  86. package/dist/gateway/static/root/assets/{utils-Lv9XYntY.js → utils-BNtcPshb.js} +1 -1
  87. package/dist/gateway/static/root/assets/voice-api-key-field-ByppRAVD.js +1 -0
  88. package/dist/gateway/static/root/assets/work-item-detail-page-5wdg0Xmy.js +1 -0
  89. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-BiygC8WQ.js → workflow-run-setup-panel-eKBZIV-N.js} +2 -2
  90. package/dist/gateway/static/root/assets/{workflow-start-dialog-CC47dSwh.js → workflow-start-dialog-B6qfU5GE.js} +1 -1
  91. package/dist/gateway/static/root/assets/workflows-page-BtLILZtw.js +18 -0
  92. package/dist/gateway/static/root/favicon-16x16.png +0 -0
  93. package/dist/gateway/static/root/favicon-32x32.png +0 -0
  94. package/dist/gateway/static/root/favicon-icon.svg +182 -27
  95. package/dist/gateway/static/root/favicon.ico +0 -0
  96. package/dist/gateway/static/root/favicon.png +0 -0
  97. package/dist/gateway/static/root/favicon.svg +182 -27
  98. package/dist/gateway/static/root/index.html +5 -6
  99. package/dist/gateway/static/root/logo-dark.svg +179 -15
  100. package/dist/gateway/static/root/logo.svg +179 -15
  101. package/dist/gateway/static/root/pwa-192x192.png +0 -0
  102. package/dist/gateway/static/root/pwa-512x512.png +0 -0
  103. package/dist/package.js +1 -1
  104. package/dist/src/agent/starter-agents.d.ts +1 -0
  105. package/dist/src/agent/starter-agents.js +38 -11
  106. package/dist/src/agent/starter-agents.js.map +1 -1
  107. package/dist/src/cli/commands/gateway/probe.js +6 -6
  108. package/dist/src/cli/commands/gateway/probe.js.map +1 -1
  109. package/dist/src/cli/commands/onboard.js +2 -2
  110. package/dist/src/cli/commands/onboard.js.map +1 -1
  111. package/dist/src/cli/commands/tui.js +11 -4
  112. package/dist/src/cli/commands/tui.js.map +1 -1
  113. package/dist/src/cli/utils/gateway-client-options.d.ts +3 -0
  114. package/dist/src/cli/utils/gateway-client-options.js +2 -1
  115. package/dist/src/cli/utils/gateway-client-options.js.map +1 -1
  116. package/dist/src/cli/utils/gateway-client.d.ts +6 -3
  117. package/dist/src/cli/utils/gateway-client.js +14 -8
  118. package/dist/src/cli/utils/gateway-client.js.map +1 -1
  119. package/dist/src/commands/agents.config.d.ts +2 -0
  120. package/dist/src/commands/agents.config.js +1 -1
  121. package/dist/src/commands/agents.config.js.map +1 -1
  122. package/dist/src/config/agent-profile.js +1 -1
  123. package/dist/src/config/schema.js +4 -2
  124. package/dist/src/config/schema.js.map +1 -1
  125. package/dist/src/gateway/agents-admin.js +1 -1
  126. package/dist/src/gateway/credential.d.ts +9 -0
  127. package/dist/src/gateway/credential.js +16 -0
  128. package/dist/src/gateway/credential.js.map +1 -0
  129. package/dist/src/gateway/hono/app.d.ts +0 -1
  130. package/dist/src/gateway/hono/app.js +2 -9
  131. package/dist/src/gateway/hono/app.js.map +1 -1
  132. package/dist/src/gateway/hono/middleware/auth.d.ts +0 -2
  133. package/dist/src/gateway/hono/middleware/auth.js +46 -21
  134. package/dist/src/gateway/hono/middleware/auth.js.map +1 -1
  135. package/dist/src/gateway/hono/routes/sessions.js +2 -27
  136. package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
  137. package/dist/src/gateway/server.js +1 -5
  138. package/dist/src/gateway/server.js.map +1 -1
  139. package/dist/src/mcp/channel-bridge.d.ts +2 -1
  140. package/dist/src/mcp/channel-bridge.js +9 -5
  141. package/dist/src/mcp/channel-bridge.js.map +1 -1
  142. package/dist/src/mcp/channel-server.d.ts +2 -1
  143. package/dist/src/mcp/channel-server.js +1 -1
  144. package/dist/src/mcp/channel-server.js.map +1 -1
  145. package/dist/src/mcp/gateway-http-client.d.ts +3 -2
  146. package/dist/src/mcp/gateway-http-client.js +9 -7
  147. package/dist/src/mcp/gateway-http-client.js.map +1 -1
  148. package/dist/src/tui/backends/gateway-sse-backend.d.ts +3 -2
  149. package/dist/src/tui/backends/gateway-sse-backend.js +45 -41
  150. package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
  151. package/dist/src/tui/tui-types.d.ts +3 -2
  152. package/dist/src/tui/tui-types.js.map +1 -1
  153. package/dist/src/tui/tui.js +1 -1
  154. package/dist/src/tui/tui.js.map +1 -1
  155. package/package.json +1 -1
  156. package/dist/gateway/static/root/assets/agents-NxdjoWyZ.js +0 -2
  157. package/dist/gateway/static/root/assets/app-management-settings-panel-DzSs6MPu.js +0 -1
  158. package/dist/gateway/static/root/assets/apps-page-CPXB5VC2.js +0 -1
  159. package/dist/gateway/static/root/assets/automations-page-C3atGynq.js +0 -4
  160. package/dist/gateway/static/root/assets/browser-settings-page-C7zmHlV-.js +0 -8
  161. package/dist/gateway/static/root/assets/capability-presets-api-CaDTlpYK.js +0 -1
  162. package/dist/gateway/static/root/assets/channel-recipient-api-BrW5Kcgc.js +0 -1
  163. package/dist/gateway/static/root/assets/channels-settings-BxjagLJ_.js +0 -1
  164. package/dist/gateway/static/root/assets/connectors-page-B817lbgc.js +0 -2
  165. package/dist/gateway/static/root/assets/desktop-pet-DuRXndhI.js +0 -1
  166. package/dist/gateway/static/root/assets/dreaming-settings-C8abJcaI.js +0 -3
  167. package/dist/gateway/static/root/assets/extension-settings-page-BI-cyHrx.js +0 -1
  168. package/dist/gateway/static/root/assets/fetch-BMjlW0g1.js +0 -1
  169. package/dist/gateway/static/root/assets/gateway-config-api-DROtL9kG.js +0 -1
  170. package/dist/gateway/static/root/assets/gateway-settings-CyTSzCnt.js +0 -1
  171. package/dist/gateway/static/root/assets/goal-create-dialog-BNh1ojty.js +0 -1
  172. package/dist/gateway/static/root/assets/goal-detail-page-D5cZ4icP.js +0 -4
  173. package/dist/gateway/static/root/assets/goals-config-api-GqxVsoOo.js +0 -1
  174. package/dist/gateway/static/root/assets/goals-page-CUznMB7d.js +0 -2
  175. package/dist/gateway/static/root/assets/goals-settings-dtOWKJda.js +0 -1
  176. package/dist/gateway/static/root/assets/heartbeat-settings-D5NztVps.js +0 -1
  177. package/dist/gateway/static/root/assets/index-CxR00gsL.js +0 -257
  178. package/dist/gateway/static/root/assets/index-EKmpPvZj.css +0 -1
  179. package/dist/gateway/static/root/assets/logs-page-DgEJ4PeH.js +0 -2
  180. package/dist/gateway/static/root/assets/models-hub-panel-9zCYTThv.js +0 -2
  181. package/dist/gateway/static/root/assets/notes-page-D7iT2jV1.js +0 -1
  182. package/dist/gateway/static/root/assets/notes-workbench-BuAkwok0.js +0 -2
  183. package/dist/gateway/static/root/assets/project-detail-page-BOKQPdB5.js +0 -1
  184. package/dist/gateway/static/root/assets/projects-page-ALHgCAPZ.js +0 -1
  185. package/dist/gateway/static/root/assets/remote-access-hub-BnowluoZ.js +0 -1
  186. package/dist/gateway/static/root/assets/sessions-page-DnW0ohat.js +0 -1
  187. package/dist/gateway/static/root/assets/settings-advanced-gate-N9Pc7kmG.js +0 -1
  188. package/dist/gateway/static/root/assets/settings-page-s-THg5D2.js +0 -1
  189. package/dist/gateway/static/root/assets/setup-status-panel-UNMcmL2b.js +0 -1
  190. package/dist/gateway/static/root/assets/share-preview-page-CfO4RaVP.js +0 -2
  191. package/dist/gateway/static/root/assets/shares-settings-Cv86ZsIh.js +0 -1
  192. package/dist/gateway/static/root/assets/skill-reload-api-B-9sluy3.js +0 -1
  193. package/dist/gateway/static/root/assets/skills-page-D4ifOU_G.js +0 -2
  194. package/dist/gateway/static/root/assets/url-DFGNSBNT.js +0 -1
  195. package/dist/gateway/static/root/assets/use-channel-catalog-CPdJ0o0i.js +0 -1
  196. package/dist/gateway/static/root/assets/voice-api-key-field-7ZjSxIDE.js +0 -1
  197. package/dist/gateway/static/root/assets/work-item-detail-page-BYqDWx6I.js +0 -1
  198. package/dist/gateway/static/root/assets/workflows-page-BP3hha0s.js +0 -18
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  <p align="center">
9
9
  <strong>Turn goals into loops.</strong><br />
10
10
  Keep what matters moving.<br />
11
- Start with a chat. Keep a project alive. Capture notes from your phone. Let automations turn repeated follow-up into a private, local-first data flywheel.
11
+ xopc is a self-hosted, local-first personal AI runtime connecting models, agents, durable state, workflows, automations, and every surface you use. You own the data, keys, and environment.
12
12
  </p>
13
13
 
14
14
  <p align="center">
@@ -35,8 +35,8 @@
35
35
  <details>
36
36
  <summary><strong>Table of Contents</strong></summary>
37
37
 
38
- - [Built for](#built-for)
39
- - [From chat to loops](#from-chat-to-loops)
38
+ - [What xopc is](#what-xopc-is)
39
+ - [One runtime, four layers](#one-runtime-four-layers)
40
40
  - [Get started](#get-started)
41
41
  - [Why xopc](#why-xopc)
42
42
  - [How xopc compares](#how-xopc-compares)
@@ -53,28 +53,24 @@
53
53
 
54
54
  ---
55
55
 
56
- ## Built for
56
+ ## What xopc is
57
57
 
58
- - **People who start simple** open a local chat first, then grow into projects, notes, channels, connectors, and automations when the work needs them.
59
- - **Long-running goals, not one-shot chats** — carry goals, blockers, decisions, and next actions across sessions. Projects grow without starting over.
60
- - **Your agent, reachable anywhere** — CLI, TUI, Web, Desktop, mobile, Telegram, WeChat, Feishu/Lark. Pair the mobile app by QR code; data stays in your runtime.
61
- - **Local-first, your keys** — bring your own keys, mix cloud/local models, add skills/extensions, and keep everything under **`~/.xopc/`**.
58
+ xopc is not a wrapper around one model, and it is more than another chat UI. It is a **self-hosted, local-first personal AI runtime** that lets models, agents, data, and triggers work together in one environment.
62
59
 
63
- ## From chat to loops
60
+ Start with a local assistant using your own model keys. When the work becomes durable, add explicit projects and goals, capture inputs as notes, break complex work into observable workflows, and trigger follow-up with schedules or webhooks. Every surface connects to the same runtime instead of creating another isolated bot.
64
61
 
65
- You do not need a perfect system on day one. xopc is designed to become more useful as you keep using it.
62
+ ## One runtime, four layers
66
63
 
67
- | Stage | What you do | What xopc starts to do |
64
+ | Layer | What xopc provides | What it solves |
68
65
  | --- | --- | --- |
69
- | **Chat** | Ask questions, think through work, make decisions. | Gives you a local AI assistant that uses your model keys and local state. |
70
- | **Project** | Ask it to keep track of one active goal. | Carries the goal, current state, blockers, and next actions across sessions. |
71
- | **Notes & ideas** | Drop rough notes, progress updates, links, and feedback as they happen. | Turns scattered inputs into context the assistant can reuse. |
72
- | **Connectors** | Pair the mobile app by QR code, or use the same assistant on desktop, terminal, messengers, and gateway APIs. | Lets you chat and capture notes from anywhere while the agent and data stay in your xopc runtime. |
73
- | **Automations** | Schedule reviews, reminders, summaries, and workflow runs. | Keeps important work resurfacing and closes the loop without you starting every turn manually. |
66
+ | **Runtime & ownership** | Self-hosted gateway, local state, BYOK, cloud and local models | You control data, cost, deployment, and availability |
67
+ | **Durable state** | Persistent sessions, projects, goals, notes, workspaces, and agent memory policy | Resume work without reconstructing it every time |
68
+ | **Execution & initiative** | Tools, skills, subagents, observable workflows, and scheduled/manual/webhook automations | Move from advice to execution, review, and follow-up |
69
+ | **Access everywhere** | CLI, TUI, web, desktop, iOS/Android, Telegram, WeChat, Feishu/Lark, and HTTP/SSE APIs | Use one system wherever the work happens |
74
70
 
75
- **That is the loop:** context comes in, xopc helps decide the next move, you act, feedback comes back, and the system gets better at keeping the project moving.
71
+ These pieces are composable rather than a fixed funnel: work on code in the terminal, dictate a note on mobile, inspect goals and failed runs from a project, generate a daily digest with an automation, then resume the same session from any surface.
76
72
 
77
- **Your timeline:** Day 1 chat and track a project. This week → feed it notes and progress. When it sticks add mobile, messengers, desktop. As work repeats → add automations.
73
+ See how the pieces form a resumable, triggerable, and inspectable loop in [The Continuous Work Model](https://xopcai.github.io/xopc/concepts/loops).
78
74
 
79
75
  ---
80
76
 
@@ -155,12 +151,12 @@ Windows git install: `& ([scriptblock]::Create((irm https://xopc.ai/install.ps1)
155
151
 
156
152
  ## Why xopc
157
153
 
158
- - 🔁 **From chat to durable loops** — Projects, notes, connectors, and automations compound into momentum across sessions.
159
- - 🏠 **Your machine** — Data and config under **`~/.xopc/`**. No mandatory cloud or surprise bills.
154
+ - 🧠 **Durable state, not one context window** — Sessions, projects, goals, notes, workspaces, and run history have explicit ownership and can be resumed.
155
+ - 🏠 **Runs in your environment** — Config and local state live under **`~/.xopc/`** by default; you choose deployment, backups, and remote access.
160
156
  - 🔑 **Bring your own keys** — OpenAI, Anthropic, Google, DeepSeek, Ollama, LM Studio, vLLM, and **20+** providers. Mix cloud and local models.
161
- - 📱 **Reachable anywhere** — Pair the [mobile app](./apps/mobile-expo) by QR code, or chat from desktop, terminal, browser, Telegram, WeChat, and Feishu/Lark. Data stays in your runtime.
162
- - 🧩 **Grows with you** — **`xopc skills install`** / **`xopc extensions install`** for tools, channels, and UI panels; multi-agent routing per context.
163
- - **Proactive** — Automations for scheduled summaries, reminders, and workflow runs; subagent fan-out for parallel tasks.
157
+ - ⚙️ **Executes and triggers work** — Tools and workflows perform tasks; scheduled, manual, and webhook automations run agents or workflows later.
158
+ - 📡 **One runtime, many surfaces** — Desktop, terminal, web, mobile, and messengers share agents, sessions, and project state.
159
+ - 🧩 **Explicit capability boundaries** — Each agent can have its own identity, model roles, workspace, tool policy, skills, memory, and boundaries, then grow through extensions.
164
160
 
165
161
  ## How xopc compares
166
162
 
@@ -169,7 +165,7 @@ xopc is not another chat UI. It is the local-first system layer for people who w
169
165
  | Product | Best at | Why use xopc too |
170
166
  | --- | --- | --- |
171
167
  | **Codex** | Software development in terminal, IDE, and cloud tasks | xopc is broader than repo work: long-running goals, BYOK/local models, scheduled loops, gateway APIs, desktop/web/mobile, and messengers share one local state. |
172
- | **Claude Code** | Project-level coding: codebase navigation, edits, tests, git workflows | xopc is a personal Agent OS for coding plus life/work loops; it can coordinate models, channels, skills, workflows, and automations outside one codebase. |
168
+ | **Claude Code** | Project-level coding: codebase navigation, edits, tests, git workflows | xopc is a personal AI runtime for coding and long-running work; it coordinates models, channels, skills, workflows, and automations outside one codebase. |
173
169
  | **Qoder / QoderWork** | Agentic coding platform plus local-first office work companion | xopc is open-source and hackable, with explicit local state under **`~/.xopc/`**, configurable providers, self-hosted gateway, and extension points. |
174
170
  | **WorkBuddy** | Office deliverables such as reports, decks, spreadsheets, research, and data analysis | xopc is for users who want to own the runtime: bring your own keys, mix local/cloud models, wire channels, and keep long-term project context locally. |
175
171
 
@@ -236,7 +232,8 @@ Full reference: **[Configuration](https://xopcai.github.io/xopc/configuration)**
236
232
  | Guide | Description |
237
233
  | --- | --- |
238
234
  | [Getting started](https://xopcai.github.io/xopc/getting-started) | Install, onboard, first chat |
239
- | [From Chat to Loops](https://xopcai.github.io/xopc/concepts/loops) | How xopc grows from simple chat into project context, connectors, and automations |
235
+ | [The Continuous Work Model](https://xopcai.github.io/xopc/concepts/loops) | How state, execution, and triggers form resumable, inspectable work loops |
236
+ | [Projects, Goals, and Notes](https://xopcai.github.io/xopc/projects-goals-notes) | Organize long-running work with explicit objects instead of one huge transcript |
240
237
  | [Configuration](https://xopcai.github.io/xopc/configuration) | `xopc.json` reference |
241
238
  | [CLI](https://xopcai.github.io/xopc/cli) | Commands and flags |
242
239
  | [Channels](https://xopcai.github.io/xopc/channels) | Telegram, WeChat, Feishu |
@@ -255,7 +252,7 @@ Also: [Tools](https://xopcai.github.io/xopc/tools) · [Mobile app](https://xopca
255
252
 
256
253
  **What is the fastest way to try it?** — [Desktop app](#get-started) for GUI users, or `xopc onboard --quick && xopc` for terminal.
257
254
 
258
- **How is this different from another chat UI?** — xopc grows from chat into goal loops: project context, notes, connectors, and automations across multiple surfaces.
255
+ **How is this different from another chat UI?** — Chat is only one surface. xopc also manages agents, persistent sessions, projects/goals, notes, workspaces, workflows, automations, and multi-surface access.
259
256
 
260
257
  **Can I use it from my phone or messengers?** — Yes. Pair the [mobile app](./apps/mobile-expo) by QR code, or configure Telegram, WeChat, or Feishu/Lark via the gateway.
261
258
 
package/README.zh-CN.md CHANGED
@@ -7,8 +7,8 @@
7
7
 
8
8
  <p align="center">
9
9
  <strong>把目标变成循环。</strong><br />
10
- 让真正重要的事情持续向前。<br />
11
- 从一次普通对话开始,把项目持续托管给 xopc;在手机上随手记录 note/idea,再用自动化养成私有的本地 AI 数据飞轮。
10
+ 让重要的事持续向前。<br />
11
+ xopc 是自托管、本地优先的个人 AI 运行时:统一连接模型、Agent、持续状态、工作流、自动化与多端入口。你掌控数据、密钥和运行环境。
12
12
  </p>
13
13
 
14
14
  <p align="center">
@@ -35,8 +35,8 @@
35
35
  <details>
36
36
  <summary><strong>目录</strong></summary>
37
37
 
38
- - [适合谁](#适合谁)
39
- - [从聊天到数据飞轮](#从聊天到数据飞轮)
38
+ - [xopc 是什么](#xopc-是什么)
39
+ - [一套运行时,四类能力](#一套运行时四类能力)
40
40
  - [快速开始](#快速开始)
41
41
  - [为什么选 xopc](#为什么选-xopc)
42
42
  - [和 Codex / Claude Code / QoderWork / WorkBuddy 的区别](#和-codex--claude-code--qoderwork--workbuddy-的区别)
@@ -53,28 +53,24 @@
53
53
 
54
54
  ---
55
55
 
56
- ## 适合谁
56
+ ## xopc 是什么
57
57
 
58
- - **想先简单用起来的人** —— 先本地开聊;项目变复杂后,再逐步加笔记、通道、连接器和自动化。
59
- - **长期目标持续跟踪** —— 目标、决策、阻塞点、下⼀步不会断在一次次聊天里。
60
- - **自己的 Agent,随时可用** —— CLI、TUI、网页、桌面、手机、Telegram、微信和飞书/Lark。手机 App 扫码连接,数据留在你的环境里。
61
- - **本地优先,用自己的密钥** —— 用你自己的 API Key,云端/本地模型混用,支持技能和扩展,数据默认保存在 **`~/.xopc/`**。
58
+ xopc 不是某个模型的套壳,也不只是另一个聊天界面。它是一套**自托管、本地优先的个人 AI 运行时**:负责让模型、Agent、数据和触发器在同一个环境里协作。
62
59
 
63
- ## 从聊天到数据飞轮
60
+ 你可以先把它当作一个使用自有密钥的本地助手;也可以进一步把真实项目交给它,用明确的目标和下一步保存状态,用笔记收集输入,用工作流拆解复杂任务,再用定时或 webhook 自动化触发后续工作。所有入口连接的是同一套运行时,而不是几个互相失忆的机器人。
64
61
 
65
- 你不需要第一天就搭好复杂系统。xopc 的价值会随着你的使用逐步长出来。
62
+ ## 一套运行时,四类能力
66
63
 
67
- | 阶段 | 你做什么 | xopc 开始帮你什么 |
64
+ | 层次 | xopc 提供什么 | 解决什么问题 |
68
65
  | --- | --- | --- |
69
- | **聊天** | 问问题、讨论想法、做判断。 | 先有一个使用你自己模型配置、状态保存在本地的 AI 助手。 |
70
- | **项目** | 让它持续跟进一个真实目标。 | 记住目标、当前状态、阻塞点和下一步。 |
71
- | **笔记 / 想法** | 随手丢进展、反馈、链接、灵感和复盘。 | 把零散输入沉淀成之后能继续使用的上下文。 |
72
- | **外部入口** | 手机 App 扫码连接,也可以在桌面、终端、网页、IM 和 gateway API 里继续使用同一个助手。 | 不在电脑前也能对话、记录 note/idea;Agent 和数据仍在你的 xopc 环境里。 |
73
- | **自动化** | 设置定时复盘、提醒、摘要和工作流运行。 | 重要事项会自己回到视野里,不完全依赖你想起来再问。 |
66
+ | **运行与所有权** | 自托管 gateway、本地状态目录、自有 API Key、云端/本地模型 | 数据、成本与可用性由你掌控 |
67
+ | **持续状态** | 持久会话、项目、目标、笔记、工作区与 Agent 记忆策略 | 工作可以恢复,不必每次从零解释 |
68
+ | **执行与主动性** | 工具、技能、子 Agent、可观测工作流、定时/手动/webhook 自动化 | 从“给建议”走向“执行、检查并继续” |
69
+ | **随处接入** | CLI、TUI、网页、桌面、iOS/Android、Telegram、微信、飞书/Lark、HTTP/SSE API | 在工作发生的地方使用同一个系统 |
74
70
 
75
- **这就是循环:** 上下文进来,xopc 帮你判断下一步;你去行动,再把结果和反馈带回来;系统越用越知道该如何继续推进。
71
+ 典型用法不是固定漏斗,而是按需组合:在终端处理代码,在手机上语音速记到 Notes,在项目页查看目标、阻塞与失败的工作流,在每天的自动化中生成摘要,最后从任意入口继续同一个会话。
76
72
 
77
- **你的时间线:** 第一天 → 聊天 + 跟进项目。第一周 → 喂进展和笔记。用顺手后 → 加手机、IM、桌面。事情重复时 → 加自动化。
73
+ 了解这些部件如何形成可恢复、可触发、可检查的闭环:[持续工作模型](https://xopcai.github.io/xopc/zh/concepts/loops)。
78
74
 
79
75
  ---
80
76
 
@@ -155,12 +151,12 @@ Windows 源码安装:`& ([scriptblock]::Create((irm https://xopc.ai/install.ps
155
151
 
156
152
  ## 为什么选 xopc
157
153
 
158
- - 🔁 **从聊天自然长成循环**项目、笔记、外部入口和自动化逐步接上,让长期事情持续滚动。
159
- - 🏠 **你的机器**配置与数据在 **`~/.xopc/`**,无强制云端、无意外账单。
154
+ - 🧠 **有状态,不只是一轮上下文**会话、项目、目标、笔记、工作区和运行记录都有明确归属,长期工作可以恢复。
155
+ - 🏠 **运行在你的环境里**配置与本地状态默认在 **`~/.xopc/`**;你决定如何部署、备份和远程访问。
160
156
  - 🔑 **使用自己的密钥** — OpenAI、Anthropic、Google、DeepSeek、Ollama、LM Studio、vLLM 等 **20+** 厂商;云端和本地模型可混用。
161
- - 📱 **随时可用**手机 App 扫码连接,也可从桌面、终端、网页、Telegram、微信和飞书/Lark 继续对话。数据不出你的环境。
162
- - 🧩 **可按需扩展****`xopc skills install`** / **`xopc extensions install`** 扩展工具、频道与 UI;多 Agent 可按场景隔离。
163
- - **支持主动任务**自动化做定时摘要、提醒和工作流运行;子 Agent 扇出并行任务。
157
+ - ⚙️ **能执行,也能主动触发**工具和工作流负责执行;自动化用定时、手动或 webhook 触发 Agent 与工作流。
158
+ - 📡 **同一个运行时,多种入口**桌面、终端、网页、手机与 IM 共享 Agent、会话和项目状态。
159
+ - 🧩 **能力边界可配置**每个 Agent 可以拥有独立的身份、模型角色、工作区、工具策略、技能、记忆与边界,并通过扩展继续增加能力。
164
160
 
165
161
  ## 和 Codex / Claude Code / QoderWork / WorkBuddy 的区别
166
162
 
@@ -169,7 +165,7 @@ xopc 不是又一个聊天 UI。它更像一个本地优先的系统层:让同
169
165
  | 产品 | 更擅长 | 为什么还需要 xopc |
170
166
  | --- | --- | --- |
171
167
  | **Codex** | 终端、IDE 和云端的软件开发任务 | xopc 不只服务一个代码仓库:长期目标、BYOK/本地模型、定时循环、Gateway API、桌面/网页/手机和 IM 都共享同一套本地状态。 |
172
- | **Claude Code** | 项目级编码:读代码、改文件、跑测试、处理 git 工作流 | xopc 是面向个人长期工作的 Agent OS:既能服务 coding,也能协调模型、频道、技能、工作流和自动化。 |
168
+ | **Claude Code** | 项目级编码:读代码、改文件、跑测试、处理 git 工作流 | xopc 是面向个人长期工作的 AI 运行时:既能服务 coding,也能协调模型、频道、技能、工作流和自动化。 |
173
169
  | **Qoder / QoderWork** | Agentic coding 平台与本地优先办公助手 | xopc 开源、可自托管、可改造,状态明确放在 **`~/.xopc/`**,支持自选 provider、自托管 gateway 和扩展机制。 |
174
170
  | **WorkBuddy** | 办公交付物:报告、PPT、表格、调研、数据分析 | xopc 更适合想掌控运行环境的人:使用自己的 key,混用本地/云端模型,接入 IM,并把长期项目上下文留在本地。 |
175
171
 
@@ -185,7 +181,7 @@ xopc 不是又一个聊天 UI。它更像一个本地优先的系统层:让同
185
181
  | **TUI** | `xopc` 或 `xopc tui`(远程:`xopc tui --url …`) | 全键盘、流式输出,最快终端路径 |
186
182
  | **CLI** | `xopc agent -i` / `xopc agent -m "…"` | 脚本、最小终端环境 |
187
183
  | **网页** | `xopc gateway` → 打开控制台地址 | 聊天、设置、日志 |
188
- | **手机** | [移动端 App](./apps/mobile-expo) + 网关扫码配对([移动端 App](https://xopcai.github.io/xopc/zh/mobile-app)、[远程访问](https://xopcai.github.io/xopc/zh/remote-access)) | 在 iOS/Android 上继续对话、记录 note/idea;Agent 仍运行在你的电脑或本地环境里 |
184
+ | **手机** | [移动端 App](./apps/mobile-expo) + 网关扫码配对([移动端 App](https://xopcai.github.io/xopc/zh/mobile-app)、[远程访问](https://xopcai.github.io/xopc/zh/remote-access)) | 在 iOS/Android 上继续对话,记录文字、语音、图片和附件;Agent 仍运行在你的电脑或本地环境里 |
189
185
  | **即时通讯** | 配置 `channels.*` 并启动网关 | Telegram、微信、飞书/Lark |
190
186
 
191
187
  ---
@@ -236,7 +232,8 @@ xopc extensions dev ./my-extension
236
232
  | 指南 | 说明 |
237
233
  | --- | --- |
238
234
  | [快速开始](https://xopcai.github.io/xopc/zh/getting-started) | 安装、向导、第一次对话 |
239
- | [从聊天到数据飞轮](https://xopcai.github.io/xopc/zh/concepts/loops) | xopc 如何从简单聊天逐步长成项目上下文、外部入口和自动化循环 |
235
+ | [持续工作模型](https://xopcai.github.io/xopc/zh/concepts/loops) | 状态、执行与触发如何组成可恢复、可检查的工作闭环 |
236
+ | [项目、目标与笔记](https://xopcai.github.io/xopc/zh/projects-goals-notes) | 用明确的数据对象组织长期工作,而不是依赖超长聊天记录 |
240
237
  | [配置](https://xopcai.github.io/xopc/zh/configuration) | `xopc.json` 字段 |
241
238
  | [CLI](https://xopcai.github.io/xopc/zh/cli) | 命令与参数 |
242
239
  | [频道](https://xopcai.github.io/xopc/zh/channels) | Telegram、微信、飞书 |
@@ -255,7 +252,7 @@ xopc extensions dev ./my-extension
255
252
 
256
253
  **最快怎么试用?** — [桌面端](#快速开始) 适合 GUI 用户,终端用户执行 `xopc onboard --quick && xopc`。
257
254
 
258
- **它和普通聊天 UI 有什么区别?** — xopc 从聊天长成目标循环:项目上下文、笔记、外部入口、自动化,跨多个入口继续工作。
255
+ **它和普通聊天 UI 有什么区别?** — 聊天只是入口。xopc 还管理 Agent、持久会话、项目/目标、笔记、工作区、工作流、自动化和多端连接。
259
256
 
260
257
  **能在手机或即时通讯里用吗?** — 可以。用 [移动端 App](./apps/mobile-expo) 扫码连接,或配置 Telegram、微信、飞书/Lark。
261
258
 
Binary file
Binary file
Binary file
Binary file
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "xopc Browser Bridge",
4
- "version": "0.0.130",
4
+ "version": "0.0.132",
5
5
  "description": "Browser automation bridge for xopc — executes commands in Chrome via the local xopc daemon.",
6
6
  "permissions": [
7
7
  "debugger",
@@ -2,7 +2,7 @@
2
2
  "id": "telegram",
3
3
  "name": "Telegram Channel",
4
4
  "description": "Bundled Telegram Bot channel (private workspace sources; ships inside @xopcai/xopc dist/)",
5
- "version": "0.0.130",
5
+ "version": "0.0.132",
6
6
  "kind": "channel",
7
7
  "main": "src/index.js",
8
8
  "channels": [
@@ -1,4 +1,3 @@
1
- import { generateSessionKey } from "../../../../src/chat-commands/session-key.js";
2
1
  import { evaluateAccess } from "../../../../src/channels/security.js";
3
2
  import { resolveWeixinPairingPath } from "../../../../src/channels/pairing/paths.js";
4
3
  import { issuePairingChallenge } from "../../../../src/channels/pairing/pairing-challenge.js";
@@ -11,6 +10,7 @@ import { downloadMediaFromItem } from "../media/media-download.js";
11
10
  import { isDebugMode } from "./debug-mode.js";
12
11
  import { sendMessageWeixin } from "./send.js";
13
12
  import { handleSlashCommand } from "./slash-commands.js";
13
+ import { generateWeixinSessionKeyWithRouting } from "../routing-integration.js";
14
14
  import path from "node:path";
15
15
  import { randomBytes } from "node:crypto";
16
16
  import { mkdir, readFile, writeFile } from "node:fs/promises";
@@ -112,13 +112,10 @@ async function processWeixinInboundMessage(full, deps) {
112
112
  }
113
113
  const ctx = weixinMessageToMsgContext(full, deps.accountId, mediaOpts);
114
114
  const body = ctx.Body?.trim() ?? "";
115
- const sessionKey = generateSessionKey({
116
- source: "weixin",
117
- chatId: senderId,
118
- senderId,
119
- isGroup: false,
120
- accountId: deps.accountId
121
- });
115
+ const sessionKey = generateWeixinSessionKeyWithRouting({
116
+ accountId: deps.accountId,
117
+ senderId
118
+ }, deps.config);
122
119
  const attachments = [];
123
120
  if (mediaOpts.decryptedPicPath) {
124
121
  const buf = await readFile(mediaOpts.decryptedPicPath);
@@ -1 +1 @@
1
- {"version":3,"file":"process-message.js","names":[],"sources":["../../../../../extensions/weixin/src/messaging/process-message.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { randomBytes } from 'node:crypto';\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport type { MessageBus } from '@xopcai/xopc/infra/bus/index.js';\nimport { generateSessionKey } from '@xopcai/xopc/chat-commands/session-key.js';\nimport { issuePairingChallenge, resolveWeixinPairingPath } from '@xopcai/xopc/channels/pairing/index.js';\nimport { evaluateAccess } from '@xopcai/xopc/channels/security.js';\nimport type { WeixinMessage } from '../api/types.js';\nimport { MessageItemType } from '../api/types.js';\nimport type { ResolvedWeixinAccount } from '../auth/accounts.js';\nimport { readFrameworkAllowFromList } from '../auth/pairing.js';\nimport { downloadMediaFromItem } from '../media/media-download.js';\nimport { logger } from '../util/logger.js';\nimport { resolveWeixinRootDir } from '../storage/state-dir.js';\nimport { handleSlashCommand } from './slash-commands.js';\nimport { sendMessageWeixin } from './send.js';\nimport {\n setContextToken,\n weixinMessageToMsgContext,\n isMediaItem,\n type WeixinInboundMediaOpts,\n} from './inbound.js';\nimport { isDebugMode } from './debug-mode.js';\n\nfunction extractTextBody(itemList?: import('../api/types.js').MessageItem[]): string {\n if (!itemList?.length) return '';\n for (const item of itemList) {\n if (item.type === MessageItemType.TEXT && item.text_item?.text != null) {\n return String(item.text_item.text);\n }\n }\n return '';\n}\n\nfunction mergeAllowFrom(account: ResolvedWeixinAccount, accountId: string): string[] {\n const store = readFrameworkAllowFromList(accountId);\n return [...new Set([...(account.allowFrom ?? []).map(String), ...store])];\n}\n\nasync function saveMediaBuffer(\n buffer: Buffer,\n contentType?: string,\n _subdir?: string,\n maxBytes?: number,\n originalFilename?: string,\n): Promise<{ path: string }> {\n const cap = maxBytes ?? 100 * 1024 * 1024;\n if (buffer.length > cap) {\n throw new Error(`weixin media exceeds max bytes (${cap})`);\n }\n const dir = path.join(resolveWeixinRootDir(), 'media', 'inbound-temp');\n await mkdir(dir, { recursive: true });\n const ext = originalFilename ? path.extname(originalFilename) : contentType?.includes('wav') ? '.wav' : '.bin';\n const fname = `${Date.now()}-${randomBytes(6).toString('hex')}${ext || '.bin'}`;\n const filePath = path.join(dir, fname);\n await writeFile(filePath, buffer);\n return { path: filePath };\n}\n\nexport type ProcessWeixinMessageDeps = {\n accountId: string;\n account: ResolvedWeixinAccount;\n config: Config;\n bus: MessageBus;\n baseUrl: string;\n cdnBaseUrl: string;\n token?: string;\n routeTag?: string;\n};\n\nexport async function processWeixinInboundMessage(\n full: WeixinMessage,\n deps: ProcessWeixinMessageDeps,\n): Promise<void> {\n const receivedAt = Date.now();\n const senderId = full.from_user_id ?? '';\n // Persist ilink context_token before slash handling and DM policy. Pairing only gates the agent\n // pipeline; cron/tools still need a cached token to call sendmessage.\n if (full.context_token?.trim() && senderId) {\n setContextToken(deps.accountId, senderId, full.context_token, { sendToUserId: senderId });\n }\n\n const textBody = extractTextBody(full.item_list);\n\n if (textBody.startsWith('/')) {\n const slashResult = await handleSlashCommand(\n textBody,\n {\n to: full.from_user_id ?? '',\n contextToken: full.context_token,\n baseUrl: deps.baseUrl,\n token: deps.token,\n accountId: deps.accountId,\n log: () => {},\n errLog: () => {},\n },\n receivedAt,\n full.create_time_ms,\n );\n if (slashResult.handled) {\n return;\n }\n }\n\n const mergedAllow = mergeAllowFrom(deps.account, deps.accountId);\n const access = evaluateAccess({\n context: {\n channel: 'weixin',\n accountId: deps.accountId,\n chatId: senderId,\n senderId,\n isGroup: false,\n isDm: true,\n },\n dmPolicy: deps.account.dmPolicy ?? 'open',\n allowFrom: mergedAllow,\n });\n if (!access.allowed) {\n if (access.reason === 'pairing-required' && deps.token) {\n try {\n await issuePairingChallenge({\n channel: 'weixin',\n pairingFilePath: resolveWeixinPairingPath(deps.accountId),\n accountId: deps.accountId,\n senderId,\n senderIdLine: `Your Weixin user id: ${senderId}`,\n sendPairingReply: async (text) => {\n await sendMessageWeixin({\n to: senderId,\n text,\n opts: {\n baseUrl: deps.baseUrl,\n token: deps.token,\n contextToken: full.context_token,\n routeTag: deps.routeTag,\n },\n });\n },\n onReplyError: (err) => {\n logger.warn(`weixin pairing reply failed from=${senderId} err=${String(err)}`);\n },\n });\n } catch (err) {\n logger.warn(`weixin pairing prompt failed from=${senderId} err=${String(err)}`);\n }\n } else {\n logger.info(\n `weixin: dropped message from=${senderId} dmPolicy=${deps.account.dmPolicy ?? 'open'} reason=${access.reason ?? 'not allowed'}`,\n );\n }\n return;\n }\n\n const mediaOpts: WeixinInboundMediaOpts = {};\n\n const hasDownloadableMedia = (m?: { encrypt_query_param?: string; full_url?: string }) =>\n Boolean(m?.encrypt_query_param || m?.full_url);\n\n const mainMediaItem =\n full.item_list?.find(\n (i) => i.type === MessageItemType.IMAGE && hasDownloadableMedia(i.image_item?.media),\n ) ??\n full.item_list?.find(\n (i) => i.type === MessageItemType.VIDEO && hasDownloadableMedia(i.video_item?.media),\n ) ??\n full.item_list?.find(\n (i) => i.type === MessageItemType.FILE && hasDownloadableMedia(i.file_item?.media),\n ) ??\n full.item_list?.find(\n (i) =>\n i.type === MessageItemType.VOICE &&\n hasDownloadableMedia(i.voice_item?.media) &&\n !i.voice_item?.text,\n );\n const refMediaItem = !mainMediaItem\n ? full.item_list?.find(\n (i) =>\n i.type === MessageItemType.TEXT &&\n i.ref_msg?.message_item &&\n isMediaItem(i.ref_msg.message_item!),\n )?.ref_msg?.message_item\n : undefined;\n\n const mediaItem = mainMediaItem ?? refMediaItem;\n if (mediaItem) {\n const label = refMediaItem ? 'ref' : 'inbound';\n const downloaded = await downloadMediaFromItem(mediaItem, {\n cdnBaseUrl: deps.cdnBaseUrl,\n saveMedia: saveMediaBuffer,\n log: (m) => logger.debug(m),\n errLog: (m) => logger.warn(m),\n label,\n });\n Object.assign(mediaOpts, downloaded);\n }\n\n const ctx = weixinMessageToMsgContext(full, deps.accountId, mediaOpts);\n const body = ctx.Body?.trim() ?? '';\n\n const sessionKey = generateSessionKey({\n source: 'weixin',\n chatId: senderId,\n senderId,\n isGroup: false,\n accountId: deps.accountId,\n });\n\n const attachments: Array<{ type: string; mimeType?: string; data?: string; name?: string }> = [];\n\n if (mediaOpts.decryptedPicPath) {\n const buf = await readFile(mediaOpts.decryptedPicPath);\n attachments.push({\n type: 'image',\n mimeType: 'image/jpeg',\n data: buf.toString('base64'),\n name: path.basename(mediaOpts.decryptedPicPath),\n });\n } else if (mediaOpts.decryptedVoicePath) {\n const buf = await readFile(mediaOpts.decryptedVoicePath);\n attachments.push({\n type: 'audio',\n mimeType: mediaOpts.voiceMediaType ?? 'audio/wav',\n data: buf.toString('base64'),\n name: path.basename(mediaOpts.decryptedVoicePath),\n });\n } else if (mediaOpts.decryptedFilePath) {\n const buf = await readFile(mediaOpts.decryptedFilePath);\n attachments.push({\n type: 'file',\n mimeType: mediaOpts.fileMediaType ?? 'application/octet-stream',\n data: buf.toString('base64'),\n name: path.basename(mediaOpts.decryptedFilePath),\n });\n } else if (mediaOpts.decryptedVideoPath) {\n const buf = await readFile(mediaOpts.decryptedVideoPath);\n attachments.push({\n type: 'file',\n mimeType: 'video/mp4',\n data: buf.toString('base64'),\n name: path.basename(mediaOpts.decryptedVideoPath),\n });\n }\n\n const debug = isDebugMode(deps.accountId);\n if (debug) {\n logger.debug(\n `weixin debug inbound from=${senderId} bodyLen=${body.length} attachments=${attachments.length}`,\n );\n }\n\n await deps.bus.publishInbound({\n channel: 'weixin',\n sender_id: senderId,\n chat_id: senderId,\n content: body,\n metadata: {\n accountId: deps.accountId,\n sessionKey,\n messageId: full.message_id != null ? String(full.message_id) : undefined,\n isGroup: false,\n isCommand: body.trim().startsWith('/'),\n contextToken: ctx.context_token,\n },\n attachments: attachments.length ? attachments : undefined,\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"process-message.js","names":[],"sources":["../../../../../extensions/weixin/src/messaging/process-message.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { randomBytes } from 'node:crypto';\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport type { MessageBus } from '@xopcai/xopc/infra/bus/index.js';\nimport { issuePairingChallenge, resolveWeixinPairingPath } from '@xopcai/xopc/channels/pairing/index.js';\nimport { evaluateAccess } from '@xopcai/xopc/channels/security.js';\nimport type { WeixinMessage } from '../api/types.js';\nimport { MessageItemType } from '../api/types.js';\nimport type { ResolvedWeixinAccount } from '../auth/accounts.js';\nimport { readFrameworkAllowFromList } from '../auth/pairing.js';\nimport { downloadMediaFromItem } from '../media/media-download.js';\nimport { logger } from '../util/logger.js';\nimport { resolveWeixinRootDir } from '../storage/state-dir.js';\nimport { handleSlashCommand } from './slash-commands.js';\nimport { sendMessageWeixin } from './send.js';\nimport {\n setContextToken,\n weixinMessageToMsgContext,\n isMediaItem,\n type WeixinInboundMediaOpts,\n} from './inbound.js';\nimport { isDebugMode } from './debug-mode.js';\nimport { generateWeixinSessionKeyWithRouting } from '../routing-integration.js';\n\nfunction extractTextBody(itemList?: import('../api/types.js').MessageItem[]): string {\n if (!itemList?.length) return '';\n for (const item of itemList) {\n if (item.type === MessageItemType.TEXT && item.text_item?.text != null) {\n return String(item.text_item.text);\n }\n }\n return '';\n}\n\nfunction mergeAllowFrom(account: ResolvedWeixinAccount, accountId: string): string[] {\n const store = readFrameworkAllowFromList(accountId);\n return [...new Set([...(account.allowFrom ?? []).map(String), ...store])];\n}\n\nasync function saveMediaBuffer(\n buffer: Buffer,\n contentType?: string,\n _subdir?: string,\n maxBytes?: number,\n originalFilename?: string,\n): Promise<{ path: string }> {\n const cap = maxBytes ?? 100 * 1024 * 1024;\n if (buffer.length > cap) {\n throw new Error(`weixin media exceeds max bytes (${cap})`);\n }\n const dir = path.join(resolveWeixinRootDir(), 'media', 'inbound-temp');\n await mkdir(dir, { recursive: true });\n const ext = originalFilename ? path.extname(originalFilename) : contentType?.includes('wav') ? '.wav' : '.bin';\n const fname = `${Date.now()}-${randomBytes(6).toString('hex')}${ext || '.bin'}`;\n const filePath = path.join(dir, fname);\n await writeFile(filePath, buffer);\n return { path: filePath };\n}\n\nexport type ProcessWeixinMessageDeps = {\n accountId: string;\n account: ResolvedWeixinAccount;\n config: Config;\n bus: MessageBus;\n baseUrl: string;\n cdnBaseUrl: string;\n token?: string;\n routeTag?: string;\n};\n\nexport async function processWeixinInboundMessage(\n full: WeixinMessage,\n deps: ProcessWeixinMessageDeps,\n): Promise<void> {\n const receivedAt = Date.now();\n const senderId = full.from_user_id ?? '';\n // Persist ilink context_token before slash handling and DM policy. Pairing only gates the agent\n // pipeline; cron/tools still need a cached token to call sendmessage.\n if (full.context_token?.trim() && senderId) {\n setContextToken(deps.accountId, senderId, full.context_token, { sendToUserId: senderId });\n }\n\n const textBody = extractTextBody(full.item_list);\n\n if (textBody.startsWith('/')) {\n const slashResult = await handleSlashCommand(\n textBody,\n {\n to: full.from_user_id ?? '',\n contextToken: full.context_token,\n baseUrl: deps.baseUrl,\n token: deps.token,\n accountId: deps.accountId,\n log: () => {},\n errLog: () => {},\n },\n receivedAt,\n full.create_time_ms,\n );\n if (slashResult.handled) {\n return;\n }\n }\n\n const mergedAllow = mergeAllowFrom(deps.account, deps.accountId);\n const access = evaluateAccess({\n context: {\n channel: 'weixin',\n accountId: deps.accountId,\n chatId: senderId,\n senderId,\n isGroup: false,\n isDm: true,\n },\n dmPolicy: deps.account.dmPolicy ?? 'open',\n allowFrom: mergedAllow,\n });\n if (!access.allowed) {\n if (access.reason === 'pairing-required' && deps.token) {\n try {\n await issuePairingChallenge({\n channel: 'weixin',\n pairingFilePath: resolveWeixinPairingPath(deps.accountId),\n accountId: deps.accountId,\n senderId,\n senderIdLine: `Your Weixin user id: ${senderId}`,\n sendPairingReply: async (text) => {\n await sendMessageWeixin({\n to: senderId,\n text,\n opts: {\n baseUrl: deps.baseUrl,\n token: deps.token,\n contextToken: full.context_token,\n routeTag: deps.routeTag,\n },\n });\n },\n onReplyError: (err) => {\n logger.warn(`weixin pairing reply failed from=${senderId} err=${String(err)}`);\n },\n });\n } catch (err) {\n logger.warn(`weixin pairing prompt failed from=${senderId} err=${String(err)}`);\n }\n } else {\n logger.info(\n `weixin: dropped message from=${senderId} dmPolicy=${deps.account.dmPolicy ?? 'open'} reason=${access.reason ?? 'not allowed'}`,\n );\n }\n return;\n }\n\n const mediaOpts: WeixinInboundMediaOpts = {};\n\n const hasDownloadableMedia = (m?: { encrypt_query_param?: string; full_url?: string }) =>\n Boolean(m?.encrypt_query_param || m?.full_url);\n\n const mainMediaItem =\n full.item_list?.find(\n (i) => i.type === MessageItemType.IMAGE && hasDownloadableMedia(i.image_item?.media),\n ) ??\n full.item_list?.find(\n (i) => i.type === MessageItemType.VIDEO && hasDownloadableMedia(i.video_item?.media),\n ) ??\n full.item_list?.find(\n (i) => i.type === MessageItemType.FILE && hasDownloadableMedia(i.file_item?.media),\n ) ??\n full.item_list?.find(\n (i) =>\n i.type === MessageItemType.VOICE &&\n hasDownloadableMedia(i.voice_item?.media) &&\n !i.voice_item?.text,\n );\n const refMediaItem = !mainMediaItem\n ? full.item_list?.find(\n (i) =>\n i.type === MessageItemType.TEXT &&\n i.ref_msg?.message_item &&\n isMediaItem(i.ref_msg.message_item!),\n )?.ref_msg?.message_item\n : undefined;\n\n const mediaItem = mainMediaItem ?? refMediaItem;\n if (mediaItem) {\n const label = refMediaItem ? 'ref' : 'inbound';\n const downloaded = await downloadMediaFromItem(mediaItem, {\n cdnBaseUrl: deps.cdnBaseUrl,\n saveMedia: saveMediaBuffer,\n log: (m) => logger.debug(m),\n errLog: (m) => logger.warn(m),\n label,\n });\n Object.assign(mediaOpts, downloaded);\n }\n\n const ctx = weixinMessageToMsgContext(full, deps.accountId, mediaOpts);\n const body = ctx.Body?.trim() ?? '';\n\n const sessionKey = generateWeixinSessionKeyWithRouting(\n { accountId: deps.accountId, senderId },\n deps.config,\n );\n\n const attachments: Array<{ type: string; mimeType?: string; data?: string; name?: string }> = [];\n\n if (mediaOpts.decryptedPicPath) {\n const buf = await readFile(mediaOpts.decryptedPicPath);\n attachments.push({\n type: 'image',\n mimeType: 'image/jpeg',\n data: buf.toString('base64'),\n name: path.basename(mediaOpts.decryptedPicPath),\n });\n } else if (mediaOpts.decryptedVoicePath) {\n const buf = await readFile(mediaOpts.decryptedVoicePath);\n attachments.push({\n type: 'audio',\n mimeType: mediaOpts.voiceMediaType ?? 'audio/wav',\n data: buf.toString('base64'),\n name: path.basename(mediaOpts.decryptedVoicePath),\n });\n } else if (mediaOpts.decryptedFilePath) {\n const buf = await readFile(mediaOpts.decryptedFilePath);\n attachments.push({\n type: 'file',\n mimeType: mediaOpts.fileMediaType ?? 'application/octet-stream',\n data: buf.toString('base64'),\n name: path.basename(mediaOpts.decryptedFilePath),\n });\n } else if (mediaOpts.decryptedVideoPath) {\n const buf = await readFile(mediaOpts.decryptedVideoPath);\n attachments.push({\n type: 'file',\n mimeType: 'video/mp4',\n data: buf.toString('base64'),\n name: path.basename(mediaOpts.decryptedVideoPath),\n });\n }\n\n const debug = isDebugMode(deps.accountId);\n if (debug) {\n logger.debug(\n `weixin debug inbound from=${senderId} bodyLen=${body.length} attachments=${attachments.length}`,\n );\n }\n\n await deps.bus.publishInbound({\n channel: 'weixin',\n sender_id: senderId,\n chat_id: senderId,\n content: body,\n metadata: {\n accountId: deps.accountId,\n sessionKey,\n messageId: full.message_id != null ? String(full.message_id) : undefined,\n isGroup: false,\n isCommand: body.trim().startsWith('/'),\n contextToken: ctx.context_token,\n },\n attachments: attachments.length ? attachments : undefined,\n });\n}\n"],"mappings":"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"}
@@ -216,6 +216,7 @@ var WeixinChannelPlugin = class {
216
216
  this.workflowProgressUnregister = getWorkflowProgressBroker().registerChannel(createWeixinWorkflowProgressCapability({ getConfig: () => this.cfg }));
217
217
  }
218
218
  async start(options) {
219
+ if (!this.workflowProgressUnregister) await this.registerWorkflowProgressCapability();
219
220
  const ids = options?.accountId ? [options.accountId] : listWeixinAccountIds(this.cfg);
220
221
  for (const accountId of ids) {
221
222
  const account = resolveWeixinAccount(this.cfg, accountId);
@@ -263,6 +264,15 @@ var WeixinChannelPlugin = class {
263
264
  });
264
265
  }
265
266
  async onConfigUpdated(cfg) {
267
+ const routingConfigChanged = !isDeepStrictEqual({
268
+ agents: this.cfg.agents,
269
+ bindings: this.cfg.bindings,
270
+ session: this.cfg.session
271
+ }, {
272
+ agents: cfg.agents,
273
+ bindings: cfg.bindings,
274
+ session: cfg.session
275
+ });
266
276
  const prevWx = this.cfg.channels?.weixin;
267
277
  const nextWx = cfg.channels?.weixin;
268
278
  if (!nextWx || nextWx.enabled !== true) {
@@ -271,7 +281,7 @@ var WeixinChannelPlugin = class {
271
281
  return;
272
282
  }
273
283
  this.cfg = cfg;
274
- if (isDeepStrictEqual(prevWx, nextWx) && this.channelIsRunning(cfg)) return;
284
+ if (!routingConfigChanged && isDeepStrictEqual(prevWx, nextWx) && this.channelIsRunning(cfg)) return;
275
285
  await this.stop();
276
286
  await this.start();
277
287
  }
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","names":[],"sources":["../../../../extensions/weixin/src/plugin.ts"],"sourcesContent":["/**\n * Weixin (WeChat ilink) channel — long-poll getUpdates, QR login, direct messages only.\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 ChannelPlugin,\n ChannelPluginDefaults,\n ChannelPluginInitOptions,\n ChannelPluginReloadMeta,\n ChannelPluginStartOptions,\n ChannelSecurityContext,\n ChannelStreamingAdapter,\n ChatType,\n} from '@xopcai/xopc/channels/plugin-types.js';\nimport { evaluateAccess, resolveDmPolicy } from '@xopcai/xopc/channels/security.js';\nimport { createStandardPairingAdapter } from '@xopcai/xopc/channels/pairing/pairing-store-adapter.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\n\nimport { restoreContextTokens } from './messaging/inbound.js';\nimport { monitorWeixinProvider } from './monitor/monitor.js';\nimport type {\n ChannelCliLoginAdapter,\n ChannelCronDeliveryAdapter,\n ChannelRuntimeActionAdapter,\n} from '@xopcai/xopc/channels/plugins/types.adapters.js';\nimport {\n listWeixinAccountIds,\n resolveWeixinAccount,\n type ResolvedWeixinAccount,\n} from './auth/accounts.js';\nimport { readFrameworkAllowFromList } from './auth/pairing.js';\nimport { createWeixinOutboundHandlers, weixinTextChunker } from './outbound-send.js';\nimport { normalizeWeixinCronDeliveryToResolved } from './delivery-to.js';\nimport { weixinConfigSurface } from './config-surface.js';\nimport { WeixinConfigSchema } from './config-schema.js';\nimport { weixinOnboardAdapter } from './adapters/onboard-cli.js';\n\nconst log = createLogger('WeixinPlugin');\n\nexport class WeixinChannelPlugin implements ChannelPlugin<ResolvedWeixinAccount> {\n readonly id = 'weixin' as const;\n\n readonly reload: ChannelPluginReloadMeta = {\n configPrefixes: ['channels.weixin'],\n };\n\n readonly meta = {\n id: 'weixin',\n label: 'Weixin',\n selectionLabel: 'Weixin (ilink)',\n docsPath: '/channels/weixin',\n blurb: 'WeChat via Tencent ilink bot API (QR login, direct chat).',\n order: 3,\n deferConnectUntilAfterListen: true,\n } as const;\n\n readonly capabilities: ChannelCapabilities = {\n chatTypes: ['direct'] as ChatType[],\n reactions: false,\n threads: false,\n media: true,\n polls: false,\n nativeCommands: false,\n blockStreaming: true,\n };\n\n readonly configSchema = {\n schema: {},\n validate: (raw: unknown) => {\n const r = WeixinConfigSchema.safeParse(raw);\n return r.success ? { ok: true as const } : { ok: false as const, errors: [r.error.message] };\n },\n };\n\n readonly cronDelivery: ChannelCronDeliveryAdapter = {\n async normalizeDeliveryTarget(to, sessionStore) {\n const { chatId, accountId } = await normalizeWeixinCronDeliveryToResolved(to, sessionStore);\n return { chatId, accountId };\n },\n };\n\n readonly onboard = weixinOnboardAdapter;\n\n readonly cliLogin: ChannelCliLoginAdapter = {\n async runLogin(params) {\n const { runWeixinQrLoginCli } = await import('./cli/qr-login.js');\n return runWeixinQrLoginCli({\n configPath: params.configPath,\n verbose: params.verbose,\n timeoutMs: params.timeoutMs,\n account: params.accountId,\n writeConfig: params.writeConfig,\n });\n },\n };\n\n readonly runtimeActions: ChannelRuntimeActionAdapter = {\n runAction: async ({ actionId, accountId, input, locale }) => {\n const zh = locale?.toLowerCase().startsWith('zh') === true;\n if (actionId === 'login.start') {\n const { startWeixinGatewayQrLogin } = await import('./cli/gateway-qr-login.js');\n const inputRecord =\n input && typeof input === 'object' && !Array.isArray(input)\n ? (input as Record<string, unknown>)\n : {};\n const timeoutMs = typeof inputRecord.timeoutMs === 'number' ? inputRecord.timeoutMs : 480_000;\n const start = await startWeixinGatewayQrLogin({\n account: accountId,\n timeoutMs,\n initialConfig: this.cfg,\n onPersisted: async (result) => {\n if (!result.ok) return;\n await this.reloadMonitorsWithConfig(result.config ?? this.cfg, this.bus);\n },\n });\n if (start.ok === false) {\n return { ok: false, message: start.message };\n }\n return {\n ok: true,\n payload: {\n type: 'qr',\n sessionKey: start.sessionKey,\n qrcodeUrl: start.qrcodeUrl,\n statusAction: 'login.status',\n pollIntervalMs: 2500,\n message: zh ? '请使用微信扫描二维码。' : 'Scan the QR code with WeChat.',\n },\n };\n }\n\n if (actionId === 'login.status') {\n const inputRecord =\n input && typeof input === 'object' && !Array.isArray(input)\n ? (input as Record<string, unknown>)\n : {};\n const sessionKey = typeof inputRecord.sessionKey === 'string' ? inputRecord.sessionKey : '';\n if (!sessionKey) return { ok: false, message: zh ? '缺少登录 sessionKey' : 'Missing login sessionKey' };\n const { getWeixinGatewayQrLoginStatus } = await import('./cli/gateway-qr-login.js');\n const status = getWeixinGatewayQrLoginStatus(sessionKey);\n if (status.phase === 'polling') {\n return {\n ok: true,\n payload: {\n type: 'poll',\n phase: 'pending',\n qrcodeUrl: status.qrcodeUrl,\n qrStatus: status.qrStatus,\n message: status.qrStatus,\n },\n };\n }\n if (status.phase === 'done') {\n return {\n ok: true,\n payload: {\n type: 'poll',\n phase: 'done',\n ok: status.ok,\n accountId: status.ok ? status.accountId : undefined,\n message: status.ok === true ? (zh ? '微信登录完成。' : 'Weixin login complete.') : status.message,\n configChanged: status.ok,\n },\n };\n }\n return {\n ok: true,\n payload: {\n type: 'poll',\n phase: 'unknown',\n ok: false,\n message: status.message,\n },\n };\n }\n\n return { ok: false, message: `Unsupported Weixin action: ${actionId}` };\n },\n };\n\n readonly configSurface = weixinConfigSurface;\n\n readonly pairing = createStandardPairingAdapter('weixin');\n\n readonly defaults: ChannelPluginDefaults = {\n queue: { debounceMs: 0 },\n outbound: { textChunkLimit: 4000 },\n streaming: {\n blockStreamingCoalesce: {\n minChars: 200,\n idleMs: 3000,\n },\n },\n };\n\n private bus!: ChannelPluginInitOptions['bus'];\n private cfg!: Config;\n private abortControllers = new Map<string, AbortController>();\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) => listWeixinAccountIds(cfg),\n resolveAccount: (cfg: Config, accountId?: string | null) => resolveWeixinAccount(cfg, accountId),\n isConfigured: async (account: ResolvedWeixinAccount) => account.configured,\n describeAccount: (account: ResolvedWeixinAccount, _cfg: Config) => ({\n accountId: account.accountId,\n channelId: 'weixin',\n name: account.name,\n enabled: account.enabled,\n configured: account.configured,\n status: undefined,\n }),\n };\n\n security = {\n resolveDmPolicy: ({ account }: { account: ResolvedWeixinAccount }) =>\n resolveDmPolicy(account.dmPolicy, 'open'),\n checkAccess: (ctx: ChannelSecurityContext, account: ResolvedWeixinAccount, _cfg: Config) => {\n const allowFrom = [...(account.allowFrom ?? []), ...readFrameworkAllowFromList(account.accountId)];\n return evaluateAccess({\n context: {\n channel: 'weixin',\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 };\n\n outbound = {\n deliveryMode: 'direct' as const,\n chunker: weixinTextChunker,\n chunkerMode: 'text' as const,\n textChunkLimit: 4000,\n ...createWeixinOutboundHandlers(),\n };\n\n streaming: ChannelStreamingAdapter = {\n startStream: () => null,\n };\n\n /** Channel plugin hints (cron targets, media paths). */\n agentPrompt = {\n augmentSystemPrompt: (): string =>\n [\n 'Weixin (ilink): direct chat only. To send an image or file, use the message tool with action send and set media to a local absolute path or an HTTPS URL; relative paths may fail.',\n 'For cron or scheduled delivery to a Weixin contact, set delivery.to to the user Weixin id (ending in @im.wechat) and delivery.accountId to the bot account id, or outbound may pick the wrong account.',\n 'When using MEDIA: to attach a file, put the MEDIA: line alone on its own line, not inline with other text.',\n ].join('\\n'),\n };\n\n async init(options: ChannelPluginInitOptions): Promise<void> {\n this.bus = options.bus;\n this.cfg = options.config;\n\n await this.registerWorkflowProgressCapability();\n\n log.debug('Weixin plugin initialized');\n }\n\n private async registerWorkflowProgressCapability(): Promise<void> {\n // Lazy-load workflow progress so Weixin runtime startup does not pull the\n // agent/workflow/provider graph into Electron's dynamic extension path.\n const [{ getWorkflowProgressBroker }, { createWeixinWorkflowProgressCapability }] = await Promise.all([\n import('@xopcai/xopc/agent/workflow/progress-broker.js'),\n import('./workflow-progress.js'),\n ]);\n this.workflowProgressUnregister = getWorkflowProgressBroker().registerChannel(\n createWeixinWorkflowProgressCapability({ getConfig: () => this.cfg }),\n );\n }\n\n async start(options?: ChannelPluginStartOptions): Promise<void> {\n const ids = options?.accountId\n ? [options.accountId]\n : listWeixinAccountIds(this.cfg);\n\n for (const accountId of ids) {\n const account = resolveWeixinAccount(this.cfg, accountId);\n if (!account.enabled || !account.configured || !account.token) continue;\n\n if (this.abortControllers.has(accountId)) continue;\n\n restoreContextTokens(account.accountId);\n\n const ac = new AbortController();\n this.abortControllers.set(accountId, ac);\n\n void monitorWeixinProvider({\n account,\n config: this.cfg,\n bus: this.bus,\n abortSignal: ac.signal,\n }).catch((err) => {\n if ((err as { name?: string; message?: string } | undefined)?.name === 'AbortError') {\n log.debug({ accountId }, 'Weixin monitor stopped');\n return;\n }\n log.error({ err, accountId }, 'Weixin monitor exited with error');\n });\n\n log.info({ accountId }, 'Weixin monitor started');\n }\n }\n\n async stop(accountId?: string): Promise<void> {\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 the cap usable for other accounts.\n if (!accountId) {\n this.workflowProgressUnregister?.();\n this.workflowProgressUnregister = null;\n }\n }\n\n channelIsRunning(cfg: Config): boolean {\n return listWeixinAccountIds(cfg).some((id) => {\n const a = resolveWeixinAccount(cfg, id);\n return a.configured && a.enabled !== false && this.abortControllers.has(id);\n });\n }\n\n async onConfigUpdated(cfg: Config): Promise<void> {\n const prevWx = this.cfg.channels?.weixin as unknown;\n const nextWx = cfg.channels?.weixin as { enabled?: boolean } | undefined;\n const channelOff = !nextWx || nextWx.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(prevWx, nextWx) && this.channelIsRunning(cfg)) {\n return;\n }\n\n await this.stop();\n await this.start();\n }\n\n /**\n * Restart long-poll monitors after credentials were written to disk without a `channels.weixin` JSON\n * delta (e.g. only token files / account index updated). Gateway calls this after QR login completes.\n *\n * Pass `bus` explicitly: if Weixin was disabled at gateway boot, `init()` was skipped and `this.bus` was never set.\n */\n async reloadMonitorsWithConfig(cfg: Config, bus: MessageBus): Promise<void> {\n this.bus = bus;\n this.cfg = cfg;\n await this.stop();\n await this.start();\n }\n}\n\nexport const weixinPlugin = new WeixinChannelPlugin();\n"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.js","names":[],"sources":["../../../../extensions/weixin/src/plugin.ts"],"sourcesContent":["/**\n * Weixin (WeChat ilink) channel — long-poll getUpdates, QR login, direct messages only.\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 ChannelPlugin,\n ChannelPluginDefaults,\n ChannelPluginInitOptions,\n ChannelPluginReloadMeta,\n ChannelPluginStartOptions,\n ChannelSecurityContext,\n ChannelStreamingAdapter,\n ChatType,\n} from '@xopcai/xopc/channels/plugin-types.js';\nimport { evaluateAccess, resolveDmPolicy } from '@xopcai/xopc/channels/security.js';\nimport { createStandardPairingAdapter } from '@xopcai/xopc/channels/pairing/pairing-store-adapter.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\n\nimport { restoreContextTokens } from './messaging/inbound.js';\nimport { monitorWeixinProvider } from './monitor/monitor.js';\nimport type {\n ChannelCliLoginAdapter,\n ChannelCronDeliveryAdapter,\n ChannelRuntimeActionAdapter,\n} from '@xopcai/xopc/channels/plugins/types.adapters.js';\nimport {\n listWeixinAccountIds,\n resolveWeixinAccount,\n type ResolvedWeixinAccount,\n} from './auth/accounts.js';\nimport { readFrameworkAllowFromList } from './auth/pairing.js';\nimport { createWeixinOutboundHandlers, weixinTextChunker } from './outbound-send.js';\nimport { normalizeWeixinCronDeliveryToResolved } from './delivery-to.js';\nimport { weixinConfigSurface } from './config-surface.js';\nimport { WeixinConfigSchema } from './config-schema.js';\nimport { weixinOnboardAdapter } from './adapters/onboard-cli.js';\n\nconst log = createLogger('WeixinPlugin');\n\nexport class WeixinChannelPlugin implements ChannelPlugin<ResolvedWeixinAccount> {\n readonly id = 'weixin' as const;\n\n readonly reload: ChannelPluginReloadMeta = {\n configPrefixes: ['channels.weixin'],\n };\n\n readonly meta = {\n id: 'weixin',\n label: 'Weixin',\n selectionLabel: 'Weixin (ilink)',\n docsPath: '/channels/weixin',\n blurb: 'WeChat via Tencent ilink bot API (QR login, direct chat).',\n order: 3,\n deferConnectUntilAfterListen: true,\n } as const;\n\n readonly capabilities: ChannelCapabilities = {\n chatTypes: ['direct'] as ChatType[],\n reactions: false,\n threads: false,\n media: true,\n polls: false,\n nativeCommands: false,\n blockStreaming: true,\n };\n\n readonly configSchema = {\n schema: {},\n validate: (raw: unknown) => {\n const r = WeixinConfigSchema.safeParse(raw);\n return r.success ? { ok: true as const } : { ok: false as const, errors: [r.error.message] };\n },\n };\n\n readonly cronDelivery: ChannelCronDeliveryAdapter = {\n async normalizeDeliveryTarget(to, sessionStore) {\n const { chatId, accountId } = await normalizeWeixinCronDeliveryToResolved(to, sessionStore);\n return { chatId, accountId };\n },\n };\n\n readonly onboard = weixinOnboardAdapter;\n\n readonly cliLogin: ChannelCliLoginAdapter = {\n async runLogin(params) {\n const { runWeixinQrLoginCli } = await import('./cli/qr-login.js');\n return runWeixinQrLoginCli({\n configPath: params.configPath,\n verbose: params.verbose,\n timeoutMs: params.timeoutMs,\n account: params.accountId,\n writeConfig: params.writeConfig,\n });\n },\n };\n\n readonly runtimeActions: ChannelRuntimeActionAdapter = {\n runAction: async ({ actionId, accountId, input, locale }) => {\n const zh = locale?.toLowerCase().startsWith('zh') === true;\n if (actionId === 'login.start') {\n const { startWeixinGatewayQrLogin } = await import('./cli/gateway-qr-login.js');\n const inputRecord =\n input && typeof input === 'object' && !Array.isArray(input)\n ? (input as Record<string, unknown>)\n : {};\n const timeoutMs = typeof inputRecord.timeoutMs === 'number' ? inputRecord.timeoutMs : 480_000;\n const start = await startWeixinGatewayQrLogin({\n account: accountId,\n timeoutMs,\n initialConfig: this.cfg,\n onPersisted: async (result) => {\n if (!result.ok) return;\n await this.reloadMonitorsWithConfig(result.config ?? this.cfg, this.bus);\n },\n });\n if (start.ok === false) {\n return { ok: false, message: start.message };\n }\n return {\n ok: true,\n payload: {\n type: 'qr',\n sessionKey: start.sessionKey,\n qrcodeUrl: start.qrcodeUrl,\n statusAction: 'login.status',\n pollIntervalMs: 2500,\n message: zh ? '请使用微信扫描二维码。' : 'Scan the QR code with WeChat.',\n },\n };\n }\n\n if (actionId === 'login.status') {\n const inputRecord =\n input && typeof input === 'object' && !Array.isArray(input)\n ? (input as Record<string, unknown>)\n : {};\n const sessionKey = typeof inputRecord.sessionKey === 'string' ? inputRecord.sessionKey : '';\n if (!sessionKey) return { ok: false, message: zh ? '缺少登录 sessionKey' : 'Missing login sessionKey' };\n const { getWeixinGatewayQrLoginStatus } = await import('./cli/gateway-qr-login.js');\n const status = getWeixinGatewayQrLoginStatus(sessionKey);\n if (status.phase === 'polling') {\n return {\n ok: true,\n payload: {\n type: 'poll',\n phase: 'pending',\n qrcodeUrl: status.qrcodeUrl,\n qrStatus: status.qrStatus,\n message: status.qrStatus,\n },\n };\n }\n if (status.phase === 'done') {\n return {\n ok: true,\n payload: {\n type: 'poll',\n phase: 'done',\n ok: status.ok,\n accountId: status.ok ? status.accountId : undefined,\n message: status.ok === true ? (zh ? '微信登录完成。' : 'Weixin login complete.') : status.message,\n configChanged: status.ok,\n },\n };\n }\n return {\n ok: true,\n payload: {\n type: 'poll',\n phase: 'unknown',\n ok: false,\n message: status.message,\n },\n };\n }\n\n return { ok: false, message: `Unsupported Weixin action: ${actionId}` };\n },\n };\n\n readonly configSurface = weixinConfigSurface;\n\n readonly pairing = createStandardPairingAdapter('weixin');\n\n readonly defaults: ChannelPluginDefaults = {\n queue: { debounceMs: 0 },\n outbound: { textChunkLimit: 4000 },\n streaming: {\n blockStreamingCoalesce: {\n minChars: 200,\n idleMs: 3000,\n },\n },\n };\n\n private bus!: ChannelPluginInitOptions['bus'];\n private cfg!: Config;\n private abortControllers = new Map<string, AbortController>();\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) => listWeixinAccountIds(cfg),\n resolveAccount: (cfg: Config, accountId?: string | null) => resolveWeixinAccount(cfg, accountId),\n isConfigured: async (account: ResolvedWeixinAccount) => account.configured,\n describeAccount: (account: ResolvedWeixinAccount, _cfg: Config) => ({\n accountId: account.accountId,\n channelId: 'weixin',\n name: account.name,\n enabled: account.enabled,\n configured: account.configured,\n status: undefined,\n }),\n };\n\n security = {\n resolveDmPolicy: ({ account }: { account: ResolvedWeixinAccount }) =>\n resolveDmPolicy(account.dmPolicy, 'open'),\n checkAccess: (ctx: ChannelSecurityContext, account: ResolvedWeixinAccount, _cfg: Config) => {\n const allowFrom = [...(account.allowFrom ?? []), ...readFrameworkAllowFromList(account.accountId)];\n return evaluateAccess({\n context: {\n channel: 'weixin',\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 };\n\n outbound = {\n deliveryMode: 'direct' as const,\n chunker: weixinTextChunker,\n chunkerMode: 'text' as const,\n textChunkLimit: 4000,\n ...createWeixinOutboundHandlers(),\n };\n\n streaming: ChannelStreamingAdapter = {\n startStream: () => null,\n };\n\n /** Channel plugin hints (cron targets, media paths). */\n agentPrompt = {\n augmentSystemPrompt: (): string =>\n [\n 'Weixin (ilink): direct chat only. To send an image or file, use the message tool with action send and set media to a local absolute path or an HTTPS URL; relative paths may fail.',\n 'For cron or scheduled delivery to a Weixin contact, set delivery.to to the user Weixin id (ending in @im.wechat) and delivery.accountId to the bot account id, or outbound may pick the wrong account.',\n 'When using MEDIA: to attach a file, put the MEDIA: line alone on its own line, not inline with other text.',\n ].join('\\n'),\n };\n\n async init(options: ChannelPluginInitOptions): Promise<void> {\n this.bus = options.bus;\n this.cfg = options.config;\n\n await this.registerWorkflowProgressCapability();\n\n log.debug('Weixin plugin initialized');\n }\n\n private async registerWorkflowProgressCapability(): Promise<void> {\n // Lazy-load workflow progress so Weixin runtime startup does not pull the\n // agent/workflow/provider graph into Electron's dynamic extension path.\n const [{ getWorkflowProgressBroker }, { createWeixinWorkflowProgressCapability }] = await Promise.all([\n import('@xopcai/xopc/agent/workflow/progress-broker.js'),\n import('./workflow-progress.js'),\n ]);\n this.workflowProgressUnregister = getWorkflowProgressBroker().registerChannel(\n createWeixinWorkflowProgressCapability({ getConfig: () => this.cfg }),\n );\n }\n\n async start(options?: ChannelPluginStartOptions): Promise<void> {\n if (!this.workflowProgressUnregister) {\n await this.registerWorkflowProgressCapability();\n }\n\n const ids = options?.accountId\n ? [options.accountId]\n : listWeixinAccountIds(this.cfg);\n\n for (const accountId of ids) {\n const account = resolveWeixinAccount(this.cfg, accountId);\n if (!account.enabled || !account.configured || !account.token) continue;\n\n if (this.abortControllers.has(accountId)) continue;\n\n restoreContextTokens(account.accountId);\n\n const ac = new AbortController();\n this.abortControllers.set(accountId, ac);\n\n void monitorWeixinProvider({\n account,\n config: this.cfg,\n bus: this.bus,\n abortSignal: ac.signal,\n }).catch((err) => {\n if ((err as { name?: string; message?: string } | undefined)?.name === 'AbortError') {\n log.debug({ accountId }, 'Weixin monitor stopped');\n return;\n }\n log.error({ err, accountId }, 'Weixin monitor exited with error');\n });\n\n log.info({ accountId }, 'Weixin monitor started');\n }\n }\n\n async stop(accountId?: string): Promise<void> {\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 the cap usable for other accounts.\n if (!accountId) {\n this.workflowProgressUnregister?.();\n this.workflowProgressUnregister = null;\n }\n }\n\n channelIsRunning(cfg: Config): boolean {\n return listWeixinAccountIds(cfg).some((id) => {\n const a = resolveWeixinAccount(cfg, id);\n return a.configured && a.enabled !== false && this.abortControllers.has(id);\n });\n }\n\n async onConfigUpdated(cfg: Config): Promise<void> {\n const routingConfigChanged = !isDeepStrictEqual(\n {\n agents: this.cfg.agents,\n bindings: this.cfg.bindings,\n session: this.cfg.session,\n },\n {\n agents: cfg.agents,\n bindings: cfg.bindings,\n session: cfg.session,\n },\n );\n const prevWx = this.cfg.channels?.weixin as unknown;\n const nextWx = cfg.channels?.weixin as { enabled?: boolean } | undefined;\n const channelOff = !nextWx || nextWx.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 (!routingConfigChanged && isDeepStrictEqual(prevWx, nextWx) && this.channelIsRunning(cfg)) {\n return;\n }\n\n await this.stop();\n await this.start();\n }\n\n /**\n * Restart long-poll monitors after credentials were written to disk without a `channels.weixin` JSON\n * delta (e.g. only token files / account index updated). Gateway calls this after QR login completes.\n *\n * Pass `bus` explicitly: if Weixin was disabled at gateway boot, `init()` was skipped and `this.bus` was never set.\n */\n async reloadMonitorsWithConfig(cfg: Config, bus: MessageBus): Promise<void> {\n this.bus = bus;\n this.cfg = cfg;\n await this.stop();\n await this.start();\n }\n}\n\nexport const weixinPlugin = new WeixinChannelPlugin();\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { Config } from '@xopcai/xopc/config/schema.js';
2
+ export type WeixinRoutingContext = {
3
+ accountId: string;
4
+ senderId: string;
5
+ };
6
+ /** Resolve a direct-message session key through the shared channel binding rules. */
7
+ export declare function generateWeixinSessionKeyWithRouting(context: WeixinRoutingContext, config: Config): string;
@@ -0,0 +1,17 @@
1
+ import { resolveRoute } from "../../../src/routing/resolve-route.js";
2
+ import "../../../src/routing/index.js";
3
+ //#region extensions/weixin/src/routing-integration.ts
4
+ /** Resolve a direct-message session key through the shared channel binding rules. */
5
+ function generateWeixinSessionKeyWithRouting(context, config) {
6
+ return resolveRoute({
7
+ config,
8
+ channel: "weixin",
9
+ accountId: context.accountId,
10
+ peerKind: "dm",
11
+ peerId: context.senderId
12
+ }).sessionKey;
13
+ }
14
+ //#endregion
15
+ export { generateWeixinSessionKeyWithRouting };
16
+
17
+ //# sourceMappingURL=routing-integration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing-integration.js","names":[],"sources":["../../../../extensions/weixin/src/routing-integration.ts"],"sourcesContent":["import type { Config } from '@xopcai/xopc/config/schema.js';\nimport { resolveRoute } from '@xopcai/xopc/routing/index.js';\n\nexport type WeixinRoutingContext = {\n accountId: string;\n senderId: string;\n};\n\n/** Resolve a direct-message session key through the shared channel binding rules. */\nexport function generateWeixinSessionKeyWithRouting(\n context: WeixinRoutingContext,\n config: Config,\n): string {\n return resolveRoute({\n config,\n channel: 'weixin',\n accountId: context.accountId,\n peerKind: 'dm',\n peerId: context.senderId,\n }).sessionKey;\n}\n"],"mappings":";;;;AASA,SAAgB,oCACd,SACA,QACQ;AACR,QAAO,aAAa;EAClB;EACA,SAAS;EACT,WAAW,QAAQ;EACnB,UAAU;EACV,QAAQ,QAAQ;EACjB,CAAC,CAAC"}