@xopcai/xopc 0.0.47 → 0.0.49

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 (98) hide show
  1. package/dist/extensions/dingtalk/src/accounts.js +1 -1
  2. package/dist/extensions/dingtalk/src/accounts.js.map +1 -1
  3. package/dist/extensions/dingtalk/src/merge-config.js +1 -1
  4. package/dist/extensions/dingtalk/src/merge-config.js.map +1 -1
  5. package/dist/extensions/dingtalk/src/plugin.js +1 -1
  6. package/dist/extensions/dingtalk/src/plugin.js.map +1 -1
  7. package/dist/extensions/feishu/src/adapters/cli-login.js +8 -8
  8. package/dist/extensions/feishu/src/adapters/cli-login.js.map +1 -1
  9. package/dist/extensions/feishu/src/adapters/onboard-cli.js +8 -8
  10. package/dist/extensions/feishu/src/adapters/onboard-cli.js.map +1 -1
  11. package/dist/extensions/feishu/src/plugin.js +1 -1
  12. package/dist/extensions/feishu/src/plugin.js.map +1 -1
  13. package/dist/extensions/feishu/src/schema/config-schema.js +2 -2
  14. package/dist/extensions/feishu/src/schema/config-schema.js.map +1 -1
  15. package/dist/extensions/feishu/src/state/accounts.js +1 -1
  16. package/dist/extensions/feishu/src/state/accounts.js.map +1 -1
  17. package/dist/extensions/feishu/src/streaming/streaming-adapter.js +82 -36
  18. package/dist/extensions/feishu/src/streaming/streaming-adapter.js.map +1 -1
  19. package/dist/extensions/telegram/src/command-handler.js +1 -1
  20. package/dist/extensions/telegram/src/command-handler.js.map +1 -1
  21. package/dist/extensions/telegram/xopc.extension.json +1 -1
  22. package/dist/extensions/weixin/src/auth/accounts.js +1 -1
  23. package/dist/extensions/weixin/src/auth/accounts.js.map +1 -1
  24. package/dist/extensions/weixin/src/config-schema.js +2 -2
  25. package/dist/extensions/weixin/src/config-schema.js.map +1 -1
  26. package/dist/extensions/weixin/src/config-surface.js +1 -1
  27. package/dist/extensions/weixin/src/config-surface.js.map +1 -1
  28. package/dist/extensions/weixin/src/messaging/process-message.js +2 -2
  29. package/dist/extensions/weixin/src/messaging/process-message.js.map +1 -1
  30. package/dist/extensions/weixin/src/plugin.js +1 -1
  31. package/dist/extensions/weixin/src/plugin.js.map +1 -1
  32. package/dist/gateway/static/root/assets/{agents-DdWPgyn-.js → agents-CQllyJhj.js} +2 -2
  33. package/dist/gateway/static/root/assets/{agents-DdWPgyn-.js.map → agents-CQllyJhj.js.map} +1 -1
  34. package/dist/gateway/static/root/assets/{apps-page-BTi_W1y1.js → apps-page-BWI3RVMh.js} +2 -2
  35. package/dist/gateway/static/root/assets/{apps-page-BTi_W1y1.js.map → apps-page-BWI3RVMh.js.map} +1 -1
  36. package/dist/gateway/static/root/assets/channels-settings-rfmhXfC4.js +2 -0
  37. package/dist/gateway/static/root/assets/channels-settings-rfmhXfC4.js.map +1 -0
  38. package/dist/gateway/static/root/assets/{cron-dreaming-jobs-DinMur-Z.js → cron-dreaming-jobs-BzCQy56Z.js} +2 -2
  39. package/dist/gateway/static/root/assets/{cron-dreaming-jobs-DinMur-Z.js.map → cron-dreaming-jobs-BzCQy56Z.js.map} +1 -1
  40. package/dist/gateway/static/root/assets/{cron-page-Bu05Z2oL.js → cron-page-CjTuH_av.js} +2 -2
  41. package/dist/gateway/static/root/assets/{cron-page-Bu05Z2oL.js.map → cron-page-CjTuH_av.js.map} +1 -1
  42. package/dist/gateway/static/root/assets/{dist-wDej8fSi.js → dist-DIeOihYP.js} +2 -2
  43. package/dist/gateway/static/root/assets/{dist-wDej8fSi.js.map → dist-DIeOihYP.js.map} +1 -1
  44. package/dist/gateway/static/root/assets/{extension-debug-page-CZBu7-zM.js → extension-debug-page-CR-4lZOn.js} +2 -2
  45. package/dist/gateway/static/root/assets/{extension-debug-page-CZBu7-zM.js.map → extension-debug-page-CR-4lZOn.js.map} +1 -1
  46. package/dist/gateway/static/root/assets/{extension-page-CnOyLPrh.js → extension-page-BNwcYj2Q.js} +2 -2
  47. package/dist/gateway/static/root/assets/{extension-page-CnOyLPrh.js.map → extension-page-BNwcYj2Q.js.map} +1 -1
  48. package/dist/gateway/static/root/assets/{extension-settings-page-BOHn3S1a.js → extension-settings-page-CDpiZPV4.js} +2 -2
  49. package/dist/gateway/static/root/assets/{extension-settings-page-BOHn3S1a.js.map → extension-settings-page-CDpiZPV4.js.map} +1 -1
  50. package/dist/gateway/static/root/assets/{heartbeat-config-api-OqFXdrMO.js → heartbeat-config-api-CSbqK-L_.js} +2 -2
  51. package/dist/gateway/static/root/assets/{heartbeat-config-api-OqFXdrMO.js.map → heartbeat-config-api-CSbqK-L_.js.map} +1 -1
  52. package/dist/gateway/static/root/assets/{index-DeELk--t.js → index-D3sMd_aw.js} +11 -11
  53. package/dist/gateway/static/root/assets/{index-DeELk--t.js.map → index-D3sMd_aw.js.map} +1 -1
  54. package/dist/gateway/static/root/assets/{logs-page-DiN42-yE.js → logs-page-DLcWAXU5.js} +2 -2
  55. package/dist/gateway/static/root/assets/{logs-page-DiN42-yE.js.map → logs-page-DLcWAXU5.js.map} +1 -1
  56. package/dist/gateway/static/root/assets/{sessions-page-B5oxRfRm.js → sessions-page-Ck3lS3N_.js} +2 -2
  57. package/dist/gateway/static/root/assets/{sessions-page-B5oxRfRm.js.map → sessions-page-Ck3lS3N_.js.map} +1 -1
  58. package/dist/gateway/static/root/assets/{settings-page-DaRY3XEp.js → settings-page-Dp_eFgub.js} +2 -2
  59. package/dist/gateway/static/root/assets/{settings-page-DaRY3XEp.js.map → settings-page-Dp_eFgub.js.map} +1 -1
  60. package/dist/gateway/static/root/assets/{skills-page-BGDLiQZ6.js → skills-page-ClRj9e6K.js} +2 -2
  61. package/dist/gateway/static/root/assets/{skills-page-BGDLiQZ6.js.map → skills-page-ClRj9e6K.js.map} +1 -1
  62. package/dist/gateway/static/root/assets/{use-image-provider-credentials-DcP2SYn3.js → use-image-provider-credentials-DAi5Iu8c.js} +2 -2
  63. package/dist/gateway/static/root/assets/{use-image-provider-credentials-DcP2SYn3.js.map → use-image-provider-credentials-DAi5Iu8c.js.map} +1 -1
  64. package/dist/gateway/static/root/index.html +1 -1
  65. package/dist/package.js +1 -1
  66. package/dist/src/agent/goals/checklist-judge.js +21 -17
  67. package/dist/src/agent/goals/checklist-judge.js.map +1 -1
  68. package/dist/src/agent/goals/evaluate-turn.js +6 -11
  69. package/dist/src/agent/goals/evaluate-turn.js.map +1 -1
  70. package/dist/src/agent/goals/judge.d.ts +16 -0
  71. package/dist/src/agent/goals/judge.js +61 -13
  72. package/dist/src/agent/goals/judge.js.map +1 -1
  73. package/dist/src/agent/goals/state.d.ts +7 -0
  74. package/dist/src/agent/goals/state.js +24 -1
  75. package/dist/src/agent/goals/state.js.map +1 -1
  76. package/dist/src/agent/service.js +2 -0
  77. package/dist/src/agent/service.js.map +1 -1
  78. package/dist/src/chat-commands/builtins/model.d.ts +2 -2
  79. package/dist/src/chat-commands/builtins/model.js +10 -8
  80. package/dist/src/chat-commands/builtins/model.js.map +1 -1
  81. package/dist/src/chat-commands/builtins/system.js +1 -1
  82. package/dist/src/chat-commands/builtins/system.js.map +1 -1
  83. package/dist/src/gateway/hono/routes/channels.js +2 -2
  84. package/dist/src/gateway/hono/routes/channels.js.map +1 -1
  85. package/dist/src/gateway/hono/routes/config.js +3 -3
  86. package/dist/src/gateway/hono/routes/config.js.map +1 -1
  87. package/dist/src/tui/backends/embedded-backend.d.ts +1 -1
  88. package/dist/src/tui/backends/embedded-backend.js +24 -3
  89. package/dist/src/tui/backends/embedded-backend.js.map +1 -1
  90. package/dist/src/tui/backends/gateway-sse-backend.js +36 -10
  91. package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
  92. package/dist/src/tui/tui-commands.js +1 -1
  93. package/dist/src/tui/tui-commands.js.map +1 -1
  94. package/dist/src/tui/tui.js +12 -1
  95. package/dist/src/tui/tui.js.map +1 -1
  96. package/package.json +1 -1
  97. package/dist/gateway/static/root/assets/channels-settings-CjUmKQrC.js +0 -2
  98. package/dist/gateway/static/root/assets/channels-settings-CjUmKQrC.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"accounts.js","names":[],"sources":["../../../../../extensions/feishu/src/state/accounts.ts"],"sourcesContent":["import type { Config } from '@xopcai/xopc/config/schema.js';\n\nimport type { FeishuAccountConfig, FeishuConfig } from '../schema/config-schema.js';\n\nexport interface ResolvedFeishuAccount {\n accountId: string;\n name?: string;\n enabled: boolean;\n configured: boolean;\n\n appId?: string;\n appSecret?: string;\n domain: 'feishu' | 'lark' | string;\n connectionMode: 'websocket' | 'webhook';\n\n webhookHost?: string;\n webhookPort?: number;\n webhookPath?: string;\n verificationToken?: string;\n encryptKey?: string;\n\n dmPolicy: 'pairing' | 'allowlist' | 'open' | 'disabled';\n groupPolicy: 'open' | 'disabled' | 'allowlist';\n allowFrom?: Array<string | number>;\n groupAllowFrom?: Array<string | number>;\n requireMention?: boolean;\n\n historyLimit: number;\n textChunkLimit: number;\n renderMode?: 'auto' | 'raw' | 'card';\n\n reactionNotifications?: 'off' | 'own' | 'all';\n\n /** Opt-in: only `true` enables Feishu streaming (Thinking… + incremental updates). */\n streaming: boolean;\n blockStreamingCoalesce?: { enabled?: boolean; minChars?: number; idleMs?: number };\n\n tools?: FeishuConfig['tools'];\n actions?: FeishuConfig['actions'];\n dynamicAgentCreation?: FeishuConfig['dynamicAgentCreation'];\n}\n\nfunction asFeishuSection(cfg: Config): FeishuConfig | undefined {\n return cfg.channels?.feishu as FeishuConfig | undefined;\n}\n\nexport function listFeishuAccountIds(cfg: Config): string[] {\n const section = asFeishuSection(cfg);\n if (!section) return [];\n const accounts = section.accounts ?? {};\n const keys = Object.keys(accounts);\n if (keys.length > 0) return keys;\n // Single-account layout\n return ['default'];\n}\n\nfunction resolveRootAccount(section: FeishuConfig): FeishuAccountConfig {\n return {\n enabled: section.enabled,\n name: 'Default Account',\n appId: section.appId,\n appSecret: section.appSecret,\n domain: section.domain,\n connectionMode: section.connectionMode,\n webhookHost: (section as any).webhookHost,\n webhookPort: (section as any).webhookPort,\n webhookPath: (section as any).webhookPath,\n verificationToken: (section as any).verificationToken,\n encryptKey: (section as any).encryptKey,\n dmPolicy: section.dmPolicy,\n groupPolicy: section.groupPolicy,\n allowFrom: section.allowFrom,\n groupAllowFrom: section.groupAllowFrom,\n requireMention: section.requireMention,\n historyLimit: section.historyLimit,\n textChunkLimit: section.textChunkLimit,\n renderMode: (section as any).renderMode,\n reactionNotifications: section.reactionNotifications,\n streaming: section.streaming,\n blockStreamingCoalesce: section.blockStreamingCoalesce,\n tools: section.tools,\n actions: section.actions,\n dynamicAgentCreation: section.dynamicAgentCreation,\n };\n}\n\nfunction mergeAccount(section: FeishuConfig, account: FeishuAccountConfig | undefined): FeishuAccountConfig {\n const root = resolveRootAccount(section);\n if (!account) return root;\n return {\n ...root,\n ...account,\n // arrays should replace, not concat\n allowFrom: account.allowFrom ?? root.allowFrom,\n groupAllowFrom: account.groupAllowFrom ?? root.groupAllowFrom,\n };\n}\n\nexport function resolveFeishuAccount(cfg: Config, accountId?: string | null): ResolvedFeishuAccount {\n const section = asFeishuSection(cfg) ?? ({ enabled: false } as FeishuConfig);\n const requested = (accountId ?? '').trim();\n\n const accounts = section.accounts ?? {};\n const hasNamedAccounts = Object.keys(accounts).length > 0;\n const effectiveAccountId = requested || section.defaultAccount || (hasNamedAccounts ? Object.keys(accounts)[0] : 'default');\n\n const raw = hasNamedAccounts ? accounts[effectiveAccountId] : resolveRootAccount(section);\n const merged = mergeAccount(section, raw);\n\n const enabled = merged.enabled !== false && section.enabled !== false;\n const appId = merged.appId?.trim() || undefined;\n const appSecret = merged.appSecret?.trim() || undefined;\n const configured = Boolean(appId && appSecret);\n\n return {\n accountId: effectiveAccountId,\n name: merged.name,\n enabled,\n configured,\n appId,\n appSecret,\n domain: (merged.domain ?? 'feishu') as any,\n connectionMode: (merged.connectionMode ?? 'websocket') as any,\n webhookHost: (merged as any).webhookHost,\n webhookPort: (merged as any).webhookPort,\n webhookPath: (merged as any).webhookPath,\n verificationToken: (merged as any).verificationToken,\n encryptKey: (merged as any).encryptKey,\n dmPolicy: (merged.dmPolicy ?? 'pairing') as any,\n groupPolicy: (merged.groupPolicy ?? 'allowlist') as any,\n allowFrom: merged.allowFrom,\n groupAllowFrom: merged.groupAllowFrom,\n requireMention: merged.requireMention,\n historyLimit: typeof merged.historyLimit === 'number' ? merged.historyLimit : 50,\n textChunkLimit: typeof merged.textChunkLimit === 'number' ? merged.textChunkLimit : 4000,\n renderMode: (merged as any).renderMode,\n reactionNotifications: merged.reactionNotifications,\n streaming: merged.streaming === true,\n blockStreamingCoalesce: merged.blockStreamingCoalesce as any,\n tools: merged.tools,\n actions: merged.actions,\n dynamicAgentCreation: merged.dynamicAgentCreation,\n };\n}\n\n"],"mappings":";AA0CA,SAAS,gBAAgB,KAAuC;AAC9D,QAAO,IAAI,UAAU;;AAGvB,SAAgB,qBAAqB,KAAuB;CAC1D,MAAM,UAAU,gBAAgB,IAAI;AACpC,KAAI,CAAC,QAAS,QAAO,EAAE;CACvB,MAAM,WAAW,QAAQ,YAAY,EAAE;CACvC,MAAM,OAAO,OAAO,KAAK,SAAS;AAClC,KAAI,KAAK,SAAS,EAAG,QAAO;AAE5B,QAAO,CAAC,UAAU;;AAGpB,SAAS,mBAAmB,SAA4C;AACtE,QAAO;EACL,SAAS,QAAQ;EACjB,MAAM;EACN,OAAO,QAAQ;EACf,WAAW,QAAQ;EACnB,QAAQ,QAAQ;EAChB,gBAAgB,QAAQ;EACxB,aAAc,QAAgB;EAC9B,aAAc,QAAgB;EAC9B,aAAc,QAAgB;EAC9B,mBAAoB,QAAgB;EACpC,YAAa,QAAgB;EAC7B,UAAU,QAAQ;EAClB,aAAa,QAAQ;EACrB,WAAW,QAAQ;EACnB,gBAAgB,QAAQ;EACxB,gBAAgB,QAAQ;EACxB,cAAc,QAAQ;EACtB,gBAAgB,QAAQ;EACxB,YAAa,QAAgB;EAC7B,uBAAuB,QAAQ;EAC/B,WAAW,QAAQ;EACnB,wBAAwB,QAAQ;EAChC,OAAO,QAAQ;EACf,SAAS,QAAQ;EACjB,sBAAsB,QAAQ;EAC/B;;AAGH,SAAS,aAAa,SAAuB,SAA+D;CAC1G,MAAM,OAAO,mBAAmB,QAAQ;AACxC,KAAI,CAAC,QAAS,QAAO;AACrB,QAAO;EACL,GAAG;EACH,GAAG;EAEH,WAAW,QAAQ,aAAa,KAAK;EACrC,gBAAgB,QAAQ,kBAAkB,KAAK;EAChD;;AAGH,SAAgB,qBAAqB,KAAa,WAAkD;CAClG,MAAM,UAAU,gBAAgB,IAAI,IAAK,EAAE,SAAS,OAAO;CAC3D,MAAM,aAAa,aAAa,IAAI,MAAM;CAE1C,MAAM,WAAW,QAAQ,YAAY,EAAE;CACvC,MAAM,mBAAmB,OAAO,KAAK,SAAS,CAAC,SAAS;CACxD,MAAM,qBAAqB,aAAa,QAAQ,mBAAmB,mBAAmB,OAAO,KAAK,SAAS,CAAC,KAAK;CAGjH,MAAM,SAAS,aAAa,SADhB,mBAAmB,SAAS,sBAAsB,mBAAmB,QAAQ,CAChD;CAEzC,MAAM,UAAU,OAAO,YAAY,SAAS,QAAQ,YAAY;CAChE,MAAM,QAAQ,OAAO,OAAO,MAAM,IAAI,KAAA;CACtC,MAAM,YAAY,OAAO,WAAW,MAAM,IAAI,KAAA;CAC9C,MAAM,aAAa,QAAQ,SAAS,UAAU;AAE9C,QAAO;EACL,WAAW;EACX,MAAM,OAAO;EACb;EACA;EACA;EACA;EACA,QAAS,OAAO,UAAU;EAC1B,gBAAiB,OAAO,kBAAkB;EAC1C,aAAc,OAAe;EAC7B,aAAc,OAAe;EAC7B,aAAc,OAAe;EAC7B,mBAAoB,OAAe;EACnC,YAAa,OAAe;EAC5B,UAAW,OAAO,YAAY;EAC9B,aAAc,OAAO,eAAe;EACpC,WAAW,OAAO;EAClB,gBAAgB,OAAO;EACvB,gBAAgB,OAAO;EACvB,cAAc,OAAO,OAAO,iBAAiB,WAAW,OAAO,eAAe;EAC9E,gBAAgB,OAAO,OAAO,mBAAmB,WAAW,OAAO,iBAAiB;EACpF,YAAa,OAAe;EAC5B,uBAAuB,OAAO;EAC9B,WAAW,OAAO,cAAc;EAChC,wBAAwB,OAAO;EAC/B,OAAO,OAAO;EACd,SAAS,OAAO;EAChB,sBAAsB,OAAO;EAC9B"}
