@xmanrui/dsh-im 4.22.0 → 4.23.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 +3 -3
- package/README.md +3 -3
- package/lib/client.js +2505 -1152
- package/lib/index.js +290 -299
- package/package.json +8 -1
- 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 +16 -3
- 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 +5 -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/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 +61 -0
- package/plugin-src/client/index.js +30 -2
- package/plugin-src/client/last-message-error.js +2 -1
- package/plugin-src/client/styles.js +6 -0
- 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 +5 -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/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/management-rpc.mjs +12 -2
- package/scripts/verify-package.mjs +3 -1
- 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 +692 -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 +593 -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 +8 -7
- package/src/channels/feishu/feishu-runtime.mjs +15 -8
- package/src/channels/feishu/group-message-permission-manager.mjs +2 -1
- 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/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 +63 -18
- 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 +90 -11
- 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/telegram-api.mjs +2 -2
- package/src/channels/telegram/telegram-runtime.mjs +17 -13
- 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,4 @@
|
|
|
1
|
+
import { createConnectionDiagnostics, diagnosticRpcResult } from '../../../../src/channels/shared/connection-error.mjs';
|
|
1
2
|
import { registerManagementRpc } from '../../../management-rpc.mjs';
|
|
2
3
|
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
|
3
4
|
import { OFFICE_RPC_CHANNEL, OFFICE_RPC_ENDPOINTS } from '../../../../src/channels/office/protocol.mjs';
|
|
@@ -15,6 +16,7 @@ function validConfigure(payload) {
|
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export function createOfficeRpcHandler(controller) {
|
|
19
|
+
const diagnostics = controller.diagnostics ?? createConnectionDiagnostics({ channel: 'office' });
|
|
18
20
|
for (const method of ['status', 'configure', 'reconnect', 'test', 'remove']) {
|
|
19
21
|
if (typeof controller?.[method] !== 'function') throw new TypeError(`AI Office controller requires ${method}()`);
|
|
20
22
|
}
|
|
@@ -35,7 +37,7 @@ export function createOfficeRpcHandler(controller) {
|
|
|
35
37
|
const message = code === 'invalid-device-token' ? 'AI Office Device Token 无效。'
|
|
36
38
|
: code === 'office-hook-unavailable' ? 'AI Office Hook 尚未上线或地址不正确。'
|
|
37
39
|
: error instanceof TypeError ? error.message : 'AI Office 连接操作失败,请稍后重试。';
|
|
38
|
-
return { ok: false, error: { code, message } };
|
|
40
|
+
return diagnosticRpcResult(diagnostics, error, { ok: false, error: { code, message } }, { operation: endpoint, botId: payload?.botId, untrustedPublicError: true });
|
|
39
41
|
}
|
|
40
42
|
};
|
|
41
43
|
}
|
|
@@ -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';
|
|
@@ -104,6 +105,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
104
105
|
...(controlExecutor ? { controlExecutor } : {}),
|
|
105
106
|
...(sessionMaintenanceExecutor ? { sessionMaintenanceExecutor } : {}),
|
|
106
107
|
...(fileIngressExecutor ? { fileIngressExecutor } : {}),
|
|
108
|
+
imageInputPolicy: () => getImageInputSettingsStore(config).get(),
|
|
107
109
|
});
|
|
108
110
|
const modelCatalog = () => listModelCatalog(harness);
|
|
109
111
|
const coreController = new Controller({
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createConnectionDiagnostics, diagnosticRpcResult } from '../../../../src/channels/shared/connection-error.mjs';
|
|
1
2
|
import { SET_ALIAS_ENDPOINT, validAliasPayload } from '../shared/bot-alias-rpc.mjs';
|
|
2
3
|
import { registerManagementRpc } from '../../../management-rpc.mjs';
|
|
3
4
|
import QRCode from 'qrcode';
|
|
@@ -132,6 +133,7 @@ async function publicStatus(status, encodeQr) {
|
|
|
132
133
|
}
|
|
133
134
|
|
|
134
135
|
export function createQqRpcHandler(controller, { encodeQr = qrDataUrl } = {}) {
|
|
136
|
+
const diagnostics = controller.diagnostics ?? createConnectionDiagnostics({ channel: 'qq' });
|
|
135
137
|
for (const method of ['status', 'startProvisioning', 'registrationStatus', 'cancelProvisioning', 'bindCredentials', 'reconnectBot', 'deleteBot']) {
|
|
136
138
|
if (typeof controller?.[method] !== 'function') throw new TypeError(`A complete QQ controller is required (${method})`);
|
|
137
139
|
}
|
|
@@ -183,7 +185,7 @@ export function createQqRpcHandler(controller, { encodeQr = qrDataUrl } = {}) {
|
|
|
183
185
|
} catch (error) {
|
|
184
186
|
testError = error;
|
|
185
187
|
}
|
|
186
|
-
testMessage = publicConnectionTestResult(testError);
|
|
188
|
+
testMessage = publicConnectionTestResult(testError, { diagnostics, botId: payload.botId });
|
|
187
189
|
}
|
|
188
190
|
}
|
|
189
191
|
value = await publicStatus({
|
|
@@ -231,10 +233,10 @@ export function createQqRpcHandler(controller, { encodeQr = qrDataUrl } = {}) {
|
|
|
231
233
|
: { ok: true, value };
|
|
232
234
|
} catch (error) {
|
|
233
235
|
const workspaceError = publicWorkspaceError(error);
|
|
234
|
-
return signal?.aborted
|
|
236
|
+
return diagnosticRpcResult(diagnostics, error, signal?.aborted
|
|
235
237
|
? { ok: false, error: { code: 'cancelled', message: 'The request was cancelled.' } }
|
|
236
238
|
: { ok: false, error: publicQqStateError(error) ?? workspaceError
|
|
237
|
-
?? { code: 'qq-operation-failed', message: 'QQ 操作失败,请稍后重试。' } };
|
|
239
|
+
?? { code: 'qq-operation-failed', message: 'QQ 操作失败,请稍后重试。' } }, { operation: endpoint, botId: payload?.botId });
|
|
238
240
|
}
|
|
239
241
|
};
|
|
240
242
|
}
|
|
@@ -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';
|
|
@@ -79,6 +80,16 @@ export async function createTokenProductionController(ctx, config, internals, de
|
|
|
79
80
|
const observedConfigStore = typeof configStore.remove === 'function'
|
|
80
81
|
? observeBotWorkspaceRemovals(configStore, { workspaces })
|
|
81
82
|
: configStore;
|
|
83
|
+
// Idempotent, and shared with createRuntime below so both paths seed a new
|
|
84
|
+
// bot's workspace identically. A channel that must write something into the
|
|
85
|
+
// workspace store before the runtime exists (email pushes its sender
|
|
86
|
+
// allowlist into the access policy) needs the record to exist first —
|
|
87
|
+
// setAccessPolicy refuses a bot the store has never seen. The call is safe to
|
|
88
|
+
// repeat: ensure() only seeds the policy when the bot has none yet.
|
|
89
|
+
const ensureWorkspace = (botId, botConfig) => workspaces.ensure(botId, {
|
|
90
|
+
defaultAgentPreset: config.agentPreset,
|
|
91
|
+
initialAccessPolicy: seedAccessPolicy(botConfig),
|
|
92
|
+
});
|
|
82
93
|
const stateStores = new Map();
|
|
83
94
|
const statePath = (botId) => resolve(paths.bots, botId, 'state.json');
|
|
84
95
|
const stateFor = async (botId) => {
|
|
@@ -113,26 +124,61 @@ export async function createTokenProductionController(ctx, config, internals, de
|
|
|
113
124
|
...(controlExecutor ? { controlExecutor } : {}),
|
|
114
125
|
...(sessionMaintenanceExecutor ? { sessionMaintenanceExecutor } : {}),
|
|
115
126
|
...(fileIngressExecutor ? { fileIngressExecutor } : {}),
|
|
127
|
+
imageInputPolicy: () => getImageInputSettingsStore(config).get(),
|
|
116
128
|
});
|
|
117
129
|
const modelCatalog = () => listModelCatalog(harness);
|
|
130
|
+
// Assigned just below. A transport may need to write a rotated token back
|
|
131
|
+
// through the controller, which is only constructible after createRuntime is
|
|
132
|
+
// defined, so the reference is held in a slot rather than captured directly.
|
|
133
|
+
let controllerRef = null;
|
|
118
134
|
const coreController = new ResolvedController({
|
|
119
135
|
credentials: ctx.credentials,
|
|
120
136
|
configStore: observedConfigStore,
|
|
121
137
|
logger,
|
|
122
138
|
...(internals.inspectToken ? { inspectToken: internals.inspectToken } : {}),
|
|
123
|
-
|
|
139
|
+
// Optional per-channel hook: a channel whose own settings also express an
|
|
140
|
+
// access rule (email's sender allowlist) can push the derived policy into
|
|
141
|
+
// the workspace store, which is what the runtime actually reads.
|
|
142
|
+
...(typeof definitions.accessPolicyForBot === 'function' ? {
|
|
143
|
+
syncAccessPolicy: async (botId, policy) => {
|
|
144
|
+
await workspaces.setAccessPolicy(botId, policy, {
|
|
145
|
+
incarnation: workspaces.incarnationFor(botId),
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
// Exposed so the controller can create the workspace record before it
|
|
149
|
+
// pushes a policy into it.
|
|
150
|
+
ensureWorkspace,
|
|
151
|
+
} : {}),
|
|
152
|
+
// Channels that pin conversations to an existing session need the per-bot
|
|
153
|
+
// state and the session catalog to drive their settings UI.
|
|
154
|
+
...(definitions.supportsSessionBinding ? {
|
|
155
|
+
stateFor,
|
|
156
|
+
listWorkspaceSessions: (workspace) => harness.listWorkspaceSessions(workspace),
|
|
157
|
+
botWorkspaceFor: (botId) => workspaces.workspaceFor(botId),
|
|
158
|
+
defaultWorkspace,
|
|
159
|
+
} : {}),
|
|
160
|
+
createRuntime: async ({ botId, config: botConfig, token, credential, createTransport }) => {
|
|
124
161
|
const state = await stateFor(botId);
|
|
125
|
-
await
|
|
126
|
-
defaultAgentPreset: config.agentPreset,
|
|
127
|
-
initialAccessPolicy: seedAccessPolicy(botConfig),
|
|
128
|
-
});
|
|
162
|
+
await ensureWorkspace(botId, botConfig);
|
|
129
163
|
const workspaceScope = createBotWorkspaceScope(harness, {
|
|
130
164
|
botId, workspaces, state, agentPresetCatalog,
|
|
131
165
|
});
|
|
166
|
+
const persistTokens = typeof definitions.persistBotCredential === 'function'
|
|
167
|
+
? (tokens) => definitions.persistBotCredential({
|
|
168
|
+
botId, config: botConfig, tokens, controller: controllerRef,
|
|
169
|
+
})
|
|
170
|
+
: null;
|
|
132
171
|
return new ResolvedRuntime({
|
|
133
172
|
...channelRuntimeOptions,
|
|
134
173
|
config: botConfig,
|
|
135
174
|
token,
|
|
175
|
+
// The mailbox may authenticate with an OAuth pair instead of a password.
|
|
176
|
+
...(credential ? { credential } : {}),
|
|
177
|
+
// A transport that rotates its tokens needs them written back.
|
|
178
|
+
...(persistTokens ? { onTokensRefreshed: persistTokens } : {}),
|
|
179
|
+
// The channel decides which transport a mailbox uses; without this the
|
|
180
|
+
// runtime falls back to its own IMAP/SMTP default.
|
|
181
|
+
...(typeof createTransport === 'function' ? { createTransport } : {}),
|
|
136
182
|
harness: workspaceScope.harness,
|
|
137
183
|
state: workspaceScope.state,
|
|
138
184
|
contextEnhancement: { botId, getSettings: () => workspaces.contextEnhancementFor(botId) },
|
|
@@ -161,6 +207,7 @@ export async function createTokenProductionController(ctx, config, internals, de
|
|
|
161
207
|
}
|
|
162
208
|
},
|
|
163
209
|
});
|
|
210
|
+
controllerRef = coreController;
|
|
164
211
|
const controller = createWorkspaceAwareController(coreController, {
|
|
165
212
|
workspaces,
|
|
166
213
|
stateFor,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createConnectionDiagnostics, diagnosticRpcResult } from '../../../../src/channels/shared/connection-error.mjs';
|
|
1
2
|
import { SET_ALIAS_ENDPOINT, validAliasPayload } from './bot-alias-rpc.mjs';
|
|
2
3
|
import { registerManagementRpc } from '../../../management-rpc.mjs';
|
|
3
4
|
import { SET_CONTEXT_ENHANCEMENT_ENDPOINT, validContextEnhancementPayload } from './context-enhancement-rpc.mjs';
|
|
@@ -131,6 +132,7 @@ function operationError(channel, error) {
|
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
export function createTokenBotRpcHandler(controller, { channel }) {
|
|
135
|
+
const diagnostics = controller.diagnostics ?? createConnectionDiagnostics({ channel: channel.toLowerCase() });
|
|
134
136
|
for (const method of ['status', 'bindCredentials', 'reconnectBot', 'deleteBot']) {
|
|
135
137
|
if (typeof controller?.[method] !== 'function') {
|
|
136
138
|
throw new TypeError(`A complete ${channel} controller is required (${method})`);
|
|
@@ -172,7 +174,7 @@ export function createTokenBotRpcHandler(controller, { channel }) {
|
|
|
172
174
|
} catch (error) {
|
|
173
175
|
testError = error;
|
|
174
176
|
}
|
|
175
|
-
value = { ...value, testMessage: publicConnectionTestResult(testError) };
|
|
177
|
+
value = { ...value, testMessage: publicConnectionTestResult(testError, { diagnostics, botId: payload.botId }) };
|
|
176
178
|
}
|
|
177
179
|
} else if (endpoint === TOKEN_BOT_ENDPOINTS.setWorkspace) {
|
|
178
180
|
if (typeof controller.updateWorkspace !== 'function') throw new Error('Workspace update is unavailable');
|
|
@@ -199,9 +201,9 @@ export function createTokenBotRpcHandler(controller, { channel }) {
|
|
|
199
201
|
? { ok: false, error: { code: 'cancelled', message: 'The request was cancelled.' } }
|
|
200
202
|
: { ok: true, value: sanitizePublic(value) };
|
|
201
203
|
} catch (error) {
|
|
202
|
-
return signal?.aborted
|
|
204
|
+
return diagnosticRpcResult(diagnostics, error, signal?.aborted
|
|
203
205
|
? { ok: false, error: { code: 'cancelled', message: 'The request was cancelled.' } }
|
|
204
|
-
: { ok: false, error: operationError(channel, error) };
|
|
206
|
+
: { ok: false, error: operationError(channel, error) }, { operation: endpoint, botId: payload?.botId });
|
|
205
207
|
}
|
|
206
208
|
};
|
|
207
209
|
}
|
|
@@ -3,7 +3,8 @@ import { t } from '../../../../src/channels/shared/i18n.mjs';
|
|
|
3
3
|
const CHANNEL_NAMES = {
|
|
4
4
|
weixin: '微信', feishu: '飞书', dingtalk: '钉钉', wecom: '企业微信',
|
|
5
5
|
'wecom-app': '企业微信应用', qq: 'QQ', slack: 'Slack', telegram: 'Telegram',
|
|
6
|
-
discord: 'Discord', whatsapp: 'WhatsApp',
|
|
6
|
+
discord: 'Discord', whatsapp: 'WhatsApp', imessage: 'iMessage',
|
|
7
|
+
email: '邮箱', office: 'AI Office',
|
|
7
8
|
};
|
|
8
9
|
const INVALID_CONFIG_MESSAGES = new Set([
|
|
9
10
|
'dsh-weixin config contains invalid account data',
|
|
@@ -11,7 +12,7 @@ const INVALID_CONFIG_MESSAGES = new Set([
|
|
|
11
12
|
'dsh-feishu config contains duplicate bot identities',
|
|
12
13
|
'dsh-feishu config is incomplete or invalid',
|
|
13
14
|
'dsh-dingtalk config contains invalid bot data',
|
|
14
|
-
...['Enterprise WeChat', 'Enterprise WeChat app', 'QQ', 'Slack', 'Telegram', 'Discord']
|
|
15
|
+
...['Enterprise WeChat', 'Enterprise WeChat app', 'QQ', 'Slack', 'Telegram', 'Discord', 'Email']
|
|
15
16
|
.map(channel => `dsh-im ${channel} config contains invalid bot data`),
|
|
16
17
|
'dsh-im WhatsApp config contains invalid account data',
|
|
17
18
|
'dsh-im AI Office config is invalid',
|
|
@@ -47,7 +48,7 @@ export function publicChannelStartupError(channel, error) {
|
|
|
47
48
|
}
|
|
48
49
|
return {
|
|
49
50
|
code: `${channel}-startup-failed`,
|
|
50
|
-
message: t('{channel}
|
|
51
|
+
message: t('{channel}初始化失败。请展开诊断详情,并通过参考号查找 DSH 启动日志,修复后重启 DSH。', params),
|
|
51
52
|
details: {},
|
|
52
53
|
};
|
|
53
54
|
}
|
|
@@ -2,6 +2,7 @@ import { registerManagementRpc } from '../../../management-rpc.mjs';
|
|
|
2
2
|
import { onImHostLanguageChange } from '../../../../src/channels/shared/i18n.mjs';
|
|
3
3
|
import { resolveRpcAuthority } from '../../rpc-authority.mjs';
|
|
4
4
|
import { publicChannelInitializing, publicChannelStartupError } from './startup-error.mjs';
|
|
5
|
+
import { createConnectionDiagnostics, diagnosticRpcResult } from '../../../../src/channels/shared/connection-error.mjs';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Keep a channel's platform-side command menu in the current host message
|
|
@@ -30,13 +31,18 @@ export async function installProductionChannel(ctx, config, {
|
|
|
30
31
|
}) {
|
|
31
32
|
let startupError = publicChannelInitializing(channel);
|
|
32
33
|
let handler = async () => ({ ok: false, error: startupError });
|
|
33
|
-
const
|
|
34
|
+
const logger = typeof ctx.logger === 'function' ? ctx.logger(`dsh-im:${channel}`) : (ctx.logger ?? console);
|
|
35
|
+
const diagnostics = createConnectionDiagnostics({ channel, logger });
|
|
36
|
+
const disposeRpc = registerManagementRpc(ctx, rpcChannel, async (endpoint, payload, signal) => {
|
|
34
37
|
if (signal?.aborted) {
|
|
35
38
|
return { ok: false, error: { code: 'cancelled', message: 'The request was cancelled.', details: {} } };
|
|
36
39
|
}
|
|
37
|
-
return handler(endpoint, payload, signal);
|
|
40
|
+
try { return await handler(endpoint, payload, signal); }
|
|
41
|
+
catch (error) {
|
|
42
|
+
// Preserve a final Host diagnostic if an endpoint misses its own catch.
|
|
43
|
+
return diagnosticRpcResult(diagnostics, error, { ok: false, error: { code: `${channel}-operation-failed` } }, { operation: endpoint });
|
|
44
|
+
}
|
|
38
45
|
}, { authority: resolveRpcAuthority(config.rpcAuthority) });
|
|
39
|
-
const logger = typeof ctx.logger === 'function' ? ctx.logger(`dsh-im:${channel}`) : (ctx.logger ?? console);
|
|
40
46
|
let production;
|
|
41
47
|
let unregisterDelivery;
|
|
42
48
|
let closing;
|
|
@@ -58,13 +64,12 @@ export async function installProductionChannel(ctx, config, {
|
|
|
58
64
|
} catch (error) {
|
|
59
65
|
startupError = reportStartupError
|
|
60
66
|
? reportStartupError(error, false)
|
|
61
|
-
: publicChannelStartupError(channel, error);
|
|
62
|
-
if (!reportStartupError) logger.error?.(`[dsh-im] failed to activate ${channel}; management RPC remains available`, error);
|
|
67
|
+
: diagnostics.report(error, { operation: 'startup', stage: 'startup.load', publicError: publicChannelStartupError(channel, error) }).publicError;
|
|
63
68
|
try {
|
|
64
69
|
await closeProduction();
|
|
65
70
|
} catch (cleanupError) {
|
|
66
71
|
if (reportStartupError) reportStartupError(cleanupError, true);
|
|
67
|
-
else
|
|
72
|
+
else diagnostics.report(cleanupError, { operation: 'startup', stage: 'connection.stop', warning: true });
|
|
68
73
|
}
|
|
69
74
|
}
|
|
70
75
|
return disposeRpc;
|
|
@@ -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 { resolve } from 'node:path';
|
|
3
4
|
|
|
@@ -92,6 +93,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
92
93
|
...(controlExecutor ? { controlExecutor } : {}),
|
|
93
94
|
...(sessionMaintenanceExecutor ? { sessionMaintenanceExecutor } : {}),
|
|
94
95
|
...(fileIngressExecutor ? { fileIngressExecutor } : {}),
|
|
96
|
+
imageInputPolicy: () => getImageInputSettingsStore(config).get(),
|
|
95
97
|
});
|
|
96
98
|
const modelCatalog = () => listModelCatalog(harness);
|
|
97
99
|
const coreController = new ResolvedController({
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createConnectionDiagnostics, diagnosticRpcResult } from '../../../../src/channels/shared/connection-error.mjs';
|
|
1
2
|
import { SET_ALIAS_ENDPOINT, validAliasPayload } from '../shared/bot-alias-rpc.mjs';
|
|
2
3
|
import { registerManagementRpc } from '../../../management-rpc.mjs';
|
|
3
4
|
import { SET_CONTEXT_ENHANCEMENT_ENDPOINT, validContextEnhancementPayload } from '../shared/context-enhancement-rpc.mjs';
|
|
@@ -131,6 +132,7 @@ function operationError(error) {
|
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
export function createSlackRpcHandler(controller) {
|
|
135
|
+
const diagnostics = controller.diagnostics ?? createConnectionDiagnostics({ channel: 'slack' });
|
|
134
136
|
for (const method of ['status', 'bindCredentials', 'reconnectBot', 'deleteBot']) {
|
|
135
137
|
if (typeof controller?.[method] !== 'function') {
|
|
136
138
|
throw new TypeError(`A complete Slack controller is required (${method})`);
|
|
@@ -171,7 +173,7 @@ export function createSlackRpcHandler(controller) {
|
|
|
171
173
|
} catch (error) {
|
|
172
174
|
testError = error;
|
|
173
175
|
}
|
|
174
|
-
value = { ...value, testMessage: publicConnectionTestResult(testError) };
|
|
176
|
+
value = { ...value, testMessage: publicConnectionTestResult(testError, { diagnostics, botId: payload.botId }) };
|
|
175
177
|
}
|
|
176
178
|
}
|
|
177
179
|
else if (endpoint === SLACK_ENDPOINTS.setWorkspace) {
|
|
@@ -203,9 +205,9 @@ export function createSlackRpcHandler(controller) {
|
|
|
203
205
|
? { ok: false, error: { code: 'cancelled', message: 'The request was cancelled.' } }
|
|
204
206
|
: { ok: true, value: sanitizePublic(value) };
|
|
205
207
|
} catch (error) {
|
|
206
|
-
return signal?.aborted
|
|
208
|
+
return diagnosticRpcResult(diagnostics, error, signal?.aborted
|
|
207
209
|
? { ok: false, error: { code: 'cancelled', message: 'The request was cancelled.' } }
|
|
208
|
-
: { ok: false, error: operationError(error) };
|
|
210
|
+
: { ok: false, error: operationError(error) }, { operation: endpoint, botId: payload?.botId });
|
|
209
211
|
}
|
|
210
212
|
};
|
|
211
213
|
}
|
|
@@ -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';
|
|
@@ -107,6 +108,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
107
108
|
...(controlExecutor ? { controlExecutor } : {}),
|
|
108
109
|
...(sessionMaintenanceExecutor ? { sessionMaintenanceExecutor } : {}),
|
|
109
110
|
...(fileIngressExecutor ? { fileIngressExecutor } : {}),
|
|
111
|
+
imageInputPolicy: () => getImageInputSettingsStore(config).get(),
|
|
110
112
|
});
|
|
111
113
|
const modelCatalog = () => listModelCatalog(harness);
|
|
112
114
|
const coreController = new Controller({
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createConnectionDiagnostics, diagnosticRpcResult } from '../../../../src/channels/shared/connection-error.mjs';
|
|
1
2
|
import { SET_ALIAS_ENDPOINT, validAliasPayload } from '../shared/bot-alias-rpc.mjs';
|
|
2
3
|
import { registerManagementRpc } from '../../../management-rpc.mjs';
|
|
3
4
|
import QRCode from 'qrcode';
|
|
@@ -131,6 +132,7 @@ async function publicStatus(status, encodeQr) {
|
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
export function createWecomRpcHandler(controller, { encodeQr = qrDataUrl } = {}) {
|
|
135
|
+
const diagnostics = controller.diagnostics ?? createConnectionDiagnostics({ channel: 'wecom' });
|
|
134
136
|
for (const method of ['status', 'startProvisioning', 'registrationStatus', 'cancelProvisioning', 'bindCredentials', 'reconnectBot', 'deleteBot']) {
|
|
135
137
|
if (typeof controller?.[method] !== 'function') {
|
|
136
138
|
throw new TypeError(`A complete Enterprise WeChat controller is required (${method})`);
|
|
@@ -185,7 +187,7 @@ export function createWecomRpcHandler(controller, { encodeQr = qrDataUrl } = {})
|
|
|
185
187
|
await controller.sendConnectionTest(payload.botId);
|
|
186
188
|
testMessage = publicConnectionTestResult();
|
|
187
189
|
} catch (error) {
|
|
188
|
-
testMessage = publicConnectionTestResult(error);
|
|
190
|
+
testMessage = publicConnectionTestResult(error, { diagnostics, botId: payload.botId });
|
|
189
191
|
}
|
|
190
192
|
}
|
|
191
193
|
}
|
|
@@ -231,10 +233,10 @@ export function createWecomRpcHandler(controller, { encodeQr = qrDataUrl } = {})
|
|
|
231
233
|
: { ok: true, value };
|
|
232
234
|
} catch (error) {
|
|
233
235
|
const workspaceError = publicWorkspaceError(error);
|
|
234
|
-
return signal?.aborted
|
|
236
|
+
return diagnosticRpcResult(diagnostics, error, signal?.aborted
|
|
235
237
|
? { ok: false, error: { code: 'cancelled', message: 'The request was cancelled.' } }
|
|
236
238
|
: { ok: false, error: workspaceError
|
|
237
|
-
?? { code: 'wecom-operation-failed', message: '企业微信操作失败,请稍后重试。' } };
|
|
239
|
+
?? { code: 'wecom-operation-failed', message: '企业微信操作失败,请稍后重试。' } }, { operation: endpoint, botId: payload?.botId });
|
|
238
240
|
}
|
|
239
241
|
};
|
|
240
242
|
}
|
|
@@ -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';
|
|
@@ -141,6 +142,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
141
142
|
...(controlExecutor ? { controlExecutor } : {}),
|
|
142
143
|
...(sessionMaintenanceExecutor ? { sessionMaintenanceExecutor } : {}),
|
|
143
144
|
...(fileIngressExecutor ? { fileIngressExecutor } : {}),
|
|
145
|
+
imageInputPolicy: () => getImageInputSettingsStore(config).get(),
|
|
144
146
|
});
|
|
145
147
|
const modelCatalog = () => listModelCatalog(harness);
|
|
146
148
|
const coreController = new Controller({
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createConnectionDiagnostics, diagnosticRpcResult } from '../../../../src/channels/shared/connection-error.mjs';
|
|
1
2
|
import { SET_ALIAS_ENDPOINT, validAliasPayload } from '../shared/bot-alias-rpc.mjs';
|
|
2
3
|
import { registerManagementRpc } from '../../../management-rpc.mjs';
|
|
3
4
|
import { SET_CONTEXT_ENHANCEMENT_ENDPOINT, validContextEnhancementPayload } from '../shared/context-enhancement-rpc.mjs';
|
|
@@ -136,6 +137,7 @@ function publicStatus(status) {
|
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
export function createWecomAppRpcHandler(controller) {
|
|
140
|
+
const diagnostics = controller.diagnostics ?? createConnectionDiagnostics({ channel: 'wecom-app' });
|
|
139
141
|
for (const method of ['status', 'bindApp', 'updateAppSettings', 'resetCallbackSecret', 'reconnectBot', 'deleteBot']) {
|
|
140
142
|
if (typeof controller?.[method] !== 'function') {
|
|
141
143
|
throw new TypeError(`A complete Enterprise WeChat app controller is required (${method})`);
|
|
@@ -178,7 +180,7 @@ export function createWecomAppRpcHandler(controller) {
|
|
|
178
180
|
await controller.sendConnectionTest(payload.botId);
|
|
179
181
|
testMessage = publicConnectionTestResult();
|
|
180
182
|
} catch (error) {
|
|
181
|
-
testMessage = publicConnectionTestResult(error);
|
|
183
|
+
testMessage = publicConnectionTestResult(error, { diagnostics, botId: payload.botId });
|
|
182
184
|
}
|
|
183
185
|
}
|
|
184
186
|
}
|
|
@@ -215,11 +217,11 @@ export function createWecomAppRpcHandler(controller) {
|
|
|
215
217
|
: { ok: true, value };
|
|
216
218
|
} catch (error) {
|
|
217
219
|
const workspaceError = publicWorkspaceError(error);
|
|
218
|
-
return signal?.aborted
|
|
220
|
+
return diagnosticRpcResult(diagnostics, error, signal?.aborted
|
|
219
221
|
? { ok: false, error: { code: 'cancelled', message: 'The request was cancelled.', details: {} } }
|
|
220
222
|
: { ok: false, error: workspaceError
|
|
221
223
|
? { ...workspaceError, details: {} }
|
|
222
|
-
: { code: 'wecom-app-operation-failed', message: '企业微信应用操作失败,请稍后重试。', details: {} } };
|
|
224
|
+
: { code: 'wecom-app-operation-failed', message: '企业微信应用操作失败,请稍后重试。', details: {} } }, { operation: endpoint, botId: payload?.botId });
|
|
223
225
|
}
|
|
224
226
|
};
|
|
225
227
|
}
|
|
@@ -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';
|
|
@@ -139,6 +140,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
139
140
|
...(controlExecutor ? { controlExecutor } : {}),
|
|
140
141
|
...(sessionMaintenanceExecutor ? { sessionMaintenanceExecutor } : {}),
|
|
141
142
|
...(fileIngressExecutor ? { fileIngressExecutor } : {}),
|
|
143
|
+
imageInputPolicy: () => getImageInputSettingsStore(config).get(),
|
|
142
144
|
});
|
|
143
145
|
const modelCatalog = () => listModelCatalog(harness);
|
|
144
146
|
const coreController = new Controller({
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getImageInputSettingsStore } from '../../../../src/channels/shared/image-input-settings-store.mjs';
|
|
1
2
|
import { rm, 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,4 @@
|
|
|
1
|
+
import { createConnectionDiagnostics, diagnosticRpcResult } from '../../../../src/channels/shared/connection-error.mjs';
|
|
1
2
|
import { SET_ALIAS_ENDPOINT, validAliasPayload } from '../shared/bot-alias-rpc.mjs';
|
|
2
3
|
import { registerManagementRpc } from '../../../management-rpc.mjs';
|
|
3
4
|
import QRCode from 'qrcode';
|
|
@@ -116,6 +117,7 @@ async function publicStatus(value, encodeQr) {
|
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
export function createWhatsappRpcHandler(controller, { encodeQr = qrDataUrl } = {}) {
|
|
120
|
+
const diagnostics = controller.diagnostics ?? createConnectionDiagnostics({ channel: 'whatsapp' });
|
|
119
121
|
for (const method of ['status', 'startProvisioning', 'registrationStatus', 'cancelProvisioning', 'reconnectBot', 'deleteBot']) {
|
|
120
122
|
if (typeof controller?.[method] !== 'function') {
|
|
121
123
|
throw new TypeError(`A complete WhatsApp controller is required (${method})`);
|
|
@@ -176,7 +178,7 @@ export function createWhatsappRpcHandler(controller, { encodeQr = qrDataUrl } =
|
|
|
176
178
|
testError = error;
|
|
177
179
|
}
|
|
178
180
|
}
|
|
179
|
-
value = { ...value, testMessage: publicConnectionTestResult(testError) };
|
|
181
|
+
value = { ...value, testMessage: publicConnectionTestResult(testError, { diagnostics, botId: payload.botId }) };
|
|
180
182
|
}
|
|
181
183
|
} else if (endpoint === WHATSAPP_ENDPOINTS.setWorkspace) {
|
|
182
184
|
if (typeof controller.updateWorkspace !== 'function') throw new Error('Workspace update is unavailable');
|
|
@@ -223,10 +225,10 @@ export function createWhatsappRpcHandler(controller, { encodeQr = qrDataUrl } =
|
|
|
223
225
|
: { ok: true, value };
|
|
224
226
|
} catch (error) {
|
|
225
227
|
const workspaceError = publicWorkspaceError(error);
|
|
226
|
-
return signal?.aborted
|
|
228
|
+
return diagnosticRpcResult(diagnostics, error, signal?.aborted
|
|
227
229
|
? { ok: false, error: { code: 'cancelled', message: 'The request was cancelled.' } }
|
|
228
230
|
: { ok: false, error: workspaceError
|
|
229
|
-
?? { code: 'whatsapp-operation-failed', message: 'WhatsApp 操作失败,请稍后重试。' } };
|
|
231
|
+
?? { code: 'whatsapp-operation-failed', message: 'WhatsApp 操作失败,请稍后重试。' } }, { operation: endpoint, botId: payload?.botId });
|
|
230
232
|
}
|
|
231
233
|
};
|
|
232
234
|
}
|
|
@@ -16,6 +16,7 @@ const CHANNELS = new Set([
|
|
|
16
16
|
'discord',
|
|
17
17
|
'whatsapp',
|
|
18
18
|
'imessage',
|
|
19
|
+
'email',
|
|
19
20
|
]);
|
|
20
21
|
|
|
21
22
|
export function supportsDeliveryChannel(channel) {
|
|
@@ -126,6 +127,16 @@ function normalizeRoute(channel, kind, route) {
|
|
|
126
127
|
case 'imessage':
|
|
127
128
|
oneOf(kind, ['user']);
|
|
128
129
|
return routeWithStrings(route, ['chatGuid']);
|
|
130
|
+
case 'email': {
|
|
131
|
+
oneOf(kind, ['user']);
|
|
132
|
+
const normalized = routeWithStrings(route, ['address']);
|
|
133
|
+
// Mail addresses are normalized to lower case everywhere so a target
|
|
134
|
+
// created from a mixed-case address still matches.
|
|
135
|
+
if (!/^[^\s@<>]+@[^\s@<>]+\.[^\s@<>]+$/.test(normalized.address)) {
|
|
136
|
+
throw invalidTarget('route.address must be a valid email address');
|
|
137
|
+
}
|
|
138
|
+
return { address: normalized.address.toLowerCase() };
|
|
139
|
+
}
|
|
129
140
|
default:
|
|
130
141
|
throw new TypeError(`Unsupported delivery channel: ${channel}`);
|
|
131
142
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { normalizeImageInputSettings } from '../../src/channels/shared/image-input-policy.mjs';
|
|
2
|
+
|
|
3
|
+
export const IMAGE_INPUT_ENDPOINTS = Object.freeze({
|
|
4
|
+
get: 'settings.image-input.get',
|
|
5
|
+
set: 'settings.image-input.set',
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export function createImageInputRpcHandler(store) {
|
|
9
|
+
return async (endpoint, payload, signal) => {
|
|
10
|
+
const object = payload && typeof payload === 'object' && !Array.isArray(payload);
|
|
11
|
+
const valid = object && (endpoint === IMAGE_INPUT_ENDPOINTS.get
|
|
12
|
+
? Object.keys(payload).length === 0
|
|
13
|
+
: endpoint === IMAGE_INPUT_ENDPOINTS.set && Object.keys(payload).length === 4
|
|
14
|
+
&& normalizeImageInputSettings(payload));
|
|
15
|
+
if (!valid) return { ok: false, error: { code: 'bad-request', message: '图片限制无效:请输入正整数,原图接收上限和总量上限不能小于单图上限。' } };
|
|
16
|
+
if (signal?.aborted) return { ok: false, error: { code: 'cancelled', message: 'Request cancelled.' } };
|
|
17
|
+
try {
|
|
18
|
+
const value = endpoint === IMAGE_INPUT_ENDPOINTS.get ? await store.get() : await store.set(payload);
|
|
19
|
+
return { ok: true, value };
|
|
20
|
+
} catch {
|
|
21
|
+
return { ok: false, error: { code: 'image-input-settings-unavailable', message: '无法读取或保存图片设置,请稍后重试。' } };
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { registerManagementRpc } from '../management-rpc.mjs';
|
|
2
2
|
import { normalizeInboundTtlHours } from '../../src/channels/shared/inbound-ttl.mjs';
|
|
3
3
|
import { getInboundTtlRuntime } from './inbound-ttl-runtime.mjs';
|
|
4
|
+
import { getImageInputSettingsStore } from '../../src/channels/shared/image-input-settings-store.mjs';
|
|
5
|
+
import { createImageInputRpcHandler, IMAGE_INPUT_ENDPOINTS } from './image-input-rpc.mjs';
|
|
4
6
|
|
|
5
7
|
export const INBOUND_TTL_RPC_CHANNEL = '/dsh-im-settings';
|
|
6
8
|
export const INBOUND_TTL_ENDPOINTS = Object.freeze({
|
|
@@ -73,9 +75,12 @@ export function installInboundTtlRpc(ctx, options = {}) {
|
|
|
73
75
|
const runtime = options.runtime ?? getInboundTtlRuntime(ctx, options.config);
|
|
74
76
|
const logger = typeof ctx?.logger === 'function'
|
|
75
77
|
? ctx.logger('dsh-im:inbound-ttl') : (ctx?.logger ?? null);
|
|
78
|
+
const ttl = createInboundTtlRpcHandler({ ...runtime, logger });
|
|
79
|
+
const image = createImageInputRpcHandler(getImageInputSettingsStore(options.config));
|
|
76
80
|
return registerManagementRpc(ctx,
|
|
77
81
|
INBOUND_TTL_RPC_CHANNEL,
|
|
78
|
-
|
|
82
|
+
(endpoint, payload, signal) => Object.values(IMAGE_INPUT_ENDPOINTS).includes(endpoint)
|
|
83
|
+
? image(endpoint, payload, signal) : ttl(endpoint, payload, signal),
|
|
79
84
|
{ authority: 'loopback' },
|
|
80
85
|
);
|
|
81
86
|
}
|
|
@@ -10,6 +10,7 @@ import { apply as applyWecomApp } from './channels/wecom-app/index.mjs';
|
|
|
10
10
|
import { apply as applyWeixin } from './channels/weixin/index.mjs';
|
|
11
11
|
import { apply as applyWhatsapp } from './channels/whatsapp/index.mjs';
|
|
12
12
|
import { apply as applyIMessage } from './channels/imessage/index.mjs';
|
|
13
|
+
import { apply as applyEmail } from './channels/email/index.mjs';
|
|
13
14
|
import { installOutboundArtifactTool } from '../../src/channels/shared/semantic/artifact.mjs';
|
|
14
15
|
import { installHostLanguage } from './host-language.mjs';
|
|
15
16
|
import { installHostLanguageRpc } from './host-language-rpc.mjs';
|
|
@@ -60,6 +61,7 @@ export function createImHostPlugin(internals = {}) {
|
|
|
60
61
|
const startOffice = internals.applyOffice ?? applyOffice;
|
|
61
62
|
const startWhatsapp = internals.applyWhatsapp ?? applyWhatsapp;
|
|
62
63
|
const startIMessage = internals.applyIMessage ?? applyIMessage;
|
|
64
|
+
const startEmail = internals.applyEmail ?? applyEmail;
|
|
63
65
|
const channels = [
|
|
64
66
|
['feishu', startFeishu],
|
|
65
67
|
['weixin', startWeixin],
|
|
@@ -72,6 +74,7 @@ export function createImHostPlugin(internals = {}) {
|
|
|
72
74
|
['discord', startDiscord],
|
|
73
75
|
['whatsapp', startWhatsapp],
|
|
74
76
|
['imessage', startIMessage],
|
|
77
|
+
['email', startEmail],
|
|
75
78
|
['office', startOffice],
|
|
76
79
|
];
|
|
77
80
|
return Object.freeze({
|
|
@@ -72,6 +72,16 @@ export function registerManagementRpc(ctx, channel, handler, { authority } = {})
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/** Keep channel UI callers unchanged while using DSH's native correlation and response validation. */
|
|
75
|
-
export function callManagementRpc(connection, channel, method, payload, signal) {
|
|
76
|
-
|
|
75
|
+
export async function callManagementRpc(connection, channel, method, payload, signal) {
|
|
76
|
+
try {
|
|
77
|
+
return await connection.rpc.call('/api', rpcEndpoint(channel), { method, payload }, signal);
|
|
78
|
+
} catch (cause) {
|
|
79
|
+
if (signal?.aborted || cause?.name === 'AbortError') throw cause;
|
|
80
|
+
const error = new Error('无法访问 DSH 管理接口,请检查 DSH 连接或重新登录后重试。', { cause });
|
|
81
|
+
error.code = 'management-unreachable';
|
|
82
|
+
error.details = { operation: method, stage: 'management.request', occurredAt: new Date().toISOString(),
|
|
83
|
+
...(Number.isInteger(cause?.status) && cause.status >= 100 && cause.status <= 599 ? { httpStatus: cause.status } : {}),
|
|
84
|
+
};
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
77
87
|
}
|
|
@@ -147,9 +147,11 @@ if (client.includes('settings.plugins.tab') || clientSources.includes('settings.
|
|
|
147
147
|
// template; the delivery target adds one ordinary checkbox template.
|
|
148
148
|
const contextEditorSource = await readFile(resolve(root, 'plugin-src/client/context-enhancement.js'), 'utf8');
|
|
149
149
|
const deliverySettingsSource = await readFile(resolve(root, 'plugin-src/client/delivery-settings.js'), 'utf8');
|
|
150
|
+
const emailSettingsSource = await readFile(resolve(root, 'plugin-src/client/channels/email/index.js'), 'utf8');
|
|
150
151
|
const otherClientSources = clientSources
|
|
151
152
|
.replace(contextEditorSource, '')
|
|
152
|
-
.replace(deliverySettingsSource, '')
|
|
153
|
+
.replace(deliverySettingsSource, '')
|
|
154
|
+
.replace(emailSettingsSource, '');
|
|
153
155
|
if (/role:\s*["']switch|type:\s*["']checkbox/.test(otherClientSources)
|
|
154
156
|
|| (deliverySettingsSource.match(/type:\s*["']checkbox["']/g) ?? []).length !== 1
|
|
155
157
|
|| /role:\s*["']switch["']/u.test(deliverySettingsSource)
|