@xopcai/xopc 0.0.109 → 0.0.110

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 (77) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/feishu/src/pairing-config-resolver.d.ts +1 -0
  3. package/dist/extensions/feishu/src/pairing-config-resolver.js +30 -0
  4. package/dist/extensions/feishu/src/pairing-config-resolver.js.map +1 -0
  5. package/dist/extensions/feishu/src/plugin.d.ts +1 -0
  6. package/dist/extensions/feishu/src/plugin.js +1 -0
  7. package/dist/extensions/feishu/src/plugin.js.map +1 -1
  8. package/dist/extensions/telegram/src/access-control.d.ts +2 -1
  9. package/dist/extensions/telegram/src/access-control.js.map +1 -1
  10. package/dist/extensions/telegram/src/account-manager.d.ts +2 -1
  11. package/dist/extensions/telegram/src/account-manager.js.map +1 -1
  12. package/dist/extensions/telegram/src/adapters/gateway-setup.js +26 -26
  13. package/dist/extensions/telegram/src/adapters/gateway-setup.js.map +1 -1
  14. package/dist/extensions/telegram/src/adapters/onboard-cli.js +8 -4
  15. package/dist/extensions/telegram/src/adapters/onboard-cli.js.map +1 -1
  16. package/dist/extensions/telegram/src/config-schema.d.ts +88 -50
  17. package/dist/extensions/telegram/src/config-schema.js +40 -32
  18. package/dist/extensions/telegram/src/config-schema.js.map +1 -1
  19. package/dist/extensions/telegram/src/config-types.d.ts +54 -0
  20. package/dist/extensions/telegram/src/config-types.js +1 -0
  21. package/dist/extensions/telegram/src/doctor.js +17 -16
  22. package/dist/extensions/telegram/src/doctor.js.map +1 -1
  23. package/dist/extensions/telegram/src/exec-approvals.js +2 -3
  24. package/dist/extensions/telegram/src/exec-approvals.js.map +1 -1
  25. package/dist/extensions/telegram/src/group-config-resolver.d.ts +1 -1
  26. package/dist/extensions/telegram/src/group-config-resolver.js.map +1 -1
  27. package/dist/extensions/telegram/src/pairing-config-resolver.d.ts +1 -0
  28. package/dist/extensions/telegram/src/pairing-config-resolver.js +56 -0
  29. package/dist/extensions/telegram/src/pairing-config-resolver.js.map +1 -0
  30. package/dist/extensions/telegram/src/plugin.d.ts +1 -0
  31. package/dist/extensions/telegram/src/plugin.js +8 -9
  32. package/dist/extensions/telegram/src/plugin.js.map +1 -1
  33. package/dist/extensions/telegram/xopc.extension.json +156 -122
  34. package/dist/extensions/weixin/src/pairing-config-resolver.d.ts +1 -0
  35. package/dist/extensions/weixin/src/pairing-config-resolver.js +54 -0
  36. package/dist/extensions/weixin/src/pairing-config-resolver.js.map +1 -0
  37. package/dist/extensions/weixin/src/plugin.d.ts +1 -0
  38. package/dist/extensions/weixin/src/plugin.js +1 -0
  39. package/dist/extensions/weixin/src/plugin.js.map +1 -1
  40. package/dist/gateway/static/root/assets/{agents-D2VtykeA.js → agents-CFFioJCI.js} +1 -1
  41. package/dist/gateway/static/root/assets/{apps-page-BPQ6ueG-.js → apps-page-BYDmkbzG.js} +1 -1
  42. package/dist/gateway/static/root/assets/{channels-settings-DgZc25cS.js → channels-settings-CsbDaMvd.js} +1 -1
  43. package/dist/gateway/static/root/assets/{cron-page-BQFw5O-8.js → cron-page-Apk-7p4p.js} +1 -1
  44. package/dist/gateway/static/root/assets/{dist-BLMShn4_.js → dist-DmGDgpy2.js} +1 -1
  45. package/dist/gateway/static/root/assets/{extension-debug-page-BbQ8uZY_.js → extension-debug-page-pxFC-tUa.js} +1 -1
  46. package/dist/gateway/static/root/assets/{extension-page-DNcTendk.js → extension-page-W1wiG38F.js} +1 -1
  47. package/dist/gateway/static/root/assets/{extension-settings-page-ClWJvwHK.js → extension-settings-page-COXSv6xP.js} +1 -1
  48. package/dist/gateway/static/root/assets/{heartbeat-config-api-4O1U_Oki.js → heartbeat-config-api-DcmAMYmN.js} +1 -1
  49. package/dist/gateway/static/root/assets/{index-S23p3aa0.js → index-C_2yKRJX.js} +3 -3
  50. package/dist/gateway/static/root/assets/{logs-page-BAIdbvSC.js → logs-page-DvTWUOzd.js} +1 -1
  51. package/dist/gateway/static/root/assets/{note-detail-page-BVDoLwec.js → note-detail-page-CZbdugWZ.js} +1 -1
  52. package/dist/gateway/static/root/assets/{note-time-COWUfBZO.js → note-time-RdyBZxj3.js} +1 -1
  53. package/dist/gateway/static/root/assets/{notes-page-CI3aD7Tm.js → notes-page-mEAwy3e1.js} +1 -1
  54. package/dist/gateway/static/root/assets/{schema-form-CDpBshDP.js → schema-form-DEvdni_r.js} +1 -1
  55. package/dist/gateway/static/root/assets/{sessions-page-DM1S4eso.js → sessions-page-BsgffzV4.js} +1 -1
  56. package/dist/gateway/static/root/assets/{settings-advanced-gate-Bz_nYzvB.js → settings-advanced-gate-t6v8Dwb8.js} +1 -1
  57. package/dist/gateway/static/root/assets/{settings-form-section-BxhPoWWH.js → settings-form-section-BoYYZmJG.js} +1 -1
  58. package/dist/gateway/static/root/assets/{settings-page-Ch_Sd28M.js → settings-page-ClIthYF8.js} +1 -1
  59. package/dist/gateway/static/root/assets/{share-preview-page-D6u4llqD.js → share-preview-page-Dd9et7KD.js} +1 -1
  60. package/dist/gateway/static/root/assets/{skills-page-XMi76Nps.js → skills-page-DEXtxyfQ.js} +1 -1
  61. package/dist/gateway/static/root/assets/{voice-api-key-field-CMt5Xc7_.js → voice-api-key-field-CNrKzQrf.js} +1 -1
  62. package/dist/gateway/static/root/assets/{workflow-page.utils-BcZQmKBX.js → workflow-page.utils-EYg_xBQS.js} +1 -1
  63. package/dist/gateway/static/root/assets/{workflows-page-CxFbPBed.js → workflows-page-EMZkzJNN.js} +1 -1
  64. package/dist/gateway/static/root/index.html +1 -1
  65. package/dist/package.js +1 -1
  66. package/dist/src/channels/channel-domain.d.ts +1 -81
  67. package/dist/src/channels/pairing/index.d.ts +2 -2
  68. package/dist/src/channels/pairing/index.js +2 -2
  69. package/dist/src/channels/pairing/pairing-service.d.ts +13 -0
  70. package/dist/src/channels/pairing/pairing-service.js +78 -63
  71. package/dist/src/channels/pairing/pairing-service.js.map +1 -1
  72. package/dist/src/chat-commands/builtins/config.js +4 -11
  73. package/dist/src/chat-commands/builtins/config.js.map +1 -1
  74. package/dist/src/config/schema.js +9 -14
  75. package/dist/src/config/schema.js.map +1 -1
  76. package/dist/src/gateway/service.js +1 -1
  77. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "xopc Browser Bridge",
4
- "version": "0.0.109",
4
+ "version": "0.0.110",
5
5
  "description": "Browser automation bridge for xopc — executes commands in Chrome via the local xopc daemon.",