1
+ {"version":3,"file":"accounts.js","names":[],"sources":["../../../../../extensions/feishu/src/state/accounts.ts"],"sourcesContent":["import type { Config } from '@xopcai/xopc/config/schema.js';\n\nimport type { FeishuAccountConfig, FeishuConfig } from '../schema/config-schema.js';\n\nexport interface ResolvedFeishuAccount {\n accountId: string;\n name?: string;\n enabled: boolean;\n configured: boolean;\n\n appId?: string;\n appSecret?: string;\n domain: 'feishu' | 'lark' | string;\n connectionMode: 'websocket' | 'webhook';\n\n webhookHost?: string;\n webhookPort?: number;\n webhookPath?: string;\n verificationToken?: string;\n encryptKey?: string;\n\n dmPolicy: 'pairing' | 'allowlist' | 'open' | 'disabled';\n groupPolicy: 'open' | 'disabled' | 'allowlist';\n allowFrom?: Array<string | number>;\n groupAllowFrom?: Array<string | number>;\n requireMention?: boolean;\n\n historyLimit: number;\n textChunkLimit: number;\n renderMode?: 'auto' | 'raw' | 'card';\n\n reactionNotifications?: 'off' | 'own' | 'all';\n\n /** Opt-in: only `true` enables Feishu streaming (Thinking… + incremental updates). */\n streaming: boolean;\n blockStreamingCoalesce?: { enabled?: boolean; minChars?: number; idleMs?: number };\n\n tools?: FeishuConfig['tools'];\n actions?: FeishuConfig['actions'];\n dynamicAgentCreation?: FeishuConfig['dynamicAgentCreation'];\n}\n\nfunction asFeishuSection(cfg: Config): FeishuConfig | undefined {\n return cfg.channels?.feishu as FeishuConfig | undefined;\n}\n\nexport function listFeishuAccountIds(cfg: Config): string[] {\n const section = asFeishuSection(cfg);\n if (!section) return [];\n const accounts = section.accounts ?? {};\n const keys = Object.keys(accounts);\n if (keys.length > 0) return keys;\n // Single-account layout\n return ['default'];\n}\n\nfunction resolveRootAccount(section: FeishuConfig): FeishuAccountConfig {\n return {\n enabled: section.enabled,\n name: 'Default Account',\n appId: section.appId,\n appSecret: section.appSecret,\n domain: section.domain,\n connectionMode: section.connectionMode,\n webhookHost: (section as any).webhookHost,\n webhookPort: (section as any).webhookPort,\n webhookPath: (section as any).webhookPath,\n verificationToken: (section as any).verificationToken,\n encryptKey: (section as any).encryptKey,\n dmPolicy: section.dmPolicy,\n groupPolicy: section.groupPolicy,\n allowFrom: section.allowFrom,\n groupAllowFrom: section.groupAllowFrom,\n requireMention: section.requireMention,\n historyLimit: section.historyLimit,\n textChunkLimit: section.textChunkLimit,\n renderMode: (section as any).renderMode,\n reactionNotifications: section.reactionNotifications,\n streaming: section.streaming,\n blockStreamingCoalesce: section.blockStreamingCoalesce,\n tools: section.tools,\n actions: section.actions,\n dynamicAgentCreation: section.dynamicAgentCreation,\n };\n}\n\nfunction mergeAccount(section: FeishuConfig, account: FeishuAccountConfig | undefined): FeishuAccountConfig {\n const root = resolveRootAccount(section);\n if (!account) return root;\n return {\n ...root,\n ...account,\n // arrays should replace, not concat\n allowFrom: account.allowFrom ?? root.allowFrom,\n groupAllowFrom: account.groupAllowFrom ?? root.groupAllowFrom,\n };\n}\n\nexport function resolveFeishuAccount(cfg: Config, accountId?: string | null): ResolvedFeishuAccount {\n const section = asFeishuSection(cfg) ?? ({ enabled: false } as FeishuConfig);\n const requested = (accountId ?? '').trim();\n\n const accounts = section.accounts ?? {};\n const hasNamedAccounts = Object.keys(accounts).length > 0;\n const effectiveAccountId = requested || section.defaultAccount || (hasNamedAccounts ? Object.keys(accounts)[0] : 'default');\n\n const raw = hasNamedAccounts ? accounts[effectiveAccountId] : resolveRootAccount(section);\n const merged = mergeAccount(section, raw);\n\n const enabled = merged.enabled !== false && section.enabled !== false;\n const appId = merged.appId?.trim() || undefined;\n const appSecret = merged.appSecret?.trim() || undefined;\n const configured = Boolean(appId && appSecret);\n\n return {\n accountId: effectiveAccountId,\n name: merged.name,\n enabled,\n configured,\n appId,\n appSecret,\n domain: (merged.domain ?? 'feishu') as any,\n connectionMode: (merged.connectionMode ?? 'websocket') as any,\n webhookHost: (merged as any).webhookHost,\n webhookPort: (merged as any).webhookPort,\n webhookPath: (merged as any).webhookPath,\n verificationToken: (merged as any).verificationToken,\n encryptKey: (merged as any).encryptKey,\n dmPolicy: (merged.dmPolicy ?? 'open') as any,\n groupPolicy: (merged.groupPolicy ?? 'allowlist') as any,\n allowFrom: merged.allowFrom,\n groupAllowFrom: merged.groupAllowFrom,\n requireMention: merged.requireMention,\n historyLimit: typeof merged.historyLimit === 'number' ? merged.historyLimit : 50,\n textChunkLimit: typeof merged.textChunkLimit === 'number' ? merged.textChunkLimit : 4000,\n renderMode: (merged as any).renderMode,\n reactionNotifications: merged.reactionNotifications,\n streaming: merged.streaming === true,\n blockStreamingCoalesce: merged.blockStreamingCoalesce as any,\n tools: merged.tools,\n actions: merged.actions,\n dynamicAgentCreation: merged.dynamicAgentCreation,\n };\n}\n\n"],"mappings":";AA0CA,SAAS,gBAAgB,KAAuC;AAC9D,QAAO,IAAI,UAAU;;AAGvB,SAAgB,qBAAqB,KAAuB;CAC1D,MAAM,UAAU,gBAAgB,IAAI;AACpC,KAAI,CAAC,QAAS,QAAO,EAAE;CACvB,MAAM,WAAW,QAAQ,YAAY,EAAE;CACvC,MAAM,OAAO,OAAO,KAAK,SAAS;AAClC,KAAI,KAAK,SAAS,EAAG,QAAO;AAE5B,QAAO,CAAC,UAAU;;AAGpB,SAAS,mBAAmB,SAA4C;AACtE,QAAO;EACL,SAAS,QAAQ;EACjB,MAAM;EACN,OAAO,QAAQ;EACf,WAAW,QAAQ;EACnB,QAAQ,QAAQ;EAChB,gBAAgB,QAAQ;EACxB,aAAc,QAAgB;EAC9B,aAAc,QAAgB;EAC9B,aAAc,QAAgB;EAC9B,mBAAoB,QAAgB;EACpC,YAAa,QAAgB;EAC7B,UAAU,QAAQ;EAClB,aAAa,QAAQ;EACrB,WAAW,QAAQ;EACnB,gBAAgB,QAAQ;EACxB,gBAAgB,QAAQ;EACxB,cAAc,QAAQ;EACtB,gBAAgB,QAAQ;EACxB,YAAa,QAAgB;EAC7B,uBAAuB,QAAQ;EAC/B,WAAW,QAAQ;EACnB,wBAAwB,QAAQ;EAChC,OAAO,QAAQ;EACf,SAAS,QAAQ;EACjB,sBAAsB,QAAQ;EAC/B;;AAGH,SAAS,aAAa,SAAuB,SAA+D;CAC1G,MAAM,OAAO,mBAAmB,QAAQ;AACxC,KAAI,CAAC,QAAS,QAAO;AACrB,QAAO;EACL,GAAG;EACH,GAAG;EAEH,WAAW,QAAQ,aAAa,KAAK;EACrC,gBAAgB,QAAQ,kBAAkB,KAAK;EAChD;;AAGH,SAAgB,qBAAqB,KAAa,WAAkD;CAClG,MAAM,UAAU,gBAAgB,IAAI,IAAK,EAAE,SAAS,OAAO;CAC3D,MAAM,aAAa,aAAa,IAAI,MAAM;CAE1C,MAAM,WAAW,QAAQ,YAAY,EAAE;CACvC,MAAM,mBAAmB,OAAO,KAAK,SAAS,CAAC,SAAS;CACxD,MAAM,qBAAqB,aAAa,QAAQ,mBAAmB,mBAAmB,OAAO,KAAK,SAAS,CAAC,KAAK;CAGjH,MAAM,SAAS,aAAa,SADhB,mBAAmB,SAAS,sBAAsB,mBAAmB,QAAQ,CAChD;CAEzC,MAAM,UAAU,OAAO,YAAY,SAAS,QAAQ,YAAY;CAChE,MAAM,QAAQ,OAAO,OAAO,MAAM,IAAI,KAAA;CACtC,MAAM,YAAY,OAAO,WAAW,MAAM,IAAI,KAAA;CAC9C,MAAM,aAAa,QAAQ,SAAS,UAAU;AAE9C,QAAO;EACL,WAAW;EACX,MAAM,OAAO;EACb;EACA;EACA;EACA;EACA,QAAS,OAAO,UAAU;EAC1B,gBAAiB,OAAO,kBAAkB;EAC1C,aAAc,OAAe;EAC7B,aAAc,OAAe;EAC7B,aAAc,OAAe;EAC7B,mBAAoB,OAAe;EACnC,YAAa,OAAe;EAC5B,UAAW,OAAO,YAAY;EAC9B,aAAc,OAAO,eAAe;EACpC,WAAW,OAAO;EAClB,gBAAgB,OAAO;EACvB,gBAAgB,OAAO;EACvB,cAAc,OAAO,OAAO,iBAAiB,WAAW,OAAO,eAAe;EAC9E,gBAAgB,OAAO,OAAO,mBAAmB,WAAW,OAAO,iBAAiB;EACpF,YAAa,OAAe;EAC5B,uBAAuB,OAAO;EAC9B,WAAW,OAAO,cAAc;EAChC,wBAAwB,OAAO;EAC/B,OAAO,OAAO;EACd,SAAS,OAAO;EAChB,sBAAsB,OAAO;EAC9B"}
@@ -4,9 +4,33 @@ import { resolveFeishuAccount } from "../state/accounts.js";
4
4
  import { createFeishuClient } from "../transport/client/client.js";
