@xmanrui/dsh-im 0.4.0 → 0.6.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.md +36 -0
- package/lib/client.js +709 -285
- package/lib/index.js +114 -110
- package/package.json +1 -1
- package/plugin-src/client/channels/dingtalk/api.js +2 -0
- package/plugin-src/client/channels/dingtalk/index.js +67 -14
- package/plugin-src/client/channels/feishu/api.js +2 -0
- package/plugin-src/client/channels/feishu/index.js +70 -22
- package/plugin-src/client/channels/qq/api.js +2 -0
- package/plugin-src/client/channels/qq/index.js +45 -8
- package/plugin-src/client/channels/shared/token-api.js +2 -0
- package/plugin-src/client/channels/shared/token-channel.js +34 -8
- package/plugin-src/client/channels/wecom/api.js +2 -0
- package/plugin-src/client/channels/wecom/index.js +45 -8
- package/plugin-src/client/channels/weixin/api.js +2 -0
- package/plugin-src/client/channels/weixin/index.js +68 -8
- package/plugin-src/client/channels/whatsapp/api.js +2 -0
- package/plugin-src/client/channels/whatsapp/index.js +27 -5
- package/plugin-src/client/i18n.js +13 -0
- package/plugin-src/client/styles.js +13 -0
- package/plugin-src/client/workspace-editor.js +96 -0
- package/plugin-src/client/workspace-snapshot-fence.js +28 -0
- package/plugin-src/host/channels/dingtalk/production.mjs +34 -11
- package/plugin-src/host/channels/dingtalk/rpc.mjs +17 -2
- package/plugin-src/host/channels/feishu/production.mjs +42 -5
- package/plugin-src/host/channels/feishu/rpc.mjs +17 -2
- package/plugin-src/host/channels/qq/production.mjs +48 -22
- package/plugin-src/host/channels/qq/rpc.mjs +16 -2
- package/plugin-src/host/channels/shared/production.mjs +48 -22
- package/plugin-src/host/channels/shared/rpc.mjs +15 -0
- package/plugin-src/host/channels/shared/workspace-rpc.mjs +25 -0
- package/plugin-src/host/channels/slack/production.mjs +48 -23
- package/plugin-src/host/channels/slack/rpc.mjs +16 -0
- package/plugin-src/host/channels/wecom/production.mjs +49 -23
- package/plugin-src/host/channels/wecom/rpc.mjs +16 -2
- package/plugin-src/host/channels/weixin/production.mjs +31 -11
- package/plugin-src/host/channels/weixin/rpc.mjs +21 -2
- package/plugin-src/host/channels/whatsapp/production.mjs +49 -23
- package/plugin-src/host/channels/whatsapp/rpc.mjs +16 -2
- package/src/channels/dingtalk/dingtalk-bridge.mjs +25 -11
- package/src/channels/dingtalk/dingtalk-controller.mjs +6 -1
- package/src/channels/dingtalk/harness-client.mjs +17 -3
- package/src/channels/dingtalk/state-store.mjs +5 -0
- package/src/channels/discord/discord-api.mjs +1 -1
- package/src/channels/feishu/bridge.mjs +39 -16
- package/src/channels/feishu/harness-client.mjs +19 -5
- package/src/channels/feishu/state-store.mjs +5 -0
- package/src/channels/qq/qq-bridge.mjs +28 -15
- package/src/channels/qq/qq-controller.mjs +8 -1
- package/src/channels/qq/state-store.mjs +5 -0
- package/src/channels/shared/bot-workspace-store.mjs +618 -0
- package/src/channels/shared/conversation-state-store.mjs +5 -0
- package/src/channels/shared/text-harness-bridge.mjs +26 -13
- package/src/channels/shared/workspace-command.mjs +115 -0
- package/src/channels/shared/workspace-session.mjs +44 -0
- package/src/channels/wecom/state-store.mjs +5 -0
- package/src/channels/wecom/wecom-bridge.mjs +25 -13
- package/src/channels/wecom/wecom-controller.mjs +8 -1
- package/src/channels/weixin/harness-client.mjs +19 -5
- package/src/channels/weixin/state-store.mjs +5 -0
- package/src/channels/weixin/weixin-api.mjs +1 -1
- package/src/channels/weixin/weixin-bridge.mjs +19 -6
- package/src/channels/weixin/weixin-controller.mjs +6 -1
- package/src/channels/whatsapp/whatsapp-controller.mjs +8 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
const WORKSPACE_COMMAND = /^\/workspace(?:\s+([\s\S]+))?$/i;
|
|
5
|
+
const WORKSPACE_LIST_COMMAND = /^\/workspacelist(?:\s+([\s\S]+))?$/i;
|
|
6
|
+
const MAX_WORKSPACE_PATH_LENGTH = 4_096;
|
|
7
|
+
const MAX_COMMAND_MESSAGE_LENGTH = 1_800;
|
|
8
|
+
|
|
9
|
+
function commandResult(message, messages = [message]) {
|
|
10
|
+
return { handled: true, message, messages };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizedWorkspacePath(value) {
|
|
14
|
+
if (typeof value !== 'string' || value.length > MAX_WORKSPACE_PATH_LENGTH
|
|
15
|
+
|| !isAbsolute(value) || /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u.test(value)) return null;
|
|
16
|
+
return resolve(value);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function existingWorkspacePaths(values) {
|
|
20
|
+
const unique = [...new Set(values.map(normalizedWorkspacePath).filter(Boolean))];
|
|
21
|
+
const checked = await Promise.all(unique.map(async (workspace) => {
|
|
22
|
+
try {
|
|
23
|
+
return (await stat(workspace)).isDirectory() ? workspace : null;
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}));
|
|
28
|
+
return checked.filter(Boolean);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function splitWorkspaceCommandMessage(message) {
|
|
32
|
+
const messages = [];
|
|
33
|
+
let offset = 0;
|
|
34
|
+
while (offset < message.length) {
|
|
35
|
+
let end = Math.min(offset + MAX_COMMAND_MESSAGE_LENGTH, message.length);
|
|
36
|
+
if (end < message.length) {
|
|
37
|
+
const lineBreak = message.lastIndexOf('\n', end - 1);
|
|
38
|
+
if (lineBreak >= offset) {
|
|
39
|
+
end = lineBreak + 1;
|
|
40
|
+
} else {
|
|
41
|
+
const trailing = message.charCodeAt(end - 1);
|
|
42
|
+
const leading = message.charCodeAt(end);
|
|
43
|
+
if (trailing >= 0xd800 && trailing <= 0xdbff
|
|
44
|
+
&& leading >= 0xdc00 && leading <= 0xdfff) end -= 1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
messages.push(message.slice(offset, end));
|
|
48
|
+
offset = end;
|
|
49
|
+
}
|
|
50
|
+
return messages;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function runWorkspaceListCommand(match, harness) {
|
|
54
|
+
if (match[1]?.trim()) return commandResult('用法:/workspacelist');
|
|
55
|
+
if (typeof harness?.listWorkspaces !== 'function') {
|
|
56
|
+
return commandResult('当前机器人暂不支持列出工作区。');
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const listed = await harness.listWorkspaces();
|
|
60
|
+
const current = typeof harness.currentWorkspace === 'function'
|
|
61
|
+
? normalizedWorkspacePath(harness.currentWorkspace())
|
|
62
|
+
: null;
|
|
63
|
+
const paths = await existingWorkspacePaths([
|
|
64
|
+
...(current ? [current] : []),
|
|
65
|
+
...(Array.isArray(listed) ? listed : []),
|
|
66
|
+
]);
|
|
67
|
+
harness.assertWorkspaceScope?.();
|
|
68
|
+
if (paths.length === 0) {
|
|
69
|
+
return commandResult('当前 Harness Host 上没有仍然存在的已登记工作区。');
|
|
70
|
+
}
|
|
71
|
+
const lines = [
|
|
72
|
+
`当前 Harness Host 上存在的工作区(${paths.length}):`,
|
|
73
|
+
...paths.map((workspace, index) => (
|
|
74
|
+
`${index + 1}. ${workspace}${workspace === current ? '(当前)' : ''}`
|
|
75
|
+
)),
|
|
76
|
+
'',
|
|
77
|
+
'切换用法:/workspace 工作区绝对路径',
|
|
78
|
+
];
|
|
79
|
+
const message = lines.join('\n');
|
|
80
|
+
return commandResult(message, splitWorkspaceCommandMessage(message));
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (error?.code === 'workspace-bot-not-found') {
|
|
83
|
+
return commandResult('机器人正在移除或已重新接入,无法列出原会话的工作区。');
|
|
84
|
+
}
|
|
85
|
+
return commandResult('暂时无法获取工作区列表,请稍后重试。');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function runWorkspaceCommand(text, harness) {
|
|
90
|
+
if (typeof text !== 'string') return null;
|
|
91
|
+
const command = text.trim();
|
|
92
|
+
const listMatch = WORKSPACE_LIST_COMMAND.exec(command);
|
|
93
|
+
if (listMatch) return runWorkspaceListCommand(listMatch, harness);
|
|
94
|
+
const match = WORKSPACE_COMMAND.exec(command);
|
|
95
|
+
if (!match) return null;
|
|
96
|
+
const workspace = match[1]?.trim();
|
|
97
|
+
if (!workspace) {
|
|
98
|
+
return commandResult('用法:/workspace 工作区绝对路径');
|
|
99
|
+
}
|
|
100
|
+
if (typeof harness?.switchWorkspace !== 'function') {
|
|
101
|
+
return commandResult('当前机器人暂不支持切换工作区。');
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const current = await harness.switchWorkspace(workspace);
|
|
105
|
+
return commandResult(`工作区已切换为:${current}`);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (['workspace-not-absolute', 'workspace-not-found', 'workspace-not-directory'].includes(error?.code)) {
|
|
108
|
+
return commandResult(`${error.message}\n用法:/workspace 工作区绝对路径`);
|
|
109
|
+
}
|
|
110
|
+
if (error?.code === 'workspace-bot-not-found') {
|
|
111
|
+
return commandResult('机器人正在移除或已重新接入,无法切换原会话的工作区。');
|
|
112
|
+
}
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const WORKSPACE_SESSION_STALE = 'workspace-session-stale';
|
|
2
|
+
|
|
3
|
+
async function sessionExists(harness, sessionId, options) {
|
|
4
|
+
return options === undefined
|
|
5
|
+
? harness.sessionExists(sessionId)
|
|
6
|
+
: harness.sessionExists(sessionId, options);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async function createSession(harness, options) {
|
|
10
|
+
return options === undefined
|
|
11
|
+
? harness.createSession()
|
|
12
|
+
: harness.createSession(options);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resolve, persist, and ask through a session that belongs to the bot's
|
|
17
|
+
* current workspace. A concurrent workspace switch invalidates the scoped
|
|
18
|
+
* session and retries before any prompt is sent to the stale session.
|
|
19
|
+
*/
|
|
20
|
+
export async function askInWorkspaceSession({
|
|
21
|
+
harness,
|
|
22
|
+
state,
|
|
23
|
+
key,
|
|
24
|
+
text,
|
|
25
|
+
createOptions,
|
|
26
|
+
existsOptions,
|
|
27
|
+
askOptions,
|
|
28
|
+
}) {
|
|
29
|
+
while (true) {
|
|
30
|
+
let sessionId = state.sessionFor(key);
|
|
31
|
+
if (!sessionId || !(await sessionExists(harness, sessionId, existsOptions))) {
|
|
32
|
+
sessionId = await createSession(harness, createOptions);
|
|
33
|
+
if (await state.setSession(key, sessionId) === false) continue;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
return {
|
|
37
|
+
sessionId,
|
|
38
|
+
answer: await harness.ask(sessionId, text, askOptions),
|
|
39
|
+
};
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (error?.code !== WORKSPACE_SESSION_STALE) throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { generateReqId } from '@wecom/aibot-node-sdk';
|
|
2
|
+
import { runWorkspaceCommand } from '../shared/workspace-command.mjs';
|
|
3
|
+
import { askInWorkspaceSession } from '../shared/workspace-session.mjs';
|
|
2
4
|
|
|
3
5
|
const HELP_TEXT = [
|
|
4
6
|
'企业微信机器人已连接 DeepSeek Harness。',
|
|
5
7
|
'',
|
|
6
8
|
'直接发送文字即可继续当前会话。',
|
|
7
9
|
'/new 开启一个全新会话',
|
|
10
|
+
'/workspace 工作区绝对路径 切换工作区',
|
|
11
|
+
'/workspacelist 列出工作区绝对路径',
|
|
8
12
|
'/status 检查连接状态',
|
|
9
13
|
'/help 显示本帮助',
|
|
10
14
|
].join('\n');
|
|
@@ -182,11 +186,13 @@ export class WecomHarnessBridge {
|
|
|
182
186
|
await this.#state.markSeen(messageId);
|
|
183
187
|
return;
|
|
184
188
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
189
|
+
const workspaceCommand = await runWorkspaceCommand(text, this.#harness);
|
|
190
|
+
if (workspaceCommand) {
|
|
191
|
+
for (const reply of workspaceCommand.messages ?? [workspaceCommand.message]) {
|
|
192
|
+
await this.#sendImmediate(frame, chatId, reply);
|
|
193
|
+
}
|
|
194
|
+
await this.#state.markSeen(messageId);
|
|
195
|
+
return;
|
|
190
196
|
}
|
|
191
197
|
|
|
192
198
|
streamId = this.#generateReqId('stream');
|
|
@@ -197,14 +203,20 @@ export class WecomHarnessBridge {
|
|
|
197
203
|
this.#logger.warn?.('[dsh-im:wecom] unable to start a stream; using an active reply:', error);
|
|
198
204
|
}
|
|
199
205
|
|
|
200
|
-
const answer = await
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
:
|
|
206
|
+
const { answer } = await askInWorkspaceSession({
|
|
207
|
+
harness: this.#harness,
|
|
208
|
+
state: this.#state,
|
|
209
|
+
key,
|
|
210
|
+
text,
|
|
211
|
+
askOptions: {
|
|
212
|
+
timeoutMs: this.#replyTimeoutMs,
|
|
213
|
+
onUpdate: streamStarted && typeof this.#client.replyStreamNonBlocking === 'function'
|
|
214
|
+
? async (update) => {
|
|
215
|
+
const progress = splitUtf8(progressText(update))[0];
|
|
216
|
+
if (progress) await this.#client.replyStreamNonBlocking(frame, streamId, progress, false);
|
|
217
|
+
}
|
|
218
|
+
: undefined,
|
|
219
|
+
},
|
|
208
220
|
});
|
|
209
221
|
|
|
210
222
|
const chunks = splitUtf8(answer || '任务已完成,但没有生成可显示的文本。');
|
|
@@ -400,7 +400,14 @@ export class WecomController {
|
|
|
400
400
|
if (record.controller.signal.aborted) {
|
|
401
401
|
await this.#stopRuntime(identity.botId);
|
|
402
402
|
if (previousConfig) await this.#configStore.save(previousConfig).catch(() => undefined);
|
|
403
|
-
else
|
|
403
|
+
else {
|
|
404
|
+
const removed = await this.#configStore.remove(identity.botId).catch(() => null);
|
|
405
|
+
if (removed) {
|
|
406
|
+
await this.#deleteState({ botId: identity.botId, config }).catch((cleanupError) => {
|
|
407
|
+
this.#logger.warn?.('[dsh-im:wecom] cancelled bot state cleanup failed:', cleanupError);
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
404
411
|
await this.#restoreCredential(identity.secretRef, previousSecret);
|
|
405
412
|
throw error;
|
|
406
413
|
}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { isAbsolute } from 'node:path';
|
|
3
4
|
|
|
4
5
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
5
6
|
|
|
7
|
+
function workspacePaths(value) {
|
|
8
|
+
if (!Array.isArray(value?.items)) return [];
|
|
9
|
+
return value.items.flatMap((item) => (
|
|
10
|
+
typeof item?.path === 'string' && isAbsolute(item.path) ? [item.path] : []
|
|
11
|
+
));
|
|
12
|
+
}
|
|
13
|
+
|
|
6
14
|
function assistantMessageText(event) {
|
|
7
15
|
return (event?.data?.message?.content ?? [])
|
|
8
16
|
.filter((part) => part.type === 'text' && typeof part.text === 'string')
|
|
@@ -187,17 +195,23 @@ export class HarnessClient {
|
|
|
187
195
|
throw new Error(`Harness did not become ready: ${lastError?.message ?? 'timeout'}`);
|
|
188
196
|
}
|
|
189
197
|
|
|
190
|
-
async
|
|
198
|
+
async listWorkspaces(options = {}) {
|
|
199
|
+
await this.ensureRunning();
|
|
200
|
+
return workspacePaths(await this.rpc('workspace.list', {}, 30_000, options));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async workspaceId(options = {}) {
|
|
204
|
+
const workspace = options.workspace ?? this.#workspace;
|
|
191
205
|
const { items } = await this.rpc('workspace.list', {});
|
|
192
|
-
const existing = items.find((item) => item.path ===
|
|
206
|
+
const existing = items.find((item) => item.path === workspace);
|
|
193
207
|
if (existing) return existing.workspaceId;
|
|
194
|
-
const created = await this.rpc('workspace.create', { path:
|
|
208
|
+
const created = await this.rpc('workspace.create', { path: workspace });
|
|
195
209
|
return created.workspace.workspaceId;
|
|
196
210
|
}
|
|
197
211
|
|
|
198
|
-
async createSession() {
|
|
212
|
+
async createSession(options = {}) {
|
|
199
213
|
await this.ensureRunning();
|
|
200
|
-
const workspaceId = await this.workspaceId();
|
|
214
|
+
const workspaceId = await this.workspaceId(options);
|
|
201
215
|
const created = await this.rpc('session.create', {
|
|
202
216
|
workspaceId,
|
|
203
217
|
agentPreset: this.#agentPreset,
|
|
@@ -3,12 +3,16 @@ import {
|
|
|
3
3
|
splitWeixinText,
|
|
4
4
|
weixinMessageId,
|
|
5
5
|
} from './weixin-api.mjs';
|
|
6
|
+
import { runWorkspaceCommand } from '../shared/workspace-command.mjs';
|
|
7
|
+
import { askInWorkspaceSession } from '../shared/workspace-session.mjs';
|
|
6
8
|
|
|
7
9
|
const HELP_TEXT = [
|
|
8
10
|
'微信已连接 DeepSeek Harness。',
|
|
9
11
|
'',
|
|
10
12
|
'直接发送文字或带文字识别结果的语音即可继续当前会话。',
|
|
11
13
|
'/new 开启一个全新会话',
|
|
14
|
+
'/workspace 工作区绝对路径 切换工作区',
|
|
15
|
+
'/workspacelist 列出工作区绝对路径',
|
|
12
16
|
'/status 检查连接状态',
|
|
13
17
|
'/help 显示本帮助',
|
|
14
18
|
].join('\n');
|
|
@@ -133,14 +137,23 @@ export class WeixinHarnessBridge {
|
|
|
133
137
|
await this.#state.markSeen(messageId);
|
|
134
138
|
return;
|
|
135
139
|
}
|
|
140
|
+
const workspaceCommand = await runWorkspaceCommand(text, this.#harness);
|
|
141
|
+
if (workspaceCommand) {
|
|
142
|
+
for (const reply of workspaceCommand.messages ?? [workspaceCommand.message]) {
|
|
143
|
+
await this.#send(sender, reply, contextToken, runId);
|
|
144
|
+
}
|
|
145
|
+
await this.#state.markSeen(messageId);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
136
148
|
|
|
137
149
|
const key = conversationKey(sender);
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
150
|
+
const { answer } = await askInWorkspaceSession({
|
|
151
|
+
harness: this.#harness,
|
|
152
|
+
state: this.#state,
|
|
153
|
+
key,
|
|
154
|
+
text,
|
|
155
|
+
askOptions: { timeoutMs: this.#replyTimeoutMs },
|
|
156
|
+
});
|
|
144
157
|
await this.#send(sender, answer, contextToken, runId);
|
|
145
158
|
await this.#state.markSeen(messageId);
|
|
146
159
|
this.#status.messagesReplied += 1;
|
|
@@ -468,7 +468,12 @@ export class WeixinController {
|
|
|
468
468
|
await this.#stopRuntime(identity.botId);
|
|
469
469
|
if (previousConfig) await this.#configStore.save(previousConfig).catch(() => undefined);
|
|
470
470
|
else if (this.#configStore.get(identity.botId)) {
|
|
471
|
-
await this.#configStore.remove(identity.botId).catch(() =>
|
|
471
|
+
const removed = await this.#configStore.remove(identity.botId).catch(() => null);
|
|
472
|
+
if (removed) {
|
|
473
|
+
await this.#deleteState({ botId: identity.botId, config }).catch((cleanupError) => {
|
|
474
|
+
this.#logger.warn?.('[dsh-weixin] failed to clean up cancelled bot state:', cleanupError);
|
|
475
|
+
});
|
|
476
|
+
}
|
|
472
477
|
}
|
|
473
478
|
await this.#restoreCredential(identity.tokenRef, previousToken);
|
|
474
479
|
if (previousConfig && previousToken?.value) {
|
|
@@ -311,7 +311,14 @@ export class WhatsappController {
|
|
|
311
311
|
if (record.controller.signal.aborted || this.#closed || error?.name === 'AbortError') {
|
|
312
312
|
await this.#stopRuntime(botId);
|
|
313
313
|
if (previous) await this.#configStore.save(previous).catch(() => undefined);
|
|
314
|
-
else
|
|
314
|
+
else {
|
|
315
|
+
const removed = await this.#configStore.remove(botId).catch(() => null);
|
|
316
|
+
if (removed) {
|
|
317
|
+
await this.#deleteState({ botId, config }).catch((cleanupError) => {
|
|
318
|
+
this.#logger.warn?.('[dsh-im:whatsapp] cancelled bot state cleanup failed:', cleanupError);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
}
|
|
315
322
|
await this.#deleteAuth(record.authDirectory).catch(() => undefined);
|
|
316
323
|
if (previous) await this.#startRuntime(previous).catch(() => undefined);
|
|
317
324
|
record.state = 'cancelled';
|