@xmanrui/dsh-im 0.7.2 → 0.9.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 +154 -0
- package/README.md +28 -132
- package/lib/client.js +15 -14
- package/lib/index.js +125 -124
- package/package.json +2 -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-bridge.mjs +14 -1
- package/src/channels/discord/discord-api.mjs +1 -1
- package/src/channels/feishu/bridge.mjs +14 -1
- package/src/channels/qq/qq-bridge.mjs +15 -1
- 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 +59 -1
- package/src/channels/shared/text-harness-bridge.mjs +14 -1
- package/src/channels/shared/workspace-command.mjs +70 -6
- package/src/channels/wecom/wecom-bridge.mjs +15 -1
- package/src/channels/weixin/weixin-api.mjs +1 -1
- package/src/channels/weixin/weixin-bridge.mjs +15 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmanrui/dsh-im",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "通过扫码、App Manifest或机器人凭据把IM机器人接入DeepSeek Harness(支持飞书、微信、钉钉、企业微信、QQ、Slack、Telegram、Discord和WhatsApp)。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"src",
|
|
33
33
|
"LICENSE",
|
|
34
34
|
"README.md",
|
|
35
|
+
"README.en.md",
|
|
35
36
|
"THIRD_PARTY_NOTICES.md"
|
|
36
37
|
],
|
|
37
38
|
"dsh": {
|
|
@@ -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] ?? {};
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
validHarnessQuestion,
|
|
10
10
|
} from '../shared/harness-question.mjs';
|
|
11
11
|
import { HarnessApprovalQueue } from '../shared/harness-approval.mjs';
|
|
12
|
+
import { runCompactCommand } from '../shared/compact-command.mjs';
|
|
12
13
|
import { runWorkspaceCommand } from '../shared/workspace-command.mjs';
|
|
13
14
|
import { askInWorkspaceSession } from '../shared/workspace-session.mjs';
|
|
14
15
|
|
|
@@ -21,10 +22,11 @@ const HELP_TEXT = [
|
|
|
21
22
|
'',
|
|
22
23
|
'直接发送文字即可继续当前会话。',
|
|
23
24
|
'/new 开启一个全新会话',
|
|
25
|
+
'/compact 压缩当前会话的较早上下文',
|
|
24
26
|
'/workspace 工作区绝对路径 切换工作区',
|
|
25
27
|
'/workspacelist 列出工作区绝对路径',
|
|
26
28
|
'/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题',
|
|
27
|
-
'/session Session ID 将当前聊天绑定到指定会话',
|
|
29
|
+
'/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话',
|
|
28
30
|
'/status 检查连接状态',
|
|
29
31
|
'/help 显示本帮助',
|
|
30
32
|
].join('\n');
|
|
@@ -346,6 +348,17 @@ export class DingtalkHarnessBridge {
|
|
|
346
348
|
}
|
|
347
349
|
return;
|
|
348
350
|
}
|
|
351
|
+
const compactCommand = await runCompactCommand(
|
|
352
|
+
text,
|
|
353
|
+
this.#harness,
|
|
354
|
+
this.#state,
|
|
355
|
+
key,
|
|
356
|
+
{ signal: this.#signal },
|
|
357
|
+
);
|
|
358
|
+
if (compactCommand) {
|
|
359
|
+
await this.#send(sessionWebhook, compactCommand.message);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
349
362
|
|
|
350
363
|
if (typeof this.#api.createAiCard === 'function'
|
|
351
364
|
&& typeof this.#api.updateAiCard === 'function'
|
|
@@ -108,7 +108,7 @@ export class DiscordApi {
|
|
|
108
108
|
headers: {
|
|
109
109
|
authorization: `Bot ${this.#token}`,
|
|
110
110
|
'content-type': 'application/json',
|
|
111
|
-
'user-agent': 'DeepSeek-Harness-dsh-im (https://github.com/xmanrui/dsh-im, 0.
|
|
111
|
+
'user-agent': 'DeepSeek-Harness-dsh-im (https://github.com/xmanrui/dsh-im, 0.9.0)',
|
|
112
112
|
},
|
|
113
113
|
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
114
114
|
signal: requestSignal(signal, timeoutMs),
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
validHarnessQuestion,
|
|
12
12
|
} from '../shared/harness-question.mjs';
|
|
13
13
|
import { HarnessApprovalQueue } from '../shared/harness-approval.mjs';
|
|
14
|
+
import { runCompactCommand } from '../shared/compact-command.mjs';
|
|
14
15
|
import { runWorkspaceCommand } from '../shared/workspace-command.mjs';
|
|
15
16
|
import { askInWorkspaceSession } from '../shared/workspace-session.mjs';
|
|
16
17
|
|
|
@@ -22,10 +23,11 @@ const HELP_TEXT = [
|
|
|
22
23
|
'',
|
|
23
24
|
'直接发送问题即可继续当前会话。',
|
|
24
25
|
'/new 开启一个全新会话',
|
|
26
|
+
'/compact 压缩当前会话的较早上下文',
|
|
25
27
|
'/workspace 工作区绝对路径 切换工作区',
|
|
26
28
|
'/workspacelist 列出工作区绝对路径',
|
|
27
29
|
'/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题',
|
|
28
|
-
'/session Session ID 将当前聊天绑定到指定会话',
|
|
30
|
+
'/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话',
|
|
29
31
|
'/status 检查连接状态',
|
|
30
32
|
'/help 显示本帮助',
|
|
31
33
|
].join('\n');
|
|
@@ -322,6 +324,17 @@ export class FeishuHarnessBridge {
|
|
|
322
324
|
}
|
|
323
325
|
return;
|
|
324
326
|
}
|
|
327
|
+
const compactCommand = await runCompactCommand(
|
|
328
|
+
text,
|
|
329
|
+
this.#harness,
|
|
330
|
+
this.#state,
|
|
331
|
+
key,
|
|
332
|
+
{ signal: this.#signal },
|
|
333
|
+
);
|
|
334
|
+
if (compactCommand) {
|
|
335
|
+
await this.#send(event.message.chat_id, compactCommand.message);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
325
338
|
|
|
326
339
|
this.#logger.info?.(`[dsh-feishu] processing ${event.message.chat_type} message ${messageId}`);
|
|
327
340
|
try {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { runWorkspaceCommand } from '../shared/workspace-command.mjs';
|
|
2
|
+
import { runCompactCommand } from '../shared/compact-command.mjs';
|
|
2
3
|
import {
|
|
3
4
|
harnessAnswerForQuestion,
|
|
4
5
|
harnessQuestionText,
|
|
@@ -14,10 +15,11 @@ const HELP_TEXT = [
|
|
|
14
15
|
'',
|
|
15
16
|
'直接发送文字即可继续当前会话。',
|
|
16
17
|
'/new 开启一个全新会话',
|
|
18
|
+
'/compact 压缩当前会话的较早上下文',
|
|
17
19
|
'/workspace 工作区绝对路径 切换工作区',
|
|
18
20
|
'/workspacelist 列出工作区绝对路径',
|
|
19
21
|
'/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题',
|
|
20
|
-
'/session Session ID 将当前聊天绑定到指定会话',
|
|
22
|
+
'/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话',
|
|
21
23
|
'/status 检查连接状态',
|
|
22
24
|
'/help 显示本帮助',
|
|
23
25
|
].join('\n');
|
|
@@ -241,6 +243,18 @@ export class QqHarnessBridge {
|
|
|
241
243
|
await this.#state.markSeen(messageId);
|
|
242
244
|
return;
|
|
243
245
|
}
|
|
246
|
+
const compactCommand = await runCompactCommand(
|
|
247
|
+
text,
|
|
248
|
+
this.#harness,
|
|
249
|
+
this.#state,
|
|
250
|
+
key,
|
|
251
|
+
{ signal: this.#signal },
|
|
252
|
+
);
|
|
253
|
+
if (compactCommand) {
|
|
254
|
+
await this.#bot.sendText(target, compactCommand.message);
|
|
255
|
+
await this.#state.markSeen(messageId);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
244
258
|
|
|
245
259
|
let stream = null;
|
|
246
260
|
let streamFinished = false;
|
|
@@ -668,6 +668,19 @@ export function createBotWorkspaceScope(harness, { botId, workspaces, state }) {
|
|
|
668
668
|
return target.ask(sessionId, ...args);
|
|
669
669
|
};
|
|
670
670
|
}
|
|
671
|
+
if (property === 'executeCommand' && typeof target.executeCommand === 'function') {
|
|
672
|
+
return (sessionId, ...args) => {
|
|
673
|
+
const generation = sessionGenerations.get(sessionId);
|
|
674
|
+
sessionGenerations.delete(sessionId);
|
|
675
|
+
if (!isCurrentScope()
|
|
676
|
+
|| (generation !== undefined && generation !== workspaces.generationFor(botId))) {
|
|
677
|
+
const error = new Error('The bot workspace changed before this command started.');
|
|
678
|
+
error.code = WORKSPACE_SESSION_STALE;
|
|
679
|
+
throw error;
|
|
680
|
+
}
|
|
681
|
+
return target.executeCommand(sessionId, ...args);
|
|
682
|
+
};
|
|
683
|
+
}
|
|
671
684
|
const value = Reflect.get(target, property, target);
|
|
672
685
|
return typeof value === 'function' ? value.bind(target) : value;
|
|
673
686
|
},
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { WORKSPACE_SESSION_STALE } from './workspace-session.mjs';
|
|
2
|
+
|
|
3
|
+
const COMPACT_COMMAND = /^\/compact(?=$|\s)([\s\S]*)$/i;
|
|
4
|
+
const COMPACT_USAGE = '用法:/compact(不带参数)';
|
|
5
|
+
|
|
6
|
+
const COMPACT_RESULT_TEXT = new Map([
|
|
7
|
+
['No compactable history yet.', '暂无可压缩的历史记录。'],
|
|
8
|
+
[
|
|
9
|
+
'Compaction is unavailable because this process has an active compaction, or the agent is not idle.',
|
|
10
|
+
'当前会话正在生成回复或执行压缩,请稍后重试。',
|
|
11
|
+
],
|
|
12
|
+
['Compaction cancelled.', '上下文压缩已取消。'],
|
|
13
|
+
[
|
|
14
|
+
'The history selected for compaction changed before it could be replaced. The conversation is unchanged; the attempt is recorded in the session log.',
|
|
15
|
+
'压缩期间会话历史发生变化,本次未修改会话,请重试。',
|
|
16
|
+
],
|
|
17
|
+
[
|
|
18
|
+
'Compaction could not produce a useful summary. The conversation is unchanged; the attempt is recorded in the session log.',
|
|
19
|
+
'未能生成有效的压缩摘要,本次未修改会话。',
|
|
20
|
+
],
|
|
21
|
+
[
|
|
22
|
+
'Compaction did not finish cleanly; some session history may have changed. Inspect the current session state before retrying.',
|
|
23
|
+
'上下文压缩未正常完成,部分会话历史可能已变化,请检查会话后再重试。',
|
|
24
|
+
],
|
|
25
|
+
[
|
|
26
|
+
'Compaction finished, but the session could not be saved.',
|
|
27
|
+
'上下文已压缩,但会话保存失败。',
|
|
28
|
+
],
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
function commandResult(message) {
|
|
32
|
+
return { handled: true, message, messages: [message] };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function compactResultText(result) {
|
|
36
|
+
if (!result || typeof result !== 'object'
|
|
37
|
+
|| !['success', 'error'].includes(result.kind)
|
|
38
|
+
|| (result.text !== undefined && typeof result.text !== 'string')) {
|
|
39
|
+
throw new TypeError('Harness returned an invalid /compact result');
|
|
40
|
+
}
|
|
41
|
+
const text = result.text?.trim() ?? '';
|
|
42
|
+
const compacted = /^Compacted (\d+) history items \(~(\d+) tokens\)\.$/u.exec(text);
|
|
43
|
+
if (compacted) {
|
|
44
|
+
return `已压缩 ${compacted[1]} 条历史记录(约 ${compacted[2]} 个 token)。`;
|
|
45
|
+
}
|
|
46
|
+
if (COMPACT_RESULT_TEXT.has(text)) return COMPACT_RESULT_TEXT.get(text);
|
|
47
|
+
if (text) return text;
|
|
48
|
+
return result.kind === 'success' ? '上下文压缩完成。' : '上下文压缩失败。';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function compactErrorMessage(error) {
|
|
52
|
+
const code = error?.code ?? error?.failure?.code;
|
|
53
|
+
if (code === 'session-not-found') {
|
|
54
|
+
return '当前聊天绑定的会话已不存在,请发送新消息开启会话。';
|
|
55
|
+
}
|
|
56
|
+
if (code === 'agent-busy') return '当前会话正在生成回复,请稍后重试。';
|
|
57
|
+
if (code === 'cancelled' || error?.name === 'AbortError') return '上下文压缩已取消。';
|
|
58
|
+
if (code === WORKSPACE_SESSION_STALE || code === 'workspace-bot-not-found') {
|
|
59
|
+
return '工作区或机器人状态已发生变化,请重试。';
|
|
60
|
+
}
|
|
61
|
+
if (code === 'commands-unavailable') {
|
|
62
|
+
return '当前 Harness 暂不支持从机器人执行上下文压缩。';
|
|
63
|
+
}
|
|
64
|
+
return '上下文压缩失败,请稍后重试。';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Execute the explicit Harness compaction command for an existing IM conversation Session.
|
|
69
|
+
* Unknown input returns null so the caller may continue ordinary message routing.
|
|
70
|
+
*/
|
|
71
|
+
export async function runCompactCommand(text, harness, state, conversationKey, options = {}) {
|
|
72
|
+
if (typeof text !== 'string') return null;
|
|
73
|
+
const match = COMPACT_COMMAND.exec(text.trim());
|
|
74
|
+
if (!match) return null;
|
|
75
|
+
if (match[1].trim()) return commandResult(COMPACT_USAGE);
|
|
76
|
+
if (typeof state?.sessionFor !== 'function') {
|
|
77
|
+
return commandResult('当前机器人没有可用的会话状态。');
|
|
78
|
+
}
|
|
79
|
+
const sessionId = state.sessionFor(conversationKey);
|
|
80
|
+
if (typeof sessionId !== 'string' || !sessionId) {
|
|
81
|
+
return commandResult('当前聊天还没有可压缩的会话,请先发送一条消息。');
|
|
82
|
+
}
|
|
83
|
+
if (typeof harness?.executeCommand !== 'function') {
|
|
84
|
+
return commandResult('当前机器人暂不支持上下文压缩。');
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const execution = await harness.executeCommand(sessionId, '/compact', options);
|
|
88
|
+
if (execution === undefined) {
|
|
89
|
+
return commandResult('当前 Harness 未注册 /compact 命令,请确认上下文压缩组件已启用。');
|
|
90
|
+
}
|
|
91
|
+
return commandResult(compactResultText(execution?.result));
|
|
92
|
+
} catch (error) {
|
|
93
|
+
return commandResult(compactErrorMessage(error));
|
|
94
|
+
}
|
|
95
|
+
}
|