@xmanrui/dsh-im 4.0.1 → 4.1.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/PROACTIVE_DELIVERY.en.md +321 -0
- package/PROACTIVE_DELIVERY.md +321 -0
- package/README.en.md +11 -5
- package/README.md +11 -5
- package/lib/client.js +1190 -108
- package/lib/index.js +229 -229
- package/package.json +4 -1
- package/plugin-src/client/channel-card-meta.js +44 -0
- package/plugin-src/client/channels/dingtalk/index.js +16 -8
- package/plugin-src/client/channels/feishu/index.js +17 -9
- package/plugin-src/client/channels/qq/index.js +16 -8
- package/plugin-src/client/channels/shared/token-channel.js +16 -8
- package/plugin-src/client/channels/wecom/index.js +16 -8
- package/plugin-src/client/channels/weixin/index.js +16 -8
- package/plugin-src/client/channels/whatsapp/index.js +16 -8
- package/plugin-src/client/delivery-settings.js +749 -0
- package/plugin-src/client/i18n.js +82 -0
- package/plugin-src/client/index.js +46 -20
- package/plugin-src/client/styles.js +72 -0
- package/plugin-src/host/channels/dingtalk/index.mjs +3 -0
- package/plugin-src/host/channels/dingtalk/production.mjs +4 -0
- package/plugin-src/host/channels/discord/index.mjs +6 -1
- package/plugin-src/host/channels/feishu/index.mjs +3 -0
- package/plugin-src/host/channels/feishu/production.mjs +4 -0
- package/plugin-src/host/channels/qq/index.mjs +6 -1
- package/plugin-src/host/channels/qq/production.mjs +2 -0
- package/plugin-src/host/channels/shared/production.mjs +7 -0
- package/plugin-src/host/channels/slack/index.mjs +6 -1
- package/plugin-src/host/channels/slack/production.mjs +4 -0
- package/plugin-src/host/channels/telegram/index.mjs +6 -1
- package/plugin-src/host/channels/wecom/index.mjs +6 -1
- package/plugin-src/host/channels/wecom/production.mjs +4 -0
- package/plugin-src/host/channels/weixin/index.mjs +3 -0
- package/plugin-src/host/channels/weixin/production.mjs +4 -0
- package/plugin-src/host/channels/whatsapp/index.mjs +6 -1
- package/plugin-src/host/channels/whatsapp/production.mjs +4 -0
- package/plugin-src/host/delivery-adapter.mjs +179 -0
- package/plugin-src/host/delivery-http.mjs +132 -0
- package/plugin-src/host/delivery-rpc.mjs +158 -0
- package/plugin-src/host/delivery-service.mjs +224 -0
- package/plugin-src/host/delivery-suggestions.mjs +135 -0
- package/plugin-src/host/index.mjs +32 -5
- package/scripts/verify-package.mjs +3 -0
- package/src/channels/dingtalk/dingtalk-api.mjs +33 -0
- package/src/channels/dingtalk/dingtalk-bridge.mjs +2 -2
- package/src/channels/dingtalk/dingtalk-controller.mjs +14 -0
- package/src/channels/dingtalk/dingtalk-runtime.mjs +39 -0
- package/src/channels/discord/discord-runtime.mjs +16 -0
- package/src/channels/feishu/bridge.mjs +4 -4
- package/src/channels/feishu/feishu-cards.mjs +4 -4
- package/src/channels/feishu/feishu-runtime.mjs +35 -0
- package/src/channels/feishu/multi-bot-controller.mjs +15 -0
- package/src/channels/qq/qq-bridge.mjs +2 -2
- package/src/channels/qq/qq-controller.mjs +14 -0
- package/src/channels/qq/qq-runtime.mjs +23 -0
- package/src/channels/shared/bot-workspace-store.mjs +192 -6
- package/src/channels/shared/i18n-en/feishu.mjs +6 -2
- package/src/channels/shared/i18n-en/shared-a.mjs +4 -0
- package/src/channels/shared/preset-command.mjs +2 -2
- package/src/channels/shared/text-harness-bridge.mjs +7 -2
- package/src/channels/shared/token-bot-controller.mjs +16 -0
- package/src/channels/shared/workspace-command.mjs +1 -1
- package/src/channels/slack/slack-controller.mjs +14 -0
- package/src/channels/slack/slack-runtime.mjs +24 -0
- package/src/channels/telegram/telegram-runtime.mjs +26 -0
- package/src/channels/wecom/state-store.mjs +4 -0
- package/src/channels/wecom/wecom-bridge.mjs +2 -2
- package/src/channels/wecom/wecom-controller.mjs +14 -0
- package/src/channels/wecom/wecom-runtime.mjs +21 -0
- package/src/channels/weixin/weixin-bridge.mjs +2 -2
- package/src/channels/weixin/weixin-controller.mjs +14 -0
- package/src/channels/weixin/weixin-runtime.mjs +24 -0
- package/src/channels/whatsapp/whatsapp-controller.mjs +14 -0
- package/src/channels/whatsapp/whatsapp-runtime.mjs +19 -0
|
@@ -563,6 +563,41 @@ export class FeishuRuntime {
|
|
|
563
563
|
});
|
|
564
564
|
}
|
|
565
565
|
|
|
566
|
+
async sendProactiveText(target, text, { signal } = {}) {
|
|
567
|
+
if (!this.#status.ready || !this.#client) {
|
|
568
|
+
const error = new Error('飞书机器人尚未连接');
|
|
569
|
+
error.code = 'bot-not-connected';
|
|
570
|
+
throw error;
|
|
571
|
+
}
|
|
572
|
+
const receiveIdType = target?.kind === 'user' ? 'open_id'
|
|
573
|
+
: target?.kind === 'group' ? 'chat_id' : null;
|
|
574
|
+
const receiveId = target?.kind === 'user'
|
|
575
|
+
? nonEmptyString(target?.route?.openId)
|
|
576
|
+
: target?.kind === 'group'
|
|
577
|
+
? nonEmptyString(target?.route?.chatId)
|
|
578
|
+
: null;
|
|
579
|
+
if (!receiveIdType || !receiveId) {
|
|
580
|
+
const error = new TypeError('Invalid Feishu proactive delivery target');
|
|
581
|
+
error.code = 'invalid-target';
|
|
582
|
+
throw error;
|
|
583
|
+
}
|
|
584
|
+
signal?.throwIfAborted();
|
|
585
|
+
const response = await this.#client.im.v1.message.create({
|
|
586
|
+
params: { receive_id_type: receiveIdType },
|
|
587
|
+
data: {
|
|
588
|
+
receive_id: receiveId,
|
|
589
|
+
msg_type: 'text',
|
|
590
|
+
content: JSON.stringify({ text }),
|
|
591
|
+
},
|
|
592
|
+
});
|
|
593
|
+
if (response?.code && response.code !== 0) {
|
|
594
|
+
const error = new Error(`Feishu proactive delivery failed: ${response.msg || response.code}`);
|
|
595
|
+
error.code = 'target-rejected';
|
|
596
|
+
throw error;
|
|
597
|
+
}
|
|
598
|
+
return { sent: true };
|
|
599
|
+
}
|
|
600
|
+
|
|
566
601
|
stop(options = {}) {
|
|
567
602
|
if (this.#stopping) return this.#stopping;
|
|
568
603
|
|
|
@@ -537,6 +537,21 @@ export class MultiBotDshFeishuController {
|
|
|
537
537
|
});
|
|
538
538
|
}
|
|
539
539
|
|
|
540
|
+
async sendProactiveText(botId, target, text, options = {}) {
|
|
541
|
+
this.#assertOpen();
|
|
542
|
+
return this.#withBotTransition(botId, async () => {
|
|
543
|
+
this.#requireBot(botId);
|
|
544
|
+
const runtime = this.#runtimes.get(botId);
|
|
545
|
+
if (!isConnected(connectionStatus(runtime))
|
|
546
|
+
|| typeof runtime.sendProactiveText !== 'function') {
|
|
547
|
+
const error = new Error('飞书机器人尚未连接');
|
|
548
|
+
error.code = 'bot-not-connected';
|
|
549
|
+
throw error;
|
|
550
|
+
}
|
|
551
|
+
return runtime.sendProactiveText(target, text, options);
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
540
555
|
async disconnectBot(botId) {
|
|
541
556
|
this.#assertOpen();
|
|
542
557
|
// An operational pause only: credentials/config remain durable, so the
|
|
@@ -83,14 +83,14 @@ function helpText() {
|
|
|
83
83
|
t('/history [数量] 查看最近历史消息(默认 3 条,最多 5 条)'),
|
|
84
84
|
t('/workspace 工作区绝对路径 切换工作区'),
|
|
85
85
|
t('/workspacelist 列出工作区绝对路径'),
|
|
86
|
-
t('/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题'),
|
|
86
|
+
t('/sessionlist 或 /sessions [工作区序号或绝对路径] 列出会话 ID 和标题'),
|
|
87
87
|
t('/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话'),
|
|
88
88
|
t('/models 按序号列出所有可用模型'),
|
|
89
89
|
t('/reasoninglist 或 /reasonings 按序号列出当前模型可用推理等级'),
|
|
90
90
|
t('/reasoning [序号、等级ID或 --default] 查看或切换当前推理等级'),
|
|
91
91
|
t('/model [序号或完整模型ID] [推理等级ID] 查看或切换当前会话模型'),
|
|
92
92
|
t('示例:先发 /models,再发 /model 2 [推理等级ID]'),
|
|
93
|
-
t('/presetlist 按序号列出可用 Agent Preset'),
|
|
93
|
+
t('/presetlist 或 /presets 按序号列出可用 Agent Preset'),
|
|
94
94
|
t('/preset [序号或完整ID] 查看或设置当前机器人 Agent Preset'),
|
|
95
95
|
t('纯数字 ID:/preset id:<ID>'),
|
|
96
96
|
t('/preset --default 跟随 Host 默认'),
|
|
@@ -268,6 +268,20 @@ export class QqController {
|
|
|
268
268
|
});
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
+
async sendProactiveText(botId, target, text, options = {}) {
|
|
272
|
+
const config = this.#configStore.get(botId);
|
|
273
|
+
if (!config) throw new Error('Unknown QQ bot');
|
|
274
|
+
return this.#withBotTransition(botId, async () => {
|
|
275
|
+
const runtime = this.#runtimes.get(botId);
|
|
276
|
+
if (!runtime?.status?.ready || typeof runtime.sendProactiveText !== 'function') {
|
|
277
|
+
const error = new Error(t('QQ机器人尚未连接'));
|
|
278
|
+
error.code = 'bot-not-connected';
|
|
279
|
+
throw error;
|
|
280
|
+
}
|
|
281
|
+
return runtime.sendProactiveText(target, text, options);
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
271
285
|
async deleteBot(botId) {
|
|
272
286
|
const config = this.#configStore.get(botId);
|
|
273
287
|
if (!config) throw new Error('Unknown QQ bot');
|
|
@@ -97,6 +97,29 @@ export class QqRuntime {
|
|
|
97
97
|
return { sent: true };
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
async sendProactiveText(target, text, { signal } = {}) {
|
|
101
|
+
const nativeId = target?.kind === 'user'
|
|
102
|
+
? (typeof target?.route?.userOpenId === 'string' ? target.route.userOpenId.trim() : '')
|
|
103
|
+
: target?.kind === 'group'
|
|
104
|
+
? (typeof target?.route?.groupOpenId === 'string' ? target.route.groupOpenId.trim() : '')
|
|
105
|
+
: '';
|
|
106
|
+
if (!nativeId) {
|
|
107
|
+
const error = new TypeError('Invalid QQ proactive delivery target');
|
|
108
|
+
error.code = 'invalid-target';
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
if (!this.#status.ready || !this.#bot) {
|
|
112
|
+
const error = new Error('QQ bot is not connected');
|
|
113
|
+
error.code = 'bot-not-connected';
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
signal?.throwIfAborted();
|
|
117
|
+
return this.#bot.sendText({
|
|
118
|
+
scope: target.kind === 'user' ? 'c2c' : 'group',
|
|
119
|
+
targetId: nativeId,
|
|
120
|
+
}, text);
|
|
121
|
+
}
|
|
122
|
+
|
|
100
123
|
async start() {
|
|
101
124
|
if (this.#status.ready && this.#bot) return this.status;
|
|
102
125
|
if (this.#starting) return this.#starting;
|
|
@@ -49,8 +49,90 @@ function botIdOf(value) {
|
|
|
49
49
|
return value;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
function deliveryTargetError(code, message, options) {
|
|
53
|
+
const error = new Error(message, options);
|
|
54
|
+
error.code = code;
|
|
55
|
+
return error;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function targetIdOf(value) {
|
|
59
|
+
if (typeof value !== 'string'
|
|
60
|
+
|| !/^[A-Za-z0-9._:@-]{1,128}$/.test(value)) {
|
|
61
|
+
throw deliveryTargetError('invalid-target', 'Invalid target id');
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function jsonRecord(value) {
|
|
67
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
68
|
+
throw deliveryTargetError('invalid-target', 'Invalid target route');
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const serialized = JSON.stringify(value);
|
|
72
|
+
if (serialized === undefined) throw new TypeError('route is not JSON');
|
|
73
|
+
const route = JSON.parse(serialized);
|
|
74
|
+
if (!route || typeof route !== 'object' || Array.isArray(route)) {
|
|
75
|
+
throw new TypeError('route is not an object');
|
|
76
|
+
}
|
|
77
|
+
return route;
|
|
78
|
+
} catch (cause) {
|
|
79
|
+
throw deliveryTargetError('invalid-target', 'Invalid target route', { cause });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function normalizeDeliveryTarget(value, { targetId } = {}) {
|
|
84
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
85
|
+
throw deliveryTargetError('invalid-target', 'Invalid delivery target');
|
|
86
|
+
}
|
|
87
|
+
const allowed = targetId === undefined
|
|
88
|
+
? new Set(['targetId', 'name', 'kind', 'route'])
|
|
89
|
+
: new Set(['name', 'kind', 'route']);
|
|
90
|
+
if (Object.keys(value).some((key) => !allowed.has(key))) {
|
|
91
|
+
throw deliveryTargetError('invalid-target', 'Invalid delivery target');
|
|
92
|
+
}
|
|
93
|
+
const id = targetIdOf(targetId ?? value.targetId);
|
|
94
|
+
if (typeof value.kind !== 'string' || !/^[a-z][a-z0-9-]{0,31}$/.test(value.kind)) {
|
|
95
|
+
throw deliveryTargetError('invalid-target', 'Invalid target kind');
|
|
96
|
+
}
|
|
97
|
+
let name;
|
|
98
|
+
if (value.name !== undefined) {
|
|
99
|
+
if (typeof value.name !== 'string' || !value.name.trim() || value.name.trim().length > 80) {
|
|
100
|
+
throw deliveryTargetError('invalid-target', 'Invalid target name');
|
|
101
|
+
}
|
|
102
|
+
name = value.name.trim();
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
targetId: id,
|
|
106
|
+
...(name === undefined ? {} : { name }),
|
|
107
|
+
kind: value.kind,
|
|
108
|
+
route: jsonRecord(value.route),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function normalizeDeliveryTargets(value) {
|
|
113
|
+
const deliveryTargets = Object.create(null);
|
|
114
|
+
if (value === undefined) return deliveryTargets;
|
|
115
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
116
|
+
try {
|
|
117
|
+
for (const [botId, targets] of Object.entries(value)) {
|
|
118
|
+
botIdOf(botId);
|
|
119
|
+
if (!targets || typeof targets !== 'object' || Array.isArray(targets)) return null;
|
|
120
|
+
const normalizedTargets = Object.create(null);
|
|
121
|
+
for (const [targetId, target] of Object.entries(targets)) {
|
|
122
|
+
const normalized = normalizeDeliveryTarget(target, { targetId });
|
|
123
|
+
const { targetId: _targetId, ...stored } = normalized;
|
|
124
|
+
normalizedTargets[targetId] = stored;
|
|
125
|
+
}
|
|
126
|
+
deliveryTargets[botId] = normalizedTargets;
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
return deliveryTargets;
|
|
132
|
+
}
|
|
133
|
+
|
|
52
134
|
function normalizeDocument(value) {
|
|
53
|
-
if (!value || value.version
|
|
135
|
+
if (!value || ![1, 2].includes(value.version) || !value.workspaces
|
|
54
136
|
|| typeof value.workspaces !== 'object' || Array.isArray(value.workspaces)) return null;
|
|
55
137
|
const workspaces = {};
|
|
56
138
|
for (const [botId, workspace] of Object.entries(value.workspaces)) {
|
|
@@ -83,7 +165,16 @@ function normalizeDocument(value) {
|
|
|
83
165
|
}
|
|
84
166
|
}
|
|
85
167
|
}
|
|
86
|
-
|
|
168
|
+
if (value.version === 1 && value.deliveryTargets !== undefined) return null;
|
|
169
|
+
const deliveryTargets = normalizeDeliveryTargets(value.deliveryTargets);
|
|
170
|
+
if (!deliveryTargets) return null;
|
|
171
|
+
return {
|
|
172
|
+
version: value.version,
|
|
173
|
+
workspaces,
|
|
174
|
+
agentPresets,
|
|
175
|
+
contextEnhancement,
|
|
176
|
+
deliveryTargets,
|
|
177
|
+
};
|
|
87
178
|
}
|
|
88
179
|
|
|
89
180
|
export async function validateWorkspacePath(value) {
|
|
@@ -112,9 +203,11 @@ export async function validateWorkspacePath(value) {
|
|
|
112
203
|
export class BotWorkspaceStore {
|
|
113
204
|
#path;
|
|
114
205
|
#defaultWorkspace;
|
|
206
|
+
#version = 1;
|
|
115
207
|
#workspaces = {};
|
|
116
208
|
#agentPresets = {};
|
|
117
209
|
#contextEnhancement = {};
|
|
210
|
+
#deliveryTargets = Object.create(null);
|
|
118
211
|
#generations = new Map();
|
|
119
212
|
#nextGeneration = 1;
|
|
120
213
|
#incarnations = new Map();
|
|
@@ -135,14 +228,18 @@ export class BotWorkspaceStore {
|
|
|
135
228
|
try {
|
|
136
229
|
const normalized = normalizeDocument(JSON.parse(await readFile(this.#path, 'utf8')));
|
|
137
230
|
if (!normalized) throw new Error('dsh-im workspace config is invalid');
|
|
231
|
+
this.#version = normalized.version;
|
|
138
232
|
this.#workspaces = normalized.workspaces;
|
|
139
233
|
this.#agentPresets = normalized.agentPresets;
|
|
140
234
|
this.#contextEnhancement = normalized.contextEnhancement;
|
|
235
|
+
this.#deliveryTargets = normalized.deliveryTargets;
|
|
141
236
|
} catch (error) {
|
|
142
237
|
if (error?.code !== 'ENOENT') throw error;
|
|
238
|
+
this.#version = 1;
|
|
143
239
|
this.#workspaces = {};
|
|
144
240
|
this.#agentPresets = {};
|
|
145
241
|
this.#contextEnhancement = {};
|
|
242
|
+
this.#deliveryTargets = Object.create(null);
|
|
146
243
|
}
|
|
147
244
|
this.#generations.clear();
|
|
148
245
|
this.#nextGeneration = 1;
|
|
@@ -181,6 +278,83 @@ export class BotWorkspaceStore {
|
|
|
181
278
|
: DEFAULT_CONTEXT_ENHANCEMENT_CONFIG;
|
|
182
279
|
}
|
|
183
280
|
|
|
281
|
+
listDeliveryTargets(botId) {
|
|
282
|
+
const id = botIdOf(botId);
|
|
283
|
+
if (!this.has(id)) throw deliveryTargetError('unknown-bot', 'Unknown bot');
|
|
284
|
+
return Object.entries(this.#deliveryTargets[id] ?? {})
|
|
285
|
+
.map(([targetId, target]) => normalizeDeliveryTarget(target, { targetId }))
|
|
286
|
+
.sort((left, right) => left.targetId.localeCompare(right.targetId));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
deliveryTargetFor(botId, targetId) {
|
|
290
|
+
const id = botIdOf(botId);
|
|
291
|
+
const targetKey = targetIdOf(targetId);
|
|
292
|
+
if (!this.has(id)) throw deliveryTargetError('unknown-bot', 'Unknown bot');
|
|
293
|
+
const target = this.#deliveryTargets[id]?.[targetKey];
|
|
294
|
+
return target ? normalizeDeliveryTarget(target, { targetId: targetKey }) : null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async createDeliveryTarget(botId, value) {
|
|
298
|
+
const id = botIdOf(botId);
|
|
299
|
+
const target = normalizeDeliveryTarget(value);
|
|
300
|
+
return this.#enqueue(id, async () => {
|
|
301
|
+
if (!this.has(id)) throw deliveryTargetError('unknown-bot', 'Unknown bot');
|
|
302
|
+
if (Object.hasOwn(this.#deliveryTargets[id] ?? {}, target.targetId)) {
|
|
303
|
+
throw deliveryTargetError('target-conflict', 'Target already exists');
|
|
304
|
+
}
|
|
305
|
+
const { targetId, ...stored } = target;
|
|
306
|
+
const next = {
|
|
307
|
+
...this.#deliveryTargets,
|
|
308
|
+
[id]: { ...(this.#deliveryTargets[id] ?? {}), [targetId]: stored },
|
|
309
|
+
};
|
|
310
|
+
await this.#persist(this.#contextEnhancement, next, 2);
|
|
311
|
+
this.#deliveryTargets = next;
|
|
312
|
+
this.#version = 2;
|
|
313
|
+
return normalizeDeliveryTarget(stored, { targetId });
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async updateDeliveryTarget(botId, targetId, value) {
|
|
318
|
+
const id = botIdOf(botId);
|
|
319
|
+
const targetKey = targetIdOf(targetId);
|
|
320
|
+
const replacement = normalizeDeliveryTarget(value, { targetId: targetKey });
|
|
321
|
+
return this.#enqueue(id, async () => {
|
|
322
|
+
if (!this.has(id)) throw deliveryTargetError('unknown-bot', 'Unknown bot');
|
|
323
|
+
if (!Object.hasOwn(this.#deliveryTargets[id] ?? {}, targetKey)) {
|
|
324
|
+
throw deliveryTargetError('unknown-target', 'Unknown target');
|
|
325
|
+
}
|
|
326
|
+
const { targetId: _targetId, ...stored } = replacement;
|
|
327
|
+
const next = {
|
|
328
|
+
...this.#deliveryTargets,
|
|
329
|
+
[id]: { ...this.#deliveryTargets[id], [targetKey]: stored },
|
|
330
|
+
};
|
|
331
|
+
await this.#persist(this.#contextEnhancement, next, 2);
|
|
332
|
+
this.#deliveryTargets = next;
|
|
333
|
+
this.#version = 2;
|
|
334
|
+
return normalizeDeliveryTarget(stored, { targetId: targetKey });
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async deleteDeliveryTarget(botId, targetId) {
|
|
339
|
+
const id = botIdOf(botId);
|
|
340
|
+
const targetKey = targetIdOf(targetId);
|
|
341
|
+
return this.#enqueue(id, async () => {
|
|
342
|
+
if (!this.has(id)) throw deliveryTargetError('unknown-bot', 'Unknown bot');
|
|
343
|
+
if (!Object.hasOwn(this.#deliveryTargets[id] ?? {}, targetKey)) {
|
|
344
|
+
throw deliveryTargetError('unknown-target', 'Unknown target');
|
|
345
|
+
}
|
|
346
|
+
const botTargets = { ...this.#deliveryTargets[id] };
|
|
347
|
+
delete botTargets[targetKey];
|
|
348
|
+
const next = { ...this.#deliveryTargets };
|
|
349
|
+
if (Object.keys(botTargets).length > 0) next[id] = botTargets;
|
|
350
|
+
else delete next[id];
|
|
351
|
+
await this.#persist(this.#contextEnhancement, next, 2);
|
|
352
|
+
this.#deliveryTargets = next;
|
|
353
|
+
this.#version = 2;
|
|
354
|
+
return true;
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
184
358
|
generationFor(botId) {
|
|
185
359
|
return this.#generations.get(botIdOf(botId)) ?? null;
|
|
186
360
|
}
|
|
@@ -471,6 +645,7 @@ export class BotWorkspaceStore {
|
|
|
471
645
|
...Object.keys(this.#workspaces),
|
|
472
646
|
...Object.keys(this.#agentPresets),
|
|
473
647
|
...Object.keys(this.#contextEnhancement),
|
|
648
|
+
...Object.keys(this.#deliveryTargets),
|
|
474
649
|
...this.#dirtyRemovals,
|
|
475
650
|
]);
|
|
476
651
|
for (const botId of candidates) {
|
|
@@ -518,10 +693,13 @@ export class BotWorkspaceStore {
|
|
|
518
693
|
const hadWorkspace = Object.hasOwn(this.#workspaces, id);
|
|
519
694
|
const hadPreset = Object.hasOwn(this.#agentPresets, id);
|
|
520
695
|
const hadContextEnhancement = Object.hasOwn(this.#contextEnhancement, id);
|
|
521
|
-
const
|
|
696
|
+
const hadDeliveryTargets = Object.hasOwn(this.#deliveryTargets, id);
|
|
697
|
+
const needsCleanup = hadWorkspace || hadPreset || hadContextEnhancement
|
|
698
|
+
|| hadDeliveryTargets || this.#dirtyRemovals.has(id);
|
|
522
699
|
delete this.#workspaces[id];
|
|
523
700
|
delete this.#agentPresets[id];
|
|
524
701
|
delete this.#contextEnhancement[id];
|
|
702
|
+
delete this.#deliveryTargets[id];
|
|
525
703
|
this.#generations.delete(id);
|
|
526
704
|
this.#incarnations.delete(id);
|
|
527
705
|
if (!needsCleanup) return {
|
|
@@ -551,14 +729,21 @@ export class BotWorkspaceStore {
|
|
|
551
729
|
return queued;
|
|
552
730
|
}
|
|
553
731
|
|
|
554
|
-
async #persist(
|
|
555
|
-
|
|
732
|
+
async #persist(
|
|
733
|
+
contextEnhancement = this.#contextEnhancement,
|
|
734
|
+
deliveryTargets = this.#deliveryTargets,
|
|
735
|
+
version = this.#version,
|
|
736
|
+
) {
|
|
737
|
+
const document = { version, workspaces: this.#workspaces };
|
|
556
738
|
if (Object.keys(this.#agentPresets).length > 0) {
|
|
557
739
|
document.agentPresets = this.#agentPresets;
|
|
558
740
|
}
|
|
559
741
|
if (Object.keys(contextEnhancement).length > 0) {
|
|
560
742
|
document.contextEnhancement = contextEnhancement;
|
|
561
743
|
}
|
|
744
|
+
if (version >= 2 && Object.keys(deliveryTargets).length > 0) {
|
|
745
|
+
document.deliveryTargets = deliveryTargets;
|
|
746
|
+
}
|
|
562
747
|
await mkdir(dirname(this.#path), { recursive: true, mode: 0o700 });
|
|
563
748
|
const temporary = `${this.#path}.tmp`;
|
|
564
749
|
await writeFile(temporary, `${JSON.stringify(document, null, 2)}\n`, {
|
|
@@ -572,7 +757,8 @@ export class BotWorkspaceStore {
|
|
|
572
757
|
async #persistCurrentDocument() {
|
|
573
758
|
if (Object.keys(this.#workspaces).length > 0
|
|
574
759
|
|| Object.keys(this.#agentPresets).length > 0
|
|
575
|
-
|| Object.keys(this.#contextEnhancement).length > 0
|
|
760
|
+
|| Object.keys(this.#contextEnhancement).length > 0
|
|
761
|
+
|| Object.keys(this.#deliveryTargets).length > 0) {
|
|
576
762
|
await this.#persist();
|
|
577
763
|
return;
|
|
578
764
|
}
|
|
@@ -223,6 +223,8 @@ export default {
|
|
|
223
223
|
'📊 系统状态': '📊 System status',
|
|
224
224
|
'📋 会话 / 工作区': '📋 Sessions / workspace',
|
|
225
225
|
'/sessionlist 列出工作区会话': '/sessionlist List workspace sessions',
|
|
226
|
+
'/sessionlist 或 /sessions 列出工作区会话':
|
|
227
|
+
'/sessionlist or /sessions List workspace sessions',
|
|
226
228
|
'/session ID 绑定已有会话': '/session ID Bind an existing session',
|
|
227
229
|
'/workspacelist 列出工作区': '/workspacelist List workspaces',
|
|
228
230
|
'/workspace 路径 切换工作区': '/workspace PATH Switch workspace',
|
|
@@ -239,12 +241,14 @@ export default {
|
|
|
239
241
|
'📦 批量输入(仅私聊)': '📦 Batch input (direct messages only)',
|
|
240
242
|
'🤖 预设 / 模型': '🤖 Presets / models',
|
|
241
243
|
'/models 列出模型': '/models List models',
|
|
244
|
+
'/presetlist 或 /presets 列出可用 Agent Preset':
|
|
245
|
+
'/presetlist or /presets List available Agent Presets',
|
|
242
246
|
'🎮 任务控制': '🎮 Task controls',
|
|
243
247
|
'/steer 指令 给 Agent 补充指令': '/steer INSTRUCTION Steer the Agent',
|
|
244
248
|
'**📋 卡片功能**\n\n1. 会话下拉 — 切换当前绑定会话\n2. 工作区下拉 — 切换工作区\n3. 🤖 预设下拉 — 切换 Agent 预设\n4. 🧠 模型下拉 — 切换模型\n5. 🆕 新会话 — 开启全新会话\n6. 📋 会话/关注 — 查看/绑定会话,管理关注\n7. ⏹ 停止 — 停止当前任务\n8. 📐 压缩 — 压缩当前会话上下文\n9. 补充指令 — 给 Agent 发送指令\n10. 🗄 归档切换 — 显示/隐藏归档会话\n11. 📊 状态 — 查看系统连接状态\n12. 📖 帮助 — 查看本帮助':
|
|
245
249
|
'**📋 Card features**\n\n1. Session dropdown — switch the bound session\n2. Workspace dropdown — switch workspace\n3. 🤖 Preset dropdown — switch Agent Preset\n4. 🧠 Model dropdown — switch model\n5. 🆕 New session — start fresh\n6. 📋 Sessions/watches — view or bind sessions and manage watches\n7. ⏹ Stop — stop the current task\n8. 📐 Compact — compact the current session context\n9. Steer task — send an instruction to the Agent\n10. 🗄 Archived toggle — show or hide archived sessions\n11. 📊 Status — view connection status\n12. 📖 Help — view this help',
|
|
246
|
-
'**⌨️ 文本命令**\n\n`/m` — 打开菜单卡片\n`/new` — 开启全新会话\n`/session ID` — 绑定已有会话\n`/sessionlist [工作区]` — 列出会话\n`/workspace 路径` — 切换工作区\n`/workspacelist` — 列出工作区\n`/status` — 查看连接状态\n`/compact` — 压缩上下文\n`/stop` — 停止当前任务\n`/steer 指令` — 补充指令\n`/watch ID` — 关注会话\n`/watchlist` — 关注列表\n`/unwatch ID` — 取消关注\n`/archived on/off` — 归档显隐\n`/presetlist` — 列出预设\n`/preset [序号/ID]` — 切换预设\n`/preset --default` — 跟随默认\n`/models` — 列出模型\n`/reasoninglist` 或 `/reasonings` — 按序号列出当前模型可用推理等级\n`/reasoning [序号、等级ID或 --default]` — 查看或切换当前推理等级\n`/model [序号或完整模型ID] [推理等级ID]` — 查看或切换当前会话模型\n`/batch` — 开启批量输入(仅私聊,最多 10 条文字)\n`/send` — 提交当前批次\n`/cancel` — 取消当前批次\n`/repair` — 补全飞书权限与卡片回调':
|
|
247
|
-
'**⌨️ Text commands**\n\n`/m` — open the menu card\n`/new` — start a new session\n`/session ID` — bind an existing session\n`/sessionlist [workspace]` — list sessions\n`/workspace PATH` — switch workspace\n`/workspacelist` — list workspaces\n`/status` — view connection status\n`/compact` — compact context\n`/stop` — stop the current task\n`/steer INSTRUCTION` — steer the task\n`/watch ID` — watch a session\n`/watchlist` — list watched sessions\n`/unwatch ID` — stop watching\n`/archived on/off` — show or hide archived sessions\n`/presetlist` — list presets\n`/preset [index/ID]` — switch preset\n`/preset --default` — follow default\n`/models` — list models\n`/reasoninglist` or `/reasonings` — list reasoning efforts for the current model\n`/reasoning [index, effort ID, or --default]` — show or switch reasoning effort\n`/model [index or full model ID] [reasoning effort ID]` — show or switch the current Session model\n`/batch` — start batch input (direct messages only, up to 10 text messages)\n`/send` — submit the current batch\n`/cancel` — cancel the current batch\n`/repair` — complete Feishu permissions and the card callback',
|
|
250
|
+
'**⌨️ 文本命令**\n\n`/m` — 打开菜单卡片\n`/new` — 开启全新会话\n`/session ID` — 绑定已有会话\n`/sessionlist [工作区]` 或 `/sessions [工作区]` — 列出会话\n`/workspace 路径` — 切换工作区\n`/workspacelist` — 列出工作区\n`/status` — 查看连接状态\n`/compact` — 压缩上下文\n`/stop` — 停止当前任务\n`/steer 指令` — 补充指令\n`/watch ID` — 关注会话\n`/watchlist` — 关注列表\n`/unwatch ID` — 取消关注\n`/archived on/off` — 归档显隐\n`/presetlist` 或 `/presets` — 列出预设\n`/preset [序号/ID]` — 切换预设\n`/preset --default` — 跟随默认\n`/models` — 列出模型\n`/reasoninglist` 或 `/reasonings` — 按序号列出当前模型可用推理等级\n`/reasoning [序号、等级ID或 --default]` — 查看或切换当前推理等级\n`/model [序号或完整模型ID] [推理等级ID]` — 查看或切换当前会话模型\n`/batch` — 开启批量输入(仅私聊,最多 10 条文字)\n`/send` — 提交当前批次\n`/cancel` — 取消当前批次\n`/repair` — 补全飞书权限与卡片回调':
|
|
251
|
+
'**⌨️ Text commands**\n\n`/m` — open the menu card\n`/new` — start a new session\n`/session ID` — bind an existing session\n`/sessionlist [workspace]` or `/sessions [workspace]` — list sessions\n`/workspace PATH` — switch workspace\n`/workspacelist` — list workspaces\n`/status` — view connection status\n`/compact` — compact context\n`/stop` — stop the current task\n`/steer INSTRUCTION` — steer the task\n`/watch ID` — watch a session\n`/watchlist` — list watched sessions\n`/unwatch ID` — stop watching\n`/archived on/off` — show or hide archived sessions\n`/presetlist` or `/presets` — list presets\n`/preset [index/ID]` — switch preset\n`/preset --default` — follow default\n`/models` — list models\n`/reasoninglist` or `/reasonings` — list reasoning efforts for the current model\n`/reasoning [index, effort ID, or --default]` — show or switch reasoning effort\n`/model [index or full model ID] [reasoning effort ID]` — show or switch the current Session model\n`/batch` — start batch input (direct messages only, up to 10 text messages)\n`/send` — submit the current batch\n`/cancel` — cancel the current batch\n`/repair` — complete Feishu permissions and the card callback',
|
|
248
252
|
'**💡 数字兜底**\n回复数字快速操作:\n**1**工作区列表 · **2**新会话 · **3**会话/关注\n**4**状态 · **5**补全权限 · **6**帮助':
|
|
249
253
|
'**💡 Number fallback**\nReply with a number for a quick action:\n**1** Workspace list · **2** New session · **3** Sessions/watches\n**4** Status · **5** Complete permissions · **6** Help',
|
|
250
254
|
'从下方下拉选择补充指令;最后一项可自定义输入。':
|
|
@@ -124,6 +124,8 @@ export default {
|
|
|
124
124
|
'/workspacelist 列出工作区绝对路径': '/workspacelist List absolute workspace paths',
|
|
125
125
|
'/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题':
|
|
126
126
|
'/sessionlist [workspace index or absolute path] List session IDs and titles',
|
|
127
|
+
'/sessionlist 或 /sessions [工作区序号或绝对路径] 列出会话 ID 和标题':
|
|
128
|
+
'/sessionlist or /sessions [workspace index or absolute path] List session IDs and titles',
|
|
127
129
|
'/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话':
|
|
128
130
|
'/session <Session ID or workspace index> Bind this chat to the specified session',
|
|
129
131
|
'/models 按序号列出所有可用模型': '/models List all available models by index',
|
|
@@ -139,6 +141,8 @@ export default {
|
|
|
139
141
|
'示例:先发 /models,再发 /model 2 [推理等级ID]':
|
|
140
142
|
'Example: send /models first, then /model 2 [reasoning effort ID]',
|
|
141
143
|
'/presetlist 按序号列出可用 Agent Preset': '/presetlist List available Agent Presets by index',
|
|
144
|
+
'/presetlist 或 /presets 按序号列出可用 Agent Preset':
|
|
145
|
+
'/presetlist or /presets List available Agent Presets by index',
|
|
142
146
|
'/preset [序号或完整ID] 查看或设置当前机器人 Agent Preset':
|
|
143
147
|
'/preset [index or full ID] Show or set the Agent Preset of this bot',
|
|
144
148
|
'纯数字 ID:/preset id:<ID>': 'Numeric-only ID: /preset id:<ID>',
|
|
@@ -8,7 +8,7 @@ import { splitWorkspaceCommandMessage } from './workspace-command.mjs';
|
|
|
8
8
|
import { WORKSPACE_SESSION_STALE } from './workspace-session.mjs';
|
|
9
9
|
|
|
10
10
|
const PRESET_COMMAND = /^\/preset(?=$|\s)/iu;
|
|
11
|
-
const PRESET_LIST_COMMAND = /^\/presetlist(?=$|\s)/iu;
|
|
11
|
+
const PRESET_LIST_COMMAND = /^\/(?:presetlist|presets)(?=$|\s)/iu;
|
|
12
12
|
const PRESET_LIST_USAGE = '用法:/presetlist(不带参数)';
|
|
13
13
|
const PRESET_USAGE = [
|
|
14
14
|
'用法:',
|
|
@@ -257,7 +257,7 @@ export async function runPresetCommand(text, harness, state, key, options = {})
|
|
|
257
257
|
const requestOptions = rpcOptions(options.signal);
|
|
258
258
|
|
|
259
259
|
if (PRESET_LIST_COMMAND.test(command)) {
|
|
260
|
-
if (!/^\/presetlist[ \t]*$/iu.test(command)) return commandResult(t(PRESET_LIST_USAGE));
|
|
260
|
+
if (!/^\/(?:presetlist|presets)[ \t]*$/iu.test(command)) return commandResult(t(PRESET_LIST_USAGE));
|
|
261
261
|
try {
|
|
262
262
|
const current = await settings(harness, requestOptions);
|
|
263
263
|
saveSnapshot(state, key, current.agentPresetCatalog.items);
|
|
@@ -469,6 +469,11 @@ export class TextHarnessBridge {
|
|
|
469
469
|
});
|
|
470
470
|
}
|
|
471
471
|
|
|
472
|
+
sendProactiveText(target, text, { signal } = {}) {
|
|
473
|
+
signal?.throwIfAborted();
|
|
474
|
+
return this.#bot.sendText(target, text);
|
|
475
|
+
}
|
|
476
|
+
|
|
472
477
|
async #deliverArtifacts(target, replyTo, artifacts = [], baseReceipt) {
|
|
473
478
|
const delivery = await deliverOutboundArtifacts({
|
|
474
479
|
artifacts,
|
|
@@ -542,14 +547,14 @@ export class TextHarnessBridge {
|
|
|
542
547
|
t('/history [数量] 查看最近历史消息(默认 3 条,最多 5 条)'),
|
|
543
548
|
t('/workspace 工作区绝对路径 切换工作区'),
|
|
544
549
|
t('/workspacelist 列出工作区绝对路径'),
|
|
545
|
-
t('/sessionlist [工作区序号或绝对路径] 列出会话 ID 和标题'),
|
|
550
|
+
t('/sessionlist 或 /sessions [工作区序号或绝对路径] 列出会话 ID 和标题'),
|
|
546
551
|
t('/session Session ID 或当前工作区序号 将当前聊天绑定到指定会话'),
|
|
547
552
|
t('/models 按序号列出所有可用模型'),
|
|
548
553
|
t('/reasoninglist 或 /reasonings 按序号列出当前模型可用推理等级'),
|
|
549
554
|
t('/reasoning [序号、等级ID或 --default] 查看或切换当前推理等级'),
|
|
550
555
|
t('/model [序号或完整模型ID] [推理等级ID] 查看或切换当前会话模型'),
|
|
551
556
|
t('示例:先发 /models,再发 /model 2 [推理等级ID]'),
|
|
552
|
-
t('/presetlist 按序号列出可用 Agent Preset'),
|
|
557
|
+
t('/presetlist 或 /presets 按序号列出可用 Agent Preset'),
|
|
553
558
|
t('/preset [序号或完整ID] 查看或设置当前机器人 Agent Preset'),
|
|
554
559
|
t('纯数字 ID:/preset id:<ID>'),
|
|
555
560
|
t('/preset --default 跟随 Host 默认'),
|
|
@@ -227,6 +227,22 @@ export class TokenBotController {
|
|
|
227
227
|
});
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
async sendProactiveText(botId, target, text, options = {}) {
|
|
231
|
+
const config = this.#configStore.get(botId);
|
|
232
|
+
if (!config) throw new Error(`Unknown ${this.#descriptor.label} bot`);
|
|
233
|
+
return this.#withBotTransition(botId, async () => {
|
|
234
|
+
const runtime = this.#runtimes.get(botId);
|
|
235
|
+
if (!runtime?.status?.ready || typeof runtime.sendProactiveText !== 'function') {
|
|
236
|
+
const error = new Error(t('{label}机器人尚未连接', {
|
|
237
|
+
label: this.#descriptor.label,
|
|
238
|
+
}));
|
|
239
|
+
error.code = 'bot-not-connected';
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
return runtime.sendProactiveText(target, text, options);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
230
246
|
async deleteBot(botId) {
|
|
231
247
|
const config = this.#configStore.get(botId);
|
|
232
248
|
if (!config) throw new Error(`Unknown ${this.#descriptor.label} bot`);
|
|
@@ -6,7 +6,7 @@ import { WORKSPACE_SESSION_STALE } from './workspace-session.mjs';
|
|
|
6
6
|
|
|
7
7
|
const WORKSPACE_COMMAND = /^\/workspace(?:\s+([\s\S]+))?$/i;
|
|
8
8
|
const WORKSPACE_LIST_COMMAND = /^\/workspacelist(?:\s+([\s\S]+))?$/i;
|
|
9
|
-
const SESSION_LIST_COMMAND = /^\/sessionlist(?:\s+([\s\S]+))?$/i;
|
|
9
|
+
const SESSION_LIST_COMMAND = /^\/(?:sessionlist|sessions)(?:\s+([\s\S]+))?$/i;
|
|
10
10
|
const SESSION_BIND_PREFIX = /^\/session(?=$|\s)/i;
|
|
11
11
|
const SESSION_BIND_COMMAND = /^\/session[ \t]+([^\s]+)$/i;
|
|
12
12
|
const MAX_WORKSPACE_PATH_LENGTH = 4_096;
|
|
@@ -189,6 +189,20 @@ export class SlackController {
|
|
|
189
189
|
});
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
async sendProactiveText(botId, target, text, options = {}) {
|
|
193
|
+
const config = this.#configStore.get(botId);
|
|
194
|
+
if (!config) throw new Error('Unknown Slack bot');
|
|
195
|
+
return this.#withBotTransition(botId, async () => {
|
|
196
|
+
const runtime = this.#runtimes.get(botId);
|
|
197
|
+
if (!runtime?.status?.ready || typeof runtime.sendProactiveText !== 'function') {
|
|
198
|
+
const error = new Error(t('Slack机器人尚未连接'));
|
|
199
|
+
error.code = 'bot-not-connected';
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
return runtime.sendProactiveText(target, text, options);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
192
206
|
async deleteBot(botId) {
|
|
193
207
|
const config = this.#configStore.get(botId);
|
|
194
208
|
if (!config) throw new Error('Unknown Slack bot');
|
|
@@ -407,6 +407,30 @@ export class SlackRuntime {
|
|
|
407
407
|
return this.#bridge.sendConnectionTest(text);
|
|
408
408
|
}
|
|
409
409
|
|
|
410
|
+
async sendProactiveText(target, text, options = {}) {
|
|
411
|
+
if (!this.#status.ready || !this.#bridge) {
|
|
412
|
+
const error = new Error('Slack bot is not connected');
|
|
413
|
+
error.code = 'bot-not-connected';
|
|
414
|
+
throw error;
|
|
415
|
+
}
|
|
416
|
+
const channelId = typeof target?.route?.channelId === 'string'
|
|
417
|
+
? target.route.channelId.trim() : '';
|
|
418
|
+
const threadTs = typeof target?.route?.threadTs === 'string'
|
|
419
|
+
? target.route.threadTs.trim() : '';
|
|
420
|
+
if (!channelId
|
|
421
|
+
|| (target?.kind !== 'conversation' && target?.kind !== 'thread')
|
|
422
|
+
|| (target.kind === 'thread' && !threadTs)
|
|
423
|
+
|| (target.kind === 'conversation' && threadTs)) {
|
|
424
|
+
const error = new TypeError('Invalid Slack proactive delivery target');
|
|
425
|
+
error.code = 'invalid-target';
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
428
|
+
return this.#bridge.sendProactiveText({
|
|
429
|
+
channelId,
|
|
430
|
+
...(threadTs ? { threadTs } : {}),
|
|
431
|
+
}, text, options);
|
|
432
|
+
}
|
|
433
|
+
|
|
410
434
|
async start() {
|
|
411
435
|
if (this.#status.ready && this.#socket) return this.status;
|
|
412
436
|
if (this.#starting) return this.#starting;
|
|
@@ -22,10 +22,12 @@ export const TELEGRAM_COMMAND_MENU = Object.freeze([
|
|
|
22
22
|
{ command: 'workspace', description: '切换工作区' },
|
|
23
23
|
{ command: 'workspacelist', description: '列出工作区绝对路径' },
|
|
24
24
|
{ command: 'sessionlist', description: '列出会话 ID 和标题' },
|
|
25
|
+
{ command: 'sessions', description: '列出会话 ID 和标题' },
|
|
25
26
|
{ command: 'session', description: '将当前聊天绑定到指定会话' },
|
|
26
27
|
{ command: 'models', description: '按序号列出所有可用模型' },
|
|
27
28
|
{ command: 'model', description: '查看或切换当前会话模型' },
|
|
28
29
|
{ command: 'presetlist', description: '列出可用 Agent Preset' },
|
|
30
|
+
{ command: 'presets', description: '列出可用 Agent Preset' },
|
|
29
31
|
{ command: 'preset', description: '查看或设置新会话 Agent Preset' },
|
|
30
32
|
{ command: 'stop', description: '停止当前任务' },
|
|
31
33
|
{ command: 'steer', description: '纠偏当前任务' },
|
|
@@ -717,6 +719,30 @@ export class TelegramRuntime {
|
|
|
717
719
|
return this.#bridge.sendConnectionTest(text);
|
|
718
720
|
}
|
|
719
721
|
|
|
722
|
+
async sendProactiveText(target, text, options = {}) {
|
|
723
|
+
if (!this.#status.ready || !this.#bridge) {
|
|
724
|
+
const error = new Error('Telegram bot is not connected');
|
|
725
|
+
error.code = 'bot-not-connected';
|
|
726
|
+
throw error;
|
|
727
|
+
}
|
|
728
|
+
const chatIdText = typeof target?.route?.chatId === 'string'
|
|
729
|
+
? target.route.chatId.trim() : '';
|
|
730
|
+
const chatId = Number(chatIdText);
|
|
731
|
+
const messageThreadId = target?.route?.messageThreadId;
|
|
732
|
+
if (!/^-?\d+$/.test(chatIdText) || !Number.isSafeInteger(chatId)
|
|
733
|
+
|| (target?.kind !== 'chat' && target?.kind !== 'topic')
|
|
734
|
+
|| (target.kind === 'topic' && (!Number.isSafeInteger(messageThreadId) || messageThreadId <= 0))
|
|
735
|
+
|| (target.kind === 'chat' && messageThreadId !== undefined)) {
|
|
736
|
+
const error = new TypeError('Invalid Telegram proactive delivery target');
|
|
737
|
+
error.code = 'invalid-target';
|
|
738
|
+
throw error;
|
|
739
|
+
}
|
|
740
|
+
return this.#bridge.sendProactiveText({
|
|
741
|
+
chatId,
|
|
742
|
+
...(target.kind === 'topic' ? { messageThreadId } : {}),
|
|
743
|
+
}, text, options);
|
|
744
|
+
}
|
|
745
|
+
|
|
720
746
|
async start() {
|
|
721
747
|
if (this.#status.ready && this.#pollTask) return this.status;
|
|
722
748
|
if (this.#starting) return this.#starting;
|