5
5
  import { getFeishuBindingByMessageId, recordFeishuMessageBinding } from "../state/message-bindings.js";
6
6
  import { formatFeishuOutboundText } from "../format.js";
7
+ import { randomUUID } from "node:crypto";
7
8
  //#region extensions/feishu/src/streaming/streaming-adapter.ts
8
9
  init_logger();
9
10
  const log = createLogger("FeishuStreaming");
11
+ /** Normalize CardKit `card.create` responses (SDK / OpenAPI envelope shapes differ). */
12
+ function extractFeishuCardKitCreateCardId(raw) {
13
+ if (!raw || typeof raw !== "object") return void 0;
14
+ const o = raw;
15
+ const tryId = (v) => {
16
+ if (typeof v !== "string") return void 0;
17
+ const t = v.trim();
18
+ return t.length > 0 ? t : void 0;
19
+ };
20
+ const fromData = (data) => {
21
+ if (!data || typeof data !== "object") return void 0;
22
+ const d = data;
23
+ return tryId(d.card_id) ?? tryId(d.cardId);
24
+ };
25
+ return fromData(o.data) ?? (o.data && typeof o.data === "object" ? fromData(o.data.data) : void 0) ?? tryId(o.card_id);
26
+ }
27
+ function feishuOpenApiOk(res) {
28
+ if (res === null || res === void 0) return false;
29
+ if (typeof res !== "object") return true;
30
+ const code = res.code;
31
+ if (code === void 0 || code === null) return true;
32
+ return Number(code) === 0;
33
+ }
10
34
  function createFeishuStreamingAdapter(getConfig) {
11
35
  return { startStream(options) {
12
36
  const account = resolveFeishuAccount(getConfig(), options.accountId ?? "default");
@@ -21,7 +45,7 @@ function createFeishuStreamingAdapter(getConfig) {
21
45
  let ready = null;
22
46
  let cardId;
23
47
  let cardSeq = 0;
24
- const cardElementId = "md_1";
48
+ const cardElementId = `md_${randomUUID().replace(/-/g, "")}`;
25
49
  let fallbackSent = false;
26
50
  const renderMode = account.renderMode ?? "auto";
27
51
  const preferCard = renderMode === "card" || renderMode === "auto";
@@ -119,7 +143,7 @@ function createFeishuStreamingAdapter(getConfig) {
119
143
  messageId,
120
144
  mode: preferCard ? "card" : "text"
121
145
  }, "Feishu streaming edit failed");
122
- if (preferCard) await sendFallbackText(text);
146
+ await sendFallbackText(text);
123
147
  }
124
148
  };
125
149
  const start = async () => {
@@ -139,41 +163,63 @@ function createFeishuStreamingAdapter(getConfig) {
139
163
  content: thinking
140
164
  }] }
141
165
  };
142
- const c = await api.cardkit.v1.card.create({ data: {
143
- type: "card_json",
144
- data: JSON.stringify(cardSpec)
145
- } });
146
- cardId = c?.data?.card_id ?? c?.card_id ?? void 0;
147
- const res = options.replyToMessageId ? await api.im.message.reply({
148
- path: { message_id: options.replyToMessageId },
149
- data: {
150
- msg_type: "interactive",
151
- content: JSON.stringify({
152
- type: "card",
153
- data: { card_id: cardId }
154
- }),
155
- ...options.threadId ? { reply_in_thread: true } : {}
156
- }
157
- }) : await api.im.message.create({
158
- params: { receive_id_type },
159
- data: {
160
- receive_id: options.chatId,
161
- msg_type: "interactive",
162
- content: JSON.stringify({
163
- type: "card",
164
- data: { card_id: cardId }
165
- })
166
- }
167
- });
168
- messageId = res?.data?.message_id ?? res?.message_id ?? void 0;
169
- if (messageId) recordBindingIfReply(messageId);
170
- log.info({
166
+ let created;
167
+ try {
168
+ created = await api.cardkit.v1.card.create({ data: {
169
+ type: "card_json",
170
+ data: JSON.stringify(cardSpec)
171
+ } });
172
+ } catch (err) {
173
+ log.warn({
174
+ err,
175
+ accountId: account.accountId
176
+ }, "Feishu cardkit.card.create threw; falling back to text stream");
177
+ created = null;
178
+ }
179
+ const apiOk = feishuOpenApiOk(created);
180
+ cardId = apiOk ? extractFeishuCardKitCreateCardId(created) : void 0;
181
+ if (!apiOk) log.warn({
171
182
  accountId: account.accountId,
172
- chatId: options.chatId,
173
- messageId,
174
- mode: "card"
175
- }, "Feishu streaming started");
176
- return;
183
+ code: created?.code,
184
+ msg: created?.msg
185
+ }, "Feishu cardkit.card.create returned non-zero code; falling back to text stream");
186
+ else if (!cardId) log.warn({
187
+ accountId: account.accountId,
188
+ responsePreview: JSON.stringify(created).slice(0, 400)
189
+ }, "Feishu cardkit.card.create returned no card_id; falling back to text stream");
190
+ if (cardId) {
191
+ const res = options.replyToMessageId ? await api.im.message.reply({
192
+ path: { message_id: options.replyToMessageId },
193
+ data: {
194
+ msg_type: "interactive",
195
+ content: JSON.stringify({
196
+ type: "card",
197
+ data: { card_id: cardId }
198
+ }),
199
+ ...options.threadId ? { reply_in_thread: true } : {}
200
+ }
201
+ }) : await api.im.message.create({
202
+ params: { receive_id_type },
203
+ data: {
204
+ receive_id: options.chatId,
205
+ msg_type: "interactive",
206
+ content: JSON.stringify({
207
+ type: "card",
208
+ data: { card_id: cardId }
209
+ })
210
+ }
211
+ });
212
+ messageId = res?.data?.message_id ?? res?.message_id ?? void 0;
213
+ if (messageId) recordBindingIfReply(messageId);
214
+ log.info({
215
+ accountId: account.accountId,
216
+ chatId: options.chatId,
217
+ messageId,
218
+ mode: "card"
219
+ }, "Feishu streaming started");
220
+ return;
221
+ }
222
+ cardId = void 0;
177
223
  }