6
6
  "permissions": [
7
7
  "debugger",
@@ -0,0 +1,30 @@
1
+ import { resolveStandardAllowFromPath, resolveStandardPairingPath } from "../../../src/channels/pairing/paths.js";
2
+ import { registerPairingChannelResolver } from "../../../src/channels/pairing/pairing-service.js";
3
+ //#region extensions/feishu/src/pairing-config-resolver.ts
4
+ function feishuBlock(config) {
5
+ const ch = config?.channels?.feishu;
6
+ return ch && typeof ch === "object" && !Array.isArray(ch) ? ch : null;
7
+ }
8
+ registerPairingChannelResolver("feishu", {
9
+ resolvePaths(accountId) {
10
+ return {
11
+ pairingPath: resolveStandardPairingPath("feishu", accountId),
12
+ allowPath: resolveStandardAllowFromPath("feishu", accountId)
13
+ };
14
+ },
15
+ resolveAccountIds(config) {
16
+ const block = feishuBlock(config);
17
+ const accounts = block?.accounts;
18
+ if (accounts && typeof accounts === "object" && !Array.isArray(accounts)) {
19
+ const keys = Object.keys(accounts).sort();
20
+ if (keys.length > 0) return keys;
21
+ }
22
+ const appId = typeof block?.appId === "string" ? block.appId.trim() : "";
23
+ const appSecret = typeof block?.appSecret === "string" ? block.appSecret.trim() : "";
24
+ return appId && appSecret ? ["default"] : [];
25
+ }
26
+ });
27
+ //#endregion
28
+ export {};
29
+
30
+ //# sourceMappingURL=pairing-config-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pairing-config-resolver.js","names":[],"sources":["../../../../extensions/feishu/src/pairing-config-resolver.ts"],"sourcesContent":["import type { Config } from '@xopcai/xopc/config/schema.js';\nimport {\n registerPairingChannelResolver,\n resolveStandardAllowFromPath,\n resolveStandardPairingPath,\n} from '@xopcai/xopc/channels/pairing/index.js';\n\nfunction feishuBlock(config: Config | undefined): Record<string, unknown> | null {\n const ch = config?.channels?.feishu;\n return ch && typeof ch === 'object' && !Array.isArray(ch) ? (ch as Record<string, unknown>) : null;\n}\n\nregisterPairingChannelResolver('feishu', {\n resolvePaths(accountId) {\n return {\n pairingPath: resolveStandardPairingPath('feishu', accountId),\n allowPath: resolveStandardAllowFromPath('feishu', accountId),\n };\n },\n resolveAccountIds(config) {\n const block = feishuBlock(config);\n const accounts = block?.accounts;\n if (accounts && typeof accounts === 'object' && !Array.isArray(accounts)) {\n const keys = Object.keys(accounts as Record<string, unknown>).sort();\n if (keys.length > 0) return keys;\n }\n const appId = typeof block?.appId === 'string' ? block.appId.trim() : '';\n const appSecret = typeof block?.appSecret === 'string' ? block.appSecret.trim() : '';\n return appId && appSecret ? ['default'] : [];\n },\n});\n"],"mappings":";;;AAOA,SAAS,YAAY,QAA4D;CAC/E,MAAM,KAAK,QAAQ,UAAU;AAC7B,QAAO,MAAM,OAAO,OAAO,YAAY,CAAC,MAAM,QAAQ,GAAG,GAAI,KAAiC;;AAGhG,+BAA+B,UAAU;CACvC,aAAa,WAAW;AACtB,SAAO;GACL,aAAa,2BAA2B,UAAU,UAAU;GAC5D,WAAW,6BAA6B,UAAU,UAAU;GAC7D;;CAEH,kBAAkB,QAAQ;EACxB,MAAM,QAAQ,YAAY,OAAO;EACjC,MAAM,WAAW,OAAO;AACxB,MAAI,YAAY,OAAO,aAAa,YAAY,CAAC,MAAM,QAAQ,SAAS,EAAE;GACxE,MAAM,OAAO,OAAO,KAAK,SAAoC,CAAC,MAAM;AACpE,OAAI,KAAK,SAAS,EAAG,QAAO;;EAE9B,MAAM,QAAQ,OAAO,OAAO,UAAU,WAAW,MAAM,MAAM,MAAM,GAAG;EACtE,MAAM,YAAY,OAAO,OAAO,cAAc,WAAW,MAAM,UAAU,MAAM,GAAG;AAClF,SAAO,SAAS,YAAY,CAAC,UAAU,GAAG,EAAE;;CAE/C,CAAC"}
@@ -4,6 +4,7 @@
4
4
  * This is intentionally decomposed into small modules so we can grow toward
5
5
  * openclaw `extensions/feishu` parity without turning `plugin.ts` into a monolith.
6
6
  */
7
+ import './pairing-config-resolver.js';
7
8
  import type { Config } from '@xopcai/xopc/config/schema.js';
8
9
  import type { ChannelCapabilities, ChannelDoctorAdapter, ChannelMessageActionAdapter, ChannelOutboundAdapter, ChannelPlugin, ChannelPluginDefaults, ChannelPluginInitOptions, ChannelPluginReloadMeta, ChannelPluginStartOptions, ChannelSecurityAdapter, ChannelStatusAdapter } from '@xopcai/xopc/channels/plugin-types.js';
9
10
  import type { ChannelCliLoginAdapter, ChannelRuntimeActionAdapter, ChannelOnboardAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';
@@ -2,6 +2,7 @@ import { createLogger } from "../../../src/utils/logger/index.js";
2
2
  import { init_logger } from "../../../src/utils/logger.js";
3
3
  import { evaluateAccess, resolveDmPolicy, resolveGroupPolicy } from "../../../src/channels/security.js";
4
4
  import { createStandardPairingAdapter } from "../../../src/channels/pairing/pairing-store-adapter.js";
5
+ import "./pairing-config-resolver.js";
5
6
  import { FeishuConfigSchema } from "./schema/config-schema.js";
6
7
  import { listFeishuAccountIds, resolveFeishuAccount } from "./state/accounts.js";
7
8
  import { createFeishuOutboundAdapter } from "./outbound/outbound-adapter.js";
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","names":[],"sources":["../../../../extensions/feishu/src/plugin.ts"],"sourcesContent":["/**\n * Feishu/Lark channel plugin (Socket Mode first).\n *\n * This is intentionally decomposed into small modules so we can grow toward\n * openclaw `extensions/feishu` parity without turning `plugin.ts` into a monolith.\n */\n\nimport { isDeepStrictEqual } from 'node:util';\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport type { MessageBus } from '@xopcai/xopc/infra/bus/index.js';\nimport type {\n ChannelCapabilities,\n ChannelDoctorAdapter,\n ChannelMessageActionAdapter,\n ChannelOutboundAdapter,\n ChannelPlugin,\n ChannelPluginDefaults,\n ChannelPluginInitOptions,\n ChannelPluginReloadMeta,\n ChannelPluginStartOptions,\n ChannelSecurityAdapter,\n ChannelSecurityContext,\n ChannelStatusAdapter,\n ChatType,\n} from '@xopcai/xopc/channels/plugin-types.js';\nimport type {\n ChannelCliLoginAdapter,\n ChannelRuntimeActionAdapter,\n ChannelOnboardAdapter,\n} from '@xopcai/xopc/channels/plugins/types.adapters.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\nimport { evaluateAccess, resolveDmPolicy, resolveGroupPolicy } from '@xopcai/xopc/channels/security.js';\nimport { createStandardPairingAdapter } from '@xopcai/xopc/channels/pairing/pairing-store-adapter.js';\n\nimport { FeishuConfigSchema, type FeishuConfig } from './schema/config-schema.js';\nimport { listFeishuAccountIds, resolveFeishuAccount, type ResolvedFeishuAccount } from './state/accounts.js';\nimport { createFeishuOutboundAdapter } from './outbound/outbound-adapter.js';\nimport { createFeishuStatusAdapter } from './status/status-adapter.js';\nimport { createFeishuDoctorAdapter } from './status/doctor.js';\nimport { feishuConfigSurface } from './ui/config-surface.js';\nimport { createFeishuStreamingAdapter } from './streaming/streaming-adapter.js';\nimport { readFrameworkAllowFromList } from './auth/pairing.js';\nimport { feishuGatewaySetupActions } from './adapters/gateway-setup.js';\nimport { handleFeishuChannelMessageAction } from './actions/message-action-handler.js';\nimport { createFeishuInboundPipeline, type FeishuInboundWork } from './transport/reliability/inbound-pipeline.js';\n\nconst log = createLogger('FeishuPlugin');\n\nexport class FeishuChannelPlugin implements ChannelPlugin<ResolvedFeishuAccount> {\n readonly id = 'feishu' as const;\n\n readonly reload: ChannelPluginReloadMeta = {\n configPrefixes: ['channels.feishu'],\n };\n\n readonly meta = {\n id: 'feishu',\n label: 'Feishu',\n selectionLabel: 'Feishu/Lark (飞书)',\n docsPath: '/channels/feishu',\n blurb: 'Feishu/Lark enterprise messaging (Socket Mode).',\n order: 4,\n deferConnectUntilAfterListen: true,\n } as const;\n\n readonly capabilities: ChannelCapabilities = {\n chatTypes: ['direct', 'channel'] as ChatType[],\n reactions: true,\n threads: true,\n media: true,\n polls: false,\n nativeCommands: false,\n blockStreaming: false,\n edit: true,\n reply: true,\n } as any;\n\n readonly defaults: ChannelPluginDefaults = {\n queue: { debounceMs: 350 },\n outbound: { textChunkLimit: 4000 },\n streaming: {\n blockStreamingCoalesce: {\n minChars: 200,\n idleMs: 2500,\n },\n },\n };\n\n readonly configSchema = {\n schema: {},\n validate: (raw: unknown) => {\n const r = FeishuConfigSchema.safeParse(raw);\n return r.success ? { ok: true as const } : { ok: false as const, errors: [r.error.message] };\n },\n };\n\n readonly configSurface = feishuConfigSurface;\n\n readonly pairing = createStandardPairingAdapter('feishu');\n\n onboard: ChannelOnboardAdapter = {\n isConfigured: (config) => {\n const feishu = config.channels?.feishu as Record<string, unknown> | undefined;\n const appId = typeof feishu?.appId === 'string' ? feishu.appId.trim() : '';\n const appSecret = typeof feishu?.appSecret === 'string' ? feishu.appSecret.trim() : '';\n return feishu?.enabled === true && Boolean(appId && appSecret);\n },\n configure: async (config) => {\n const { feishuOnboardAdapter } = await import('./adapters/onboard-cli.js');\n return feishuOnboardAdapter.configure(config);\n },\n };\n\n readonly cliLogin: ChannelCliLoginAdapter = {\n runLogin: async (params) => {\n const { feishuCliLoginAdapter } = await import('./adapters/cli-login.js');\n return feishuCliLoginAdapter.runLogin(params);\n },\n };\n\n readonly runtimeActions: ChannelRuntimeActionAdapter = feishuGatewaySetupActions;\n\n private bus!: MessageBus;\n private cfg!: Config;\n private abortControllers = new Map<string, AbortController>();\n private inboundPipeline?: ReturnType<typeof createFeishuInboundPipeline>;\n /** Unregister fn for the workflow-progress capability registered against the global broker. */\n private workflowProgressUnregister: (() => void) | null = null;\n\n config = {\n listAccountIds: (cfg: Config) => listFeishuAccountIds(cfg),\n resolveAccount: (cfg: Config, accountId?: string | null) => resolveFeishuAccount(cfg, accountId),\n isConfigured: async (account: ResolvedFeishuAccount) => account.configured,\n describeAccount: (account: ResolvedFeishuAccount) => ({\n accountId: account.accountId,\n channelId: 'feishu',\n enabled: account.enabled,\n configured: account.configured,\n status: account.configured ? undefined : 'unconfigured',\n }),\n };\n\n security: ChannelSecurityAdapter<ResolvedFeishuAccount> = {\n resolveDmPolicy: ({ account }: { account: ResolvedFeishuAccount }) =>\n resolveDmPolicy(account.dmPolicy, 'open'),\n resolveGroupPolicy: ({ account }: { account: ResolvedFeishuAccount }) =>\n resolveGroupPolicy(account.groupPolicy, 'allowlist'),\n checkAccess: (ctx: ChannelSecurityContext, account: ResolvedFeishuAccount, _cfg: Config) => {\n const isDm = !ctx.isGroup;\n const frameworkAllowFrom = readFrameworkAllowFromList(account.accountId);\n const baseAllowFrom = isDm ? account.allowFrom : account.groupAllowFrom ?? account.allowFrom;\n const allowFrom = [...(baseAllowFrom ?? []), ...frameworkAllowFrom];\n if (isDm) {\n return evaluateAccess({\n context: {\n channel: 'feishu',\n accountId: account.accountId,\n chatId: ctx.chatId,\n senderId: ctx.senderId,\n senderName: ctx.senderName,\n isGroup: false,\n isDm: true,\n },\n dmPolicy: account.dmPolicy,\n allowFrom,\n });\n }\n return evaluateAccess({\n context: {\n channel: 'feishu',\n accountId: account.accountId,\n chatId: ctx.chatId,\n senderId: ctx.senderId,\n senderName: ctx.senderName,\n isGroup: true,\n isDm: false,\n },\n groupPolicy: account.groupPolicy,\n allowFrom,\n });\n },\n };\n\n outbound: ChannelOutboundAdapter = createFeishuOutboundAdapter();\n\n streaming = createFeishuStreamingAdapter(() => this.cfg);\n\n status: ChannelStatusAdapter<ResolvedFeishuAccount> = createFeishuStatusAdapter();\n\n doctor: ChannelDoctorAdapter = createFeishuDoctorAdapter();\n\n directory = {\n resolveDisplayName: async (params) => {\n const { createFeishuDirectoryAdapter } = await import('./directory/directory-adapter.js');\n return createFeishuDirectoryAdapter().resolveDisplayName?.(params);\n },\n };\n\n actions: ChannelMessageActionAdapter = {\n handleAction: handleFeishuChannelMessageAction,\n };\n\n agentTools = [\n {\n name: 'feishu_read',\n description: 'Read a Feishu message by messageId (debug/utility).',\n execute: async (toolCtx, args) => {\n const messageId = typeof (args as any)?.messageId === 'string' ? (args as any).messageId : toolCtx.messageId;\n if (!messageId) throw new Error('feishu_read requires messageId');\n const { getMessageFeishu } = await import('./outbound/actions.js');\n return await getMessageFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId });\n },\n },\n {\n name: 'feishu_edit',\n description: 'Edit a Feishu message by messageId.',\n execute: async (toolCtx, args) => {\n const messageId = typeof (args as any)?.messageId === 'string' ? (args as any).messageId : toolCtx.messageId;\n const text = typeof (args as any)?.text === 'string' ? (args as any).text : '';\n if (!messageId) throw new Error('feishu_edit requires messageId');\n if (!text.trim()) throw new Error('feishu_edit requires text');\n const { editMessageFeishu } = await import('./outbound/actions.js');\n return await editMessageFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId, text });\n },\n },\n {\n name: 'feishu_scopes_probe',\n description: 'Probe Feishu credentials/scopes (placeholder for full docs/wiki/drive tools).',\n execute: async (toolCtx) => {\n const { feishuWhoAmI } = await import('./tools/tools.js');\n return await feishuWhoAmI({ cfg: this.cfg, accountId: toolCtx.accountId });\n },\n },\n {\n name: 'feishu_react',\n description: 'Add/remove/list reactions for a message.',\n execute: async (toolCtx, args) => {\n const a = args as any;\n const messageId = typeof a?.messageId === 'string' ? a.messageId : toolCtx.messageId;\n if (!messageId) throw new Error('feishu_react requires messageId');\n\n const account = resolveFeishuAccount(this.cfg, toolCtx.accountId ?? 'default');\n const enabled = (account.actions as any)?.reactions !== false;\n if (!enabled) {\n throw new Error('Feishu reactions are disabled via channels.feishu.actions.reactions');\n }\n\n if (a?.list === true) {\n const { listReactionsFeishu } = await import('./outbound/actions.js');\n return await listReactionsFeishu({\n cfg: this.cfg,\n accountId: toolCtx.accountId,\n messageId,\n emojiType: typeof a?.emojiType === 'string' ? a.emojiType : undefined,\n });\n }\n\n if (a?.remove === true) {\n const reactionId = typeof a?.reactionId === 'string' ? a.reactionId : '';\n if (!reactionId) throw new Error('feishu_react remove requires reactionId');\n const { removeReactionFeishu } = await import('./outbound/actions.js');\n return await removeReactionFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId, reactionId });\n }\n\n const emojiType = typeof a?.emojiType === 'string' ? a.emojiType : '';\n if (!emojiType) throw new Error('feishu_react requires emojiType');\n const { addReactionFeishu } = await import('./outbound/actions.js');\n return await addReactionFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId, emojiType });\n },\n },\n {\n name: 'feishu_pins',\n description: 'Pin/unpin/list pins for a chat.',\n execute: async (toolCtx, args) => {\n const a = args as any;\n const action = typeof a?.action === 'string' ? a.action : '';\n if (action === 'list') {\n const chatId = typeof a?.chatId === 'string' ? a.chatId : toolCtx.chatId;\n if (!chatId) throw new Error('feishu_pins list requires chatId');\n const { listPinsFeishu } = await import('./outbound/actions.js');\n return await listPinsFeishu({\n cfg: this.cfg,\n accountId: toolCtx.accountId,\n chatId,\n startTime: typeof a?.startTime === 'string' ? a.startTime : undefined,\n endTime: typeof a?.endTime === 'string' ? a.endTime : undefined,\n pageSize: typeof a?.pageSize === 'number' ? a.pageSize : undefined,\n pageToken: typeof a?.pageToken === 'string' ? a.pageToken : undefined,\n });\n }\n const messageId = typeof a?.messageId === 'string' ? a.messageId : toolCtx.messageId;\n if (!messageId) throw new Error('feishu_pins requires messageId');\n if (action === 'pin') {\n const { pinMessageFeishu } = await import('./outbound/actions.js');\n return await pinMessageFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId });\n }\n if (action === 'unpin') {\n const { unpinMessageFeishu } = await import('./outbound/actions.js');\n return await unpinMessageFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId });\n }\n throw new Error('feishu_pins requires action: pin | unpin | list');\n },\n },\n ];\n\n async init(options: ChannelPluginInitOptions): Promise<void> {\n this.bus = options.bus;\n this.cfg = options.config;\n const defaultDebounce = this.defaults.queue?.debounceMs ?? 350;\n this.inboundPipeline = createFeishuInboundPipeline({\n bus: this.bus,\n defaultDebounceMs: defaultDebounce,\n onError: (err, items) => {\n log.error({ err, count: items.length }, 'Feishu inbound pipeline flush failed');\n },\n });\n\n await this.registerWorkflowProgressCapability();\n\n log.debug('Feishu plugin initialized');\n }\n\n private async registerWorkflowProgressCapability(): Promise<void> {\n // Lazy-load workflow progress so Feishu runtime startup does not pull the\n // agent/workflow/provider graph into Electron's dynamic extension path.\n const [{ getWorkflowProgressBroker }, { createFeishuWorkflowProgressCapability }] = await Promise.all([\n import('@xopcai/xopc/agent/workflow/progress-broker.js'),\n import('./workflow-progress.js'),\n ]);\n this.workflowProgressUnregister = getWorkflowProgressBroker().registerChannel(\n createFeishuWorkflowProgressCapability({ getConfig: () => this.cfg }),\n );\n }\n\n async start(options?: ChannelPluginStartOptions): Promise<void> {\n const section = this.cfg.channels?.feishu as FeishuConfig | undefined;\n if (!section || section.enabled !== true) {\n return;\n }\n\n const ids = options?.accountId ? [options.accountId] : listFeishuAccountIds(this.cfg);\n for (const accountId of ids) {\n const account = resolveFeishuAccount(this.cfg, accountId);\n if (!account.enabled || !account.configured) continue;\n if (this.abortControllers.has(accountId)) continue;\n\n const ac = new AbortController();\n this.abortControllers.set(accountId, ac);\n\n const defaultDebounce = this.defaults.queue?.debounceMs ?? 350;\n const enqueueInbound = (work: FeishuInboundWork) => {\n const p = this.inboundPipeline;\n if (!p) {\n return this.bus.publishInbound(work.inbound);\n }\n return p.enqueue(work);\n };\n\n const monitorDeps = {\n account,\n config: this.cfg,\n enqueueInbound,\n inboundDebounceDefaultMs: defaultDebounce,\n abortSignal: ac.signal,\n security: {\n checkAccess: (ctx: ChannelSecurityContext) => this.security.checkAccess?.(ctx, account, this.cfg),\n },\n };\n const runner =\n account.connectionMode === 'webhook'\n ? (await import('./transport/webhook/monitor.js')).createFeishuWebhookMonitor(monitorDeps)\n : (await import('./transport/socket-mode/monitor.js')).createFeishuSocketModeMonitor(monitorDeps);\n\n void runner.run().catch((err) => {\n if ((err as { name?: string } | undefined)?.name === 'AbortError') {\n log.debug({ accountId }, 'Feishu monitor stopped');\n return;\n }\n log.error({ err, accountId }, 'Feishu monitor exited with error');\n });\n\n log.info({ accountId, mode: account.connectionMode }, 'Feishu monitor started');\n }\n }\n\n async stop(accountId?: string): Promise<void> {\n try {\n await this.inboundPipeline?.flushAll();\n } catch (err) {\n log.warn({ err }, 'Feishu inbound pipeline flushAll failed');\n }\n const ids = accountId ? [accountId] : [...this.abortControllers.keys()];\n for (const id of ids) {\n const ac = this.abortControllers.get(id);\n if (ac) {\n ac.abort();\n this.abortControllers.delete(id);\n }\n }\n // Only unregister the broker capability on a full stop. Per-account stops\n // still leave other accounts capable of delivering progress through the\n // same registered cap.\n if (!accountId) {\n this.workflowProgressUnregister?.();\n this.workflowProgressUnregister = null;\n }\n }\n\n channelIsRunning(cfg: Config): boolean {\n const ids = listFeishuAccountIds(cfg);\n return ids.some((id) => {\n const a = resolveFeishuAccount(cfg, id);\n return a.enabled !== false && a.configured && this.abortControllers.has(id);\n });\n }\n\n async onConfigUpdated(cfg: Config): Promise<void> {\n const prev = this.cfg.channels?.feishu as unknown;\n const next = cfg.channels?.feishu as { enabled?: boolean } | undefined;\n const channelOff = !next || next.enabled !== true;\n\n if (channelOff) {\n this.cfg = cfg;\n await this.stop();\n return;\n }\n\n this.cfg = cfg;\n\n if (isDeepStrictEqual(prev, next) && this.channelIsRunning(cfg)) {\n return;\n }\n\n await this.stop();\n await this.start();\n }\n}\n\nexport const feishuPlugin = new FeishuChannelPlugin();\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;aA+B4D;AAgB5D,MAAM,MAAM,aAAa,eAAe;AAExC,IAAa,sBAAb,MAAiF;CAC/E,KAAc;CAEd,SAA2C,EACzC,gBAAgB,CAAC,kBAAkB,EACpC;CAED,OAAgB;EACd,IAAI;EACJ,OAAO;EACP,gBAAgB;EAChB,UAAU;EACV,OAAO;EACP,OAAO;EACP,8BAA8B;EAC/B;CAED,eAA6C;EAC3C,WAAW,CAAC,UAAU,UAAU;EAChC,WAAW;EACX,SAAS;EACT,OAAO;EACP,OAAO;EACP,gBAAgB;EAChB,gBAAgB;EAChB,MAAM;EACN,OAAO;EACR;CAED,WAA2C;EACzC,OAAO,EAAE,YAAY,KAAK;EAC1B,UAAU,EAAE,gBAAgB,KAAM;EAClC,WAAW,EACT,wBAAwB;GACtB,UAAU;GACV,QAAQ;GACT,EACF;EACF;CAED,eAAwB;EACtB,QAAQ,EAAE;EACV,WAAW,QAAiB;GAC1B,MAAM,IAAI,mBAAmB,UAAU,IAAI;AAC3C,UAAO,EAAE,UAAU,EAAE,IAAI,MAAe,GAAG;IAAE,IAAI;IAAgB,QAAQ,CAAC,EAAE,MAAM,QAAQ;IAAE;;EAE/F;CAED,gBAAyB;CAEzB,UAAmB,6BAA6B,SAAS;CAEzD,UAAiC;EAC/B,eAAe,WAAW;GACxB,MAAM,SAAS,OAAO,UAAU;GAChC,MAAM,QAAQ,OAAO,QAAQ,UAAU,WAAW,OAAO,MAAM,MAAM,GAAG;GACxE,MAAM,YAAY,OAAO,QAAQ,cAAc,WAAW,OAAO,UAAU,MAAM,GAAG;AACpF,UAAO,QAAQ,YAAY,QAAQ,QAAQ,SAAS,UAAU;;EAEhE,WAAW,OAAO,WAAW;GAC3B,MAAM,EAAE,yBAAyB,MAAM,OAAO;AAC9C,UAAO,qBAAqB,UAAU,OAAO;;EAEhD;CAED,WAA4C,EAC1C,UAAU,OAAO,WAAW;EAC1B,MAAM,EAAE,0BAA0B,MAAM,OAAO;AAC/C,SAAO,sBAAsB,SAAS,OAAO;IAEhD;CAED,iBAAuD;CAEvD;CACA;CACA,mCAA2B,IAAI,KAA8B;CAC7D;;CAEA,6BAA0D;CAE1D,SAAS;EACP,iBAAiB,QAAgB,qBAAqB,IAAI;EAC1D,iBAAiB,KAAa,cAA8B,qBAAqB,KAAK,UAAU;EAChG,cAAc,OAAO,YAAmC,QAAQ;EAChE,kBAAkB,aAAoC;GACpD,WAAW,QAAQ;GACnB,WAAW;GACX,SAAS,QAAQ;GACjB,YAAY,QAAQ;GACpB,QAAQ,QAAQ,aAAa,KAAA,IAAY;GAC1C;EACF;CAED,WAA0D;EACxD,kBAAkB,EAAE,cAClB,gBAAgB,QAAQ,UAAU,OAAO;EAC3C,qBAAqB,EAAE,cACrB,mBAAmB,QAAQ,aAAa,YAAY;EACtD,cAAc,KAA6B,SAAgC,SAAiB;GAC1F,MAAM,OAAO,CAAC,IAAI;GAClB,MAAM,qBAAqB,2BAA2B,QAAQ,UAAU;GAExE,MAAM,YAAY,CAAC,IADG,OAAO,QAAQ,YAAY,QAAQ,kBAAkB,QAAQ,cAC3C,EAAE,EAAG,GAAG,mBAAmB;AACnE,OAAI,KACF,QAAO,eAAe;IACpB,SAAS;KACP,SAAS;KACT,WAAW,QAAQ;KACnB,QAAQ,IAAI;KACZ,UAAU,IAAI;KACd,YAAY,IAAI;KAChB,SAAS;KACT,MAAM;KACP;IACD,UAAU,QAAQ;IAClB;IACD,CAAC;AAEJ,UAAO,eAAe;IACpB,SAAS;KACP,SAAS;KACT,WAAW,QAAQ;KACnB,QAAQ,IAAI;KACZ,UAAU,IAAI;KACd,YAAY,IAAI;KAChB,SAAS;KACT,MAAM;KACP;IACD,aAAa,QAAQ;IACrB;IACD,CAAC;;EAEL;CAED,WAAmC,6BAA6B;CAEhE,YAAY,mCAAmC,KAAK,IAAI;CAExD,SAAsD,2BAA2B;CAEjF,SAA+B,2BAA2B;CAE1D,YAAY,EACV,oBAAoB,OAAO,WAAW;EACpC,MAAM,EAAE,iCAAiC,MAAM,OAAO;AACtD,SAAO,8BAA8B,CAAC,qBAAqB,OAAO;IAErE;CAED,UAAuC,EACrC,cAAc,kCACf;CAED,aAAa;EACX;GACE,MAAM;GACN,aAAa;GACb,SAAS,OAAO,SAAS,SAAS;IAChC,MAAM,YAAY,OAAQ,MAAc,cAAc,WAAY,KAAa,YAAY,QAAQ;AACnG,QAAI,CAAC,UAAW,OAAM,IAAI,MAAM,iCAAiC;IACjE,MAAM,EAAE,qBAAqB,MAAM,OAAO;AAC1C,WAAO,MAAM,iBAAiB;KAAE,KAAK,KAAK;KAAK,WAAW,QAAQ;KAAW;KAAW,CAAC;;GAE5F;EACD;GACE,MAAM;GACN,aAAa;GACb,SAAS,OAAO,SAAS,SAAS;IAChC,MAAM,YAAY,OAAQ,MAAc,cAAc,WAAY,KAAa,YAAY,QAAQ;IACnG,MAAM,OAAO,OAAQ,MAAc,SAAS,WAAY,KAAa,OAAO;AAC5E,QAAI,CAAC,UAAW,OAAM,IAAI,MAAM,iCAAiC;AACjE,QAAI,CAAC,KAAK,MAAM,CAAE,OAAM,IAAI,MAAM,4BAA4B;IAC9D,MAAM,EAAE,sBAAsB,MAAM,OAAO;AAC3C,WAAO,MAAM,kBAAkB;KAAE,KAAK,KAAK;KAAK,WAAW,QAAQ;KAAW;KAAW;KAAM,CAAC;;GAEnG;EACD;GACE,MAAM;GACN,aAAa;GACb,SAAS,OAAO,YAAY;IAC1B,MAAM,EAAE,iBAAiB,MAAM,OAAO;AACtC,WAAO,MAAM,aAAa;KAAE,KAAK,KAAK;KAAK,WAAW,QAAQ;KAAW,CAAC;;GAE7E;EACD;GACE,MAAM;GACN,aAAa;GACb,SAAS,OAAO,SAAS,SAAS;IAChC,MAAM,IAAI;IACV,MAAM,YAAY,OAAO,GAAG,cAAc,WAAW,EAAE,YAAY,QAAQ;AAC3E,QAAI,CAAC,UAAW,OAAM,IAAI,MAAM,kCAAkC;AAIlE,QAAI,EAFY,qBAAqB,KAAK,KAAK,QAAQ,aAAa,UAC5C,CAAC,SAAiB,cAAc,OAEtD,OAAM,IAAI,MAAM,sEAAsE;AAGxF,QAAI,GAAG,SAAS,MAAM;KACpB,MAAM,EAAE,wBAAwB,MAAM,OAAO;AAC7C,YAAO,MAAM,oBAAoB;MAC/B,KAAK,KAAK;MACV,WAAW,QAAQ;MACnB;MACA,WAAW,OAAO,GAAG,cAAc,WAAW,EAAE,YAAY,KAAA;MAC7D,CAAC;;AAGJ,QAAI,GAAG,WAAW,MAAM;KACtB,MAAM,aAAa,OAAO,GAAG,eAAe,WAAW,EAAE,aAAa;AACtE,SAAI,CAAC,WAAY,OAAM,IAAI,MAAM,0CAA0C;KAC3E,MAAM,EAAE,yBAAyB,MAAM,OAAO;AAC9C,YAAO,MAAM,qBAAqB;MAAE,KAAK,KAAK;MAAK,WAAW,QAAQ;MAAW;MAAW;MAAY,CAAC;;IAG3G,MAAM,YAAY,OAAO,GAAG,cAAc,WAAW,EAAE,YAAY;AACnE,QAAI,CAAC,UAAW,OAAM,IAAI,MAAM,kCAAkC;IAClE,MAAM,EAAE,sBAAsB,MAAM,OAAO;AAC3C,WAAO,MAAM,kBAAkB;KAAE,KAAK,KAAK;KAAK,WAAW,QAAQ;KAAW;KAAW;KAAW,CAAC;;GAExG;EACD;GACE,MAAM;GACN,aAAa;GACb,SAAS,OAAO,SAAS,SAAS;IAChC,MAAM,IAAI;IACV,MAAM,SAAS,OAAO,GAAG,WAAW,WAAW,EAAE,SAAS;AAC1D,QAAI,WAAW,QAAQ;KACrB,MAAM,SAAS,OAAO,GAAG,WAAW,WAAW,EAAE,SAAS,QAAQ;AAClE,SAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,mCAAmC;KAChE,MAAM,EAAE,mBAAmB,MAAM,OAAO;AACxC,YAAO,MAAM,eAAe;MAC1B,KAAK,KAAK;MACV,WAAW,QAAQ;MACnB;MACA,WAAW,OAAO,GAAG,cAAc,WAAW,EAAE,YAAY,KAAA;MAC5D,SAAS,OAAO,GAAG,YAAY,WAAW,EAAE,UAAU,KAAA;MACtD,UAAU,OAAO,GAAG,aAAa,WAAW,EAAE,WAAW,KAAA;MACzD,WAAW,OAAO,GAAG,cAAc,WAAW,EAAE,YAAY,KAAA;MAC7D,CAAC;;IAEJ,MAAM,YAAY,OAAO,GAAG,cAAc,WAAW,EAAE,YAAY,QAAQ;AAC3E,QAAI,CAAC,UAAW,OAAM,IAAI,MAAM,iCAAiC;AACjE,QAAI,WAAW,OAAO;KACpB,MAAM,EAAE,qBAAqB,MAAM,OAAO;AAC1C,YAAO,MAAM,iBAAiB;MAAE,KAAK,KAAK;MAAK,WAAW,QAAQ;MAAW;MAAW,CAAC;;AAE3F,QAAI,WAAW,SAAS;KACtB,MAAM,EAAE,uBAAuB,MAAM,OAAO;AAC5C,YAAO,MAAM,mBAAmB;MAAE,KAAK,KAAK;MAAK,WAAW,QAAQ;MAAW;MAAW,CAAC;;AAE7F,UAAM,IAAI,MAAM,kDAAkD;;GAErE;EACF;CAED,MAAM,KAAK,SAAkD;AAC3D,OAAK,MAAM,QAAQ;AACnB,OAAK,MAAM,QAAQ;EACnB,MAAM,kBAAkB,KAAK,SAAS,OAAO,cAAc;AAC3D,OAAK,kBAAkB,4BAA4B;GACjD,KAAK,KAAK;GACV,mBAAmB;GACnB,UAAU,KAAK,UAAU;AACvB,QAAI,MAAM;KAAE;KAAK,OAAO,MAAM;KAAQ,EAAE,uCAAuC;;GAElF,CAAC;AAEF,QAAM,KAAK,oCAAoC;AAE/C,MAAI,MAAM,4BAA4B;;CAGxC,MAAc,qCAAoD;EAGhE,MAAM,CAAC,EAAE,6BAA6B,EAAE,4CAA4C,MAAM,QAAQ,IAAI,CACpG,OAAO,mDACP,OAAO,0BACR,CAAC;AACF,OAAK,6BAA6B,2BAA2B,CAAC,gBAC5D,uCAAuC,EAAE,iBAAiB,KAAK,KAAK,CAAC,CACtE;;CAGH,MAAM,MAAM,SAAoD;EAC9D,MAAM,UAAU,KAAK,IAAI,UAAU;AACnC,MAAI,CAAC,WAAW,QAAQ,YAAY,KAClC;EAGF,MAAM,MAAM,SAAS,YAAY,CAAC,QAAQ,UAAU,GAAG,qBAAqB,KAAK,IAAI;AACrF,OAAK,MAAM,aAAa,KAAK;GAC3B,MAAM,UAAU,qBAAqB,KAAK,KAAK,UAAU;AACzD,OAAI,CAAC,QAAQ,WAAW,CAAC,QAAQ,WAAY;AAC7C,OAAI,KAAK,iBAAiB,IAAI,UAAU,CAAE;GAE1C,MAAM,KAAK,IAAI,iBAAiB;AAChC,QAAK,iBAAiB,IAAI,WAAW,GAAG;GAExC,MAAM,kBAAkB,KAAK,SAAS,OAAO,cAAc;GAC3D,MAAM,kBAAkB,SAA4B;IAClD,MAAM,IAAI,KAAK;AACf,QAAI,CAAC,EACH,QAAO,KAAK,IAAI,eAAe,KAAK,QAAQ;AAE9C,WAAO,EAAE,QAAQ,KAAK;;GAGxB,MAAM,cAAc;IAClB;IACA,QAAQ,KAAK;IACb;IACA,0BAA0B;IAC1B,aAAa,GAAG;IAChB,UAAU,EACR,cAAc,QAAgC,KAAK,SAAS,cAAc,KAAK,SAAS,KAAK,IAAI,EAClG;IACF;AAMD,IAJE,QAAQ,mBAAmB,aACtB,MAAM,OAAO,mCAAmC,2BAA2B,YAAY,IACvF,MAAM,OAAO,uCAAuC,8BAA8B,YAAY,EAEzF,KAAK,CAAC,OAAO,QAAQ;AAC/B,QAAK,KAAuC,SAAS,cAAc;AACjE,SAAI,MAAM,EAAE,WAAW,EAAE,yBAAyB;AAClD;;AAEF,QAAI,MAAM;KAAE;KAAK;KAAW,EAAE,mCAAmC;KACjE;AAEF,OAAI,KAAK;IAAE;IAAW,MAAM,QAAQ;IAAgB,EAAE,yBAAyB;;;CAInF,MAAM,KAAK,WAAmC;AAC5C,MAAI;AACF,SAAM,KAAK,iBAAiB,UAAU;WAC/B,KAAK;AACZ,OAAI,KAAK,EAAE,KAAK,EAAE,0CAA0C;;EAE9D,MAAM,MAAM,YAAY,CAAC,UAAU,GAAG,CAAC,GAAG,KAAK,iBAAiB,MAAM,CAAC;AACvE,OAAK,MAAM,MAAM,KAAK;GACpB,MAAM,KAAK,KAAK,iBAAiB,IAAI,GAAG;AACxC,OAAI,IAAI;AACN,OAAG,OAAO;AACV,SAAK,iBAAiB,OAAO,GAAG;;;AAMpC,MAAI,CAAC,WAAW;AACd,QAAK,8BAA8B;AACnC,QAAK,6BAA6B;;;CAItC,iBAAiB,KAAsB;AAErC,SADY,qBAAqB,IACvB,CAAC,MAAM,OAAO;GACtB,MAAM,IAAI,qBAAqB,KAAK,GAAG;AACvC,UAAO,EAAE,YAAY,SAAS,EAAE,cAAc,KAAK,iBAAiB,IAAI,GAAG;IAC3E;;CAGJ,MAAM,gBAAgB,KAA4B;EAChD,MAAM,OAAO,KAAK,IAAI,UAAU;EAChC,MAAM,OAAO,IAAI,UAAU;AAG3B,MAFmB,CAAC,QAAQ,KAAK,YAAY,MAE7B;AACd,QAAK,MAAM;AACX,SAAM,KAAK,MAAM;AACjB;;AAGF,OAAK,MAAM;AAEX,MAAI,kBAAkB,MAAM,KAAK,IAAI,KAAK,iBAAiB,IAAI,CAC7D;AAGF,QAAM,KAAK,MAAM;AACjB,QAAM,KAAK,OAAO;;;AAItB,MAAa,eAAe,IAAI,qBAAqB"}
1
+ {"version":3,"file":"plugin.js","names":[],"sources":["../../../../extensions/feishu/src/plugin.ts"],"sourcesContent":["/**\n * Feishu/Lark channel plugin (Socket Mode first).\n *\n * This is intentionally decomposed into small modules so we can grow toward\n * openclaw `extensions/feishu` parity without turning `plugin.ts` into a monolith.\n */\n\nimport { isDeepStrictEqual } from 'node:util';\n\nimport './pairing-config-resolver.js';\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport type { MessageBus } from '@xopcai/xopc/infra/bus/index.js';\nimport type {\n ChannelCapabilities,\n ChannelDoctorAdapter,\n ChannelMessageActionAdapter,\n ChannelOutboundAdapter,\n ChannelPlugin,\n ChannelPluginDefaults,\n ChannelPluginInitOptions,\n ChannelPluginReloadMeta,\n ChannelPluginStartOptions,\n ChannelSecurityAdapter,\n ChannelSecurityContext,\n ChannelStatusAdapter,\n ChatType,\n} from '@xopcai/xopc/channels/plugin-types.js';\nimport type {\n ChannelCliLoginAdapter,\n ChannelRuntimeActionAdapter,\n ChannelOnboardAdapter,\n} from '@xopcai/xopc/channels/plugins/types.adapters.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\nimport { evaluateAccess, resolveDmPolicy, resolveGroupPolicy } from '@xopcai/xopc/channels/security.js';\nimport { createStandardPairingAdapter } from '@xopcai/xopc/channels/pairing/pairing-store-adapter.js';\n\nimport { FeishuConfigSchema, type FeishuConfig } from './schema/config-schema.js';\nimport { listFeishuAccountIds, resolveFeishuAccount, type ResolvedFeishuAccount } from './state/accounts.js';\nimport { createFeishuOutboundAdapter } from './outbound/outbound-adapter.js';\nimport { createFeishuStatusAdapter } from './status/status-adapter.js';\nimport { createFeishuDoctorAdapter } from './status/doctor.js';\nimport { feishuConfigSurface } from './ui/config-surface.js';\nimport { createFeishuStreamingAdapter } from './streaming/streaming-adapter.js';\nimport { readFrameworkAllowFromList } from './auth/pairing.js';\nimport { feishuGatewaySetupActions } from './adapters/gateway-setup.js';\nimport { handleFeishuChannelMessageAction } from './actions/message-action-handler.js';\nimport { createFeishuInboundPipeline, type FeishuInboundWork } from './transport/reliability/inbound-pipeline.js';\n\nconst log = createLogger('FeishuPlugin');\n\nexport class FeishuChannelPlugin implements ChannelPlugin<ResolvedFeishuAccount> {\n readonly id = 'feishu' as const;\n\n readonly reload: ChannelPluginReloadMeta = {\n configPrefixes: ['channels.feishu'],\n };\n\n readonly meta = {\n id: 'feishu',\n label: 'Feishu',\n selectionLabel: 'Feishu/Lark (飞书)',\n docsPath: '/channels/feishu',\n blurb: 'Feishu/Lark enterprise messaging (Socket Mode).',\n order: 4,\n deferConnectUntilAfterListen: true,\n } as const;\n\n readonly capabilities: ChannelCapabilities = {\n chatTypes: ['direct', 'channel'] as ChatType[],\n reactions: true,\n threads: true,\n media: true,\n polls: false,\n nativeCommands: false,\n blockStreaming: false,\n edit: true,\n reply: true,\n } as any;\n\n readonly defaults: ChannelPluginDefaults = {\n queue: { debounceMs: 350 },\n outbound: { textChunkLimit: 4000 },\n streaming: {\n blockStreamingCoalesce: {\n minChars: 200,\n idleMs: 2500,\n },\n },\n };\n\n readonly configSchema = {\n schema: {},\n validate: (raw: unknown) => {\n const r = FeishuConfigSchema.safeParse(raw);\n return r.success ? { ok: true as const } : { ok: false as const, errors: [r.error.message] };\n },\n };\n\n readonly configSurface = feishuConfigSurface;\n\n readonly pairing = createStandardPairingAdapter('feishu');\n\n onboard: ChannelOnboardAdapter = {\n isConfigured: (config) => {\n const feishu = config.channels?.feishu as Record<string, unknown> | undefined;\n const appId = typeof feishu?.appId === 'string' ? feishu.appId.trim() : '';\n const appSecret = typeof feishu?.appSecret === 'string' ? feishu.appSecret.trim() : '';\n return feishu?.enabled === true && Boolean(appId && appSecret);\n },\n configure: async (config) => {\n const { feishuOnboardAdapter } = await import('./adapters/onboard-cli.js');\n return feishuOnboardAdapter.configure(config);\n },\n };\n\n readonly cliLogin: ChannelCliLoginAdapter = {\n runLogin: async (params) => {\n const { feishuCliLoginAdapter } = await import('./adapters/cli-login.js');\n return feishuCliLoginAdapter.runLogin(params);\n },\n };\n\n readonly runtimeActions: ChannelRuntimeActionAdapter = feishuGatewaySetupActions;\n\n private bus!: MessageBus;\n private cfg!: Config;\n private abortControllers = new Map<string, AbortController>();\n private inboundPipeline?: ReturnType<typeof createFeishuInboundPipeline>;\n /** Unregister fn for the workflow-progress capability registered against the global broker. */\n private workflowProgressUnregister: (() => void) | null = null;\n\n config = {\n listAccountIds: (cfg: Config) => listFeishuAccountIds(cfg),\n resolveAccount: (cfg: Config, accountId?: string | null) => resolveFeishuAccount(cfg, accountId),\n isConfigured: async (account: ResolvedFeishuAccount) => account.configured,\n describeAccount: (account: ResolvedFeishuAccount) => ({\n accountId: account.accountId,\n channelId: 'feishu',\n enabled: account.enabled,\n configured: account.configured,\n status: account.configured ? undefined : 'unconfigured',\n }),\n };\n\n security: ChannelSecurityAdapter<ResolvedFeishuAccount> = {\n resolveDmPolicy: ({ account }: { account: ResolvedFeishuAccount }) =>\n resolveDmPolicy(account.dmPolicy, 'open'),\n resolveGroupPolicy: ({ account }: { account: ResolvedFeishuAccount }) =>\n resolveGroupPolicy(account.groupPolicy, 'allowlist'),\n checkAccess: (ctx: ChannelSecurityContext, account: ResolvedFeishuAccount, _cfg: Config) => {\n const isDm = !ctx.isGroup;\n const frameworkAllowFrom = readFrameworkAllowFromList(account.accountId);\n const baseAllowFrom = isDm ? account.allowFrom : account.groupAllowFrom ?? account.allowFrom;\n const allowFrom = [...(baseAllowFrom ?? []), ...frameworkAllowFrom];\n if (isDm) {\n return evaluateAccess({\n context: {\n channel: 'feishu',\n accountId: account.accountId,\n chatId: ctx.chatId,\n senderId: ctx.senderId,\n senderName: ctx.senderName,\n isGroup: false,\n isDm: true,\n },\n dmPolicy: account.dmPolicy,\n allowFrom,\n });\n }\n return evaluateAccess({\n context: {\n channel: 'feishu',\n accountId: account.accountId,\n chatId: ctx.chatId,\n senderId: ctx.senderId,\n senderName: ctx.senderName,\n isGroup: true,\n isDm: false,\n },\n groupPolicy: account.groupPolicy,\n allowFrom,\n });\n },\n };\n\n outbound: ChannelOutboundAdapter = createFeishuOutboundAdapter();\n\n streaming = createFeishuStreamingAdapter(() => this.cfg);\n\n status: ChannelStatusAdapter<ResolvedFeishuAccount> = createFeishuStatusAdapter();\n\n doctor: ChannelDoctorAdapter = createFeishuDoctorAdapter();\n\n directory = {\n resolveDisplayName: async (params) => {\n const { createFeishuDirectoryAdapter } = await import('./directory/directory-adapter.js');\n return createFeishuDirectoryAdapter().resolveDisplayName?.(params);\n },\n };\n\n actions: ChannelMessageActionAdapter = {\n handleAction: handleFeishuChannelMessageAction,\n };\n\n agentTools = [\n {\n name: 'feishu_read',\n description: 'Read a Feishu message by messageId (debug/utility).',\n execute: async (toolCtx, args) => {\n const messageId = typeof (args as any)?.messageId === 'string' ? (args as any).messageId : toolCtx.messageId;\n if (!messageId) throw new Error('feishu_read requires messageId');\n const { getMessageFeishu } = await import('./outbound/actions.js');\n return await getMessageFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId });\n },\n },\n {\n name: 'feishu_edit',\n description: 'Edit a Feishu message by messageId.',\n execute: async (toolCtx, args) => {\n const messageId = typeof (args as any)?.messageId === 'string' ? (args as any).messageId : toolCtx.messageId;\n const text = typeof (args as any)?.text === 'string' ? (args as any).text : '';\n if (!messageId) throw new Error('feishu_edit requires messageId');\n if (!text.trim()) throw new Error('feishu_edit requires text');\n const { editMessageFeishu } = await import('./outbound/actions.js');\n return await editMessageFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId, text });\n },\n },\n {\n name: 'feishu_scopes_probe',\n description: 'Probe Feishu credentials/scopes (placeholder for full docs/wiki/drive tools).',\n execute: async (toolCtx) => {\n const { feishuWhoAmI } = await import('./tools/tools.js');\n return await feishuWhoAmI({ cfg: this.cfg, accountId: toolCtx.accountId });\n },\n },\n {\n name: 'feishu_react',\n description: 'Add/remove/list reactions for a message.',\n execute: async (toolCtx, args) => {\n const a = args as any;\n const messageId = typeof a?.messageId === 'string' ? a.messageId : toolCtx.messageId;\n if (!messageId) throw new Error('feishu_react requires messageId');\n\n const account = resolveFeishuAccount(this.cfg, toolCtx.accountId ?? 'default');\n const enabled = (account.actions as any)?.reactions !== false;\n if (!enabled) {\n throw new Error('Feishu reactions are disabled via channels.feishu.actions.reactions');\n }\n\n if (a?.list === true) {\n const { listReactionsFeishu } = await import('./outbound/actions.js');\n return await listReactionsFeishu({\n cfg: this.cfg,\n accountId: toolCtx.accountId,\n messageId,\n emojiType: typeof a?.emojiType === 'string' ? a.emojiType : undefined,\n });\n }\n\n if (a?.remove === true) {\n const reactionId = typeof a?.reactionId === 'string' ? a.reactionId : '';\n if (!reactionId) throw new Error('feishu_react remove requires reactionId');\n const { removeReactionFeishu } = await import('./outbound/actions.js');\n return await removeReactionFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId, reactionId });\n }\n\n const emojiType = typeof a?.emojiType === 'string' ? a.emojiType : '';\n if (!emojiType) throw new Error('feishu_react requires emojiType');\n const { addReactionFeishu } = await import('./outbound/actions.js');\n return await addReactionFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId, emojiType });\n },\n },\n {\n name: 'feishu_pins',\n description: 'Pin/unpin/list pins for a chat.',\n execute: async (toolCtx, args) => {\n const a = args as any;\n const action = typeof a?.action === 'string' ? a.action : '';\n if (action === 'list') {\n const chatId = typeof a?.chatId === 'string' ? a.chatId : toolCtx.chatId;\n if (!chatId) throw new Error('feishu_pins list requires chatId');\n const { listPinsFeishu } = await import('./outbound/actions.js');\n return await listPinsFeishu({\n cfg: this.cfg,\n accountId: toolCtx.accountId,\n chatId,\n startTime: typeof a?.startTime === 'string' ? a.startTime : undefined,\n endTime: typeof a?.endTime === 'string' ? a.endTime : undefined,\n pageSize: typeof a?.pageSize === 'number' ? a.pageSize : undefined,\n pageToken: typeof a?.pageToken === 'string' ? a.pageToken : undefined,\n });\n }\n const messageId = typeof a?.messageId === 'string' ? a.messageId : toolCtx.messageId;\n if (!messageId) throw new Error('feishu_pins requires messageId');\n if (action === 'pin') {\n const { pinMessageFeishu } = await import('./outbound/actions.js');\n return await pinMessageFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId });\n }\n if (action === 'unpin') {\n const { unpinMessageFeishu } = await import('./outbound/actions.js');\n return await unpinMessageFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId });\n }\n throw new Error('feishu_pins requires action: pin | unpin | list');\n },\n },\n ];\n\n async init(options: ChannelPluginInitOptions): Promise<void> {\n this.bus = options.bus;\n this.cfg = options.config;\n const defaultDebounce = this.defaults.queue?.debounceMs ?? 350;\n this.inboundPipeline = createFeishuInboundPipeline({\n bus: this.bus,\n defaultDebounceMs: defaultDebounce,\n onError: (err, items) => {\n log.error({ err, count: items.length }, 'Feishu inbound pipeline flush failed');\n },\n });\n\n await this.registerWorkflowProgressCapability();\n\n log.debug('Feishu plugin initialized');\n }\n\n private async registerWorkflowProgressCapability(): Promise<void> {\n // Lazy-load workflow progress so Feishu runtime startup does not pull the\n // agent/workflow/provider graph into Electron's dynamic extension path.\n const [{ getWorkflowProgressBroker }, { createFeishuWorkflowProgressCapability }] = await Promise.all([\n import('@xopcai/xopc/agent/workflow/progress-broker.js'),\n import('./workflow-progress.js'),\n ]);\n this.workflowProgressUnregister = getWorkflowProgressBroker().registerChannel(\n createFeishuWorkflowProgressCapability({ getConfig: () => this.cfg }),\n );\n }\n\n async start(options?: ChannelPluginStartOptions): Promise<void> {\n const section = this.cfg.channels?.feishu as FeishuConfig | undefined;\n if (!section || section.enabled !== true) {\n return;\n }\n\n const ids = options?.accountId ? [options.accountId] : listFeishuAccountIds(this.cfg);\n for (const accountId of ids) {\n const account = resolveFeishuAccount(this.cfg, accountId);\n if (!account.enabled || !account.configured) continue;\n if (this.abortControllers.has(accountId)) continue;\n\n const ac = new AbortController();\n this.abortControllers.set(accountId, ac);\n\n const defaultDebounce = this.defaults.queue?.debounceMs ?? 350;\n const enqueueInbound = (work: FeishuInboundWork) => {\n const p = this.inboundPipeline;\n if (!p) {\n return this.bus.publishInbound(work.inbound);\n }\n return p.enqueue(work);\n };\n\n const monitorDeps = {\n account,\n config: this.cfg,\n enqueueInbound,\n inboundDebounceDefaultMs: defaultDebounce,\n abortSignal: ac.signal,\n security: {\n checkAccess: (ctx: ChannelSecurityContext) => this.security.checkAccess?.(ctx, account, this.cfg),\n },\n };\n const runner =\n account.connectionMode === 'webhook'\n ? (await import('./transport/webhook/monitor.js')).createFeishuWebhookMonitor(monitorDeps)\n : (await import('./transport/socket-mode/monitor.js')).createFeishuSocketModeMonitor(monitorDeps);\n\n void runner.run().catch((err) => {\n if ((err as { name?: string } | undefined)?.name === 'AbortError') {\n log.debug({ accountId }, 'Feishu monitor stopped');\n return;\n }\n log.error({ err, accountId }, 'Feishu monitor exited with error');\n });\n\n log.info({ accountId, mode: account.connectionMode }, 'Feishu monitor started');\n }\n }\n\n async stop(accountId?: string): Promise<void> {\n try {\n await this.inboundPipeline?.flushAll();\n } catch (err) {\n log.warn({ err }, 'Feishu inbound pipeline flushAll failed');\n }\n const ids = accountId ? [accountId] : [...this.abortControllers.keys()];\n for (const id of ids) {\n const ac = this.abortControllers.get(id);\n if (ac) {\n ac.abort();\n this.abortControllers.delete(id);\n }\n }\n // Only unregister the broker capability on a full stop. Per-account stops\n // still leave other accounts capable of delivering progress through the\n // same registered cap.\n if (!accountId) {\n this.workflowProgressUnregister?.();\n this.workflowProgressUnregister = null;\n }\n }\n\n channelIsRunning(cfg: Config): boolean {\n const ids = listFeishuAccountIds(cfg);\n return ids.some((id) => {\n const a = resolveFeishuAccount(cfg, id);\n return a.enabled !== false && a.configured && this.abortControllers.has(id);\n });\n }\n\n async onConfigUpdated(cfg: Config): Promise<void> {\n const prev = this.cfg.channels?.feishu as unknown;\n const next = cfg.channels?.feishu as { enabled?: boolean } | undefined;\n const channelOff = !next || next.enabled !== true;\n\n if (channelOff) {\n this.cfg = cfg;\n await this.stop();\n return;\n }\n\n this.cfg = cfg;\n\n if (isDeepStrictEqual(prev, next) && this.channelIsRunning(cfg)) {\n return;\n }\n\n await this.stop();\n await this.start();\n }\n}\n\nexport const feishuPlugin = new FeishuChannelPlugin();\n"],"mappings":"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"}
@@ -4,7 +4,8 @@
4
4
  * Implements hierarchical access control for groups and topics
5
5
  *
6
6
  */
7
- import type { NormalizedAllowFrom, AllowFromMatch, GroupAccessResult, TelegramGroupConfig, TelegramTopicConfig, GroupPolicy } from '@xopcai/xopc/channels/channel-domain.js';
7
+ import type { NormalizedAllowFrom, AllowFromMatch, GroupAccessResult, GroupPolicy } from '@xopcai/xopc/channels/channel-domain.js';
8
+ import type { TelegramGroupConfig, TelegramTopicConfig } from './config-types.js';
8
9
  /**
9
10
  * Normalize allowFrom list to structured format
10
11
  */
@@ -1 +1 @@
1
- {"version":3,"file":"access-control.js","names":[],"sources":["../../../../extensions/telegram/src/access-control.ts"],"sourcesContent":["/**\n * Channel Access Control Utilities\n * \n * Implements hierarchical access control for groups and topics\n *\n */\n\nimport type {\n NormalizedAllowFrom,\n AllowFromMatch,\n GroupAccessResult,\n TelegramGroupConfig,\n TelegramTopicConfig,\n GroupPolicy,\n} from '@xopcai/xopc/channels/channel-domain.js';\n\n// ============================================\n// AllowFrom Normalization\n// ============================================\n\n/**\n * Normalize allowFrom list to structured format\n */\nexport function normalizeAllowFrom(list?: Array<string | number>): NormalizedAllowFrom {\n const entries = (list ?? []).map((value) => String(value).trim()).filter(Boolean);\n const hasWildcard = entries.includes('*');\n const normalized = entries\n .filter((value) => value !== '*')\n .map((value) => value.replace(/^(telegram|tg):/i, ''));\n const ids = normalized.filter((value) => /^\\d+$/.test(value));\n \n return {\n entries: ids,\n hasWildcard,\n hasEntries: ids.length > 0,\n };\n}\n\n/**\n * Normalize allowFrom combining config and store\n */\nexport function normalizeAllowFromWithStore(params: {\n allowFrom?: Array<string | number>;\n storeAllowFrom?: string[];\n}): NormalizedAllowFrom {\n const combined = [\n ...(params.allowFrom ?? []),\n ...(params.storeAllowFrom ?? []),\n ]\n .map((value) => String(value).trim())\n .filter(Boolean);\n return normalizeAllowFrom(combined);\n}\n\n/**\n * Check if sender is allowed based on normalized allowFrom\n */\nexport function isSenderAllowed(params: {\n allow: NormalizedAllowFrom;\n senderId?: string;\n senderUsername?: string;\n}): boolean {\n const { allow, senderId } = params;\n \n // No entries = allow all\n if (!allow.hasEntries) {\n return true;\n }\n \n // Wildcard = allow all\n if (allow.hasWildcard) {\n return true;\n }\n \n // Check if sender ID is in allowlist\n if (senderId && allow.entries.includes(senderId)) {\n return true;\n }\n \n return false;\n}\n\n/**\n * Resolve sender allowlist match\n */\nexport function resolveSenderAllowMatch(params: {\n allow: NormalizedAllowFrom;\n senderId?: string;\n senderUsername?: string;\n}): AllowFromMatch {\n const { allow, senderId } = params;\n \n if (allow.hasWildcard) {\n return { allowed: true, matchKey: '*', matchSource: 'wildcard' };\n }\n \n if (!allow.hasEntries) {\n return { allowed: false };\n }\n \n if (senderId && allow.entries.includes(senderId)) {\n return { allowed: true, matchKey: senderId, matchSource: 'id' };\n }\n \n return { allowed: false };\n}\n\n// ============================================\n// Group Access Control\n// ============================================\n\n/**\n * Evaluate base group access (group/topic enabled, allowFrom override)\n */\nexport function evaluateGroupBaseAccess(params: {\n isGroup: boolean;\n groupConfig?: TelegramGroupConfig;\n topicConfig?: TelegramTopicConfig;\n hasGroupAllowOverride: boolean;\n effectiveGroupAllow: NormalizedAllowFrom;\n senderId?: string;\n senderUsername?: string;\n}): GroupAccessResult {\n // Not a group = always allowed\n if (!params.isGroup) {\n return { allowed: true };\n }\n \n // Check if group is disabled\n if (params.groupConfig?.enabled === false) {\n return { allowed: false, reason: 'group-disabled' };\n }\n \n // Check if topic is disabled\n if (params.topicConfig?.enabled === false) {\n return { allowed: false, reason: 'topic-disabled' };\n }\n \n // Check allowFrom override if present\n if (params.hasGroupAllowOverride) {\n const senderId = params.senderId ?? '';\n if (!senderId) {\n return { allowed: false, reason: 'unauthorized' };\n }\n \n const allowed = isSenderAllowed({\n allow: params.effectiveGroupAllow,\n senderId,\n senderUsername: params.senderUsername,\n });\n \n if (!allowed) {\n return { allowed: false, reason: 'unauthorized' };\n }\n }\n \n return { allowed: true };\n}\n\n/**\n * Evaluate group policy access (open/disabled/allowlist)\n */\nexport function evaluateGroupPolicyAccess(params: {\n isGroup: boolean;\n groupPolicy: GroupPolicy;\n effectiveGroupAllow: NormalizedAllowFrom;\n senderId?: string;\n senderUsername?: string;\n}): GroupAccessResult {\n const { isGroup, groupPolicy, effectiveGroupAllow, senderId, senderUsername } = params;\n \n // Not a group or policy not enforced = always allowed\n if (!isGroup) {\n return { allowed: true, groupPolicy };\n }\n \n // Disabled policy = block all\n if (groupPolicy === 'disabled') {\n return { allowed: false, reason: 'policy-blocked', groupPolicy };\n }\n \n // Open policy = allow all (mention gating handled separately)\n if (groupPolicy === 'open') {\n return { allowed: true, groupPolicy };\n }\n \n // Allowlist policy = check allowFrom\n if (groupPolicy === 'allowlist') {\n if (!effectiveGroupAllow.hasEntries && !effectiveGroupAllow.hasWildcard) {\n return { allowed: false, reason: 'policy-blocked', groupPolicy };\n }\n \n const allowed = isSenderAllowed({\n allow: effectiveGroupAllow,\n senderId,\n senderUsername,\n });\n \n if (!allowed) {\n return { allowed: false, reason: 'policy-blocked', groupPolicy };\n }\n }\n \n return { allowed: true, groupPolicy };\n}\n\n/**\n * Resolve group policy from config hierarchy\n */\nexport function resolveGroupPolicy(params: {\n topicConfig?: TelegramTopicConfig;\n groupConfig?: TelegramGroupConfig;\n accountGroupPolicy?: GroupPolicy;\n defaultGroupPolicy?: GroupPolicy;\n}): GroupPolicy {\n // Priority: topic > group > account > default\n return (\n params.topicConfig?.groupPolicy ??\n params.groupConfig?.groupPolicy ??\n params.accountGroupPolicy ??\n params.defaultGroupPolicy ??\n 'open'\n );\n}\n\n/**\n * Resolve requireMention from config hierarchy\n */\nexport function resolveRequireMention(params: {\n topicConfig?: TelegramTopicConfig;\n groupConfig?: TelegramGroupConfig;\n defaultRequireMention?: boolean;\n}): boolean {\n return (\n params.topicConfig?.requireMention ??\n params.groupConfig?.requireMention ??\n params.defaultRequireMention ??\n true\n );\n}\n\n// ============================================\n// Mention Detection\n// ============================================\n\n/**\n * Build mention regexes for bot username\n */\nexport function buildMentionRegex(botUsername: string): RegExp {\n const escaped = botUsername.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n return new RegExp(`@${escaped}`, 'i');\n}\n\n/**\n * Check if message has bot mention (text or entity)\n */\nexport function hasBotMention(params: {\n botUsername: string;\n text?: string;\n entities?: Array<{ type: string; offset: number; length: number }>;\n}): boolean {\n const { botUsername, text = '', entities = [] } = params;\n const botUsernameLower = botUsername.toLowerCase();\n const textLower = text.toLowerCase();\n \n // Check text mention\n if (textLower.includes(`@${botUsernameLower}`)) {\n return true;\n }\n \n // Check entity mentions\n for (const ent of entities) {\n if (ent.type === 'mention') {\n const mentionText = text.slice(ent.offset, ent.offset + ent.length);\n if (mentionText.toLowerCase() === `@${botUsernameLower}`) {\n return true;\n }\n }\n }\n \n return false;\n}\n\n/**\n * Remove bot mention from text\n */\nexport function removeBotMention(text: string, botUsername: string): string {\n const botUsernameLower = botUsername.toLowerCase();\n return text.replace(new RegExp(`@${botUsernameLower}\\\\s*`, 'gi'), '').trim();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"access-control.js","names":[],"sources":["../../../../extensions/telegram/src/access-control.ts"],"sourcesContent":["/**\n * Channel Access Control Utilities\n * \n * Implements hierarchical access control for groups and topics\n *\n */\n\nimport type {\n NormalizedAllowFrom,\n AllowFromMatch,\n GroupAccessResult,\n GroupPolicy,\n} from '@xopcai/xopc/channels/channel-domain.js';\nimport type { TelegramGroupConfig, TelegramTopicConfig } from './config-types.js';\n\n// ============================================\n// AllowFrom Normalization\n// ============================================\n\n/**\n * Normalize allowFrom list to structured format\n */\nexport function normalizeAllowFrom(list?: Array<string | number>): NormalizedAllowFrom {\n const entries = (list ?? []).map((value) => String(value).trim()).filter(Boolean);\n const hasWildcard = entries.includes('*');\n const normalized = entries\n .filter((value) => value !== '*')\n .map((value) => value.replace(/^(telegram|tg):/i, ''));\n const ids = normalized.filter((value) => /^\\d+$/.test(value));\n \n return {\n entries: ids,\n hasWildcard,\n hasEntries: ids.length > 0,\n };\n}\n\n/**\n * Normalize allowFrom combining config and store\n */\nexport function normalizeAllowFromWithStore(params: {\n allowFrom?: Array<string | number>;\n storeAllowFrom?: string[];\n}): NormalizedAllowFrom {\n const combined = [\n ...(params.allowFrom ?? []),\n ...(params.storeAllowFrom ?? []),\n ]\n .map((value) => String(value).trim())\n .filter(Boolean);\n return normalizeAllowFrom(combined);\n}\n\n/**\n * Check if sender is allowed based on normalized allowFrom\n */\nexport function isSenderAllowed(params: {\n allow: NormalizedAllowFrom;\n senderId?: string;\n senderUsername?: string;\n}): boolean {\n const { allow, senderId } = params;\n \n // No entries = allow all\n if (!allow.hasEntries) {\n return true;\n }\n \n // Wildcard = allow all\n if (allow.hasWildcard) {\n return true;\n }\n \n // Check if sender ID is in allowlist\n if (senderId && allow.entries.includes(senderId)) {\n return true;\n }\n \n return false;\n}\n\n/**\n * Resolve sender allowlist match\n */\nexport function resolveSenderAllowMatch(params: {\n allow: NormalizedAllowFrom;\n senderId?: string;\n senderUsername?: string;\n}): AllowFromMatch {\n const { allow, senderId } = params;\n \n if (allow.hasWildcard) {\n return { allowed: true, matchKey: '*', matchSource: 'wildcard' };\n }\n \n if (!allow.hasEntries) {\n return { allowed: false };\n }\n \n if (senderId && allow.entries.includes(senderId)) {\n return { allowed: true, matchKey: senderId, matchSource: 'id' };\n }\n \n return { allowed: false };\n}\n\n// ============================================\n// Group Access Control\n// ============================================\n\n/**\n * Evaluate base group access (group/topic enabled, allowFrom override)\n */\nexport function evaluateGroupBaseAccess(params: {\n isGroup: boolean;\n groupConfig?: TelegramGroupConfig;\n topicConfig?: TelegramTopicConfig;\n hasGroupAllowOverride: boolean;\n effectiveGroupAllow: NormalizedAllowFrom;\n senderId?: string;\n senderUsername?: string;\n}): GroupAccessResult {\n // Not a group = always allowed\n if (!params.isGroup) {\n return { allowed: true };\n }\n \n // Check if group is disabled\n if (params.groupConfig?.enabled === false) {\n return { allowed: false, reason: 'group-disabled' };\n }\n \n // Check if topic is disabled\n if (params.topicConfig?.enabled === false) {\n return { allowed: false, reason: 'topic-disabled' };\n }\n \n // Check allowFrom override if present\n if (params.hasGroupAllowOverride) {\n const senderId = params.senderId ?? '';\n if (!senderId) {\n return { allowed: false, reason: 'unauthorized' };\n }\n \n const allowed = isSenderAllowed({\n allow: params.effectiveGroupAllow,\n senderId,\n senderUsername: params.senderUsername,\n });\n \n if (!allowed) {\n return { allowed: false, reason: 'unauthorized' };\n }\n }\n \n return { allowed: true };\n}\n\n/**\n * Evaluate group policy access (open/disabled/allowlist)\n */\nexport function evaluateGroupPolicyAccess(params: {\n isGroup: boolean;\n groupPolicy: GroupPolicy;\n effectiveGroupAllow: NormalizedAllowFrom;\n senderId?: string;\n senderUsername?: string;\n}): GroupAccessResult {\n const { isGroup, groupPolicy, effectiveGroupAllow, senderId, senderUsername } = params;\n \n // Not a group or policy not enforced = always allowed\n if (!isGroup) {\n return { allowed: true, groupPolicy };\n }\n \n // Disabled policy = block all\n if (groupPolicy === 'disabled') {\n return { allowed: false, reason: 'policy-blocked', groupPolicy };\n }\n \n // Open policy = allow all (mention gating handled separately)\n if (groupPolicy === 'open') {\n return { allowed: true, groupPolicy };\n }\n \n // Allowlist policy = check allowFrom\n if (groupPolicy === 'allowlist') {\n if (!effectiveGroupAllow.hasEntries && !effectiveGroupAllow.hasWildcard) {\n return { allowed: false, reason: 'policy-blocked', groupPolicy };\n }\n \n const allowed = isSenderAllowed({\n allow: effectiveGroupAllow,\n senderId,\n senderUsername,\n });\n \n if (!allowed) {\n return { allowed: false, reason: 'policy-blocked', groupPolicy };\n }\n }\n \n return { allowed: true, groupPolicy };\n}\n\n/**\n * Resolve group policy from config hierarchy\n */\nexport function resolveGroupPolicy(params: {\n topicConfig?: TelegramTopicConfig;\n groupConfig?: TelegramGroupConfig;\n accountGroupPolicy?: GroupPolicy;\n defaultGroupPolicy?: GroupPolicy;\n}): GroupPolicy {\n // Priority: topic > group > account > default\n return (\n params.topicConfig?.groupPolicy ??\n params.groupConfig?.groupPolicy ??\n params.accountGroupPolicy ??\n params.defaultGroupPolicy ??\n 'open'\n );\n}\n\n/**\n * Resolve requireMention from config hierarchy\n */\nexport function resolveRequireMention(params: {\n topicConfig?: TelegramTopicConfig;\n groupConfig?: TelegramGroupConfig;\n defaultRequireMention?: boolean;\n}): boolean {\n return (\n params.topicConfig?.requireMention ??\n params.groupConfig?.requireMention ??\n params.defaultRequireMention ??\n true\n );\n}\n\n// ============================================\n// Mention Detection\n// ============================================\n\n/**\n * Build mention regexes for bot username\n */\nexport function buildMentionRegex(botUsername: string): RegExp {\n const escaped = botUsername.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n return new RegExp(`@${escaped}`, 'i');\n}\n\n/**\n * Check if message has bot mention (text or entity)\n */\nexport function hasBotMention(params: {\n botUsername: string;\n text?: string;\n entities?: Array<{ type: string; offset: number; length: number }>;\n}): boolean {\n const { botUsername, text = '', entities = [] } = params;\n const botUsernameLower = botUsername.toLowerCase();\n const textLower = text.toLowerCase();\n \n // Check text mention\n if (textLower.includes(`@${botUsernameLower}`)) {\n return true;\n }\n \n // Check entity mentions\n for (const ent of entities) {\n if (ent.type === 'mention') {\n const mentionText = text.slice(ent.offset, ent.offset + ent.length);\n if (mentionText.toLowerCase() === `@${botUsernameLower}`) {\n return true;\n }\n }\n }\n \n return false;\n}\n\n/**\n * Remove bot mention from text\n */\nexport function removeBotMention(text: string, botUsername: string): string {\n const botUsernameLower = botUsername.toLowerCase();\n return text.replace(new RegExp(`@${botUsernameLower}\\\\s*`, 'gi'), '').trim();\n}\n"],"mappings":"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"}
@@ -9,8 +9,9 @@
9
9
  * - Bot username mapping
10
10
  */
11
11
  import type { Bot, Context } from 'grammy';
12
- import type { TelegramAccountConfig, ChannelStatus } from '@xopcai/xopc/channels/channel-domain.js';
12
+ import type { ChannelStatus } from '@xopcai/xopc/channels/channel-domain.js';
13
13
  import type { TelegramPollingSession } from './polling-session.js';
14
+ import type { TelegramAccountConfig } from './config-types.js';
14
15
  export declare class TelegramAccountManager {
15
16
  private accounts;
16
17
  private bots;
@@ -1 +1 @@
1
- {"version":3,"file":"account-manager.js","names":[],"sources":["../../../../extensions/telegram/src/account-manager.ts"],"sourcesContent":["/**\n * Telegram Account Manager\n *\n * Manages multiple Telegram bot accounts, including:\n * - Account configuration storage\n * - Bot instance management\n * - Runner lifecycle\n * - Status tracking\n * - Bot username mapping\n */\n\nimport type { Bot, Context } from 'grammy';\nimport type { TelegramAccountConfig, ChannelStatus } from '@xopcai/xopc/channels/channel-domain.js';\nimport type { TelegramPollingSession } from './polling-session.js';\n\nexport class TelegramAccountManager {\n private accounts = new Map<string, TelegramAccountConfig>();\n private bots = new Map<string, Bot>();\n private pollingSessions = new Map<string, TelegramPollingSession>();\n private statuses = new Map<string, ChannelStatus>();\n private botUsernames = new Map<string, string>();\n private startingAccounts = new Set<string>();\n\n registerAccount(account: TelegramAccountConfig): void {\n this.accounts.set(account.accountId, account);\n this.statuses.set(account.accountId, {\n accountId: account.accountId,\n running: false,\n mode: 'stopped',\n });\n }\n\n getAccount(accountId: string): TelegramAccountConfig | undefined {\n return this.accounts.get(accountId);\n }\n\n getAllAccounts(): TelegramAccountConfig[] {\n return Array.from(this.accounts.values());\n }\n\n /** Clear all accounts and runtime state (used when reloading channel config). */\n reset(): void {\n this.accounts.clear();\n this.bots.clear();\n this.pollingSessions.clear();\n this.statuses.clear();\n this.botUsernames.clear();\n this.startingAccounts.clear();\n }\n\n registerBot(accountId: string, bot: Bot): void {\n this.bots.set(accountId, bot);\n }\n\n getBot(accountId: string): Bot | undefined {\n return this.bots.get(accountId);\n }\n\n registerPollingSession(accountId: string, session: TelegramPollingSession): void {\n this.pollingSessions.set(accountId, session);\n }\n\n async stopRunner(accountId: string): Promise<void> {\n const session = this.pollingSessions.get(accountId);\n const bot = this.bots.get(accountId);\n\n if (session) {\n await session.stop();\n this.pollingSessions.delete(accountId);\n }\n\n if (bot) {\n bot.stop();\n this.bots.delete(accountId);\n }\n \n this.startingAccounts.delete(accountId);\n \n // Update status to stopped\n this.updateStatus({\n accountId,\n running: false,\n mode: 'stopped',\n });\n }\n\n isStarting(accountId: string): boolean {\n return this.startingAccounts.has(accountId);\n }\n\n markStarting(accountId: string): void {\n this.startingAccounts.add(accountId);\n }\n\n markStartComplete(accountId: string): void {\n this.startingAccounts.delete(accountId);\n }\n\n isRunning(accountId: string): boolean {\n const status = this.statuses.get(accountId);\n return status?.running ?? false;\n }\n\n updateStatus(status: ChannelStatus): void {\n this.statuses.set(status.accountId, status);\n }\n\n getStatus(accountId: string): ChannelStatus | undefined {\n return this.statuses.get(accountId);\n }\n\n setBotUsername(accountId: string, username: string): void {\n this.botUsernames.set(accountId, username);\n }\n\n getBotUsername(accountId: string): string | undefined {\n return this.botUsernames.get(accountId);\n }\n\n /** Map the bot handling this update to a configured account id (session keys must match inbound). */\n resolveAccountIdFromContext(ctx: Context): string {\n const botId = ctx.me?.id;\n if (botId == null) {\n const accounts = this.getAllAccounts();\n return accounts[0]?.accountId ?? 'default';\n }\n for (const [accountId, bot] of this.bots) {\n if (bot.botInfo?.id === botId) return accountId;\n }\n const accounts = this.getAllAccounts();\n if (accounts.length === 1) return accounts[0]!.accountId;\n return 'default';\n }\n}\n"],"mappings":";AAeA,IAAa,yBAAb,MAAoC;CAClC,2BAAmB,IAAI,KAAoC;CAC3D,uBAAe,IAAI,KAAkB;CACrC,kCAA0B,IAAI,KAAqC;CACnE,2BAAmB,IAAI,KAA4B;CACnD,+BAAuB,IAAI,KAAqB;CAChD,mCAA2B,IAAI,KAAa;CAE5C,gBAAgB,SAAsC;AACpD,OAAK,SAAS,IAAI,QAAQ,WAAW,QAAQ;AAC7C,OAAK,SAAS,IAAI,QAAQ,WAAW;GACnC,WAAW,QAAQ;GACnB,SAAS;GACT,MAAM;GACP,CAAC;;CAGJ,WAAW,WAAsD;AAC/D,SAAO,KAAK,SAAS,IAAI,UAAU;;CAGrC,iBAA0C;AACxC,SAAO,MAAM,KAAK,KAAK,SAAS,QAAQ,CAAC;;;CAI3C,QAAc;AACZ,OAAK,SAAS,OAAO;AACrB,OAAK,KAAK,OAAO;AACjB,OAAK,gBAAgB,OAAO;AAC5B,OAAK,SAAS,OAAO;AACrB,OAAK,aAAa,OAAO;AACzB,OAAK,iBAAiB,OAAO;;CAG/B,YAAY,WAAmB,KAAgB;AAC7C,OAAK,KAAK,IAAI,WAAW,IAAI;;CAG/B,OAAO,WAAoC;AACzC,SAAO,KAAK,KAAK,IAAI,UAAU;;CAGjC,uBAAuB,WAAmB,SAAuC;AAC/E,OAAK,gBAAgB,IAAI,WAAW,QAAQ;;CAG9C,MAAM,WAAW,WAAkC;EACjD,MAAM,UAAU,KAAK,gBAAgB,IAAI,UAAU;EACnD,MAAM,MAAM,KAAK,KAAK,IAAI,UAAU;AAEpC,MAAI,SAAS;AACX,SAAM,QAAQ,MAAM;AACpB,QAAK,gBAAgB,OAAO,UAAU;;AAGxC,MAAI,KAAK;AACP,OAAI,MAAM;AACV,QAAK,KAAK,OAAO,UAAU;;AAG7B,OAAK,iBAAiB,OAAO,UAAU;AAGvC,OAAK,aAAa;GAChB;GACA,SAAS;GACT,MAAM;GACP,CAAC;;CAGJ,WAAW,WAA4B;AACrC,SAAO,KAAK,iBAAiB,IAAI,UAAU;;CAG7C,aAAa,WAAyB;AACpC,OAAK,iBAAiB,IAAI,UAAU;;CAGtC,kBAAkB,WAAyB;AACzC,OAAK,iBAAiB,OAAO,UAAU;;CAGzC,UAAU,WAA4B;AAEpC,SADe,KAAK,SAAS,IAAI,UACpB,EAAE,WAAW;;CAG5B,aAAa,QAA6B;AACxC,OAAK,SAAS,IAAI,OAAO,WAAW,OAAO;;CAG7C,UAAU,WAA8C;AACtD,SAAO,KAAK,SAAS,IAAI,UAAU;;CAGrC,eAAe,WAAmB,UAAwB;AACxD,OAAK,aAAa,IAAI,WAAW,SAAS;;CAG5C,eAAe,WAAuC;AACpD,SAAO,KAAK,aAAa,IAAI,UAAU;;;CAIzC,4BAA4B,KAAsB;EAChD,MAAM,QAAQ,IAAI,IAAI;AACtB,MAAI,SAAS,KAEX,QADiB,KAAK,gBACP,CAAC,IAAI,aAAa;AAEnC,OAAK,MAAM,CAAC,WAAW,QAAQ,KAAK,KAClC,KAAI,IAAI,SAAS,OAAO,MAAO,QAAO;EAExC,MAAM,WAAW,KAAK,gBAAgB;AACtC,MAAI,SAAS,WAAW,EAAG,QAAO,SAAS,GAAI;AAC/C,SAAO"}
1
+ {"version":3,"file":"account-manager.js","names":[],"sources":["../../../../extensions/telegram/src/account-manager.ts"],"sourcesContent":["/**\n * Telegram Account Manager\n *\n * Manages multiple Telegram bot accounts, including:\n * - Account configuration storage\n * - Bot instance management\n * - Runner lifecycle\n * - Status tracking\n * - Bot username mapping\n */\n\nimport type { Bot, Context } from 'grammy';\nimport type { ChannelStatus } from '@xopcai/xopc/channels/channel-domain.js';\nimport type { TelegramPollingSession } from './polling-session.js';\nimport type { TelegramAccountConfig } from './config-types.js';\n\nexport class TelegramAccountManager {\n private accounts = new Map<string, TelegramAccountConfig>();\n private bots = new Map<string, Bot>();\n private pollingSessions = new Map<string, TelegramPollingSession>();\n private statuses = new Map<string, ChannelStatus>();\n private botUsernames = new Map<string, string>();\n private startingAccounts = new Set<string>();\n\n registerAccount(account: TelegramAccountConfig): void {\n this.accounts.set(account.accountId, account);\n this.statuses.set(account.accountId, {\n accountId: account.accountId,\n running: false,\n mode: 'stopped',\n });\n }\n\n getAccount(accountId: string): TelegramAccountConfig | undefined {\n return this.accounts.get(accountId);\n }\n\n getAllAccounts(): TelegramAccountConfig[] {\n return Array.from(this.accounts.values());\n }\n\n /** Clear all accounts and runtime state (used when reloading channel config). */\n reset(): void {\n this.accounts.clear();\n this.bots.clear();\n this.pollingSessions.clear();\n this.statuses.clear();\n this.botUsernames.clear();\n this.startingAccounts.clear();\n }\n\n registerBot(accountId: string, bot: Bot): void {\n this.bots.set(accountId, bot);\n }\n\n getBot(accountId: string): Bot | undefined {\n return this.bots.get(accountId);\n }\n\n registerPollingSession(accountId: string, session: TelegramPollingSession): void {\n this.pollingSessions.set(accountId, session);\n }\n\n async stopRunner(accountId: string): Promise<void> {\n const session = this.pollingSessions.get(accountId);\n const bot = this.bots.get(accountId);\n\n if (session) {\n await session.stop();\n this.pollingSessions.delete(accountId);\n }\n\n if (bot) {\n bot.stop();\n this.bots.delete(accountId);\n }\n \n this.startingAccounts.delete(accountId);\n \n // Update status to stopped\n this.updateStatus({\n accountId,\n running: false,\n mode: 'stopped',\n });\n }\n\n isStarting(accountId: string): boolean {\n return this.startingAccounts.has(accountId);\n }\n\n markStarting(accountId: string): void {\n this.startingAccounts.add(accountId);\n }\n\n markStartComplete(accountId: string): void {\n this.startingAccounts.delete(accountId);\n }\n\n isRunning(accountId: string): boolean {\n const status = this.statuses.get(accountId);\n return status?.running ?? false;\n }\n\n updateStatus(status: ChannelStatus): void {\n this.statuses.set(status.accountId, status);\n }\n\n getStatus(accountId: string): ChannelStatus | undefined {\n return this.statuses.get(accountId);\n }\n\n setBotUsername(accountId: string, username: string): void {\n this.botUsernames.set(accountId, username);\n }\n\n getBotUsername(accountId: string): string | undefined {\n return this.botUsernames.get(accountId);\n }\n\n /** Map the bot handling this update to a configured account id (session keys must match inbound). */\n resolveAccountIdFromContext(ctx: Context): string {\n const botId = ctx.me?.id;\n if (botId == null) {\n const accounts = this.getAllAccounts();\n return accounts[0]?.accountId ?? 'default';\n }\n for (const [accountId, bot] of this.bots) {\n if (bot.botInfo?.id === botId) return accountId;\n }\n const accounts = this.getAllAccounts();\n if (accounts.length === 1) return accounts[0]!.accountId;\n return 'default';\n }\n}\n"],"mappings":";AAgBA,IAAa,yBAAb,MAAoC;CAClC,2BAAmB,IAAI,KAAoC;CAC3D,uBAAe,IAAI,KAAkB;CACrC,kCAA0B,IAAI,KAAqC;CACnE,2BAAmB,IAAI,KAA4B;CACnD,+BAAuB,IAAI,KAAqB;CAChD,mCAA2B,IAAI,KAAa;CAE5C,gBAAgB,SAAsC;AACpD,OAAK,SAAS,IAAI,QAAQ,WAAW,QAAQ;AAC7C,OAAK,SAAS,IAAI,QAAQ,WAAW;GACnC,WAAW,QAAQ;GACnB,SAAS;GACT,MAAM;GACP,CAAC;;CAGJ,WAAW,WAAsD;AAC/D,SAAO,KAAK,SAAS,IAAI,UAAU;;CAGrC,iBAA0C;AACxC,SAAO,MAAM,KAAK,KAAK,SAAS,QAAQ,CAAC;;;CAI3C,QAAc;AACZ,OAAK,SAAS,OAAO;AACrB,OAAK,KAAK,OAAO;AACjB,OAAK,gBAAgB,OAAO;AAC5B,OAAK,SAAS,OAAO;AACrB,OAAK,aAAa,OAAO;AACzB,OAAK,iBAAiB,OAAO;;CAG/B,YAAY,WAAmB,KAAgB;AAC7C,OAAK,KAAK,IAAI,WAAW,IAAI;;CAG/B,OAAO,WAAoC;AACzC,SAAO,KAAK,KAAK,IAAI,UAAU;;CAGjC,uBAAuB,WAAmB,SAAuC;AAC/E,OAAK,gBAAgB,IAAI,WAAW,QAAQ;;CAG9C,MAAM,WAAW,WAAkC;EACjD,MAAM,UAAU,KAAK,gBAAgB,IAAI,UAAU;EACnD,MAAM,MAAM,KAAK,KAAK,IAAI,UAAU;AAEpC,MAAI,SAAS;AACX,SAAM,QAAQ,MAAM;AACpB,QAAK,gBAAgB,OAAO,UAAU;;AAGxC,MAAI,KAAK;AACP,OAAI,MAAM;AACV,QAAK,KAAK,OAAO,UAAU;;AAG7B,OAAK,iBAAiB,OAAO,UAAU;AAGvC,OAAK,aAAa;GAChB;GACA,SAAS;GACT,MAAM;GACP,CAAC;;CAGJ,WAAW,WAA4B;AACrC,SAAO,KAAK,iBAAiB,IAAI,UAAU;;CAG7C,aAAa,WAAyB;AACpC,OAAK,iBAAiB,IAAI,UAAU;;CAGtC,kBAAkB,WAAyB;AACzC,OAAK,iBAAiB,OAAO,UAAU;;CAGzC,UAAU,WAA4B;AAEpC,SADe,KAAK,SAAS,IAAI,UACpB,EAAE,WAAW;;CAG5B,aAAa,QAA6B;AACxC,OAAK,SAAS,IAAI,OAAO,WAAW,OAAO;;CAG7C,UAAU,WAA8C;AACtD,SAAO,KAAK,SAAS,IAAI,UAAU;;CAGrC,eAAe,WAAmB,UAAwB;AACxD,OAAK,aAAa,IAAI,WAAW,SAAS;;CAG5C,eAAe,WAAuC;AACpD,SAAO,KAAK,aAAa,IAAI,UAAU;;;CAIzC,4BAA4B,KAAsB;EAChD,MAAM,QAAQ,IAAI,IAAI;AACtB,MAAI,SAAS,KAEX,QADiB,KAAK,gBACP,CAAC,IAAI,aAAa;AAEnC,OAAK,MAAM,CAAC,WAAW,QAAQ,KAAK,KAClC,KAAI,IAAI,SAAS,OAAO,MAAO,QAAO;EAExC,MAAM,WAAW,KAAK,gBAAgB;AACtC,MAAI,SAAS,WAAW,EAAG,QAAO,SAAS,GAAI;AAC/C,SAAO"}
@@ -9,36 +9,44 @@ function pickString(input) {
9
9
  return typeof input === "string" ? input.trim() : "";
10
10
  }
11
11
  function pickDmPolicy(input) {
12
- return input === "pairing" || input === "allowlist" || input === "disabled" ? input : "open";
12
+ return input === "open" || input === "pairing" || input === "allowlist" || input === "disabled" ? input : "pairing";
13
13
  }
14
14
  function existingDefaultAccount(config) {
15
15
  const account = ((config.channels?.telegram)?.accounts)?.default;
16
16
  return account && typeof account === "object" && !Array.isArray(account) ? account : {};
17
17
  }
18
+ function existingDefaults(config) {
19
+ const defaults = (config.channels?.telegram)?.defaults;
20
+ return defaults && typeof defaults === "object" && !Array.isArray(defaults) ? defaults : {};
21
+ }
18
22
  function buildTelegramConfig(config, input) {
19
23
  const existing = config.channels?.telegram ?? {};
20
24
  const existingAccounts = existing.accounts ?? {};
21
25
  const previousDefault = existingDefaultAccount(config);
26
+ const previousDefaults = existingDefaults(config);
22
27
  const botToken = pickString(input.botToken) || pickString(previousDefault.botToken);
23
28
  const apiRoot = pickString(input.apiRoot);
24
29
  const proxy = pickString(input.proxy);
25
- const dmPolicy = pickDmPolicy(input.dmPolicy ?? previousDefault.dmPolicy ?? existing.dmPolicy);
30
+ const dmPolicy = pickDmPolicy(input.dmPolicy ?? previousDefault.dmPolicy ?? previousDefaults.dmPolicy);
31
+ const defaults = {
32
+ ...previousDefaults,
33
+ dmPolicy: previousDefaults.dmPolicy ?? "pairing",
34
+ groupPolicy: previousDefaults.groupPolicy ?? "open",
35
+ replyToMode: previousDefaults.replyToMode ?? "off",
36
+ historyLimit: previousDefaults.historyLimit ?? 50,
37
+ textChunkLimit: previousDefaults.textChunkLimit ?? 4e3,
38
+ streaming: previousDefaults.streaming ?? { mode: "partial" }
39
+ };
40
+ if (apiRoot) defaults.apiRoot = apiRoot;
41
+ if (proxy) defaults.proxy = proxy;
26
42
  const defaultAccount = {
27
43
  ...previousDefault,
28
44
  accountId: "default",
29
45
  enabled: true,
30
46
  botToken,
31
47
  dmPolicy,
32
- allowFrom: dmPolicy === "open" ? ["*"] : previousDefault.allowFrom ?? [],
33
- groupPolicy: previousDefault.groupPolicy ?? existing.groupPolicy ?? "open",
34
- replyToMode: previousDefault.replyToMode ?? existing.replyToMode ?? "off",
35
- historyLimit: previousDefault.historyLimit ?? existing.historyLimit ?? 50,
36
- textChunkLimit: previousDefault.textChunkLimit ?? existing.textChunkLimit ?? 4e3,
37
- streaming: previousDefault.streaming ?? existing.streaming ?? { mode: "partial" }
48
+ allowFrom: dmPolicy === "open" ? ["*"] : previousDefault.allowFrom ?? []
38
49
  };
39
- if (apiRoot) defaultAccount.apiRoot = apiRoot;
40
- if (proxy) defaultAccount.proxy = proxy;
41
- const topAllowFrom = dmPolicy === "open" ? ["*"] : existing.allowFrom ?? [];
42
50
  return {
43
51
  ...config,
44
52
  channels: {
@@ -46,15 +54,7 @@ function buildTelegramConfig(config, input) {
46
54
  telegram: {
47
55
  ...existing,
48
56
  enabled: true,
49
- dmPolicy,
50
- allowFrom: topAllowFrom,
51
- groupPolicy: existing.groupPolicy ?? "open",
52
- replyToMode: existing.replyToMode ?? "off",
53
- streaming: existing.streaming ?? { mode: "partial" },
54
- historyLimit: existing.historyLimit ?? 50,
55
- textChunkLimit: existing.textChunkLimit ?? 4e3,
56
- ...apiRoot ? { apiRoot } : {},
57
- ...proxy ? { proxy } : {},
57
+ defaults,
58
58
  accounts: {
59
59
  ...existingAccounts,
60
60
  default: defaultAccount
@@ -65,12 +65,12 @@ function buildTelegramConfig(config, input) {
65
65
  }
66
66
  function formPayload(config, locale) {
67
67
  const zh = isZh(locale);
68
- const telegram = config.channels?.telegram ?? {};
69
68
  const account = existingDefaultAccount(config);
69
+ const defaults = existingDefaults(config);
70
70
  return {
71
71
  type: "form",
72
72
  submitAction: "setup.save",
73
- message: zh ? "填写 Bot Token 后保存即可启用 Telegram 私聊。群聊、多个账号、allowlist 等放在高级配置。" : "Enter a Bot Token and save. Private chat works by default; groups, multi-account, and allowlists live in Advanced configuration.",
73
+ message: zh ? "填写 Bot Token 后保存即可启用 Telegram。默认私聊策略为 pairing;允许列表在账号配置里维护。" : "Enter a Bot Token and save. The default DM policy is pairing; manage allow lists on the account configuration.",
74
74
  schema: {
75
75
  type: "object",
76
76
  properties: {
@@ -105,9 +105,9 @@ function formPayload(config, locale) {
105
105
  },
106
106
  values: {
107
107
  botToken: pickString(account.botToken),
108
- dmPolicy: pickDmPolicy(account.dmPolicy ?? telegram.dmPolicy ?? "open"),
109
- apiRoot: pickString(account.apiRoot ?? telegram.apiRoot),
110
- proxy: pickString(account.proxy ?? telegram.proxy)
108
+ dmPolicy: pickDmPolicy(account.dmPolicy ?? defaults.dmPolicy ?? "pairing"),
109
+ apiRoot: pickString(account.apiRoot ?? defaults.apiRoot),
110
+ proxy: pickString(account.proxy ?? defaults.proxy)
111
111
  }
112
112
  };
113
113
  }
@@ -132,7 +132,7 @@ const telegramGatewaySetupActions = { async runAction({ actionId, input, locale
132
132
  ok: true,
133
133
  payload: {
134
134
  type: "ok",
135
- message: zh ? "Telegram 配置已保存。私聊现在默认可用。" : "Telegram configuration saved. Private chat is enabled by default.",
135
+ message: zh ? "Telegram 配置已保存。私聊将按 pairing/允许列表策略放行。" : "Telegram configuration saved. Private chat now follows the pairing/allow-list policy.",
136
136
  configChanged: true
137
137
  }
138
138
  };
@@ -1 +1 @@
1
- {"version":3,"file":"gateway-setup.js","names":[],"sources":["../../../../../extensions/telegram/src/adapters/gateway-setup.ts"],"sourcesContent":["import type { Config } from '@xopcai/xopc/config/schema.js';\nimport type { ChannelRuntimeActionAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';\n\nfunction isZh(locale: string | undefined): boolean {\n return locale?.toLowerCase().startsWith('zh') === true;\n}\n\nfunction readInput(input: unknown): Record<string, unknown> {\n return input && typeof input === 'object' && !Array.isArray(input)\n ? (input as Record<string, unknown>)\n : {};\n}\n\nfunction pickString(input: unknown): string {\n return typeof input === 'string' ? input.trim() : '';\n}\n\nfunction pickDmPolicy(input: unknown): 'open' | 'pairing' | 'allowlist' | 'disabled' {\n return input === 'pairing' || input === 'allowlist' || input === 'disabled' ? input : 'open';\n}\n\nfunction existingDefaultAccount(config: Config): Record<string, unknown> {\n const telegram = config.channels?.telegram as Record<string, unknown> | undefined;\n const accounts = telegram?.accounts as Record<string, unknown> | undefined;\n const account = accounts?.default;\n return account && typeof account === 'object' && !Array.isArray(account)\n ? (account as Record<string, unknown>)\n : {};\n}\n\nfunction buildTelegramConfig(config: Config, input: Record<string, unknown>): Config {\n const existing = (config.channels?.telegram as Record<string, unknown> | undefined) ?? {};\n const existingAccounts = (existing.accounts as Record<string, unknown> | undefined) ?? {};\n const previousDefault = existingDefaultAccount(config);\n\n const botToken = pickString(input.botToken) || pickString(previousDefault.botToken);\n const apiRoot = pickString(input.apiRoot);\n const proxy = pickString(input.proxy);\n const dmPolicy = pickDmPolicy(input.dmPolicy ?? previousDefault.dmPolicy ?? existing.dmPolicy);\n\n const defaultAccount: Record<string, unknown> = {\n ...previousDefault,\n accountId: 'default',\n enabled: true,\n botToken,\n dmPolicy,\n allowFrom: dmPolicy === 'open' ? ['*'] : (previousDefault.allowFrom ?? []),\n groupPolicy: previousDefault.groupPolicy ?? existing.groupPolicy ?? 'open',\n replyToMode: previousDefault.replyToMode ?? existing.replyToMode ?? 'off',\n historyLimit: previousDefault.historyLimit ?? existing.historyLimit ?? 50,\n textChunkLimit: previousDefault.textChunkLimit ?? existing.textChunkLimit ?? 4000,\n streaming: previousDefault.streaming ?? existing.streaming ?? { mode: 'partial' },\n };\n\n if (apiRoot) defaultAccount.apiRoot = apiRoot;\n if (proxy) defaultAccount.proxy = proxy;\n\n const topAllowFrom = dmPolicy === 'open' ? ['*'] : (existing.allowFrom ?? []);\n\n return {\n ...config,\n channels: {\n ...config.channels,\n telegram: {\n ...existing,\n enabled: true,\n dmPolicy,\n allowFrom: topAllowFrom,\n groupPolicy: existing.groupPolicy ?? 'open',\n replyToMode: existing.replyToMode ?? 'off',\n streaming: existing.streaming ?? { mode: 'partial' },\n historyLimit: existing.historyLimit ?? 50,\n textChunkLimit: existing.textChunkLimit ?? 4000,\n ...(apiRoot ? { apiRoot } : {}),\n ...(proxy ? { proxy } : {}),\n accounts: {\n ...existingAccounts,\n default: defaultAccount,\n },\n },\n },\n };\n}\n\nfunction formPayload(config: Config, locale?: string) {\n const zh = isZh(locale);\n const telegram = (config.channels?.telegram as Record<string, unknown> | undefined) ?? {};\n const account = existingDefaultAccount(config);\n return {\n type: 'form' as const,\n submitAction: 'setup.save',\n message: zh\n ? '填写 Bot Token 后保存即可启用 Telegram 私聊。群聊、多个账号、allowlist 等放在高级配置。'\n : 'Enter a Bot Token and save. Private chat works by default; groups, multi-account, and allowlists live in Advanced configuration.',\n schema: {\n type: 'object',\n properties: {\n botToken: { type: 'string', title: zh ? 'Bot Token' : 'Bot Token', format: 'password' },\n dmPolicy: {\n type: 'string',\n title: zh ? '私聊访问' : 'Private chat access',\n enum: ['open', 'pairing', 'allowlist', 'disabled'],\n default: 'open',\n },\n apiRoot: {\n type: 'string',\n title: zh ? 'API Root(可选)' : 'API root (optional)',\n placeholder: 'https://api.telegram.org',\n },\n proxy: {\n type: 'string',\n title: zh ? '代理(可选)' : 'Proxy (optional)',\n placeholder: 'http://127.0.0.1:7897',\n },\n },\n required: ['botToken'],\n },\n values: {\n botToken: pickString(account.botToken),\n dmPolicy: pickDmPolicy(account.dmPolicy ?? telegram.dmPolicy ?? 'open'),\n apiRoot: pickString(account.apiRoot ?? telegram.apiRoot),\n proxy: pickString(account.proxy ?? telegram.proxy),\n },\n };\n}\n\nexport const telegramGatewaySetupActions: ChannelRuntimeActionAdapter = {\n async runAction({ actionId, input, locale }) {\n const zh = isZh(locale);\n if (actionId === 'setup.start') {\n const { loadConfig } = await import('@xopcai/xopc/config/loader.js');\n return { ok: true, payload: formPayload(loadConfig(), locale) };\n }\n\n if (actionId === 'setup.save') {\n const raw = readInput(input);\n const botToken = pickString(raw.botToken);\n if (!botToken) {\n return { ok: false, message: zh ? 'Bot Token 为必填项。' : 'Bot Token is required.' };\n }\n const { loadConfig, saveConfig } = await import('@xopcai/xopc/config/loader.js');\n await saveConfig(buildTelegramConfig(loadConfig(), raw));\n return {\n ok: true,\n payload: {\n type: 'ok',\n message: zh ? 'Telegram 配置已保存。私聊现在默认可用。' : 'Telegram configuration saved. Private chat is enabled by default.',\n configChanged: true,\n },\n };\n }\n\n return { ok: false, message: `${zh ? '不支持的 Telegram 操作' : 'Unsupported Telegram action'}: ${actionId}` };\n },\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"gateway-setup.js","names":[],"sources":["../../../../../extensions/telegram/src/adapters/gateway-setup.ts"],"sourcesContent":["import type { Config } from '@xopcai/xopc/config/schema.js';\nimport type { ChannelRuntimeActionAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';\n\nfunction isZh(locale: string | undefined): boolean {\n return locale?.toLowerCase().startsWith('zh') === true;\n}\n\nfunction readInput(input: unknown): Record<string, unknown> {\n return input && typeof input === 'object' && !Array.isArray(input)\n ? (input as Record<string, unknown>)\n : {};\n}\n\nfunction pickString(input: unknown): string {\n return typeof input === 'string' ? input.trim() : '';\n}\n\nfunction pickDmPolicy(input: unknown): 'open' | 'pairing' | 'allowlist' | 'disabled' {\n return input === 'open' || input === 'pairing' || input === 'allowlist' || input === 'disabled'\n ? input\n : 'pairing';\n}\n\nfunction existingDefaultAccount(config: Config): Record<string, unknown> {\n const telegram = config.channels?.telegram as Record<string, unknown> | undefined;\n const accounts = telegram?.accounts as Record<string, unknown> | undefined;\n const account = accounts?.default;\n return account && typeof account === 'object' && !Array.isArray(account)\n ? (account as Record<string, unknown>)\n : {};\n}\n\nfunction existingDefaults(config: Config): Record<string, unknown> {\n const telegram = config.channels?.telegram as Record<string, unknown> | undefined;\n const defaults = telegram?.defaults;\n return defaults && typeof defaults === 'object' && !Array.isArray(defaults)\n ? (defaults as Record<string, unknown>)\n : {};\n}\n\nfunction buildTelegramConfig(config: Config, input: Record<string, unknown>): Config {\n const existing = (config.channels?.telegram as Record<string, unknown> | undefined) ?? {};\n const existingAccounts = (existing.accounts as Record<string, unknown> | undefined) ?? {};\n const previousDefault = existingDefaultAccount(config);\n const previousDefaults = existingDefaults(config);\n\n const botToken = pickString(input.botToken) || pickString(previousDefault.botToken);\n const apiRoot = pickString(input.apiRoot);\n const proxy = pickString(input.proxy);\n const dmPolicy = pickDmPolicy(input.dmPolicy ?? previousDefault.dmPolicy ?? previousDefaults.dmPolicy);\n\n const defaults: Record<string, unknown> = {\n ...previousDefaults,\n dmPolicy: previousDefaults.dmPolicy ?? 'pairing',\n groupPolicy: previousDefaults.groupPolicy ?? 'open',\n replyToMode: previousDefaults.replyToMode ?? 'off',\n historyLimit: previousDefaults.historyLimit ?? 50,\n textChunkLimit: previousDefaults.textChunkLimit ?? 4000,\n streaming: previousDefaults.streaming ?? { mode: 'partial' },\n };\n if (apiRoot) defaults.apiRoot = apiRoot;\n if (proxy) defaults.proxy = proxy;\n\n const defaultAccount: Record<string, unknown> = {\n ...previousDefault,\n accountId: 'default',\n enabled: true,\n botToken,\n dmPolicy,\n allowFrom: dmPolicy === 'open' ? ['*'] : (previousDefault.allowFrom ?? []),\n };\n\n return {\n ...config,\n channels: {\n ...config.channels,\n telegram: {\n ...existing,\n enabled: true,\n defaults,\n accounts: {\n ...existingAccounts,\n default: defaultAccount,\n },\n },\n },\n };\n}\n\nfunction formPayload(config: Config, locale?: string) {\n const zh = isZh(locale);\n const account = existingDefaultAccount(config);\n const defaults = existingDefaults(config);\n return {\n type: 'form' as const,\n submitAction: 'setup.save',\n message: zh\n ? '填写 Bot Token 后保存即可启用 Telegram。默认私聊策略为 pairing;允许列表在账号配置里维护。'\n : 'Enter a Bot Token and save. The default DM policy is pairing; manage allow lists on the account configuration.',\n schema: {\n type: 'object',\n properties: {\n botToken: { type: 'string', title: zh ? 'Bot Token' : 'Bot Token', format: 'password' },\n dmPolicy: {\n type: 'string',\n title: zh ? '私聊访问' : 'Private chat access',\n enum: ['open', 'pairing', 'allowlist', 'disabled'],\n default: 'open',\n },\n apiRoot: {\n type: 'string',\n title: zh ? 'API Root(可选)' : 'API root (optional)',\n placeholder: 'https://api.telegram.org',\n },\n proxy: {\n type: 'string',\n title: zh ? '代理(可选)' : 'Proxy (optional)',\n placeholder: 'http://127.0.0.1:7897',\n },\n },\n required: ['botToken'],\n },\n values: {\n botToken: pickString(account.botToken),\n dmPolicy: pickDmPolicy(account.dmPolicy ?? defaults.dmPolicy ?? 'pairing'),\n apiRoot: pickString(account.apiRoot ?? defaults.apiRoot),\n proxy: pickString(account.proxy ?? defaults.proxy),\n },\n };\n}\n\nexport const telegramGatewaySetupActions: ChannelRuntimeActionAdapter = {\n async runAction({ actionId, input, locale }) {\n const zh = isZh(locale);\n if (actionId === 'setup.start') {\n const { loadConfig } = await import('@xopcai/xopc/config/loader.js');\n return { ok: true, payload: formPayload(loadConfig(), locale) };\n }\n\n if (actionId === 'setup.save') {\n const raw = readInput(input);\n const botToken = pickString(raw.botToken);\n if (!botToken) {\n return { ok: false, message: zh ? 'Bot Token 为必填项。' : 'Bot Token is required.' };\n }\n const { loadConfig, saveConfig } = await import('@xopcai/xopc/config/loader.js');\n await saveConfig(buildTelegramConfig(loadConfig(), raw));\n return {\n ok: true,\n payload: {\n type: 'ok',\n message: zh ? 'Telegram 配置已保存。私聊将按 pairing/允许列表策略放行。' : 'Telegram configuration saved. Private chat now follows the pairing/allow-list policy.',\n configChanged: true,\n },\n };\n }\n\n return { ok: false, message: `${zh ? '不支持的 Telegram 操作' : 'Unsupported Telegram action'}: ${actionId}` };\n },\n};\n"],"mappings":"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"}
@@ -123,10 +123,14 @@ async function configureTelegram(config) {
123
123
  const telegramConfig = {
124
124
  ...prev ?? {},
125
125
  enabled: true,
126
- dmPolicy,
127
- groupPolicy,
128
- allowFrom: allowFrom ?? prev?.allowFrom ?? [],
129
- groupAllowFrom: groupAllowFrom ?? prev?.groupAllowFrom ?? [],
126
+ defaults: prev?.defaults && typeof prev.defaults === "object" && !Array.isArray(prev.defaults) ? prev.defaults : {
127
+ dmPolicy: "pairing",
128
+ groupPolicy: "open",
129
+ replyToMode: "off",
130
+ historyLimit: 50,
131
+ textChunkLimit: 4e3,
132
+ streaming: { mode: "partial" }
133
+ },
130
134
  accounts: {
131
135
  ...prevAccounts,
132
136
  default: {
@@ -1 +1 @@
1
- {"version":3,"file":"onboard-cli.js","names":[],"sources":["../../../../../extensions/telegram/src/adapters/onboard-cli.ts"],"sourcesContent":["/**\n * Telegram interactive onboarding (CLI onboard) — {@link ChannelOnboardAdapter}.\n */\n\nimport { input, confirm, select } from '@inquirer/prompts';\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport type { ChannelOnboardAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';\n\ntype DmPolicy = 'pairing' | 'allowlist' | 'open' | 'disabled';\ntype GroupPolicy = 'open' | 'disabled' | 'allowlist';\n\nfunction isTelegramConfigured(config: Config): boolean {\n const telegram = config.channels?.telegram as Record<string, unknown> | undefined;\n if (!telegram) return false;\n\n const accounts = telegram.accounts as Record<string, Record<string, unknown>> | undefined;\n if (accounts) {\n for (const account of Object.values(accounts)) {\n if (account?.enabled === false) continue;\n if (typeof account.botToken === 'string' && account.botToken.trim()) return true;\n }\n }\n\n return false;\n}\n\nfunction detectEnvToken(): string | null {\n return process.env.TELEGRAM_BOT_TOKEN?.trim() || null;\n}\n\nasync function promptBotToken(config: Config): Promise<string | null> {\n const envToken = detectEnvToken();\n const tg = config.channels?.telegram as Record<string, unknown> | undefined;\n const accounts = tg?.accounts as Record<string, { botToken?: string }> | undefined;\n const existing = accounts?.default?.botToken?.trim();\n\n if (envToken && !existing) {\n const useEnv = await confirm({\n message: 'TELEGRAM_BOT_TOKEN is set in the environment. Use it?',\n default: true,\n });\n if (useEnv) return envToken;\n }\n\n if (typeof existing === 'string' && existing) {\n const keep = await confirm({\n message: 'A Telegram bot token is already configured. Keep it?',\n default: true,\n });\n if (keep) return existing;\n }\n\n console.log('\\n📝 Telegram Bot Token:');\n console.log(' 1. Open Telegram and chat with @BotFather');\n console.log(' 2. Send /newbot and follow the prompts');\n console.log(' 3. Copy the token BotFather gives you\\n');\n\n const token = await input({\n message: 'Enter bot token:',\n validate: (v) => v.trim().length > 0 || 'Token cannot be empty',\n });\n\n return token.trim();\n}\n\nasync function promptDmPolicy(): Promise<DmPolicy> {\n return select<DmPolicy>({\n message: 'DM (private chat) policy:',\n choices: [\n {\n value: 'pairing',\n name: 'pairing [recommended] verify new users with /pair',\n description: 'New users must send /pair before chatting',\n },\n {\n value: 'allowlist',\n name: 'allowlist allowlisted users only',\n description: 'Only listed users can message the bot',\n },\n {\n value: 'open',\n name: 'open anyone can DM (not recommended)',\n description: 'Any user can start a conversation',\n },\n {\n value: 'disabled',\n name: 'disabled DMs disabled',\n description: 'The bot does not respond to private messages',\n },\n ],\n default: 'pairing',\n });\n}\n\nasync function promptAllowlist(message: string): Promise<Array<string | number>> {\n const value = await input({\n message,\n default: '',\n });\n\n if (!value.trim()) return [];\n\n const entries = value\n .split(/[,\\s\\n]+/)\n .map((s) => s.trim())\n .filter(Boolean);\n\n return entries.map((e) => {\n const num = parseInt(e, 10);\n return !isNaN(num) && String(num) === e ? num : e;\n });\n}\n\nasync function promptGroupPolicy(): Promise<GroupPolicy> {\n return select<GroupPolicy>({\n message: 'Group chat policy:',\n choices: [\n {\n value: 'open',\n name: 'open all groups allowed',\n description: 'The bot can be used in any group',\n },\n {\n value: 'disabled',\n name: 'disabled groups disabled',\n description: 'The bot does not respond in groups',\n },\n {\n value: 'allowlist',\n name: 'allowlist allowlisted groups only',\n description: 'Only listed group chats can use the bot',\n },\n ],\n default: 'open',\n });\n}\n\nasync function configureTelegram(config: Config): Promise<Config> {\n console.log(`\\n${'='.repeat(50)}`);\n console.log('📱 Telegram setup');\n console.log(`${'='.repeat(50)}\\n`);\n\n const botToken = await promptBotToken(config);\n if (!botToken) {\n console.log('⚠️ No bot token; skipping Telegram setup.');\n return config;\n }\n\n const dmPolicy = await promptDmPolicy();\n\n let allowFrom: Array<string | number> | undefined;\n if (dmPolicy === 'allowlist') {\n allowFrom = await promptAllowlist('User IDs or usernames allowed for DMs (comma-separated):');\n }\n\n const groupPolicy = await promptGroupPolicy();\n\n let groupAllowFrom: Array<string | number> | undefined;\n if (groupPolicy === 'allowlist') {\n groupAllowFrom = await promptAllowlist('Allowed group chat IDs (comma-separated):');\n }\n\n const prev = config.channels?.telegram as Record<string, unknown> | undefined;\n const prevAccounts =\n prev?.accounts && typeof prev.accounts === 'object' && !Array.isArray(prev.accounts)\n ? { ...(prev.accounts as Record<string, unknown>) }\n : {};\n const prevDefault =\n prevAccounts.default && typeof prevAccounts.default === 'object' && !Array.isArray(prevAccounts.default)\n ? { ...(prevAccounts.default as Record<string, unknown>) }\n : {};\n\n const telegramConfig: Record<string, unknown> = {\n ...(prev ?? {}),\n enabled: true,\n dmPolicy,\n groupPolicy,\n allowFrom: allowFrom ?? (prev?.allowFrom as Array<string | number> | undefined) ?? [],\n groupAllowFrom: groupAllowFrom ?? (prev?.groupAllowFrom as Array<string | number> | undefined) ?? [],\n accounts: {\n ...prevAccounts,\n default: {\n ...prevDefault,\n accountId: 'default',\n enabled: true,\n botToken,\n dmPolicy,\n groupPolicy,\n allowFrom: allowFrom ?? (prevDefault.allowFrom as Array<string | number> | undefined) ?? [],\n groupAllowFrom:\n groupAllowFrom ?? (prevDefault.groupAllowFrom as Array<string | number> | undefined) ?? [],\n },\n },\n };\n\n const newConfig: Config = {\n ...config,\n channels: {\n ...config.channels,\n telegram: telegramConfig,\n },\n };\n\n console.log('\\n✅ Telegram configuration complete\\n');\n return newConfig;\n}\n\nexport const telegramOnboardAdapter: ChannelOnboardAdapter = {\n isConfigured: isTelegramConfigured,\n configure: configureTelegram,\n};\n\n/** Optional entry: confirm before running the full Telegram configure flow. */\nexport async function setupTelegramOnboard(\n config: Config,\n options: { confirmMessage?: string; confirmDefault?: boolean } = {},\n): Promise<Config> {\n const shouldEnable = await confirm({\n message: options.confirmMessage || 'Enable Telegram channel?',\n default: options.confirmDefault ?? true,\n });\n if (!shouldEnable) {\n console.log('ℹ️ Telegram skipped.');\n return config;\n }\n return configureTelegram(config);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"onboard-cli.js","names":[],"sources":["../../../../../extensions/telegram/src/adapters/onboard-cli.ts"],"sourcesContent":["/**\n * Telegram interactive onboarding (CLI onboard) — {@link ChannelOnboardAdapter}.\n */\n\nimport { input, confirm, select } from '@inquirer/prompts';\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport type { ChannelOnboardAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';\n\ntype DmPolicy = 'pairing' | 'allowlist' | 'open' | 'disabled';\ntype GroupPolicy = 'open' | 'disabled' | 'allowlist';\n\nfunction isTelegramConfigured(config: Config): boolean {\n const telegram = config.channels?.telegram as Record<string, unknown> | undefined;\n if (!telegram) return false;\n\n const accounts = telegram.accounts as Record<string, Record<string, unknown>> | undefined;\n if (accounts) {\n for (const account of Object.values(accounts)) {\n if (account?.enabled === false) continue;\n if (typeof account.botToken === 'string' && account.botToken.trim()) return true;\n }\n }\n\n return false;\n}\n\nfunction detectEnvToken(): string | null {\n return process.env.TELEGRAM_BOT_TOKEN?.trim() || null;\n}\n\nasync function promptBotToken(config: Config): Promise<string | null> {\n const envToken = detectEnvToken();\n const tg = config.channels?.telegram as Record<string, unknown> | undefined;\n const accounts = tg?.accounts as Record<string, { botToken?: string }> | undefined;\n const existing = accounts?.default?.botToken?.trim();\n\n if (envToken && !existing) {\n const useEnv = await confirm({\n message: 'TELEGRAM_BOT_TOKEN is set in the environment. Use it?',\n default: true,\n });\n if (useEnv) return envToken;\n }\n\n if (typeof existing === 'string' && existing) {\n const keep = await confirm({\n message: 'A Telegram bot token is already configured. Keep it?',\n default: true,\n });\n if (keep) return existing;\n }\n\n console.log('\\n📝 Telegram Bot Token:');\n console.log(' 1. Open Telegram and chat with @BotFather');\n console.log(' 2. Send /newbot and follow the prompts');\n console.log(' 3. Copy the token BotFather gives you\\n');\n\n const token = await input({\n message: 'Enter bot token:',\n validate: (v) => v.trim().length > 0 || 'Token cannot be empty',\n });\n\n return token.trim();\n}\n\nasync function promptDmPolicy(): Promise<DmPolicy> {\n return select<DmPolicy>({\n message: 'DM (private chat) policy:',\n choices: [\n {\n value: 'pairing',\n name: 'pairing [recommended] verify new users with /pair',\n description: 'New users must send /pair before chatting',\n },\n {\n value: 'allowlist',\n name: 'allowlist allowlisted users only',\n description: 'Only listed users can message the bot',\n },\n {\n value: 'open',\n name: 'open anyone can DM (not recommended)',\n description: 'Any user can start a conversation',\n },\n {\n value: 'disabled',\n name: 'disabled DMs disabled',\n description: 'The bot does not respond to private messages',\n },\n ],\n default: 'pairing',\n });\n}\n\nasync function promptAllowlist(message: string): Promise<Array<string | number>> {\n const value = await input({\n message,\n default: '',\n });\n\n if (!value.trim()) return [];\n\n const entries = value\n .split(/[,\\s\\n]+/)\n .map((s) => s.trim())\n .filter(Boolean);\n\n return entries.map((e) => {\n const num = parseInt(e, 10);\n return !isNaN(num) && String(num) === e ? num : e;\n });\n}\n\nasync function promptGroupPolicy(): Promise<GroupPolicy> {\n return select<GroupPolicy>({\n message: 'Group chat policy:',\n choices: [\n {\n value: 'open',\n name: 'open all groups allowed',\n description: 'The bot can be used in any group',\n },\n {\n value: 'disabled',\n name: 'disabled groups disabled',\n description: 'The bot does not respond in groups',\n },\n {\n value: 'allowlist',\n name: 'allowlist allowlisted groups only',\n description: 'Only listed group chats can use the bot',\n },\n ],\n default: 'open',\n });\n}\n\nasync function configureTelegram(config: Config): Promise<Config> {\n console.log(`\\n${'='.repeat(50)}`);\n console.log('📱 Telegram setup');\n console.log(`${'='.repeat(50)}\\n`);\n\n const botToken = await promptBotToken(config);\n if (!botToken) {\n console.log('⚠️ No bot token; skipping Telegram setup.');\n return config;\n }\n\n const dmPolicy = await promptDmPolicy();\n\n let allowFrom: Array<string | number> | undefined;\n if (dmPolicy === 'allowlist') {\n allowFrom = await promptAllowlist('User IDs or usernames allowed for DMs (comma-separated):');\n }\n\n const groupPolicy = await promptGroupPolicy();\n\n let groupAllowFrom: Array<string | number> | undefined;\n if (groupPolicy === 'allowlist') {\n groupAllowFrom = await promptAllowlist('Allowed group chat IDs (comma-separated):');\n }\n\n const prev = config.channels?.telegram as Record<string, unknown> | undefined;\n const prevAccounts =\n prev?.accounts && typeof prev.accounts === 'object' && !Array.isArray(prev.accounts)\n ? { ...(prev.accounts as Record<string, unknown>) }\n : {};\n const prevDefault =\n prevAccounts.default && typeof prevAccounts.default === 'object' && !Array.isArray(prevAccounts.default)\n ? { ...(prevAccounts.default as Record<string, unknown>) }\n : {};\n\n const telegramConfig: Record<string, unknown> = {\n ...(prev ?? {}),\n enabled: true,\n defaults:\n prev?.defaults && typeof prev.defaults === 'object' && !Array.isArray(prev.defaults)\n ? prev.defaults\n : {\n dmPolicy: 'pairing',\n groupPolicy: 'open',\n replyToMode: 'off',\n historyLimit: 50,\n textChunkLimit: 4000,\n streaming: { mode: 'partial' },\n },\n accounts: {\n ...prevAccounts,\n default: {\n ...prevDefault,\n accountId: 'default',\n enabled: true,\n botToken,\n dmPolicy,\n groupPolicy,\n allowFrom: allowFrom ?? (prevDefault.allowFrom as Array<string | number> | undefined) ?? [],\n groupAllowFrom:\n groupAllowFrom ?? (prevDefault.groupAllowFrom as Array<string | number> | undefined) ?? [],\n },\n },\n };\n\n const newConfig: Config = {\n ...config,\n channels: {\n ...config.channels,\n telegram: telegramConfig,\n },\n };\n\n console.log('\\n✅ Telegram configuration complete\\n');\n return newConfig;\n}\n\nexport const telegramOnboardAdapter: ChannelOnboardAdapter = {\n isConfigured: isTelegramConfigured,\n configure: configureTelegram,\n};\n\n/** Optional entry: confirm before running the full Telegram configure flow. */\nexport async function setupTelegramOnboard(\n config: Config,\n options: { confirmMessage?: string; confirmDefault?: boolean } = {},\n): Promise<Config> {\n const shouldEnable = await confirm({\n message: options.confirmMessage || 'Enable Telegram channel?',\n default: options.confirmDefault ?? true,\n });\n if (!shouldEnable) {\n console.log('ℹ️ Telegram skipped.');\n return config;\n }\n return configureTelegram(config);\n}\n"],"mappings":"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"}