@xmanrui/dsh-im 0.8.0 → 0.10.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 +8 -4
- package/README.md +8 -4
- package/lib/client.js +16 -14
- package/lib/index.js +123 -121
- package/package.json +1 -1
- package/plugin-src/client/styles.js +15 -14
- package/plugin-src/host/channels/dingtalk/index.mjs +1 -1
- package/plugin-src/host/channels/dingtalk/production.mjs +3 -0
- package/plugin-src/host/channels/discord/index.mjs +1 -1
- package/plugin-src/host/channels/feishu/index.mjs +1 -1
- package/plugin-src/host/channels/feishu/production.mjs +3 -0
- package/plugin-src/host/channels/qq/index.mjs +1 -1
- package/plugin-src/host/channels/qq/production.mjs +3 -0
- package/plugin-src/host/channels/shared/production.mjs +3 -0
- package/plugin-src/host/channels/slack/index.mjs +1 -1
- package/plugin-src/host/channels/slack/production.mjs +3 -0
- package/plugin-src/host/channels/telegram/index.mjs +1 -1
- package/plugin-src/host/channels/wecom/index.mjs +1 -1
- package/plugin-src/host/channels/wecom/production.mjs +3 -0
- package/plugin-src/host/channels/weixin/index.mjs +1 -1
- package/plugin-src/host/channels/weixin/production.mjs +3 -0
- package/plugin-src/host/channels/whatsapp/index.mjs +1 -1
- package/plugin-src/host/channels/whatsapp/production.mjs +3 -0
- package/plugin-src/host/harness-command-executor.mjs +21 -0
- package/plugin-src/host/index.mjs +1 -1
- package/src/channels/dingtalk/dingtalk-api.mjs +82 -1
- package/src/channels/dingtalk/dingtalk-bridge.mjs +162 -13
- package/src/channels/discord/discord-api.mjs +1 -1
- package/src/channels/discord/discord-runtime.mjs +44 -1
- package/src/channels/feishu/bridge.mjs +45 -11
- package/src/channels/feishu/message-utils.mjs +142 -8
- package/src/channels/feishu/plugin-controller.mjs +1 -0
- package/src/channels/qq/qq-bridge.mjs +107 -13
- package/src/channels/shared/bot-workspace-store.mjs +13 -0
- package/src/channels/shared/compact-command.mjs +95 -0
- package/src/channels/shared/harness-client.mjs +32 -2
- package/src/channels/shared/image-prompt.mjs +268 -0
- package/src/channels/shared/text-harness-bridge.mjs +49 -9
- package/src/channels/shared/workspace-session.mjs +2 -1
- package/src/channels/slack/manifest.mjs +1 -0
- package/src/channels/slack/slack-api.mjs +95 -0
- package/src/channels/slack/slack-runtime.mjs +24 -3
- package/src/channels/telegram/telegram-api.mjs +37 -0
- package/src/channels/telegram/telegram-runtime.mjs +71 -9
- package/src/channels/wecom/wecom-bridge.mjs +163 -16
- package/src/channels/weixin/weixin-api.mjs +98 -2
- package/src/channels/weixin/weixin-bridge.mjs +55 -12
- package/src/channels/whatsapp/whatsapp-runtime.mjs +144 -1
package/package.json
CHANGED
|
@@ -132,12 +132,13 @@ const CSS = String.raw`
|
|
|
132
132
|
.dim-panel .dim-confirm { padding: 18px 24px; border-top: 1px solid var(--dsw-alias-border-l1, #eef0f3); background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); }
|
|
133
133
|
.dim-panel .dim-confirm strong, .dim-panel .dim-confirm h4 { margin: 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 14px; line-height: 1.4; font-weight: 650; }
|
|
134
134
|
.dim-panel .dim-confirm p { margin: 7px 0 0; color: var(--dsw-alias-label-secondary, #646a73); line-height: 1.6; }
|
|
135
|
-
.dim-panel .dim-cardFooter { display: flex; align-items: center; justify-content: space-between; gap:
|
|
136
|
-
.dim-panel .dim-workspace { min-width: 0; display: grid; gap:
|
|
137
|
-
.dim-panel .dim-workspaceHeader { display:
|
|
138
|
-
.dim-panel .dim-
|
|
135
|
+
.dim-panel .dim-cardFooter { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding-top: 6px; border-top: 1px solid var(--dsw-alias-border-l1, #eef0f3); }
|
|
136
|
+
.dim-panel .dim-workspace { min-width: 0; display: grid; grid-template-columns: minmax(0, 1fr) max-content; align-items: center; column-gap: 10px; row-gap: 4px; margin-top: 6px; padding: 6px 10px; border: 1px solid var(--dsw-alias-border-l1, #eef0f3); border-radius: 9px; background: var(--dsw-alias-bg-module-platform, #f7f8fa); }
|
|
137
|
+
.dim-panel .dim-workspaceHeader { display: contents; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: normal; }
|
|
138
|
+
.dim-panel .dim-workspaceHeader > span { grid-column: 1; grid-row: 1; white-space: nowrap; }
|
|
139
|
+
.dim-panel .dim-workspaceEdit { grid-column: 2; grid-row: 1; padding: 0; border: 0; color: #1677ff; background: transparent; font: inherit; font-weight: 560; white-space: nowrap; cursor: pointer; }
|
|
139
140
|
.dim-panel .dim-workspaceEdit:disabled { cursor: not-allowed; opacity: .55; }
|
|
140
|
-
.dim-panel .dim-workspacePath { min-width: 0; display: block; overflow: hidden; color: var(--dsw-alias-label-primary, #1f2329); font: 12px/1.
|
|
141
|
+
.dim-panel .dim-workspacePath { min-width: 0; max-width: 100%; grid-column: 1 / -1; grid-row: 2; display: block; overflow-x: auto; overflow-y: hidden; color: var(--dsw-alias-label-primary, #1f2329); font: 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: nowrap; }
|
|
141
142
|
.dim-directoryPickerBackdrop { --dim-blue: var(--dsw-alias-state-business-primary, #3370ff); --dim-blue-soft: color-mix(in srgb, var(--dim-blue) 9%, transparent); position: fixed; inset: 0; z-index: 1000; display: grid; place-items: center; padding: 24px; background: rgb(15 17 21 / 42%); backdrop-filter: blur(3px); }
|
|
142
143
|
.dim-directoryPickerBackdrop, .dim-directoryPickerBackdrop *, .dim-directoryPickerBackdrop *::before, .dim-directoryPickerBackdrop *::after { box-sizing: border-box; }
|
|
143
144
|
.dim-directoryPicker { width: min(720px, 100%); height: min(620px, calc(100vh - 48px)); min-height: 420px; display: grid; grid-template-rows: auto minmax(0, 1fr) auto; overflow: hidden; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 18px; outline: none; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); box-shadow: 0 24px 72px rgb(15 17 21 / 24%); }
|
|
@@ -181,16 +182,16 @@ const CSS = String.raw`
|
|
|
181
182
|
.dim-directoryPickerActions button:disabled { cursor: not-allowed; opacity: .52; }
|
|
182
183
|
.dim-panel .dim-cardSummary { min-width: 0; color: var(--dsw-alias-label-secondary, #646a73); font: inherit; font-size: 12px; font-weight: 400; line-height: normal; }
|
|
183
184
|
.dim-panel .dim-cardActions { flex: none; display: flex; align-items: center; flex-wrap: nowrap; gap: 8px; margin: 0 0 0 auto; }
|
|
184
|
-
.dim-panel .dim-cardActions .dim-cardAction { flex: none; min-height:
|
|
185
|
+
.dim-panel .dim-cardActions .dim-cardAction { flex: none; min-height: 32px; padding: 0 11px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; font-size: 13px; font-weight: 560; line-height: normal; white-space: nowrap; }
|
|
185
186
|
.dim-panel .dim-cardActions .dim-cardAction:hover:not(:disabled) { border-color: #aeb3bb; background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); }
|
|
186
187
|
.dim-panel .dim-cardActions .dim-cardAction[data-kind="danger"] { color: var(--dsw-alias-state-error-primary, #d54941); }
|
|
187
188
|
.dim-panel .dim-botCard { position: relative; overflow: hidden; border: 1px solid var(--dsw-alias-border-l2, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-layer-1, #fff); box-shadow: 0 1px 2px rgb(31 35 41 / 3%); }
|
|
188
189
|
.dim-panel .dim-botCard::before { display: none; }
|
|
189
|
-
.dim-panel .dim-botCardBody { position: relative; padding:
|
|
190
|
-
.dim-panel .dim-botCardTop { display: flex; align-items: flex-start; justify-content: space-between; gap:
|
|
191
|
-
.dim-panel .dim-botIdentity { min-width: 0; display: flex; align-items: center; gap:
|
|
192
|
-
.dim-panel .dim-botAvatar { flex: none; width:
|
|
193
|
-
.dim-panel .dim-botAvatar svg { width:
|
|
190
|
+
.dim-panel .dim-botCardBody { position: relative; padding: 12px; }
|
|
191
|
+
.dim-panel .dim-botCardTop { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
|
|
192
|
+
.dim-panel .dim-botIdentity { min-width: 0; display: flex; align-items: center; gap: 10px; }
|
|
193
|
+
.dim-panel .dim-botAvatar { flex: none; width: 38px; height: 38px; display: grid; place-items: center; overflow: hidden; border-radius: 11px; box-shadow: none; }
|
|
194
|
+
.dim-panel .dim-botAvatar svg { width: 27px; height: 27px; }
|
|
194
195
|
.dim-panel .dim-botName { min-width: 0; }
|
|
195
196
|
.dim-panel .dim-botName h3 { overflow: hidden; margin: 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 15px; font-weight: 650; line-height: normal; text-overflow: ellipsis; white-space: nowrap; }
|
|
196
197
|
.dim-panel .dim-botName p { overflow: hidden; margin: 4px 0 0; color: var(--dsw-alias-label-secondary, #646a73); font: 12px ui-monospace, SFMono-Regular, monospace; line-height: normal; text-overflow: ellipsis; white-space: nowrap; }
|
|
@@ -199,10 +200,10 @@ const CSS = String.raw`
|
|
|
199
200
|
.dim-panel .dim-botCard .dim-healthDot[data-tone="success"] { background: var(--dsw-alias-state-success-primary, #20a162); box-shadow: 0 0 0 3px color-mix(in srgb, var(--dsw-alias-state-success-primary, #20a162) 14%, transparent); }
|
|
200
201
|
.dim-panel .dim-botCard .dim-healthDot[data-tone="warning"] { background: var(--dsw-alias-state-warn-primary, #d97706); }
|
|
201
202
|
.dim-panel .dim-botCard .dim-healthDot[data-tone="error"] { background: var(--dsw-alias-state-error-primary, #d54941); }
|
|
202
|
-
.dim-panel .dim-botMetrics { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap:
|
|
203
|
-
.dim-panel .dim-botMetric { min-width: 0; padding: 8px; border: 0; border-radius: 9px; background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); }
|
|
203
|
+
.dim-panel .dim-botMetrics { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin: 6px 0; }
|
|
204
|
+
.dim-panel .dim-botMetric { min-width: 0; padding: 6px 8px; border: 0; border-radius: 9px; background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); }
|
|
204
205
|
.dim-panel .dim-botMetric dt { margin: 0; color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; font-weight: 400; line-height: normal; }
|
|
205
|
-
.dim-panel .dim-botMetric dd { overflow: hidden; margin:
|
|
206
|
+
.dim-panel .dim-botMetric dd { overflow: hidden; margin: 3px 0 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 12px; font-weight: 400; line-height: normal; text-overflow: ellipsis; white-space: nowrap; }
|
|
206
207
|
.dim-panel .dim-botCard .dim-cardFooter { margin-top: 0; }
|
|
207
208
|
.dim-panel .ddt-headingCopy { display: none; }
|
|
208
209
|
.dim-panel .ddt-qrFrame, .dim-panel .ddt-countdown { width: min(270px, 100%); }
|
|
@@ -2,7 +2,7 @@ import { createProductionController } from './production.mjs';
|
|
|
2
2
|
import { installDingtalkRpc } from './rpc.mjs';
|
|
3
3
|
|
|
4
4
|
export const name = 'dsh-dingtalk-host';
|
|
5
|
-
export const inject = ['connection', 'credentials', 'webServer'];
|
|
5
|
+
export const inject = ['connection', 'credentials', 'webServer', 'typertGateway'];
|
|
6
6
|
|
|
7
7
|
export async function apply(ctx, config = {}) {
|
|
8
8
|
if (config?.controller) {
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
observeBotWorkspaceRemovals,
|
|
16
16
|
} from '../../../../src/channels/shared/bot-workspace-store.mjs';
|
|
17
17
|
import { createConnectionSupervisor } from './connection-supervisor.mjs';
|
|
18
|
+
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
|
|
18
19
|
|
|
19
20
|
function harnessOrigin(webServer, configured) {
|
|
20
21
|
if (configured !== undefined) return new URL(configured);
|
|
@@ -79,12 +80,14 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
79
80
|
}
|
|
80
81
|
return state;
|
|
81
82
|
};
|
|
83
|
+
const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
|
|
82
84
|
const harness = new Harness({
|
|
83
85
|
baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
|
|
84
86
|
workspace: defaultWorkspace,
|
|
85
87
|
agentPreset: config.agentPreset ?? 'standard',
|
|
86
88
|
autostart: false,
|
|
87
89
|
dshBin: config.dshBin ?? 'dsh',
|
|
90
|
+
...(commandExecutor ? { commandExecutor } : {}),
|
|
88
91
|
});
|
|
89
92
|
const coreController = new Controller({
|
|
90
93
|
deviceAuth,
|
|
@@ -2,7 +2,7 @@ import { createProductionController } from './production.mjs';
|
|
|
2
2
|
import { installDiscordRpc } from './rpc.mjs';
|
|
3
3
|
|
|
4
4
|
export const name = 'dsh-im-discord-host';
|
|
5
|
-
export const inject = ['connection', 'credentials', 'webServer'];
|
|
5
|
+
export const inject = ['connection', 'credentials', 'webServer', 'typertGateway'];
|
|
6
6
|
|
|
7
7
|
export async function apply(ctx, config = {}) {
|
|
8
8
|
if (config?.controller) {
|
|
@@ -3,7 +3,7 @@ import { createProductionController } from './production.mjs';
|
|
|
3
3
|
import { installFeishuRpc } from './rpc.mjs';
|
|
4
4
|
|
|
5
5
|
export const name = 'dsh-feishu-host';
|
|
6
|
-
export const inject = ['connection', 'credentials', 'webServer'];
|
|
6
|
+
export const inject = ['connection', 'credentials', 'webServer', 'typertGateway'];
|
|
7
7
|
|
|
8
8
|
function controllerFrom(ctx, config) {
|
|
9
9
|
if (config?.controller) return config.controller;
|
|
@@ -3,6 +3,7 @@ import { join, resolve } from 'node:path';
|
|
|
3
3
|
import { unlink } from 'node:fs/promises';
|
|
4
4
|
import * as Lark from '@larksuiteoapi/node-sdk';
|
|
5
5
|
import { createConnectionSupervisor } from './connection-supervisor.mjs';
|
|
6
|
+
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
|
|
6
7
|
import { verifyFeishuApp } from '../../../../src/channels/feishu/feishu-app.mjs';
|
|
7
8
|
import { FeishuRuntime } from '../../../../src/channels/feishu/feishu-runtime.mjs';
|
|
8
9
|
import { HarnessClient } from '../../../../src/channels/feishu/harness-client.mjs';
|
|
@@ -105,6 +106,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
105
106
|
if (!botConfig) throw new Error('Unknown Feishu bot');
|
|
106
107
|
return stateFor(botConfig);
|
|
107
108
|
};
|
|
109
|
+
const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
|
|
108
110
|
const harness = new Harness({
|
|
109
111
|
baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
|
|
110
112
|
workspace: defaultWorkspace,
|
|
@@ -113,6 +115,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
113
115
|
// second DSH would create a competing server and lifecycle.
|
|
114
116
|
autostart: false,
|
|
115
117
|
dshBin: config.dshBin ?? 'dsh',
|
|
118
|
+
...(commandExecutor ? { commandExecutor } : {}),
|
|
116
119
|
});
|
|
117
120
|
|
|
118
121
|
const coreController = new Controller({
|
|
@@ -2,7 +2,7 @@ import { createProductionController } from './production.mjs';
|
|
|
2
2
|
import { installQqRpc } from './rpc.mjs';
|
|
3
3
|
|
|
4
4
|
export const name = 'dsh-im-qq-host';
|
|
5
|
-
export const inject = ['connection', 'credentials', 'webServer'];
|
|
5
|
+
export const inject = ['connection', 'credentials', 'webServer', 'typertGateway'];
|
|
6
6
|
|
|
7
7
|
export async function apply(ctx, config = {}) {
|
|
8
8
|
if (config?.controller) {
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
observeBotWorkspaceRemovals,
|
|
16
16
|
} from '../../../../src/channels/shared/bot-workspace-store.mjs';
|
|
17
17
|
import { createConnectionSupervisor } from './connection-supervisor.mjs';
|
|
18
|
+
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
|
|
18
19
|
|
|
19
20
|
function harnessOrigin(webServer, configured) {
|
|
20
21
|
if (configured !== undefined) return new URL(configured);
|
|
@@ -70,12 +71,14 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
70
71
|
}
|
|
71
72
|
return state;
|
|
72
73
|
};
|
|
74
|
+
const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
|
|
73
75
|
const harness = new Harness({
|
|
74
76
|
baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
|
|
75
77
|
workspace: defaultWorkspace,
|
|
76
78
|
agentPreset: config.agentPreset ?? 'standard',
|
|
77
79
|
autostart: false,
|
|
78
80
|
dshBin: config.dshBin ?? 'dsh',
|
|
81
|
+
...(commandExecutor ? { commandExecutor } : {}),
|
|
79
82
|
});
|
|
80
83
|
const coreController = new Controller({
|
|
81
84
|
qrAuth,
|
|
@@ -3,6 +3,7 @@ import { homedir } from 'node:os';
|
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
|
|
5
5
|
import { createTokenConnectionSupervisor } from './connection-supervisor.mjs';
|
|
6
|
+
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
|
|
6
7
|
import {
|
|
7
8
|
BotWorkspaceStore,
|
|
8
9
|
createBotWorkspaceScope,
|
|
@@ -64,12 +65,14 @@ export async function createTokenProductionController(ctx, config, internals, de
|
|
|
64
65
|
}
|
|
65
66
|
return state;
|
|
66
67
|
};
|
|
68
|
+
const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
|
|
67
69
|
const harness = new ResolvedHarness({
|
|
68
70
|
baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
|
|
69
71
|
workspace: defaultWorkspace,
|
|
70
72
|
agentPreset: config.agentPreset ?? 'standard',
|
|
71
73
|
autostart: false,
|
|
72
74
|
dshBin: config.dshBin ?? 'dsh',
|
|
75
|
+
...(commandExecutor ? { commandExecutor } : {}),
|
|
73
76
|
});
|
|
74
77
|
const coreController = new ResolvedController({
|
|
75
78
|
credentials: ctx.credentials,
|
|
@@ -2,7 +2,7 @@ import { createProductionController } from './production.mjs';
|
|
|
2
2
|
import { installSlackRpc } from './rpc.mjs';
|
|
3
3
|
|
|
4
4
|
export const name = 'dsh-im-slack-host';
|
|
5
|
-
export const inject = ['connection', 'credentials', 'webServer'];
|
|
5
|
+
export const inject = ['connection', 'credentials', 'webServer', 'typertGateway'];
|
|
6
6
|
|
|
7
7
|
export async function apply(ctx, config = {}) {
|
|
8
8
|
if (config?.controller) return installSlackRpc(ctx, config.controller, config.rpcAuthority);
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from '../../../../src/channels/shared/bot-workspace-store.mjs';
|
|
15
15
|
import { createTokenConnectionSupervisor } from '../shared/connection-supervisor.mjs';
|
|
16
16
|
import { harnessOrigin, pluginPaths } from '../shared/production.mjs';
|
|
17
|
+
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
|
|
17
18
|
|
|
18
19
|
export async function createProductionController(ctx, config = {}, internals = {}) {
|
|
19
20
|
if (!ctx?.credentials) throw new TypeError('dsh-im slack requires ctx.credentials');
|
|
@@ -49,12 +50,14 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
49
50
|
}
|
|
50
51
|
return state;
|
|
51
52
|
};
|
|
53
|
+
const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
|
|
52
54
|
const harness = new ResolvedHarness({
|
|
53
55
|
baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
|
|
54
56
|
workspace: defaultWorkspace,
|
|
55
57
|
agentPreset: config.agentPreset ?? 'standard',
|
|
56
58
|
autostart: false,
|
|
57
59
|
dshBin: config.dshBin ?? 'dsh',
|
|
60
|
+
...(commandExecutor ? { commandExecutor } : {}),
|
|
58
61
|
});
|
|
59
62
|
const coreController = new ResolvedController({
|
|
60
63
|
credentials: ctx.credentials,
|
|
@@ -2,7 +2,7 @@ import { createProductionController } from './production.mjs';
|
|
|
2
2
|
import { installTelegramRpc } from './rpc.mjs';
|
|
3
3
|
|
|
4
4
|
export const name = 'dsh-im-telegram-host';
|
|
5
|
-
export const inject = ['connection', 'credentials', 'webServer'];
|
|
5
|
+
export const inject = ['connection', 'credentials', 'webServer', 'typertGateway'];
|
|
6
6
|
|
|
7
7
|
export async function apply(ctx, config = {}) {
|
|
8
8
|
if (config?.controller) {
|
|
@@ -2,7 +2,7 @@ import { createProductionController } from './production.mjs';
|
|
|
2
2
|
import { installWecomRpc } from './rpc.mjs';
|
|
3
3
|
|
|
4
4
|
export const name = 'dsh-im-wecom-host';
|
|
5
|
-
export const inject = ['connection', 'credentials', 'webServer'];
|
|
5
|
+
export const inject = ['connection', 'credentials', 'webServer', 'typertGateway'];
|
|
6
6
|
|
|
7
7
|
export async function apply(ctx, config = {}) {
|
|
8
8
|
if (config?.controller) {
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
observeBotWorkspaceRemovals,
|
|
16
16
|
} from '../../../../src/channels/shared/bot-workspace-store.mjs';
|
|
17
17
|
import { createConnectionSupervisor } from './connection-supervisor.mjs';
|
|
18
|
+
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
|
|
18
19
|
|
|
19
20
|
function harnessOrigin(webServer, configured) {
|
|
20
21
|
if (configured !== undefined) return new URL(configured);
|
|
@@ -73,12 +74,14 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
73
74
|
}
|
|
74
75
|
return state;
|
|
75
76
|
};
|
|
77
|
+
const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
|
|
76
78
|
const harness = new Harness({
|
|
77
79
|
baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
|
|
78
80
|
workspace: defaultWorkspace,
|
|
79
81
|
agentPreset: config.agentPreset ?? 'standard',
|
|
80
82
|
autostart: false,
|
|
81
83
|
dshBin: config.dshBin ?? 'dsh',
|
|
84
|
+
...(commandExecutor ? { commandExecutor } : {}),
|
|
82
85
|
});
|
|
83
86
|
const coreController = new Controller({
|
|
84
87
|
qrAuth,
|
|
@@ -2,7 +2,7 @@ import { createProductionController } from './production.mjs';
|
|
|
2
2
|
import { installWeixinRpc } from './rpc.mjs';
|
|
3
3
|
|
|
4
4
|
export const name = 'dsh-weixin-host';
|
|
5
|
-
export const inject = ['connection', 'credentials', 'webServer'];
|
|
5
|
+
export const inject = ['connection', 'credentials', 'webServer', 'typertGateway'];
|
|
6
6
|
|
|
7
7
|
export async function apply(ctx, config = {}) {
|
|
8
8
|
if (config?.controller) {
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
observeBotWorkspaceRemovals,
|
|
16
16
|
} from '../../../../src/channels/shared/bot-workspace-store.mjs';
|
|
17
17
|
import { createConnectionSupervisor } from './connection-supervisor.mjs';
|
|
18
|
+
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
|
|
18
19
|
|
|
19
20
|
function harnessOrigin(webServer, configured) {
|
|
20
21
|
if (configured !== undefined) return new URL(configured);
|
|
@@ -73,12 +74,14 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
73
74
|
}
|
|
74
75
|
return state;
|
|
75
76
|
};
|
|
77
|
+
const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
|
|
76
78
|
const harness = new Harness({
|
|
77
79
|
baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
|
|
78
80
|
workspace: defaultWorkspace,
|
|
79
81
|
agentPreset: config.agentPreset ?? 'standard',
|
|
80
82
|
autostart: false,
|
|
81
83
|
dshBin: config.dshBin ?? 'dsh',
|
|
84
|
+
...(commandExecutor ? { commandExecutor } : {}),
|
|
82
85
|
});
|
|
83
86
|
const coreController = new Controller({
|
|
84
87
|
api,
|
|
@@ -2,7 +2,7 @@ import { createProductionController } from './production.mjs';
|
|
|
2
2
|
import { installWhatsappRpc } from './rpc.mjs';
|
|
3
3
|
|
|
4
4
|
export const name = 'dsh-im-whatsapp-host';
|
|
5
|
-
export const inject = ['connection', 'webServer'];
|
|
5
|
+
export const inject = ['connection', 'webServer', 'typertGateway'];
|
|
6
6
|
|
|
7
7
|
export async function apply(ctx, config = {}) {
|
|
8
8
|
if (config?.controller) {
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
observeBotWorkspaceRemovals,
|
|
16
16
|
} from '../../../../src/channels/shared/bot-workspace-store.mjs';
|
|
17
17
|
import { createTokenConnectionSupervisor } from '../shared/connection-supervisor.mjs';
|
|
18
|
+
import { createHarnessCommandExecutor } from '../../harness-command-executor.mjs';
|
|
18
19
|
|
|
19
20
|
const AUTH_DIRECTORY_PATTERN = /^[a-f0-9-]{36}$/;
|
|
20
21
|
|
|
@@ -76,12 +77,14 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
76
77
|
}
|
|
77
78
|
return state;
|
|
78
79
|
};
|
|
80
|
+
const commandExecutor = createHarnessCommandExecutor(ctx, internals.commandExecutor);
|
|
79
81
|
const harness = new Harness({
|
|
80
82
|
baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
|
|
81
83
|
workspace: defaultWorkspace,
|
|
82
84
|
agentPreset: config.agentPreset ?? 'standard',
|
|
83
85
|
autostart: false,
|
|
84
86
|
dshBin: config.dshBin ?? 'dsh',
|
|
87
|
+
...(commandExecutor ? { commandExecutor } : {}),
|
|
85
88
|
});
|
|
86
89
|
const coreController = new Controller({
|
|
87
90
|
configStore: observedConfigStore,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapt the Host's Typert command gateway to the channel Harness client.
|
|
3
|
+
* Programmatic production fixtures may omit the gateway and exercise other assembly paths.
|
|
4
|
+
*/
|
|
5
|
+
export function createHarnessCommandExecutor(ctx, provided) {
|
|
6
|
+
if (provided !== undefined) {
|
|
7
|
+
if (typeof provided !== 'function') throw new TypeError('commandExecutor must be a function');
|
|
8
|
+
return provided;
|
|
9
|
+
}
|
|
10
|
+
const gateway = ctx?.typertGateway;
|
|
11
|
+
if (!gateway) return undefined;
|
|
12
|
+
if (typeof gateway.invoke !== 'function') {
|
|
13
|
+
throw new TypeError('dsh-im requires a callable ctx.typertGateway');
|
|
14
|
+
}
|
|
15
|
+
return (sessionId, line, options = {}) => gateway.invoke({
|
|
16
|
+
namespace: 'commands',
|
|
17
|
+
method: 'execute',
|
|
18
|
+
args: { agentId: sessionId, line },
|
|
19
|
+
signal: options.signal,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -9,7 +9,7 @@ import { apply as applyWeixin } from './channels/weixin/index.mjs';
|
|
|
9
9
|
import { apply as applyWhatsapp } from './channels/whatsapp/index.mjs';
|
|
10
10
|
|
|
11
11
|
export const name = 'dsh-im-host';
|
|
12
|
-
export const inject = ['connection', 'credentials', 'webServer'];
|
|
12
|
+
export const inject = ['connection', 'credentials', 'webServer', 'typertGateway'];
|
|
13
13
|
|
|
14
14
|
function channelConfig(config, name) {
|
|
15
15
|
const channel = config[name] ?? {};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
|
|
3
|
+
import { fetchImageBuffer, ImagePromptError } from '../shared/image-prompt.mjs';
|
|
4
|
+
|
|
3
5
|
export const DINGTALK_REGISTRATION_BASE_URL = 'https://oapi.dingtalk.com/';
|
|
4
6
|
export const DINGTALK_API_BASE_URL = 'https://api.dingtalk.com/';
|
|
5
7
|
export const DINGTALK_REGISTRATION_SOURCE = 'DING_DWS_CLAW';
|
|
@@ -14,6 +16,7 @@ export class DingtalkApiError extends Error {
|
|
|
14
16
|
this.name = 'DingtalkApiError';
|
|
15
17
|
this.code = code;
|
|
16
18
|
this.status = options.status;
|
|
19
|
+
this.providerCode = options.providerCode;
|
|
17
20
|
}
|
|
18
21
|
}
|
|
19
22
|
|
|
@@ -21,6 +24,25 @@ function nonEmptyString(value) {
|
|
|
21
24
|
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
22
25
|
}
|
|
23
26
|
|
|
27
|
+
function safeProviderCode(value) {
|
|
28
|
+
const code = nonEmptyString(value);
|
|
29
|
+
return code && /^[A-Za-z0-9_.:-]{1,160}$/.test(code) ? code : undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function secureDingtalkDownloadUrl(value) {
|
|
33
|
+
let url;
|
|
34
|
+
try {
|
|
35
|
+
url = new URL(value);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
throw new DingtalkApiError('invalid-image-download', '钉钉服务返回了无效的图片下载地址。', { cause: error });
|
|
38
|
+
}
|
|
39
|
+
if (url.protocol === 'http:' && (!url.port || url.port === '80')) {
|
|
40
|
+
url.protocol = 'https:';
|
|
41
|
+
url.port = '';
|
|
42
|
+
}
|
|
43
|
+
return url;
|
|
44
|
+
}
|
|
45
|
+
|
|
24
46
|
function isDingtalkHost(hostname) {
|
|
25
47
|
const normalized = hostname.toLowerCase().replace(/\.$/, '');
|
|
26
48
|
return normalized === 'dingtalk.com' || normalized.endsWith('.dingtalk.com');
|
|
@@ -123,10 +145,17 @@ async function requestJson(fetchImpl, url, {
|
|
|
123
145
|
signal: controller.signal,
|
|
124
146
|
});
|
|
125
147
|
if (!response.ok) {
|
|
148
|
+
let providerCode;
|
|
149
|
+
try {
|
|
150
|
+
const errorBody = await response.json();
|
|
151
|
+
providerCode = safeProviderCode(errorBody?.code ?? errorBody?.errcode);
|
|
152
|
+
} catch {
|
|
153
|
+
// DingTalk occasionally returns an empty or non-JSON error body.
|
|
154
|
+
}
|
|
126
155
|
throw new DingtalkApiError(
|
|
127
156
|
'http-error',
|
|
128
157
|
`钉钉服务请求失败(HTTP ${response.status})。`,
|
|
129
|
-
{ status: response.status },
|
|
158
|
+
{ status: response.status, providerCode },
|
|
130
159
|
);
|
|
131
160
|
}
|
|
132
161
|
try {
|
|
@@ -403,6 +432,58 @@ export function createDingtalkApi({
|
|
|
403
432
|
|
|
404
433
|
accessToken,
|
|
405
434
|
|
|
435
|
+
async downloadImage({
|
|
436
|
+
clientId,
|
|
437
|
+
clientSecret,
|
|
438
|
+
robotCode,
|
|
439
|
+
downloadCode,
|
|
440
|
+
signal,
|
|
441
|
+
maxBytes,
|
|
442
|
+
}) {
|
|
443
|
+
const botCode = nonEmptyString(robotCode);
|
|
444
|
+
const fileCode = nonEmptyString(downloadCode);
|
|
445
|
+
if (!botCode || !fileCode) throw new TypeError('robotCode and downloadCode are required');
|
|
446
|
+
const token = await accessToken({ clientId, clientSecret, signal });
|
|
447
|
+
let response;
|
|
448
|
+
try {
|
|
449
|
+
response = await requestJson(
|
|
450
|
+
fetchImpl,
|
|
451
|
+
endpoint(apiBase, 'v1.0/robot/messageFiles/download'),
|
|
452
|
+
{
|
|
453
|
+
body: { downloadCode: fileCode, robotCode: botCode },
|
|
454
|
+
headers: { 'x-acs-dingtalk-access-token': token },
|
|
455
|
+
signal,
|
|
456
|
+
action: '图片下载地址',
|
|
457
|
+
},
|
|
458
|
+
);
|
|
459
|
+
} catch (error) {
|
|
460
|
+
if (signal?.aborted) throw error;
|
|
461
|
+
throw new DingtalkApiError(
|
|
462
|
+
'image-download-address-failed',
|
|
463
|
+
'钉钉图片下载地址获取失败。',
|
|
464
|
+
{ cause: error, status: error?.status, providerCode: error?.providerCode },
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
const downloadUrl = nonEmptyString(response?.downloadUrl ?? response?.download_url);
|
|
468
|
+
if (!downloadUrl) {
|
|
469
|
+
throw new DingtalkApiError('invalid-image-download', '钉钉服务没有返回图片下载地址。');
|
|
470
|
+
}
|
|
471
|
+
try {
|
|
472
|
+
return await fetchImageBuffer(secureDingtalkDownloadUrl(downloadUrl), {
|
|
473
|
+
fetchImpl,
|
|
474
|
+
signal,
|
|
475
|
+
maxBytes,
|
|
476
|
+
});
|
|
477
|
+
} catch (error) {
|
|
478
|
+
if (signal?.aborted || error instanceof ImagePromptError) throw error;
|
|
479
|
+
throw new DingtalkApiError(
|
|
480
|
+
'image-content-download-failed',
|
|
481
|
+
'钉钉图片内容下载失败。',
|
|
482
|
+
{ cause: error },
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
},
|
|
486
|
+
|
|
406
487
|
async createAiCard({ clientId, clientSecret, target, initialText, signal }) {
|
|
407
488
|
const appKey = nonEmptyString(clientId);
|
|
408
489
|
const appSecret = nonEmptyString(clientSecret);
|