@xopcai/xopc 0.0.104 → 0.0.106

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 (184) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/feishu/src/adapters/gateway-setup.js +2 -2
  3. package/dist/extensions/feishu/src/adapters/gateway-setup.js.map +1 -1
  4. package/dist/extensions/feishu/src/directory/directory-adapter.js +1 -1
  5. package/dist/extensions/feishu/src/directory/directory-adapter.js.map +1 -1
  6. package/dist/extensions/feishu/src/outbound/outbound-adapter.js +4 -3
  7. package/dist/extensions/feishu/src/outbound/outbound-adapter.js.map +1 -1
  8. package/dist/extensions/feishu/src/plugin.d.ts +6 -3
  9. package/dist/extensions/feishu/src/plugin.js +59 -40
  10. package/dist/extensions/feishu/src/plugin.js.map +1 -1
  11. package/dist/extensions/feishu/src/streaming/streaming-adapter.js +20 -16
  12. package/dist/extensions/feishu/src/streaming/streaming-adapter.js.map +1 -1
  13. package/dist/extensions/feishu/src/transport/socket-mode/monitor.js +1 -1
  14. package/dist/extensions/feishu/src/transport/webhook/monitor.js +1 -1
  15. package/dist/extensions/feishu/src/workflow-progress.d.ts +1 -1
  16. package/dist/extensions/feishu/src/workflow-progress.js +2 -2
  17. package/dist/extensions/feishu/src/workflow-progress.js.map +1 -1
  18. package/dist/extensions/telegram/src/adapters/gateway-setup.js +8 -6
  19. package/dist/extensions/telegram/src/adapters/gateway-setup.js.map +1 -1
  20. package/dist/extensions/telegram/src/command-handler.d.ts +1 -1
  21. package/dist/extensions/telegram/src/command-handler.js +11 -9
  22. package/dist/extensions/telegram/src/command-handler.js.map +1 -1
  23. package/dist/extensions/telegram/src/inbound-processor.js +1 -2
  24. package/dist/extensions/telegram/src/inbound-processor.js.map +1 -1
  25. package/dist/extensions/telegram/src/plugin.d.ts +1 -0
  26. package/dist/extensions/telegram/src/plugin.js +6 -6
  27. package/dist/extensions/telegram/src/plugin.js.map +1 -1
  28. package/dist/extensions/telegram/src/workflow-progress.d.ts +1 -1
  29. package/dist/extensions/telegram/src/workflow-progress.js.map +1 -1
  30. package/dist/extensions/telegram/xopc.extension.json +1 -1
  31. package/dist/extensions/weixin/src/cli/gateway-qr-login.d.ts +4 -0
  32. package/dist/extensions/weixin/src/cli/gateway-qr-login.js +34 -5
  33. package/dist/extensions/weixin/src/cli/gateway-qr-login.js.map +1 -1
  34. package/dist/extensions/weixin/src/cli/qr-login.js +4 -3
  35. package/dist/extensions/weixin/src/cli/qr-login.js.map +1 -1
  36. package/dist/extensions/weixin/src/plugin.d.ts +1 -0
  37. package/dist/extensions/weixin/src/plugin.js +7 -6
  38. package/dist/extensions/weixin/src/plugin.js.map +1 -1
  39. package/dist/extensions/weixin/src/workflow-progress.d.ts +1 -1
  40. package/dist/extensions/weixin/src/workflow-progress.js.map +1 -1
  41. package/dist/gateway/static/root/assets/agents-3eP0vqFO.js +396 -0
  42. package/dist/gateway/static/root/assets/{apps-page-L-IwQTe0.js → apps-page-CZ2ZKrko.js} +1 -1
  43. package/dist/gateway/static/root/assets/channels-settings-BbETa93K.js +1 -0
  44. package/dist/gateway/static/root/assets/cron-page-DCtUkHMu.js +1 -0
  45. package/dist/gateway/static/root/assets/{dist-C6vclgEL.js → dist-OM9qGfm5.js} +1 -1
  46. package/dist/gateway/static/root/assets/{extension-debug-page-D4JYAbsX.js → extension-debug-page-D-WHrr_w.js} +1 -1
  47. package/dist/gateway/static/root/assets/{extension-page-_T62gi6e.js → extension-page-CCgjSoKn.js} +1 -1
  48. package/dist/gateway/static/root/assets/{extension-settings-page-CkJbjyM-.js → extension-settings-page-BJH5a-Gn.js} +1 -1
  49. package/dist/gateway/static/root/assets/{fetch-B5aiIguv.js → fetch-CkvRO30i.js} +1 -1
  50. package/dist/gateway/static/root/assets/{gateway-startup-retry-DqmLXhs6.js → gateway-startup-retry-BkKyRDrF.js} +1 -1
  51. package/dist/gateway/static/root/assets/{heartbeat-config-api-HwJV1K_Y.js → heartbeat-config-api-CVyP6pFj.js} +1 -1
  52. package/dist/gateway/static/root/assets/index-CEZxETzw.js +4845 -0
  53. package/dist/gateway/static/root/assets/{logs-page-DsjtTjBy.js → logs-page-R6Vv_Ja6.js} +2 -2
  54. package/dist/gateway/static/root/assets/{note-detail-page-B2V7RhgD.js → note-detail-page-BUDarLhh.js} +54 -54
  55. package/dist/gateway/static/root/assets/{note-time-BG8GCTr4.js → note-time-Cc2-T21g.js} +1 -1
  56. package/dist/gateway/static/root/assets/{notes-page-xrcVBQ6Z.js → notes-page-EAHl16bq.js} +1 -1
  57. package/dist/gateway/static/root/assets/{schema-form-S-QUvhul.js → schema-form-CrlOmEZt.js} +1 -1
  58. package/dist/gateway/static/root/assets/sessions-page-Ce_xuuSB.js +1 -0
  59. package/dist/gateway/static/root/assets/{settings-advanced-gate-DujQl9c7.js → settings-advanced-gate-BBAQxxew.js} +1 -1
  60. package/dist/gateway/static/root/assets/{settings-form-section-r1V7dRV6.js → settings-form-section-DjPD-ErC.js} +1 -1
  61. package/dist/gateway/static/root/assets/settings-page-KhQ9QgH0.js +3 -0
  62. package/dist/gateway/static/root/assets/{share-preview-page-DOFy0FNt.js → share-preview-page-BjvPht3V.js} +1 -1
  63. package/dist/gateway/static/root/assets/skills-page-C0laq7l1.js +2 -0
  64. package/dist/gateway/static/root/assets/{theme-store-Bo3j5Ovv.js → theme-store-DYgVdRB4.js} +1 -1
  65. package/dist/gateway/static/root/assets/url-DPEStU4M.js +3 -0
  66. package/dist/gateway/static/root/assets/{voice-api-key-field-AjmUAlEQ.js → voice-api-key-field-DeGckWkl.js} +1 -1
  67. package/dist/gateway/static/root/assets/{workflow-page.utils-DLEJ92gK.js → workflow-page.utils-D4sqR0yX.js} +1 -1
  68. package/dist/gateway/static/root/assets/workflows-page-DFjM95g5.js +27 -0
  69. package/dist/gateway/static/root/index.html +5 -5
  70. package/dist/package.js +1 -1
  71. package/dist/src/agent/context/workspace-seed.js +1 -1
  72. package/dist/src/agent/inbound/turn-dispatcher.d.ts +2 -0
  73. package/dist/src/agent/inbound/turn-dispatcher.js +1 -0
  74. package/dist/src/agent/inbound/turn-dispatcher.js.map +1 -1
  75. package/dist/src/agent/mcp/bundle-mcp-config.js +16 -8
  76. package/dist/src/agent/mcp/bundle-mcp-config.js.map +1 -1
  77. package/dist/src/agent/mcp/bundle-mcp-policy.js +2 -2
  78. package/dist/src/agent/mcp/bundle-mcp-policy.js.map +1 -1
  79. package/dist/src/agent/mcp/bundle-mcp-runtime.js +1 -1
  80. package/dist/src/agent/mcp/mcp-http.js +1 -1
  81. package/dist/src/agent/mcp/mcp-http.js.map +1 -1
  82. package/dist/src/agent/mcp/mcp-transport-config.js +2 -6
  83. package/dist/src/agent/mcp/mcp-transport-config.js.map +1 -1
  84. package/dist/src/agent/prompt/memory/index.js +30 -2
  85. package/dist/src/agent/prompt/memory/index.js.map +1 -1
  86. package/dist/src/agent/service/direct-turn-helpers.d.ts +2 -0
  87. package/dist/src/agent/service/direct-turn-helpers.js +5 -2
  88. package/dist/src/agent/service/direct-turn-helpers.js.map +1 -1
  89. package/dist/src/agent/service/process-direct-streaming.d.ts +2 -0
  90. package/dist/src/agent/service/process-direct-streaming.js +15 -1
  91. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  92. package/dist/src/agent/service.js +2 -1
  93. package/dist/src/agent/service.js.map +1 -1
  94. package/dist/src/agent/service.types.d.ts +3 -0
  95. package/dist/src/agent/skills/hub-pull.js +1 -1
  96. package/dist/src/agent/source-context/index.d.ts +2 -0
  97. package/dist/src/agent/source-context/index.js +3 -0
  98. package/dist/src/agent/source-context/injector.d.ts +3 -0
  99. package/dist/src/agent/source-context/injector.js +36 -0
  100. package/dist/src/agent/source-context/injector.js.map +1 -0
  101. package/dist/src/agent/source-context/types.d.ts +19 -0
  102. package/dist/src/agent/source-context/types.js +10 -0
  103. package/dist/src/agent/source-context/types.js.map +1 -0
  104. package/dist/src/agent/tools/factory.js +5 -5
  105. package/dist/src/agent/tools/index.js +3 -3
  106. package/dist/src/agent/tools/session-search-tool.js +1 -1
  107. package/dist/src/agent/tools/shell.js +1 -1
  108. package/dist/src/agent/tools/web-extract.js +2 -2
  109. package/dist/src/agent/tools/web.js +1 -1
  110. package/dist/src/agent/workflow/index.js +2 -1
  111. package/dist/src/agent/workflow/runtime.d.ts +2 -2
  112. package/dist/src/agent/workflow/runtime.js +1 -14
  113. package/dist/src/agent/workflow/runtime.js.map +1 -1
  114. package/dist/src/agent/workflow/snapshot-empty.d.ts +2 -0
  115. package/dist/src/agent/workflow/snapshot-empty.js +19 -0
  116. package/dist/src/agent/workflow/snapshot-empty.js.map +1 -0
  117. package/dist/src/agent/workflow/snapshot.js +1 -1
  118. package/dist/src/agent/workflow/snapshot.js.map +1 -1
  119. package/dist/src/browser/actions/extension-registry-bridge.js +1 -1
  120. package/dist/src/browser/actions/registry.js +2 -2
  121. package/dist/src/browser/cdp-local-launcher.js +1 -1
  122. package/dist/src/browser/index.js +3 -3
  123. package/dist/src/browser/providers/browser-ext-install.js +1 -1
  124. package/dist/src/browser/providers/cloakbrowser.js +1 -1
  125. package/dist/src/cli/commands/browser-cli-helpers.js +1 -1
  126. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  127. package/dist/src/config/paths.d.ts +1 -0
  128. package/dist/src/config/paths.js +5 -1
  129. package/dist/src/config/paths.js.map +1 -1
  130. package/dist/src/connectors/health.js +1 -1
  131. package/dist/src/connectors/install.js +1 -1
  132. package/dist/src/document-understanding/extract.d.ts +13 -0
  133. package/dist/src/document-understanding/extract.js +63 -0
  134. package/dist/src/document-understanding/extract.js.map +1 -0
  135. package/dist/src/gateway/hono/routes/mcp.js +1 -1
  136. package/dist/src/gateway/hono/routes/notes.js +46 -27
  137. package/dist/src/gateway/hono/routes/notes.js.map +1 -1
  138. package/dist/src/gateway/service.js +11 -0
  139. package/dist/src/gateway/service.js.map +1 -1
  140. package/dist/src/mcp/channel-bridge.js +1 -1
  141. package/dist/src/notes/agent-context.d.ts +40 -0
  142. package/dist/src/notes/agent-context.js +383 -0
  143. package/dist/src/notes/agent-context.js.map +1 -0
  144. package/dist/src/notes/index.d.ts +2 -0
  145. package/dist/src/notes/index.js +2 -1
  146. package/dist/src/notes/service.d.ts +5 -0
  147. package/dist/src/notes/service.js +22 -0
  148. package/dist/src/notes/service.js.map +1 -1
  149. package/dist/src/notes/store.js +2 -0
  150. package/dist/src/notes/store.js.map +1 -1
  151. package/dist/src/share/share-thumbnail.js +1 -1
  152. package/dist/src/storage/sqlite/index.d.ts +1 -0
  153. package/dist/src/storage/sqlite/index.js +2 -1
  154. package/dist/src/storage/sqlite/migrations/005_add_note_agent_contexts.sql +10 -0
  155. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  156. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  157. package/dist/src/storage/sqlite/migrations/runner.js.map +1 -1
  158. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  159. package/dist/src/storage/sqlite/note-agent-context-repository.d.ts +10 -0
  160. package/dist/src/storage/sqlite/note-agent-context-repository.js +35 -0
  161. package/dist/src/storage/sqlite/note-agent-context-repository.js.map +1 -0
  162. package/dist/src/storage/sqlite/schema.js +1 -1
  163. package/dist/src/tui/tui-startup-resources.js +1 -1
  164. package/dist/src/voice/stt/availability.d.ts +6 -0
  165. package/dist/src/voice/stt/availability.js +14 -6
  166. package/dist/src/voice/stt/availability.js.map +1 -1
  167. package/dist/src/voice/stt/index.js +2 -1
  168. package/dist/src/voice/stt/index.js.map +1 -1
  169. package/dist/src/voice/stt/mention.d.ts +2 -0
  170. package/dist/src/voice/stt/mention.js +20 -0
  171. package/dist/src/voice/stt/mention.js.map +1 -0
  172. package/dist/src/voice/stt/preflight.d.ts +1 -4
  173. package/dist/src/voice/stt/preflight.js +1 -18
  174. package/dist/src/voice/stt/preflight.js.map +1 -1
  175. package/package.json +4 -4
  176. package/dist/gateway/static/root/assets/agents-CvsBrBfi.js +0 -396
  177. package/dist/gateway/static/root/assets/channels-settings-D95KuPeP.js +0 -1
  178. package/dist/gateway/static/root/assets/cron-page-CfkWXDAI.js +0 -1
  179. package/dist/gateway/static/root/assets/index-DvTb6wNR.js +0 -4845
  180. package/dist/gateway/static/root/assets/sessions-page-CrQer6Au.js +0 -1
  181. package/dist/gateway/static/root/assets/settings-page-C9nrONQ2.js +0 -3
  182. package/dist/gateway/static/root/assets/skills-page-DmTsrs_a.js +0 -2
  183. package/dist/gateway/static/root/assets/url-WgQIjFZu.js +0 -3
  184. package/dist/gateway/static/root/assets/workflows-page-BnjjKrM1.js +0 -27