178
224
  const res = options.replyToMessageId ? await api.im.message.reply({
179
225
  path: { message_id: options.replyToMessageId },
@@ -1 +1 @@
1
- {"version":3,"file":"streaming-adapter.js","names":[],"sources":["../../../../../extensions/feishu/src/streaming/streaming-adapter.ts"],"sourcesContent":["import type {\n ChannelStreamHandle,\n ChannelStreamingAdapter,\n} from '@xopcai/xopc/channels/plugin-types.js';\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\n\nimport { resolveFeishuAccount } from '../state/accounts.js';\nimport { formatFeishuOutboundText } from '../format.js';\nimport { getFeishuBindingByMessageId, recordFeishuMessageBinding } from '../state/message-bindings.js';\nimport { createFeishuClient } from '../transport/client/client.js';\n\nconst log = createLogger('FeishuStreaming');\n\nexport function createFeishuStreamingAdapter(getConfig: () => Config): ChannelStreamingAdapter {\n return {\n startStream(options: {\n chatId: string;\n accountId?: string;\n threadId?: string;\n replyToMessageId?: string;\n parseMode?: 'Markdown' | 'HTML';\n }): ChannelStreamHandle | null {\n const cfg = getConfig();\n const account = resolveFeishuAccount(cfg, options.accountId ?? 'default');\n if (!account.configured) {\n return null;\n }\n // Feishu streaming is opt-in. When omitted/false, fall back to normal final outbound.\n if (account.streaming !== true) {\n return null;\n }\n const { api } = createFeishuClient(account);\n\n let messageId: string | undefined;\n let lastText = '';\n let timer: NodeJS.Timeout | null = null;\n let aborted = false;\n let editedAtLeastOnce = false;\n let ready: Promise<void> | null = null;\n let cardId: string | undefined;\n let cardSeq = 0;\n const cardElementId = 'md_1';\n let fallbackSent = false;\n\n const renderMode = account.renderMode ?? 'auto';\n const preferCard = renderMode === 'card' || renderMode === 'auto';\n\n const formatStreamText = (text: string) => {\n if (!text.trim()) return text;\n if (text.trim() === 'Thinking…') return text;\n const forCardMarkdown = Boolean(preferCard && cardId);\n return formatFeishuOutboundText({\n text,\n renderMode,\n forCardMarkdown,\n });\n };\n\n const recordBindingIfReply = (childMessageId: string) => {\n if (!childMessageId) return;\n const parentId = options.replyToMessageId;\n if (!parentId) return;\n const parent = getFeishuBindingByMessageId(parentId);\n if (!parent) return;\n recordFeishuMessageBinding({ ...parent, messageId: childMessageId });\n };\n\n const edit = async (text: string) => {\n if (ready) await ready;\n if (!messageId) return;\n const outbound = formatStreamText(text);\n if (cardId && preferCard) {\n cardSeq += 1;\n await (api as any).cardkit.v1.cardElement.content({\n path: { card_id: cardId, element_id: cardElementId },\n data: {\n content: outbound,\n sequence: cardSeq,\n uuid: `${cardId}:${cardSeq}`,\n },\n });\n } else {\n await (api as any).im.v1.message.update({\n path: { message_id: messageId },\n data: { msg_type: 'text', content: JSON.stringify({ text: outbound }) },\n });\n }\n editedAtLeastOnce = true;\n };\n\n const sendFallbackText = async (text: string) => {\n if (fallbackSent) return;\n fallbackSent = true;\n const receive_id_type =\n options.chatId.startsWith('ou_') || options.chatId.startsWith('on_') ? 'open_id' : 'chat_id';\n try {\n const outbound = formatStreamText(text);\n const res = options.replyToMessageId\n ? await (api as any).im.message.reply({\n path: { message_id: options.replyToMessageId },\n data: {\n msg_type: 'text',\n content: JSON.stringify({ text: outbound }),\n ...(options.threadId ? { reply_in_thread: true } : {}),\n },\n })\n : await (api as any).im.message.create({\n params: { receive_id_type },\n data: {\n receive_id: options.chatId,\n msg_type: 'text',\n content: JSON.stringify({ text: outbound }),\n },\n });\n const mid = res?.data?.message_id ?? res?.message_id ?? undefined;\n if (mid) recordBindingIfReply(mid);\n // Mark as delivered through the channel so the final outbound is skipped.\n editedAtLeastOnce = true;\n messageId = mid ?? messageId;\n // If we were in card mode, stop trying to update the card.\n cardId = undefined;\n } catch (err) {\n log.warn({ err, accountId: account.accountId }, 'Feishu streaming fallback send failed');\n }\n };\n\n const flush = async () => {\n if (aborted) return;\n if (ready) await ready;\n if (!messageId) return;\n const text = lastText;\n if (!text.trim()) return;\n try {\n await edit(text);\n } catch (err) {\n // Don't crash the agent loop. In card mode, card streaming can fail due to missing CardKit scopes;\n // fall back to sending a plain text reply so the user never gets stuck on \"Thinking…\".\n log.warn({ err, accountId: account.accountId, messageId, mode: preferCard ? 'card' : 'text' }, 'Feishu streaming edit failed');\n if (preferCard) {\n await sendFallbackText(text);\n }\n }\n };\n\n const start = async () => {\n const receive_id_type =\n options.chatId.startsWith('ou_') || options.chatId.startsWith('on_') ? 'open_id' : 'chat_id';\n const thinking = 'Thinking…';\n\n if (preferCard) {\n const cardSpec = {\n schema: '2.0',\n config: { update_multi: true },\n header: {\n title: { tag: 'plain_text', content: 'xopc' },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n element_id: cardElementId,\n content: thinking,\n },\n ],\n },\n };\n\n const c = await (api as any).cardkit.v1.card.create({\n data: { type: 'card_json', data: JSON.stringify(cardSpec) },\n });\n cardId = c?.data?.card_id ?? c?.card_id ?? undefined;\n\n const res = options.replyToMessageId\n ? await (api as any).im.message.reply({\n path: { message_id: options.replyToMessageId },\n data: {\n msg_type: 'interactive',\n content: JSON.stringify({ type: 'card', data: { card_id: cardId } }),\n ...(options.threadId ? { reply_in_thread: true } : {}),\n },\n })\n : await (api as any).im.message.create({\n params: { receive_id_type },\n data: {\n receive_id: options.chatId,\n msg_type: 'interactive',\n content: JSON.stringify({ type: 'card', data: { card_id: cardId } }),\n },\n });\n messageId = res?.data?.message_id ?? res?.message_id ?? undefined;\n if (messageId) recordBindingIfReply(messageId);\n log.info(\n { accountId: account.accountId, chatId: options.chatId, messageId, mode: 'card' },\n 'Feishu streaming started',\n );\n return;\n }\n\n const res = options.replyToMessageId\n ? await (api as any).im.message.reply({\n path: { message_id: options.replyToMessageId },\n data: {\n msg_type: 'text',\n content: JSON.stringify({ text: thinking }),\n ...(options.threadId ? { reply_in_thread: true } : {}),\n },\n })\n : await (api as any).im.message.create({\n params: { receive_id_type },\n data: {\n receive_id: options.chatId,\n msg_type: 'text',\n content: JSON.stringify({ text: thinking }),\n },\n });\n messageId = res?.data?.message_id ?? res?.message_id ?? undefined;\n if (messageId) recordBindingIfReply(messageId);\n log.info(\n { accountId: account.accountId, chatId: options.chatId, messageId, mode: 'text' },\n 'Feishu streaming started',\n );\n };\n\n ready = start().catch((err) => {\n log.warn({ err, accountId: account.accountId }, 'Feishu streaming start failed');\n });\n\n return {\n update: (text: string) => {\n lastText = text;\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n void flush().catch((err) => log.warn({ err }, 'Feishu streaming flush failed'));\n }, 800);\n },\n end: async () => {\n if (timer) clearTimeout(timer);\n await flush();\n log.debug(\n {\n accountId: account.accountId,\n chatId: options.chatId,\n messageId,\n editedAtLeastOnce,\n lastTextLen: lastText.length,\n mode: preferCard ? 'card' : 'text',\n },\n 'Feishu streaming ended',\n );\n },\n abort: async () => {\n aborted = true;\n if (timer) clearTimeout(timer);\n },\n messageId: () => undefined,\n skipFinalOutbound: () => Boolean(messageId) && editedAtLeastOnce,\n updateProgress: undefined,\n setProgress: undefined,\n };\n },\n };\n}\n\n"],"mappings":"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"}
1
+ {"version":3,"file":"streaming-adapter.js","names":[],"sources":["../../../../../extensions/feishu/src/streaming/streaming-adapter.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport type {\n ChannelStreamHandle,\n ChannelStreamingAdapter,\n} from '@xopcai/xopc/channels/plugin-types.js';\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\n\nimport { resolveFeishuAccount } from '../state/accounts.js';\nimport { formatFeishuOutboundText } from '../format.js';\nimport { getFeishuBindingByMessageId, recordFeishuMessageBinding } from '../state/message-bindings.js';\nimport { createFeishuClient } from '../transport/client/client.js';\n\nconst log = createLogger('FeishuStreaming');\n\n/** Normalize CardKit `card.create` responses (SDK / OpenAPI envelope shapes differ). */\nfunction extractFeishuCardKitCreateCardId(raw: unknown): string | undefined {\n if (!raw || typeof raw !== 'object') return undefined;\n const o = raw as Record<string, unknown>;\n const tryId = (v: unknown): string | undefined => {\n if (typeof v !== 'string') return undefined;\n const t = v.trim();\n return t.length > 0 ? t : undefined;\n };\n\n const fromData = (data: unknown): string | undefined => {\n if (!data || typeof data !== 'object') return undefined;\n const d = data as Record<string, unknown>;\n return tryId(d.card_id) ?? tryId(d.cardId);\n };\n\n return (\n fromData(o.data) ??\n (o.data && typeof o.data === 'object' ? fromData((o.data as Record<string, unknown>).data) : undefined) ??\n tryId(o.card_id)\n );\n}\n\nfunction feishuOpenApiOk(res: unknown): boolean {\n if (res === null || res === undefined) return false;\n if (typeof res !== 'object') return true;\n const code = (res as Record<string, unknown>).code;\n if (code === undefined || code === null) return true;\n return Number(code) === 0;\n}\n\nexport function createFeishuStreamingAdapter(getConfig: () => Config): ChannelStreamingAdapter {\n return {\n startStream(options: {\n chatId: string;\n accountId?: string;\n threadId?: string;\n replyToMessageId?: string;\n parseMode?: 'Markdown' | 'HTML';\n }): ChannelStreamHandle | null {\n const cfg = getConfig();\n const account = resolveFeishuAccount(cfg, options.accountId ?? 'default');\n if (!account.configured) {\n return null;\n }\n // Feishu streaming is opt-in. When omitted/false, fall back to normal final outbound.\n if (account.streaming !== true) {\n return null;\n }\n const { api } = createFeishuClient(account);\n\n let messageId: string | undefined;\n let lastText = '';\n let timer: NodeJS.Timeout | null = null;\n let aborted = false;\n let editedAtLeastOnce = false;\n let ready: Promise<void> | null = null;\n let cardId: string | undefined;\n let cardSeq = 0;\n const cardElementId = `md_${randomUUID().replace(/-/g, '')}`;\n let fallbackSent = false;\n\n const renderMode = account.renderMode ?? 'auto';\n const preferCard = renderMode === 'card' || renderMode === 'auto';\n\n const formatStreamText = (text: string) => {\n if (!text.trim()) return text;\n if (text.trim() === 'Thinking…') return text;\n const forCardMarkdown = Boolean(preferCard && cardId);\n return formatFeishuOutboundText({\n text,\n renderMode,\n forCardMarkdown,\n });\n };\n\n const recordBindingIfReply = (childMessageId: string) => {\n if (!childMessageId) return;\n const parentId = options.replyToMessageId;\n if (!parentId) return;\n const parent = getFeishuBindingByMessageId(parentId);\n if (!parent) return;\n recordFeishuMessageBinding({ ...parent, messageId: childMessageId });\n };\n\n const edit = async (text: string) => {\n if (ready) await ready;\n if (!messageId) return;\n const outbound = formatStreamText(text);\n if (cardId && preferCard) {\n cardSeq += 1;\n await (api as any).cardkit.v1.cardElement.content({\n path: { card_id: cardId, element_id: cardElementId },\n data: {\n content: outbound,\n sequence: cardSeq,\n uuid: `${cardId}:${cardSeq}`,\n },\n });\n } else {\n await (api as any).im.v1.message.update({\n path: { message_id: messageId },\n data: { msg_type: 'text', content: JSON.stringify({ text: outbound }) },\n });\n }\n editedAtLeastOnce = true;\n };\n\n const sendFallbackText = async (text: string) => {\n if (fallbackSent) return;\n fallbackSent = true;\n const receive_id_type =\n options.chatId.startsWith('ou_') || options.chatId.startsWith('on_') ? 'open_id' : 'chat_id';\n try {\n const outbound = formatStreamText(text);\n const res = options.replyToMessageId\n ? await (api as any).im.message.reply({\n path: { message_id: options.replyToMessageId },\n data: {\n msg_type: 'text',\n content: JSON.stringify({ text: outbound }),\n ...(options.threadId ? { reply_in_thread: true } : {}),\n },\n })\n : await (api as any).im.message.create({\n params: { receive_id_type },\n data: {\n receive_id: options.chatId,\n msg_type: 'text',\n content: JSON.stringify({ text: outbound }),\n },\n });\n const mid = res?.data?.message_id ?? res?.message_id ?? undefined;\n if (mid) recordBindingIfReply(mid);\n // Mark as delivered through the channel so the final outbound is skipped.\n editedAtLeastOnce = true;\n messageId = mid ?? messageId;\n // If we were in card mode, stop trying to update the card.\n cardId = undefined;\n } catch (err) {\n log.warn({ err, accountId: account.accountId }, 'Feishu streaming fallback send failed');\n }\n };\n\n const flush = async () => {\n if (aborted) return;\n if (ready) await ready;\n if (!messageId) return;\n const text = lastText;\n if (!text.trim()) return;\n try {\n await edit(text);\n } catch (err) {\n // Don't crash the agent loop. CardKit updates can fail (scopes, element conflicts); plain\n // `im.message.update` can also fail for some message states — always fall back to a text\n // reply so the user is not stuck on \"Thinking…\".\n log.warn({ err, accountId: account.accountId, messageId, mode: preferCard ? 'card' : 'text' }, 'Feishu streaming edit failed');\n await sendFallbackText(text);\n }\n };\n\n const start = async () => {\n const receive_id_type =\n options.chatId.startsWith('ou_') || options.chatId.startsWith('on_') ? 'open_id' : 'chat_id';\n const thinking = 'Thinking…';\n\n if (preferCard) {\n const cardSpec = {\n schema: '2.0',\n config: { update_multi: true },\n header: {\n title: { tag: 'plain_text', content: 'xopc' },\n },\n body: {\n elements: [\n {\n tag: 'markdown',\n element_id: cardElementId,\n content: thinking,\n },\n ],\n },\n };\n\n let created: unknown;\n try {\n created = await (api as any).cardkit.v1.card.create({\n data: { type: 'card_json', data: JSON.stringify(cardSpec) },\n });\n } catch (err) {\n log.warn({ err, accountId: account.accountId }, 'Feishu cardkit.card.create threw; falling back to text stream');\n created = null;\n }\n\n const apiOk = feishuOpenApiOk(created);\n cardId = apiOk ? extractFeishuCardKitCreateCardId(created) : undefined;\n\n if (!apiOk) {\n log.warn(\n {\n accountId: account.accountId,\n code: (created as Record<string, unknown> | null)?.code,\n msg: (created as Record<string, unknown> | null)?.msg,\n },\n 'Feishu cardkit.card.create returned non-zero code; falling back to text stream',\n );\n } else if (!cardId) {\n log.warn(\n { accountId: account.accountId, responsePreview: JSON.stringify(created).slice(0, 400) },\n 'Feishu cardkit.card.create returned no card_id; falling back to text stream',\n );\n }\n\n if (cardId) {\n const res = options.replyToMessageId\n ? await (api as any).im.message.reply({\n path: { message_id: options.replyToMessageId },\n data: {\n msg_type: 'interactive',\n content: JSON.stringify({ type: 'card', data: { card_id: cardId } }),\n ...(options.threadId ? { reply_in_thread: true } : {}),\n },\n })\n : await (api as any).im.message.create({\n params: { receive_id_type },\n data: {\n receive_id: options.chatId,\n msg_type: 'interactive',\n content: JSON.stringify({ type: 'card', data: { card_id: cardId } }),\n },\n });\n messageId = res?.data?.message_id ?? res?.message_id ?? undefined;\n if (messageId) recordBindingIfReply(messageId);\n log.info(\n { accountId: account.accountId, chatId: options.chatId, messageId, mode: 'card' },\n 'Feishu streaming started',\n );\n return;\n }\n\n // Clear so downstream edits use im.message.update (text), not CardKit.\n cardId = undefined;\n }\n\n const res = options.replyToMessageId\n ? await (api as any).im.message.reply({\n path: { message_id: options.replyToMessageId },\n data: {\n msg_type: 'text',\n content: JSON.stringify({ text: thinking }),\n ...(options.threadId ? { reply_in_thread: true } : {}),\n },\n })\n : await (api as any).im.message.create({\n params: { receive_id_type },\n data: {\n receive_id: options.chatId,\n msg_type: 'text',\n content: JSON.stringify({ text: thinking }),\n },\n });\n messageId = res?.data?.message_id ?? res?.message_id ?? undefined;\n if (messageId) recordBindingIfReply(messageId);\n log.info(\n { accountId: account.accountId, chatId: options.chatId, messageId, mode: 'text' },\n 'Feishu streaming started',\n );\n };\n\n ready = start().catch((err) => {\n log.warn({ err, accountId: account.accountId }, 'Feishu streaming start failed');\n });\n\n return {\n update: (text: string) => {\n lastText = text;\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n void flush().catch((err) => log.warn({ err }, 'Feishu streaming flush failed'));\n }, 800);\n },\n end: async () => {\n if (timer) clearTimeout(timer);\n await flush();\n log.debug(\n {\n accountId: account.accountId,\n chatId: options.chatId,\n messageId,\n editedAtLeastOnce,\n lastTextLen: lastText.length,\n mode: preferCard ? 'card' : 'text',\n },\n 'Feishu streaming ended',\n );\n },\n abort: async () => {\n aborted = true;\n if (timer) clearTimeout(timer);\n },\n messageId: () => undefined,\n skipFinalOutbound: () => Boolean(messageId) && editedAtLeastOnce,\n updateProgress: undefined,\n setProgress: undefined,\n };\n },\n };\n}\n\n"],"mappings":"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"}
@@ -57,7 +57,7 @@ function createTelegramCommandHandler(deps) {
57
57
  const handleStart = async (ctx) => {
58
58
  try {
59
59
  const hasProviders = getAvailableProviders().length > 0;
60
- await ctx.reply("👋 *Welcome to xopc!*\n\nI am your AI assistant. Here are the available commands:\n\n🤖 *Model Selection*\n/models - Select a model to use\n/switch <model-id> - Switch to a specific model\n\n📊 *Session Management*\n/new - Start a new session (archive current)\n/list - List all your sessions\n/usage - View token usage statistics\n/cleanup - Clean up old sessions\n\n🔊 *Voice (TTS)*\n/tts - Show TTS settings\n/tts on|off - Enable/disable TTS\n/tts always|inbound|tagged|never - Set trigger mode\n\n🛠️ *Skills*\n/skills reload - Reload all skills from disk\n\n💡 *Tip*: Just send a message to start chatting!" + (hasProviders ? "" : "\n\n⚠️ *Note*: No LLM providers configured. Please set up API keys in your config."), { parse_mode: "Markdown" });
60
+ await ctx.reply("👋 *Welcome to xopc!*\n\nI am your AI assistant. Here are the available commands:\n\n🤖 *Model Selection*\n/models - List models (name + `provider/model` ref for /switch)\n/switch <provider/model-id> - Same ref as shown in /models\n\n📊 *Session Management*\n/new - Start a new session (archive current)\n/list - List all your sessions\n/usage - View token usage statistics\n/cleanup - Clean up old sessions\n\n🔊 *Voice (TTS)*\n/tts - Show TTS settings\n/tts on|off - Enable/disable TTS\n/tts always|inbound|tagged|never - Set trigger mode\n\n🛠️ *Skills*\n/skills reload - Reload all skills from disk\n\n💡 *Tip*: Just send a message to start chatting!" + (hasProviders ? "" : "\n\n⚠️ *Note*: No LLM providers configured. Please set up API keys in your config."), { parse_mode: "Markdown" });
61
61
  } catch (err) {
62
62
  log.error({ err }, "Failed to handle start command");
63
63
  await ctx.reply("❌ Failed to show welcome message.");
@@ -1 +1 @@
1
- {"version":3,"file":"command-handler.js","names":[],"sources":["../../../../extensions/telegram/src/command-handler.ts"],"sourcesContent":["/**\n * Telegram Command Handler\n * \n * Handles Telegram-specific UI interactions (inline keyboards, callbacks).\n * Core commands (/new, /usage, /models, etc.) are handled by the unified command system.\n */\n\nimport type { Context } from 'grammy';\n\nimport type { TelegramAccountManager } from './account-manager.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\nimport type { Config } from '@xopcai/xopc/config/index.js';\nimport { isProviderConfiguredSync } from '@xopcai/xopc/providers/index.js';\nimport { TelegramInlineKeyboards, type ProviderInfo } from './inline-keyboards.js';\nimport { getProviderDisplayName, getModelsByProvider, getDefaultModelSync, getAllProviders } from '@xopcai/xopc/providers/index.js';\nimport { generateSessionKey } from '@xopcai/xopc/chat-commands/session-key.js';\n\nconst log = createLogger('TelegramCommandHandler');\n\nexport interface TelegramCommandHandlerDeps {\n bus: any;\n config: Config;\n accountManager: TelegramAccountManager;\n getSessionModel: (sessionKey: string) => string | undefined;\n setSessionModel: (sessionKey: string, modelId: string) => void | Promise<void>;\n // Optional callbacks for inline keyboard handling\n showProviderModels?: (ctx: Context, providerId: string) => Promise<void>;\n showProvidersAgain?: (ctx: Context) => Promise<void>;\n handleCleanupConfirm?: (ctx: Context) => Promise<void>;\n}\n\nexport function createTelegramCommandHandler(deps: TelegramCommandHandlerDeps) {\n const { config, accountManager, getSessionModel, setSessionModel, bus } = deps;\n\n // ========== Helper Functions ==========\n\n const getAvailableProviders = (): ProviderInfo[] => {\n const allProviders = getAllProviders();\n const available: ProviderInfo[] = [];\n\n for (const providerId of allProviders) {\n if (isProviderConfiguredSync(providerId)) {\n available.push({ id: providerId, name: getProviderDisplayName(providerId) });\n }\n }\n\n if (available.length === 0) {\n available.push({ id: 'minimax', name: 'MiniMax' });\n }\n\n return available;\n };\n\n const getModelsForProvider = (providerId: string) => {\n const models = getModelsByProvider(providerId);\n \n if (models.length === 0) {\n return [{ id: `${providerId}/default`, name: 'default', provider: providerId }];\n }\n \n return models.map(m => ({\n id: `${m.provider}/${m.id}`,\n name: m.name || m.id,\n provider: m.provider,\n }));\n };\n\n // ========== Helper Functions ==========\n\n // Helper to get sessionKey from Telegram context\n const getSessionKeyFromCtx = (ctx: Context): string => {\n const chatId = String(ctx.chat?.id);\n const senderId = String(ctx.from?.id);\n const isGroup = ctx.chat?.type === 'group' || ctx.chat?.type === 'supergroup';\n const threadId =\n (ctx.message as { message_thread_id?: number })?.message_thread_id ??\n (ctx.callbackQuery?.message as { message_thread_id?: number } | undefined)?.message_thread_id;\n\n const accountId = accountManager.resolveAccountIdFromContext(ctx);\n\n return generateSessionKey({\n source: 'telegram',\n chatId,\n senderId,\n isGroup,\n threadId: threadId ? String(threadId) : undefined,\n accountId,\n });\n };\n\n // ========== Command Handlers ==========\n\n /**\n * /start - Show welcome message\n * Note: Other commands (/new, /usage, /models, etc.) are handled by the unified command system\n */\n const handleStart = async (ctx: Context): Promise<void> => {\n try {\n const providers = getAvailableProviders();\n const hasProviders = providers.length > 0;\n\n await ctx.reply(\n '👋 *Welcome to xopc!*\\n\\n' +\n 'I am your AI assistant. Here are the available commands:\\n\\n' +\n '🤖 *Model Selection*\\n' +\n '/models - Select a model to use\\n' +\n '/switch \\u003cmodel-id\\u003e - Switch to a specific model\\n\\n' +\n '📊 *Session Management*\\n' +\n '/new - Start a new session (archive current)\\n' +\n '/list - List all your sessions\\n' +\n '/usage - View token usage statistics\\n' +\n '/cleanup - Clean up old sessions\\n\\n' +\n '🔊 *Voice (TTS)*\\n' +\n '/tts - Show TTS settings\\n' +\n '/tts on|off - Enable/disable TTS\\n' +\n '/tts always|inbound|tagged|never - Set trigger mode\\n\\n' +\n '🛠️ *Skills*\\n' +\n '/skills reload - Reload all skills from disk\\n\\n' +\n '💡 *Tip*: Just send a message to start chatting!' +\n (hasProviders ? '' : '\\n\\n⚠️ *Note*: No LLM providers configured. Please set up API keys in your config.'),\n { parse_mode: 'Markdown' }\n );\n } catch (err) {\n log.error({ err }, 'Failed to handle start command');\n await ctx.reply('❌ Failed to show welcome message.');\n }\n };\n\n /**\n * /models - Show model selector with inline keyboard\n * Note: This provides a UI enhancement over the basic /models command\n */\n const handleModels = async (ctx: Context): Promise<void> => {\n try {\n const providers = getAvailableProviders();\n\n if (providers.length === 0) {\n await ctx.reply('❌ No providers available. Please check your configuration.');\n return;\n }\n\n await ctx.reply('🤖 Select a provider:', {\n reply_markup: TelegramInlineKeyboards.providerSelector(providers),\n });\n } catch (err) {\n log.error({ err }, 'Failed to show provider selector');\n await ctx.reply('❌ Failed to load providers. Please try again.');\n }\n };\n\n /**\n * /cleanup - Show cleanup confirmation dialog\n */\n const handleCleanup = async (ctx: Context): Promise<void> => {\n try {\n await ctx.reply(\n '🧹 *Session Cleanup*\\n\\n' +\n 'This will archive sessions that have been inactive for more than 30 days.\\n\\n' +\n 'Archived sessions can be restored later.',\n { parse_mode: 'Markdown', reply_markup: TelegramInlineKeyboards.cleanupConfirm() }\n );\n } catch (err) {\n log.error({ err }, 'Failed to show cleanup dialog');\n await ctx.reply('❌ Failed to initiate cleanup.');\n }\n };\n\n // ========== Callback Handlers (Inline Keyboard) ==========\n\n const handleProviderSelect = async (ctx: Context, providerId: string): Promise<void> => {\n try {\n const sessionKey = getSessionKeyFromCtx(ctx);\n const modelConfig = config.agents?.defaults?.model;\n const defaultModel = typeof modelConfig === 'string' ? modelConfig : modelConfig?.primary || getDefaultModelSync(config);\n const currentModel = getSessionModel(sessionKey) || defaultModel;\n\n const models = getModelsForProvider(providerId);\n \n if (models.length === 0) {\n await ctx.editMessageText('❌ No models available for this provider.');\n await ctx.answerCallbackQuery();\n return;\n }\n\n const keyboard = TelegramInlineKeyboards.modelSelector(models, currentModel);\n const providerName = getProviderDisplayName(providerId);\n \n await ctx.editMessageText(`🤖 Select a model from *${providerName}*:`, { \n reply_markup: keyboard,\n parse_mode: 'Markdown',\n });\n await ctx.answerCallbackQuery();\n } catch (err) {\n log.error({ err, providerId }, 'Failed to show provider models');\n await ctx.answerCallbackQuery('Failed to load models');\n }\n };\n\n const handleModelSelect = async (ctx: Context, modelId: string): Promise<void> => {\n try {\n const sessionKey = getSessionKeyFromCtx(ctx);\n await Promise.resolve(setSessionModel(sessionKey, modelId));\n\n const modelName = modelId.split('/').pop() || modelId;\n await ctx.editMessageText(\n `✅ Model switched to *${modelName}*\\n\\nThis model will be used for your next message.`,\n { parse_mode: 'Markdown' }\n );\n await ctx.answerCallbackQuery(`Switched to ${modelName}`);\n\n log.info({ sessionKey, modelId }, 'Model switched via Telegram');\n } catch (err) {\n log.error({ err }, 'Failed to handle model selection');\n await ctx.answerCallbackQuery('Failed to switch model');\n }\n };\n\n const handleShowProviders = async (ctx: Context): Promise<void> => {\n try {\n const providers = getAvailableProviders();\n\n await ctx.editMessageText('🤖 Select a provider:', {\n reply_markup: TelegramInlineKeyboards.providerSelector(providers),\n });\n await ctx.answerCallbackQuery();\n } catch (err) {\n log.error({ err }, 'Failed to show providers again');\n await ctx.answerCallbackQuery('Failed to go back');\n }\n };\n\n const handleCleanupConfirm = async (ctx: Context): Promise<void> => {\n try {\n await ctx.editMessageText('🧹 Cleaning up old sessions...');\n \n const chatId = String(ctx.chat?.id);\n\n await bus.publishInbound({\n channel: 'system',\n sender_id: 'telegram:cleanup',\n chat_id: chatId,\n content: '/cleanup --days 30 --force',\n });\n\n await ctx.editMessageText('✅ Cleanup initiated. Old sessions will be archived.');\n await ctx.answerCallbackQuery('Cleanup started');\n } catch (err) {\n log.error({ err }, 'Failed to execute cleanup');\n await ctx.editMessageText('❌ Cleanup failed. Please try again later.');\n await ctx.answerCallbackQuery('Cleanup failed');\n }\n };\n\n const handleCancel = async (ctx: Context): Promise<void> => {\n await ctx.editMessageText('Cancelled.');\n await ctx.answerCallbackQuery();\n };\n\n return {\n // Command handlers\n handleStart,\n handleModels,\n handleCleanup,\n // Callback handlers\n handleProviderSelect,\n handleModelSelect,\n handleShowProviders,\n handleCleanupConfirm,\n handleCancel,\n // Helpers\n getAvailableProviders,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"command-handler.js","names":[],"sources":["../../../../extensions/telegram/src/command-handler.ts"],"sourcesContent":["/**\n * Telegram Command Handler\n * \n * Handles Telegram-specific UI interactions (inline keyboards, callbacks).\n * Core commands (/new, /usage, /models, etc.) are handled by the unified command system.\n */\n\nimport type { Context } from 'grammy';\n\nimport type { TelegramAccountManager } from './account-manager.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\nimport type { Config } from '@xopcai/xopc/config/index.js';\nimport { isProviderConfiguredSync } from '@xopcai/xopc/providers/index.js';\nimport { TelegramInlineKeyboards, type ProviderInfo } from './inline-keyboards.js';\nimport { getProviderDisplayName, getModelsByProvider, getDefaultModelSync, getAllProviders } from '@xopcai/xopc/providers/index.js';\nimport { generateSessionKey } from '@xopcai/xopc/chat-commands/session-key.js';\n\nconst log = createLogger('TelegramCommandHandler');\n\nexport interface TelegramCommandHandlerDeps {\n bus: any;\n config: Config;\n accountManager: TelegramAccountManager;\n getSessionModel: (sessionKey: string) => string | undefined;\n setSessionModel: (sessionKey: string, modelId: string) => void | Promise<void>;\n // Optional callbacks for inline keyboard handling\n showProviderModels?: (ctx: Context, providerId: string) => Promise<void>;\n showProvidersAgain?: (ctx: Context) => Promise<void>;\n handleCleanupConfirm?: (ctx: Context) => Promise<void>;\n}\n\nexport function createTelegramCommandHandler(deps: TelegramCommandHandlerDeps) {\n const { config, accountManager, getSessionModel, setSessionModel, bus } = deps;\n\n // ========== Helper Functions ==========\n\n const getAvailableProviders = (): ProviderInfo[] => {\n const allProviders = getAllProviders();\n const available: ProviderInfo[] = [];\n\n for (const providerId of allProviders) {\n if (isProviderConfiguredSync(providerId)) {\n available.push({ id: providerId, name: getProviderDisplayName(providerId) });\n }\n }\n\n if (available.length === 0) {\n available.push({ id: 'minimax', name: 'MiniMax' });\n }\n\n return available;\n };\n\n const getModelsForProvider = (providerId: string) => {\n const models = getModelsByProvider(providerId);\n \n if (models.length === 0) {\n return [{ id: `${providerId}/default`, name: 'default', provider: providerId }];\n }\n \n return models.map(m => ({\n id: `${m.provider}/${m.id}`,\n name: m.name || m.id,\n provider: m.provider,\n }));\n };\n\n // ========== Helper Functions ==========\n\n // Helper to get sessionKey from Telegram context\n const getSessionKeyFromCtx = (ctx: Context): string => {\n const chatId = String(ctx.chat?.id);\n const senderId = String(ctx.from?.id);\n const isGroup = ctx.chat?.type === 'group' || ctx.chat?.type === 'supergroup';\n const threadId =\n (ctx.message as { message_thread_id?: number })?.message_thread_id ??\n (ctx.callbackQuery?.message as { message_thread_id?: number } | undefined)?.message_thread_id;\n\n const accountId = accountManager.resolveAccountIdFromContext(ctx);\n\n return generateSessionKey({\n source: 'telegram',\n chatId,\n senderId,\n isGroup,\n threadId: threadId ? String(threadId) : undefined,\n accountId,\n });\n };\n\n // ========== Command Handlers ==========\n\n /**\n * /start - Show welcome message\n * Note: Other commands (/new, /usage, /models, etc.) are handled by the unified command system\n */\n const handleStart = async (ctx: Context): Promise<void> => {\n try {\n const providers = getAvailableProviders();\n const hasProviders = providers.length > 0;\n\n await ctx.reply(\n '👋 *Welcome to xopc!*\\n\\n' +\n 'I am your AI assistant. Here are the available commands:\\n\\n' +\n '🤖 *Model Selection*\\n' +\n '/models - List models (name + `provider/model` ref for /switch)\\n' +\n '/switch \\u003cprovider/model-id\\u003e - Same ref as shown in /models\\n\\n' +\n '📊 *Session Management*\\n' +\n '/new - Start a new session (archive current)\\n' +\n '/list - List all your sessions\\n' +\n '/usage - View token usage statistics\\n' +\n '/cleanup - Clean up old sessions\\n\\n' +\n '🔊 *Voice (TTS)*\\n' +\n '/tts - Show TTS settings\\n' +\n '/tts on|off - Enable/disable TTS\\n' +\n '/tts always|inbound|tagged|never - Set trigger mode\\n\\n' +\n '🛠️ *Skills*\\n' +\n '/skills reload - Reload all skills from disk\\n\\n' +\n '💡 *Tip*: Just send a message to start chatting!' +\n (hasProviders ? '' : '\\n\\n⚠️ *Note*: No LLM providers configured. Please set up API keys in your config.'),\n { parse_mode: 'Markdown' }\n );\n } catch (err) {\n log.error({ err }, 'Failed to handle start command');\n await ctx.reply('❌ Failed to show welcome message.');\n }\n };\n\n /**\n * /models - Show model selector with inline keyboard\n * Note: This provides a UI enhancement over the basic /models command\n */\n const handleModels = async (ctx: Context): Promise<void> => {\n try {\n const providers = getAvailableProviders();\n\n if (providers.length === 0) {\n await ctx.reply('❌ No providers available. Please check your configuration.');\n return;\n }\n\n await ctx.reply('🤖 Select a provider:', {\n reply_markup: TelegramInlineKeyboards.providerSelector(providers),\n });\n } catch (err) {\n log.error({ err }, 'Failed to show provider selector');\n await ctx.reply('❌ Failed to load providers. Please try again.');\n }\n };\n\n /**\n * /cleanup - Show cleanup confirmation dialog\n */\n const handleCleanup = async (ctx: Context): Promise<void> => {\n try {\n await ctx.reply(\n '🧹 *Session Cleanup*\\n\\n' +\n 'This will archive sessions that have been inactive for more than 30 days.\\n\\n' +\n 'Archived sessions can be restored later.',\n { parse_mode: 'Markdown', reply_markup: TelegramInlineKeyboards.cleanupConfirm() }\n );\n } catch (err) {\n log.error({ err }, 'Failed to show cleanup dialog');\n await ctx.reply('❌ Failed to initiate cleanup.');\n }\n };\n\n // ========== Callback Handlers (Inline Keyboard) ==========\n\n const handleProviderSelect = async (ctx: Context, providerId: string): Promise<void> => {\n try {\n const sessionKey = getSessionKeyFromCtx(ctx);\n const modelConfig = config.agents?.defaults?.model;\n const defaultModel = typeof modelConfig === 'string' ? modelConfig : modelConfig?.primary || getDefaultModelSync(config);\n const currentModel = getSessionModel(sessionKey) || defaultModel;\n\n const models = getModelsForProvider(providerId);\n \n if (models.length === 0) {\n await ctx.editMessageText('❌ No models available for this provider.');\n await ctx.answerCallbackQuery();\n return;\n }\n\n const keyboard = TelegramInlineKeyboards.modelSelector(models, currentModel);\n const providerName = getProviderDisplayName(providerId);\n \n await ctx.editMessageText(`🤖 Select a model from *${providerName}*:`, { \n reply_markup: keyboard,\n parse_mode: 'Markdown',\n });\n await ctx.answerCallbackQuery();\n } catch (err) {\n log.error({ err, providerId }, 'Failed to show provider models');\n await ctx.answerCallbackQuery('Failed to load models');\n }\n };\n\n const handleModelSelect = async (ctx: Context, modelId: string): Promise<void> => {\n try {\n const sessionKey = getSessionKeyFromCtx(ctx);\n await Promise.resolve(setSessionModel(sessionKey, modelId));\n\n const modelName = modelId.split('/').pop() || modelId;\n await ctx.editMessageText(\n `✅ Model switched to *${modelName}*\\n\\nThis model will be used for your next message.`,\n { parse_mode: 'Markdown' }\n );\n await ctx.answerCallbackQuery(`Switched to ${modelName}`);\n\n log.info({ sessionKey, modelId }, 'Model switched via Telegram');\n } catch (err) {\n log.error({ err }, 'Failed to handle model selection');\n await ctx.answerCallbackQuery('Failed to switch model');\n }\n };\n\n const handleShowProviders = async (ctx: Context): Promise<void> => {\n try {\n const providers = getAvailableProviders();\n\n await ctx.editMessageText('🤖 Select a provider:', {\n reply_markup: TelegramInlineKeyboards.providerSelector(providers),\n });\n await ctx.answerCallbackQuery();\n } catch (err) {\n log.error({ err }, 'Failed to show providers again');\n await ctx.answerCallbackQuery('Failed to go back');\n }\n };\n\n const handleCleanupConfirm = async (ctx: Context): Promise<void> => {\n try {\n await ctx.editMessageText('🧹 Cleaning up old sessions...');\n \n const chatId = String(ctx.chat?.id);\n\n await bus.publishInbound({\n channel: 'system',\n sender_id: 'telegram:cleanup',\n chat_id: chatId,\n content: '/cleanup --days 30 --force',\n });\n\n await ctx.editMessageText('✅ Cleanup initiated. Old sessions will be archived.');\n await ctx.answerCallbackQuery('Cleanup started');\n } catch (err) {\n log.error({ err }, 'Failed to execute cleanup');\n await ctx.editMessageText('❌ Cleanup failed. Please try again later.');\n await ctx.answerCallbackQuery('Cleanup failed');\n }\n };\n\n const handleCancel = async (ctx: Context): Promise<void> => {\n await ctx.editMessageText('Cancelled.');\n await ctx.answerCallbackQuery();\n };\n\n return {\n // Command handlers\n handleStart,\n handleModels,\n handleCleanup,\n // Callback handlers\n handleProviderSelect,\n handleModelSelect,\n handleShowProviders,\n handleCleanupConfirm,\n handleCancel,\n // Helpers\n getAvailableProviders,\n };\n}\n"],"mappings":"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"}
@@ -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.47",
5
+ "version": "0.0.49",
6
6
  "kind": "channel",
7
7
  "main": "src/index.ts",
8
8
  "channels": ["telegram"],
@@ -135,7 +135,7 @@ function resolveWeixinAccount(cfg, accountId) {
135
135
  enabled: channelEnabled && accountCfg.enabled !== false,
136
136
  configured: Boolean(token),
137
137
  name: accountCfg.name?.trim() || void 0,
138
- dmPolicy: accountCfg.dmPolicy ?? "pairing",
138
+ dmPolicy: accountCfg.dmPolicy ?? "open",
139
139
  allowFrom: accountCfg.allowFrom ?? [],
140
140
  streamMode: accountCfg.streamMode,
141
141
  routeTag,
@@ -1 +1 @@
1
- {"version":3,"file":"accounts.js","names":["fs"],"sources":["../../../../../extensions/weixin/src/auth/accounts.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\n\nimport { clearContextTokensForAccount } from '../messaging/inbound.js';\nimport { resolveWeixinRootDir } from '../storage/state-dir.js';\nimport { logger } from '../util/logger.js';\n\nimport { normalizeWeixinAccountId } from './weixin-account-id.js';\n\nexport { normalizeWeixinAccountId };\n\nexport const DEFAULT_BASE_URL = 'https://ilinkai.weixin.qq.com';\nexport const CDN_BASE_URL = 'https://novac2c.cdn.weixin.qq.com/c2c';\n\nfunction resolveWeixinStateDir(): string {\n return resolveWeixinRootDir();\n}\n\nfunction resolveAccountIndexPath(): string {\n return path.join(resolveWeixinStateDir(), 'accounts.json');\n}\n\nexport function listIndexedWeixinAccountIds(): string[] {\n const filePath = resolveAccountIndexPath();\n try {\n if (!fs.existsSync(filePath)) return [];\n const raw = fs.readFileSync(filePath, 'utf-8');\n const parsed = JSON.parse(raw) as unknown;\n if (!Array.isArray(parsed)) return [];\n return parsed.filter((id): id is string => typeof id === 'string' && id.trim() !== '');\n } catch {\n return [];\n }\n}\n\nexport function registerWeixinAccountId(accountId: string): void {\n const dir = resolveWeixinStateDir();\n fs.mkdirSync(dir, { recursive: true });\n\n const existing = listIndexedWeixinAccountIds();\n if (existing.includes(accountId)) return;\n\n const updated = [...existing, accountId];\n fs.writeFileSync(resolveAccountIndexPath(), JSON.stringify(updated, null, 2), 'utf-8');\n}\n\nexport function unregisterWeixinAccountId(accountId: string): void {\n const existing = listIndexedWeixinAccountIds();\n const updated = existing.filter((id) => id !== accountId);\n if (updated.length !== existing.length) {\n fs.writeFileSync(resolveAccountIndexPath(), JSON.stringify(updated, null, 2), 'utf-8');\n }\n}\n\nexport function clearStaleAccountsForUserId(\n currentAccountId: string,\n userId: string,\n onClearContextTokens?: (accountId: string) => void,\n): void {\n if (!userId) return;\n const allIds = listIndexedWeixinAccountIds();\n for (const id of allIds) {\n if (id === currentAccountId) continue;\n const data = loadWeixinAccount(id);\n if (data?.userId?.trim() === userId) {\n logger.info(`clearStaleAccountsForUserId: removing stale account=${id} (same userId=${userId})`);\n onClearContextTokens?.(id);\n clearWeixinAccount(id);\n unregisterWeixinAccountId(id);\n }\n }\n}\n\nexport type WeixinAccountData = {\n token?: string;\n savedAt?: string;\n baseUrl?: string;\n userId?: string;\n};\n\nfunction resolveAccountsDir(): string {\n return path.join(resolveWeixinStateDir(), 'accounts');\n}\n\nfunction resolveAccountPath(accountId: string): string {\n return path.join(resolveAccountsDir(), `${accountId}.json`);\n}\n\nfunction readAccountFile(filePath: string): WeixinAccountData | null {\n try {\n if (fs.existsSync(filePath)) {\n return JSON.parse(fs.readFileSync(filePath, 'utf-8')) as WeixinAccountData;\n }\n } catch {\n // ignore\n }\n return null;\n}\n\nexport function loadWeixinAccount(accountId: string): WeixinAccountData | null {\n return readAccountFile(resolveAccountPath(accountId));\n}\n\nexport function saveWeixinAccount(\n accountId: string,\n update: { token?: string; baseUrl?: string; userId?: string },\n): void {\n const dir = resolveAccountsDir();\n fs.mkdirSync(dir, { recursive: true });\n\n const existing = loadWeixinAccount(accountId) ?? {};\n\n const token = update.token?.trim() || existing.token;\n const baseUrl = update.baseUrl?.trim() || existing.baseUrl;\n const userId =\n update.userId !== undefined ? update.userId.trim() || undefined : existing.userId?.trim() || undefined;\n\n const data: WeixinAccountData = {\n ...(token ? { token, savedAt: new Date().toISOString() } : {}),\n ...(baseUrl ? { baseUrl } : {}),\n ...(userId ? { userId } : {}),\n };\n\n const filePath = resolveAccountPath(accountId);\n fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');\n try {\n fs.chmodSync(filePath, 0o600);\n } catch {\n // best-effort\n }\n}\n\nexport function clearWeixinAccount(accountId: string): void {\n const dir = resolveAccountsDir();\n const accountFiles = [\n `${accountId}.json`,\n `${accountId}.sync.json`,\n `${accountId}.context-tokens.json`,\n ];\n for (const file of accountFiles) {\n try {\n fs.unlinkSync(path.join(dir, file));\n } catch {\n // ignore\n }\n }\n try {\n fs.unlinkSync(resolveFrameworkAllowFromPath(accountId));\n } catch {\n // ignore\n }\n}\n\nexport type ResolvedWeixinAccount = {\n accountId: string;\n baseUrl: string;\n cdnBaseUrl: string;\n token?: string;\n enabled: boolean;\n configured: boolean;\n name?: string;\n dmPolicy: 'pairing' | 'allowlist' | 'open' | 'disabled';\n allowFrom: string[];\n streamMode?: 'off' | 'partial' | 'block';\n routeTag?: string;\n debug?: boolean;\n};\n\ntype WeixinAccountCfg = {\n name?: string;\n enabled?: boolean;\n cdnBaseUrl?: string;\n routeTag?: number | string;\n dmPolicy?: ResolvedWeixinAccount['dmPolicy'];\n allowFrom?: string[];\n streamMode?: ResolvedWeixinAccount['streamMode'];\n debug?: boolean;\n accounts?: Record<string, WeixinAccountCfg>;\n};\n\nfunction weixinSection(cfg: Config): (WeixinAccountCfg & { accounts?: Record<string, WeixinAccountCfg> }) | undefined {\n return cfg.channels?.weixin as\n | (WeixinAccountCfg & { accounts?: Record<string, WeixinAccountCfg> })\n | undefined;\n}\n\nexport function listWeixinAccountIds(cfg: Config): string[] {\n const section = weixinSection(cfg);\n const fromConfig = section?.accounts ? Object.keys(section.accounts) : [];\n // Only merge disk index when the channel is explicitly enabled. Otherwise removed / disabled\n // config still leaves token files + accounts.json on disk and monitors would keep running.\n if (!section || section.enabled !== true) {\n return fromConfig;\n }\n const indexed = listIndexedWeixinAccountIds();\n return [...new Set([...indexed, ...fromConfig])];\n}\n\nexport function resolveWeixinAccount(cfg: Config, accountId?: string | null): ResolvedWeixinAccount {\n const raw = accountId?.trim();\n if (!raw) {\n throw new Error('weixin: accountId is required');\n }\n const id = normalizeWeixinAccountId(raw);\n const section = weixinSection(cfg);\n const fromAccount = section?.accounts?.[id];\n const { accounts: _omit, ...sectionRest } = section ?? {};\n void _omit;\n const accountCfg: WeixinAccountCfg = {\n ...sectionRest,\n ...(fromAccount ?? {}),\n };\n\n const accountData = loadWeixinAccount(id);\n const token = accountData?.token?.trim() || undefined;\n const stateBaseUrl = accountData?.baseUrl?.trim() || '';\n\n const routeTagRaw = accountCfg.routeTag;\n const routeTag =\n typeof routeTagRaw === 'number'\n ? String(routeTagRaw)\n : typeof routeTagRaw === 'string' && routeTagRaw.trim()\n ? routeTagRaw.trim()\n : undefined;\n\n const channelEnabled = section?.enabled === true;\n\n return {\n accountId: id,\n baseUrl: stateBaseUrl || DEFAULT_BASE_URL,\n cdnBaseUrl: accountCfg.cdnBaseUrl?.trim() || CDN_BASE_URL,\n token,\n enabled: channelEnabled && accountCfg.enabled !== false,\n configured: Boolean(token),\n name: accountCfg.name?.trim() || undefined,\n dmPolicy: accountCfg.dmPolicy ?? 'pairing',\n allowFrom: accountCfg.allowFrom ?? [],\n streamMode: accountCfg.streamMode,\n routeTag,\n debug: accountCfg.debug ?? false,\n };\n}\n\nexport function resolveFrameworkAllowFromPath(accountId: string): string {\n const base = 'xopc-weixin'.replace(/[\\\\/:*?\"<>|]/g, '_');\n const safeAccount = accountId.trim().toLowerCase().replace(/[\\\\/:*?\"<>|]/g, '_').replace(/\\.\\./g, '_');\n const { join } = path;\n const credRoot = process.env.XOPC_CREDENTIALS_DIR?.trim()\n ? process.env.XOPC_CREDENTIALS_DIR!\n : join(resolveWeixinRootDir(), 'credentials');\n return join(credRoot, `${base}-${safeAccount}-allowFrom.json`);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"accounts.js","names":["fs"],"sources":["../../../../../extensions/weixin/src/auth/accounts.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\n\nimport { clearContextTokensForAccount } from '../messaging/inbound.js';\nimport { resolveWeixinRootDir } from '../storage/state-dir.js';\nimport { logger } from '../util/logger.js';\n\nimport { normalizeWeixinAccountId } from './weixin-account-id.js';\n\nexport { normalizeWeixinAccountId };\n\nexport const DEFAULT_BASE_URL = 'https://ilinkai.weixin.qq.com';\nexport const CDN_BASE_URL = 'https://novac2c.cdn.weixin.qq.com/c2c';\n\nfunction resolveWeixinStateDir(): string {\n return resolveWeixinRootDir();\n}\n\nfunction resolveAccountIndexPath(): string {\n return path.join(resolveWeixinStateDir(), 'accounts.json');\n}\n\nexport function listIndexedWeixinAccountIds(): string[] {\n const filePath = resolveAccountIndexPath();\n try {\n if (!fs.existsSync(filePath)) return [];\n const raw = fs.readFileSync(filePath, 'utf-8');\n const parsed = JSON.parse(raw) as unknown;\n if (!Array.isArray(parsed)) return [];\n return parsed.filter((id): id is string => typeof id === 'string' && id.trim() !== '');\n } catch {\n return [];\n }\n}\n\nexport function registerWeixinAccountId(accountId: string): void {\n const dir = resolveWeixinStateDir();\n fs.mkdirSync(dir, { recursive: true });\n\n const existing = listIndexedWeixinAccountIds();\n if (existing.includes(accountId)) return;\n\n const updated = [...existing, accountId];\n fs.writeFileSync(resolveAccountIndexPath(), JSON.stringify(updated, null, 2), 'utf-8');\n}\n\nexport function unregisterWeixinAccountId(accountId: string): void {\n const existing = listIndexedWeixinAccountIds();\n const updated = existing.filter((id) => id !== accountId);\n if (updated.length !== existing.length) {\n fs.writeFileSync(resolveAccountIndexPath(), JSON.stringify(updated, null, 2), 'utf-8');\n }\n}\n\nexport function clearStaleAccountsForUserId(\n currentAccountId: string,\n userId: string,\n onClearContextTokens?: (accountId: string) => void,\n): void {\n if (!userId) return;\n const allIds = listIndexedWeixinAccountIds();\n for (const id of allIds) {\n if (id === currentAccountId) continue;\n const data = loadWeixinAccount(id);\n if (data?.userId?.trim() === userId) {\n logger.info(`clearStaleAccountsForUserId: removing stale account=${id} (same userId=${userId})`);\n onClearContextTokens?.(id);\n clearWeixinAccount(id);\n unregisterWeixinAccountId(id);\n }\n }\n}\n\nexport type WeixinAccountData = {\n token?: string;\n savedAt?: string;\n baseUrl?: string;\n userId?: string;\n};\n\nfunction resolveAccountsDir(): string {\n return path.join(resolveWeixinStateDir(), 'accounts');\n}\n\nfunction resolveAccountPath(accountId: string): string {\n return path.join(resolveAccountsDir(), `${accountId}.json`);\n}\n\nfunction readAccountFile(filePath: string): WeixinAccountData | null {\n try {\n if (fs.existsSync(filePath)) {\n return JSON.parse(fs.readFileSync(filePath, 'utf-8')) as WeixinAccountData;\n }\n } catch {\n // ignore\n }\n return null;\n}\n\nexport function loadWeixinAccount(accountId: string): WeixinAccountData | null {\n return readAccountFile(resolveAccountPath(accountId));\n}\n\nexport function saveWeixinAccount(\n accountId: string,\n update: { token?: string; baseUrl?: string; userId?: string },\n): void {\n const dir = resolveAccountsDir();\n fs.mkdirSync(dir, { recursive: true });\n\n const existing = loadWeixinAccount(accountId) ?? {};\n\n const token = update.token?.trim() || existing.token;\n const baseUrl = update.baseUrl?.trim() || existing.baseUrl;\n const userId =\n update.userId !== undefined ? update.userId.trim() || undefined : existing.userId?.trim() || undefined;\n\n const data: WeixinAccountData = {\n ...(token ? { token, savedAt: new Date().toISOString() } : {}),\n ...(baseUrl ? { baseUrl } : {}),\n ...(userId ? { userId } : {}),\n };\n\n const filePath = resolveAccountPath(accountId);\n fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');\n try {\n fs.chmodSync(filePath, 0o600);\n } catch {\n // best-effort\n }\n}\n\nexport function clearWeixinAccount(accountId: string): void {\n const dir = resolveAccountsDir();\n const accountFiles = [\n `${accountId}.json`,\n `${accountId}.sync.json`,\n `${accountId}.context-tokens.json`,\n ];\n for (const file of accountFiles) {\n try {\n fs.unlinkSync(path.join(dir, file));\n } catch {\n // ignore\n }\n }\n try {\n fs.unlinkSync(resolveFrameworkAllowFromPath(accountId));\n } catch {\n // ignore\n }\n}\n\nexport type ResolvedWeixinAccount = {\n accountId: string;\n baseUrl: string;\n cdnBaseUrl: string;\n token?: string;\n enabled: boolean;\n configured: boolean;\n name?: string;\n dmPolicy: 'pairing' | 'allowlist' | 'open' | 'disabled';\n allowFrom: string[];\n streamMode?: 'off' | 'partial' | 'block';\n routeTag?: string;\n debug?: boolean;\n};\n\ntype WeixinAccountCfg = {\n name?: string;\n enabled?: boolean;\n cdnBaseUrl?: string;\n routeTag?: number | string;\n dmPolicy?: ResolvedWeixinAccount['dmPolicy'];\n allowFrom?: string[];\n streamMode?: ResolvedWeixinAccount['streamMode'];\n debug?: boolean;\n accounts?: Record<string, WeixinAccountCfg>;\n};\n\nfunction weixinSection(cfg: Config): (WeixinAccountCfg & { accounts?: Record<string, WeixinAccountCfg> }) | undefined {\n return cfg.channels?.weixin as\n | (WeixinAccountCfg & { accounts?: Record<string, WeixinAccountCfg> })\n | undefined;\n}\n\nexport function listWeixinAccountIds(cfg: Config): string[] {\n const section = weixinSection(cfg);\n const fromConfig = section?.accounts ? Object.keys(section.accounts) : [];\n // Only merge disk index when the channel is explicitly enabled. Otherwise removed / disabled\n // config still leaves token files + accounts.json on disk and monitors would keep running.\n if (!section || section.enabled !== true) {\n return fromConfig;\n }\n const indexed = listIndexedWeixinAccountIds();\n return [...new Set([...indexed, ...fromConfig])];\n}\n\nexport function resolveWeixinAccount(cfg: Config, accountId?: string | null): ResolvedWeixinAccount {\n const raw = accountId?.trim();\n if (!raw) {\n throw new Error('weixin: accountId is required');\n }\n const id = normalizeWeixinAccountId(raw);\n const section = weixinSection(cfg);\n const fromAccount = section?.accounts?.[id];\n const { accounts: _omit, ...sectionRest } = section ?? {};\n void _omit;\n const accountCfg: WeixinAccountCfg = {\n ...sectionRest,\n ...(fromAccount ?? {}),\n };\n\n const accountData = loadWeixinAccount(id);\n const token = accountData?.token?.trim() || undefined;\n const stateBaseUrl = accountData?.baseUrl?.trim() || '';\n\n const routeTagRaw = accountCfg.routeTag;\n const routeTag =\n typeof routeTagRaw === 'number'\n ? String(routeTagRaw)\n : typeof routeTagRaw === 'string' && routeTagRaw.trim()\n ? routeTagRaw.trim()\n : undefined;\n\n const channelEnabled = section?.enabled === true;\n\n return {\n accountId: id,\n baseUrl: stateBaseUrl || DEFAULT_BASE_URL,\n cdnBaseUrl: accountCfg.cdnBaseUrl?.trim() || CDN_BASE_URL,\n token,\n enabled: channelEnabled && accountCfg.enabled !== false,\n configured: Boolean(token),\n name: accountCfg.name?.trim() || undefined,\n dmPolicy: accountCfg.dmPolicy ?? 'open',\n allowFrom: accountCfg.allowFrom ?? [],\n streamMode: accountCfg.streamMode,\n routeTag,\n debug: accountCfg.debug ?? false,\n };\n}\n\nexport function resolveFrameworkAllowFromPath(accountId: string): string {\n const base = 'xopc-weixin'.replace(/[\\\\/:*?\"<>|]/g, '_');\n const safeAccount = accountId.trim().toLowerCase().replace(/[\\\\/:*?\"<>|]/g, '_').replace(/\\.\\./g, '_');\n const { join } = path;\n const credRoot = process.env.XOPC_CREDENTIALS_DIR?.trim()\n ? process.env.XOPC_CREDENTIALS_DIR!\n : join(resolveWeixinRootDir(), 'credentials');\n return join(credRoot, `${base}-${safeAccount}-allowFrom.json`);\n}\n"],"mappings":";;;;;;AAaA,MAAa,mBAAmB;AAChC,MAAa,eAAe;AAE5B,SAAS,wBAAgC;AACvC,QAAO,sBAAsB;;AAG/B,SAAS,0BAAkC;AACzC,QAAO,KAAK,KAAK,uBAAuB,EAAE,gBAAgB;;AAG5D,SAAgB,8BAAwC;CACtD,MAAM,WAAW,yBAAyB;AAC1C,KAAI;AACF,MAAI,CAACA,OAAG,WAAW,SAAS,CAAE,QAAO,EAAE;EACvC,MAAM,MAAMA,OAAG,aAAa,UAAU,QAAQ;EAC9C,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MAAI,CAAC,MAAM,QAAQ,OAAO,CAAE,QAAO,EAAE;AACrC,SAAO,OAAO,QAAQ,OAAqB,OAAO,OAAO,YAAY,GAAG,MAAM,KAAK,GAAG;SAChF;AACN,SAAO,EAAE;;;AAIb,SAAgB,wBAAwB,WAAyB;CAC/D,MAAM,MAAM,uBAAuB;AACnC,QAAG,UAAU,KAAK,EAAE,WAAW,MAAM,CAAC;CAEtC,MAAM,WAAW,6BAA6B;AAC9C,KAAI,SAAS,SAAS,UAAU,CAAE;CAElC,MAAM,UAAU,CAAC,GAAG,UAAU,UAAU;AACxC,QAAG,cAAc,yBAAyB,EAAE,KAAK,UAAU,SAAS,MAAM,EAAE,EAAE,QAAQ;;AAGxF,SAAgB,0BAA0B,WAAyB;CACjE,MAAM,WAAW,6BAA6B;CAC9C,MAAM,UAAU,SAAS,QAAQ,OAAO,OAAO,UAAU;AACzD,KAAI,QAAQ,WAAW,SAAS,OAC9B,QAAG,cAAc,yBAAyB,EAAE,KAAK,UAAU,SAAS,MAAM,EAAE,EAAE,QAAQ;;AAI1F,SAAgB,4BACd,kBACA,QACA,sBACM;AACN,KAAI,CAAC,OAAQ;CACb,MAAM,SAAS,6BAA6B;AAC5C,MAAK,MAAM,MAAM,QAAQ;AACvB,MAAI,OAAO,iBAAkB;AAE7B,MADa,kBAAkB,GACvB,EAAE,QAAQ,MAAM,KAAK,QAAQ;AACnC,UAAO,KAAK,uDAAuD,GAAG,gBAAgB,OAAO,GAAG;AAChG,0BAAuB,GAAG;AAC1B,sBAAmB,GAAG;AACtB,6BAA0B,GAAG;;;;AAYnC,SAAS,qBAA6B;AACpC,QAAO,KAAK,KAAK,uBAAuB,EAAE,WAAW;;AAGvD,SAAS,mBAAmB,WAA2B;AACrD,QAAO,KAAK,KAAK,oBAAoB,EAAE,GAAG,UAAU,OAAO;;AAG7D,SAAS,gBAAgB,UAA4C;AACnE,KAAI;AACF,MAAIA,OAAG,WAAW,SAAS,CACzB,QAAO,KAAK,MAAMA,OAAG,aAAa,UAAU,QAAQ,CAAC;SAEjD;AAGR,QAAO;;AAGT,SAAgB,kBAAkB,WAA6C;AAC7E,QAAO,gBAAgB,mBAAmB,UAAU,CAAC;;AAGvD,SAAgB,kBACd,WACA,QACM;CACN,MAAM,MAAM,oBAAoB;AAChC,QAAG,UAAU,KAAK,EAAE,WAAW,MAAM,CAAC;CAEtC,MAAM,WAAW,kBAAkB,UAAU,IAAI,EAAE;CAEnD,MAAM,QAAQ,OAAO,OAAO,MAAM,IAAI,SAAS;CAC/C,MAAM,UAAU,OAAO,SAAS,MAAM,IAAI,SAAS;CACnD,MAAM,SACJ,OAAO,WAAW,KAAA,IAAY,OAAO,OAAO,MAAM,IAAI,KAAA,IAAY,SAAS,QAAQ,MAAM,IAAI,KAAA;CAE/F,MAAM,OAA0B;EAC9B,GAAI,QAAQ;GAAE;GAAO,0BAAS,IAAI,MAAM,EAAC,aAAa;GAAE,GAAG,EAAE;EAC7D,GAAI,UAAU,EAAE,SAAS,GAAG,EAAE;EAC9B,GAAI,SAAS,EAAE,QAAQ,GAAG,EAAE;EAC7B;CAED,MAAM,WAAW,mBAAmB,UAAU;AAC9C,QAAG,cAAc,UAAU,KAAK,UAAU,MAAM,MAAM,EAAE,EAAE,QAAQ;AAClE,KAAI;AACF,SAAG,UAAU,UAAU,IAAM;SACvB;;AAKV,SAAgB,mBAAmB,WAAyB;CAC1D,MAAM,MAAM,oBAAoB;CAChC,MAAM,eAAe;EACnB,GAAG,UAAU;EACb,GAAG,UAAU;EACb,GAAG,UAAU;EACd;AACD,MAAK,MAAM,QAAQ,aACjB,KAAI;AACF,SAAG,WAAW,KAAK,KAAK,KAAK,KAAK,CAAC;SAC7B;AAIV,KAAI;AACF,SAAG,WAAW,8BAA8B,UAAU,CAAC;SACjD;;AAgCV,SAAS,cAAc,KAA+F;AACpH,QAAO,IAAI,UAAU;;AAKvB,SAAgB,qBAAqB,KAAuB;CAC1D,MAAM,UAAU,cAAc,IAAI;CAClC,MAAM,aAAa,SAAS,WAAW,OAAO,KAAK,QAAQ,SAAS,GAAG,EAAE;AAGzE,KAAI,CAAC,WAAW,QAAQ,YAAY,KAClC,QAAO;CAET,MAAM,UAAU,6BAA6B;AAC7C,QAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,SAAS,GAAG,WAAW,CAAC,CAAC;;AAGlD,SAAgB,qBAAqB,KAAa,WAAkD;CAClG,MAAM,MAAM,WAAW,MAAM;AAC7B,KAAI,CAAC,IACH,OAAM,IAAI,MAAM,gCAAgC;CAElD,MAAM,KAAK,yBAAyB,IAAI;CACxC,MAAM,UAAU,cAAc,IAAI;CAClC,MAAM,cAAc,SAAS,WAAW;CACxC,MAAM,EAAE,UAAU,OAAO,GAAG,gBAAgB,WAAW,EAAE;CAEzD,MAAM,aAA+B;EACnC,GAAG;EACH,GAAI,eAAe,EAAE;EACtB;CAED,MAAM,cAAc,kBAAkB,GAAG;CACzC,MAAM,QAAQ,aAAa,OAAO,MAAM,IAAI,KAAA;CAC5C,MAAM,eAAe,aAAa,SAAS,MAAM,IAAI;CAErD,MAAM,cAAc,WAAW;CAC/B,MAAM,WACJ,OAAO,gBAAgB,WACnB,OAAO,YAAY,GACnB,OAAO,gBAAgB,YAAY,YAAY,MAAM,GACnD,YAAY,MAAM,GAClB,KAAA;CAER,MAAM,iBAAiB,SAAS,YAAY;AAE5C,QAAO;EACL,WAAW;EACX,SAAS,gBAAA;EACT,YAAY,WAAW,YAAY,MAAM,IAAA;EACzC;EACA,SAAS,kBAAkB,WAAW,YAAY;EAClD,YAAY,QAAQ,MAAM;EAC1B,MAAM,WAAW,MAAM,MAAM,IAAI,KAAA;EACjC,UAAU,WAAW,YAAY;EACjC,WAAW,WAAW,aAAa,EAAE;EACrC,YAAY,WAAW;EACvB;EACA,OAAO,WAAW,SAAS;EAC5B;;AAGH,SAAgB,8BAA8B,WAA2B;CACvE,MAAM,OAAO,cAAc,QAAQ,iBAAiB,IAAI;CACxD,MAAM,cAAc,UAAU,MAAM,CAAC,aAAa,CAAC,QAAQ,iBAAiB,IAAI,CAAC,QAAQ,SAAS,IAAI;CACtG,MAAM,EAAE,SAAS;AAIjB,QAAO,KAHU,QAAQ,IAAI,sBAAsB,MAAM,GACrD,QAAQ,IAAI,uBACZ,KAAK,sBAAsB,EAAE,cAAc,EACzB,GAAG,KAAK,GAAG,YAAY,iBAAiB"}
@@ -13,7 +13,7 @@ var init_config_schema = __esmMin((() => {
13
13
  "allowlist",
14
14
  "open",
15
15
  "disabled"
16
- ]).default("pairing"),
16
+ ]).default("open"),
17
17
  allowFrom: z.array(z.string()).default([]),
18
18
  streamMode: z.enum([
19
19
  "off",
@@ -29,7 +29,7 @@ var init_config_schema = __esmMin((() => {
29
29
  "allowlist",
30
30
  "open",
31
31
  "disabled"
32
- ]).default("pairing"),
32
+ ]).default("open"),
33
33
  allowFrom: z.array(z.string()).default([]),
34
34
  debug: z.boolean().default(false),
35
35
  streamMode: z.enum([
@@ -1 +1 @@
1
- {"version":3,"file":"config-schema.js","names":[],"sources":["../../../../extensions/weixin/src/config-schema.ts"],"sourcesContent":["import { z } from 'zod';\n\nexport const WeixinAccountConfigSchema = z.object({\n name: z.string().optional(),\n enabled: z.boolean().optional(),\n cdnBaseUrl: z.string().optional(),\n routeTag: z.union([z.string(), z.number()]).optional(),\n dmPolicy: z.enum(['pairing', 'allowlist', 'open', 'disabled']).default('pairing'),\n allowFrom: z.array(z.string()).default([]),\n streamMode: z.enum(['off', 'partial', 'block']).optional(),\n debug: z.boolean().optional(),\n});\n\nexport const WeixinConfigSchema = z.object({\n enabled: z.boolean().default(false),\n dmPolicy: z.enum(['pairing', 'allowlist', 'open', 'disabled']).default('pairing'),\n allowFrom: z.array(z.string()).default([]),\n debug: z.boolean().default(false),\n streamMode: z.enum(['off', 'partial', 'block']).optional(),\n historyLimit: z.number().default(50),\n textChunkLimit: z.number().default(4000),\n routeTag: z.union([z.string(), z.number()]).optional(),\n accounts: z.record(z.string(), WeixinAccountConfigSchema).optional(),\n});\n\nexport type WeixinConfig = z.infer<typeof WeixinConfigSchema>;\n"],"mappings":";;;;;AAEa,6BAA4B,EAAE,OAAO;EAChD,MAAM,EAAE,QAAQ,CAAC,UAAU;EAC3B,SAAS,EAAE,SAAS,CAAC,UAAU;EAC/B,YAAY,EAAE,QAAQ,CAAC,UAAU;EACjC,UAAU,EAAE,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC,UAAU;EACtD,UAAU,EAAE,KAAK;GAAC;GAAW;GAAa;GAAQ;GAAW,CAAC,CAAC,QAAQ,UAAU;EACjF,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC;EAC1C,YAAY,EAAE,KAAK;GAAC;GAAO;GAAW;GAAQ,CAAC,CAAC,UAAU;EAC1D,OAAO,EAAE,SAAS,CAAC,UAAU;EAC9B,CAAC;AAEW,sBAAqB,EAAE,OAAO;EACzC,SAAS,EAAE,SAAS,CAAC,QAAQ,MAAM;EACnC,UAAU,EAAE,KAAK;GAAC;GAAW;GAAa;GAAQ;GAAW,CAAC,CAAC,QAAQ,UAAU;EACjF,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC;EAC1C,OAAO,EAAE,SAAS,CAAC,QAAQ,MAAM;EACjC,YAAY,EAAE,KAAK;GAAC;GAAO;GAAW;GAAQ,CAAC,CAAC,UAAU;EAC1D,cAAc,EAAE,QAAQ,CAAC,QAAQ,GAAG;EACpC,gBAAgB,EAAE,QAAQ,CAAC,QAAQ,IAAK;EACxC,UAAU,EAAE,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC,UAAU;EACtD,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,0BAA0B,CAAC,UAAU;EACrE,CAAC"}
1
+ {"version":3,"file":"config-schema.js","names":[],"sources":["../../../../extensions/weixin/src/config-schema.ts"],"sourcesContent":["import { z } from 'zod';\n\nexport const WeixinAccountConfigSchema = z.object({\n name: z.string().optional(),\n enabled: z.boolean().optional(),\n cdnBaseUrl: z.string().optional(),\n routeTag: z.union([z.string(), z.number()]).optional(),\n dmPolicy: z.enum(['pairing', 'allowlist', 'open', 'disabled']).default('open'),\n allowFrom: z.array(z.string()).default([]),\n streamMode: z.enum(['off', 'partial', 'block']).optional(),\n debug: z.boolean().optional(),\n});\n\nexport const WeixinConfigSchema = z.object({\n enabled: z.boolean().default(false),\n dmPolicy: z.enum(['pairing', 'allowlist', 'open', 'disabled']).default('open'),\n allowFrom: z.array(z.string()).default([]),\n debug: z.boolean().default(false),\n streamMode: z.enum(['off', 'partial', 'block']).optional(),\n historyLimit: z.number().default(50),\n textChunkLimit: z.number().default(4000),\n routeTag: z.union([z.string(), z.number()]).optional(),\n accounts: z.record(z.string(), WeixinAccountConfigSchema).optional(),\n});\n\nexport type WeixinConfig = z.infer<typeof WeixinConfigSchema>;\n"],"mappings":";;;;;AAEa,6BAA4B,EAAE,OAAO;EAChD,MAAM,EAAE,QAAQ,CAAC,UAAU;EAC3B,SAAS,EAAE,SAAS,CAAC,UAAU;EAC/B,YAAY,EAAE,QAAQ,CAAC,UAAU;EACjC,UAAU,EAAE,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC,UAAU;EACtD,UAAU,EAAE,KAAK;GAAC;GAAW;GAAa;GAAQ;GAAW,CAAC,CAAC,QAAQ,OAAO;EAC9E,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC;EAC1C,YAAY,EAAE,KAAK;GAAC;GAAO;GAAW;GAAQ,CAAC,CAAC,UAAU;EAC1D,OAAO,EAAE,SAAS,CAAC,UAAU;EAC9B,CAAC;AAEW,sBAAqB,EAAE,OAAO;EACzC,SAAS,EAAE,SAAS,CAAC,QAAQ,MAAM;EACnC,UAAU,EAAE,KAAK;GAAC;GAAW;GAAa;GAAQ;GAAW,CAAC,CAAC,QAAQ,OAAO;EAC9E,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC;EAC1C,OAAO,EAAE,SAAS,CAAC,QAAQ,MAAM;EACjC,YAAY,EAAE,KAAK;GAAC;GAAO;GAAW;GAAQ,CAAC,CAAC,UAAU;EAC1D,cAAc,EAAE,QAAQ,CAAC,QAAQ,GAAG;EACpC,gBAAgB,EAAE,QAAQ,CAAC,QAAQ,IAAK;EACxC,UAAU,EAAE,MAAM,CAAC,EAAE,QAAQ,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC,UAAU;EACtD,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,0BAA0B,CAAC,UAAU;EACrE,CAAC"}
@@ -3,7 +3,7 @@ const weixinConfigSurface = { buildConfigSurface(cfg) {
3
3
  const weixin = cfg.channels?.weixin;
4
4
  return {
5
5
  enabled: weixin?.enabled ?? false,
6
- dmPolicy: weixin?.dmPolicy || "pairing",
6
+ dmPolicy: weixin?.dmPolicy || "open",
7
7
  allowFrom: weixin?.allowFrom || [],
8
8
  debug: weixin?.debug ?? false,
9
9
  streamMode: weixin?.streamMode ?? "partial",
@@ -1 +1 @@
1
- {"version":3,"file":"config-surface.js","names":[],"sources":["../../../../extensions/weixin/src/config-surface.ts"],"sourcesContent":["import type { Config } from '@xopcai/xopc/config/index.js';\nimport type { ChannelConfigSurfaceAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';\n\nexport const weixinConfigSurface: ChannelConfigSurfaceAdapter = {\n buildConfigSurface(cfg: Config): Record<string, unknown> {\n const weixin = cfg.channels?.weixin as Record<string, unknown> | undefined;\n return {\n enabled: weixin?.enabled ?? false,\n dmPolicy: weixin?.dmPolicy || 'pairing',\n allowFrom: weixin?.allowFrom || [],\n debug: weixin?.debug ?? false,\n streamMode: weixin?.streamMode ?? 'partial',\n historyLimit: weixin?.historyLimit ?? 50,\n textChunkLimit: weixin?.textChunkLimit ?? 4000,\n routeTag: weixin?.routeTag,\n accounts: weixin?.accounts || {},\n };\n },\n};\n"],"mappings":";AAGA,MAAa,sBAAmD,EAC9D,mBAAmB,KAAsC;CACvD,MAAM,SAAS,IAAI,UAAU;AAC7B,QAAO;EACL,SAAS,QAAQ,WAAW;EAC5B,UAAU,QAAQ,YAAY;EAC9B,WAAW,QAAQ,aAAa,EAAE;EAClC,OAAO,QAAQ,SAAS;EACxB,YAAY,QAAQ,cAAc;EAClC,cAAc,QAAQ,gBAAgB;EACtC,gBAAgB,QAAQ,kBAAkB;EAC1C,UAAU,QAAQ;EAClB,UAAU,QAAQ,YAAY,EAAE;EACjC;GAEJ"}
1
+ {"version":3,"file":"config-surface.js","names":[],"sources":["../../../../extensions/weixin/src/config-surface.ts"],"sourcesContent":["import type { Config } from '@xopcai/xopc/config/index.js';\nimport type { ChannelConfigSurfaceAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';\n\nexport const weixinConfigSurface: ChannelConfigSurfaceAdapter = {\n buildConfigSurface(cfg: Config): Record<string, unknown> {\n const weixin = cfg.channels?.weixin as Record<string, unknown> | undefined;\n return {\n enabled: weixin?.enabled ?? false,\n dmPolicy: weixin?.dmPolicy || 'open',\n allowFrom: weixin?.allowFrom || [],\n debug: weixin?.debug ?? false,\n streamMode: weixin?.streamMode ?? 'partial',\n historyLimit: weixin?.historyLimit ?? 50,\n textChunkLimit: weixin?.textChunkLimit ?? 4000,\n routeTag: weixin?.routeTag,\n accounts: weixin?.accounts || {},\n };\n },\n};\n"],"mappings":";AAGA,MAAa,sBAAmD,EAC9D,mBAAmB,KAAsC;CACvD,MAAM,SAAS,IAAI,UAAU;AAC7B,QAAO;EACL,SAAS,QAAQ,WAAW;EAC5B,UAAU,QAAQ,YAAY;EAC9B,WAAW,QAAQ,aAAa,EAAE;EAClC,OAAO,QAAQ,SAAS;EACxB,YAAY,QAAQ,cAAc;EAClC,cAAc,QAAQ,gBAAgB;EACtC,gBAAgB,QAAQ,kBAAkB;EAC1C,UAAU,QAAQ;EAClB,UAAU,QAAQ,YAAY,EAAE;EACjC;GAEJ"}
@@ -61,7 +61,7 @@ async function processWeixinInboundMessage(full, deps) {
61
61
  isGroup: false,
62
62
  isDm: true
63
63
  },
64
- dmPolicy: deps.account.dmPolicy ?? "pairing",
64
+ dmPolicy: deps.account.dmPolicy ?? "open",
65
65
  allowFrom: mergedAllow
66
66
  });
67
67
  if (!access.allowed) {
@@ -91,7 +91,7 @@ async function processWeixinInboundMessage(full, deps) {
91
91
  } catch (err) {
92
92
  logger.warn(`weixin pairing prompt failed from=${senderId} err=${String(err)}`);
93
93
  }
94
- else logger.info(`weixin: dropped message from=${senderId} dmPolicy=${deps.account.dmPolicy ?? "pairing"} reason=${access.reason ?? "not allowed"}`);
94
+ else logger.info(`weixin: dropped message from=${senderId} dmPolicy=${deps.account.dmPolicy ?? "open"} reason=${access.reason ?? "not allowed"}`);
95
95
  return;
96
96
  }
97
97
  const mediaOpts = {};
@@ -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 ?? 'pairing',\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 ?? 'pairing'} 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 { 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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"}
@@ -93,7 +93,7 @@ var WeixinChannelPlugin = class {
93
93
  })
94
94
  };
95
95
  security = {
96
- resolveDmPolicy: ({ account }) => resolveDmPolicy(account.dmPolicy, "pairing"),
96
+ resolveDmPolicy: ({ account }) => resolveDmPolicy(account.dmPolicy, "open"),
97
97
  checkAccess: (ctx, account, _cfg) => {
98
98
  const allowFrom = [...account.allowFrom ?? [], ...readFrameworkAllowFromList(account.accountId)];
99
99
  return evaluateAccess({