@wu529778790/open-im 1.11.9 → 1.11.10-beta.1

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.
@@ -64,6 +64,11 @@ export interface Config {
64
64
  shortRetrySeconds: number;
65
65
  autoResumePrompt: string;
66
66
  };
67
+ keepalive?: {
68
+ enabled: boolean;
69
+ intervalHours: number;
70
+ target: AiCommand;
71
+ };
67
72
  platforms: {
68
73
  telegram?: {
69
74
  enabled: boolean;
@@ -211,6 +216,11 @@ export interface FileToolOpenCode {
211
216
  /** 模型(格式: providerID/modelID) */
212
217
  model?: string;
213
218
  }
219
+ export interface KeepaliveConfig {
220
+ enabled?: boolean;
221
+ intervalHours?: number;
222
+ target?: AiCommand;
223
+ }
214
224
  export interface FileConfig {
215
225
  telegramBotToken?: string;
216
226
  feishuAppId?: string;
@@ -241,4 +251,5 @@ export interface FileConfig {
241
251
  url?: string;
242
252
  token?: string;
243
253
  };
254
+ keepalive?: KeepaliveConfig;
244
255
  }
@@ -421,6 +421,29 @@ export async function startWebConfigServer(options) {
421
421
  }
422
422
  return;
423
423
  }
424
+ // --- 保活设置 ---
425
+ if (request.method === "GET" && requestUrl.pathname === "/api/keepalive/config") {
426
+ try {
427
+ const { getKeepaliveConfig } = await import("./shared/keepalive.js");
428
+ json(response, 200, getKeepaliveConfig(), request);
429
+ }
430
+ catch (error) {
431
+ json(response, 500, { error: error instanceof Error ? error.message : String(error) }, request);
432
+ }
433
+ return;
434
+ }
435
+ if (request.method === "POST" && requestUrl.pathname === "/api/keepalive/config") {
436
+ try {
437
+ const body = await readJson(request);
438
+ const { saveKeepaliveConfig } = await import("./shared/keepalive.js");
439
+ saveKeepaliveConfig(body);
440
+ json(response, 200, { message: "保活配置已保存" }, request);
441
+ }
442
+ catch (error) {
443
+ json(response, 500, { error: error instanceof Error ? error.message : String(error) }, request);
444
+ }
445
+ return;
446
+ }
424
447
  if (request.method === "GET" && requestUrl.pathname === "/api/health") {
425
448
  const file = loadFileConfig();
426
449
  const platforms = getHealthPlatformSnapshot(file);
package/dist/config.js CHANGED
@@ -519,6 +519,13 @@ export function loadConfig() {
519
519
  apiToken: clawbotApiToken,
520
520
  },
521
521
  };
522
+ // 保活配置
523
+ const keepaliveFile = file.keepalive ?? {};
524
+ const keepalive = {
525
+ enabled: keepaliveFile.enabled ?? true,
526
+ intervalHours: keepaliveFile.intervalHours ?? 5,
527
+ target: keepaliveFile.target ?? 'claude',
528
+ };
522
529
  return {
523
530
  enabledPlatforms,
524
531
  telegramBotToken: telegramBotToken ?? '',
@@ -556,6 +563,7 @@ export function loadConfig() {
556
563
  enabled: telemetryEnabled,
557
564
  },
558
565
  autopilot,
566
+ keepalive,
559
567
  platforms,
560
568
  };
561
569
  }
package/dist/index.js CHANGED
@@ -29,6 +29,7 @@ import { setupClawbotHandlers } from "./clawbot/event-handler.js";
29
29
  import { sendTextReply as sendClawbotTextReply } from "./clawbot/message-sender.js";
30
30
  import { initClawBotSender } from "./clawbot/message-sender.js";
31
31
  import { initAdapters, cleanupAdapters } from "./adapters/registry.js";
32
+ import { startKeepalive, stopKeepalive } from "./shared/keepalive.js";
32
33
  import { SessionManager } from "./session/session-manager.js";
33
34
  import { loadActiveChats, getActiveChatId, flushActiveChats, } from "./shared/active-chats.js";
34
35
  import { destroyAllLiveChildren } from "./shared/process-kill.js";
@@ -271,6 +272,7 @@ export async function main() {
271
272
  }
272
273
  loadActiveChats();
273
274
  initAdapters(config);
275
+ startKeepalive();
274
276
  // 尽早启动 shutdown 并写入 port 文件,使 open-im start 的 8s 就绪超时能通过(平台初始化可能较慢)
275
277
  let shutdownServer = null;
