@xopcai/xopc 0.0.47 → 0.0.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/extensions/dingtalk/src/accounts.js +1 -1
- package/dist/extensions/dingtalk/src/accounts.js.map +1 -1
- package/dist/extensions/dingtalk/src/merge-config.js +1 -1
- package/dist/extensions/dingtalk/src/merge-config.js.map +1 -1
- package/dist/extensions/dingtalk/src/plugin.js +1 -1
- package/dist/extensions/dingtalk/src/plugin.js.map +1 -1
- package/dist/extensions/feishu/src/adapters/cli-login.js +8 -8
- package/dist/extensions/feishu/src/adapters/cli-login.js.map +1 -1
- package/dist/extensions/feishu/src/adapters/onboard-cli.js +8 -8
- package/dist/extensions/feishu/src/adapters/onboard-cli.js.map +1 -1
- package/dist/extensions/feishu/src/plugin.js +1 -1
- package/dist/extensions/feishu/src/plugin.js.map +1 -1
- package/dist/extensions/feishu/src/schema/config-schema.js +2 -2
- package/dist/extensions/feishu/src/schema/config-schema.js.map +1 -1
- package/dist/extensions/feishu/src/state/accounts.js +1 -1
- package/dist/extensions/feishu/src/state/accounts.js.map +1 -1
- package/dist/extensions/feishu/src/streaming/streaming-adapter.js +82 -36
- package/dist/extensions/feishu/src/streaming/streaming-adapter.js.map +1 -1
- package/dist/extensions/telegram/src/command-handler.js +1 -1
- package/dist/extensions/telegram/src/command-handler.js.map +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/extensions/weixin/src/auth/accounts.js +1 -1
- package/dist/extensions/weixin/src/auth/accounts.js.map +1 -1
- package/dist/extensions/weixin/src/config-schema.js +2 -2
- package/dist/extensions/weixin/src/config-schema.js.map +1 -1
- package/dist/extensions/weixin/src/config-surface.js +1 -1
- package/dist/extensions/weixin/src/config-surface.js.map +1 -1
- package/dist/extensions/weixin/src/messaging/process-message.js +2 -2
- package/dist/extensions/weixin/src/messaging/process-message.js.map +1 -1
- package/dist/extensions/weixin/src/plugin.js +1 -1
- package/dist/extensions/weixin/src/plugin.js.map +1 -1
- package/dist/gateway/static/root/assets/{agents-DdWPgyn-.js → agents-CQllyJhj.js} +2 -2
- package/dist/gateway/static/root/assets/{agents-DdWPgyn-.js.map → agents-CQllyJhj.js.map} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-BTi_W1y1.js → apps-page-BWI3RVMh.js} +2 -2
- package/dist/gateway/static/root/assets/{apps-page-BTi_W1y1.js.map → apps-page-BWI3RVMh.js.map} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-rfmhXfC4.js +2 -0
- package/dist/gateway/static/root/assets/channels-settings-rfmhXfC4.js.map +1 -0
- package/dist/gateway/static/root/assets/{cron-dreaming-jobs-DinMur-Z.js → cron-dreaming-jobs-BzCQy56Z.js} +2 -2
- package/dist/gateway/static/root/assets/{cron-dreaming-jobs-DinMur-Z.js.map → cron-dreaming-jobs-BzCQy56Z.js.map} +1 -1
- package/dist/gateway/static/root/assets/{cron-page-Bu05Z2oL.js → cron-page-CjTuH_av.js} +2 -2
- package/dist/gateway/static/root/assets/{cron-page-Bu05Z2oL.js.map → cron-page-CjTuH_av.js.map} +1 -1
- package/dist/gateway/static/root/assets/{dist-wDej8fSi.js → dist-DIeOihYP.js} +2 -2
- package/dist/gateway/static/root/assets/{dist-wDej8fSi.js.map → dist-DIeOihYP.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-CZBu7-zM.js → extension-debug-page-CR-4lZOn.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-debug-page-CZBu7-zM.js.map → extension-debug-page-CR-4lZOn.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-CnOyLPrh.js → extension-page-BNwcYj2Q.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-page-CnOyLPrh.js.map → extension-page-BNwcYj2Q.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-BOHn3S1a.js → extension-settings-page-CDpiZPV4.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-settings-page-BOHn3S1a.js.map → extension-settings-page-CDpiZPV4.js.map} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-config-api-OqFXdrMO.js → heartbeat-config-api-CSbqK-L_.js} +2 -2
- package/dist/gateway/static/root/assets/{heartbeat-config-api-OqFXdrMO.js.map → heartbeat-config-api-CSbqK-L_.js.map} +1 -1
- package/dist/gateway/static/root/assets/{index-DeELk--t.js → index-D3sMd_aw.js} +11 -11
- package/dist/gateway/static/root/assets/{index-DeELk--t.js.map → index-D3sMd_aw.js.map} +1 -1
- package/dist/gateway/static/root/assets/{logs-page-DiN42-yE.js → logs-page-DLcWAXU5.js} +2 -2
- package/dist/gateway/static/root/assets/{logs-page-DiN42-yE.js.map → logs-page-DLcWAXU5.js.map} +1 -1
- package/dist/gateway/static/root/assets/{sessions-page-B5oxRfRm.js → sessions-page-Ck3lS3N_.js} +2 -2
- package/dist/gateway/static/root/assets/{sessions-page-B5oxRfRm.js.map → sessions-page-Ck3lS3N_.js.map} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-DaRY3XEp.js → settings-page-Dp_eFgub.js} +2 -2
- package/dist/gateway/static/root/assets/{settings-page-DaRY3XEp.js.map → settings-page-Dp_eFgub.js.map} +1 -1
- package/dist/gateway/static/root/assets/{skills-page-BGDLiQZ6.js → skills-page-ClRj9e6K.js} +2 -2
- package/dist/gateway/static/root/assets/{skills-page-BGDLiQZ6.js.map → skills-page-ClRj9e6K.js.map} +1 -1
- package/dist/gateway/static/root/assets/{use-image-provider-credentials-DcP2SYn3.js → use-image-provider-credentials-DAi5Iu8c.js} +2 -2
- package/dist/gateway/static/root/assets/{use-image-provider-credentials-DcP2SYn3.js.map → use-image-provider-credentials-DAi5Iu8c.js.map} +1 -1
- package/dist/gateway/static/root/index.html +1 -1
- package/dist/package.js +1 -1
- package/dist/src/agent/goals/checklist-judge.js +21 -17
- package/dist/src/agent/goals/checklist-judge.js.map +1 -1
- package/dist/src/agent/goals/evaluate-turn.js +6 -11
- package/dist/src/agent/goals/evaluate-turn.js.map +1 -1
- package/dist/src/agent/goals/judge.d.ts +16 -0
- package/dist/src/agent/goals/judge.js +61 -13
- package/dist/src/agent/goals/judge.js.map +1 -1
- package/dist/src/agent/goals/state.d.ts +7 -0
- package/dist/src/agent/goals/state.js +24 -1
- package/dist/src/agent/goals/state.js.map +1 -1
- package/dist/src/agent/service.js +2 -0
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/chat-commands/builtins/model.d.ts +2 -2
- package/dist/src/chat-commands/builtins/model.js +10 -8
- package/dist/src/chat-commands/builtins/model.js.map +1 -1
- package/dist/src/chat-commands/builtins/system.js +1 -1
- package/dist/src/chat-commands/builtins/system.js.map +1 -1
- package/dist/src/gateway/hono/routes/channels.js +2 -2
- package/dist/src/gateway/hono/routes/channels.js.map +1 -1
- package/dist/src/gateway/hono/routes/config.js +3 -3
- package/dist/src/gateway/hono/routes/config.js.map +1 -1
- package/dist/src/tui/backends/embedded-backend.d.ts +1 -1
- package/dist/src/tui/backends/embedded-backend.js +24 -3
- package/dist/src/tui/backends/embedded-backend.js.map +1 -1
- package/dist/src/tui/backends/gateway-sse-backend.js +36 -10
- package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
- package/dist/src/tui/tui-commands.js +1 -1
- package/dist/src/tui/tui-commands.js.map +1 -1
- package/dist/src/tui/tui.js +12 -1
- package/dist/src/tui/tui.js.map +1 -1
- package/package.json +1 -1
- package/dist/gateway/static/root/assets/channels-settings-CjUmKQrC.js +0 -2
- package/dist/gateway/static/root/assets/channels-settings-CjUmKQrC.js.map +0 -1
|
@@ -48,7 +48,7 @@ function resolveDingtalkAccount(cfg, accountId) {
|
|
|
48
48
|
configured,
|
|
49
49
|
clientId,
|
|
50
50
|
clientSecret,
|
|
51
|
-
dmPolicy: merged.dmPolicy ?? "
|
|
51
|
+
dmPolicy: merged.dmPolicy ?? "open",
|
|
52
52
|
groupPolicy: merged.groupPolicy ?? "open",
|
|
53
53
|
allowFrom: merged.allowFrom ?? [],
|
|
54
54
|
groupAllowFrom: merged.groupAllowFrom ?? merged.allowFrom ?? [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"accounts.js","names":[],"sources":["../../../../extensions/dingtalk/src/accounts.ts"],"sourcesContent":["import type { Config } from '@xopcai/xopc/config/schema.js';\n\nimport { DEFAULT_DINGTALK_ACCOUNT_ID, type DingtalkConfig, type DingtalkAccountOverride } from './config-schema.js';\n\nexport type ResolvedDingtalkAccount = {\n accountId: string;\n enabled: boolean;\n configured: boolean;\n clientId: string;\n clientSecret: string;\n dmPolicy: 'pairing' | 'allowlist' | 'open' | 'disabled';\n groupPolicy: 'open' | 'disabled' | 'allowlist';\n allowFrom: Array<string | number>;\n groupAllowFrom: Array<string | number>;\n requireMention: boolean;\n debug: boolean;\n endpoint: string;\n historyLimit: number;\n textChunkLimit: number;\n raw: DingtalkConfig;\n};\n\nfunction getSection(cfg: Config): DingtalkConfig | undefined {\n return cfg.channels?.dingtalk as DingtalkConfig | undefined;\n}\n\nfunction listConfiguredAccountIds(cfg: Config): string[] {\n const accounts = getSection(cfg)?.accounts;\n if (!accounts || typeof accounts !== 'object') {\n return [];\n }\n return Object.keys(accounts).filter(Boolean);\n}\n\nexport function listDingtalkAccountIds(cfg: Config): string[] {\n const ids = listConfiguredAccountIds(cfg);\n if (ids.length === 0) {\n return [DEFAULT_DINGTALK_ACCOUNT_ID];\n }\n return [...ids].toSorted((a, b) => a.localeCompare(b));\n}\n\nexport function resolveDefaultDingtalkAccountId(cfg: Config): string {\n const preferred = getSection(cfg)?.defaultAccount?.trim();\n if (preferred) return preferred;\n const ids = listDingtalkAccountIds(cfg);\n if (ids.includes(DEFAULT_DINGTALK_ACCOUNT_ID)) return DEFAULT_DINGTALK_ACCOUNT_ID;\n return ids[0] ?? DEFAULT_DINGTALK_ACCOUNT_ID;\n}\n\nfunction accountOverride(cfg: Config, accountId: string): DingtalkAccountOverride | undefined {\n return getSection(cfg)?.accounts?.[accountId];\n}\n\n/** Merge top-level dingtalk config with per-account overrides. */\nfunction mergeAccountConfig(cfg: Config, accountId: string): DingtalkConfig {\n const base = getSection(cfg) ?? {};\n const { accounts: _a, defaultAccount: _d, ...rest } = base;\n const acc = accountOverride(cfg, accountId) ?? {};\n return { ...rest, ...acc } as DingtalkConfig;\n}\n\nexport function resolveDingtalkAccount(cfg: Config, accountId?: string | null): ResolvedDingtalkAccount {\n const id = accountId?.trim() || resolveDefaultDingtalkAccountId(cfg);\n const merged = mergeAccountConfig(cfg, id);\n const section = getSection(cfg);\n const topEnabled = section?.enabled === true;\n const ov = accountOverride(cfg, id);\n const clientId = String(merged.clientId ?? '').trim();\n const clientSecret = String(merged.clientSecret ?? '').trim();\n const configured = Boolean(clientId && clientSecret);\n\n const enabled =\n Boolean(section) &&\n topEnabled &&\n configured &&\n (id === DEFAULT_DINGTALK_ACCOUNT_ID ? ov?.enabled !== false : ov?.enabled === true);\n\n return {\n accountId: id,\n enabled,\n configured,\n clientId,\n clientSecret,\n dmPolicy: merged.dmPolicy ?? '
|
|
1
|
+
{"version":3,"file":"accounts.js","names":[],"sources":["../../../../extensions/dingtalk/src/accounts.ts"],"sourcesContent":["import type { Config } from '@xopcai/xopc/config/schema.js';\n\nimport { DEFAULT_DINGTALK_ACCOUNT_ID, type DingtalkConfig, type DingtalkAccountOverride } from './config-schema.js';\n\nexport type ResolvedDingtalkAccount = {\n accountId: string;\n enabled: boolean;\n configured: boolean;\n clientId: string;\n clientSecret: string;\n dmPolicy: 'pairing' | 'allowlist' | 'open' | 'disabled';\n groupPolicy: 'open' | 'disabled' | 'allowlist';\n allowFrom: Array<string | number>;\n groupAllowFrom: Array<string | number>;\n requireMention: boolean;\n debug: boolean;\n endpoint: string;\n historyLimit: number;\n textChunkLimit: number;\n raw: DingtalkConfig;\n};\n\nfunction getSection(cfg: Config): DingtalkConfig | undefined {\n return cfg.channels?.dingtalk as DingtalkConfig | undefined;\n}\n\nfunction listConfiguredAccountIds(cfg: Config): string[] {\n const accounts = getSection(cfg)?.accounts;\n if (!accounts || typeof accounts !== 'object') {\n return [];\n }\n return Object.keys(accounts).filter(Boolean);\n}\n\nexport function listDingtalkAccountIds(cfg: Config): string[] {\n const ids = listConfiguredAccountIds(cfg);\n if (ids.length === 0) {\n return [DEFAULT_DINGTALK_ACCOUNT_ID];\n }\n return [...ids].toSorted((a, b) => a.localeCompare(b));\n}\n\nexport function resolveDefaultDingtalkAccountId(cfg: Config): string {\n const preferred = getSection(cfg)?.defaultAccount?.trim();\n if (preferred) return preferred;\n const ids = listDingtalkAccountIds(cfg);\n if (ids.includes(DEFAULT_DINGTALK_ACCOUNT_ID)) return DEFAULT_DINGTALK_ACCOUNT_ID;\n return ids[0] ?? DEFAULT_DINGTALK_ACCOUNT_ID;\n}\n\nfunction accountOverride(cfg: Config, accountId: string): DingtalkAccountOverride | undefined {\n return getSection(cfg)?.accounts?.[accountId];\n}\n\n/** Merge top-level dingtalk config with per-account overrides. */\nfunction mergeAccountConfig(cfg: Config, accountId: string): DingtalkConfig {\n const base = getSection(cfg) ?? {};\n const { accounts: _a, defaultAccount: _d, ...rest } = base;\n const acc = accountOverride(cfg, accountId) ?? {};\n return { ...rest, ...acc } as DingtalkConfig;\n}\n\nexport function resolveDingtalkAccount(cfg: Config, accountId?: string | null): ResolvedDingtalkAccount {\n const id = accountId?.trim() || resolveDefaultDingtalkAccountId(cfg);\n const merged = mergeAccountConfig(cfg, id);\n const section = getSection(cfg);\n const topEnabled = section?.enabled === true;\n const ov = accountOverride(cfg, id);\n const clientId = String(merged.clientId ?? '').trim();\n const clientSecret = String(merged.clientSecret ?? '').trim();\n const configured = Boolean(clientId && clientSecret);\n\n const enabled =\n Boolean(section) &&\n topEnabled &&\n configured &&\n (id === DEFAULT_DINGTALK_ACCOUNT_ID ? ov?.enabled !== false : ov?.enabled === true);\n\n return {\n accountId: id,\n enabled,\n configured,\n clientId,\n clientSecret,\n dmPolicy: merged.dmPolicy ?? 'open',\n groupPolicy: merged.groupPolicy ?? 'open',\n allowFrom: merged.allowFrom ?? [],\n groupAllowFrom: merged.groupAllowFrom ?? merged.allowFrom ?? [],\n requireMention: merged.requireMention === true,\n debug: merged.debug === true,\n endpoint: (merged.endpoint ?? 'https://api.dingtalk.com').trim() || 'https://api.dingtalk.com',\n historyLimit: typeof merged.historyLimit === 'number' ? merged.historyLimit : 50,\n textChunkLimit: typeof merged.textChunkLimit === 'number' ? merged.textChunkLimit : 4000,\n raw: merged,\n };\n}\n"],"mappings":";;oBAEoH;AAoBpH,SAAS,WAAW,KAAyC;AAC3D,QAAO,IAAI,UAAU;;AAGvB,SAAS,yBAAyB,KAAuB;CACvD,MAAM,WAAW,WAAW,IAAI,EAAE;AAClC,KAAI,CAAC,YAAY,OAAO,aAAa,SACnC,QAAO,EAAE;AAEX,QAAO,OAAO,KAAK,SAAS,CAAC,OAAO,QAAQ;;AAG9C,SAAgB,uBAAuB,KAAuB;CAC5D,MAAM,MAAM,yBAAyB,IAAI;AACzC,KAAI,IAAI,WAAW,EACjB,QAAO,CAAC,4BAA4B;AAEtC,QAAO,CAAC,GAAG,IAAI,CAAC,UAAU,GAAG,MAAM,EAAE,cAAc,EAAE,CAAC;;AAGxD,SAAgB,gCAAgC,KAAqB;CACnE,MAAM,YAAY,WAAW,IAAI,EAAE,gBAAgB,MAAM;AACzD,KAAI,UAAW,QAAO;CACtB,MAAM,MAAM,uBAAuB,IAAI;AACvC,KAAI,IAAI,SAAA,UAAqC,CAAE,QAAO;AACtD,QAAO,IAAI,MAAA;;AAGb,SAAS,gBAAgB,KAAa,WAAwD;AAC5F,QAAO,WAAW,IAAI,EAAE,WAAW;;;AAIrC,SAAS,mBAAmB,KAAa,WAAmC;CAE1E,MAAM,EAAE,UAAU,IAAI,gBAAgB,IAAI,GAAG,SADhC,WAAW,IAAI,IAAI,EAAE;CAElC,MAAM,MAAM,gBAAgB,KAAK,UAAU,IAAI,EAAE;AACjD,QAAO;EAAE,GAAG;EAAM,GAAG;EAAK;;AAG5B,SAAgB,uBAAuB,KAAa,WAAoD;CACtG,MAAM,KAAK,WAAW,MAAM,IAAI,gCAAgC,IAAI;CACpE,MAAM,SAAS,mBAAmB,KAAK,GAAG;CAC1C,MAAM,UAAU,WAAW,IAAI;CAC/B,MAAM,aAAa,SAAS,YAAY;CACxC,MAAM,KAAK,gBAAgB,KAAK,GAAG;CACnC,MAAM,WAAW,OAAO,OAAO,YAAY,GAAG,CAAC,MAAM;CACrD,MAAM,eAAe,OAAO,OAAO,gBAAgB,GAAG,CAAC,MAAM;CAC7D,MAAM,aAAa,QAAQ,YAAY,aAAa;AAQpD,QAAO;EACL,WAAW;EACX,SAPA,QAAQ,QAAQ,IAChB,cACA,eACC,OAAA,YAAqC,IAAI,YAAY,QAAQ,IAAI,YAAY;EAK9E;EACA;EACA;EACA,UAAU,OAAO,YAAY;EAC7B,aAAa,OAAO,eAAe;EACnC,WAAW,OAAO,aAAa,EAAE;EACjC,gBAAgB,OAAO,kBAAkB,OAAO,aAAa,EAAE;EAC/D,gBAAgB,OAAO,mBAAmB;EAC1C,OAAO,OAAO,UAAU;EACxB,WAAW,OAAO,YAAY,4BAA4B,MAAM,IAAI;EACpE,cAAc,OAAO,OAAO,iBAAiB,WAAW,OAAO,eAAe;EAC9E,gBAAgB,OAAO,OAAO,mBAAmB,WAAW,OAAO,iBAAiB;EACpF,KAAK;EACN"}
|
|
@@ -10,7 +10,7 @@ function mergeDingtalkCredentialsIntoConfig(cfg, creds, policies) {
|
|
|
10
10
|
enabled: true,
|
|
11
11
|
clientId: creds.clientId,
|
|
12
12
|
clientSecret: creds.clientSecret,
|
|
13
|
-
dmPolicy: policies?.dmPolicy ?? prev.dmPolicy ?? "
|
|
13
|
+
dmPolicy: policies?.dmPolicy ?? prev.dmPolicy ?? "open",
|
|
14
14
|
groupPolicy: policies?.groupPolicy ?? prev.groupPolicy ?? "open",
|
|
15
15
|
allowFrom: policies?.allowFrom ?? prev.allowFrom ?? [],
|
|
16
16
|
groupAllowFrom: policies?.groupAllowFrom ?? prev.groupAllowFrom ?? [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge-config.js","names":[],"sources":["../../../../extensions/dingtalk/src/merge-config.ts"],"sourcesContent":["import type { Config } from '@xopcai/xopc/config/schema.js';\n\nimport type { DingtalkConfig } from './config-schema.js';\n\nexport type DingtalkPolicyPatch = Partial<\n Pick<DingtalkConfig, 'dmPolicy' | 'groupPolicy' | 'allowFrom' | 'groupAllowFrom' | 'requireMention'>\n>;\n\nexport function mergeDingtalkCredentialsIntoConfig(\n cfg: Config,\n creds: { clientId: string; clientSecret: string },\n policies?: DingtalkPolicyPatch,\n): Config {\n const prev = (cfg.channels?.dingtalk ?? {}) as DingtalkConfig;\n return {\n ...cfg,\n channels: {\n ...cfg.channels,\n dingtalk: {\n ...prev,\n enabled: true,\n clientId: creds.clientId,\n clientSecret: creds.clientSecret,\n dmPolicy: policies?.dmPolicy ?? prev.dmPolicy ?? '
|
|
1
|
+
{"version":3,"file":"merge-config.js","names":[],"sources":["../../../../extensions/dingtalk/src/merge-config.ts"],"sourcesContent":["import type { Config } from '@xopcai/xopc/config/schema.js';\n\nimport type { DingtalkConfig } from './config-schema.js';\n\nexport type DingtalkPolicyPatch = Partial<\n Pick<DingtalkConfig, 'dmPolicy' | 'groupPolicy' | 'allowFrom' | 'groupAllowFrom' | 'requireMention'>\n>;\n\nexport function mergeDingtalkCredentialsIntoConfig(\n cfg: Config,\n creds: { clientId: string; clientSecret: string },\n policies?: DingtalkPolicyPatch,\n): Config {\n const prev = (cfg.channels?.dingtalk ?? {}) as DingtalkConfig;\n return {\n ...cfg,\n channels: {\n ...cfg.channels,\n dingtalk: {\n ...prev,\n enabled: true,\n clientId: creds.clientId,\n clientSecret: creds.clientSecret,\n dmPolicy: policies?.dmPolicy ?? prev.dmPolicy ?? 'open',\n groupPolicy: policies?.groupPolicy ?? prev.groupPolicy ?? 'open',\n allowFrom: policies?.allowFrom ?? prev.allowFrom ?? [],\n groupAllowFrom: policies?.groupAllowFrom ?? prev.groupAllowFrom ?? [],\n requireMention: policies?.requireMention ?? prev.requireMention,\n },\n },\n };\n}\n"],"mappings":";AAQA,SAAgB,mCACd,KACA,OACA,UACQ;CACR,MAAM,OAAQ,IAAI,UAAU,YAAY,EAAE;AAC1C,QAAO;EACL,GAAG;EACH,UAAU;GACR,GAAG,IAAI;GACP,UAAU;IACR,GAAG;IACH,SAAS;IACT,UAAU,MAAM;IAChB,cAAc,MAAM;IACpB,UAAU,UAAU,YAAY,KAAK,YAAY;IACjD,aAAa,UAAU,eAAe,KAAK,eAAe;IAC1D,WAAW,UAAU,aAAa,KAAK,aAAa,EAAE;IACtD,gBAAgB,UAAU,kBAAkB,KAAK,kBAAkB,EAAE;IACrE,gBAAgB,UAAU,kBAAkB,KAAK;IAClD;GACF;EACF"}
|
|
@@ -78,7 +78,7 @@ var DingtalkChannelPlugin = class {
|
|
|
78
78
|
defaultAccountId: (cfg) => listDingtalkAccountIds(cfg)[0] ?? "default"
|
|
79
79
|
};
|
|
80
80
|
security = {
|
|
81
|
-
resolveDmPolicy: ({ account }) => resolveDmPolicy(account.dmPolicy, "
|
|
81
|
+
resolveDmPolicy: ({ account }) => resolveDmPolicy(account.dmPolicy, "open"),
|
|
82
82
|
resolveGroupPolicy: ({ account }) => resolveGroupPolicy(account.groupPolicy, "open"),
|
|
83
83
|
checkAccess: (ctx, account, _cfg) => {
|
|
84
84
|
const isDm = !ctx.isGroup;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","names":[],"sources":["../../../../extensions/dingtalk/src/plugin.ts"],"sourcesContent":["/**\n * DingTalk channel plugin (Stream / robot inbound).\n * Device registration flow derived from dingtalk-openclaw-connector (MIT).\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 ChannelPlugin,\n ChannelPluginInitOptions,\n ChannelPluginReloadMeta,\n ChannelPluginStartOptions,\n ChannelSecurityAdapter,\n ChannelSecurityContext,\n ChatType,\n} from '@xopcai/xopc/channels/plugin-types.js';\nimport type { ChannelMeta } from '@xopcai/xopc/channels/plugins/types.core.js';\nimport type { ChannelCliLoginAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';\nimport type { ChannelOnboardAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\nimport { readAllowFromIdsSync, resolveStandardAllowFromPath } from '@xopcai/xopc/channels/pairing/index.js';\nimport { evaluateAccess, resolveDmPolicy, resolveGroupPolicy } from '@xopcai/xopc/channels/security.js';\n\nimport { DingtalkConfigSchema, type DingtalkConfig } from './config-schema.js';\nimport { listDingtalkAccountIds, resolveDingtalkAccount, type ResolvedDingtalkAccount } from './accounts.js';\nimport { runDingtalkStreamMonitor } from './stream-monitor.js';\nimport { createDingtalkOutboundAdapter } from './outbound-adapter.js';\nimport { dingtalkConfigSurface } from './ui/config-surface.js';\nimport { dingtalkCliLoginAdapter } from './adapters/cli-login.js';\nimport { dingtalkOnboardAdapter } from './adapters/onboard-cli.js';\nimport { createDingtalkDoctorAdapter } from './status/doctor.js';\n\nconst log = createLogger('DingTalkPlugin');\n\nexport class DingtalkChannelPlugin implements ChannelPlugin<ResolvedDingtalkAccount> {\n readonly id = 'dingtalk' as const;\n\n readonly reload: ChannelPluginReloadMeta = {\n configPrefixes: ['channels.dingtalk'],\n };\n\n readonly meta: ChannelMeta = {\n id: 'dingtalk',\n label: 'DingTalk',\n selectionLabel: 'DingTalk (钉钉)',\n docsPath: '/channels/dingtalk',\n blurb: 'DingTalk enterprise robot via Stream mode (QR app registration).',\n order: 35,\n aliases: ['dd', 'ding'],\n deferConnectUntilAfterListen: true,\n };\n\n readonly capabilities: ChannelCapabilities = {\n chatTypes: ['direct', 'group'] as ChatType[],\n reactions: false,\n threads: false,\n media: false,\n polls: false,\n nativeCommands: false,\n blockStreaming: true,\n };\n\n readonly defaults = {\n outbound: { textChunkLimit: 4000 },\n queue: { debounceMs: 0 },\n };\n\n readonly configSchema = {\n schema: {},\n validate: (raw: unknown) => {\n const r = DingtalkConfigSchema.safeParse(raw);\n return r.success ? { ok: true as const } : { ok: false as const, errors: [r.error.message] };\n },\n };\n\n readonly configSurface = dingtalkConfigSurface;\n readonly onboard: ChannelOnboardAdapter = dingtalkOnboardAdapter;\n readonly cliLogin: ChannelCliLoginAdapter = dingtalkCliLoginAdapter;\n readonly outbound = createDingtalkOutboundAdapter();\n readonly doctor: ChannelDoctorAdapter = createDingtalkDoctorAdapter();\n\n private bus!: MessageBus;\n private cfg!: Config;\n private abortControllers = new Map<string, AbortController>();\n\n config = {\n listAccountIds: (cfg: Config) => listDingtalkAccountIds(cfg),\n resolveAccount: (cfg: Config, accountId?: string | null) => resolveDingtalkAccount(cfg, accountId),\n isConfigured: async (account: ResolvedDingtalkAccount) => account.configured,\n describeAccount: (account: ResolvedDingtalkAccount) => ({\n accountId: account.accountId,\n channelId: 'dingtalk',\n enabled: account.enabled,\n configured: account.configured,\n status: account.configured ? undefined : 'unconfigured',\n }),\n defaultAccountId: (cfg: Config) => listDingtalkAccountIds(cfg)[0] ?? 'default',\n };\n\n security: ChannelSecurityAdapter<ResolvedDingtalkAccount> = {\n resolveDmPolicy: ({ account }) => resolveDmPolicy(account.dmPolicy, 'pairing'),\n resolveGroupPolicy: ({ account }) => resolveGroupPolicy(account.groupPolicy, 'open'),\n checkAccess: (ctx: ChannelSecurityContext, account: ResolvedDingtalkAccount, _cfg: Config) => {\n const isDm = !ctx.isGroup;\n const storeAllow = isDm\n ? readAllowFromIdsSync(resolveStandardAllowFromPath('dingtalk', account.accountId))\n : [];\n const baseAllowFrom = isDm ? account.allowFrom : account.groupAllowFrom ?? account.allowFrom;\n const allowFrom = [...(baseAllowFrom ?? []), ...storeAllow];\n if (isDm) {\n return evaluateAccess({\n context: {\n channel: 'dingtalk',\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: 'dingtalk',\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 async init(options: ChannelPluginInitOptions): Promise<void> {\n this.bus = options.bus;\n this.cfg = options.config;\n log.debug('DingTalk plugin initialized');\n }\n\n async start(options?: ChannelPluginStartOptions): Promise<void> {\n const section = this.cfg.channels?.dingtalk as DingtalkConfig | undefined;\n if (!section || section.enabled !== true) {\n return;\n }\n\n const ids = options?.accountId ? [options.accountId] : listDingtalkAccountIds(this.cfg);\n for (const accountId of ids) {\n const account = resolveDingtalkAccount(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 void runDingtalkStreamMonitor({\n account,\n bus: this.bus,\n abortSignal: ac.signal,\n security: {\n checkAccess: (ctx: ChannelSecurityContext) =>\n this.security.checkAccess?.(ctx, account, this.cfg) ?? { allowed: true },\n },\n }).catch((err) => {\n if ((err as { name?: string } | undefined)?.name === 'AbortError') {\n log.debug({ accountId }, 'DingTalk monitor stopped');\n return;\n }\n log.error({ err, accountId }, 'DingTalk monitor exited with error');\n });\n\n log.info({ accountId }, 'DingTalk Stream monitor started');\n }\n }\n\n async stop(accountId?: string): Promise<void> {\n const ids = accountId ? [accountId] : [...this.abortControllers.keys()];\n for (const id of ids) {\n const ac = this.abortControllers.get(id);\n if (ac) {\n ac.abort();\n this.abortControllers.delete(id);\n }\n }\n }\n\n channelIsRunning(cfg: Config): boolean {\n const ids = listDingtalkAccountIds(cfg);\n return ids.some((id) => {\n const a = resolveDingtalkAccount(cfg, id);\n return a.enabled && a.configured && this.abortControllers.has(id);\n });\n }\n\n async onConfigUpdated(cfg: Config): Promise<void> {\n const prev = this.cfg.channels?.dingtalk as unknown;\n const next = cfg.channels?.dingtalk 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 dingtalkPlugin = new DingtalkChannelPlugin();\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin.js","names":[],"sources":["../../../../extensions/dingtalk/src/plugin.ts"],"sourcesContent":["/**\n * DingTalk channel plugin (Stream / robot inbound).\n * Device registration flow derived from dingtalk-openclaw-connector (MIT).\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 ChannelPlugin,\n ChannelPluginInitOptions,\n ChannelPluginReloadMeta,\n ChannelPluginStartOptions,\n ChannelSecurityAdapter,\n ChannelSecurityContext,\n ChatType,\n} from '@xopcai/xopc/channels/plugin-types.js';\nimport type { ChannelMeta } from '@xopcai/xopc/channels/plugins/types.core.js';\nimport type { ChannelCliLoginAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';\nimport type { ChannelOnboardAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\nimport { readAllowFromIdsSync, resolveStandardAllowFromPath } from '@xopcai/xopc/channels/pairing/index.js';\nimport { evaluateAccess, resolveDmPolicy, resolveGroupPolicy } from '@xopcai/xopc/channels/security.js';\n\nimport { DingtalkConfigSchema, type DingtalkConfig } from './config-schema.js';\nimport { listDingtalkAccountIds, resolveDingtalkAccount, type ResolvedDingtalkAccount } from './accounts.js';\nimport { runDingtalkStreamMonitor } from './stream-monitor.js';\nimport { createDingtalkOutboundAdapter } from './outbound-adapter.js';\nimport { dingtalkConfigSurface } from './ui/config-surface.js';\nimport { dingtalkCliLoginAdapter } from './adapters/cli-login.js';\nimport { dingtalkOnboardAdapter } from './adapters/onboard-cli.js';\nimport { createDingtalkDoctorAdapter } from './status/doctor.js';\n\nconst log = createLogger('DingTalkPlugin');\n\nexport class DingtalkChannelPlugin implements ChannelPlugin<ResolvedDingtalkAccount> {\n readonly id = 'dingtalk' as const;\n\n readonly reload: ChannelPluginReloadMeta = {\n configPrefixes: ['channels.dingtalk'],\n };\n\n readonly meta: ChannelMeta = {\n id: 'dingtalk',\n label: 'DingTalk',\n selectionLabel: 'DingTalk (钉钉)',\n docsPath: '/channels/dingtalk',\n blurb: 'DingTalk enterprise robot via Stream mode (QR app registration).',\n order: 35,\n aliases: ['dd', 'ding'],\n deferConnectUntilAfterListen: true,\n };\n\n readonly capabilities: ChannelCapabilities = {\n chatTypes: ['direct', 'group'] as ChatType[],\n reactions: false,\n threads: false,\n media: false,\n polls: false,\n nativeCommands: false,\n blockStreaming: true,\n };\n\n readonly defaults = {\n outbound: { textChunkLimit: 4000 },\n queue: { debounceMs: 0 },\n };\n\n readonly configSchema = {\n schema: {},\n validate: (raw: unknown) => {\n const r = DingtalkConfigSchema.safeParse(raw);\n return r.success ? { ok: true as const } : { ok: false as const, errors: [r.error.message] };\n },\n };\n\n readonly configSurface = dingtalkConfigSurface;\n readonly onboard: ChannelOnboardAdapter = dingtalkOnboardAdapter;\n readonly cliLogin: ChannelCliLoginAdapter = dingtalkCliLoginAdapter;\n readonly outbound = createDingtalkOutboundAdapter();\n readonly doctor: ChannelDoctorAdapter = createDingtalkDoctorAdapter();\n\n private bus!: MessageBus;\n private cfg!: Config;\n private abortControllers = new Map<string, AbortController>();\n\n config = {\n listAccountIds: (cfg: Config) => listDingtalkAccountIds(cfg),\n resolveAccount: (cfg: Config, accountId?: string | null) => resolveDingtalkAccount(cfg, accountId),\n isConfigured: async (account: ResolvedDingtalkAccount) => account.configured,\n describeAccount: (account: ResolvedDingtalkAccount) => ({\n accountId: account.accountId,\n channelId: 'dingtalk',\n enabled: account.enabled,\n configured: account.configured,\n status: account.configured ? undefined : 'unconfigured',\n }),\n defaultAccountId: (cfg: Config) => listDingtalkAccountIds(cfg)[0] ?? 'default',\n };\n\n security: ChannelSecurityAdapter<ResolvedDingtalkAccount> = {\n resolveDmPolicy: ({ account }) => resolveDmPolicy(account.dmPolicy, 'open'),\n resolveGroupPolicy: ({ account }) => resolveGroupPolicy(account.groupPolicy, 'open'),\n checkAccess: (ctx: ChannelSecurityContext, account: ResolvedDingtalkAccount, _cfg: Config) => {\n const isDm = !ctx.isGroup;\n const storeAllow = isDm\n ? readAllowFromIdsSync(resolveStandardAllowFromPath('dingtalk', account.accountId))\n : [];\n const baseAllowFrom = isDm ? account.allowFrom : account.groupAllowFrom ?? account.allowFrom;\n const allowFrom = [...(baseAllowFrom ?? []), ...storeAllow];\n if (isDm) {\n return evaluateAccess({\n context: {\n channel: 'dingtalk',\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: 'dingtalk',\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 async init(options: ChannelPluginInitOptions): Promise<void> {\n this.bus = options.bus;\n this.cfg = options.config;\n log.debug('DingTalk plugin initialized');\n }\n\n async start(options?: ChannelPluginStartOptions): Promise<void> {\n const section = this.cfg.channels?.dingtalk as DingtalkConfig | undefined;\n if (!section || section.enabled !== true) {\n return;\n }\n\n const ids = options?.accountId ? [options.accountId] : listDingtalkAccountIds(this.cfg);\n for (const accountId of ids) {\n const account = resolveDingtalkAccount(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 void runDingtalkStreamMonitor({\n account,\n bus: this.bus,\n abortSignal: ac.signal,\n security: {\n checkAccess: (ctx: ChannelSecurityContext) =>\n this.security.checkAccess?.(ctx, account, this.cfg) ?? { allowed: true },\n },\n }).catch((err) => {\n if ((err as { name?: string } | undefined)?.name === 'AbortError') {\n log.debug({ accountId }, 'DingTalk monitor stopped');\n return;\n }\n log.error({ err, accountId }, 'DingTalk monitor exited with error');\n });\n\n log.info({ accountId }, 'DingTalk Stream monitor started');\n }\n }\n\n async stop(accountId?: string): Promise<void> {\n const ids = accountId ? [accountId] : [...this.abortControllers.keys()];\n for (const id of ids) {\n const ac = this.abortControllers.get(id);\n if (ac) {\n ac.abort();\n this.abortControllers.delete(id);\n }\n }\n }\n\n channelIsRunning(cfg: Config): boolean {\n const ids = listDingtalkAccountIds(cfg);\n return ids.some((id) => {\n const a = resolveDingtalkAccount(cfg, id);\n return a.enabled && a.configured && this.abortControllers.has(id);\n });\n }\n\n async onConfigUpdated(cfg: Config): Promise<void> {\n const prev = this.cfg.channels?.dingtalk as unknown;\n const next = cfg.channels?.dingtalk 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 dingtalkPlugin = new DingtalkChannelPlugin();\n"],"mappings":"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"}
|
|
@@ -93,28 +93,28 @@ async function promptSecurityPolicies(ownerOpenId) {
|
|
|
93
93
|
const dmPolicy = await select({
|
|
94
94
|
message: "DM (private chat) policy:",
|
|
95
95
|
choices: [
|
|
96
|
+
{
|
|
97
|
+
value: "open",
|
|
98
|
+
name: "open [default]",
|
|
99
|
+
description: "Anyone can DM the bot after setup"
|
|
100
|
+
},
|
|
96
101
|
{
|
|
97
102
|
value: "pairing",
|
|
98
|
-
name: "pairing
|
|
99
|
-
description: "
|
|
103
|
+
name: "pairing",
|
|
104
|
+
description: "New users need `xopc channels pairing approve`"
|
|
100
105
|
},
|
|
101
106
|
{
|
|
102
107
|
value: "allowlist",
|
|
103
108
|
name: "allowlist",
|
|
104
109
|
description: "Only allowlisted users"
|
|
105
110
|
},
|
|
106
|
-
{
|
|
107
|
-
value: "open",
|
|
108
|
-
name: "open",
|
|
109
|
-
description: "Anyone can DM"
|
|
110
|
-
},
|
|
111
111
|
{
|
|
112
112
|
value: "disabled",
|
|
113
113
|
name: "disabled",
|
|
114
114
|
description: "Disable DMs"
|
|
115
115
|
}
|
|
116
116
|
],
|
|
117
|
-
default: "
|
|
117
|
+
default: "open"
|
|
118
118
|
});
|
|
119
119
|
let allowFrom = [];
|
|
120
120
|
if (dmPolicy === "allowlist") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-login.js","names":["fs"],"sources":["../../../../../extensions/feishu/src/adapters/cli-login.ts"],"sourcesContent":["/**\n * Feishu CLI Login adapter — implements `ChannelCliLoginAdapter`.\n *\n * Provides `xopc channels login --channel feishu` interactive credential setup.\n * Supports QR scan-to-create and manual credential input.\n */\n\nimport fs from 'node:fs';\n\nimport { confirm, input, select } from '@inquirer/prompts';\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport { mergeDistinctSenderIds } from '@xopcai/xopc/channels/pairing/index.js';\nimport type { ChannelCliLoginAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';\n\nimport {\n initAppRegistration,\n beginAppRegistration,\n pollAppRegistration,\n printQrCode,\n type FeishuDomain,\n} from '../auth/app-registration.js';\n\ntype DmPolicy = 'pairing' | 'allowlist' | 'open' | 'disabled';\ntype GroupPolicy = 'open' | 'disabled' | 'allowlist';\n\nfunction loadConfigFromPath(configPath: string): Config {\n try {\n const raw = fs.readFileSync(configPath, 'utf8');\n return JSON.parse(raw) as Config;\n } catch {\n return {} as Config;\n }\n}\n\nfunction writeConfigToPath(configPath: string, config: Config): void {\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n', 'utf8');\n}\n\nfunction isFeishuConfigured(config: Config): boolean {\n const feishu = config.channels?.feishu as Record<string, unknown> | undefined;\n if (!feishu) return false;\n const appId = typeof feishu.appId === 'string' ? feishu.appId.trim() : '';\n const appSecret = typeof feishu.appSecret === 'string' ? feishu.appSecret.trim() : '';\n return Boolean(appId && appSecret);\n}\n\nfunction parseAllowlistRaw(raw: string): Array<string | number> {\n if (!raw.trim()) return [];\n return raw\n .split(/[,\\s\\n]+/)\n .map((entry) => entry.trim())\n .filter(Boolean)\n .map((entry) => {\n const asNumber = parseInt(entry, 10);\n return !isNaN(asNumber) && String(asNumber) === entry ? asNumber : entry;\n });\n}\n\nasync function acquireCredentials(params: {\n existingAppId: string;\n timeoutMs: number;\n}): Promise<{\n appId: string;\n appSecret: string;\n domain: FeishuDomain;\n ownerOpenId?: string;\n}> {\n const domain = await select<FeishuDomain>({\n message: 'Feishu/Lark domain:',\n choices: [\n { value: 'feishu', name: 'feishu (open.feishu.cn)', description: 'China / Feishu' },\n { value: 'lark', name: 'lark (open.larksuite.com)', description: 'International / Lark' },\n ],\n default: 'feishu',\n });\n\n const canScan = await initAppRegistration(domain);\n const useScan = canScan\n ? await confirm({\n message: 'Create an app by scanning a QR code (recommended)?',\n default: true,\n })\n : false;\n\n if (useScan) {\n console.log('\\nScan this QR code with Feishu/Lark to create an app:\\n');\n const begin = await beginAppRegistration(domain);\n await printQrCode(begin.qrUrl);\n console.log('\\nWaiting for confirmation...\\n');\n\n const expireInSec = Math.min(begin.expireInSec, Math.floor(params.timeoutMs / 1000));\n const outcome = await pollAppRegistration({\n deviceCode: begin.deviceCode,\n intervalSec: begin.intervalSec,\n expireInSec,\n initialDomain: domain,\n });\n\n if (outcome.status === 'success') {\n console.log(`✅ App created. Domain: \"${outcome.result.domain}\".\\n`);\n return {\n appId: outcome.result.appId,\n appSecret: outcome.result.appSecret,\n domain: outcome.result.domain,\n ownerOpenId: outcome.result.openId,\n };\n }\n\n const reason =\n outcome.status === 'access_denied'\n ? 'User denied authorization.'\n : outcome.status === 'expired'\n ? 'Session expired.'\n : outcome.status === 'timeout'\n ? 'Scan timed out.'\n : `Error: ${'message' in outcome ? outcome.message : 'unknown'}`;\n console.log(`${reason} Falling back to manual input.\\n`);\n }\n\n console.log('📝 Enter Feishu app credentials (from Feishu Open Platform developer console):\\n');\n\n const appId = (\n await input({\n message: 'App ID (cli_xxx):',\n default: params.existingAppId || undefined,\n validate: (value) => value.trim().length > 0 || 'App ID cannot be empty',\n })\n ).trim();\n\n const appSecret = (\n await input({\n message: 'App Secret:',\n validate: (value) => value.trim().length > 0 || 'App Secret cannot be empty',\n })\n ).trim();\n\n return { appId, appSecret, domain };\n}\n\nasync function promptSecurityPolicies(ownerOpenId?: string): Promise<{\n dmPolicy: DmPolicy;\n groupPolicy: GroupPolicy;\n allowFrom: Array<string | number>;\n groupAllowFrom: Array<string | number>;\n requireMention: boolean;\n}> {\n const dmPolicy = await select<DmPolicy>({\n message: 'DM (private chat) policy:',\n choices: [\n {\n value: 'pairing',\n name: 'pairing [recommended]',\n description: 'Pairing code for new users; scanner pre-seeded after QR create',\n },\n { value: 'allowlist', name: 'allowlist', description: 'Only allowlisted users' },\n { value: 'open', name: 'open', description: 'Anyone can DM' },\n { value: 'disabled', name: 'disabled', description: 'Disable DMs' },\n ],\n default: 'pairing',\n });\n\n let allowFrom: Array<string | number> = [];\n if (dmPolicy === 'allowlist') {\n const defaultAllow = ownerOpenId ?? '';\n const raw = await input({\n message: 'Allowed user open_id / union_id (comma-separated):',\n default: defaultAllow,\n });\n allowFrom = parseAllowlistRaw(raw || defaultAllow);\n }\n\n const groupPolicy = await select<GroupPolicy>({\n message: 'Group chat policy:',\n choices: [\n { value: 'allowlist', name: 'allowlist [recommended]', description: 'Only allowlisted groups' },\n { value: 'open', name: 'open', description: 'All groups allowed' },\n { value: 'disabled', name: 'disabled', description: 'Disable groups' },\n ],\n default: 'allowlist',\n });\n\n let groupAllowFrom: Array<string | number> = [];\n if (groupPolicy === 'allowlist') {\n const raw = await input({\n message: 'Allowed group chat IDs (comma-separated, e.g. oc_xxx):',\n default: '',\n });\n groupAllowFrom = parseAllowlistRaw(raw);\n }\n\n const requireMention = await confirm({\n message: 'Require @mention in groups?',\n default: true,\n });\n\n const extras =\n ownerOpenId?.trim() && (dmPolicy === 'pairing' || dmPolicy === 'allowlist') ? [ownerOpenId.trim()] : [];\n const mergedAllowFrom = mergeDistinctSenderIds(allowFrom, extras);\n\n return { dmPolicy, groupPolicy, allowFrom: mergedAllowFrom, groupAllowFrom, requireMention };\n}\n\nexport const feishuCliLoginAdapter: ChannelCliLoginAdapter = {\n async runLogin(params) {\n const { configPath, verbose, timeoutMs = 480_000, accountId, writeConfig = true } = params;\n\n if (verbose) {\n console.log(`[feishu-login] configPath=${configPath}, timeoutMs=${timeoutMs}`);\n }\n\n const config = loadConfigFromPath(configPath);\n const existingFeishu = config.channels?.feishu as Record<string, unknown> | undefined;\n const existingAppId = typeof existingFeishu?.appId === 'string' ? existingFeishu.appId : '';\n const alreadyConfigured = isFeishuConfigured(config);\n\n console.log(`\\n${'='.repeat(50)}`);\n console.log('📱 Feishu / Lark login');\n console.log(`${'='.repeat(50)}\\n`);\n\n if (alreadyConfigured) {\n const reconfigure = await confirm({\n message: `Feishu is already configured (App ID: ${existingAppId}). Reconfigure?`,\n default: false,\n });\n if (!reconfigure) {\n console.log('Keeping existing configuration.\\n');\n return { ok: true, message: 'Existing configuration kept.', accountId };\n }\n }\n\n const credentials = await acquireCredentials({\n existingAppId,\n timeoutMs,\n });\n\n const policies = await promptSecurityPolicies(credentials.ownerOpenId);\n\n const nextFeishu: Record<string, unknown> = {\n ...(existingFeishu ?? {}),\n enabled: true,\n appId: credentials.appId,\n appSecret: credentials.appSecret,\n domain: credentials.domain,\n connectionMode: (existingFeishu?.connectionMode as string) || 'websocket',\n dmPolicy: policies.dmPolicy,\n groupPolicy: policies.groupPolicy,\n allowFrom: policies.allowFrom,\n groupAllowFrom: policies.groupAllowFrom,\n requireMention: policies.requireMention,\n };\n\n const nextConfig: Config = {\n ...config,\n channels: {\n ...config.channels,\n feishu: nextFeishu,\n },\n };\n\n if (writeConfig) {\n writeConfigToPath(configPath, nextConfig);\n console.log(`✅ Feishu configuration saved to ${configPath}\\n`);\n } else {\n console.log('✅ Feishu credentials acquired (--credentials-only: config not updated).\\n');\n }\n\n return {\n ok: true,\n message: `Feishu login complete (App ID: ${credentials.appId}).`,\n accountId: accountId ?? undefined,\n };\n },\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli-login.js","names":["fs"],"sources":["../../../../../extensions/feishu/src/adapters/cli-login.ts"],"sourcesContent":["/**\n * Feishu CLI Login adapter — implements `ChannelCliLoginAdapter`.\n *\n * Provides `xopc channels login --channel feishu` interactive credential setup.\n * Supports QR scan-to-create and manual credential input.\n */\n\nimport fs from 'node:fs';\n\nimport { confirm, input, select } from '@inquirer/prompts';\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport { mergeDistinctSenderIds } from '@xopcai/xopc/channels/pairing/index.js';\nimport type { ChannelCliLoginAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';\n\nimport {\n initAppRegistration,\n beginAppRegistration,\n pollAppRegistration,\n printQrCode,\n type FeishuDomain,\n} from '../auth/app-registration.js';\n\ntype DmPolicy = 'pairing' | 'allowlist' | 'open' | 'disabled';\ntype GroupPolicy = 'open' | 'disabled' | 'allowlist';\n\nfunction loadConfigFromPath(configPath: string): Config {\n try {\n const raw = fs.readFileSync(configPath, 'utf8');\n return JSON.parse(raw) as Config;\n } catch {\n return {} as Config;\n }\n}\n\nfunction writeConfigToPath(configPath: string, config: Config): void {\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n', 'utf8');\n}\n\nfunction isFeishuConfigured(config: Config): boolean {\n const feishu = config.channels?.feishu as Record<string, unknown> | undefined;\n if (!feishu) return false;\n const appId = typeof feishu.appId === 'string' ? feishu.appId.trim() : '';\n const appSecret = typeof feishu.appSecret === 'string' ? feishu.appSecret.trim() : '';\n return Boolean(appId && appSecret);\n}\n\nfunction parseAllowlistRaw(raw: string): Array<string | number> {\n if (!raw.trim()) return [];\n return raw\n .split(/[,\\s\\n]+/)\n .map((entry) => entry.trim())\n .filter(Boolean)\n .map((entry) => {\n const asNumber = parseInt(entry, 10);\n return !isNaN(asNumber) && String(asNumber) === entry ? asNumber : entry;\n });\n}\n\nasync function acquireCredentials(params: {\n existingAppId: string;\n timeoutMs: number;\n}): Promise<{\n appId: string;\n appSecret: string;\n domain: FeishuDomain;\n ownerOpenId?: string;\n}> {\n const domain = await select<FeishuDomain>({\n message: 'Feishu/Lark domain:',\n choices: [\n { value: 'feishu', name: 'feishu (open.feishu.cn)', description: 'China / Feishu' },\n { value: 'lark', name: 'lark (open.larksuite.com)', description: 'International / Lark' },\n ],\n default: 'feishu',\n });\n\n const canScan = await initAppRegistration(domain);\n const useScan = canScan\n ? await confirm({\n message: 'Create an app by scanning a QR code (recommended)?',\n default: true,\n })\n : false;\n\n if (useScan) {\n console.log('\\nScan this QR code with Feishu/Lark to create an app:\\n');\n const begin = await beginAppRegistration(domain);\n await printQrCode(begin.qrUrl);\n console.log('\\nWaiting for confirmation...\\n');\n\n const expireInSec = Math.min(begin.expireInSec, Math.floor(params.timeoutMs / 1000));\n const outcome = await pollAppRegistration({\n deviceCode: begin.deviceCode,\n intervalSec: begin.intervalSec,\n expireInSec,\n initialDomain: domain,\n });\n\n if (outcome.status === 'success') {\n console.log(`✅ App created. Domain: \"${outcome.result.domain}\".\\n`);\n return {\n appId: outcome.result.appId,\n appSecret: outcome.result.appSecret,\n domain: outcome.result.domain,\n ownerOpenId: outcome.result.openId,\n };\n }\n\n const reason =\n outcome.status === 'access_denied'\n ? 'User denied authorization.'\n : outcome.status === 'expired'\n ? 'Session expired.'\n : outcome.status === 'timeout'\n ? 'Scan timed out.'\n : `Error: ${'message' in outcome ? outcome.message : 'unknown'}`;\n console.log(`${reason} Falling back to manual input.\\n`);\n }\n\n console.log('📝 Enter Feishu app credentials (from Feishu Open Platform developer console):\\n');\n\n const appId = (\n await input({\n message: 'App ID (cli_xxx):',\n default: params.existingAppId || undefined,\n validate: (value) => value.trim().length > 0 || 'App ID cannot be empty',\n })\n ).trim();\n\n const appSecret = (\n await input({\n message: 'App Secret:',\n validate: (value) => value.trim().length > 0 || 'App Secret cannot be empty',\n })\n ).trim();\n\n return { appId, appSecret, domain };\n}\n\nasync function promptSecurityPolicies(ownerOpenId?: string): Promise<{\n dmPolicy: DmPolicy;\n groupPolicy: GroupPolicy;\n allowFrom: Array<string | number>;\n groupAllowFrom: Array<string | number>;\n requireMention: boolean;\n}> {\n const dmPolicy = await select<DmPolicy>({\n message: 'DM (private chat) policy:',\n choices: [\n { value: 'open', name: 'open [default]', description: 'Anyone can DM the bot after setup' },\n {\n value: 'pairing',\n name: 'pairing',\n description: 'New users need `xopc channels pairing approve`',\n },\n { value: 'allowlist', name: 'allowlist', description: 'Only allowlisted users' },\n { value: 'disabled', name: 'disabled', description: 'Disable DMs' },\n ],\n default: 'open',\n });\n\n let allowFrom: Array<string | number> = [];\n if (dmPolicy === 'allowlist') {\n const defaultAllow = ownerOpenId ?? '';\n const raw = await input({\n message: 'Allowed user open_id / union_id (comma-separated):',\n default: defaultAllow,\n });\n allowFrom = parseAllowlistRaw(raw || defaultAllow);\n }\n\n const groupPolicy = await select<GroupPolicy>({\n message: 'Group chat policy:',\n choices: [\n { value: 'allowlist', name: 'allowlist [recommended]', description: 'Only allowlisted groups' },\n { value: 'open', name: 'open', description: 'All groups allowed' },\n { value: 'disabled', name: 'disabled', description: 'Disable groups' },\n ],\n default: 'allowlist',\n });\n\n let groupAllowFrom: Array<string | number> = [];\n if (groupPolicy === 'allowlist') {\n const raw = await input({\n message: 'Allowed group chat IDs (comma-separated, e.g. oc_xxx):',\n default: '',\n });\n groupAllowFrom = parseAllowlistRaw(raw);\n }\n\n const requireMention = await confirm({\n message: 'Require @mention in groups?',\n default: true,\n });\n\n const extras =\n ownerOpenId?.trim() && (dmPolicy === 'pairing' || dmPolicy === 'allowlist') ? [ownerOpenId.trim()] : [];\n const mergedAllowFrom = mergeDistinctSenderIds(allowFrom, extras);\n\n return { dmPolicy, groupPolicy, allowFrom: mergedAllowFrom, groupAllowFrom, requireMention };\n}\n\nexport const feishuCliLoginAdapter: ChannelCliLoginAdapter = {\n async runLogin(params) {\n const { configPath, verbose, timeoutMs = 480_000, accountId, writeConfig = true } = params;\n\n if (verbose) {\n console.log(`[feishu-login] configPath=${configPath}, timeoutMs=${timeoutMs}`);\n }\n\n const config = loadConfigFromPath(configPath);\n const existingFeishu = config.channels?.feishu as Record<string, unknown> | undefined;\n const existingAppId = typeof existingFeishu?.appId === 'string' ? existingFeishu.appId : '';\n const alreadyConfigured = isFeishuConfigured(config);\n\n console.log(`\\n${'='.repeat(50)}`);\n console.log('📱 Feishu / Lark login');\n console.log(`${'='.repeat(50)}\\n`);\n\n if (alreadyConfigured) {\n const reconfigure = await confirm({\n message: `Feishu is already configured (App ID: ${existingAppId}). Reconfigure?`,\n default: false,\n });\n if (!reconfigure) {\n console.log('Keeping existing configuration.\\n');\n return { ok: true, message: 'Existing configuration kept.', accountId };\n }\n }\n\n const credentials = await acquireCredentials({\n existingAppId,\n timeoutMs,\n });\n\n const policies = await promptSecurityPolicies(credentials.ownerOpenId);\n\n const nextFeishu: Record<string, unknown> = {\n ...(existingFeishu ?? {}),\n enabled: true,\n appId: credentials.appId,\n appSecret: credentials.appSecret,\n domain: credentials.domain,\n connectionMode: (existingFeishu?.connectionMode as string) || 'websocket',\n dmPolicy: policies.dmPolicy,\n groupPolicy: policies.groupPolicy,\n allowFrom: policies.allowFrom,\n groupAllowFrom: policies.groupAllowFrom,\n requireMention: policies.requireMention,\n };\n\n const nextConfig: Config = {\n ...config,\n channels: {\n ...config.channels,\n feishu: nextFeishu,\n },\n };\n\n if (writeConfig) {\n writeConfigToPath(configPath, nextConfig);\n console.log(`✅ Feishu configuration saved to ${configPath}\\n`);\n } else {\n console.log('✅ Feishu credentials acquired (--credentials-only: config not updated).\\n');\n }\n\n return {\n ok: true,\n message: `Feishu login complete (App ID: ${credentials.appId}).`,\n accountId: accountId ?? undefined,\n };\n },\n};\n"],"mappings":"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"}
|
|
@@ -132,28 +132,28 @@ async function configureFeishu(config) {
|
|
|
132
132
|
const dmPolicy = await select({
|
|
133
133
|
message: "DM (private chat) policy:",
|
|
134
134
|
choices: [
|
|
135
|
+
{
|
|
136
|
+
value: "open",
|
|
137
|
+
name: "open [default]",
|
|
138
|
+
description: "Anyone can DM the bot after setup"
|
|
139
|
+
},
|
|
135
140
|
{
|
|
136
141
|
value: "pairing",
|
|
137
|
-
name: "pairing
|
|
138
|
-
description: "
|
|
142
|
+
name: "pairing",
|
|
143
|
+
description: "New users need `xopc channels pairing approve` (scanner pre-seeded after QR create)"
|
|
139
144
|
},
|
|
140
145
|
{
|
|
141
146
|
value: "allowlist",
|
|
142
147
|
name: "allowlist",
|
|
143
148
|
description: "Only allowlisted users can DM"
|
|
144
149
|
},
|
|
145
|
-
{
|
|
146
|
-
value: "open",
|
|
147
|
-
name: "open",
|
|
148
|
-
description: "Anyone can DM (not recommended)"
|
|
149
|
-
},
|
|
150
150
|
{
|
|
151
151
|
value: "disabled",
|
|
152
152
|
name: "disabled",
|
|
153
153
|
description: "Disable DMs"
|
|
154
154
|
}
|
|
155
155
|
],
|
|
156
|
-
default: "
|
|
156
|
+
default: "open"
|
|
157
157
|
});
|
|
158
158
|
let allowFrom;
|
|
159
159
|
if (dmPolicy === "allowlist") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"onboard-cli.js","names":[],"sources":["../../../../../extensions/feishu/src/adapters/onboard-cli.ts"],"sourcesContent":["/**\n * Feishu interactive onboarding (CLI onboard) — {@link ChannelOnboardAdapter}.\n *\n * This configures the single-account layout under `channels.feishu.*`.\n */\n\nimport { confirm, input, select } from '@inquirer/prompts';\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport { mergeDistinctSenderIds } from '@xopcai/xopc/channels/pairing/index.js';\nimport type { ChannelOnboardAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';\n\nimport {\n initAppRegistration,\n beginAppRegistration,\n pollAppRegistration,\n printQrCode,\n type FeishuDomain,\n} from '../auth/app-registration.js';\n\ntype DmPolicy = 'pairing' | 'allowlist' | 'open' | 'disabled';\ntype GroupPolicy = 'open' | 'disabled' | 'allowlist';\ntype RenderMode = 'auto' | 'raw' | 'card';\ntype ConnectionMode = 'websocket' | 'webhook';\ntype ReactionNotifications = 'off' | 'own' | 'all';\n\nfunction isFeishuConfigured(config: Config): boolean {\n const feishu = config.channels?.feishu as Record<string, unknown> | undefined;\n if (!feishu) return false;\n const appId = typeof feishu.appId === 'string' ? feishu.appId.trim() : '';\n const appSecret = typeof feishu.appSecret === 'string' ? feishu.appSecret.trim() : '';\n const enabled = feishu.enabled === true;\n return enabled && Boolean(appId && appSecret);\n}\n\nfunction parseAllowlistRaw(raw: string): Array<string | number> {\n if (!raw.trim()) return [];\n const entries = raw\n .split(/[,\\s\\n]+/)\n .map((s) => s.trim())\n .filter(Boolean);\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 configureFeishu(config: Config): Promise<Config> {\n console.log(`\\n${'='.repeat(50)}`);\n console.log('📱 Feishu / Lark setup');\n console.log(`${'='.repeat(50)}\\n`);\n\n const existing = config.channels?.feishu as Record<string, unknown> | undefined;\n const existingAppId = typeof existing?.appId === 'string' ? existing.appId : '';\n const existingDomain = typeof existing?.domain === 'string' ? existing.domain : '';\n\n if (existing?.enabled === true && existingAppId) {\n const keep = await confirm({\n message: 'A Feishu config already exists. Reconfigure it?',\n default: false,\n });\n if (!keep) return config;\n }\n\n const initialDomain = await select<FeishuDomain>({\n message: 'Feishu/Lark domain:',\n choices: [\n { value: 'feishu', name: 'feishu (open.feishu.cn)', description: 'China / Feishu' },\n { value: 'lark', name: 'lark (open.larksuite.com)', description: 'International / Lark' },\n ],\n default: existingDomain === 'lark' ? 'lark' : 'feishu',\n });\n\n const canScanToCreate = await initAppRegistration(initialDomain);\n const useScanToCreate = canScanToCreate\n ? await confirm({\n message: 'Create an app by scanning a QR code (recommended)?',\n default: true,\n })\n : false;\n\n let appId = '';\n let appSecret = '';\n let domain: FeishuDomain = initialDomain;\n let ownerOpenId: string | undefined;\n\n if (useScanToCreate) {\n console.log('\\nScan this QR code with Feishu/Lark to create an app:\\n');\n const begin = await beginAppRegistration(initialDomain);\n await printQrCode(begin.qrUrl);\n console.log('\\nWaiting for confirmation...\\n');\n const outcome = await pollAppRegistration({\n deviceCode: begin.deviceCode,\n intervalSec: begin.intervalSec,\n expireInSec: begin.expireInSec,\n initialDomain,\n });\n if (outcome.status !== 'success') {\n console.log('Scan-to-create did not complete. Falling back to manual input.\\n');\n } else {\n appId = outcome.result.appId;\n appSecret = outcome.result.appSecret;\n domain = outcome.result.domain;\n ownerOpenId = outcome.result.openId;\n console.log(`✅ App created. Domain detected as \"${domain}\".\\n`);\n }\n }\n\n if (!appId || !appSecret) {\n console.log('📝 Feishu app credentials (from Feishu Open Platform developer console):\\n');\n\n appId = (await input({\n message: 'App ID (cli_xxx):',\n default: existingAppId || undefined,\n validate: (v) => v.trim().length > 0 || 'App ID cannot be empty',\n })).trim();\n\n appSecret = (await input({\n message: 'App Secret:',\n validate: (v) => v.trim().length > 0 || 'App Secret cannot be empty',\n })).trim();\n\n domain = initialDomain;\n }\n\n const connectionMode = await select<ConnectionMode>({\n message: 'Connection mode:',\n choices: [\n { value: 'websocket', name: 'websocket [recommended]', description: 'Socket Mode (persistent connection)' },\n { value: 'webhook', name: 'webhook', description: 'Local HTTP server receives events' },\n ],\n default: 'websocket',\n });\n\n let verificationToken: string | undefined;\n let encryptKey: string | undefined;\n let webhookHost: string | undefined;\n let webhookPort: number | undefined;\n let webhookPath: string | undefined;\n if (connectionMode === 'webhook') {\n console.log('\\n🪝 Webhook secrets (from Feishu event subscription settings):\\n');\n verificationToken = (await input({\n message: 'Verification Token:',\n validate: (v) => v.trim().length > 0 || 'Verification Token cannot be empty',\n })).trim();\n encryptKey = (await input({\n message: 'Encrypt Key:',\n validate: (v) => v.trim().length > 0 || 'Encrypt Key cannot be empty',\n })).trim();\n webhookHost = (await input({\n message: 'Webhook host:',\n default: typeof existing?.webhookHost === 'string' ? existing.webhookHost : '127.0.0.1',\n })).trim();\n webhookPort = Number(\n (await input({\n message: 'Webhook port:',\n default: typeof existing?.webhookPort === 'number' ? String(existing.webhookPort) : '3000',\n })).trim(),\n );\n webhookPath = (await input({\n message: 'Webhook path:',\n default: typeof existing?.webhookPath === 'string' ? existing.webhookPath : '/feishu/events',\n })).trim();\n }\n\n const dmPolicy = await select<DmPolicy>({\n message: 'DM (private chat) policy:',\n choices: [\n {\n value: 'pairing',\n name: 'pairing [recommended]',\n description: 'Pairing code for new users; scanner pre-seeded after QR create',\n },\n { value: 'allowlist', name: 'allowlist', description: 'Only allowlisted users can DM' },\n { value: 'open', name: 'open', description: 'Anyone can DM (not recommended)' },\n { value: 'disabled', name: 'disabled', description: 'Disable DMs' },\n ],\n default: 'pairing',\n });\n\n let allowFrom: Array<string | number> | undefined;\n if (dmPolicy === 'allowlist') {\n const defaultAllow =\n ownerOpenId && (existing?.allowFrom == null || (Array.isArray(existing.allowFrom) && existing.allowFrom.length === 0))\n ? ownerOpenId\n : '';\n const raw = await input({\n message: 'Allowed user open_id / union_id / numeric ids (comma-separated):',\n default: defaultAllow,\n });\n allowFrom = parseAllowlistRaw(raw || defaultAllow);\n }\n\n const groupPolicy = await select<GroupPolicy>({\n message: 'Group chat policy:',\n choices: [\n { value: 'allowlist', name: 'allowlist [recommended]', description: 'Only allowlisted groups can use the bot' },\n { value: 'open', name: 'open', description: 'All groups allowed' },\n { value: 'disabled', name: 'disabled', description: 'Disable groups' },\n ],\n default: 'allowlist',\n });\n\n let groupAllowFrom: Array<string | number> | undefined;\n if (groupPolicy === 'allowlist') {\n const raw = await input({\n message: 'Allowed group chat IDs (comma-separated, e.g. oc_xxx):',\n default: '',\n });\n groupAllowFrom = parseAllowlistRaw(raw);\n }\n\n const requireMention = await confirm({\n message: 'Require @mention in groups?',\n default: true,\n });\n\n const renderMode = await select<RenderMode>({\n message: 'Default render mode:',\n choices: [\n { value: 'auto', name: 'auto', description: 'Let xopc decide (default)' },\n { value: 'raw', name: 'raw', description: 'Send plain text only' },\n { value: 'card', name: 'card', description: 'Prefer interactive cards (CardKit streaming)' },\n ],\n default: 'auto',\n });\n\n const streaming = await confirm({\n message: 'Enable streaming updates (Thinking… + incremental output)?',\n default: false,\n });\n\n const reactionNotifications = await select<ReactionNotifications>({\n message: 'Reaction notifications:',\n choices: [\n { value: 'off', name: 'off', description: 'Disable reaction notifications' },\n { value: 'own', name: 'own [recommended]', description: 'Only notify reactions to bot messages' },\n { value: 'all', name: 'all', description: 'Notify all reactions (noisy)' },\n ],\n default: 'own',\n });\n\n const enableFeishuTools = await select<'minimal' | 'docs' | 'full'>({\n message: 'Enable Feishu tools (docs/wiki/drive/etc.)?',\n choices: [\n { value: 'minimal', name: 'minimal', description: 'No extra tools (chat only)' },\n { value: 'docs', name: 'docs', description: 'Enable doc/wiki/drive/scopes (common)' },\n { value: 'full', name: 'full', description: 'Enable doc/wiki/drive/bitable/perm/scopes' },\n ],\n default: 'docs',\n });\n\n const tools =\n enableFeishuTools === 'minimal'\n ? { doc: false, wiki: false, drive: false, perm: false, bitable: false, scopes: true }\n : enableFeishuTools === 'docs'\n ? { doc: true, wiki: true, drive: true, perm: false, bitable: true, scopes: true }\n : { doc: true, wiki: true, drive: true, perm: true, bitable: true, scopes: true };\n\n const extras =\n ownerOpenId?.trim() && (dmPolicy === 'pairing' || dmPolicy === 'allowlist') ? [ownerOpenId.trim()] : [];\n const mergedAllowFrom = mergeDistinctSenderIds(\n allowFrom ?? (existing?.allowFrom as Array<string | number> | undefined) ?? [],\n extras,\n );\n\n const nextFeishu: Record<string, unknown> = {\n ...(existing ?? {}),\n enabled: true,\n appId,\n appSecret,\n domain,\n connectionMode,\n ...(connectionMode === 'webhook'\n ? {\n verificationToken,\n encryptKey,\n webhookHost,\n webhookPort,\n webhookPath,\n }\n : {}),\n dmPolicy,\n groupPolicy,\n allowFrom: mergedAllowFrom,\n groupAllowFrom: groupAllowFrom ?? (existing?.groupAllowFrom as any) ?? [],\n requireMention,\n renderMode,\n streaming,\n reactionNotifications,\n actions: { reactions: true },\n tools,\n historyLimit: typeof existing?.historyLimit === 'number' ? existing.historyLimit : 50,\n textChunkLimit: typeof existing?.textChunkLimit === 'number' ? existing.textChunkLimit : 4000,\n };\n\n const newConfig: Config = {\n ...config,\n channels: {\n ...config.channels,\n feishu: nextFeishu,\n },\n };\n\n console.log('\\n✅ Feishu configuration complete\\n');\n return newConfig;\n}\n\nexport const feishuOnboardAdapter: ChannelOnboardAdapter = {\n isConfigured: isFeishuConfigured,\n configure: configureFeishu,\n};\n\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"onboard-cli.js","names":[],"sources":["../../../../../extensions/feishu/src/adapters/onboard-cli.ts"],"sourcesContent":["/**\n * Feishu interactive onboarding (CLI onboard) — {@link ChannelOnboardAdapter}.\n *\n * This configures the single-account layout under `channels.feishu.*`.\n */\n\nimport { confirm, input, select } from '@inquirer/prompts';\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport { mergeDistinctSenderIds } from '@xopcai/xopc/channels/pairing/index.js';\nimport type { ChannelOnboardAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';\n\nimport {\n initAppRegistration,\n beginAppRegistration,\n pollAppRegistration,\n printQrCode,\n type FeishuDomain,\n} from '../auth/app-registration.js';\n\ntype DmPolicy = 'pairing' | 'allowlist' | 'open' | 'disabled';\ntype GroupPolicy = 'open' | 'disabled' | 'allowlist';\ntype RenderMode = 'auto' | 'raw' | 'card';\ntype ConnectionMode = 'websocket' | 'webhook';\ntype ReactionNotifications = 'off' | 'own' | 'all';\n\nfunction isFeishuConfigured(config: Config): boolean {\n const feishu = config.channels?.feishu as Record<string, unknown> | undefined;\n if (!feishu) return false;\n const appId = typeof feishu.appId === 'string' ? feishu.appId.trim() : '';\n const appSecret = typeof feishu.appSecret === 'string' ? feishu.appSecret.trim() : '';\n const enabled = feishu.enabled === true;\n return enabled && Boolean(appId && appSecret);\n}\n\nfunction parseAllowlistRaw(raw: string): Array<string | number> {\n if (!raw.trim()) return [];\n const entries = raw\n .split(/[,\\s\\n]+/)\n .map((s) => s.trim())\n .filter(Boolean);\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 configureFeishu(config: Config): Promise<Config> {\n console.log(`\\n${'='.repeat(50)}`);\n console.log('📱 Feishu / Lark setup');\n console.log(`${'='.repeat(50)}\\n`);\n\n const existing = config.channels?.feishu as Record<string, unknown> | undefined;\n const existingAppId = typeof existing?.appId === 'string' ? existing.appId : '';\n const existingDomain = typeof existing?.domain === 'string' ? existing.domain : '';\n\n if (existing?.enabled === true && existingAppId) {\n const keep = await confirm({\n message: 'A Feishu config already exists. Reconfigure it?',\n default: false,\n });\n if (!keep) return config;\n }\n\n const initialDomain = await select<FeishuDomain>({\n message: 'Feishu/Lark domain:',\n choices: [\n { value: 'feishu', name: 'feishu (open.feishu.cn)', description: 'China / Feishu' },\n { value: 'lark', name: 'lark (open.larksuite.com)', description: 'International / Lark' },\n ],\n default: existingDomain === 'lark' ? 'lark' : 'feishu',\n });\n\n const canScanToCreate = await initAppRegistration(initialDomain);\n const useScanToCreate = canScanToCreate\n ? await confirm({\n message: 'Create an app by scanning a QR code (recommended)?',\n default: true,\n })\n : false;\n\n let appId = '';\n let appSecret = '';\n let domain: FeishuDomain = initialDomain;\n let ownerOpenId: string | undefined;\n\n if (useScanToCreate) {\n console.log('\\nScan this QR code with Feishu/Lark to create an app:\\n');\n const begin = await beginAppRegistration(initialDomain);\n await printQrCode(begin.qrUrl);\n console.log('\\nWaiting for confirmation...\\n');\n const outcome = await pollAppRegistration({\n deviceCode: begin.deviceCode,\n intervalSec: begin.intervalSec,\n expireInSec: begin.expireInSec,\n initialDomain,\n });\n if (outcome.status !== 'success') {\n console.log('Scan-to-create did not complete. Falling back to manual input.\\n');\n } else {\n appId = outcome.result.appId;\n appSecret = outcome.result.appSecret;\n domain = outcome.result.domain;\n ownerOpenId = outcome.result.openId;\n console.log(`✅ App created. Domain detected as \"${domain}\".\\n`);\n }\n }\n\n if (!appId || !appSecret) {\n console.log('📝 Feishu app credentials (from Feishu Open Platform developer console):\\n');\n\n appId = (await input({\n message: 'App ID (cli_xxx):',\n default: existingAppId || undefined,\n validate: (v) => v.trim().length > 0 || 'App ID cannot be empty',\n })).trim();\n\n appSecret = (await input({\n message: 'App Secret:',\n validate: (v) => v.trim().length > 0 || 'App Secret cannot be empty',\n })).trim();\n\n domain = initialDomain;\n }\n\n const connectionMode = await select<ConnectionMode>({\n message: 'Connection mode:',\n choices: [\n { value: 'websocket', name: 'websocket [recommended]', description: 'Socket Mode (persistent connection)' },\n { value: 'webhook', name: 'webhook', description: 'Local HTTP server receives events' },\n ],\n default: 'websocket',\n });\n\n let verificationToken: string | undefined;\n let encryptKey: string | undefined;\n let webhookHost: string | undefined;\n let webhookPort: number | undefined;\n let webhookPath: string | undefined;\n if (connectionMode === 'webhook') {\n console.log('\\n🪝 Webhook secrets (from Feishu event subscription settings):\\n');\n verificationToken = (await input({\n message: 'Verification Token:',\n validate: (v) => v.trim().length > 0 || 'Verification Token cannot be empty',\n })).trim();\n encryptKey = (await input({\n message: 'Encrypt Key:',\n validate: (v) => v.trim().length > 0 || 'Encrypt Key cannot be empty',\n })).trim();\n webhookHost = (await input({\n message: 'Webhook host:',\n default: typeof existing?.webhookHost === 'string' ? existing.webhookHost : '127.0.0.1',\n })).trim();\n webhookPort = Number(\n (await input({\n message: 'Webhook port:',\n default: typeof existing?.webhookPort === 'number' ? String(existing.webhookPort) : '3000',\n })).trim(),\n );\n webhookPath = (await input({\n message: 'Webhook path:',\n default: typeof existing?.webhookPath === 'string' ? existing.webhookPath : '/feishu/events',\n })).trim();\n }\n\n const dmPolicy = await select<DmPolicy>({\n message: 'DM (private chat) policy:',\n choices: [\n { value: 'open', name: 'open [default]', description: 'Anyone can DM the bot after setup' },\n {\n value: 'pairing',\n name: 'pairing',\n description: 'New users need `xopc channels pairing approve` (scanner pre-seeded after QR create)',\n },\n { value: 'allowlist', name: 'allowlist', description: 'Only allowlisted users can DM' },\n { value: 'disabled', name: 'disabled', description: 'Disable DMs' },\n ],\n default: 'open',\n });\n\n let allowFrom: Array<string | number> | undefined;\n if (dmPolicy === 'allowlist') {\n const defaultAllow =\n ownerOpenId && (existing?.allowFrom == null || (Array.isArray(existing.allowFrom) && existing.allowFrom.length === 0))\n ? ownerOpenId\n : '';\n const raw = await input({\n message: 'Allowed user open_id / union_id / numeric ids (comma-separated):',\n default: defaultAllow,\n });\n allowFrom = parseAllowlistRaw(raw || defaultAllow);\n }\n\n const groupPolicy = await select<GroupPolicy>({\n message: 'Group chat policy:',\n choices: [\n { value: 'allowlist', name: 'allowlist [recommended]', description: 'Only allowlisted groups can use the bot' },\n { value: 'open', name: 'open', description: 'All groups allowed' },\n { value: 'disabled', name: 'disabled', description: 'Disable groups' },\n ],\n default: 'allowlist',\n });\n\n let groupAllowFrom: Array<string | number> | undefined;\n if (groupPolicy === 'allowlist') {\n const raw = await input({\n message: 'Allowed group chat IDs (comma-separated, e.g. oc_xxx):',\n default: '',\n });\n groupAllowFrom = parseAllowlistRaw(raw);\n }\n\n const requireMention = await confirm({\n message: 'Require @mention in groups?',\n default: true,\n });\n\n const renderMode = await select<RenderMode>({\n message: 'Default render mode:',\n choices: [\n { value: 'auto', name: 'auto', description: 'Let xopc decide (default)' },\n { value: 'raw', name: 'raw', description: 'Send plain text only' },\n { value: 'card', name: 'card', description: 'Prefer interactive cards (CardKit streaming)' },\n ],\n default: 'auto',\n });\n\n const streaming = await confirm({\n message: 'Enable streaming updates (Thinking… + incremental output)?',\n default: false,\n });\n\n const reactionNotifications = await select<ReactionNotifications>({\n message: 'Reaction notifications:',\n choices: [\n { value: 'off', name: 'off', description: 'Disable reaction notifications' },\n { value: 'own', name: 'own [recommended]', description: 'Only notify reactions to bot messages' },\n { value: 'all', name: 'all', description: 'Notify all reactions (noisy)' },\n ],\n default: 'own',\n });\n\n const enableFeishuTools = await select<'minimal' | 'docs' | 'full'>({\n message: 'Enable Feishu tools (docs/wiki/drive/etc.)?',\n choices: [\n { value: 'minimal', name: 'minimal', description: 'No extra tools (chat only)' },\n { value: 'docs', name: 'docs', description: 'Enable doc/wiki/drive/scopes (common)' },\n { value: 'full', name: 'full', description: 'Enable doc/wiki/drive/bitable/perm/scopes' },\n ],\n default: 'docs',\n });\n\n const tools =\n enableFeishuTools === 'minimal'\n ? { doc: false, wiki: false, drive: false, perm: false, bitable: false, scopes: true }\n : enableFeishuTools === 'docs'\n ? { doc: true, wiki: true, drive: true, perm: false, bitable: true, scopes: true }\n : { doc: true, wiki: true, drive: true, perm: true, bitable: true, scopes: true };\n\n const extras =\n ownerOpenId?.trim() && (dmPolicy === 'pairing' || dmPolicy === 'allowlist') ? [ownerOpenId.trim()] : [];\n const mergedAllowFrom = mergeDistinctSenderIds(\n allowFrom ?? (existing?.allowFrom as Array<string | number> | undefined) ?? [],\n extras,\n );\n\n const nextFeishu: Record<string, unknown> = {\n ...(existing ?? {}),\n enabled: true,\n appId,\n appSecret,\n domain,\n connectionMode,\n ...(connectionMode === 'webhook'\n ? {\n verificationToken,\n encryptKey,\n webhookHost,\n webhookPort,\n webhookPath,\n }\n : {}),\n dmPolicy,\n groupPolicy,\n allowFrom: mergedAllowFrom,\n groupAllowFrom: groupAllowFrom ?? (existing?.groupAllowFrom as any) ?? [],\n requireMention,\n renderMode,\n streaming,\n reactionNotifications,\n actions: { reactions: true },\n tools,\n historyLimit: typeof existing?.historyLimit === 'number' ? existing.historyLimit : 50,\n textChunkLimit: typeof existing?.textChunkLimit === 'number' ? existing.textChunkLimit : 4000,\n };\n\n const newConfig: Config = {\n ...config,\n channels: {\n ...config.channels,\n feishu: nextFeishu,\n },\n };\n\n console.log('\\n✅ Feishu configuration complete\\n');\n return newConfig;\n}\n\nexport const feishuOnboardAdapter: ChannelOnboardAdapter = {\n isConfigured: isFeishuConfigured,\n configure: configureFeishu,\n};\n\n"],"mappings":"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"}
|
|
@@ -86,7 +86,7 @@ var FeishuChannelPlugin = class {
|
|
|
86
86
|
})
|
|
87
87
|
};
|
|
88
88
|
security = {
|
|
89
|
-
resolveDmPolicy: ({ account }) => resolveDmPolicy(account.dmPolicy, "
|
|
89
|
+
resolveDmPolicy: ({ account }) => resolveDmPolicy(account.dmPolicy, "open"),
|
|
90
90
|
resolveGroupPolicy: ({ account }) => resolveGroupPolicy(account.groupPolicy, "allowlist"),
|
|
91
91
|
checkAccess: (ctx, account, _cfg) => {
|
|
92
92
|
const isDm = !ctx.isGroup;
|
|
@@ -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 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 { ChannelCliLoginAdapter } 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';\n\nimport { FeishuConfigSchema, type FeishuConfig } from './schema/config-schema.js';\nimport { listFeishuAccountIds, resolveFeishuAccount, type ResolvedFeishuAccount } from './state/accounts.js';\nimport { createFeishuSocketModeMonitor } from './transport/socket-mode/monitor.js';\nimport { createFeishuWebhookMonitor } from './transport/webhook/monitor.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 {\n addReactionFeishu,\n editMessageFeishu,\n getMessageFeishu,\n listPinsFeishu,\n listReactionsFeishu,\n pinMessageFeishu,\n removeReactionFeishu,\n unpinMessageFeishu,\n} from './outbound/actions.js';\nimport { createFeishuDirectoryAdapter } from './directory/directory-adapter.js';\nimport { feishuWhoAmI } from './tools/tools.js';\nimport { feishuCliLoginAdapter } from './adapters/cli-login.js';\nimport { feishuOnboardAdapter } from './adapters/onboard-cli.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: 0 },\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 onboard = feishuOnboardAdapter;\n\n readonly cliLogin: ChannelCliLoginAdapter = feishuCliLoginAdapter;\n\n private bus!: MessageBus;\n private cfg!: Config;\n private abortControllers = new Map<string, AbortController>();\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, 'pairing'),\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 = createFeishuDirectoryAdapter();\n\n actions = {\n async handleAction(_ctx: any): Promise<void> {\n // TODO: Wire interactive card actions (card.action.trigger) to this adapter.\n },\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 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 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 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 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 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 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 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 return await pinMessageFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId });\n }\n if (action === 'unpin') {\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 log.debug('Feishu plugin initialized');\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 monitor = createFeishuSocketModeMonitor({\n account,\n config: this.cfg,\n bus: this.bus,\n abortSignal: ac.signal,\n security: {\n checkAccess: (ctx: ChannelSecurityContext) => this.security.checkAccess?.(ctx, account, this.cfg),\n },\n });\n\n const runner =\n account.connectionMode === 'webhook'\n ? createFeishuWebhookMonitor({\n account,\n config: this.cfg,\n bus: this.bus,\n abortSignal: ac.signal,\n security: {\n checkAccess: (ctx: ChannelSecurityContext) =>\n this.security.checkAccess?.(ctx, account, this.cfg),\n },\n })\n : monitor;\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 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 }\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\n"],"mappings":"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"}
|
|
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 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 { ChannelCliLoginAdapter } 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';\n\nimport { FeishuConfigSchema, type FeishuConfig } from './schema/config-schema.js';\nimport { listFeishuAccountIds, resolveFeishuAccount, type ResolvedFeishuAccount } from './state/accounts.js';\nimport { createFeishuSocketModeMonitor } from './transport/socket-mode/monitor.js';\nimport { createFeishuWebhookMonitor } from './transport/webhook/monitor.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 {\n addReactionFeishu,\n editMessageFeishu,\n getMessageFeishu,\n listPinsFeishu,\n listReactionsFeishu,\n pinMessageFeishu,\n removeReactionFeishu,\n unpinMessageFeishu,\n} from './outbound/actions.js';\nimport { createFeishuDirectoryAdapter } from './directory/directory-adapter.js';\nimport { feishuWhoAmI } from './tools/tools.js';\nimport { feishuCliLoginAdapter } from './adapters/cli-login.js';\nimport { feishuOnboardAdapter } from './adapters/onboard-cli.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: 0 },\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 onboard = feishuOnboardAdapter;\n\n readonly cliLogin: ChannelCliLoginAdapter = feishuCliLoginAdapter;\n\n private bus!: MessageBus;\n private cfg!: Config;\n private abortControllers = new Map<string, AbortController>();\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 = createFeishuDirectoryAdapter();\n\n actions = {\n async handleAction(_ctx: any): Promise<void> {\n // TODO: Wire interactive card actions (card.action.trigger) to this adapter.\n },\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 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 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 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 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 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 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 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 return await pinMessageFeishu({ cfg: this.cfg, accountId: toolCtx.accountId, messageId });\n }\n if (action === 'unpin') {\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 log.debug('Feishu plugin initialized');\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 monitor = createFeishuSocketModeMonitor({\n account,\n config: this.cfg,\n bus: this.bus,\n abortSignal: ac.signal,\n security: {\n checkAccess: (ctx: ChannelSecurityContext) => this.security.checkAccess?.(ctx, account, this.cfg),\n },\n });\n\n const runner =\n account.connectionMode === 'webhook'\n ? createFeishuWebhookMonitor({\n account,\n config: this.cfg,\n bus: this.bus,\n abortSignal: ac.signal,\n security: {\n checkAccess: (ctx: ChannelSecurityContext) =>\n this.security.checkAccess?.(ctx, account, this.cfg),\n },\n })\n : monitor;\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 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 }\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\n"],"mappings":"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"}
|
|
@@ -23,7 +23,7 @@ const FeishuAccountConfigSchema = z.object({
|
|
|
23
23
|
"allowlist",
|
|
24
24
|
"open",
|
|
25
25
|
"disabled"
|
|
26
|
-
]).default("
|
|
26
|
+
]).default("open").optional(),
|
|
27
27
|
groupPolicy: z.enum([
|
|
28
28
|
"open",
|
|
29
29
|
"disabled",
|
|
@@ -91,7 +91,7 @@ const FeishuConfigSchema = z.object({
|
|
|
91
91
|
"allowlist",
|
|
92
92
|
"open",
|
|
93
93
|
"disabled"
|
|
94
|
-
]).default("
|
|
94
|
+
]).default("open").optional(),
|
|
95
95
|
groupPolicy: z.enum([
|
|
96
96
|
"open",
|
|
97
97
|
"disabled",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-schema.js","names":[],"sources":["../../../../../extensions/feishu/src/schema/config-schema.ts"],"sourcesContent":["import { z } from 'zod';\n\nexport const FeishuAccountConfigSchema = z.object({\n name: z.string().optional(),\n enabled: z.boolean().optional(),\n\n /** Feishu app credentials */\n appId: z.string().optional(),\n appSecret: z.string().optional(),\n\n /** feishu | lark | https://open.feishu.cn (custom base) */\n domain: z.union([z.enum(['feishu', 'lark']), z.string().url()]).optional(),\n\n /** Socket Mode is the default transport in xopc. */\n connectionMode: z.enum(['websocket', 'webhook']).default('websocket').optional(),\n\n /** Webhook mode transport config (openclaw parity). */\n webhookHost: z.string().optional(),\n webhookPort: z.number().int().positive().optional(),\n webhookPath: z.string().optional(),\n\n /** Webhook mode secrets (openclaw parity). */\n verificationToken: z.string().optional(),\n encryptKey: z.string().optional(),\n\n /** Access control */\n dmPolicy: z.enum(['pairing', 'allowlist', 'open', 'disabled']).default('pairing').optional(),\n groupPolicy: z.enum(['open', 'disabled', 'allowlist']).default('allowlist').optional(),\n allowFrom: z.array(z.union([z.string(), z.number()])).optional(),\n groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),\n requireMention: z.boolean().optional(),\n\n /** Messaging */\n historyLimit: z.number().int().min(0).optional(),\n textChunkLimit: z.number().int().positive().optional(),\n renderMode: z.enum(['auto', 'raw', 'card']).optional(),\n replyInThread: z.enum(['disabled', 'enabled']).optional(),\n typingIndicator: z.boolean().optional(),\n reactionNotifications: z.enum(['off', 'own', 'all']).optional(),\n\n /** Streaming */\n streaming: z.boolean().optional(),\n blockStreamingCoalesce: z\n .object({\n enabled: z.boolean().optional(),\n minChars: z.number().int().positive().optional(),\n idleMs: z.number().int().positive().optional(),\n })\n .strict()\n .optional(),\n\n /** Tool gates (parity surface; implementations come later) */\n tools: z\n .object({\n doc: z.boolean().optional(),\n chat: z.boolean().optional(),\n wiki: z.boolean().optional(),\n drive: z.boolean().optional(),\n perm: z.boolean().optional(),\n bitable: z.boolean().optional(),\n scopes: z.boolean().optional(),\n })\n .strict()\n .optional(),\n\n actions: z\n .object({\n reactions: z.boolean().optional(),\n })\n .strict()\n .optional(),\n\n /** Dynamic agent creation for DMs (parity surface; core wiring comes later) */\n dynamicAgentCreation: z\n .object({\n enabled: z.boolean().optional(),\n workspaceTemplate: z.string().optional(),\n agentDirTemplate: z.string().optional(),\n maxAgents: z.number().int().positive().optional(),\n })\n .strict()\n .optional(),\n});\n\nexport const FeishuConfigSchema = z\n .object({\n enabled: z.boolean().default(false),\n\n defaultAccount: z.string().optional(),\n\n /** Single-account shorthand (backward compatible in our own schema) */\n appId: z.string().optional(),\n appSecret: z.string().optional(),\n domain: z.union([z.enum(['feishu', 'lark']), z.string().url()]).default('feishu').optional(),\n connectionMode: z.enum(['websocket', 'webhook']).default('websocket').optional(),\n\n webhookHost: z.string().optional(),\n webhookPort: z.number().int().positive().optional(),\n webhookPath: z.string().optional(),\n verificationToken: z.string().optional(),\n encryptKey: z.string().optional(),\n\n dmPolicy: z.enum(['pairing', 'allowlist', 'open', 'disabled']).default('pairing').optional(),\n groupPolicy: z.enum(['open', 'disabled', 'allowlist']).default('allowlist').optional(),\n allowFrom: z.array(z.union([z.string(), z.number()])).default([]).optional(),\n groupAllowFrom: z.array(z.union([z.string(), z.number()])).default([]).optional(),\n requireMention: z.boolean().optional(),\n\n historyLimit: z.number().int().min(0).default(50).optional(),\n textChunkLimit: z.number().int().positive().default(4000).optional(),\n reactionNotifications: z.enum(['off', 'own', 'all']).optional(),\n\n streaming: z.boolean().optional(),\n blockStreamingCoalesce: z\n .object({\n enabled: z.boolean().optional(),\n minChars: z.number().int().positive().optional(),\n idleMs: z.number().int().positive().optional(),\n })\n .strict()\n .optional(),\n\n tools: z\n .object({\n doc: z.boolean().optional(),\n chat: z.boolean().optional(),\n wiki: z.boolean().optional(),\n drive: z.boolean().optional(),\n perm: z.boolean().optional(),\n bitable: z.boolean().optional(),\n scopes: z.boolean().optional(),\n })\n .strict()\n .optional(),\n\n actions: z\n .object({\n reactions: z.boolean().optional(),\n })\n .strict()\n .optional(),\n\n dynamicAgentCreation: z\n .object({\n enabled: z.boolean().optional(),\n workspaceTemplate: z.string().optional(),\n agentDirTemplate: z.string().optional(),\n maxAgents: z.number().int().positive().optional(),\n })\n .strict()\n .optional(),\n\n accounts: z.record(z.string(), FeishuAccountConfigSchema).optional(),\n})\n .superRefine((value, ctx) => {\n // Single-account layout required fields when enabled.\n if (value.enabled) {\n const hasNamed = value.accounts && Object.keys(value.accounts).length > 0;\n if (!hasNamed) {\n if (!value.appId?.trim()) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['appId'],\n message: 'channels.feishu.enabled=true requires channels.feishu.appId (or configure channels.feishu.accounts)',\n });\n }\n if (!value.appSecret?.trim()) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['appSecret'],\n message:\n 'channels.feishu.enabled=true requires channels.feishu.appSecret (or configure channels.feishu.accounts)',\n });\n }\n }\n }\n\n // Multi-account required fields when account is enabled.\n for (const [id, acc] of Object.entries(value.accounts ?? {})) {\n if (!acc) continue;\n if (acc.enabled === false) continue;\n const effectiveAppId = acc.appId?.trim() || value.appId?.trim() || '';\n const effectiveAppSecret = acc.appSecret?.trim() || value.appSecret?.trim() || '';\n if (!effectiveAppId) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['accounts', id, 'appId'],\n message: `channels.feishu.accounts.${id} requires appId (or top-level channels.feishu.appId)`,\n });\n }\n if (!effectiveAppSecret) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['accounts', id, 'appSecret'],\n message: `channels.feishu.accounts.${id} requires appSecret (or top-level channels.feishu.appSecret)`,\n });\n }\n\n const effectiveConnectionMode = (acc.connectionMode ?? value.connectionMode ?? 'websocket') as\n | 'websocket'\n | 'webhook';\n if (effectiveConnectionMode === 'webhook') {\n const vt = acc.verificationToken?.trim() || value.verificationToken?.trim() || '';\n const ek = acc.encryptKey?.trim() || value.encryptKey?.trim() || '';\n if (!vt) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['accounts', id, 'verificationToken'],\n message: `channels.feishu.accounts.${id} webhook mode requires verificationToken (or top-level channels.feishu.verificationToken)`,\n });\n }\n if (!ek) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['accounts', id, 'encryptKey'],\n message: `channels.feishu.accounts.${id} webhook mode requires encryptKey (or top-level channels.feishu.encryptKey)`,\n });\n }\n }\n }\n });\n\nexport type FeishuConfig = z.infer<typeof FeishuConfigSchema>;\nexport type FeishuAccountConfig = z.infer<typeof FeishuAccountConfigSchema>;\n\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"config-schema.js","names":[],"sources":["../../../../../extensions/feishu/src/schema/config-schema.ts"],"sourcesContent":["import { z } from 'zod';\n\nexport const FeishuAccountConfigSchema = z.object({\n name: z.string().optional(),\n enabled: z.boolean().optional(),\n\n /** Feishu app credentials */\n appId: z.string().optional(),\n appSecret: z.string().optional(),\n\n /** feishu | lark | https://open.feishu.cn (custom base) */\n domain: z.union([z.enum(['feishu', 'lark']), z.string().url()]).optional(),\n\n /** Socket Mode is the default transport in xopc. */\n connectionMode: z.enum(['websocket', 'webhook']).default('websocket').optional(),\n\n /** Webhook mode transport config (openclaw parity). */\n webhookHost: z.string().optional(),\n webhookPort: z.number().int().positive().optional(),\n webhookPath: z.string().optional(),\n\n /** Webhook mode secrets (openclaw parity). */\n verificationToken: z.string().optional(),\n encryptKey: z.string().optional(),\n\n /** Access control */\n dmPolicy: z.enum(['pairing', 'allowlist', 'open', 'disabled']).default('open').optional(),\n groupPolicy: z.enum(['open', 'disabled', 'allowlist']).default('allowlist').optional(),\n allowFrom: z.array(z.union([z.string(), z.number()])).optional(),\n groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),\n requireMention: z.boolean().optional(),\n\n /** Messaging */\n historyLimit: z.number().int().min(0).optional(),\n textChunkLimit: z.number().int().positive().optional(),\n renderMode: z.enum(['auto', 'raw', 'card']).optional(),\n replyInThread: z.enum(['disabled', 'enabled']).optional(),\n typingIndicator: z.boolean().optional(),\n reactionNotifications: z.enum(['off', 'own', 'all']).optional(),\n\n /** Streaming */\n streaming: z.boolean().optional(),\n blockStreamingCoalesce: z\n .object({\n enabled: z.boolean().optional(),\n minChars: z.number().int().positive().optional(),\n idleMs: z.number().int().positive().optional(),\n })\n .strict()\n .optional(),\n\n /** Tool gates (parity surface; implementations come later) */\n tools: z\n .object({\n doc: z.boolean().optional(),\n chat: z.boolean().optional(),\n wiki: z.boolean().optional(),\n drive: z.boolean().optional(),\n perm: z.boolean().optional(),\n bitable: z.boolean().optional(),\n scopes: z.boolean().optional(),\n })\n .strict()\n .optional(),\n\n actions: z\n .object({\n reactions: z.boolean().optional(),\n })\n .strict()\n .optional(),\n\n /** Dynamic agent creation for DMs (parity surface; core wiring comes later) */\n dynamicAgentCreation: z\n .object({\n enabled: z.boolean().optional(),\n workspaceTemplate: z.string().optional(),\n agentDirTemplate: z.string().optional(),\n maxAgents: z.number().int().positive().optional(),\n })\n .strict()\n .optional(),\n});\n\nexport const FeishuConfigSchema = z\n .object({\n enabled: z.boolean().default(false),\n\n defaultAccount: z.string().optional(),\n\n /** Single-account shorthand (backward compatible in our own schema) */\n appId: z.string().optional(),\n appSecret: z.string().optional(),\n domain: z.union([z.enum(['feishu', 'lark']), z.string().url()]).default('feishu').optional(),\n connectionMode: z.enum(['websocket', 'webhook']).default('websocket').optional(),\n\n webhookHost: z.string().optional(),\n webhookPort: z.number().int().positive().optional(),\n webhookPath: z.string().optional(),\n verificationToken: z.string().optional(),\n encryptKey: z.string().optional(),\n\n dmPolicy: z.enum(['pairing', 'allowlist', 'open', 'disabled']).default('open').optional(),\n groupPolicy: z.enum(['open', 'disabled', 'allowlist']).default('allowlist').optional(),\n allowFrom: z.array(z.union([z.string(), z.number()])).default([]).optional(),\n groupAllowFrom: z.array(z.union([z.string(), z.number()])).default([]).optional(),\n requireMention: z.boolean().optional(),\n\n historyLimit: z.number().int().min(0).default(50).optional(),\n textChunkLimit: z.number().int().positive().default(4000).optional(),\n reactionNotifications: z.enum(['off', 'own', 'all']).optional(),\n\n streaming: z.boolean().optional(),\n blockStreamingCoalesce: z\n .object({\n enabled: z.boolean().optional(),\n minChars: z.number().int().positive().optional(),\n idleMs: z.number().int().positive().optional(),\n })\n .strict()\n .optional(),\n\n tools: z\n .object({\n doc: z.boolean().optional(),\n chat: z.boolean().optional(),\n wiki: z.boolean().optional(),\n drive: z.boolean().optional(),\n perm: z.boolean().optional(),\n bitable: z.boolean().optional(),\n scopes: z.boolean().optional(),\n })\n .strict()\n .optional(),\n\n actions: z\n .object({\n reactions: z.boolean().optional(),\n })\n .strict()\n .optional(),\n\n dynamicAgentCreation: z\n .object({\n enabled: z.boolean().optional(),\n workspaceTemplate: z.string().optional(),\n agentDirTemplate: z.string().optional(),\n maxAgents: z.number().int().positive().optional(),\n })\n .strict()\n .optional(),\n\n accounts: z.record(z.string(), FeishuAccountConfigSchema).optional(),\n})\n .superRefine((value, ctx) => {\n // Single-account layout required fields when enabled.\n if (value.enabled) {\n const hasNamed = value.accounts && Object.keys(value.accounts).length > 0;\n if (!hasNamed) {\n if (!value.appId?.trim()) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['appId'],\n message: 'channels.feishu.enabled=true requires channels.feishu.appId (or configure channels.feishu.accounts)',\n });\n }\n if (!value.appSecret?.trim()) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['appSecret'],\n message:\n 'channels.feishu.enabled=true requires channels.feishu.appSecret (or configure channels.feishu.accounts)',\n });\n }\n }\n }\n\n // Multi-account required fields when account is enabled.\n for (const [id, acc] of Object.entries(value.accounts ?? {})) {\n if (!acc) continue;\n if (acc.enabled === false) continue;\n const effectiveAppId = acc.appId?.trim() || value.appId?.trim() || '';\n const effectiveAppSecret = acc.appSecret?.trim() || value.appSecret?.trim() || '';\n if (!effectiveAppId) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['accounts', id, 'appId'],\n message: `channels.feishu.accounts.${id} requires appId (or top-level channels.feishu.appId)`,\n });\n }\n if (!effectiveAppSecret) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['accounts', id, 'appSecret'],\n message: `channels.feishu.accounts.${id} requires appSecret (or top-level channels.feishu.appSecret)`,\n });\n }\n\n const effectiveConnectionMode = (acc.connectionMode ?? value.connectionMode ?? 'websocket') as\n | 'websocket'\n | 'webhook';\n if (effectiveConnectionMode === 'webhook') {\n const vt = acc.verificationToken?.trim() || value.verificationToken?.trim() || '';\n const ek = acc.encryptKey?.trim() || value.encryptKey?.trim() || '';\n if (!vt) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['accounts', id, 'verificationToken'],\n message: `channels.feishu.accounts.${id} webhook mode requires verificationToken (or top-level channels.feishu.verificationToken)`,\n });\n }\n if (!ek) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['accounts', id, 'encryptKey'],\n message: `channels.feishu.accounts.${id} webhook mode requires encryptKey (or top-level channels.feishu.encryptKey)`,\n });\n }\n }\n }\n });\n\nexport type FeishuConfig = z.infer<typeof FeishuConfigSchema>;\nexport type FeishuAccountConfig = z.infer<typeof FeishuAccountConfigSchema>;\n\n"],"mappings":"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"}
|
|
@@ -74,7 +74,7 @@ function resolveFeishuAccount(cfg, accountId) {
|
|
|
74
74
|
webhookPath: merged.webhookPath,
|
|
75
75
|
verificationToken: merged.verificationToken,
|
|
76
76
|
encryptKey: merged.encryptKey,
|
|
77
|
-
dmPolicy: merged.dmPolicy ?? "
|
|
77
|
+
dmPolicy: merged.dmPolicy ?? "open",
|
|
78
78
|
groupPolicy: merged.groupPolicy ?? "allowlist",
|
|
79
79
|
allowFrom: merged.allowFrom,
|
|
80
80
|
groupAllowFrom: merged.groupAllowFrom,
|