@wu529778790/open-im 1.11.1-beta.0 → 1.11.1-beta.2
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/dist/clawbot/client.js +46 -1
- package/dist/index.js +22 -17
- package/package.json +1 -1
package/dist/clawbot/client.js
CHANGED
|
@@ -21,6 +21,7 @@ let channelState = 'disconnected';
|
|
|
21
21
|
let messageHandler = null;
|
|
22
22
|
let stateChangeHandler = null;
|
|
23
23
|
let reconnectTimer = null;
|
|
24
|
+
let watchdogTimer = null;
|
|
24
25
|
let reconnectAttempt = 0;
|
|
25
26
|
let fatal = false;
|
|
26
27
|
let stopped = false;
|
|
@@ -28,6 +29,13 @@ let apiUrl = 'https://ilinkai.weixin.qq.com';
|
|
|
28
29
|
let apiToken = '';
|
|
29
30
|
/** Opaque cursor for getupdates pagination (replaces numeric offset) */
|
|
30
31
|
let getUpdatesBuf = '';
|
|
32
|
+
/** Timestamp of last successful poll response (for watchdog) */
|
|
33
|
+
let lastResponseAt = 0;
|
|
34
|
+
/** Per-request timeout for long-polling (ms) */
|
|
35
|
+
const POLL_REQUEST_TIMEOUT_MS = 3 * 60 * 1000; // 3 minutes
|
|
36
|
+
/** Watchdog interval: force reconnect if no response for this long (ms) */
|
|
37
|
+
const WATCHDOG_INTERVAL_MS = 60_000; // check every 60s
|
|
38
|
+
const WATCHDOG_STALE_MS = 5 * 60 * 1000; // 5 minutes without response = stale
|
|
31
39
|
export function getChannelState() {
|
|
32
40
|
return channelState;
|
|
33
41
|
}
|
|
@@ -58,14 +66,21 @@ function startPolling() {
|
|
|
58
66
|
return;
|
|
59
67
|
pollController = new AbortController();
|
|
60
68
|
const signal = pollController.signal;
|
|
69
|
+
// Start watchdog to detect stale connections (e.g. Mac sleep / network drop)
|
|
70
|
+
startWatchdog();
|
|
61
71
|
(async () => {
|
|
62
72
|
log.info('ClawBot long-polling started');
|
|
63
73
|
while (!stopped && !signal.aborted) {
|
|
64
74
|
try {
|
|
75
|
+
// Combine the poll controller signal with a per-request timeout
|
|
76
|
+
const timeoutSignal = AbortSignal.timeout(POLL_REQUEST_TIMEOUT_MS);
|
|
77
|
+
const combinedSignal = AbortSignal.any([signal, timeoutSignal]);
|
|
65
78
|
const res = await postApi('/ilink/bot/getupdates', {
|
|
66
79
|
get_updates_buf: getUpdatesBuf,
|
|
67
80
|
base_info: BASE_INFO,
|
|
68
|
-
},
|
|
81
|
+
}, combinedSignal);
|
|
82
|
+
// Record successful response time for watchdog
|
|
83
|
+
lastResponseAt = Date.now();
|
|
69
84
|
if (signal.aborted)
|
|
70
85
|
break;
|
|
71
86
|
if (!res.ok) {
|
|
@@ -168,6 +183,35 @@ function updateState(state) {
|
|
|
168
183
|
stateChangeHandler?.(state);
|
|
169
184
|
log.debug(`ClawBot state: ${state}`);
|
|
170
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Watchdog: periodically check if the poll loop is alive.
|
|
188
|
+
* After Mac sleep or network drop, the fetch may hang without throwing.
|
|
189
|
+
* If no successful response for WATCHDOG_STALE_MS, force a reconnect.
|
|
190
|
+
*/
|
|
191
|
+
function startWatchdog() {
|
|
192
|
+
stopWatchdog();
|
|
193
|
+
lastResponseAt = Date.now();
|
|
194
|
+
watchdogTimer = setInterval(() => {
|
|
195
|
+
if (stopped)
|
|
196
|
+
return;
|
|
197
|
+
const elapsed = Date.now() - lastResponseAt;
|
|
198
|
+
if (elapsed > WATCHDOG_STALE_MS) {
|
|
199
|
+
log.warn(`ClawBot watchdog: no response for ${Math.round(elapsed / 1000)}s, forcing reconnect`);
|
|
200
|
+
if (pollController) {
|
|
201
|
+
pollController.abort();
|
|
202
|
+
pollController = null;
|
|
203
|
+
}
|
|
204
|
+
updateState('error');
|
|
205
|
+
scheduleReconnect();
|
|
206
|
+
}
|
|
207
|
+
}, WATCHDOG_INTERVAL_MS);
|
|
208
|
+
}
|
|
209
|
+
function stopWatchdog() {
|
|
210
|
+
if (watchdogTimer) {
|
|
211
|
+
clearInterval(watchdogTimer);
|
|
212
|
+
watchdogTimer = null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
171
215
|
/**
|
|
172
216
|
* Extract text content from an iLink message's item_list.
|
|
173
217
|
* Returns the first text item found, or a placeholder for media types.
|
|
@@ -272,6 +316,7 @@ export function stopClawbot() {
|
|
|
272
316
|
clearTimeout(reconnectTimer);
|
|
273
317
|
reconnectTimer = null;
|
|
274
318
|
}
|
|
319
|
+
stopWatchdog();
|
|
275
320
|
messageHandler = null;
|
|
276
321
|
// Don't clear context_token here — it's persisted for startup notifications
|
|
277
322
|
updateState('disconnected');
|
package/dist/index.js
CHANGED
|
@@ -35,7 +35,7 @@ import { destroyAllLiveChildren } from "./shared/process-kill.js";
|
|
|
35
35
|
import { initLogger, createLogger, closeLogger, emitStructuredEvent, shutdownLoggerTelemetry, } from "./logger.js";
|
|
36
36
|
import { APP_HOME, SHUTDOWN_PORT } from "./constants.js";
|
|
37
37
|
import { createRequire } from "node:module";
|
|
38
|
-
import { escapePathForMarkdown } from "./shared/utils.js";
|
|
38
|
+
import { escapePathForMarkdown, getAIToolDisplayName } from "./shared/utils.js";
|
|
39
39
|
import { applyOpenImGitCoauthorToProcessEnv } from "./shared/git-coauthor.js";
|
|
40
40
|
import { emitInterruptedTerminals } from "./shared/task-cleanup.js";
|
|
41
41
|
const require = createRequire(import.meta.url);
|
|
@@ -127,6 +127,15 @@ async function sendLifecycleNotification(platform, message) {
|
|
|
127
127
|
await mod.sendNotification(chatId, message);
|
|
128
128
|
log.info(`[${platform}] Lifecycle notification sent successfully`);
|
|
129
129
|
}
|
|
130
|
+
const PLATFORM_DISPLAY_NAMES = {
|
|
131
|
+
telegram: 'Telegram',
|
|
132
|
+
feishu: '飞书',
|
|
133
|
+
wework: '企业微信',
|
|
134
|
+
dingtalk: '钉钉',
|
|
135
|
+
qq: 'QQ',
|
|
136
|
+
workbuddy: '微信',
|
|
137
|
+
clawbot: '微信 (ClawBot)',
|
|
138
|
+
};
|
|
130
139
|
function buildStartupMessage(platform, appVersion, aiCommand, defaultWorkDir, sessionManager) {
|
|
131
140
|
let sessionDir;
|
|
132
141
|
// Telegram 私聊、企业微信当前实现里,活跃 chatId 可直接对应到 session userId。
|
|
@@ -136,27 +145,23 @@ function buildStartupMessage(platform, appVersion, aiCommand, defaultWorkDir, se
|
|
|
136
145
|
sessionDir = sessionManager.getWorkDir(activeChatId);
|
|
137
146
|
}
|
|
138
147
|
}
|
|
139
|
-
const
|
|
140
|
-
|
|
148
|
+
const platformName = PLATFORM_DISPLAY_NAMES[platform] ?? platform;
|
|
149
|
+
const toolName = getAIToolDisplayName(aiCommand);
|
|
150
|
+
const dir = sessionDir ? escapePathForMarkdown(sessionDir) : '发送 `/pwd` 查看';
|
|
151
|
+
return [
|
|
152
|
+
`✅ open-im v${appVersion} 已就绪`,
|
|
141
153
|
"",
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
];
|
|
146
|
-
if (sessionDir) {
|
|
147
|
-
lines.push(`- 会话目录: ${escapePathForMarkdown(sessionDir)}`);
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
lines.push(`- 会话目录: 发送 \`/pwd\` 查看`);
|
|
151
|
-
}
|
|
152
|
-
return lines.join("\n");
|
|
154
|
+
`📱 平台: ${platformName}`,
|
|
155
|
+
`🤖 AI: ${toolName}`,
|
|
156
|
+
`📁 目录: ${dir}`,
|
|
157
|
+
].join("\n");
|
|
153
158
|
}
|
|
154
159
|
function buildShutdownMessage(uptimeMinutes) {
|
|
160
|
+
const uptime = uptimeMinutes < 1 ? '< 1' : String(uptimeMinutes);
|
|
155
161
|
return [
|
|
156
|
-
|
|
162
|
+
`🛑 open-im 正在关闭`,
|
|
157
163
|
"",
|
|
158
|
-
|
|
159
|
-
`- 运行时长: \`${uptimeMinutes} 分钟\``,
|
|
164
|
+
`⏱️ 运行时长: ${uptime} 分钟`,
|
|
160
165
|
].join("\n");
|
|
161
166
|
}
|
|
162
167
|
export async function main() {
|