276
278
  await new Promise((resolve, reject) => {
@@ -386,6 +388,7 @@ export async function main() {
386
388
  }
387
389
  sessionManager.destroy();
388
390
  cleanupAdapters();
391
+ stopKeepalive();
389
392
  flushActiveChats();
390
393
  await flushSentry();
391
394
  await closeLogger();
@@ -0,0 +1,16 @@
1
+ /** 启动保活定时器 */
2
+ export declare function startKeepalive(): void;
3
+ /** 停止保活定时器 */
4
+ export declare function stopKeepalive(): void;
5
+ /** 从 API 读取保活配置(返回给前端) */
6
+ export declare function getKeepaliveConfig(): {
7
+ enabled: boolean;
8
+ intervalHours: number;
9
+ target: string;
10
+ };
11
+ /** 从 API 保存保活配置 */
12
+ export declare function saveKeepaliveConfig(cfg: {
13
+ enabled: boolean;
14
+ intervalHours: number;
15
+ target: string;
16
+ }): void;
@@ -0,0 +1,109 @@
1
+ import { loadFileConfig, saveFileConfig } from "../config/file-io.js";
2
+ import { loadConfig } from "../config.js";
3
+ import { createLogger } from "../logger.js";
4
+ import { getAdapter } from "../adapters/registry.js";
5
+ import { ClaudeSDKAdapter } from "../adapters/claude-sdk-adapter.js";
6
+ import { CodexAdapter } from "../adapters/codex-adapter.js";
7
+ import { CodeBuddyAdapter } from "../adapters/codebuddy-adapter.js";
8
+ import { OpenCodeAdapter } from "../adapters/opencode-adapter.js";
9
+ const log = createLogger("Keepalive");
10
+ const PING_PROMPT = "在吗";
11
+ const PING_TIMEOUT_MS = 60_000;
12
+ let timer = null;
13
+ /** 按需创建临时 adapter(保活的目标 AI 可能未在 enabledPlatforms 中,registry 不会注册) */
14
+ function resolveAdapter(target) {
15
+ const cached = getAdapter(target);
16
+ if (cached)
17
+ return cached;
18
+ const config = loadConfig();
19
+ switch (target) {
20
+ case "claude": return new ClaudeSDKAdapter();
21
+ case "codex": return new CodexAdapter(config.codexCliPath);
22
+ case "codebuddy": return new CodeBuddyAdapter(config.codebuddyCliPath);
23
+ case "opencode": return new OpenCodeAdapter();
24
+ default:
25
+ log.warn(`Unknown AI command: ${target}`);
26
+ return null;
27
+ }
28
+ }
29
+ /** 发送一句保活消息,等待完成或超时 */
30
+ async function ping(target, workDir) {
31
+ const adapter = resolveAdapter(target);
32
+ if (!adapter)
33
+ return false;
34
+ log.info(`Keepalive ping → ${target}`);
35
+ return await new Promise((resolve) => {
36
+ let settled = false;
37
+ const settle = (ok, reason) => {
38
+ if (settled)
39
+ return;
40
+ settled = true;
41
+ clearTimeout(timeoutHandle);
42
+ log.info(`Keepalive ${target} ${ok ? "OK" : "FAIL"}: ${reason}`);
43
+ resolve(ok);
44
+ };
45
+ const handle = adapter.run(PING_PROMPT, undefined, workDir, {
46
+ onText: () => { },
47
+ onComplete: (r) => settle(r.success, `${r.numTurns} turn(s), ${r.durationMs}ms`),
48
+ onError: (e) => settle(false, `error: ${e}`),
49
+ }, { skipPermissions: true, skipAutoResume: true });
50
+ const timeoutHandle = setTimeout(() => {
51
+ try {
52
+ handle.abort();
53
+ }
54
+ catch { }
55
+ settle(false, `timeout after ${PING_TIMEOUT_MS}ms`);
56
+ }, PING_TIMEOUT_MS);
57
+ });
58
+ }
59
+ /** 启动保活定时器 */
60
+ export function startKeepalive() {
61
+ stopKeepalive();
62
+ const file = loadFileConfig();
63
+ const ka = file.keepalive ?? {};
64
+ const enabled = ka.enabled ?? true;
65
+ const intervalHours = ka.intervalHours ?? 5;
66
+ const target = ka.target ?? "claude";
67
+ if (!enabled) {
68
+ log.info("Keepalive 已禁用,跳过");
69
+ return;
70
+ }
71
+ const config = loadConfig();
72
+ const workDir = config.claudeWorkDir;
73
+ const intervalMs = intervalHours * 60 * 60 * 1000;
74
+ log.info(`Keepalive 已启动,每 ${intervalHours} 小时 ping ${target}`);
75
+ // 不立即执行(启动时不打扰),等到第一个间隔
76
+ timer = setInterval(() => {
77
+ ping(target, workDir).catch((e) => log.warn(`Keepalive ping unhandled: ${e}`));
78
+ }, intervalMs);
79
+ timer.unref?.();
80
+ }
81
+ /** 停止保活定时器 */
82
+ export function stopKeepalive() {
83
+ if (timer) {
84
+ clearInterval(timer);
85
+ timer = null;
86
+ log.info("Keepalive 已停止");
87
+ }
88
+ }
89
+ /** 从 API 读取保活配置(返回给前端) */
90
+ export function getKeepaliveConfig() {
91
+ const file = loadFileConfig();
92
+ const ka = file.keepalive ?? {};
93
+ return {
94
+ enabled: ka.enabled ?? true,
95
+ intervalHours: ka.intervalHours ?? 5,
96
+ target: ka.target ?? "claude",
97
+ };
98
+ }
99
+ /** 从 API 保存保活配置 */
100
+ export function saveKeepaliveConfig(cfg) {
101
+ const file = loadFileConfig();
102
+ file.keepalive = {
103
+ enabled: cfg.enabled,
104
+ intervalHours: cfg.intervalHours,
105
+ target: cfg.target,
106
+ };
107
+ saveFileConfig(file);
108
+ startKeepalive();
109
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.11.9",
3
+ "version": "1.11.10-beta.1",
4
4
  "description": "Your AI coding assistant, in every chat app. Multi-platform IM bridge for Claude Code, Codex, and CodeBuddy.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",