@xmanrui/dsh-im 4.22.0 → 4.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +5 -4
- package/README.md +5 -4
- package/lib/client.js +2584 -1153
- package/lib/index.js +290 -298
- package/package.json +14 -2
- package/plugin-src/client/channel-card-meta.js +2 -0
- package/plugin-src/client/channel-logos.js +11 -0
- package/plugin-src/client/channels/dingtalk/api.js +5 -1
- package/plugin-src/client/channels/dingtalk/index.js +15 -13
- package/plugin-src/client/channels/email/api.js +52 -0
- package/plugin-src/client/channels/email/index.js +728 -0
- package/plugin-src/client/channels/email/styles.js +51 -0
- package/plugin-src/client/channels/feishu/api.js +4 -2
- package/plugin-src/client/channels/feishu/index.js +23 -7
- package/plugin-src/client/channels/imessage/index.js +2 -1
- package/plugin-src/client/channels/office/api.js +2 -0
- package/plugin-src/client/channels/office/index.js +6 -5
- package/plugin-src/client/channels/qq/api.js +7 -0
- package/plugin-src/client/channels/qq/index.js +16 -3
- package/plugin-src/client/channels/shared/token-api.js +8 -1
- package/plugin-src/client/channels/shared/token-channel.js +28 -3
- package/plugin-src/client/channels/slack/index.js +2 -1
- package/plugin-src/client/channels/telegram/index.js +3 -0
- package/plugin-src/client/channels/telegram/styles.js +12 -0
- package/plugin-src/client/channels/telegram/thinking-traces.js +25 -0
- package/plugin-src/client/channels/wecom/api.js +7 -1
- package/plugin-src/client/channels/wecom/index.js +16 -3
- package/plugin-src/client/channels/wecom-app/api.js +6 -1
- package/plugin-src/client/channels/wecom-app/index.js +16 -3
- package/plugin-src/client/channels/weixin/api.js +2 -1
- package/plugin-src/client/channels/weixin/connection-error.js +1 -71
- package/plugin-src/client/channels/whatsapp/api.js +5 -0
- package/plugin-src/client/channels/whatsapp/index.js +16 -3
- package/plugin-src/client/connection-error.js +87 -0
- package/plugin-src/client/global-settings.js +81 -1
- package/plugin-src/client/i18n.js +69 -1
- package/plugin-src/client/index.js +40 -4
- package/plugin-src/client/last-message-error.js +2 -1
- package/plugin-src/client/styles.js +8 -2
- package/plugin-src/host/build.mjs +4 -0
- package/plugin-src/host/channels/dingtalk/production.mjs +2 -0
- package/plugin-src/host/channels/dingtalk/rpc.mjs +7 -4
- package/plugin-src/host/channels/email/availability.mjs +60 -0
- package/plugin-src/host/channels/email/index.mjs +36 -0
- package/plugin-src/host/channels/email/production.mjs +58 -0
- package/plugin-src/host/channels/email/rpc.mjs +163 -0
- package/plugin-src/host/channels/feishu/production.mjs +3 -0
- package/plugin-src/host/channels/feishu/rpc.mjs +7 -4
- package/plugin-src/host/channels/imessage/rpc.mjs +13 -6
- package/plugin-src/host/channels/office/rpc.mjs +3 -1
- package/plugin-src/host/channels/qq/production.mjs +2 -0
- package/plugin-src/host/channels/qq/rpc.mjs +5 -3
- package/plugin-src/host/channels/shared/production.mjs +52 -5
- package/plugin-src/host/channels/shared/rpc.mjs +14 -3
- package/plugin-src/host/channels/shared/startup-error.mjs +4 -3
- package/plugin-src/host/channels/shared/startup.mjs +11 -6
- package/plugin-src/host/channels/shared/thinking-traces-rpc.mjs +11 -0
- package/plugin-src/host/channels/slack/production.mjs +2 -0
- package/plugin-src/host/channels/slack/rpc.mjs +5 -3
- package/plugin-src/host/channels/wecom/production.mjs +2 -0
- package/plugin-src/host/channels/wecom/rpc.mjs +5 -3
- package/plugin-src/host/channels/wecom-app/production.mjs +2 -0
- package/plugin-src/host/channels/wecom-app/rpc.mjs +5 -3
- package/plugin-src/host/channels/weixin/production.mjs +2 -0
- package/plugin-src/host/channels/whatsapp/production.mjs +2 -0
- package/plugin-src/host/channels/whatsapp/rpc.mjs +5 -3
- package/plugin-src/host/delivery-adapter.mjs +11 -0
- package/plugin-src/host/image-input-rpc.mjs +24 -0
- package/plugin-src/host/inbound-ttl-rpc.mjs +6 -1
- package/plugin-src/host/index.mjs +3 -0
- package/plugin-src/host/modern-harness-api.mjs +7 -2
- package/plugin-src/management-rpc.mjs +12 -2
- package/scripts/verify-package.mjs +13 -5
- package/src/channels/dingtalk/connection-error.mjs +5 -12
- package/src/channels/dingtalk/device-auth.mjs +4 -1
- package/src/channels/dingtalk/dingtalk-bridge.mjs +6 -4
- package/src/channels/dingtalk/dingtalk-controller.mjs +60 -45
- package/src/channels/dingtalk/dingtalk-runtime.mjs +20 -4
- package/src/channels/discord/discord-api.mjs +3 -3
- package/src/channels/discord/discord-runtime.mjs +18 -12
- package/src/channels/email/config-store.mjs +194 -0
- package/src/channels/email/email-api.mjs +20 -0
- package/src/channels/email/email-bridge.mjs +17 -0
- package/src/channels/email/email-controller.mjs +824 -0
- package/src/channels/email/email-runtime.mjs +699 -0
- package/src/channels/email/harness-client.mjs +7 -0
- package/src/channels/email/mail-format.mjs +91 -0
- package/src/channels/email/state-store.mjs +176 -0
- package/src/channels/email/transport.mjs +64 -0
- package/src/channels/email/transports/agent-mail.mjs +604 -0
- package/src/channels/email/transports/agently-cli.mjs +288 -0
- package/src/channels/email/transports/imap-smtp.mjs +165 -0
- package/src/channels/feishu/bridge.mjs +39 -11
- package/src/channels/feishu/feishu-channel.mjs +35 -0
- package/src/channels/feishu/feishu-runtime.mjs +15 -8
- package/src/channels/feishu/group-message-permission-manager.mjs +2 -1
- package/src/channels/feishu/live-cot.mjs +260 -0
- package/src/channels/feishu/message-utils.mjs +3 -2
- package/src/channels/feishu/multi-bot-controller.mjs +56 -47
- package/src/channels/feishu/registration-manager.mjs +8 -4
- package/src/channels/feishu/repair-manager.mjs +2 -1
- package/src/channels/feishu/slash-command-registry.mjs +17 -0
- package/src/channels/feishu/step-push-mode.mjs +10 -4
- package/src/channels/imessage/runtime.mjs +9 -5
- package/src/channels/office/office-controller.mjs +23 -12
- package/src/channels/office/office-runtime.mjs +6 -3
- package/src/channels/office/office-transport.mjs +1 -0
- package/src/channels/qq/qq-bridge.mjs +7 -5
- package/src/channels/qq/qq-controller.mjs +56 -42
- package/src/channels/qq/qq-runtime.mjs +16 -11
- package/src/channels/shared/bot-workspace-store.mjs +29 -10
- package/src/channels/shared/connection-error.mjs +191 -0
- package/src/channels/shared/connection-test.mjs +3 -1
- package/src/channels/shared/conversation-state-store.mjs +46 -1
- package/src/channels/shared/diagnostic-details.mjs +95 -0
- package/src/channels/shared/harness-client.mjs +287 -57
- package/src/channels/shared/i18n-en/diagnostics.mjs +55 -0
- package/src/channels/shared/i18n-en/email.mjs +25 -0
- package/src/channels/shared/i18n-en/image-input.mjs +17 -0
- package/src/channels/shared/i18n-en/shared-a.mjs +1 -0
- package/src/channels/shared/i18n-en/shared-c.mjs +1 -1
- package/src/channels/shared/i18n-en.mjs +6 -0
- package/src/channels/shared/image-input-policy.mjs +35 -0
- package/src/channels/shared/image-input-settings-store.mjs +60 -0
- package/src/channels/shared/image-input.mjs +124 -0
- package/src/channels/shared/image-prompt.mjs +15 -7
- package/src/channels/shared/message-failure.mjs +12 -0
- package/src/channels/shared/semantic/reply-reference.mjs +3 -3
- package/src/channels/shared/text-harness-bridge.mjs +150 -25
- package/src/channels/shared/token-bot-controller.mjs +52 -47
- package/src/channels/slack/slack-api.mjs +3 -3
- package/src/channels/slack/slack-controller.mjs +59 -51
- package/src/channels/slack/slack-runtime.mjs +18 -12
- package/src/channels/telegram/config-store.mjs +4 -1
- package/src/channels/telegram/telegram-api.mjs +2 -2
- package/src/channels/telegram/telegram-controller.mjs +13 -1
- package/src/channels/telegram/telegram-runtime.mjs +215 -14
- package/src/channels/wecom/wecom-bridge.mjs +15 -6
- package/src/channels/wecom/wecom-controller.mjs +51 -37
- package/src/channels/wecom/wecom-runtime.mjs +10 -6
- package/src/channels/wecom-app/wecom-app-bridge.mjs +7 -5
- package/src/channels/wecom-app/wecom-app-controller.mjs +50 -37
- package/src/channels/wecom-app/wecom-app-runtime.mjs +2 -1
- package/src/channels/weixin/connection-error.mjs +26 -92
- package/src/channels/weixin/diagnostic-details.mjs +1 -63
- package/src/channels/weixin/weixin-bridge.mjs +7 -5
- package/src/channels/whatsapp/whatsapp-controller.mjs +34 -17
- package/src/channels/whatsapp/whatsapp-runtime.mjs +35 -8
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import imageInput from '../../src/channels/shared/i18n-en/image-input.mjs';
|
|
2
|
+
import diagnostics from '../../src/channels/shared/i18n-en/diagnostics.mjs';
|
|
1
3
|
import weixinDiagnostics from '../../src/channels/weixin/connection-error.en.mjs';
|
|
2
4
|
import * as React from 'react';
|
|
3
5
|
|
|
@@ -5,6 +7,8 @@ export const IM_LOCALE_NAMESPACE = 'dsh-im';
|
|
|
5
7
|
|
|
6
8
|
const EN = Object.freeze({
|
|
7
9
|
...weixinDiagnostics,
|
|
10
|
+
...diagnostics,
|
|
11
|
+
...imageInput,
|
|
8
12
|
'$locale': 'en',
|
|
9
13
|
'修改别名': 'Edit alias',
|
|
10
14
|
'关闭修改别名': 'Close alias editor',
|
|
@@ -258,11 +262,13 @@ const EN = Object.freeze({
|
|
|
258
262
|
'查看分步直推说明': 'View step push help',
|
|
259
263
|
'开启后逐步推送工具调用与过程说明': 'Push tool calls and process notes step by step',
|
|
260
264
|
'任务过程展示': 'Task progress display',
|
|
261
|
-
'
|
|
265
|
+
'设置任务执行过程的呈现方式:不显示、原生直播、实时卡片或逐步消息': 'Choose how execution is presented: hidden, native live process, one live card, or step-by-step messages',
|
|
262
266
|
'不显示过程(只发送最终答案)': 'Hide the process (send the final answer only)',
|
|
267
|
+
'实时直播(飞书原生思考过程)': 'Live process (native Feishu thinking process)',
|
|
263
268
|
'实时过程卡(全程一张卡片动态更新)': 'Live process card (one card updated throughout)',
|
|
264
269
|
'逐步直播(每一步单独发一条消息)': 'Step-by-step feed (one message per step)',
|
|
265
270
|
'适合日常问答:执行过程中不显示工具调用等中间步骤,只回复最终结果': 'For everyday Q&A: tool calls and other interim steps stay hidden, only the final result is replied',
|
|
271
|
+
'使用飞书原生思考过程展示推理、工具调用与结果,最终答案单独发送': 'Use Feishu’s native thinking process for reasoning, tool calls, and results, then send the final answer separately',
|
|
266
272
|
'推荐长任务使用:过程与最终答案都在同一张卡片里实时更新,不刷屏': 'Recommended for long tasks: the process and the final answer update live in one card without flooding the chat',
|
|
267
273
|
'每一步都单独发一条消息(含工具调用和过程说明);注意长任务会连续发送较多消息': 'Every step is sent as its own message (including tool calls and notes); long tasks may send many messages in a row',
|
|
268
274
|
'分步直推设置保存失败,请重试。': 'Could not save the step push setting. Try again.',
|
|
@@ -663,6 +669,11 @@ const EN = Object.freeze({
|
|
|
663
669
|
'接入 Telegram 机器人': 'Connect a Telegram bot',
|
|
664
670
|
'先通过 @BotFather 获取 Bot Token,再在这里完成接入。': 'Get a Bot Token from @BotFather, then connect it here.',
|
|
665
671
|
'填写 @BotFather 生成的 Bot Token': 'Enter the Bot Token from @BotFather',
|
|
672
|
+
'会话行为': 'Conversation behavior',
|
|
673
|
+
'思考过程留痕': 'Thinking traces',
|
|
674
|
+
'实时显示思考步骤与工具调用,最终答案单独成条': 'Show thinking steps and tool calls as they happen; the final answer arrives as its own message',
|
|
675
|
+
'请提交有效的思考过程留痕设置。': 'Submit a valid thinking-traces setting.',
|
|
676
|
+
'正在使用{name}…': 'Using {name}…',
|
|
666
677
|
'访问模式': 'Access mode',
|
|
667
678
|
'Telegram 访问模式': 'Telegram access mode',
|
|
668
679
|
'查看 Telegram 访问模式说明': 'View Telegram access mode details',
|
|
@@ -1005,6 +1016,63 @@ const EN = Object.freeze({
|
|
|
1005
1016
|
'企业微信应用操作失败,请稍后重试': 'The WeCom app operation failed; try again later',
|
|
1006
1017
|
'企业微信应用服务返回了无法识别的响应': 'The WeCom app service returned an unrecognized response',
|
|
1007
1018
|
'企业微信应用服务没有返回有效的机器人列表': 'The WeCom app service did not return a valid bot list',
|
|
1019
|
+
'邮箱': 'Email',
|
|
1020
|
+
' IMAP/SMTP 邮箱': ' IMAP/SMTP mailbox',
|
|
1021
|
+
'QQ 邮箱': 'QQ Mail',
|
|
1022
|
+
'163 邮箱': '163 Mail',
|
|
1023
|
+
'自定义服务器': 'Custom servers',
|
|
1024
|
+
'在 QQ 邮箱「设置 → 账户」中开启 IMAP/SMTP 服务,并生成 16 位授权码(不是登录密码)。': 'In QQ Mail open Settings → Account, enable IMAP/SMTP, and generate a 16-character authorization code (not your login password).',
|
|
1025
|
+
'在 163 邮箱「设置 → POP3/SMTP/IMAP」中开启服务,并新增授权密码。': 'In 163 Mail open Settings → POP3/SMTP/IMAP, enable the service, and add an authorization password.',
|
|
1026
|
+
'需要先开启两步验证,再生成「应用专用密码」。': 'Enable two-step verification first, then generate an app-specific password.',
|
|
1027
|
+
'请填写邮箱服务商提供的 IMAP 与 SMTP 服务器地址及端口。': 'Enter the IMAP and SMTP host and port provided by your mail provider.',
|
|
1028
|
+
'接入邮箱': 'Connect a mailbox',
|
|
1029
|
+
'DeepSeek Harness 会读取该邮箱的新邮件作为指令,并在同一邮件线程内回复处理结果。': 'DeepSeek Harness reads new mail in this mailbox as instructions and replies with the result inside the same mail thread.',
|
|
1030
|
+
'邮箱服务商': 'Mail provider',
|
|
1031
|
+
'邮箱地址': 'Email address',
|
|
1032
|
+
'应用密码 / 授权码': 'App password / authorization code',
|
|
1033
|
+
'IMAP/SMTP 授权码': 'IMAP/SMTP authorization code',
|
|
1034
|
+
'不是邮箱登录密码;请在邮箱设置中单独生成。': 'This is not your login password; generate it separately in your mailbox settings.',
|
|
1035
|
+
'IMAP 服务器': 'IMAP server',
|
|
1036
|
+
'端口': 'Port',
|
|
1037
|
+
'SMTP 服务器': 'SMTP server',
|
|
1038
|
+
'允许的发件人': 'Allowed senders',
|
|
1039
|
+
'me@example.com\\n同事@example.com': 'me@example.com\\ncolleague@example.com',
|
|
1040
|
+
'必填。只有这些地址发来的邮件会触发 Harness;多个地址用换行或逗号分隔。': 'Required. Only mail from these addresses triggers the Harness; separate multiple addresses with newlines or commas.',
|
|
1041
|
+
'正在连接邮箱…': 'Connecting the mailbox…',
|
|
1042
|
+
'连接邮箱': 'Connect mailbox',
|
|
1043
|
+
'保存后会重新连接邮箱以使设置立即生效。': 'Saving reconnects the mailbox so the change takes effect immediately.',
|
|
1044
|
+
'使用现有邮箱收发指令:邮件进来触发 Harness,处理结果以回信形式送达。': 'Use an existing mailbox to send instructions: incoming mail triggers the Harness and the result is delivered as a reply.',
|
|
1045
|
+
'配置邮箱收发': 'Configure email send and receive',
|
|
1046
|
+
'配置邮箱': 'Configure mailbox',
|
|
1047
|
+
'邮箱配置': 'Mailbox configuration',
|
|
1048
|
+
'会话绑定': 'Session binding',
|
|
1049
|
+
'会话绑定已保存。': 'Session binding saved.',
|
|
1050
|
+
'不绑定则每封新邮件开启一个新会话;绑定固定会话后,来信都在该会话内继续。': 'With no binding every new mail starts a new session; with a fixed session, incoming mail continues in that session.',
|
|
1051
|
+
'固定会话(账号级)': 'Fixed session (account level)',
|
|
1052
|
+
'不绑定(每封新邮件新建会话)': 'No binding (new session per mail)',
|
|
1053
|
+
'按发件人覆盖(优先于账号级)': 'Per-sender override (takes priority)',
|
|
1054
|
+
'跟随账号级': 'Follow the account setting',
|
|
1055
|
+
'尚无可覆盖的发件人(先在上方配置允许的发件人)。': 'No senders to override yet (configure allowed senders above first).',
|
|
1056
|
+
'保存绑定': 'Save binding',
|
|
1057
|
+
'清除绑定': 'Clear binding',
|
|
1058
|
+
'所有发件人同上': 'Point every sender at the same session',
|
|
1059
|
+
'已选:': 'Selected: ',
|
|
1060
|
+
'接入方式': 'Access method',
|
|
1061
|
+
'生成授权链接': 'Generate authorization link',
|
|
1062
|
+
'重新生成': 'Regenerate',
|
|
1063
|
+
'授权链接': 'Authorization link',
|
|
1064
|
+
'配对码': 'Pairing code',
|
|
1065
|
+
'等待授权中…': 'Waiting for authorization…',
|
|
1066
|
+
'等待授权中,': 'Waiting for authorization — valid for ',
|
|
1067
|
+
' 分钟': ' minutes',
|
|
1068
|
+
'内有效,可保持本页打开。': '; you can keep this page open.',
|
|
1069
|
+
'授权成功,正在接入邮箱…': 'Authorized; connecting the mailbox…',
|
|
1070
|
+
'授权成功;请填写邮箱地址与允许的发件人后完成接入。': 'Authorized. Enter the mailbox address and at least one allowed sender to finish connecting.',
|
|
1071
|
+
'授权后自动填入': 'Filled in after authorization',
|
|
1072
|
+
'由授权结果自动填入,无需手工填写。': 'Filled in from the authorization; no need to type it.',
|
|
1073
|
+
'刷新会话列表': 'Refresh the session list',
|
|
1074
|
+
'腾讯 Agent 邮箱需要微信扫码授权。点下面的按钮生成授权链接,在打开的页面里用微信扫码登录并确认。': 'The Tencent Agent mailbox authorizes with a WeChat QR code. Generate an authorization link, then scan it with WeChat on the page that opens and confirm.',
|
|
1075
|
+
'请求失败': 'The request failed',
|
|
1008
1076
|
});
|
|
1009
1077
|
|
|
1010
1078
|
export const en = EN;
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
WeixinLogoGlyph,
|
|
15
15
|
WhatsappLogoGlyph,
|
|
16
16
|
IMessageLogoGlyph,
|
|
17
|
+
EmailLogoGlyph,
|
|
17
18
|
} from './channel-logos.js';
|
|
18
19
|
import { DINGTALK_RPC_CHANNEL } from './channels/dingtalk/api.js';
|
|
19
20
|
import { DingtalkSettingsTab } from './channels/dingtalk/index.js';
|
|
@@ -48,8 +49,11 @@ import { WHATSAPP_RPC_CHANNEL } from './channels/whatsapp/api.js';
|
|
|
48
49
|
import { WhatsappSettingsTab } from './channels/whatsapp/index.js';
|
|
49
50
|
import { installWhatsappStyles } from './channels/whatsapp/styles.js';
|
|
50
51
|
import { IMESSAGE_RPC_CHANNEL } from './channels/imessage/api.js';
|
|
52
|
+
import { EMAIL_RPC_CHANNEL } from './channels/email/api.js';
|
|
51
53
|
import { IMessageSettingsTab } from './channels/imessage/index.js';
|
|
54
|
+
import { EmailSettingsTab, useEmailChannelEnabled } from './channels/email/index.js';
|
|
52
55
|
import { installIMessageStyles } from './channels/imessage/styles.js';
|
|
56
|
+
import { installEmailStyles } from './channels/email/styles.js';
|
|
53
57
|
import { en, h, IM_LOCALE_NAMESPACE, setImTranslator, zh } from './i18n.js';
|
|
54
58
|
import {
|
|
55
59
|
HOST_LANGUAGE_RPC_CHANNEL,
|
|
@@ -102,6 +106,7 @@ const CHANNELS = Object.freeze([
|
|
|
102
106
|
{ id: 'whatsapp', label: 'WhatsApp' },
|
|
103
107
|
{ id: 'wecomApp', label: '企业微信应用', note: '(实验功能)' },
|
|
104
108
|
{ id: 'imessage', label: 'iMessage', note: '(实验功能)' },
|
|
109
|
+
{ id: 'email', label: '邮箱', note: '(实验功能)' },
|
|
105
110
|
{ id: 'office', label: 'AI Office', note: '(实验功能)' },
|
|
106
111
|
]);
|
|
107
112
|
|
|
@@ -157,6 +162,11 @@ function IMessageLogo() {
|
|
|
157
162
|
h(IMessageLogoGlyph));
|
|
158
163
|
}
|
|
159
164
|
|
|
165
|
+
function EmailLogo() {
|
|
166
|
+
return h('span', { className: 'dim-logo dim-logoEmail', 'aria-hidden': 'true' },
|
|
167
|
+
h(EmailLogoGlyph));
|
|
168
|
+
}
|
|
169
|
+
|
|
160
170
|
function OfficeLogo() {
|
|
161
171
|
return h('span', { className: 'dim-logo dim-logoOffice', 'aria-hidden': 'true' },
|
|
162
172
|
h(OfficeLogoGlyph));
|
|
@@ -174,6 +184,7 @@ function ChannelLogo({ channel }) {
|
|
|
174
184
|
if (channel === 'discord') return h(DiscordLogo);
|
|
175
185
|
if (channel === 'whatsapp') return h(WhatsappLogo);
|
|
176
186
|
if (channel === 'imessage') return h(IMessageLogo);
|
|
187
|
+
if (channel === 'email') return h(EmailLogo);
|
|
177
188
|
return h(OfficeLogo);
|
|
178
189
|
}
|
|
179
190
|
|
|
@@ -198,6 +209,7 @@ export function IMSettingsTab({
|
|
|
198
209
|
discordRpcCall,
|
|
199
210
|
feishuRpcCall,
|
|
200
211
|
imessageRpcCall,
|
|
212
|
+
emailRpcCall,
|
|
201
213
|
qqRpcCall,
|
|
202
214
|
slackRpcCall,
|
|
203
215
|
telegramRpcCall,
|
|
@@ -222,10 +234,18 @@ export function IMSettingsTab({
|
|
|
222
234
|
const [loopbackRecovery, setLoopbackRecovery] = React.useState(null);
|
|
223
235
|
const [runningVersion, setRunningVersion] = React.useState(IM_PLUGIN_VERSION);
|
|
224
236
|
const [deliverySettings, setDeliverySettings] = React.useState(null);
|
|
237
|
+
// The Host owns email availability and reports it over RPC. The
|
|
238
|
+
// mailbox entry point is omitted entirely while it is closed, and the visible
|
|
239
|
+
// channel list is what every later lookup (active tab, rail) reads from.
|
|
240
|
+
const emailEnabled = useEmailChannelEnabled(emailRpcCall);
|
|
241
|
+
const visibleChannels = React.useMemo(
|
|
242
|
+
() => CHANNELS.filter((channel) => channel.id !== 'email' || emailEnabled),
|
|
243
|
+
[emailEnabled],
|
|
244
|
+
);
|
|
225
245
|
const githubTooltipId = React.useId();
|
|
226
246
|
const generalSettingsTooltipId = React.useId();
|
|
227
247
|
const globalSettingsSelected = selected === GLOBAL_SETTINGS_TAB_ID;
|
|
228
|
-
const active =
|
|
248
|
+
const active = visibleChannels.find((channel) => channel.id === selected) ?? visibleChannels[0];
|
|
229
249
|
const activeTabId = globalSettingsSelected
|
|
230
250
|
? 'dim-general-settings-trigger'
|
|
231
251
|
: `dim-tab-${active.id}`;
|
|
@@ -254,6 +274,7 @@ export function IMSettingsTab({
|
|
|
254
274
|
deliveryRpcCall,
|
|
255
275
|
globalSettingsRpcCall,
|
|
256
276
|
imessageRpcCall,
|
|
277
|
+
emailRpcCall,
|
|
257
278
|
}, {
|
|
258
279
|
location: browserLocation,
|
|
259
280
|
onRecovery: reportLoopbackRecovery,
|
|
@@ -265,6 +286,7 @@ export function IMSettingsTab({
|
|
|
265
286
|
feishuRpcCall,
|
|
266
287
|
globalSettingsRpcCall,
|
|
267
288
|
imessageRpcCall,
|
|
289
|
+
emailRpcCall,
|
|
268
290
|
officeRpcCall,
|
|
269
291
|
qqRpcCall,
|
|
270
292
|
reportLoopbackRecovery,
|
|
@@ -302,8 +324,16 @@ export function IMSettingsTab({
|
|
|
302
324
|
'aria-label': 'dsh-im GitHub',
|
|
303
325
|
'aria-describedby': githubTooltipId,
|
|
304
326
|
},
|
|
305
|
-
h('
|
|
306
|
-
|
|
327
|
+
h('svg', {
|
|
328
|
+
width: 18,
|
|
329
|
+
height: 18,
|
|
330
|
+
viewBox: '0 0 16 16',
|
|
331
|
+
fill: 'currentColor',
|
|
332
|
+
focusable: 'false',
|
|
333
|
+
'aria-hidden': 'true',
|
|
334
|
+
}, h('path', {
|
|
335
|
+
d: 'M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82a7.65 7.65 0 0 1 2-.27c.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8Z',
|
|
336
|
+
}))),
|
|
307
337
|
h('span', {
|
|
308
338
|
id: githubTooltipId,
|
|
309
339
|
className: 'dim-githubTooltip',
|
|
@@ -331,7 +361,7 @@ export function IMSettingsTab({
|
|
|
331
361
|
),
|
|
332
362
|
h('div', { className: 'dim-layout' },
|
|
333
363
|
h('nav', { className: 'dim-rail', role: 'tablist', 'aria-label': 'IM 设置导航' },
|
|
334
|
-
|
|
364
|
+
visibleChannels.map((channel) => h('button', {
|
|
335
365
|
key: channel.id,
|
|
336
366
|
type: 'button',
|
|
337
367
|
role: 'tab',
|
|
@@ -395,6 +425,8 @@ export function IMSettingsTab({
|
|
|
395
425
|
? h(WhatsappSettingsTab, { rpcCall: rpcCalls.whatsappRpcCall })
|
|
396
426
|
: active.id === 'imessage'
|
|
397
427
|
? h(IMessageSettingsTab, { rpcCall: rpcCalls.imessageRpcCall })
|
|
428
|
+
: active.id === 'email'
|
|
429
|
+
? h(EmailSettingsTab, { rpcCall: rpcCalls.emailRpcCall })
|
|
398
430
|
: h(OfficeSettingsTab, { rpcCall: rpcCalls.officeRpcCall }))),
|
|
399
431
|
),
|
|
400
432
|
));
|
|
@@ -433,6 +465,7 @@ export function apply(ctx) {
|
|
|
433
465
|
installDiscordStyles(),
|
|
434
466
|
installWhatsappStyles(),
|
|
435
467
|
installIMessageStyles(),
|
|
468
|
+
installEmailStyles(),
|
|
436
469
|
installOfficeStyles(),
|
|
437
470
|
installImStyles(),
|
|
438
471
|
];
|
|
@@ -461,6 +494,8 @@ export function apply(ctx) {
|
|
|
461
494
|
callManagementRpc(ctx.connection, WHATSAPP_RPC_CHANNEL, endpoint, payload, signal);
|
|
462
495
|
const imessageRpcCall = (endpoint, payload, signal) =>
|
|
463
496
|
callManagementRpc(ctx.connection, IMESSAGE_RPC_CHANNEL, endpoint, payload, signal);
|
|
497
|
+
const emailRpcCall = (endpoint, payload, signal) =>
|
|
498
|
+
callManagementRpc(ctx.connection, EMAIL_RPC_CHANNEL, endpoint, payload, signal);
|
|
464
499
|
const slackRpcCall = (endpoint, payload, signal) =>
|
|
465
500
|
callManagementRpc(ctx.connection, SLACK_RPC_CHANNEL, endpoint, payload, signal);
|
|
466
501
|
const officeRpcCall = (endpoint, payload, signal) =>
|
|
@@ -490,6 +525,7 @@ export function apply(ctx) {
|
|
|
490
525
|
whatsappRpcCall,
|
|
491
526
|
imessageRpcCall,
|
|
492
527
|
officeRpcCall,
|
|
528
|
+
emailRpcCall,
|
|
493
529
|
updateRpcCall,
|
|
494
530
|
deliveryRpcCall,
|
|
495
531
|
globalSettingsRpcCall,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { diagnosticFields } from '../../src/channels/shared/diagnostic-details.mjs';
|
|
1
2
|
function text(value, maxLength) {
|
|
2
3
|
if (typeof value !== 'string') return null;
|
|
3
4
|
const trimmed = value.trim();
|
|
@@ -12,6 +13,6 @@ export function normalizeLastMessageError(value) {
|
|
|
12
13
|
const referenceId = text(value.referenceId, 40);
|
|
13
14
|
const at = Number.isFinite(value.at) ? value.at : null;
|
|
14
15
|
return code && reason && message && referenceId && at !== null
|
|
15
|
-
? { code, reason, message, referenceId, at }
|
|
16
|
+
? { code, reason, message, referenceId, at, ...(value.details ? diagnosticFields(value) : {}) }
|
|
16
17
|
: null;
|
|
17
18
|
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
export const IM_STYLE_ID = 'xmanrui-dsh-im-settings';
|
|
2
2
|
|
|
3
3
|
const CSS = String.raw`
|
|
4
|
+
.dim-connectionDiagnostic { min-width: 0; width: 100%; color: var(--dsw-alias-label-secondary, #646a73); overflow-wrap: anywhere; font-size: 13px; line-height: 1.6; }
|
|
5
|
+
.dim-connectionDiagnostic[data-warning="true"] { color: var(--dsw-alias-state-warn-primary, #d97706); }
|
|
6
|
+
.dim-connectionDiagnostic p { margin: 4px 0; }
|
|
7
|
+
.dim-connectionDiagnostic button { border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 6px; padding: 6px 10px; color: inherit; background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; cursor: pointer; }
|
|
8
|
+
.dim-connectionDiagnostic button:focus-visible, .dim-connectionDiagnostic summary:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; }
|
|
9
|
+
|
|
4
10
|
.dim-aliasName { display: flex; align-items: center; gap: 4px; min-width: 0; }
|
|
5
11
|
.dim-aliasName h3 { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
6
12
|
.dim-aliasName h3:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; border-radius: 3px; }
|
|
@@ -87,10 +93,10 @@ const CSS = String.raw`
|
|
|
87
93
|
.dim-updateFooter .dim-updateButton:first-child { margin-right: auto; }
|
|
88
94
|
.dim-updatePrimary, .dim-updatePrimary:hover:not(:disabled) { border-color: var(--dsw-alias-state-business-primary, #3370ff); color: #fff; background: var(--dsw-alias-state-business-primary, #3370ff); }
|
|
89
95
|
.dim-githubAction { position: relative; display: inline-flex; flex: none; }
|
|
90
|
-
.dim-githubLink {
|
|
96
|
+
.dim-githubLink { width: 30px; height: 30px; display: grid; place-items: center; flex: none; padding: 0; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-layer-1, #fff); text-decoration: none; transition: border-color .15s ease, color .15s ease, background .15s ease; }
|
|
97
|
+
.dim-githubLink svg { display: block; }
|
|
91
98
|
.dim-githubLink:hover { border-color: #aeb3bb; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); }
|
|
92
99
|
.dim-githubLink:focus-visible { outline: 2px solid color-mix(in srgb, var(--dim-blue) 70%, white); outline-offset: 2px; }
|
|
93
|
-
.dim-githubArrow { font-size: 13px; line-height: 1; }
|
|
94
100
|
.dim-githubTooltip { position: absolute; top: calc(100% + 8px); right: 0; z-index: 20; width: max-content; max-width: min(220px, 80vw); padding: 6px 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 8px 24px rgb(31 35 41 / 14%); font-size: 11px; line-height: 16px; font-weight: 500; white-space: nowrap; opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; }
|
|
95
101
|
.dim-githubAction:hover .dim-githubTooltip, .dim-githubAction:focus-within .dim-githubTooltip { opacity: 1; visibility: visible; transform: translateY(0); }
|
|
96
102
|
.dim-generalSettingsAction { position: relative; display: inline-flex; flex: none; }
|
|
@@ -14,7 +14,11 @@ const externalRuntimePackages = [
|
|
|
14
14
|
'@tencent-connect/qqbot-nodejs',
|
|
15
15
|
'@wecom/aibot-node-sdk',
|
|
16
16
|
'dingtalk-stream',
|
|
17
|
+
'imapflow',
|
|
18
|
+
'mailparser',
|
|
19
|
+
'nodemailer',
|
|
17
20
|
'qrcode',
|
|
21
|
+
'sharp',
|
|
18
22
|
'undici',
|
|
19
23
|
];
|
|
20
24
|
const external = externalRuntimePackages.flatMap((name) => [name, `${name}/*`]);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getImageInputSettingsStore } from '../../../../src/channels/shared/image-input-settings-store.mjs';
|
|
1
2
|
import { unlink } from 'node:fs/promises';
|
|
2
3
|
import { homedir } from 'node:os';
|
|
3
4
|
import { join, resolve } from 'node:path';
|
|
@@ -113,6 +114,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
113
114
|
...(controlExecutor ? { controlExecutor } : {}),
|
|
114
115
|
...(sessionMaintenanceExecutor ? { sessionMaintenanceExecutor } : {}),
|
|
115
116
|
...(fileIngressExecutor ? { fileIngressExecutor } : {}),
|
|
117
|
+
imageInputPolicy: () => getImageInputSettingsStore(config).get(),
|
|
116
118
|
});
|
|
117
119
|
const modelCatalog = () => listModelCatalog(harness);
|
|
118
120
|
const coreController = new Controller({
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { diagnosticFields } from '../../../../src/channels/shared/diagnostic-details.mjs';
|
|
2
|
+
import { createConnectionDiagnostics, diagnosticRpcResult } from '../../../../src/channels/shared/connection-error.mjs';
|
|
1
3
|
import { SET_ALIAS_ENDPOINT, validAliasPayload } from '../shared/bot-alias-rpc.mjs';
|
|
2
4
|
import { registerManagementRpc } from '../../../management-rpc.mjs';
|
|
3
5
|
import QRCode from 'qrcode';
|
|
@@ -169,7 +171,7 @@ function publicConnectionFailure(error) {
|
|
|
169
171
|
? source.referenceId
|
|
170
172
|
: null;
|
|
171
173
|
return code && message && hint && referenceId
|
|
172
|
-
? { code, message, hint, referenceId }
|
|
174
|
+
? { code, message, hint, referenceId, ...diagnosticFields(source) }
|
|
173
175
|
: null;
|
|
174
176
|
}
|
|
175
177
|
|
|
@@ -227,6 +229,7 @@ function assertController(controller) {
|
|
|
227
229
|
}
|
|
228
230
|
|
|
229
231
|
export function createDingtalkRpcHandler(controller, { encodeQr = qrDataUrl } = {}) {
|
|
232
|
+
const diagnostics = controller.diagnostics ?? createConnectionDiagnostics({ channel: 'dingtalk' });
|
|
230
233
|
assertController(controller);
|
|
231
234
|
const qrCache = new Map();
|
|
232
235
|
const cachedEncode = (url) => {
|
|
@@ -283,7 +286,7 @@ export function createDingtalkRpcHandler(controller, { encodeQr = qrDataUrl } =
|
|
|
283
286
|
await controller.sendConnectionTest(payload.botId);
|
|
284
287
|
testMessage = publicConnectionTestResult();
|
|
285
288
|
} catch (error) {
|
|
286
|
-
testMessage = publicConnectionTestResult(error);
|
|
289
|
+
testMessage = publicConnectionTestResult(error, { diagnostics, botId: payload.botId });
|
|
287
290
|
}
|
|
288
291
|
}
|
|
289
292
|
}
|
|
@@ -338,11 +341,11 @@ export function createDingtalkRpcHandler(controller, { encodeQr = qrDataUrl } =
|
|
|
338
341
|
} catch (error) {
|
|
339
342
|
const workspaceError = publicWorkspaceError(error);
|
|
340
343
|
const connectionError = publicConnectionFailure(error);
|
|
341
|
-
return signal?.aborted ? cancelled() : workspaceError
|
|
344
|
+
return diagnosticRpcResult(diagnostics, error, signal?.aborted ? cancelled() : workspaceError
|
|
342
345
|
? { ok: false, error: workspaceError }
|
|
343
346
|
: connectionError
|
|
344
347
|
? { ok: false, error: connectionError }
|
|
345
|
-
: internalFailure();
|
|
348
|
+
: internalFailure(), { operation: endpoint, botId: payload?.botId });
|
|
346
349
|
}
|
|
347
350
|
};
|
|
348
351
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single switch that opens or closes the email channel.
|
|
3
|
+
*
|
|
4
|
+
* Email is enabled by default. A deployment can opt out with
|
|
5
|
+
* `EMAIL_CHANNEL_ENABLED=0` or `emailChannelEnabled: false` in the channel config.
|
|
6
|
+
* Setting either option to true enables the entry point, runtime and config
|
|
7
|
+
* restore again without a code change.
|
|
8
|
+
*
|
|
9
|
+
* Closing is deliberately non-destructive: it never deletes a mailbox config or
|
|
10
|
+
* a credential. It only refuses to expose the entry point and to start the
|
|
11
|
+
* runtime, so every existing mailbox is still there when the switch reopens.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Environment variable that controls the email channel. */
|
|
15
|
+
export const EMAIL_CHANNEL_ENABLED_ENV = 'EMAIL_CHANNEL_ENABLED';
|
|
16
|
+
|
|
17
|
+
const TRUTHY = new Set(['1', 'true', 'yes', 'on']);
|
|
18
|
+
const FALSY = new Set(['0', 'false', 'no', 'off', '']);
|
|
19
|
+
|
|
20
|
+
function readBoolean(value) {
|
|
21
|
+
if (typeof value === 'boolean') return value;
|
|
22
|
+
if (typeof value === 'string') {
|
|
23
|
+
const normalized = value.trim().toLowerCase();
|
|
24
|
+
if (TRUTHY.has(normalized)) return true;
|
|
25
|
+
if (FALSY.has(normalized)) return false;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Decide whether the email channel is open.
|
|
32
|
+
*
|
|
33
|
+
* An explicit config value wins over the environment so a deployment can pin
|
|
34
|
+
* the channel without changing its environment; an unreadable value falls back
|
|
35
|
+
* to the environment, and an absent one keeps the channel enabled.
|
|
36
|
+
*/
|
|
37
|
+
export function isEmailChannelEnabled(config = {}, env = process.env) {
|
|
38
|
+
return readBoolean(config.emailChannelEnabled)
|
|
39
|
+
?? readBoolean(env?.[EMAIL_CHANNEL_ENABLED_ENV])
|
|
40
|
+
?? true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The single host-side gate.
|
|
45
|
+
*
|
|
46
|
+
* Returns `null` when email is open, or a public "not available" result when it
|
|
47
|
+
* is closed. Both the management RPC and the production startup consult this so
|
|
48
|
+
* a closed channel can neither be configured nor connected.
|
|
49
|
+
*/
|
|
50
|
+
export function emailChannelGate(config = {}, env = process.env) {
|
|
51
|
+
if (isEmailChannelEnabled(config, env)) return null;
|
|
52
|
+
return Object.freeze({
|
|
53
|
+
ok: false,
|
|
54
|
+
error: Object.freeze({
|
|
55
|
+
code: 'email-channel-disabled',
|
|
56
|
+
message: 'Email is not available yet.',
|
|
57
|
+
details: Object.freeze({}),
|
|
58
|
+
}),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createProductionController } from './production.mjs';
|
|
2
|
+
import { createEmailRpcHandler, installEmailRpc, EMAIL_RPC_CHANNEL } from './rpc.mjs';
|
|
3
|
+
import { installProductionChannel } from '../shared/startup.mjs';
|
|
4
|
+
import { emailChannelGate } from './availability.mjs';
|
|
5
|
+
|
|
6
|
+
export const name = 'dsh-im-email-host';
|
|
7
|
+
export const inject = ['connection', 'credentials', 'typertGateway'];
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Cordis/DSH Host plugin entry. The management RPC is mounted before the
|
|
11
|
+
* fallible production initialization so the channel's settings page stays
|
|
12
|
+
* reachable (and can report why the mailbox failed) even when startup throws.
|
|
13
|
+
* Tests and embedded distributions may inject a controller through config.
|
|
14
|
+
*
|
|
15
|
+
* While `EMAIL_CHANNEL_ENABLED` is off this mounts the management RPC only and
|
|
16
|
+
* returns: no mailbox runtime is started and no configured mailbox is restored,
|
|
17
|
+
* so a machine that already holds mailboxes sends no network request for them.
|
|
18
|
+
* Nothing is deleted — reopening the switch restores every mailbox untouched.
|
|
19
|
+
*/
|
|
20
|
+
export async function apply(ctx, config = {}) {
|
|
21
|
+
if (config?.controller) {
|
|
22
|
+
return installEmailRpc(ctx, config.controller, config.rpcAuthority);
|
|
23
|
+
}
|
|
24
|
+
const closed = emailChannelGate(config);
|
|
25
|
+
if (closed) return installEmailRpc(ctx, { disabled: () => closed }, config.rpcAuthority);
|
|
26
|
+
return installProductionChannel(ctx, config, {
|
|
27
|
+
channel: 'email',
|
|
28
|
+
rpcChannel: EMAIL_RPC_CHANNEL,
|
|
29
|
+
createProduction: () => createProductionController(ctx, config, config.internals ?? {}),
|
|
30
|
+
createHandler: controller => createEmailRpcHandler(controller),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { createProductionController } from './production.mjs';
|
|
35
|
+
export { EMAIL_ENDPOINTS, EMAIL_RPC_CHANNEL, EMAIL_RPC_ENDPOINTS,
|
|
36
|
+
createEmailRpcHandler, installEmailRpc } from './rpc.mjs';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { EmailConfigStore } from '../../../../src/channels/email/config-store.mjs';
|
|
2
|
+
import { EmailHarnessClient } from '../../../../src/channels/email/harness-client.mjs';
|
|
3
|
+
import { EmailStateStore } from '../../../../src/channels/email/state-store.mjs';
|
|
4
|
+
import { EmailController } from '../../../../src/channels/email/email-controller.mjs';
|
|
5
|
+
import { EmailRuntime } from '../../../../src/channels/email/email-runtime.mjs';
|
|
6
|
+
import {
|
|
7
|
+
createAccessPolicy,
|
|
8
|
+
createAccessPolicyScope,
|
|
9
|
+
} from '../../../../src/channels/shared/access-policy.mjs';
|
|
10
|
+
import { createTokenProductionController } from '../shared/production.mjs';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Email has no group concept, and a mail address is trivially forgeable, so
|
|
14
|
+
* both scopes start as an allowlist seeded from the mailbox's configured
|
|
15
|
+
* senders instead of the open baseline other token channels use. With no
|
|
16
|
+
* senders configured the allowlist is empty, which denies everyone until the
|
|
17
|
+
* user adds one — the channel fails closed.
|
|
18
|
+
*/
|
|
19
|
+
function emailAccessPolicyFor(bot) {
|
|
20
|
+
const senders = Array.isArray(bot?.allowedSenders) ? bot.allowedSenders : [];
|
|
21
|
+
const scope = createAccessPolicyScope({
|
|
22
|
+
mode: 'allowlist',
|
|
23
|
+
open: { defaultCanExecuteCommands: false, commandPermissionOverrides: [] },
|
|
24
|
+
// Every policy user entry carries its command permission alongside the id.
|
|
25
|
+
allowlist: { users: senders.map((id) => ({ id, canExecuteCommands: true })) },
|
|
26
|
+
});
|
|
27
|
+
return createAccessPolicy({ direct: scope, group: scope });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Email is a token-shaped channel: one mailbox identity plus one secret. It
|
|
32
|
+
* reuses the shared token production assembly, which wires the workspace
|
|
33
|
+
* scope, access policy, delivery adapter, and connection supervisor.
|
|
34
|
+
*/
|
|
35
|
+
export function createProductionController(ctx, config = {}, internals = {}) {
|
|
36
|
+
return createTokenProductionController(ctx, config, internals, {
|
|
37
|
+
channel: 'email',
|
|
38
|
+
ConfigStore: EmailConfigStore,
|
|
39
|
+
StateStore: EmailStateStore,
|
|
40
|
+
HarnessClient: EmailHarnessClient,
|
|
41
|
+
Controller: EmailController,
|
|
42
|
+
Runtime: EmailRuntime,
|
|
43
|
+
runtimeOptions: (channelConfig) => ({
|
|
44
|
+
...(channelConfig.pollIntervalMs === undefined
|
|
45
|
+
? {} : { pollIntervalMs: channelConfig.pollIntervalMs }),
|
|
46
|
+
}),
|
|
47
|
+
initialAccessPolicyForBot: emailAccessPolicyFor,
|
|
48
|
+
// Declares that this channel keeps its allowlist in its own config and
|
|
49
|
+
// pushes the derived policy into the workspace store on change.
|
|
50
|
+
accessPolicyForBot: emailAccessPolicyFor,
|
|
51
|
+
// The mailbox settings page can pin the chat to an existing session.
|
|
52
|
+
supportsSessionBinding: true,
|
|
53
|
+
// The Agent mailbox rotates its refresh token on every refresh, so the new
|
|
54
|
+
// pair must be written back through the controller.
|
|
55
|
+
persistBotCredential: ({ botId, tokens, controller }) =>
|
|
56
|
+
controller?.persistTokens?.(botId, tokens) ?? Promise.resolve(false),
|
|
57
|
+
});
|
|
58
|
+
}
|