@xmanrui/dsh-im 0.17.1 → 0.19.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 -2
- package/README.md +3 -2
- package/lib/client.js +1297 -600
- package/lib/index.js +190 -145
- package/package.json +1 -1
- package/plugin-src/client/agent-preset.js +110 -0
- package/plugin-src/client/channels/dingtalk/api.js +5 -0
- package/plugin-src/client/channels/dingtalk/index.js +48 -2
- package/plugin-src/client/channels/feishu/api.js +28 -5
- package/plugin-src/client/channels/feishu/index.js +330 -41
- package/plugin-src/client/channels/feishu/styles.js +46 -0
- package/plugin-src/client/channels/qq/api.js +5 -0
- package/plugin-src/client/channels/qq/index.js +37 -6
- package/plugin-src/client/channels/shared/token-api.js +5 -0
- package/plugin-src/client/channels/shared/token-channel.js +34 -6
- package/plugin-src/client/channels/wecom/api.js +5 -0
- package/plugin-src/client/channels/wecom/index.js +37 -6
- package/plugin-src/client/channels/weixin/api.js +5 -0
- package/plugin-src/client/channels/weixin/index.js +50 -4
- package/plugin-src/client/channels/whatsapp/api.js +5 -0
- package/plugin-src/client/channels/whatsapp/index.js +30 -4
- package/plugin-src/client/i18n.js +59 -0
- package/plugin-src/client/styles.js +8 -0
- package/plugin-src/host/channels/dingtalk/production.mjs +10 -4
- package/plugin-src/host/channels/dingtalk/rpc.mjs +12 -0
- package/plugin-src/host/channels/feishu/connection-supervisor.mjs +1 -1
- package/plugin-src/host/channels/feishu/production.mjs +8 -3
- package/plugin-src/host/channels/feishu/rpc.mjs +78 -7
- package/plugin-src/host/channels/qq/production.mjs +10 -4
- package/plugin-src/host/channels/qq/rpc.mjs +12 -0
- package/plugin-src/host/channels/shared/agent-preset-rpc.mjs +25 -0
- package/plugin-src/host/channels/shared/production.mjs +10 -4
- package/plugin-src/host/channels/shared/rpc.mjs +12 -0
- package/plugin-src/host/channels/shared/workspace-rpc.mjs +2 -0
- package/plugin-src/host/channels/slack/production.mjs +10 -4
- package/plugin-src/host/channels/slack/rpc.mjs +13 -0
- package/plugin-src/host/channels/wecom/production.mjs +10 -4
- package/plugin-src/host/channels/wecom/rpc.mjs +12 -0
- package/plugin-src/host/channels/weixin/production.mjs +10 -4
- package/plugin-src/host/channels/weixin/rpc.mjs +15 -0
- package/plugin-src/host/channels/whatsapp/production.mjs +10 -4
- package/plugin-src/host/channels/whatsapp/rpc.mjs +12 -0
- package/src/channels/discord/discord-api.mjs +1 -1
- package/src/channels/feishu/bridge.mjs +29 -4
- package/src/channels/feishu/feishu-runtime.mjs +14 -0
- package/src/channels/feishu/group-message-permission-manager.mjs +71 -0
- package/src/channels/feishu/group-response-mode.mjs +15 -0
- package/src/channels/feishu/multi-bot-controller.mjs +258 -9
- package/src/channels/feishu/plugin-config-store.mjs +3 -0
- package/src/channels/feishu/repair-manager.mjs +17 -3
- package/src/channels/shared/agent-preset.mjs +74 -0
- package/src/channels/shared/bot-workspace-store.mjs +141 -14
- package/src/channels/shared/harness-client.mjs +6 -4
- package/src/channels/telegram/telegram-api.mjs +31 -0
- package/src/channels/telegram/telegram-runtime.mjs +25 -1
- package/src/channels/weixin/weixin-api.mjs +1 -1
|
@@ -9,6 +9,10 @@ import {
|
|
|
9
9
|
} from 'node:fs/promises';
|
|
10
10
|
import { dirname, isAbsolute, resolve } from 'node:path';
|
|
11
11
|
|
|
12
|
+
import {
|
|
13
|
+
normalizeAgentPresetCatalog,
|
|
14
|
+
validateAgentPresetId,
|
|
15
|
+
} from './agent-preset.mjs';
|
|
12
16
|
import { CONNECTION_TEST_STATE_IDENTITY } from './connection-test.mjs';
|
|
13
17
|
import { WORKSPACE_SESSION_STALE } from './workspace-session.mjs';
|
|
14
18
|
|
|
@@ -49,7 +53,22 @@ function normalizeDocument(value) {
|
|
|
49
53
|
|| typeof workspace !== 'string' || !isAbsolute(workspace)) return null;
|
|
50
54
|
workspaces[botId] = resolve(workspace);
|
|
51
55
|
}
|
|
52
|
-
|
|
56
|
+
let agentPresets = {};
|
|
57
|
+
if (value.agentPresets !== undefined) {
|
|
58
|
+
if (!value.agentPresets || typeof value.agentPresets !== 'object'
|
|
59
|
+
|| Array.isArray(value.agentPresets)) return null;
|
|
60
|
+
for (const [botId, agentPreset] of Object.entries(value.agentPresets)) {
|
|
61
|
+
if (!/^[A-Za-z0-9_-]{1,128}$/.test(botId)) return null;
|
|
62
|
+
try {
|
|
63
|
+
const normalized = validateAgentPresetId(agentPreset);
|
|
64
|
+
if (!normalized) return null;
|
|
65
|
+
agentPresets[botId] = normalized;
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { version: 1, workspaces, agentPresets };
|
|
53
72
|
}
|
|
54
73
|
|
|
55
74
|
export async function validateWorkspacePath(value) {
|
|
@@ -79,6 +98,7 @@ export class BotWorkspaceStore {
|
|
|
79
98
|
#path;
|
|
80
99
|
#defaultWorkspace;
|
|
81
100
|
#workspaces = {};
|
|
101
|
+
#agentPresets = {};
|
|
82
102
|
#generations = new Map();
|
|
83
103
|
#nextGeneration = 1;
|
|
84
104
|
#incarnations = new Map();
|
|
@@ -100,9 +120,11 @@ export class BotWorkspaceStore {
|
|
|
100
120
|
const normalized = normalizeDocument(JSON.parse(await readFile(this.#path, 'utf8')));
|
|
101
121
|
if (!normalized) throw new Error('dsh-im workspace config is invalid');
|
|
102
122
|
this.#workspaces = normalized.workspaces;
|
|
123
|
+
this.#agentPresets = normalized.agentPresets;
|
|
103
124
|
} catch (error) {
|
|
104
125
|
if (error?.code !== 'ENOENT') throw error;
|
|
105
126
|
this.#workspaces = {};
|
|
127
|
+
this.#agentPresets = {};
|
|
106
128
|
}
|
|
107
129
|
this.#generations.clear();
|
|
108
130
|
this.#nextGeneration = 1;
|
|
@@ -130,6 +152,10 @@ export class BotWorkspaceStore {
|
|
|
130
152
|
return this.#workspaces[botIdOf(botId)] ?? this.#defaultWorkspace;
|
|
131
153
|
}
|
|
132
154
|
|
|
155
|
+
agentPresetFor(botId) {
|
|
156
|
+
return this.#agentPresets[botIdOf(botId)] ?? null;
|
|
157
|
+
}
|
|
158
|
+
|
|
133
159
|
generationFor(botId) {
|
|
134
160
|
return this.#generations.get(botIdOf(botId)) ?? null;
|
|
135
161
|
}
|
|
@@ -148,18 +174,24 @@ export class BotWorkspaceStore {
|
|
|
148
174
|
}
|
|
149
175
|
}
|
|
150
176
|
|
|
151
|
-
async ensure(botId, { workspace = this.#defaultWorkspace } = {}) {
|
|
177
|
+
async ensure(botId, { workspace = this.#defaultWorkspace, defaultAgentPreset } = {}) {
|
|
152
178
|
const id = botIdOf(botId);
|
|
153
179
|
const initialWorkspace = resolve(workspace);
|
|
154
180
|
return this.#enqueue(id, async () => {
|
|
155
181
|
if (!this.#workspaces[id]) {
|
|
182
|
+
const agentPreset = validateAgentPresetId(defaultAgentPreset);
|
|
183
|
+
const hadAgentPreset = Object.hasOwn(this.#agentPresets, id);
|
|
184
|
+
const previousAgentPreset = this.#agentPresets[id];
|
|
156
185
|
this.#workspaces[id] = initialWorkspace;
|
|
186
|
+
if (agentPreset) this.#agentPresets[id] = agentPreset;
|
|
157
187
|
this.#generations.set(id, this.#freshGeneration());
|
|
158
188
|
this.#incarnations.set(id, this.#freshIncarnation());
|
|
159
189
|
try {
|
|
160
190
|
await this.#persist();
|
|
161
191
|
} catch (error) {
|
|
162
192
|
delete this.#workspaces[id];
|
|
193
|
+
if (hadAgentPreset) this.#agentPresets[id] = previousAgentPreset;
|
|
194
|
+
else delete this.#agentPresets[id];
|
|
163
195
|
this.#generations.delete(id);
|
|
164
196
|
this.#incarnations.delete(id);
|
|
165
197
|
throw error;
|
|
@@ -207,6 +239,37 @@ export class BotWorkspaceStore {
|
|
|
207
239
|
});
|
|
208
240
|
}
|
|
209
241
|
|
|
242
|
+
async setAgentPreset(botId, value, { incarnation } = {}) {
|
|
243
|
+
const id = botIdOf(botId);
|
|
244
|
+
if (!this.has(id)
|
|
245
|
+
|| (incarnation !== undefined && incarnation !== this.incarnationFor(id))) {
|
|
246
|
+
const error = new Error('找不到要修改的机器人。');
|
|
247
|
+
error.code = 'workspace-bot-not-found';
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
const agentPreset = validateAgentPresetId(value);
|
|
251
|
+
return this.#enqueue(id, async () => {
|
|
252
|
+
if (!this.has(id)
|
|
253
|
+
|| (incarnation !== undefined && incarnation !== this.incarnationFor(id))) {
|
|
254
|
+
const error = new Error('找不到要修改的机器人。');
|
|
255
|
+
error.code = 'workspace-bot-not-found';
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
const previous = this.#agentPresets[id] ?? null;
|
|
259
|
+
if (previous === agentPreset) return agentPreset;
|
|
260
|
+
if (agentPreset) this.#agentPresets[id] = agentPreset;
|
|
261
|
+
else delete this.#agentPresets[id];
|
|
262
|
+
try {
|
|
263
|
+
await this.#persist();
|
|
264
|
+
} catch (error) {
|
|
265
|
+
if (previous) this.#agentPresets[id] = previous;
|
|
266
|
+
else delete this.#agentPresets[id];
|
|
267
|
+
throw error;
|
|
268
|
+
}
|
|
269
|
+
return agentPreset;
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
210
273
|
async bindWorkspaceSession(botId, value, {
|
|
211
274
|
conversationKey,
|
|
212
275
|
sessionId,
|
|
@@ -353,7 +416,11 @@ export class BotWorkspaceStore {
|
|
|
353
416
|
|
|
354
417
|
async reconcile(activeBotIds) {
|
|
355
418
|
const active = new Set([...activeBotIds].map(botIdOf));
|
|
356
|
-
const candidates = new Set([
|
|
419
|
+
const candidates = new Set([
|
|
420
|
+
...Object.keys(this.#workspaces),
|
|
421
|
+
...Object.keys(this.#agentPresets),
|
|
422
|
+
...this.#dirtyRemovals,
|
|
423
|
+
]);
|
|
357
424
|
for (const botId of candidates) {
|
|
358
425
|
if (!active.has(botId)) await this.remove(botId);
|
|
359
426
|
}
|
|
@@ -364,7 +431,11 @@ export class BotWorkspaceStore {
|
|
|
364
431
|
return {
|
|
365
432
|
...status,
|
|
366
433
|
bots: status.bots.map((bot) => bot?.botId
|
|
367
|
-
? {
|
|
434
|
+
? {
|
|
435
|
+
...bot,
|
|
436
|
+
workspace: this.workspaceFor(bot.botId),
|
|
437
|
+
agentPreset: this.agentPresetFor(bot.botId),
|
|
438
|
+
}
|
|
368
439
|
: bot),
|
|
369
440
|
};
|
|
370
441
|
}
|
|
@@ -392,8 +463,10 @@ export class BotWorkspaceStore {
|
|
|
392
463
|
|
|
393
464
|
async #retireCurrentIncarnation(id) {
|
|
394
465
|
const hadWorkspace = Object.hasOwn(this.#workspaces, id);
|
|
395
|
-
const
|
|
466
|
+
const hadPreset = Object.hasOwn(this.#agentPresets, id);
|
|
467
|
+
const needsCleanup = hadWorkspace || hadPreset || this.#dirtyRemovals.has(id);
|
|
396
468
|
delete this.#workspaces[id];
|
|
469
|
+
delete this.#agentPresets[id];
|
|
397
470
|
this.#generations.delete(id);
|
|
398
471
|
this.#incarnations.delete(id);
|
|
399
472
|
if (!needsCleanup) return {
|
|
@@ -425,6 +498,9 @@ export class BotWorkspaceStore {
|
|
|
425
498
|
|
|
426
499
|
async #persist() {
|
|
427
500
|
const document = { version: 1, workspaces: this.#workspaces };
|
|
501
|
+
if (Object.keys(this.#agentPresets).length > 0) {
|
|
502
|
+
document.agentPresets = this.#agentPresets;
|
|
503
|
+
}
|
|
428
504
|
await mkdir(dirname(this.#path), { recursive: true, mode: 0o700 });
|
|
429
505
|
const temporary = `${this.#path}.tmp`;
|
|
430
506
|
await writeFile(temporary, `${JSON.stringify(document, null, 2)}\n`, {
|
|
@@ -436,7 +512,8 @@ export class BotWorkspaceStore {
|
|
|
436
512
|
}
|
|
437
513
|
|
|
438
514
|
async #persistCurrentDocument() {
|
|
439
|
-
if (Object.keys(this.#workspaces).length > 0
|
|
515
|
+
if (Object.keys(this.#workspaces).length > 0
|
|
516
|
+
|| Object.keys(this.#agentPresets).length > 0) {
|
|
440
517
|
await this.#persist();
|
|
441
518
|
return;
|
|
442
519
|
}
|
|
@@ -450,10 +527,29 @@ export class BotWorkspaceStore {
|
|
|
450
527
|
}
|
|
451
528
|
}
|
|
452
529
|
|
|
453
|
-
function
|
|
530
|
+
function resolveAgentPresetCatalog(catalog) {
|
|
531
|
+
if (!catalog) return null;
|
|
532
|
+
const value = typeof catalog === 'function' ? catalog() : catalog;
|
|
533
|
+
return value && typeof value.then === 'function'
|
|
534
|
+
? value.then(normalizeAgentPresetCatalog)
|
|
535
|
+
: normalizeAgentPresetCatalog(value);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function decorateResult(workspaces, result, catalog) {
|
|
539
|
+
const decorate = (value) => {
|
|
540
|
+
const decorated = workspaces.decorateStatus(value);
|
|
541
|
+
if (!catalog || !decorated || typeof decorated !== 'object') return decorated;
|
|
542
|
+
const attachCatalog = (agentPresetCatalog) => (
|
|
543
|
+
agentPresetCatalog ? { ...decorated, agentPresetCatalog } : decorated
|
|
544
|
+
);
|
|
545
|
+
const agentPresetCatalog = resolveAgentPresetCatalog(catalog);
|
|
546
|
+
return agentPresetCatalog && typeof agentPresetCatalog.then === 'function'
|
|
547
|
+
? agentPresetCatalog.then(attachCatalog)
|
|
548
|
+
: attachCatalog(agentPresetCatalog);
|
|
549
|
+
};
|
|
454
550
|
return result && typeof result.then === 'function'
|
|
455
|
-
? result.then(
|
|
456
|
-
:
|
|
551
|
+
? result.then(decorate)
|
|
552
|
+
: decorate(result);
|
|
457
553
|
}
|
|
458
554
|
|
|
459
555
|
function targetStatus(controller) {
|
|
@@ -608,9 +704,11 @@ export function createBotWorkspaceScope(harness, { botId, workspaces, state }) {
|
|
|
608
704
|
throw error;
|
|
609
705
|
}
|
|
610
706
|
const generation = workspaces.generationFor(botId);
|
|
707
|
+
const agentPreset = workspaces.agentPresetFor(botId);
|
|
611
708
|
const sessionId = await target.createSession({
|
|
612
709
|
...options,
|
|
613
710
|
workspace: workspaces.workspaceFor(botId),
|
|
711
|
+
...(agentPreset == null ? {} : { agentPreset }),
|
|
614
712
|
});
|
|
615
713
|
sessionGenerations.set(sessionId, generation);
|
|
616
714
|
return sessionId;
|
|
@@ -765,7 +863,7 @@ export function createBotScopedHarness(harness, options) {
|
|
|
765
863
|
return createBotWorkspaceScope(harness, options).harness;
|
|
766
864
|
}
|
|
767
865
|
|
|
768
|
-
export function createWorkspaceAwareController(controller, { workspaces, stateFor }) {
|
|
866
|
+
export function createWorkspaceAwareController(controller, { workspaces, stateFor, agentPresetCatalog } = {}) {
|
|
769
867
|
if (!controller || !workspaces || typeof stateFor !== 'function') {
|
|
770
868
|
throw new TypeError('controller, workspaces, and stateFor are required');
|
|
771
869
|
}
|
|
@@ -778,6 +876,7 @@ export function createWorkspaceAwareController(controller, { workspaces, stateFo
|
|
|
778
876
|
if (transitions.get(botId) === current) transitions.delete(botId);
|
|
779
877
|
});
|
|
780
878
|
};
|
|
879
|
+
const decorate = (value) => decorateResult(workspaces, value, agentPresetCatalog);
|
|
781
880
|
const updateWorkspace = (botId, workspace) => {
|
|
782
881
|
// Capture at API invocation, before even waiting for an older outer
|
|
783
882
|
// transition. A queued request still belongs to the incarnation that the
|
|
@@ -795,7 +894,34 @@ export function createWorkspaceAwareController(controller, { workspaces, stateFo
|
|
|
795
894
|
clearSessions: () => state.clearSessions(),
|
|
796
895
|
incarnation,
|
|
797
896
|
});
|
|
798
|
-
return
|
|
897
|
+
return decorate(await controller.status());
|
|
898
|
+
});
|
|
899
|
+
};
|
|
900
|
+
const updateAgentPreset = (botId, agentPreset) => {
|
|
901
|
+
const incarnation = workspaces.incarnationFor(botId);
|
|
902
|
+
const normalizedAgentPreset = validateAgentPresetId(agentPreset);
|
|
903
|
+
return withBotTransition(botId, async () => {
|
|
904
|
+
const snapshot = await controller.status();
|
|
905
|
+
if (!snapshot?.bots?.some((bot) => bot?.botId === botId)) {
|
|
906
|
+
const error = new Error('找不到要修改的机器人。');
|
|
907
|
+
error.code = 'workspace-bot-not-found';
|
|
908
|
+
throw error;
|
|
909
|
+
}
|
|
910
|
+
const catalog = normalizedAgentPreset && agentPresetCatalog
|
|
911
|
+
? await resolveAgentPresetCatalog(agentPresetCatalog)
|
|
912
|
+
: null;
|
|
913
|
+
if (normalizedAgentPreset && agentPresetCatalog
|
|
914
|
+
&& !catalog?.items.some((item) => item.id === normalizedAgentPreset)) {
|
|
915
|
+
const error = new Error('Agent Preset 不存在或不可用。');
|
|
916
|
+
error.code = 'agent-preset-unavailable';
|
|
917
|
+
throw error;
|
|
918
|
+
}
|
|
919
|
+
await workspaces.setAgentPreset(botId, normalizedAgentPreset, { incarnation });
|
|
920
|
+
return decorateResult(
|
|
921
|
+
workspaces,
|
|
922
|
+
await controller.status(),
|
|
923
|
+
catalog ?? agentPresetCatalog,
|
|
924
|
+
);
|
|
799
925
|
});
|
|
800
926
|
};
|
|
801
927
|
const deleteWithWorkspace = (botId, invokeDelete) => withBotTransition(botId, async () => {
|
|
@@ -822,7 +948,7 @@ export function createWorkspaceAwareController(controller, { workspaces, stateFo
|
|
|
822
948
|
try {
|
|
823
949
|
const result = await invokeDelete();
|
|
824
950
|
await workspaces.finishRemoval(removal);
|
|
825
|
-
return
|
|
951
|
+
return decorate(result);
|
|
826
952
|
} catch (error) {
|
|
827
953
|
const after = await targetStatus(controller).catch(() => null);
|
|
828
954
|
const knownAbsent = Array.isArray(after?.bots)
|
|
@@ -836,6 +962,7 @@ export function createWorkspaceAwareController(controller, { workspaces, stateFo
|
|
|
836
962
|
return new Proxy(controller, {
|
|
837
963
|
get(target, property) {
|
|
838
964
|
if (property === 'updateWorkspace') return updateWorkspace;
|
|
965
|
+
if (property === 'updateAgentPreset') return updateAgentPreset;
|
|
839
966
|
const value = Reflect.get(target, property, target);
|
|
840
967
|
if (typeof value !== 'function') return value;
|
|
841
968
|
if (property === 'deleteBot') {
|
|
@@ -848,11 +975,11 @@ export function createWorkspaceAwareController(controller, { workspaces, stateFo
|
|
|
848
975
|
return async (...args) => {
|
|
849
976
|
const before = await target.status();
|
|
850
977
|
const botId = before?.bots?.[0]?.botId;
|
|
851
|
-
if (!botId) return
|
|
978
|
+
if (!botId) return decorate(value.apply(target, args));
|
|
852
979
|
return deleteWithWorkspace(botId, () => value.apply(target, args));
|
|
853
980
|
};
|
|
854
981
|
}
|
|
855
|
-
return (...args) =>
|
|
982
|
+
return (...args) => decorate(value.apply(target, args));
|
|
856
983
|
},
|
|
857
984
|
});
|
|
858
985
|
}
|
|
@@ -592,11 +592,13 @@ export class HarnessClient {
|
|
|
592
592
|
}
|
|
593
593
|
|
|
594
594
|
async createSession(options = {}) {
|
|
595
|
-
|
|
596
|
-
|
|
595
|
+
const { agentPreset: requestedPreset, ...rpcOptions } = options;
|
|
596
|
+
await this.ensureRunning(rpcOptions);
|
|
597
|
+
const workspaceId = await this.workspaceId(rpcOptions);
|
|
597
598
|
const payload = { workspaceId };
|
|
598
|
-
|
|
599
|
-
|
|
599
|
+
const agentPreset = requestedPreset !== undefined ? requestedPreset : this.#agentPreset;
|
|
600
|
+
if (agentPreset != null) payload.agentPreset = agentPreset;
|
|
601
|
+
const created = await this.rpc('session.create', payload, 30_000, rpcOptions);
|
|
600
602
|
return created.sessionId;
|
|
601
603
|
}
|
|
602
604
|
|
|
@@ -16,6 +16,18 @@ export function validTelegramToken(value) {
|
|
|
16
16
|
return typeof value === 'string' && /^\d{5,20}:[A-Za-z0-9_-]{20,}$/.test(value.trim());
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
const TELEGRAM_COMMAND_NAME = /^[a-z0-9_]{1,32}$/;
|
|
20
|
+
|
|
21
|
+
function validBotCommand(value) {
|
|
22
|
+
return Boolean(value)
|
|
23
|
+
&& typeof value === 'object' && !Array.isArray(value)
|
|
24
|
+
&& typeof value.command === 'string' && TELEGRAM_COMMAND_NAME.test(value.command)
|
|
25
|
+
&& typeof value.description === 'string' && value.description.length >= 1
|
|
26
|
+
&& value.description.length <= 256;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const COMMANDS_MENU_BUTTON = Object.freeze({ type: 'commands' });
|
|
30
|
+
|
|
19
31
|
export class TelegramApi {
|
|
20
32
|
#token;
|
|
21
33
|
#fetch;
|
|
@@ -113,6 +125,25 @@ export class TelegramApi {
|
|
|
113
125
|
}, { signal });
|
|
114
126
|
}
|
|
115
127
|
|
|
128
|
+
async setMyCommands({ commands, scope, languageCode, signal } = {}) {
|
|
129
|
+
if (!Array.isArray(commands) || commands.length === 0
|
|
130
|
+
|| commands.some((command) => !validBotCommand(command))) {
|
|
131
|
+
throw new TypeError('Telegram bot commands are invalid');
|
|
132
|
+
}
|
|
133
|
+
return this.#call('setMyCommands', {
|
|
134
|
+
commands,
|
|
135
|
+
...(scope ? { scope } : {}),
|
|
136
|
+
...(cleanString(languageCode) ? { language_code: cleanString(languageCode) } : {}),
|
|
137
|
+
}, { signal });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async setChatMenuButton({ menuButton = COMMANDS_MENU_BUTTON, signal } = {}) {
|
|
141
|
+
if (!menuButton || typeof menuButton !== 'object' || Array.isArray(menuButton)) {
|
|
142
|
+
throw new TypeError('Telegram menu button is invalid');
|
|
143
|
+
}
|
|
144
|
+
return this.#call('setChatMenuButton', { menu_button: menuButton }, { signal });
|
|
145
|
+
}
|
|
146
|
+
|
|
116
147
|
async #call(method, payload, { signal, timeoutMs = 15_000 } = {}) {
|
|
117
148
|
const url = new URL(this.#baseUrl);
|
|
118
149
|
url.pathname = `${url.pathname.replace(/\/$/, '')}/bot${this.#token}/${method}`;
|
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
import { createEditableMessageStream, splitMessageText } from '../shared/editable-message-stream.mjs';
|
|
2
|
-
import { TelegramApi } from './telegram-api.mjs';
|
|
2
|
+
import { COMMANDS_MENU_BUTTON, TelegramApi } from './telegram-api.mjs';
|
|
3
3
|
import { createTelegramBridgeStatus, TelegramHarnessBridge } from './telegram-bridge.mjs';
|
|
4
4
|
import {
|
|
5
5
|
TELEGRAM_ACCESS_MODES,
|
|
6
6
|
normalizeTelegramAccessPolicy,
|
|
7
7
|
} from './config-store.mjs';
|
|
8
8
|
|
|
9
|
+
export const TELEGRAM_COMMAND_MENU = Object.freeze([
|
|
10
|
+
{ command: 'new', description: '开启一个全新会话' },
|
|
11
|
+
{ command: 'compact', description: '压缩当前会话的较早上下文' },
|
|
12
|
+
{ command: 'workspace', description: '切换工作区' },
|
|
13
|
+
{ command: 'workspacelist', description: '列出工作区绝对路径' },
|
|
14
|
+
{ command: 'sessionlist', description: '列出会话 ID 和标题' },
|
|
15
|
+
{ command: 'session', description: '将当前聊天绑定到指定会话' },
|
|
16
|
+
{ command: 'models', description: '按序号列出所有可用模型' },
|
|
17
|
+
{ command: 'model', description: '查看或切换当前会话模型' },
|
|
18
|
+
{ command: 'stop', description: '停止当前任务' },
|
|
19
|
+
{ command: 'steer', description: '纠偏当前任务' },
|
|
20
|
+
{ command: 'status', description: '检查连接状态' },
|
|
21
|
+
{ command: 'help', description: '显示帮助' },
|
|
22
|
+
]);
|
|
23
|
+
|
|
9
24
|
function escaped(value) {
|
|
10
25
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
11
26
|
}
|
|
@@ -290,6 +305,15 @@ export class TelegramRuntime {
|
|
|
290
305
|
error.code = 'webhook-configured';
|
|
291
306
|
throw error;
|
|
292
307
|
}
|
|
308
|
+
try {
|
|
309
|
+
await api.setMyCommands({ commands: TELEGRAM_COMMAND_MENU, signal: controller.signal });
|
|
310
|
+
await api.setChatMenuButton({ menuButton: COMMANDS_MENU_BUTTON, signal: controller.signal });
|
|
311
|
+
} catch (error) {
|
|
312
|
+
this.#logger.warn?.(
|
|
313
|
+
`[dsh-im:telegram] bot ${this.#config.botId} command menu setup failed:`,
|
|
314
|
+
error,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
293
317
|
const client = new TelegramBotClient({ api, signal: controller.signal });
|
|
294
318
|
this.#bridge = new TelegramHarnessBridge({
|
|
295
319
|
bot: client,
|