@xmanrui/dsh-im 4.3.0 → 4.4.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/lib/client.js +951 -626
- package/lib/index.js +233 -233
- package/package.json +1 -1
- package/plugin-src/client/access-policy-settings.js +351 -0
- package/plugin-src/client/channel-card-meta.js +2 -1
- package/plugin-src/client/channels/dingtalk/api.js +5 -0
- package/plugin-src/client/channels/dingtalk/index.js +1 -0
- package/plugin-src/client/channels/feishu/api.js +5 -0
- package/plugin-src/client/channels/feishu/index.js +1 -0
- package/plugin-src/client/channels/qq/api.js +5 -0
- package/plugin-src/client/channels/qq/index.js +1 -0
- package/plugin-src/client/channels/shared/token-api.js +5 -0
- package/plugin-src/client/channels/shared/token-channel.js +1 -0
- package/plugin-src/client/channels/telegram/api.js +2 -17
- package/plugin-src/client/channels/telegram/index.js +0 -112
- package/plugin-src/client/channels/telegram/styles.js +0 -28
- package/plugin-src/client/channels/wecom/api.js +5 -0
- package/plugin-src/client/channels/wecom/index.js +1 -0
- package/plugin-src/client/channels/weixin/api.js +5 -0
- package/plugin-src/client/channels/weixin/index.js +1 -0
- package/plugin-src/client/channels/whatsapp/api.js +4 -10
- package/plugin-src/client/channels/whatsapp/index.js +1 -125
- package/plugin-src/client/channels/whatsapp/styles.js +0 -25
- package/plugin-src/client/delivery-settings.js +61 -9
- package/plugin-src/client/i18n.js +56 -1
- package/plugin-src/client/index.js +1 -0
- package/plugin-src/client/styles.js +50 -4
- package/plugin-src/host/channels/dingtalk/production.mjs +12 -1
- package/plugin-src/host/channels/dingtalk/rpc.mjs +11 -0
- package/plugin-src/host/channels/feishu/production.mjs +39 -1
- package/plugin-src/host/channels/feishu/rpc.mjs +19 -2
- package/plugin-src/host/channels/qq/production.mjs +12 -1
- package/plugin-src/host/channels/qq/rpc.mjs +11 -0
- package/plugin-src/host/channels/shared/access-policy-production.mjs +137 -0
- package/plugin-src/host/channels/shared/access-policy-rpc.mjs +17 -0
- package/plugin-src/host/channels/shared/production.mjs +15 -1
- package/plugin-src/host/channels/shared/rpc.mjs +9 -0
- package/plugin-src/host/channels/slack/production.mjs +12 -1
- package/plugin-src/host/channels/slack/rpc.mjs +10 -0
- package/plugin-src/host/channels/telegram/rpc.mjs +2 -49
- package/plugin-src/host/channels/wecom/production.mjs +12 -1
- package/plugin-src/host/channels/wecom/rpc.mjs +11 -0
- package/plugin-src/host/channels/weixin/production.mjs +12 -1
- package/plugin-src/host/channels/weixin/rpc.mjs +11 -0
- package/plugin-src/host/channels/whatsapp/production.mjs +16 -2
- package/plugin-src/host/channels/whatsapp/rpc.mjs +10 -15
- package/src/channels/dingtalk/dingtalk-bridge.mjs +61 -9
- package/src/channels/dingtalk/dingtalk-runtime.mjs +4 -0
- package/src/channels/discord/discord-runtime.mjs +29 -1
- package/src/channels/feishu/bridge.mjs +188 -80
- package/src/channels/feishu/feishu-runtime.mjs +4 -0
- package/src/channels/qq/qq-bridge.mjs +59 -17
- package/src/channels/qq/qq-runtime.mjs +20 -2
- package/src/channels/shared/access-policy.mjs +210 -0
- package/src/channels/shared/bot-workspace-store.mjs +176 -33
- package/src/channels/shared/command-permission.mjs +32 -0
- package/src/channels/shared/compact-command.mjs +5 -2
- package/src/channels/shared/i18n-en/shared-c.mjs +2 -0
- package/src/channels/shared/inbound-access.mjs +43 -0
- package/src/channels/shared/text-harness-bridge.mjs +47 -4
- package/src/channels/shared/workspace-command.mjs +10 -1
- package/src/channels/slack/slack-runtime.mjs +4 -0
- package/src/channels/telegram/telegram-runtime.mjs +5 -13
- package/src/channels/wecom/wecom-bridge.mjs +51 -4
- package/src/channels/wecom/wecom-runtime.mjs +4 -0
- package/src/channels/weixin/weixin-bridge.mjs +59 -16
- package/src/channels/weixin/weixin-runtime.mjs +4 -0
- package/src/channels/whatsapp/whatsapp-runtime.mjs +42 -20
|
@@ -3,60 +3,13 @@ import {
|
|
|
3
3
|
createTokenBotRpcHandler,
|
|
4
4
|
} from '../shared/rpc.mjs';
|
|
5
5
|
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
|
6
|
-
import { normalizeTelegramAccessPolicy } from '../../../../src/channels/telegram/config-store.mjs';
|
|
7
6
|
|
|
8
7
|
export const TELEGRAM_RPC_CHANNEL = '/telegram';
|
|
9
|
-
export const TELEGRAM_ENDPOINTS =
|
|
10
|
-
...TOKEN_BOT_ENDPOINTS,
|
|
11
|
-
setAccessPolicy: 'bot.access-policy.set',
|
|
12
|
-
});
|
|
8
|
+
export const TELEGRAM_ENDPOINTS = TOKEN_BOT_ENDPOINTS;
|
|
13
9
|
export const TELEGRAM_RPC_ENDPOINTS = Object.freeze(Object.values(TELEGRAM_ENDPOINTS));
|
|
14
10
|
|
|
15
11
|
export function createTelegramRpcHandler(controller) {
|
|
16
|
-
|
|
17
|
-
throw new TypeError('A complete Telegram controller is required (setAccessPolicy)');
|
|
18
|
-
}
|
|
19
|
-
const sharedHandler = createTokenBotRpcHandler(controller, { channel: 'Telegram' });
|
|
20
|
-
return async (endpoint, payload, signal) => {
|
|
21
|
-
if (endpoint !== TELEGRAM_ENDPOINTS.setAccessPolicy) {
|
|
22
|
-
return sharedHandler(endpoint, payload, signal);
|
|
23
|
-
}
|
|
24
|
-
if (signal?.aborted) {
|
|
25
|
-
return { ok: false, error: { code: 'cancelled', message: 'The request was cancelled.' } };
|
|
26
|
-
}
|
|
27
|
-
const keys = payload && typeof payload === 'object' && !Array.isArray(payload)
|
|
28
|
-
? Object.keys(payload) : [];
|
|
29
|
-
if (keys.length !== 3 || !keys.every((key) => (
|
|
30
|
-
['botId', 'accessMode', 'allowedUsers'].includes(key)
|
|
31
|
-
)) || typeof payload.botId !== 'string' || !/^[A-Za-z0-9_-]{1,128}$/.test(payload.botId)) {
|
|
32
|
-
return {
|
|
33
|
-
ok: false,
|
|
34
|
-
error: { code: 'bad-request', message: 'bot.access-policy.set requires a valid policy.' },
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
let accessPolicy;
|
|
38
|
-
try {
|
|
39
|
-
accessPolicy = normalizeTelegramAccessPolicy(payload);
|
|
40
|
-
} catch {
|
|
41
|
-
return {
|
|
42
|
-
ok: false,
|
|
43
|
-
error: { code: 'bad-request', message: '请输入有效的 Telegram 访问模式和数字 User ID。' },
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
try {
|
|
47
|
-
const value = await controller.setAccessPolicy(payload.botId, accessPolicy);
|
|
48
|
-
return signal?.aborted
|
|
49
|
-
? { ok: false, error: { code: 'cancelled', message: 'The request was cancelled.' } }
|
|
50
|
-
: { ok: true, value };
|
|
51
|
-
} catch {
|
|
52
|
-
return signal?.aborted
|
|
53
|
-
? { ok: false, error: { code: 'cancelled', message: 'The request was cancelled.' } }
|
|
54
|
-
: {
|
|
55
|
-
ok: false,
|
|
56
|
-
error: { code: 'telegram-operation-failed', message: 'Telegram 操作失败,请稍后重试。' },
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
};
|
|
12
|
+
return createTokenBotRpcHandler(controller, { channel: 'Telegram' });
|
|
60
13
|
}
|
|
61
14
|
|
|
62
15
|
export function installTelegramRpc(ctx, controller, authority) {
|
|
@@ -20,6 +20,10 @@ import { createConnectionSupervisor } from './connection-supervisor.mjs';
|
|
|
20
20
|
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
|
|
21
21
|
import { harnessConnection } from '../../harness-connection.mjs';
|
|
22
22
|
import { createHarnessSessionExecutors } from '../../harness-session-coordinator.mjs';
|
|
23
|
+
import {
|
|
24
|
+
accessPolicyProvider,
|
|
25
|
+
initialAccessPolicyFor,
|
|
26
|
+
} from '../shared/access-policy-production.mjs';
|
|
23
27
|
|
|
24
28
|
function pluginPaths(config) {
|
|
25
29
|
const dshHome = resolve(config.dshHome ?? process.env.DSH_HOME ?? join(homedir(), '.dsh'));
|
|
@@ -54,6 +58,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
54
58
|
await workspaces.reconcile(configuredBots.map((bot) => bot.botId));
|
|
55
59
|
await Promise.all(configuredBots.map((bot) => workspaces.ensure(bot.botId, {
|
|
56
60
|
defaultAgentPreset: config.agentPreset,
|
|
61
|
+
initialAccessPolicy: initialAccessPolicyFor('wecom', bot),
|
|
57
62
|
})));
|
|
58
63
|
const observedConfigStore = typeof configStore.remove === 'function'
|
|
59
64
|
? observeBotWorkspaceRemovals(configStore, { workspaces })
|
|
@@ -95,7 +100,10 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
95
100
|
logger,
|
|
96
101
|
createRuntime: async ({ botId, config: botConfig, secret }) => {
|
|
97
102
|
const state = await stateFor(botId);
|
|
98
|
-
await workspaces.ensure(botId, {
|
|
103
|
+
await workspaces.ensure(botId, {
|
|
104
|
+
defaultAgentPreset: config.agentPreset,
|
|
105
|
+
initialAccessPolicy: initialAccessPolicyFor('wecom', botConfig),
|
|
106
|
+
});
|
|
99
107
|
const workspaceScope = createBotWorkspaceScope(harness, {
|
|
100
108
|
botId, workspaces, state, agentPresetCatalog,
|
|
101
109
|
});
|
|
@@ -105,6 +113,9 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
105
113
|
harness: workspaceScope.harness,
|
|
106
114
|
state: workspaceScope.state,
|
|
107
115
|
contextEnhancement: { botId, getSettings: () => workspaces.contextEnhancementFor(botId) },
|
|
116
|
+
accessPolicy: accessPolicyProvider(workspaces, botId, {
|
|
117
|
+
channel: 'wecom', config: botConfig,
|
|
118
|
+
}),
|
|
108
119
|
replyTimeoutMs: config.replyTimeoutMs ?? 600_000,
|
|
109
120
|
connectTimeoutMs: config.connectTimeoutMs ?? 20_000,
|
|
110
121
|
maxReconnectAttempts: config.maxReconnectAttempts ?? 10,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import QRCode from 'qrcode';
|
|
2
2
|
import { SET_CONTEXT_ENHANCEMENT_ENDPOINT, validContextEnhancementPayload } from '../shared/context-enhancement-rpc.mjs';
|
|
3
|
+
import { SET_ACCESS_POLICY_ENDPOINT, validAccessPolicyPayload } from '../shared/access-policy-rpc.mjs';
|
|
3
4
|
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
|
4
5
|
import { publicWorkspaceError, SET_WORKSPACE_ENDPOINT, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
|
|
5
6
|
import { SET_AGENT_PRESET_ENDPOINT, validAgentPresetPayload } from '../shared/agent-preset-rpc.mjs';
|
|
@@ -20,6 +21,7 @@ export const WECOM_ENDPOINTS = Object.freeze({
|
|
|
20
21
|
setWorkspace: SET_WORKSPACE_ENDPOINT,
|
|
21
22
|
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
|
|
22
23
|
setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
|
|
24
|
+
setAccessPolicy: SET_ACCESS_POLICY_ENDPOINT,
|
|
23
25
|
});
|
|
24
26
|
export const WECOM_RPC_ENDPOINTS = Object.freeze(Object.values(WECOM_ENDPOINTS));
|
|
25
27
|
|
|
@@ -82,6 +84,10 @@ function payloadFailure(endpoint, payload) {
|
|
|
82
84
|
return validContextEnhancementPayload(payload)
|
|
83
85
|
? null : '请提交有效的上下文增强设置。';
|
|
84
86
|
}
|
|
87
|
+
if (endpoint === WECOM_ENDPOINTS.setAccessPolicy) {
|
|
88
|
+
return validAccessPolicyPayload(payload)
|
|
89
|
+
? null : '请提交有效的访问设置。';
|
|
90
|
+
}
|
|
85
91
|
return 'Unknown Enterprise WeChat endpoint.';
|
|
86
92
|
}
|
|
87
93
|
|
|
@@ -183,6 +189,11 @@ export function createWecomRpcHandler(controller, { encodeQr = qrDataUrl } = {})
|
|
|
183
189
|
value = await controller.updateContextEnhancement(
|
|
184
190
|
payload.botId, payload.config, (status) => publicStatus(status, cachedEncode),
|
|
185
191
|
);
|
|
192
|
+
} else if (endpoint === WECOM_ENDPOINTS.setAccessPolicy) {
|
|
193
|
+
if (typeof controller.updateAccessPolicy !== 'function') throw new Error('Access policy update is unavailable');
|
|
194
|
+
value = await controller.updateAccessPolicy(
|
|
195
|
+
payload.botId, payload.policy, (status) => publicStatus(status, cachedEncode),
|
|
196
|
+
);
|
|
186
197
|
} else if (endpoint === WECOM_ENDPOINTS.setAgentPreset) {
|
|
187
198
|
if (typeof controller.updateAgentPreset !== 'function') throw new Error('Agent preset update is unavailable');
|
|
188
199
|
value = await publicStatus(
|
|
@@ -23,6 +23,10 @@ import { createConnectionSupervisor } from './connection-supervisor.mjs';
|
|
|
23
23
|
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
|
|
24
24
|
import { harnessConnection } from '../../harness-connection.mjs';
|
|
25
25
|
import { createHarnessSessionExecutors } from '../../harness-session-coordinator.mjs';
|
|
26
|
+
import {
|
|
27
|
+
accessPolicyProvider,
|
|
28
|
+
initialAccessPolicyFor,
|
|
29
|
+
} from '../shared/access-policy-production.mjs';
|
|
26
30
|
|
|
27
31
|
function pluginPaths(config) {
|
|
28
32
|
const dshHome = resolve(config.dshHome ?? process.env.DSH_HOME ?? join(homedir(), '.dsh'));
|
|
@@ -60,6 +64,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
60
64
|
await workspaces.reconcile(configuredBots.map((bot) => bot.botId));
|
|
61
65
|
await Promise.all(configuredBots.map((bot) => workspaces.ensure(bot.botId, {
|
|
62
66
|
defaultAgentPreset: config.agentPreset,
|
|
67
|
+
initialAccessPolicy: initialAccessPolicyFor('weixin', bot),
|
|
63
68
|
})));
|
|
64
69
|
const observedConfigStore = typeof configStore.remove === 'function'
|
|
65
70
|
? observeBotWorkspaceRemovals(configStore, { workspaces })
|
|
@@ -98,7 +103,10 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
98
103
|
logger,
|
|
99
104
|
createRuntime: async ({ botId, config: accountConfig, token }) => {
|
|
100
105
|
const state = await stateFor(botId);
|
|
101
|
-
await workspaces.ensure(botId, {
|
|
106
|
+
await workspaces.ensure(botId, {
|
|
107
|
+
defaultAgentPreset: config.agentPreset,
|
|
108
|
+
initialAccessPolicy: initialAccessPolicyFor('weixin', accountConfig),
|
|
109
|
+
});
|
|
102
110
|
const workspaceScope = createBotWorkspaceScope(harness, {
|
|
103
111
|
botId, workspaces, state, agentPresetCatalog,
|
|
104
112
|
});
|
|
@@ -109,6 +117,9 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
109
117
|
harness: workspaceScope.harness,
|
|
110
118
|
state: workspaceScope.state,
|
|
111
119
|
contextEnhancement: { botId, getSettings: () => workspaces.contextEnhancementFor(botId) },
|
|
120
|
+
accessPolicy: accessPolicyProvider(workspaces, botId, {
|
|
121
|
+
channel: 'weixin', config: accountConfig,
|
|
122
|
+
}),
|
|
112
123
|
replyTimeoutMs: config.replyTimeoutMs ?? 600_000,
|
|
113
124
|
maxMessageChars: config.maxMessageChars ?? DEFAULT_WEIXIN_MAX_MESSAGE_CHARS,
|
|
114
125
|
logger: {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import QRCode from 'qrcode';
|
|
2
2
|
import { SET_CONTEXT_ENHANCEMENT_ENDPOINT, validContextEnhancementPayload } from '../shared/context-enhancement-rpc.mjs';
|
|
3
|
+
import { SET_ACCESS_POLICY_ENDPOINT, validAccessPolicyPayload } from '../shared/access-policy-rpc.mjs';
|
|
3
4
|
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
|
4
5
|
import {
|
|
5
6
|
publicWorkspaceError,
|
|
@@ -27,6 +28,7 @@ export const WEIXIN_ENDPOINTS = Object.freeze({
|
|
|
27
28
|
setWorkspace: SET_WORKSPACE_ENDPOINT,
|
|
28
29
|
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
|
|
29
30
|
setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
|
|
31
|
+
setAccessPolicy: SET_ACCESS_POLICY_ENDPOINT,
|
|
30
32
|
});
|
|
31
33
|
export const WEIXIN_RPC_ENDPOINTS = Object.freeze(Object.values(WEIXIN_ENDPOINTS));
|
|
32
34
|
|
|
@@ -89,6 +91,10 @@ function payloadFailure(endpoint, payload) {
|
|
|
89
91
|
return validContextEnhancementPayload(payload)
|
|
90
92
|
? null : '请提交有效的上下文增强设置。';
|
|
91
93
|
}
|
|
94
|
+
if (endpoint === WEIXIN_ENDPOINTS.setAccessPolicy) {
|
|
95
|
+
return validAccessPolicyPayload(payload)
|
|
96
|
+
? null : '请提交有效的访问设置。';
|
|
97
|
+
}
|
|
92
98
|
return 'Unknown Weixin endpoint.';
|
|
93
99
|
}
|
|
94
100
|
|
|
@@ -218,6 +224,11 @@ export function createWeixinRpcHandler(controller, { encodeQr = qrDataUrl } = {}
|
|
|
218
224
|
value = await controller.updateContextEnhancement(
|
|
219
225
|
payload.botId, payload.config, (status) => publicStatus(status, cachedEncode),
|
|
220
226
|
);
|
|
227
|
+
} else if (endpoint === WEIXIN_ENDPOINTS.setAccessPolicy) {
|
|
228
|
+
if (typeof controller.updateAccessPolicy !== 'function') throw new Error('Access policy update is unavailable');
|
|
229
|
+
value = await controller.updateAccessPolicy(
|
|
230
|
+
payload.botId, payload.policy, (status) => publicStatus(status, cachedEncode),
|
|
231
|
+
);
|
|
221
232
|
} else if (endpoint === WEIXIN_ENDPOINTS.setAgentPreset) {
|
|
222
233
|
if (typeof controller.updateAgentPreset !== 'function') throw new Error('Agent preset update is unavailable');
|
|
223
234
|
value = await publicStatus(
|
|
@@ -6,7 +6,10 @@ import { WhatsappConfigStore } from '../../../../src/channels/whatsapp/config-st
|
|
|
6
6
|
import { WhatsappHarnessClient } from '../../../../src/channels/whatsapp/harness-client.mjs';
|
|
7
7
|
import { WhatsappStateStore } from '../../../../src/channels/whatsapp/state-store.mjs';
|
|
8
8
|
import { WhatsappController } from '../../../../src/channels/whatsapp/whatsapp-controller.mjs';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
WhatsappRuntime,
|
|
11
|
+
whatsappAccessPolicyIdsEqual,
|
|
12
|
+
} from '../../../../src/channels/whatsapp/whatsapp-runtime.mjs';
|
|
10
13
|
import { createWhatsappWebSession } from '../../../../src/channels/whatsapp/whatsapp-web-session.mjs';
|
|
11
14
|
import {
|
|
12
15
|
BotWorkspaceStore,
|
|
@@ -20,6 +23,10 @@ import { createTokenConnectionSupervisor } from '../shared/connection-supervisor
|
|
|
20
23
|
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
|
|
21
24
|
import { harnessConnection } from '../../harness-connection.mjs';
|
|
22
25
|
import { createHarnessSessionExecutors } from '../../harness-session-coordinator.mjs';
|
|
26
|
+
import {
|
|
27
|
+
accessPolicyProvider,
|
|
28
|
+
initialAccessPolicyFor,
|
|
29
|
+
} from '../shared/access-policy-production.mjs';
|
|
23
30
|
|
|
24
31
|
const AUTH_DIRECTORY_PATTERN = /^[a-f0-9-]{36}$/;
|
|
25
32
|
|
|
@@ -61,6 +68,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
61
68
|
await workspaces.reconcile(configuredBots.map((bot) => bot.botId));
|
|
62
69
|
await Promise.all(configuredBots.map((bot) => workspaces.ensure(bot.botId, {
|
|
63
70
|
defaultAgentPreset: config.agentPreset,
|
|
71
|
+
initialAccessPolicy: initialAccessPolicyFor('whatsapp', bot),
|
|
64
72
|
})));
|
|
65
73
|
const observedConfigStore = typeof configStore.remove === 'function'
|
|
66
74
|
? observeBotWorkspaceRemovals(configStore, { workspaces })
|
|
@@ -98,7 +106,10 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
98
106
|
logger,
|
|
99
107
|
createRuntime: async ({ botId, config: botConfig, authDir }) => {
|
|
100
108
|
const state = await stateFor(botId);
|
|
101
|
-
await workspaces.ensure(botId, {
|
|
109
|
+
await workspaces.ensure(botId, {
|
|
110
|
+
defaultAgentPreset: config.agentPreset,
|
|
111
|
+
initialAccessPolicy: initialAccessPolicyFor('whatsapp', botConfig),
|
|
112
|
+
});
|
|
102
113
|
const workspaceScope = createBotWorkspaceScope(harness, {
|
|
103
114
|
botId, workspaces, state, agentPresetCatalog,
|
|
104
115
|
});
|
|
@@ -108,6 +119,9 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
108
119
|
harness: workspaceScope.harness,
|
|
109
120
|
state: workspaceScope.state,
|
|
110
121
|
contextEnhancement: { botId, getSettings: () => workspaces.contextEnhancementFor(botId) },
|
|
122
|
+
accessPolicy: accessPolicyProvider(workspaces, botId, {
|
|
123
|
+
channel: 'whatsapp', config: botConfig, equals: whatsappAccessPolicyIdsEqual,
|
|
124
|
+
}),
|
|
111
125
|
replyTimeoutMs: config.replyTimeoutMs ?? 600_000,
|
|
112
126
|
connectTimeoutMs: config.connectTimeoutMs ?? 30_000,
|
|
113
127
|
createSession,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import QRCode from 'qrcode';
|
|
2
2
|
|
|
3
3
|
import { publicConnectionTestResult } from '../../../../src/channels/shared/connection-test.mjs';
|
|
4
|
-
import {
|
|
4
|
+
import { SET_ACCESS_POLICY_ENDPOINT, validAccessPolicyPayload } from '../shared/access-policy-rpc.mjs';
|
|
5
5
|
import { SET_CONTEXT_ENHANCEMENT_ENDPOINT, validContextEnhancementPayload } from '../shared/context-enhancement-rpc.mjs';
|
|
6
6
|
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
|
7
7
|
import { publicWorkspaceError, SET_WORKSPACE_ENDPOINT, validWorkspacePayload } from '../shared/workspace-rpc.mjs';
|
|
@@ -15,7 +15,7 @@ export const WHATSAPP_ENDPOINTS = Object.freeze({
|
|
|
15
15
|
cancelProvisioning: 'provision.cancel',
|
|
16
16
|
reconnectBot: 'bot.reconnect',
|
|
17
17
|
deleteBot: 'bot.delete',
|
|
18
|
-
setAccessPolicy:
|
|
18
|
+
setAccessPolicy: SET_ACCESS_POLICY_ENDPOINT,
|
|
19
19
|
setWorkspace: SET_WORKSPACE_ENDPOINT,
|
|
20
20
|
setAgentPreset: SET_AGENT_PRESET_ENDPOINT,
|
|
21
21
|
setContextEnhancement: SET_CONTEXT_ENHANCEMENT_ENDPOINT,
|
|
@@ -55,15 +55,8 @@ function payloadFailure(endpoint, payload) {
|
|
|
55
55
|
&& payload.confirm === true ? null : 'bot.delete requires a botId and confirm=true.';
|
|
56
56
|
}
|
|
57
57
|
if (endpoint === WHATSAPP_ENDPOINTS.setAccessPolicy) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|| !validId(payload.botId)) return '请输入有效的 WhatsApp 访问模式和电话号码。';
|
|
61
|
-
try {
|
|
62
|
-
normalizeWhatsappAccessPolicy(payload);
|
|
63
|
-
return null;
|
|
64
|
-
} catch {
|
|
65
|
-
return '请输入有效的 WhatsApp 访问模式和电话号码。';
|
|
66
|
-
}
|
|
58
|
+
return validAccessPolicyPayload(payload)
|
|
59
|
+
? null : '请提交有效的访问设置。';
|
|
67
60
|
}
|
|
68
61
|
if (endpoint === WHATSAPP_ENDPOINTS.setWorkspace) {
|
|
69
62
|
return validWorkspacePayload(payload)
|
|
@@ -111,7 +104,7 @@ async function publicStatus(value, encodeQr) {
|
|
|
111
104
|
}
|
|
112
105
|
|
|
113
106
|
export function createWhatsappRpcHandler(controller, { encodeQr = qrDataUrl } = {}) {
|
|
114
|
-
for (const method of ['status', 'startProvisioning', 'registrationStatus', 'cancelProvisioning', 'reconnectBot', 'deleteBot'
|
|
107
|
+
for (const method of ['status', 'startProvisioning', 'registrationStatus', 'cancelProvisioning', 'reconnectBot', 'deleteBot']) {
|
|
115
108
|
if (typeof controller?.[method] !== 'function') {
|
|
116
109
|
throw new TypeError(`A complete WhatsApp controller is required (${method})`);
|
|
117
110
|
}
|
|
@@ -191,9 +184,11 @@ export function createWhatsappRpcHandler(controller, { encodeQr = qrDataUrl } =
|
|
|
191
184
|
cachedEncode,
|
|
192
185
|
);
|
|
193
186
|
} else if (endpoint === WHATSAPP_ENDPOINTS.setAccessPolicy) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
187
|
+
if (typeof controller.updateAccessPolicy !== 'function') throw new Error('Access policy update is unavailable');
|
|
188
|
+
value = await controller.updateAccessPolicy(
|
|
189
|
+
payload.botId,
|
|
190
|
+
payload.policy,
|
|
191
|
+
(status) => publicStatus(status, cachedEncode),
|
|
197
192
|
);
|
|
198
193
|
} else {
|
|
199
194
|
value = await publicStatus(await controller.deleteBot(payload.botId), cachedEncode);
|
|
@@ -58,6 +58,10 @@ import {
|
|
|
58
58
|
messageFailureText,
|
|
59
59
|
setLastMessageFailure,
|
|
60
60
|
} from '../shared/message-failure.mjs';
|
|
61
|
+
import {
|
|
62
|
+
COMMAND_PERMISSION_DENIED_MESSAGE,
|
|
63
|
+
evaluateInboundAccess,
|
|
64
|
+
} from '../shared/inbound-access.mjs';
|
|
61
65
|
import { t } from '../shared/i18n.mjs';
|
|
62
66
|
|
|
63
67
|
const CARD_INITIAL_TEXT = '已连接 DeepSeek Harness,正在思考…';
|
|
@@ -374,6 +378,7 @@ export class DingtalkHarnessBridge {
|
|
|
374
378
|
#harness;
|
|
375
379
|
#state;
|
|
376
380
|
#contextEnhancement;
|
|
381
|
+
#accessPolicy;
|
|
377
382
|
#status;
|
|
378
383
|
#logger;
|
|
379
384
|
#replyTimeoutMs;
|
|
@@ -397,6 +402,7 @@ export class DingtalkHarnessBridge {
|
|
|
397
402
|
harness,
|
|
398
403
|
state,
|
|
399
404
|
contextEnhancement,
|
|
405
|
+
accessPolicy,
|
|
400
406
|
status = createDingtalkBridgeStatus(),
|
|
401
407
|
logger = console,
|
|
402
408
|
replyTimeoutMs = 600_000,
|
|
@@ -415,6 +421,7 @@ export class DingtalkHarnessBridge {
|
|
|
415
421
|
this.#harness = harness;
|
|
416
422
|
this.#state = state;
|
|
417
423
|
this.#contextEnhancement = contextEnhancement;
|
|
424
|
+
this.#accessPolicy = accessPolicy;
|
|
418
425
|
this.#status = status;
|
|
419
426
|
this.#logger = logger;
|
|
420
427
|
this.#approvals = new HarnessApprovalQueue({ label: 'DingTalk', logger });
|
|
@@ -439,17 +446,13 @@ export class DingtalkHarnessBridge {
|
|
|
439
446
|
const sender = senderStaffId(message);
|
|
440
447
|
if (!messageId || !sender || this.#state.hasSeen(messageId)
|
|
441
448
|
|| this.#acceptedMessageIds.has(messageId)) return Promise.resolve();
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
message.conversationType === '1' || message.conversationType === 1 ? 'direct'
|
|
445
|
-
: message.conversationType === '2' || message.conversationType === 2 ? 'group' : null,
|
|
446
|
-
) : contextSnapshot);
|
|
449
|
+
const conversationType = String(message.conversationType) === '2' ? 'group'
|
|
450
|
+
: String(message.conversationType) === '1' ? 'direct' : null;
|
|
447
451
|
|
|
448
452
|
let key;
|
|
449
453
|
try {
|
|
450
454
|
key = conversationKey(message, sender);
|
|
451
455
|
} catch {
|
|
452
|
-
this.#acceptedMessageIds.delete(messageId);
|
|
453
456
|
increment(this.#status, 'messagesRejected');
|
|
454
457
|
this.#status.lastRejectedAt = new Date().toISOString();
|
|
455
458
|
return Promise.resolve();
|
|
@@ -461,9 +464,6 @@ export class DingtalkHarnessBridge {
|
|
|
461
464
|
} catch {
|
|
462
465
|
// An unsafe reply route must never be able to submit an approval.
|
|
463
466
|
}
|
|
464
|
-
if (sessionWebhook && String(message.conversationType) !== '2') {
|
|
465
|
-
rememberConnectionTestTarget(this.#state, { sessionWebhook });
|
|
466
|
-
}
|
|
467
467
|
const pending = this.#pendingInteractions.get(key);
|
|
468
468
|
const promptMessage = dingtalkInboundMessage(message, {
|
|
469
469
|
api: this.#api,
|
|
@@ -473,6 +473,26 @@ export class DingtalkHarnessBridge {
|
|
|
473
473
|
const commandText = nonEmptyString(promptMessage.content) ?? '';
|
|
474
474
|
const addressed = String(message.conversationType) !== '2' || message?.isInAtList === true;
|
|
475
475
|
const direct = String(message.conversationType) !== '2';
|
|
476
|
+
if (addressed) {
|
|
477
|
+
const access = evaluateInboundAccess(this.#accessPolicy, {
|
|
478
|
+
conversationType,
|
|
479
|
+
senderIds: sender,
|
|
480
|
+
text: commandText,
|
|
481
|
+
hasImages: hasInboundImages(promptMessage),
|
|
482
|
+
hasFiles: hasInboundFiles(promptMessage),
|
|
483
|
+
});
|
|
484
|
+
if (!access.allowed) {
|
|
485
|
+
this.#acceptedMessageIds.set(messageId, null);
|
|
486
|
+
return this.#finishAccessDecision(message, messageId, sessionWebhook, access);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
this.#acceptedMessageIds.set(messageId, contextSnapshot === undefined ? captureContextEnhancement(
|
|
490
|
+
this.#contextEnhancement,
|
|
491
|
+
conversationType,
|
|
492
|
+
) : contextSnapshot);
|
|
493
|
+
if (sessionWebhook && direct) {
|
|
494
|
+
rememberConnectionTestTarget(this.#state, { sessionWebhook });
|
|
495
|
+
}
|
|
476
496
|
const statusReaction = sessionWebhook && addressed ? this.#startStatusReaction(message) : null;
|
|
477
497
|
const finish = (task) => Promise.resolve(task).then(
|
|
478
498
|
(value) => {
|
|
@@ -855,6 +875,38 @@ export class DingtalkHarnessBridge {
|
|
|
855
875
|
return task;
|
|
856
876
|
}
|
|
857
877
|
|
|
878
|
+
#finishAccessDecision(message, messageId, sessionWebhook, access) {
|
|
879
|
+
let task;
|
|
880
|
+
task = Promise.resolve().then(async () => {
|
|
881
|
+
if (this.#state.hasSeen(messageId)) return;
|
|
882
|
+
await this.#state.markSeen(messageId);
|
|
883
|
+
if (access.reason === 'command-not-allowed' && sessionWebhook) {
|
|
884
|
+
increment(this.#status, 'messagesReceived');
|
|
885
|
+
this.#status.lastMessageAt = new Date().toISOString();
|
|
886
|
+
await this.#send(
|
|
887
|
+
sessionWebhook,
|
|
888
|
+
t(COMMAND_PERMISSION_DENIED_MESSAGE),
|
|
889
|
+
this.#atUsersFor(message),
|
|
890
|
+
);
|
|
891
|
+
increment(this.#status, 'messagesReplied');
|
|
892
|
+
this.#status.lastReplyAt = new Date().toISOString();
|
|
893
|
+
} else {
|
|
894
|
+
increment(this.#status, 'messagesRejected');
|
|
895
|
+
this.#status.lastRejectedAt = new Date().toISOString();
|
|
896
|
+
}
|
|
897
|
+
this.#status.lastError = null;
|
|
898
|
+
}).catch((error) => {
|
|
899
|
+
if (this.#signal?.aborted) return;
|
|
900
|
+
this.#status.lastError = error?.message ?? String(error);
|
|
901
|
+
this.#logger.error?.('[dsh-dingtalk] failed to apply inbound access policy', error);
|
|
902
|
+
}).finally(() => {
|
|
903
|
+
this.#acceptedMessageIds.delete(messageId);
|
|
904
|
+
this.#commandTasks.delete(task);
|
|
905
|
+
});
|
|
906
|
+
this.#commandTasks.add(task);
|
|
907
|
+
return task;
|
|
908
|
+
}
|
|
909
|
+
|
|
858
910
|
async #process(message, messageId, sender, key, {
|
|
859
911
|
alreadyRecorded = false,
|
|
860
912
|
preparedMessage,
|
|
@@ -130,6 +130,7 @@ export class DingtalkRuntime {
|
|
|
130
130
|
#harness;
|
|
131
131
|
#state;
|
|
132
132
|
#contextEnhancement;
|
|
133
|
+
#accessPolicy;
|
|
133
134
|
#logger;
|
|
134
135
|
#replyTimeoutMs;
|
|
135
136
|
#maxMessageChars;
|
|
@@ -152,6 +153,7 @@ export class DingtalkRuntime {
|
|
|
152
153
|
harness,
|
|
153
154
|
state,
|
|
154
155
|
contextEnhancement,
|
|
156
|
+
accessPolicy,
|
|
155
157
|
logger = console,
|
|
156
158
|
replyTimeoutMs = 600_000,
|
|
157
159
|
maxMessageChars = 4_000,
|
|
@@ -170,6 +172,7 @@ export class DingtalkRuntime {
|
|
|
170
172
|
this.#harness = harness;
|
|
171
173
|
this.#state = state;
|
|
172
174
|
this.#contextEnhancement = contextEnhancement;
|
|
175
|
+
this.#accessPolicy = accessPolicy;
|
|
173
176
|
this.#logger = logger;
|
|
174
177
|
this.#replyTimeoutMs = replyTimeoutMs;
|
|
175
178
|
this.#maxMessageChars = maxMessageChars;
|
|
@@ -238,6 +241,7 @@ export class DingtalkRuntime {
|
|
|
238
241
|
harness: this.#harness,
|
|
239
242
|
state: this.#state,
|
|
240
243
|
contextEnhancement: this.#contextEnhancement,
|
|
244
|
+
accessPolicy: this.#accessPolicy,
|
|
241
245
|
status: this.#status,
|
|
242
246
|
logger: this.#logger,
|
|
243
247
|
replyTimeoutMs: this.#replyTimeoutMs,
|
|
@@ -3,6 +3,7 @@ import { fetchFileStream } from '../shared/file-download.mjs';
|
|
|
3
3
|
import { fetchImageBuffer } from '../shared/image-prompt.mjs';
|
|
4
4
|
import { t } from '../shared/i18n.mjs';
|
|
5
5
|
import { captureContextEnhancement } from '../shared/context-enhancement.mjs';
|
|
6
|
+
import { evaluateInboundAccess } from '../shared/inbound-access.mjs';
|
|
6
7
|
import { DiscordApi } from './discord-api.mjs';
|
|
7
8
|
import { createDiscordBridgeStatus, DiscordHarnessBridge } from './discord-bridge.mjs';
|
|
8
9
|
|
|
@@ -433,6 +434,7 @@ export class DiscordRuntime {
|
|
|
433
434
|
#harness;
|
|
434
435
|
#state;
|
|
435
436
|
#contextEnhancement;
|
|
437
|
+
#accessPolicy;
|
|
436
438
|
#logger;
|
|
437
439
|
#replyTimeoutMs;
|
|
438
440
|
#connectTimeoutMs;
|
|
@@ -464,6 +466,7 @@ export class DiscordRuntime {
|
|
|
464
466
|
harness,
|
|
465
467
|
state,
|
|
466
468
|
contextEnhancement,
|
|
469
|
+
accessPolicy,
|
|
467
470
|
logger = console,
|
|
468
471
|
replyTimeoutMs = 600_000,
|
|
469
472
|
connectTimeoutMs = 20_000,
|
|
@@ -480,6 +483,7 @@ export class DiscordRuntime {
|
|
|
480
483
|
this.#harness = harness;
|
|
481
484
|
this.#state = state;
|
|
482
485
|
this.#contextEnhancement = contextEnhancement;
|
|
486
|
+
this.#accessPolicy = accessPolicy;
|
|
483
487
|
this.#logger = logger;
|
|
484
488
|
this.#replyTimeoutMs = replyTimeoutMs;
|
|
485
489
|
this.#connectTimeoutMs = connectTimeoutMs;
|
|
@@ -559,6 +563,7 @@ export class DiscordRuntime {
|
|
|
559
563
|
harness: this.#harness,
|
|
560
564
|
state: this.#state,
|
|
561
565
|
contextEnhancement: this.#contextEnhancement,
|
|
566
|
+
accessPolicy: this.#accessPolicy,
|
|
562
567
|
status: this.#status,
|
|
563
568
|
logger: this.#logger,
|
|
564
569
|
replyTimeoutMs: this.#replyTimeoutMs,
|
|
@@ -730,6 +735,24 @@ export class DiscordRuntime {
|
|
|
730
735
|
async #acceptMessage(message, bridge) {
|
|
731
736
|
const messageId = String(message?.id ?? '');
|
|
732
737
|
if (!messageId || this.#state.hasSeen(messageId)) return;
|
|
738
|
+
const preflight = normalizeDiscordMessage(message, this.#config.platformId);
|
|
739
|
+
let accessDecision;
|
|
740
|
+
if (preflight?.kind === 'group' && preflight.addressed === true
|
|
741
|
+
&& preflight.senderIsBot !== true) {
|
|
742
|
+
accessDecision = evaluateInboundAccess(this.#accessPolicy, {
|
|
743
|
+
conversationType: 'group',
|
|
744
|
+
senderIds: [preflight.senderId],
|
|
745
|
+
text: preflight.content,
|
|
746
|
+
hasImages: preflight.images.length > 0,
|
|
747
|
+
hasFiles: preflight.files.length > 0,
|
|
748
|
+
});
|
|
749
|
+
if (!accessDecision.allowed) {
|
|
750
|
+
// Let the shared bridge apply its normal silent/command-denial behavior,
|
|
751
|
+
// but do so against the source channel before creating a Thread.
|
|
752
|
+
await bridge.accept(preflight, { accessDecision });
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
733
756
|
let route = this.#routing.get(messageId);
|
|
734
757
|
if (!route) {
|
|
735
758
|
const contextSnapshot = captureContextEnhancement(
|
|
@@ -750,7 +773,12 @@ export class DiscordRuntime {
|
|
|
750
773
|
}
|
|
751
774
|
try {
|
|
752
775
|
const normalized = await route.pendingRoute;
|
|
753
|
-
if (normalized)
|
|
776
|
+
if (normalized) {
|
|
777
|
+
await bridge.accept(normalized, {
|
|
778
|
+
contextSnapshot: route.contextSnapshot,
|
|
779
|
+
...(accessDecision ? { accessDecision } : {}),
|
|
780
|
+
});
|
|
781
|
+
}
|
|
754
782
|
} catch (error) {
|
|
755
783
|
if (error?.code === 'discord-thread-create-uncertain') {
|
|
756
784
|
await this.#state.markSeen(messageId);
|