@@ -1,5 +1,4 @@
1
1
  import { ConfigSchema, init_schema } from "../../../../src/config/schema.js";
2
- import { init_loader, loadConfig, saveConfig } from "../../../../src/config/loader.js";
3
2
  import { logger } from "../util/logger.js";
4
3
  import { normalizeWeixinAccountId } from "../auth/weixin-account-id.js";
5
4
  import { clearContextTokensForAccount } from "../messaging/inbound.js";
@@ -7,7 +6,6 @@ import { DEFAULT_BASE_URL, clearStaleAccountsForUserId, registerWeixinAccountId,
7
6
  import { startWeixinLoginWithQr, waitForWeixinLogin } from "../auth/login-qr.js";
8
7
  //#region extensions/weixin/src/cli/qr-login.ts
9
8
  init_schema();
10
- init_loader();
11
9
  function getWeixinLoginApiContext(cfg, accountHint) {
12
10
  const routeTagRaw = (cfg.channels?.weixin)?.routeTag;
13
11
  const sectionRouteTag = typeof routeTagRaw === "number" ? String(routeTagRaw) : typeof routeTagRaw === "string" && routeTagRaw.trim() ? routeTagRaw.trim() : void 0;
@@ -50,6 +48,7 @@ function mergeWeixinConfigAfterLogin(cfg, normalizedAccountId) {
50
48
  */
51
49
  async function runWeixinQrLoginCli(opts) {
52
50
  const configPath = opts.configPath ?? process.env.XOPC_CONFIG_PATH;
51
+ const { loadConfig } = await import("../../../../src/config/loader.js");
53
52
  const cfg = opts.existingConfig ?? loadConfig(configPath);
54
53
  const { baseUrl, routeTag } = getWeixinLoginApiContext(cfg, opts.account);
55
54
  const timeoutMs = opts.timeoutMs ?? 48e4;
@@ -111,7 +110,9 @@ async function runWeixinQrLoginCli(opts) {
111
110
  };
112
111
  }
113
112
  if (opts.writeConfig !== false) try {
114
- await saveConfig(mergeWeixinConfigAfterLogin(cfg, normalizedId), configPath);
113
+ const next = mergeWeixinConfigAfterLogin(cfg, normalizedId);
114
+ const { saveConfig } = await import("../../../../src/config/loader.js");
115
+ await saveConfig(next, configPath);
115
116
  } catch (err) {
116
117
  logger.warn(`Config merge failed (credentials saved on disk): ${String(err)}`);
117
118
  }
@@ -1 +1 @@
1
- {"version":3,"file":"qr-login.js","names":[],"sources":["../../../../../extensions/weixin/src/cli/qr-login.ts"],"sourcesContent":["/**\n * CLI: Weixin QR login — start poll, show terminal QR, persist token + optional config merge.\n */\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport { ConfigSchema } from '@xopcai/xopc/config/schema.js';\nimport { loadConfig, saveConfig } from '@xopcai/xopc/config/loader.js';\n\nimport {\n clearStaleAccountsForUserId,\n normalizeWeixinAccountId,\n registerWeixinAccountId,\n resolveWeixinAccount,\n saveWeixinAccount,\n DEFAULT_BASE_URL,\n} from '../auth/accounts.js';\nimport { clearContextTokensForAccount } from '../messaging/inbound.js';\nimport {\n DEFAULT_ILINK_BOT_TYPE,\n startWeixinLoginWithQr,\n waitForWeixinLogin,\n} from '../auth/login-qr.js';\nimport { logger } from '../util/logger.js';\n\nexport type WeixinQrLoginCliOptions = {\n /** Config file path (same as CLI --config / XOPC_CONFIG). */\n configPath?: string;\n verbose?: boolean;\n /** Client wait for scan (ms). */\n timeoutMs?: number;\n /** Existing account id to re-login, or omit for a new account session. */\n account?: string;\n /** When true, merge `channels.weixin.enabled` and accounts entry after success. */\n writeConfig?: boolean;\n /**\n * When set (e.g. interactive onboard), merges Weixin after login into this in-memory snapshot\n * instead of reloading from disk so prior unsaved onboarding edits are preserved when saving.\n */\n existingConfig?: Config;\n};\n\nexport function getWeixinLoginApiContext(cfg: Config, accountHint?: string): { baseUrl: string; routeTag?: string } {\n const section = cfg.channels?.weixin as { routeTag?: string | number } | undefined;\n const routeTagRaw = section?.routeTag;\n const sectionRouteTag =\n typeof routeTagRaw === 'number'\n ? String(routeTagRaw)\n : typeof routeTagRaw === 'string' && routeTagRaw.trim()\n ? routeTagRaw.trim()\n : undefined;\n\n if (accountHint?.trim()) {\n try {\n const r = resolveWeixinAccount(cfg, accountHint);\n return { baseUrl: r.baseUrl, routeTag: r.routeTag ?? sectionRouteTag };\n } catch {\n // ignore\n }\n }\n return { baseUrl: DEFAULT_BASE_URL, routeTag: sectionRouteTag };\n}\n\nexport function mergeWeixinConfigAfterLogin(cfg: Config, normalizedAccountId: string): Config {\n const prev = cfg.channels?.weixin as Record<string, unknown> | undefined;\n const prevAccounts = (prev?.accounts as Record<string, Record<string, unknown>> | undefined) ?? {};\n const merged = {\n ...cfg,\n channels: {\n ...cfg.channels,\n weixin: {\n ...(prev ?? {}),\n enabled: true,\n accounts: {\n ...prevAccounts,\n [normalizedAccountId]: {\n ...(prevAccounts[normalizedAccountId] ?? {}),\n enabled: true,\n },\n },\n },\n },\n };\n return ConfigSchema.parse(merged);\n}\n\n/**\n * Run interactive Weixin QR login (terminal QR + long-poll until confirmed or timeout).\n */\nexport async function runWeixinQrLoginCli(opts: WeixinQrLoginCliOptions): Promise<{\n ok: boolean;\n accountId?: string;\n message: string;\n}> {\n const configPath = opts.configPath ?? process.env.XOPC_CONFIG_PATH;\n const cfg = opts.existingConfig ?? loadConfig(configPath);\n const { baseUrl, routeTag } = getWeixinLoginApiContext(cfg, opts.account);\n const timeoutMs = opts.timeoutMs ?? 480_000;\n const verbose = Boolean(opts.verbose);\n\n console.log('\\n🔐 Weixin QR login');\n console.log(` API base: ${baseUrl}\\n`);\n\n const startResult = await startWeixinLoginWithQr({\n accountId: opts.account?.trim() || undefined,\n botType: DEFAULT_ILINK_BOT_TYPE,\n routeTag,\n verbose,\n });\n\n if (!startResult.qrcodeUrl) {\n return { ok: false, message: startResult.message || 'Failed to get QR code' };\n }\n\n console.log('Scan this QR code with WeChat:\\n');\n try {\n const qrcodeTerminal = await import(/* @vite-ignore */ 'qrcode-terminal');\n await new Promise<void>((resolve) => {\n qrcodeTerminal.default.generate(startResult.qrcodeUrl!, { small: true }, (qr: string) => {\n console.log(qr);\n resolve();\n });\n });\n } catch {\n console.log('Open this URL in a browser to scan:\\n');\n console.log(startResult.qrcodeUrl);\n }\n console.log('\\nWaiting for confirmation...\\n');\n\n const waitResult = await waitForWeixinLogin({\n sessionKey: startResult.sessionKey,\n timeoutMs,\n verbose,\n routeTag,\n botType: DEFAULT_ILINK_BOT_TYPE,\n });\n\n if (!waitResult.connected || !waitResult.botToken || !waitResult.accountId) {\n return {\n ok: false,\n message: waitResult.message || 'Login did not complete',\n };\n }\n\n const normalizedId = normalizeWeixinAccountId(waitResult.accountId);\n\n try {\n saveWeixinAccount(normalizedId, {\n token: waitResult.botToken,\n baseUrl: waitResult.baseUrl?.trim() || baseUrl,\n userId: waitResult.userId,\n });\n registerWeixinAccountId(normalizedId);\n if (waitResult.userId) {\n clearStaleAccountsForUserId(normalizedId, waitResult.userId, clearContextTokensForAccount);\n }\n } catch (err) {\n logger.error(`Failed to save Weixin account: ${String(err)}`);\n return { ok: false, message: `Saved login failed: ${String(err)}` };\n }\n\n if (opts.writeConfig !== false) {\n try {\n const next = mergeWeixinConfigAfterLogin(cfg, normalizedId);\n await saveConfig(next, configPath);\n } catch (err) {\n logger.warn(`Config merge failed (credentials saved on disk): ${String(err)}`);\n }\n }\n\n console.log(`\\n✅ Weixin connected as account \"${normalizedId}\".`);\n console.log(' Restart the gateway if it is already running.\\n');\n\n return { ok: true, accountId: normalizedId, message: 'OK' };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"qr-login.js","names":[],"sources":["../../../../../extensions/weixin/src/cli/qr-login.ts"],"sourcesContent":["/**\n * CLI: Weixin QR login — start poll, show terminal QR, persist token + optional config merge.\n */\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport { ConfigSchema } from '@xopcai/xopc/config/schema.js';\n\nimport {\n clearStaleAccountsForUserId,\n normalizeWeixinAccountId,\n registerWeixinAccountId,\n resolveWeixinAccount,\n saveWeixinAccount,\n DEFAULT_BASE_URL,\n} from '../auth/accounts.js';\nimport { clearContextTokensForAccount } from '../messaging/inbound.js';\nimport {\n DEFAULT_ILINK_BOT_TYPE,\n startWeixinLoginWithQr,\n waitForWeixinLogin,\n} from '../auth/login-qr.js';\nimport { logger } from '../util/logger.js';\n\nexport type WeixinQrLoginCliOptions = {\n /** Config file path (same as CLI --config / XOPC_CONFIG). */\n configPath?: string;\n verbose?: boolean;\n /** Client wait for scan (ms). */\n timeoutMs?: number;\n /** Existing account id to re-login, or omit for a new account session. */\n account?: string;\n /** When true, merge `channels.weixin.enabled` and accounts entry after success. */\n writeConfig?: boolean;\n /**\n * When set (e.g. interactive onboard), merges Weixin after login into this in-memory snapshot\n * instead of reloading from disk so prior unsaved onboarding edits are preserved when saving.\n */\n existingConfig?: Config;\n};\n\nexport function getWeixinLoginApiContext(cfg: Config, accountHint?: string): { baseUrl: string; routeTag?: string } {\n const section = cfg.channels?.weixin as { routeTag?: string | number } | undefined;\n const routeTagRaw = section?.routeTag;\n const sectionRouteTag =\n typeof routeTagRaw === 'number'\n ? String(routeTagRaw)\n : typeof routeTagRaw === 'string' && routeTagRaw.trim()\n ? routeTagRaw.trim()\n : undefined;\n\n if (accountHint?.trim()) {\n try {\n const r = resolveWeixinAccount(cfg, accountHint);\n return { baseUrl: r.baseUrl, routeTag: r.routeTag ?? sectionRouteTag };\n } catch {\n // ignore\n }\n }\n return { baseUrl: DEFAULT_BASE_URL, routeTag: sectionRouteTag };\n}\n\nexport function mergeWeixinConfigAfterLogin(cfg: Config, normalizedAccountId: string): Config {\n const prev = cfg.channels?.weixin as Record<string, unknown> | undefined;\n const prevAccounts = (prev?.accounts as Record<string, Record<string, unknown>> | undefined) ?? {};\n const merged = {\n ...cfg,\n channels: {\n ...cfg.channels,\n weixin: {\n ...(prev ?? {}),\n enabled: true,\n accounts: {\n ...prevAccounts,\n [normalizedAccountId]: {\n ...(prevAccounts[normalizedAccountId] ?? {}),\n enabled: true,\n },\n },\n },\n },\n };\n return ConfigSchema.parse(merged);\n}\n\n/**\n * Run interactive Weixin QR login (terminal QR + long-poll until confirmed or timeout).\n */\nexport async function runWeixinQrLoginCli(opts: WeixinQrLoginCliOptions): Promise<{\n ok: boolean;\n accountId?: string;\n message: string;\n}> {\n const configPath = opts.configPath ?? process.env.XOPC_CONFIG_PATH;\n const { loadConfig } = await import('@xopcai/xopc/config/loader.js');\n const cfg = opts.existingConfig ?? loadConfig(configPath);\n const { baseUrl, routeTag } = getWeixinLoginApiContext(cfg, opts.account);\n const timeoutMs = opts.timeoutMs ?? 480_000;\n const verbose = Boolean(opts.verbose);\n\n console.log('\\n🔐 Weixin QR login');\n console.log(` API base: ${baseUrl}\\n`);\n\n const startResult = await startWeixinLoginWithQr({\n accountId: opts.account?.trim() || undefined,\n botType: DEFAULT_ILINK_BOT_TYPE,\n routeTag,\n verbose,\n });\n\n if (!startResult.qrcodeUrl) {\n return { ok: false, message: startResult.message || 'Failed to get QR code' };\n }\n\n console.log('Scan this QR code with WeChat:\\n');\n try {\n const qrcodeTerminal = await import(/* @vite-ignore */ 'qrcode-terminal');\n await new Promise<void>((resolve) => {\n qrcodeTerminal.default.generate(startResult.qrcodeUrl!, { small: true }, (qr: string) => {\n console.log(qr);\n resolve();\n });\n });\n } catch {\n console.log('Open this URL in a browser to scan:\\n');\n console.log(startResult.qrcodeUrl);\n }\n console.log('\\nWaiting for confirmation...\\n');\n\n const waitResult = await waitForWeixinLogin({\n sessionKey: startResult.sessionKey,\n timeoutMs,\n verbose,\n routeTag,\n botType: DEFAULT_ILINK_BOT_TYPE,\n });\n\n if (!waitResult.connected || !waitResult.botToken || !waitResult.accountId) {\n return {\n ok: false,\n message: waitResult.message || 'Login did not complete',\n };\n }\n\n const normalizedId = normalizeWeixinAccountId(waitResult.accountId);\n\n try {\n saveWeixinAccount(normalizedId, {\n token: waitResult.botToken,\n baseUrl: waitResult.baseUrl?.trim() || baseUrl,\n userId: waitResult.userId,\n });\n registerWeixinAccountId(normalizedId);\n if (waitResult.userId) {\n clearStaleAccountsForUserId(normalizedId, waitResult.userId, clearContextTokensForAccount);\n }\n } catch (err) {\n logger.error(`Failed to save Weixin account: ${String(err)}`);\n return { ok: false, message: `Saved login failed: ${String(err)}` };\n }\n\n if (opts.writeConfig !== false) {\n try {\n const next = mergeWeixinConfigAfterLogin(cfg, normalizedId);\n const { saveConfig } = await import('@xopcai/xopc/config/loader.js');\n await saveConfig(next, configPath);\n } catch (err) {\n logger.warn(`Config merge failed (credentials saved on disk): ${String(err)}`);\n }\n }\n\n console.log(`\\n✅ Weixin connected as account \"${normalizedId}\".`);\n console.log(' Restart the gateway if it is already running.\\n');\n\n return { ok: true, accountId: normalizedId, message: 'OK' };\n}\n"],"mappings":"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"}
@@ -76,6 +76,7 @@ export declare class WeixinChannelPlugin implements ChannelPlugin<ResolvedWeixin
76
76
  augmentSystemPrompt: () => string;
77
77
  };
78
78
  init(options: ChannelPluginInitOptions): Promise<void>;
79
+ private registerWorkflowProgressCapability;
79
80
  start(options?: ChannelPluginStartOptions): Promise<void>;
80
81
  stop(accountId?: string): Promise<void>;
81
82
  channelIsRunning(cfg: Config): boolean;
@@ -1,8 +1,6 @@
1
1
  import { createLogger } from "../../../src/utils/logger/index.js";
2
2
  import { init_logger } from "../../../src/utils/logger.js";
3
3
  import { WeixinConfigSchema, init_config_schema } from "./config-schema.js";
4
- import { getWorkflowProgressBroker } from "../../../src/agent/workflow/progress-broker.js";
5
- import "../../../src/agent/workflow/index.js";
6
4
  import { evaluateAccess, resolveDmPolicy } from "../../../src/channels/security.js";
7
5
  import { createStandardPairingAdapter } from "../../../src/channels/pairing/pairing-store-adapter.js";
8
6
  import { weixinConfigSurface } from "./config-surface.js";
@@ -13,7 +11,6 @@ import { readFrameworkAllowFromList } from "./auth/pairing.js";
13
11
  import { monitorWeixinProvider } from "./monitor/monitor.js";
14
12
  import { createWeixinOutboundHandlers, weixinTextChunker } from "./outbound-send.js";
15
13
  import { weixinOnboardAdapter } from "./adapters/onboard-cli.js";
16
- import { createWeixinWorkflowProgressCapability } from "./workflow-progress.js";
17
14
  import { isDeepStrictEqual } from "node:util";
18
15
  //#region extensions/weixin/src/plugin.ts
19
16
  /**
@@ -79,10 +76,10 @@ var WeixinChannelPlugin = class {
79
76
  const start = await startWeixinGatewayQrLogin({
80
77
  account: accountId,
81
78
  timeoutMs: typeof inputRecord.timeoutMs === "number" ? inputRecord.timeoutMs : 48e4,
79
+ initialConfig: this.cfg,
82
80
  onPersisted: async (result) => {
83
81
  if (!result.ok) return;
84
- const { loadConfig } = await import("../../../src/config/loader.js");
85
- await this.reloadMonitorsWithConfig(loadConfig(), this.bus);
82
+ await this.reloadMonitorsWithConfig(result.config ?? this.cfg, this.bus);
86
83
  }
87
84
  });
88
85
  if (start.ok === false) return {
@@ -210,9 +207,13 @@ var WeixinChannelPlugin = class {
210
207
  async init(options) {
211
208
  this.bus = options.bus;
212
209
  this.cfg = options.config;
213
- this.workflowProgressUnregister = getWorkflowProgressBroker().registerChannel(createWeixinWorkflowProgressCapability({ getConfig: () => this.cfg }));
210
+ await this.registerWorkflowProgressCapability();
214
211
  log.debug("Weixin plugin initialized");
215
212
  }
213
+ async registerWorkflowProgressCapability() {
214
+ const [{ getWorkflowProgressBroker }, { createWeixinWorkflowProgressCapability }] = await Promise.all([import("../../../src/agent/workflow/progress-broker.js"), import("./workflow-progress.js")]);
215
+ this.workflowProgressUnregister = getWorkflowProgressBroker().registerChannel(createWeixinWorkflowProgressCapability({ getConfig: () => this.cfg }));
216
+ }
216
217
  async start(options) {
217
218
  const ids = options?.accountId ? [options.accountId] : listWeixinAccountIds(this.cfg);
218
219
  for (const accountId of ids) {
@@ -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 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';\nimport { getWorkflowProgressBroker } from '@xopcai/xopc/agent/workflow/index.js';\nimport { createWeixinWorkflowProgressCapability } from './workflow-progress.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 onPersisted: async (result) => {\n if (!result.ok) return;\n const { loadConfig } = await import('@xopcai/xopc/config/loader.js');\n await this.reloadMonitorsWithConfig(loadConfig(), 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 // Workflow progress broker capability — WeChat has no editMessage, so the\n // capability runs in `final-only` mode: the broker silently drops every\n // mid-run snapshot and only invokes us once when `tool_end` lands.\n this.workflowProgressUnregister = getWorkflowProgressBroker().registerChannel(\n createWeixinWorkflowProgressCapability({ getConfig: () => this.cfg }),\n );\n\n log.debug('Weixin plugin initialized');\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 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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"}
@@ -19,7 +19,7 @@
19
19
  * without a WeChat notification. This is consistent with how other WeChat
20
20
  * outbound paths behave.
21
21
  */
22
- import type { ChannelProgressCapability } from '@xopcai/xopc/agent/workflow/index.js';
22
+ import type { ChannelProgressCapability } from '@xopcai/xopc/agent/workflow/channel-capability.js';
23
23
  import type { Config } from '@xopcai/xopc/config/schema.js';
24
24
  export declare function createWeixinWorkflowProgressCapability(opts: {
25
25
  getConfig: () => Config | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-progress.js","names":[],"sources":["../../../../extensions/weixin/src/workflow-progress.ts"],"sourcesContent":["/**\n * WeChat capability for the workflow progress broker.\n *\n * WeChat (ilink/personal account) does not expose an \"edit message\" API for\n * bot replies, so live edit-in-place — what Telegram and Feishu do — is\n * structurally impossible. Trying to fake it with a `append` mode would\n * spam the chat with one message per tick, which violates the social\n * properties of the surface (\"Don't @ me twenty times\").\n *\n * Strategy: **final-only**. The broker silently drops every mid-run update;\n * when `tool_end` fires, the broker calls us once with `isFinal: true` and\n * the full final snapshot rendered as text. The user sees a single\n * tasteful summary at the end.\n *\n * Routing: sessionKey `agent:main:weixin:<accountId>:direct:<ilinkUserId>`. We need a\n * valid `contextToken` for the recipient — which requires the user to have\n * recently messaged the bot (the token is harvested from inbound). If\n * missing, we throw and the broker logs; the run still completes, just\n * without a WeChat notification. This is consistent with how other WeChat\n * outbound paths behave.\n */\n\nimport type {\n ChannelProgressCapability,\n WorkflowProgressPostInput,\n} from '@xopcai/xopc/agent/workflow/index.js';\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport { parseSessionKey } from '@xopcai/xopc/routing/session-key.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\n\nimport { resolveWeixinAccount } from './auth/accounts.js';\nimport { ensureWeixinContextTokenForOutbound } from './messaging/context-token-init.js';\nimport { getContextToken } from './messaging/inbound.js';\nimport { sendMessageWeixin } from './messaging/send.js';\n\nconst log = createLogger('WeixinWorkflowProgress');\n\nconst WEIXIN_TEXT_MAX = 4_000;\n/**\n * Conservative throttle — only enforced when the user overrides the default\n * `final-only` mode to `append`. Set so back-to-back workflows can't spam.\n */\nconst DEFAULT_THROTTLE_MS = 60_000;\n\nexport function createWeixinWorkflowProgressCapability(opts: {\n getConfig: () => Config | undefined;\n}): ChannelProgressCapability {\n return {\n channelId: 'weixin',\n supportsEdit: false,\n defaultThrottleMs: DEFAULT_THROTTLE_MS,\n defaultMode: 'final-only',\n\n async postProgress(input: WorkflowProgressPostInput) {\n const cfg = opts.getConfig();\n if (!cfg) {\n throw new Error('weixin workflow progress: no config loaded');\n }\n const target = resolveTarget(input.sessionKey);\n if (!target) {\n throw new Error(`weixin workflow progress: cannot route sessionKey \"${input.sessionKey}\"`);\n }\n\n let account;\n try {\n account = resolveWeixinAccount(cfg, target.accountId);\n } catch (err) {\n throw new Error(\n `weixin workflow progress: cannot resolve account \"${target.accountId}\": ${errorMessage(err)}`,\n );\n }\n if (!account.configured || !account.token) {\n throw new Error(\n `weixin workflow progress: account \"${target.accountId}\" not configured / logged in`,\n );\n }\n\n let ctxTok = getContextToken(account.accountId, target.to)?.trim();\n if (!ctxTok) {\n ctxTok = (await ensureWeixinContextTokenForOutbound(account.accountId, target.to, account))?.trim();\n }\n if (!ctxTok) {\n // No usable context token — without one WeChat refuses outbound. Better\n // to drop the progress notice than to spam an error; the parent agent\n // still surfaces the result through its normal reply path.\n log.debug(\n { sessionKey: input.sessionKey, accountId: account.accountId },\n 'no context token for recipient; skipping workflow progress send',\n );\n return { messageId: '' };\n }\n\n const text = clampForWeixin(decorateForWeixin(input.text, input.mode, input.isFinal));\n const r = await sendMessageWeixin({\n to: target.to,\n text,\n opts: {\n baseUrl: account.baseUrl,\n token: account.token,\n routeTag: account.routeTag,\n contextToken: ctxTok,\n },\n });\n return { messageId: r.messageId };\n },\n };\n}\n\ninterface ResolvedTarget {\n accountId: string;\n to: string;\n}\n\nfunction resolveTarget(sessionKey: string): ResolvedTarget | null {\n const parsed = parseSessionKey(sessionKey);\n if (!parsed) return null;\n if (parsed.source !== 'weixin') return null;\n if (!parsed.peerId) return null;\n return {\n accountId: parsed.accountId || 'default',\n to: parsed.peerId,\n };\n}\n\nfunction clampForWeixin(text: string): string {\n if (text.length <= WEIXIN_TEXT_MAX) return text;\n return `${text.slice(0, WEIXIN_TEXT_MAX - 1)}…`;\n}\n\n/**\n * Add a one-line header on append-mode messages so the user can tell mid-run\n * snapshots apart from the final summary — WeChat has no editMessage, so they\n * pile up as separate messages and look identical without a marker.\n *\n * - `final-only` mode: no header (default; one summary message per run).\n * - `append` mode + mid-run: \"▾ 工作流进展\" header so the user knows more\n * updates are coming.\n * - `append` mode + final: \"✓ 工作流完成\" header to mark the conclusion.\n *\n * `edit` mode is never reached on WeChat (`supportsEdit: false`), so we don't\n * branch on it. Unknown / missing mode falls through to no header — safe\n * default for hand-rolled callers and tests.\n */\nfunction decorateForWeixin(\n text: string,\n mode: string | undefined,\n isFinal: boolean,\n): string {\n if (mode !== 'append') return text;\n const header = isFinal ? '✓ 工作流完成' : '▾ 工作流进展';\n return `${header}\\n${text}`;\n}\n\nfunction errorMessage(err: unknown): string {\n if (!err) return '';\n if (err instanceof Error) return err.message;\n return String(err);\n}\n"],"mappings":";;;;;;;;kBA2BsE;aACV;AAO5D,MAAM,MAAM,aAAa,yBAAyB;AAElD,MAAM,kBAAkB;;;;;AAKxB,MAAM,sBAAsB;AAE5B,SAAgB,uCAAuC,MAEzB;AAC5B,QAAO;EACL,WAAW;EACX,cAAc;EACd,mBAAmB;EACnB,aAAa;EAEb,MAAM,aAAa,OAAkC;GACnD,MAAM,MAAM,KAAK,WAAW;AAC5B,OAAI,CAAC,IACH,OAAM,IAAI,MAAM,6CAA6C;GAE/D,MAAM,SAAS,cAAc,MAAM,WAAW;AAC9C,OAAI,CAAC,OACH,OAAM,IAAI,MAAM,sDAAsD,MAAM,WAAW,GAAG;GAG5F,IAAI;AACJ,OAAI;AACF,cAAU,qBAAqB,KAAK,OAAO,UAAU;YAC9C,KAAK;AACZ,UAAM,IAAI,MACR,qDAAqD,OAAO,UAAU,KAAK,aAAa,IAAI,GAC7F;;AAEH,OAAI,CAAC,QAAQ,cAAc,CAAC,QAAQ,MAClC,OAAM,IAAI,MACR,sCAAsC,OAAO,UAAU,8BACxD;GAGH,IAAI,SAAS,gBAAgB,QAAQ,WAAW,OAAO,GAAG,EAAE,MAAM;AAClE,OAAI,CAAC,OACH,WAAU,MAAM,oCAAoC,QAAQ,WAAW,OAAO,IAAI,QAAQ,GAAG,MAAM;AAErG,OAAI,CAAC,QAAQ;AAIX,QAAI,MACF;KAAE,YAAY,MAAM;KAAY,WAAW,QAAQ;KAAW,EAC9D,kEACD;AACD,WAAO,EAAE,WAAW,IAAI;;GAG1B,MAAM,OAAO,eAAe,kBAAkB,MAAM,MAAM,MAAM,MAAM,MAAM,QAAQ,CAAC;AAWrF,UAAO,EAAE,YAAW,MAVJ,kBAAkB;IAChC,IAAI,OAAO;IACX;IACA,MAAM;KACJ,SAAS,QAAQ;KACjB,OAAO,QAAQ;KACf,UAAU,QAAQ;KAClB,cAAc;KACf;IACF,CAAC,EACoB,WAAW;;EAEpC;;AAQH,SAAS,cAAc,YAA2C;CAChE,MAAM,SAAS,gBAAgB,WAAW;AAC1C,KAAI,CAAC,OAAQ,QAAO;AACpB,KAAI,OAAO,WAAW,SAAU,QAAO;AACvC,KAAI,CAAC,OAAO,OAAQ,QAAO;AAC3B,QAAO;EACL,WAAW,OAAO,aAAa;EAC/B,IAAI,OAAO;EACZ;;AAGH,SAAS,eAAe,MAAsB;AAC5C,KAAI,KAAK,UAAU,gBAAiB,QAAO;AAC3C,QAAO,GAAG,KAAK,MAAM,GAAG,kBAAkB,EAAE,CAAC;;;;;;;;;;;;;;;;AAiB/C,SAAS,kBACP,MACA,MACA,SACQ;AACR,KAAI,SAAS,SAAU,QAAO;AAE9B,QAAO,GADQ,UAAU,YAAY,UACpB,IAAI;;AAGvB,SAAS,aAAa,KAAsB;AAC1C,KAAI,CAAC,IAAK,QAAO;AACjB,KAAI,eAAe,MAAO,QAAO,IAAI;AACrC,QAAO,OAAO,IAAI"}
1
+ {"version":3,"file":"workflow-progress.js","names":[],"sources":["../../../../extensions/weixin/src/workflow-progress.ts"],"sourcesContent":["/**\n * WeChat capability for the workflow progress broker.\n *\n * WeChat (ilink/personal account) does not expose an \"edit message\" API for\n * bot replies, so live edit-in-place — what Telegram and Feishu do — is\n * structurally impossible. Trying to fake it with a `append` mode would\n * spam the chat with one message per tick, which violates the social\n * properties of the surface (\"Don't @ me twenty times\").\n *\n * Strategy: **final-only**. The broker silently drops every mid-run update;\n * when `tool_end` fires, the broker calls us once with `isFinal: true` and\n * the full final snapshot rendered as text. The user sees a single\n * tasteful summary at the end.\n *\n * Routing: sessionKey `agent:main:weixin:<accountId>:direct:<ilinkUserId>`. We need a\n * valid `contextToken` for the recipient — which requires the user to have\n * recently messaged the bot (the token is harvested from inbound). If\n * missing, we throw and the broker logs; the run still completes, just\n * without a WeChat notification. This is consistent with how other WeChat\n * outbound paths behave.\n */\n\nimport type {\n ChannelProgressCapability,\n WorkflowProgressPostInput,\n} from '@xopcai/xopc/agent/workflow/channel-capability.js';\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport { parseSessionKey } from '@xopcai/xopc/routing/session-key.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\n\nimport { resolveWeixinAccount } from './auth/accounts.js';\nimport { ensureWeixinContextTokenForOutbound } from './messaging/context-token-init.js';\nimport { getContextToken } from './messaging/inbound.js';\nimport { sendMessageWeixin } from './messaging/send.js';\n\nconst log = createLogger('WeixinWorkflowProgress');\n\nconst WEIXIN_TEXT_MAX = 4_000;\n/**\n * Conservative throttle — only enforced when the user overrides the default\n * `final-only` mode to `append`. Set so back-to-back workflows can't spam.\n */\nconst DEFAULT_THROTTLE_MS = 60_000;\n\nexport function createWeixinWorkflowProgressCapability(opts: {\n getConfig: () => Config | undefined;\n}): ChannelProgressCapability {\n return {\n channelId: 'weixin',\n supportsEdit: false,\n defaultThrottleMs: DEFAULT_THROTTLE_MS,\n defaultMode: 'final-only',\n\n async postProgress(input: WorkflowProgressPostInput) {\n const cfg = opts.getConfig();\n if (!cfg) {\n throw new Error('weixin workflow progress: no config loaded');\n }\n const target = resolveTarget(input.sessionKey);\n if (!target) {\n throw new Error(`weixin workflow progress: cannot route sessionKey \"${input.sessionKey}\"`);\n }\n\n let account;\n try {\n account = resolveWeixinAccount(cfg, target.accountId);\n } catch (err) {\n throw new Error(\n `weixin workflow progress: cannot resolve account \"${target.accountId}\": ${errorMessage(err)}`,\n );\n }\n if (!account.configured || !account.token) {\n throw new Error(\n `weixin workflow progress: account \"${target.accountId}\" not configured / logged in`,\n );\n }\n\n let ctxTok = getContextToken(account.accountId, target.to)?.trim();\n if (!ctxTok) {\n ctxTok = (await ensureWeixinContextTokenForOutbound(account.accountId, target.to, account))?.trim();\n }\n if (!ctxTok) {\n // No usable context token — without one WeChat refuses outbound. Better\n // to drop the progress notice than to spam an error; the parent agent\n // still surfaces the result through its normal reply path.\n log.debug(\n { sessionKey: input.sessionKey, accountId: account.accountId },\n 'no context token for recipient; skipping workflow progress send',\n );\n return { messageId: '' };\n }\n\n const text = clampForWeixin(decorateForWeixin(input.text, input.mode, input.isFinal));\n const r = await sendMessageWeixin({\n to: target.to,\n text,\n opts: {\n baseUrl: account.baseUrl,\n token: account.token,\n routeTag: account.routeTag,\n contextToken: ctxTok,\n },\n });\n return { messageId: r.messageId };\n },\n };\n}\n\ninterface ResolvedTarget {\n accountId: string;\n to: string;\n}\n\nfunction resolveTarget(sessionKey: string): ResolvedTarget | null {\n const parsed = parseSessionKey(sessionKey);\n if (!parsed) return null;\n if (parsed.source !== 'weixin') return null;\n if (!parsed.peerId) return null;\n return {\n accountId: parsed.accountId || 'default',\n to: parsed.peerId,\n };\n}\n\nfunction clampForWeixin(text: string): string {\n if (text.length <= WEIXIN_TEXT_MAX) return text;\n return `${text.slice(0, WEIXIN_TEXT_MAX - 1)}…`;\n}\n\n/**\n * Add a one-line header on append-mode messages so the user can tell mid-run\n * snapshots apart from the final summary — WeChat has no editMessage, so they\n * pile up as separate messages and look identical without a marker.\n *\n * - `final-only` mode: no header (default; one summary message per run).\n * - `append` mode + mid-run: \"▾ 工作流进展\" header so the user knows more\n * updates are coming.\n * - `append` mode + final: \"✓ 工作流完成\" header to mark the conclusion.\n *\n * `edit` mode is never reached on WeChat (`supportsEdit: false`), so we don't\n * branch on it. Unknown / missing mode falls through to no header — safe\n * default for hand-rolled callers and tests.\n */\nfunction decorateForWeixin(\n text: string,\n mode: string | undefined,\n isFinal: boolean,\n): string {\n if (mode !== 'append') return text;\n const header = isFinal ? '✓ 工作流完成' : '▾ 工作流进展';\n return `${header}\\n${text}`;\n}\n\nfunction errorMessage(err: unknown): string {\n if (!err) return '';\n if (err instanceof Error) return err.message;\n return String(err);\n}\n"],"mappings":";;;;;;;;kBA2BsE;aACV;AAO5D,MAAM,MAAM,aAAa,yBAAyB;AAElD,MAAM,kBAAkB;;;;;AAKxB,MAAM,sBAAsB;AAE5B,SAAgB,uCAAuC,MAEzB;AAC5B,QAAO;EACL,WAAW;EACX,cAAc;EACd,mBAAmB;EACnB,aAAa;EAEb,MAAM,aAAa,OAAkC;GACnD,MAAM,MAAM,KAAK,WAAW;AAC5B,OAAI,CAAC,IACH,OAAM,IAAI,MAAM,6CAA6C;GAE/D,MAAM,SAAS,cAAc,MAAM,WAAW;AAC9C,OAAI,CAAC,OACH,OAAM,IAAI,MAAM,sDAAsD,MAAM,WAAW,GAAG;GAG5F,IAAI;AACJ,OAAI;AACF,cAAU,qBAAqB,KAAK,OAAO,UAAU;YAC9C,KAAK;AACZ,UAAM,IAAI,MACR,qDAAqD,OAAO,UAAU,KAAK,aAAa,IAAI,GAC7F;;AAEH,OAAI,CAAC,QAAQ,cAAc,CAAC,QAAQ,MAClC,OAAM,IAAI,MACR,sCAAsC,OAAO,UAAU,8BACxD;GAGH,IAAI,SAAS,gBAAgB,QAAQ,WAAW,OAAO,GAAG,EAAE,MAAM;AAClE,OAAI,CAAC,OACH,WAAU,MAAM,oCAAoC,QAAQ,WAAW,OAAO,IAAI,QAAQ,GAAG,MAAM;AAErG,OAAI,CAAC,QAAQ;AAIX,QAAI,MACF;KAAE,YAAY,MAAM;KAAY,WAAW,QAAQ;KAAW,EAC9D,kEACD;AACD,WAAO,EAAE,WAAW,IAAI;;GAG1B,MAAM,OAAO,eAAe,kBAAkB,MAAM,MAAM,MAAM,MAAM,MAAM,QAAQ,CAAC;AAWrF,UAAO,EAAE,YAAW,MAVJ,kBAAkB;IAChC,IAAI,OAAO;IACX;IACA,MAAM;KACJ,SAAS,QAAQ;KACjB,OAAO,QAAQ;KACf,UAAU,QAAQ;KAClB,cAAc;KACf;IACF,CAAC,EACoB,WAAW;;EAEpC;;AAQH,SAAS,cAAc,YAA2C;CAChE,MAAM,SAAS,gBAAgB,WAAW;AAC1C,KAAI,CAAC,OAAQ,QAAO;AACpB,KAAI,OAAO,WAAW,SAAU,QAAO;AACvC,KAAI,CAAC,OAAO,OAAQ,QAAO;AAC3B,QAAO;EACL,WAAW,OAAO,aAAa;EAC/B,IAAI,OAAO;EACZ;;AAGH,SAAS,eAAe,MAAsB;AAC5C,KAAI,KAAK,UAAU,gBAAiB,QAAO;AAC3C,QAAO,GAAG,KAAK,MAAM,GAAG,kBAAkB,EAAE,CAAC;;;;;;;;;;;;;;;;AAiB/C,SAAS,kBACP,MACA,MACA,SACQ;AACR,KAAI,SAAS,SAAU,QAAO;AAE9B,QAAO,GADQ,UAAU,YAAY,UACpB,IAAI;;AAGvB,SAAS,aAAa,KAAsB;AAC1C,KAAI,CAAC,IAAK,QAAO;AACjB,KAAI,eAAe,MAAO,QAAO,IAAI;AACrC,QAAO,OAAO,IAAI"}