@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.
- package/README.md +23 -26
- package/README.zh-CN.md +25 -28
- package/dist/browser-ext/icons/icon-128.png +0 -0
- package/dist/browser-ext/icons/icon-16.png +0 -0
- package/dist/browser-ext/icons/icon-32.png +0 -0
- package/dist/browser-ext/icons/icon-48.png +0 -0
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/extensions/weixin/src/messaging/process-message.js +5 -8
- package/dist/extensions/weixin/src/messaging/process-message.js.map +1 -1
- package/dist/extensions/weixin/src/plugin.js +11 -1
- package/dist/extensions/weixin/src/plugin.js.map +1 -1
- package/dist/extensions/weixin/src/routing-integration.d.ts +7 -0
- package/dist/extensions/weixin/src/routing-integration.js +17 -0
- package/dist/extensions/weixin/src/routing-integration.js.map +1 -0
- package/dist/gateway/static/root/apple-touch-icon.png +0 -0
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear--iz8s0k-.js → agent-avatar-dicebear-CnZIdUyv.js} +1 -1
- package/dist/gateway/static/root/assets/agents-C-rdLOrb.js +2 -0
- package/dist/gateway/static/root/assets/app-management-settings-panel-BcvaXb7T.js +1 -0
- package/dist/gateway/static/root/assets/{appearance-settings-9Y9ulFHw.js → appearance-settings-CuxzNz-I.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-_VJm0o44.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-DLWkp7gy.js → archive-plugin-CcXXh0X9.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-aDP5GIUn.js → attachment-load-BYhYBw17.js} +1 -1
- package/dist/gateway/static/root/assets/{automation-suggestion-card-B1cByea5.js → automation-suggestion-card-ByjDtlOw.js} +1 -1
- package/dist/gateway/static/root/assets/automations-page-DUFct5t3.js +4 -0
- package/dist/gateway/static/root/assets/{binary-plugins-DUisfrnF.js → binary-plugins-B1A5KhWX.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-D9aGvHMF.js → block-editor-DWpXiVTf.js} +4 -4
- package/dist/gateway/static/root/assets/browser-settings-page-Cff75b5Z.js +8 -0
- package/dist/gateway/static/root/assets/capability-presets-api-DFWL6PJ0.js +1 -0
- package/dist/gateway/static/root/assets/{capability-presets-settings-panel-BXDIjaXI.js → capability-presets-settings-panel-CBT89nI-.js} +1 -1
- package/dist/gateway/static/root/assets/channel-recipient-api-RANYrzfa.js +1 -0
- package/dist/gateway/static/root/assets/channels-settings-DHBjZ2fa.js +1 -0
- package/dist/gateway/static/root/assets/channels-status-swr-BG_j4Ci-.js +1 -0
- package/dist/gateway/static/root/assets/connectors-page-B4CGNErH.js +2 -0
- package/dist/gateway/static/root/assets/desktop-pet-CDD29oYv.js +1 -0
- package/dist/gateway/static/root/assets/{desktop-pet-settings-iLgj-52b.js → desktop-pet-settings-CEkfvSnb.js} +1 -1
- package/dist/gateway/static/root/assets/{directory-picker-path-field-RtmB1V_x.js → directory-picker-path-field-JbVQ2_md.js} +1 -1
- package/dist/gateway/static/root/assets/dreaming-settings-C-fzh8SN.js +3 -0
- package/dist/gateway/static/root/assets/{extension-debug-page-5-InZoH2.js → extension-debug-page-B4jZ0Yim.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-Bsai0Lgm.js → extension-page-Bi0GmOZ2.js} +1 -1
- package/dist/gateway/static/root/assets/extension-settings-page-BJpxz8Dq.js +1 -0
- package/dist/gateway/static/root/assets/fetch-6HXXdO77.js +1 -0
- package/dist/gateway/static/root/assets/gateway-config-api-Ci1tJtJ4.js +1 -0
- package/dist/gateway/static/root/assets/gateway-settings-C-YBJHLX.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-startup-retry-CY5W2Pp4.js → gateway-startup-retry-BE1M9fH_.js} +1 -1
- package/dist/gateway/static/root/assets/goal-create-dialog-DhfNsglx.js +1 -0
- package/dist/gateway/static/root/assets/goal-detail-page-9WeYgD-u.js +4 -0
- package/dist/gateway/static/root/assets/goals-config-api-DRJGqa_W.js +1 -0
- package/dist/gateway/static/root/assets/goals-page-DXEQqzEX.js +2 -0
- package/dist/gateway/static/root/assets/goals-settings-5pFGRkD6.js +1 -0
- package/dist/gateway/static/root/assets/heartbeat-settings-CGX78-fM.js +1 -0
- package/dist/gateway/static/root/assets/index-BjGnv8uJ.js +262 -0
- package/dist/gateway/static/root/assets/index-W4ogAiHt.css +1 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BHyCbDmv.js → keyboard-shortcuts-settings-D23eHsli.js} +1 -1
- package/dist/gateway/static/root/assets/logs-page-jMLLOxWk.js +7 -0
- package/dist/gateway/static/root/assets/{markdown-split-Cfv4fIaA.js → markdown-split-Dn7rJV-v.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-DHIecUIi.js → media-plugins-DOnTRGyE.js} +1 -1
- package/dist/gateway/static/root/assets/{messages-YzjnURPJ.js → messages-DR9ODkb8.js} +3 -3
- package/dist/gateway/static/root/assets/models-hub-panel-Br6l-Di9.js +2 -0
- package/dist/gateway/static/root/assets/notes-page-BiXfX1he.js +1 -0
- package/dist/gateway/static/root/assets/notes-workbench-DjaTvXj3.js +2 -0
- package/dist/gateway/static/root/assets/{page-tabs-xL5C9yyM.js → page-tabs-CYEVap7D.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-Dlzt9LTH.js → preview-open-alternatives-Dk7Cjd6E.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-B6UccBNl.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-B56s8p-Q.js +1 -0
- package/dist/gateway/static/root/assets/remote-access-hub-D3ekwIaB.js +1 -0
- package/dist/gateway/static/root/assets/{save-bar-controls-CxMWRml4.js → save-bar-controls-D7FzAPOc.js} +1 -1
- package/dist/gateway/static/root/assets/{schedule-field-XCWPTiWa.js → schedule-field-BZP1-U9M.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-BaQCsnUq.js → schema-form-1NNFTrvK.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-08vT4R9X.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-IsaFJ6ZC.js +1 -0
- package/dist/gateway/static/root/assets/{settings-collapsible-section-D1SE9kRq.js → settings-collapsible-section-CX5IUGk1.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-form-section-DnEPGUgO.js → settings-form-section-a71ujXN4.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-loading-skeleton-DOvWEjHg.js → settings-loading-skeleton-D4mq628A.js} +1 -1
- package/dist/gateway/static/root/assets/settings-page-DSQWZsZt.js +1 -0
- package/dist/gateway/static/root/assets/{settings-page-layout-CCjhhWJI.js → settings-page-layout-BG-k3_CF.js} +1 -1
- package/dist/gateway/static/root/assets/setup-status-panel-CFBgxLKm.js +1 -0
- package/dist/gateway/static/root/assets/share-preview-page-Cz4X3Vcf.js +2 -0
- package/dist/gateway/static/root/assets/shares-settings-BHjEkA9t.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-CfHG_ekm.js +1 -0
- package/dist/gateway/static/root/assets/skills-page-C0KVNC0o.js +2 -0
- package/dist/gateway/static/root/assets/{system-settings-panel-CMMpS4gB.js → system-settings-panel-Bo9mLewC.js} +1 -1
- package/dist/gateway/static/root/assets/{typed-models-lib-JlIzyeQ_.js → typed-models-lib-Bp6wXydR.js} +1 -1
- package/dist/gateway/static/root/assets/use-channel-catalog-BCBfSoHx.js +1 -0
- package/dist/gateway/static/root/assets/{user-profile-settings-panel-CZVtp4He.js → user-profile-settings-panel-NcG1KFUf.js} +1 -1
- package/dist/gateway/static/root/assets/{utils-Lv9XYntY.js → utils-BNtcPshb.js} +1 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-ByppRAVD.js +1 -0
- package/dist/gateway/static/root/assets/work-item-detail-page-5wdg0Xmy.js +1 -0
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-BiygC8WQ.js → workflow-run-setup-panel-eKBZIV-N.js} +2 -2
- package/dist/gateway/static/root/assets/{workflow-start-dialog-CC47dSwh.js → workflow-start-dialog-B6qfU5GE.js} +1 -1
- package/dist/gateway/static/root/assets/workflows-page-BtLILZtw.js +18 -0
- package/dist/gateway/static/root/favicon-16x16.png +0 -0
- package/dist/gateway/static/root/favicon-32x32.png +0 -0
- package/dist/gateway/static/root/favicon-icon.svg +182 -27
- package/dist/gateway/static/root/favicon.ico +0 -0
- package/dist/gateway/static/root/favicon.png +0 -0
- package/dist/gateway/static/root/favicon.svg +182 -27
- package/dist/gateway/static/root/index.html +5 -6
- package/dist/gateway/static/root/logo-dark.svg +179 -15
- package/dist/gateway/static/root/logo.svg +179 -15
- package/dist/gateway/static/root/pwa-192x192.png +0 -0
- package/dist/gateway/static/root/pwa-512x512.png +0 -0
- package/dist/package.js +1 -1
- package/dist/src/agent/starter-agents.d.ts +1 -0
- package/dist/src/agent/starter-agents.js +38 -11
- package/dist/src/agent/starter-agents.js.map +1 -1
- package/dist/src/cli/commands/gateway/probe.js +6 -6
- package/dist/src/cli/commands/gateway/probe.js.map +1 -1
- package/dist/src/cli/commands/onboard.js +2 -2
- package/dist/src/cli/commands/onboard.js.map +1 -1
- package/dist/src/cli/commands/tui.js +11 -4
- package/dist/src/cli/commands/tui.js.map +1 -1
- package/dist/src/cli/utils/gateway-client-options.d.ts +3 -0
- package/dist/src/cli/utils/gateway-client-options.js +2 -1
- package/dist/src/cli/utils/gateway-client-options.js.map +1 -1
- package/dist/src/cli/utils/gateway-client.d.ts +6 -3
- package/dist/src/cli/utils/gateway-client.js +14 -8
- package/dist/src/cli/utils/gateway-client.js.map +1 -1
- package/dist/src/commands/agents.config.d.ts +2 -0
- package/dist/src/commands/agents.config.js +1 -1
- package/dist/src/commands/agents.config.js.map +1 -1
- package/dist/src/config/agent-profile.js +1 -1
- package/dist/src/config/schema.js +4 -2
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/gateway/agents-admin.js +1 -1
- package/dist/src/gateway/credential.d.ts +9 -0
- package/dist/src/gateway/credential.js +16 -0
- package/dist/src/gateway/credential.js.map +1 -0
- package/dist/src/gateway/hono/app.d.ts +0 -1
- package/dist/src/gateway/hono/app.js +2 -9
- package/dist/src/gateway/hono/app.js.map +1 -1
- package/dist/src/gateway/hono/middleware/auth.d.ts +0 -2
- package/dist/src/gateway/hono/middleware/auth.js +46 -21
- package/dist/src/gateway/hono/middleware/auth.js.map +1 -1
- package/dist/src/gateway/hono/routes/sessions.js +2 -27
- package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
- package/dist/src/gateway/server.js +1 -5
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/mcp/channel-bridge.d.ts +2 -1
- package/dist/src/mcp/channel-bridge.js +9 -5
- package/dist/src/mcp/channel-bridge.js.map +1 -1
- package/dist/src/mcp/channel-server.d.ts +2 -1
- package/dist/src/mcp/channel-server.js +1 -1
- package/dist/src/mcp/channel-server.js.map +1 -1
- package/dist/src/mcp/gateway-http-client.d.ts +3 -2
- package/dist/src/mcp/gateway-http-client.js +9 -7
- package/dist/src/mcp/gateway-http-client.js.map +1 -1
- package/dist/src/tui/backends/gateway-sse-backend.d.ts +3 -2
- package/dist/src/tui/backends/gateway-sse-backend.js +45 -41
- package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
- package/dist/src/tui/tui-types.d.ts +3 -2
- package/dist/src/tui/tui-types.js.map +1 -1
- package/dist/src/tui/tui.js +1 -1
- package/dist/src/tui/tui.js.map +1 -1
- package/package.json +1 -1
- package/dist/gateway/static/root/assets/agents-NxdjoWyZ.js +0 -2
- package/dist/gateway/static/root/assets/app-management-settings-panel-DzSs6MPu.js +0 -1
- package/dist/gateway/static/root/assets/apps-page-CPXB5VC2.js +0 -1
- package/dist/gateway/static/root/assets/automations-page-C3atGynq.js +0 -4
- package/dist/gateway/static/root/assets/browser-settings-page-C7zmHlV-.js +0 -8
- package/dist/gateway/static/root/assets/capability-presets-api-CaDTlpYK.js +0 -1
- package/dist/gateway/static/root/assets/channel-recipient-api-BrW5Kcgc.js +0 -1
- package/dist/gateway/static/root/assets/channels-settings-BxjagLJ_.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-B817lbgc.js +0 -2
- package/dist/gateway/static/root/assets/desktop-pet-DuRXndhI.js +0 -1
- package/dist/gateway/static/root/assets/dreaming-settings-C8abJcaI.js +0 -3
- package/dist/gateway/static/root/assets/extension-settings-page-BI-cyHrx.js +0 -1
- package/dist/gateway/static/root/assets/fetch-BMjlW0g1.js +0 -1
- package/dist/gateway/static/root/assets/gateway-config-api-DROtL9kG.js +0 -1
- package/dist/gateway/static/root/assets/gateway-settings-CyTSzCnt.js +0 -1
- package/dist/gateway/static/root/assets/goal-create-dialog-BNh1ojty.js +0 -1
- package/dist/gateway/static/root/assets/goal-detail-page-D5cZ4icP.js +0 -4
- package/dist/gateway/static/root/assets/goals-config-api-GqxVsoOo.js +0 -1
- package/dist/gateway/static/root/assets/goals-page-CUznMB7d.js +0 -2
- package/dist/gateway/static/root/assets/goals-settings-dtOWKJda.js +0 -1
- package/dist/gateway/static/root/assets/heartbeat-settings-D5NztVps.js +0 -1
- package/dist/gateway/static/root/assets/index-CxR00gsL.js +0 -257
- package/dist/gateway/static/root/assets/index-EKmpPvZj.css +0 -1
- package/dist/gateway/static/root/assets/logs-page-DgEJ4PeH.js +0 -2
- package/dist/gateway/static/root/assets/models-hub-panel-9zCYTThv.js +0 -2
- package/dist/gateway/static/root/assets/notes-page-D7iT2jV1.js +0 -1
- package/dist/gateway/static/root/assets/notes-workbench-BuAkwok0.js +0 -2
- package/dist/gateway/static/root/assets/project-detail-page-BOKQPdB5.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-ALHgCAPZ.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-BnowluoZ.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-DnW0ohat.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-N9Pc7kmG.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-s-THg5D2.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-UNMcmL2b.js +0 -1
- package/dist/gateway/static/root/assets/share-preview-page-CfO4RaVP.js +0 -2
- package/dist/gateway/static/root/assets/shares-settings-Cv86ZsIh.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-B-9sluy3.js +0 -1
- package/dist/gateway/static/root/assets/skills-page-D4ifOU_G.js +0 -2
- package/dist/gateway/static/root/assets/url-DFGNSBNT.js +0 -1
- package/dist/gateway/static/root/assets/use-channel-catalog-CPdJ0o0i.js +0 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-7ZjSxIDE.js +0 -1
- package/dist/gateway/static/root/assets/work-item-detail-page-BYqDWx6I.js +0 -1
- 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
|
-
|
|
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
|
-
- [
|
|
39
|
-
- [
|
|
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
|
-
##
|
|
56
|
+
## What xopc is
|
|
57
57
|
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
62
|
+
## One runtime, four layers
|
|
66
63
|
|
|
67
|
-
|
|
|
64
|
+
| Layer | What xopc provides | What it solves |
|
|
68
65
|
| --- | --- | --- |
|
|
69
|
-
| **
|
|
70
|
-
| **
|
|
71
|
-
| **
|
|
72
|
-
| **
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
159
|
-
- 🏠 **
|
|
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
|
-
-
|
|
162
|
-
-
|
|
163
|
-
-
|
|
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
|
|
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
|
-
| [
|
|
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?** —
|
|
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
|
-
|
|
11
|
-
|
|
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
|
-
|
|
62
|
+
## 一套运行时,四类能力
|
|
66
63
|
|
|
67
|
-
|
|
|
64
|
+
| 层次 | xopc 提供什么 | 解决什么问题 |
|
|
68
65
|
| --- | --- | --- |
|
|
69
|
-
|
|
|
70
|
-
|
|
|
71
|
-
|
|
|
72
|
-
|
|
|
73
|
-
| **自动化** | 设置定时复盘、提醒、摘要和工作流运行。 | 重要事项会自己回到视野里,不完全依赖你想起来再问。 |
|
|
66
|
+
| **运行与所有权** | 自托管 gateway、本地状态目录、自有 API Key、云端/本地模型 | 数据、成本与可用性由你掌控 |
|
|
67
|
+
| **持续状态** | 持久会话、项目、目标、笔记、工作区与 Agent 记忆策略 | 工作可以恢复,不必每次从零解释 |
|
|
68
|
+
| **执行与主动性** | 工具、技能、子 Agent、可观测工作流、定时/手动/webhook 自动化 | 从“给建议”走向“执行、检查并继续” |
|
|
69
|
+
| **随处接入** | CLI、TUI、网页、桌面、iOS/Android、Telegram、微信、飞书/Lark、HTTP/SSE API | 在工作发生的地方使用同一个系统 |
|
|
74
70
|
|
|
75
|
-
|
|
71
|
+
典型用法不是固定漏斗,而是按需组合:在终端处理代码,在手机上语音速记到 Notes,在项目页查看目标、阻塞与失败的工作流,在每天的自动化中生成摘要,最后从任意入口继续同一个会话。
|
|
76
72
|
|
|
77
|
-
|
|
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
|
-
- 🏠
|
|
154
|
+
- 🧠 **有状态,不只是一轮上下文** — 会话、项目、目标、笔记、工作区和运行记录都有明确归属,长期工作可以恢复。
|
|
155
|
+
- 🏠 **运行在你的环境里** — 配置与本地状态默认在 **`~/.xopc/`**;你决定如何部署、备份和远程访问。
|
|
160
156
|
- 🔑 **使用自己的密钥** — OpenAI、Anthropic、Google、DeepSeek、Ollama、LM Studio、vLLM 等 **20+** 厂商;云端和本地模型可混用。
|
|
161
|
-
-
|
|
162
|
-
-
|
|
163
|
-
-
|
|
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 是面向个人长期工作的
|
|
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
|
|
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
|
-
| [
|
|
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
|
|
@@ -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.
|
|
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 =
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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"}
|
|
Binary file
|