@wu529778790/open-im 1.11.2-beta.1 → 1.11.2-beta.11

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 CHANGED
@@ -1,91 +1,91 @@
1
1
  # open-im
2
2
 
3
- **English** · [中文](./README.zh-CN.md)
3
+ [English](./README.md) · **中文**
4
4
 
5
- > Your AI coding assistant, in every chat app.
5
+ > 你的 AI 编程助手,在每个聊天 App 里。
6
6
 
7
- open-im bridges Claude Code, Codex, and CodeBuddy to Telegram, Feishu, WeCom, DingTalk, QQ, WeChat (WorkBuddy), and WeChat (ClawBot). Send a message from your phone, get code written on your server.
7
+ open-im Claude CodeCodexCodeBuddy 接入 Telegram、飞书、企业微信、钉钉、QQ、微信(WorkBuddy)、微信(ClawBot)。手机发条消息,服务器上就写好代码。
8
8
 
9
- ## Architecture
9
+ ## 架构
10
10
 
11
- ![Open-IM Architecture](./diagram/architecture/open-im-architecture.svg)
11
+ ![Open-IM 架构图](./diagram/architecture/open-im-architecture.svg)
12
12
 
13
- ## Why
13
+ ## 为什么用 open-im
14
14
 
15
- - **Work from anywhere** trigger Claude Code from your phone while commuting, waiting in line, or on the couch
16
- - **Seamless handoff** — open-im shares sessions with the Claude Code CLI; pick up on your computer exactly where you left off on your phone
17
- - **Full power, simple interface** stream responses, manage sessions, switch models — all through chat commands
18
- - **One bridge, many platforms** same bot works on Telegram, Feishu, DingTalk, WeChat, and more
15
+ - **随时随地**通勤、排队、躺沙发上,手机发消息就能让 Claude Code 干活
16
+ - **无缝接力** — open-im Claude Code CLI 共享 session,在手机上聊到一半,电脑上接着来
17
+ - **完整能力,简单界面**流式输出、会话管理、模型切换,全靠聊天命令搞定
18
+ - **一个桥接,多个平台**同一个 bot 支持 Telegram、飞书、钉钉、微信等
19
19
 
20
- ## Features
20
+ ## 功能
21
21
 
22
- ### Chat commands
22
+ ### 聊天命令
23
23
 
24
- | Command | Description |
24
+ | 命令 | 说明 |
25
25
  | --- | --- |
26
- | `/help` | Show all commands |
27
- | `/new` | Start a fresh AI session |
28
- | `/sessions` | Browse session history with previews |
29
- | `/resume [N]` | Resume a session (no arg = most recent) |
30
- | `/history [N]` | View conversation messages in a session |
31
- | `/delete <N>` | Delete a session |
32
- | `/rename <title>` | Rename the current session |
33
- | `/fork [N]` | Fork a session (create a branch) |
34
- | `/models` | List available AI models |
35
- | `/context` | Show context window usage |
36
- | `/status` | Show AI tool, account, and session info |
37
- | `/cd <path>` / `/pwd` | Switch work directory (auto-resumes that dir's session) |
38
- | `/allow` / `/y`, `/deny` / `/n` | Respond to permission prompts |
39
-
40
- ### Session continuity
41
-
42
- open-im and Claude Code CLI share the same session storage. In the same directory, you can seamlessly switch between phone and computer.
26
+ | `/help` | 显示所有命令 |
27
+ | `/new` | 开启新 AI 会话 |
28
+ | `/sessions` | 浏览历史会话(含摘要预览) |
29
+ | `/resume [序号]` | 恢复会话(无参数恢复最近一条) |
30
+ | `/history [序号]` | 查看会话对话记录 |
31
+ | `/delete <序号>` | 删除历史会话 |
32
+ | `/rename <标题>` | 重命名当前会话 |
33
+ | `/fork [序号]` | 分支会话(创建副本) |
34
+ | `/models` | 查看可用 AI 模型 |
35
+ | `/context` | 查看上下文窗口占用 |
36
+ | `/status` | 显示 AI 工具、账号、会话信息 |
37
+ | `/cd <路径>` / `/pwd` | 切换工作目录(自动恢复该目录的历史会话) |
38
+ | `/allow` / `/y`、`/deny` / `/n` | 权限确认 |
39
+
40
+ ### 会话接力
41
+
42
+ open-im Claude Code CLI 共享同一份 session 存储。同一个目录下,手机和电脑可以无缝切换。
43
43
 
44
- ```
45
- # On computer
46
- cd /my-project && claude # work as usual, then Ctrl+C
44
+ ```bash
45
+ # 电脑端
46
+ cd /my-project && claude # 正常工作,退出时 Ctrl+C
47
47
 
48
- # On phone (via IM)
49
- "help me fix the login bug" # open-im auto-resumes the same session
48
+ # 手机端(IM 消息)
49
+ "帮我修复登录 bug" # open-im 自动接续同一个 session
50
50
 
51
- # Back on computer
52
- claude -c # continues the phone conversation
51
+ # 回到电脑端
52
+ claude -c # 接上手机端的对话
53
53
  ```
54
54
 
55
- > Only one side can be active at a time. Exit the CLI before sending messages from the phone, and vice versa.
55
+ > 同一时刻只能有一端活跃。从手机发消息前先退出 CLI,反之亦然。
56
56
 
57
- ### Platform support
57
+ ### 平台支持
58
58
 
59
- Seven IM platforms, three AI backends, per-platform override:
59
+ 七个 IM 平台,三种 AI 后端,可按平台覆盖:
60
60
 
61
- | Platform | Streaming | Media | Notes |
62
- | --- | --- | --- | --- |
63
- | Telegram | Yes | Images | Full bot support |
64
- | Feishu | Yes | Images | Streaming card |
65
- | WeCom | Yes | Images | Streaming card |
66
- | DingTalk | Partial | Images | Fallback to text |
67
- | QQ | Yes | Images | |
68
- | WorkBuddy | Yes | Images | WeChat-based |
69
- | ClawBot | Yes | Images | WeChat-based |
61
+ | 平台 | 流式输出 | 备注 |
62
+ | --- | --- | --- |
63
+ | Telegram | 支持 | 完整 bot 支持 |
64
+ | 飞书 | 支持 | 流式卡片 |
65
+ | 企业微信 | 支持 | 流式卡片 |
66
+ | 钉钉 | 部分 | 回退到纯文本 |
67
+ | QQ | 支持 | |
68
+ | WorkBuddy | 支持 | 微信生态 |
69
+ | ClawBot | 支持 | 微信生态 |
70
70
 
71
- Set `platforms.<name>.aiCommand` (`claude` / `codex` / `codebuddy`) per channel. Default: `claude`.
71
+ 在每个平台上设置 `platforms.<name>.aiCommand`(`claude` / `codex` / `codebuddy`),默认 `claude`。
72
72
 
73
- ### Web dashboard
73
+ ### Web 控制台
74
74
 
75
- `open-im start` serves a built-in SPA and API at **`http://127.0.0.1:39282`** (configurable via `OPEN_IM_WEB_PORT`). For LAN access: `export OPEN_IM_WEB_HOST=0.0.0.0`.
75
+ `open-im start` **`http://127.0.0.1:39282`** 提供内置页面与 API(通过 `OPEN_IM_WEB_PORT` 配置端口)。局域网访问:`export OPEN_IM_WEB_HOST=0.0.0.0`。
76
76
 
77
- ## Quick start
77
+ ## 快速开始
78
78
 
79
79
  ```bash
80
- npx @wu529778790/open-im init # interactive setup
81
- npx @wu529778790/open-im start # run the bridge
80
+ npx @wu529778790/open-im init # 交互式配置
81
+ npx @wu529778790/open-im start # 启动桥接
82
82
  ```
83
83
 
84
- Or install globally: `npm install -g @wu529778790/open-im` then `open-im start`.
84
+ 或全局安装:`npm install -g @wu529778790/open-im` 后执行 `open-im start`。
85
85
 
86
- Config file: **`~/.open-im/config.json`**
86
+ 配置文件:**`~/.open-im/config.json`**
87
87
 
88
- ### Minimal config
88
+ ### 最小配置
89
89
 
90
90
  ```json
91
91
  {
@@ -98,11 +98,11 @@ Config file: **`~/.open-im/config.json`**
98
98
  }
99
99
  ```
100
100
 
101
- Run `open-im init` for a full template with all platforms.
101
+ 完整模板请用 `open-im init`。
102
102
 
103
- ### Claude (Agent SDK)
103
+ ### ClaudeAgent SDK
104
104
 
105
- No local `claude` binary required. Supports third-party / compatible APIs:
105
+ 无需本地 `claude` 可执行文件。支持第三方兼容接口:
106
106
 
107
107
  ```json
108
108
  {
@@ -118,36 +118,36 @@ No local `claude` binary required. Supports third-party / compatible APIs:
118
118
  }
119
119
  ```
120
120
 
121
- ### CLI reference
121
+ ### CLI 命令
122
122
 
123
- | Command | Description |
123
+ | 命令 | 说明 |
124
124
  | --- | --- |
125
- | `open-im init` | Interactive setup (does not start the bridge) |
126
- | `open-im start` | Run the bridge in the background |
127
- | `open-im stop` | Stop the background bridge |
128
- | `open-im restart` | Stop then start |
129
- | `open-im dashboard` | Web config server only (no bridge) |
125
+ | `open-im init` | 交互配置(不启动桥接) |
126
+ | `open-im start` | 后台运行桥接 |
127
+ | `open-im stop` | 停止后台服务 |
128
+ | `open-im restart` | 重启 |
129
+ | `open-im dashboard` | Web 配置服务(不启动桥接) |
130
130
 
131
- ### Environment variables
131
+ ### 环境变量
132
132
 
133
- **`ANTHROPIC_*`** (shell or `tools.claude.env`), **`TELEGRAM_BOT_TOKEN`**, **`OPEN_IM_WEB_PORT`**, **`OPEN_IM_WEB_HOST`**, plus platform-specific `*_APP_ID`, `*_SECRET`, `WORKBUDDY_*`, etc.
133
+ **`ANTHROPIC_*`**(shell `tools.claude.env`)、**`TELEGRAM_BOT_TOKEN`**、**`OPEN_IM_WEB_PORT`**、**`OPEN_IM_WEB_HOST`**,以及各平台的 `*_APP_ID`、`*_SECRET`、`WORKBUDDY_*` 等。
134
134
 
135
- ### Git co-authors
135
+ ### Git 共同作者
136
136
 
137
- `Co-authored-by` is appended by default on AI-driven commits. Disable: set **`OPEN_IM_GIT_COAUTHOR=0`** in the environment and restart the bridge.
137
+ 默认在 AI 发起的提交里追加 `Co-authored-by`。关闭:设置环境变量 **`OPEN_IM_GIT_COAUTHOR=0`** 并重启桥接。
138
138
 
139
- ### Privacy
139
+ ### 隐私
140
140
 
141
- **Anonymous** usage information may be collected to improve reliability (no chat or prompt content). To disable: **`OPEN_IM_TELEMETRY=false`** or `"telemetry": { "enabled": false }` in `config.json`.
141
+ 为改进稳定性,可能记录**匿名**运行信息(不含聊天或 prompt 内容)。若需关闭:环境变量 **`OPEN_IM_TELEMETRY=false`**,或 `config.json` `"telemetry": { "enabled": false }`。
142
142
 
143
- ## Platform setup & troubleshooting
143
+ ## 平台配置与故障排除
144
144
 
145
- See **[docs/platforms.md](./docs/platforms.md)** for detailed per-platform configuration, credential setup, and troubleshooting.
145
+ 详见 **[docs/platforms.zh-CN.md](./docs/platforms.zh-CN.md)**。
146
146
 
147
- ## Requirements
147
+ ## 环境要求
148
148
 
149
149
  - Node.js >= 20
150
- - At least one IM platform configured + credentials for your AI tool
150
+ - 至少配置一个 IM 平台 + 所选 AI 的凭证
151
151
 
152
152
  ## License
153
153
 
package/dist/config.js CHANGED
@@ -8,7 +8,7 @@ import { accessSync, constants } from 'node:fs';
8
8
  import { execFileSync } from 'node:child_process';
9
9
  import { join, isAbsolute } from 'node:path';
10
10
  import { createLogger } from './logger.js';
11
- import { APP_HOME, DEFAULT_TELEMETRY_INGEST_URL, DEFAULT_TELEMETRY_INGEST_TOKEN, } from './constants.js';
11
+ import { APP_HOME, } from './constants.js';
12
12
  const log = createLogger('config');
13
13
  /** 分渠道 AI 工具:未配置时使用 AI_COMMAND / 旧版根级 aiCommand / 默认 claude */
14
14
  function resolveFilePlatformAi(file, platform) {
@@ -401,48 +401,8 @@ export function loadConfig() {
401
401
  else {
402
402
  telemetryEnabled = true;
403
403
  }
404
- let telemetryUrl;
405
- const telemetryUrlRaw = process.env.OPEN_IM_TELEMETRY_URL ?? file.telemetry?.url;
406
- if (telemetryUrlRaw && typeof telemetryUrlRaw === 'string' && telemetryUrlRaw.trim()) {
407
- try {
408
- const u = new URL(telemetryUrlRaw.trim());
409
- if (u.protocol !== 'https:') {
410
- log.warn('OPEN_IM_TELEMETRY_URL / telemetry.url 必须为 https,已忽略上传地址');
411
- }
412
- else {
413
- telemetryUrl = u.href;
414
- }
415
- }
416
- catch {
417
- log.warn('无效的 OPEN_IM_TELEMETRY_URL / telemetry.url,已忽略上传地址');
418
- }
419
- }
420
- if (!telemetryUrl && telemetryEnabled && DEFAULT_TELEMETRY_INGEST_URL.trim()) {
421
- try {
422
- const u = new URL(DEFAULT_TELEMETRY_INGEST_URL.trim());
423
- if (u.protocol === 'https:') {
424
- telemetryUrl = u.href;
425
- }
426
- }
427
- catch {
428
- /* ignore */
429
- }
430
- }
431
- let telemetryToken;
432
- if (process.env.OPEN_IM_TELEMETRY_TOKEN !== undefined) {
433
- const t = process.env.OPEN_IM_TELEMETRY_TOKEN.trim();
434
- telemetryToken = t.length > 0 ? t : undefined;
435
- }
436
- else if (file.telemetry?.token !== undefined) {
437
- const t = String(file.telemetry.token).trim();
438
- telemetryToken = t.length > 0 ? t : undefined;
439
- }
440
- else {
441
- telemetryToken = DEFAULT_TELEMETRY_INGEST_TOKEN.trim() || undefined;
442
- }
443
- if (telemetryEnabled && !telemetryUrl) {
444
- log.warn('遥测已开启但未配置有效的 HTTPS 采集 URL:仅写入本地 events-*.jsonl;可设置 OPEN_IM_TELEMETRY_URL 或在 constants 中配置 DEFAULT_TELEMETRY_INGEST_URL。');
445
- }
404
+ // Telemetry: local event logging only (no cloud upload)
405
+ // Users can disable via OPEN_IM_TELEMETRY=false or telemetry.enabled=false
446
406
  const platforms = {
447
407
  telegram: telegramEnabled
448
408
  ? {
@@ -576,8 +536,6 @@ export function loadConfig() {
576
536
  logLevel,
577
537
  telemetry: {
578
538
  enabled: telemetryEnabled,
579
- url: telemetryUrl,
580
- token: telemetryToken,
581
539
  },
582
540
  platforms,
583
541
  };
@@ -1,14 +1,4 @@
1
1
  export declare const APP_HOME: string;
2
- /**
3
- * 未设置 `OPEN_IM_TELEMETRY_URL` / `telemetry.url` 时使用的默认采集端(HTTPS)。
4
- * 工具发行方可写死自有域名;留空字符串则仅在上游显式配置 URL 时上传。
5
- */
6
- export declare const DEFAULT_TELEMETRY_INGEST_URL = "https://open-im.shenzjd.com/v1/ingest";
7
- /**
8
- * 未设置 `OPEN_IM_TELEMETRY_TOKEN` / `telemetry.token` 时使用的默认 Bearer。
9
- * 须与 Cloudflare Worker 的 `wrangler secret put TELEMETRY_INGEST_TOKEN` 值完全一致。
10
- */
11
- export declare const DEFAULT_TELEMETRY_INGEST_TOKEN = "610457d55274f20f2d031d38cdfd86c8498016e75160f60cdbce0dab93a78240";
12
2
  /** 优雅关闭 HTTP 端口(stop 命令通过此端口触发 shutdown) */
13
3
  export declare const SHUTDOWN_PORT = 39281;
14
4
  /** 本地 Web 配置 API 固定端口(完整 UI 由 web/dist 随包提供,见 getPublicWebDashboardUrl) */
package/dist/constants.js CHANGED
@@ -1,16 +1,6 @@
1
1
  import { join } from "node:path";
2
2
  import { homedir, tmpdir } from "node:os";
3
3
  export const APP_HOME = join(homedir(), ".open-im");
4
- /**
5
- * 未设置 `OPEN_IM_TELEMETRY_URL` / `telemetry.url` 时使用的默认采集端(HTTPS)。
6
- * 工具发行方可写死自有域名;留空字符串则仅在上游显式配置 URL 时上传。
7
- */
8
- export const DEFAULT_TELEMETRY_INGEST_URL = "https://open-im.shenzjd.com/v1/ingest";
9
- /**
10
- * 未设置 `OPEN_IM_TELEMETRY_TOKEN` / `telemetry.token` 时使用的默认 Bearer。
11
- * 须与 Cloudflare Worker 的 `wrangler secret put TELEMETRY_INGEST_TOKEN` 值完全一致。
12
- */
13
- export const DEFAULT_TELEMETRY_INGEST_TOKEN = "610457d55274f20f2d031d38cdfd86c8498016e75160f60cdbce0dab93a78240";
14
4
  /** 优雅关闭 HTTP 端口(stop 命令通过此端口触发 shutdown) */
15
5
  export const SHUTDOWN_PORT = 39281;
16
6
  /** 本地 Web 配置 API 固定端口(完整 UI 由 web/dist 随包提供,见 getPublicWebDashboardUrl) */
package/dist/index.js CHANGED
@@ -32,7 +32,7 @@ import { initAdapters, cleanupAdapters } from "./adapters/registry.js";
32
32
  import { SessionManager } from "./session/session-manager.js";
33
33
  import { loadActiveChats, getActiveChatId, flushActiveChats, } from "./shared/active-chats.js";
34
34
  import { destroyAllLiveChildren } from "./shared/process-kill.js";
35
- import { initLogger, createLogger, closeLogger, emitStructuredEvent, shutdownLoggerTelemetry, } from "./logger.js";
35
+ import { initLogger, createLogger, closeLogger, } from "./logger.js";
36
36
  import { APP_HOME, SHUTDOWN_PORT } from "./constants.js";
37
37
  import { createRequire } from "node:module";
38
38
  import { escapePathForMarkdown, getAIToolDisplayName } from "./shared/utils.js";
@@ -267,10 +267,6 @@ export async function main() {
267
267
  }
268
268
  log.info("Service is running. Press Ctrl+C to stop.");
269
269
  log.info(`Successfully initialized platforms: ${successfulPlatforms.join(", ")}`);
270
- emitStructuredEvent("Main", "service.platform.init", {
271
- platforms: successfulPlatforms,
272
- version: APP_VERSION,
273
- });
274
270
  // Send notification only to successfully initialized platforms
275
271
  for (const platform of successfulPlatforms) {
276
272
  const startupMsg = buildStartupMessage(platform, APP_VERSION, resolvePlatformAiCommand(config, platform), startupCwd, sessionManager);
@@ -328,7 +324,6 @@ export async function main() {
328
324
  sessionManager.destroy();
329
325
  cleanupAdapters();
330
326
  flushActiveChats();
331
- await shutdownLoggerTelemetry();
332
327
  await flushSentry();
333
328
  await closeLogger();
334
329
  process.exit(0);
@@ -381,8 +376,7 @@ export async function main() {
381
376
  }
382
377
  }
383
378
  }
384
- void shutdownLoggerTelemetry()
385
- .then(() => closeLogger())
379
+ void closeLogger()
386
380
  .finally(() => process.exit(1));
387
381
  });
388
382
  }
@@ -391,8 +385,7 @@ const isEntry = process.argv[1]?.replace(/\\/g, "/").endsWith("/index.js") ||
391
385
  if (isEntry) {
392
386
  main().catch((err) => {
393
387
  log.error("Fatal error:", err);
394
- void shutdownLoggerTelemetry()
395
- .then(() => closeLogger())
388
+ void closeLogger()
396
389
  .finally(() => process.exit(1));
397
390
  });
398
391
  }
package/dist/logger.d.ts CHANGED
@@ -21,14 +21,11 @@ export declare function createLogger(tag: string): {
21
21
  warn: (msg: string, ...args: unknown[]) => void;
22
22
  error: (msg: string, ...args: unknown[]) => void;
23
23
  debug: (msg: string, ...args: unknown[]) => void;
24
- infoEvent: (event: string, data?: Record<string, unknown>, msg?: string) => void;
25
24
  };
26
25
  /**
27
26
  * Audit log — records user interactions for debugging and compliance.
28
27
  * Always enabled, writes to audit.log.
29
28
  */
30
29
  export declare function auditLog(platform: string, userId: string, action: string, detail?: Record<string, unknown>): void;
31
- export declare function emitStructuredEvent(tag: string, event: string, data?: Record<string, unknown>, level?: LogLevel, msg?: string): void;
32
- export declare function shutdownLoggerTelemetry(): Promise<void>;
33
30
  export declare function closeLogger(): Promise<void>;
34
31
  export {};
package/dist/logger.js CHANGED
@@ -3,20 +3,13 @@ import { join } from 'node:path';
3
3
  import { finished } from 'node:stream/promises';
4
4
  import { sanitize } from './sanitize.js';
5
5
  import { APP_HOME } from './constants.js';
6
- import { sanitizeTelemetryData } from './telemetry/telemetry-sanitize.js';
7
- import { enqueueTelemetryLine, getTelemetryUploadStats, initTelemetryUpload, shutdownTelemetryUpload, } from './telemetry/telemetry-upload.js';
8
6
  const DEFAULT_LOG_DIR = join(APP_HOME, 'logs');
9
7
  const MAX_LOG_FILES = 10;
10
8
  const LOG_LEVELS = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 };
11
9
  let logDir = DEFAULT_LOG_DIR;
12
10
  let minLevel = LOG_LEVELS.DEBUG;
13
11
  let logStream;
14
- let eventsStream;
15
12
  let auditStream;
16
- let telemetryEnabled = false;
17
- let telemetryStatsTimer = null;
18
- let lastTelemetryStatsSignature = '';
19
- const TELEMETRY_STATS_INTERVAL_MS = 5 * 60_000;
20
13
  function pad(n) {
21
14
  return String(n).padStart(2, '0');
22
15
  }
@@ -84,41 +77,6 @@ export function initLogger(dirOrOpts, level, telemetry) {
84
77
  auditStream = undefined;
85
78
  }
86
79
  auditStream = createWriteStream(join(logDir, 'audit.log'), { flags: 'a' });
87
- telemetryEnabled = !!tel?.enabled;
88
- if (telemetryStatsTimer) {
89
- clearInterval(telemetryStatsTimer);
90
- telemetryStatsTimer = null;
91
- }
92
- lastTelemetryStatsSignature = '';
93
- if (eventsStream) {
94
- eventsStream.end();
95
- eventsStream = undefined;
96
- }
97
- if (telemetryEnabled) {
98
- rotateOldJsonl();
99
- eventsStream = createWriteStream(join(logDir, getEventsFileName()), { flags: 'a' });
100
- initTelemetryUpload({
101
- enabled: true,
102
- url: tel?.url,
103
- token: tel?.token,
104
- });
105
- telemetryStatsTimer = setInterval(() => {
106
- emitTelemetryUploadStats(false);
107
- }, TELEMETRY_STATS_INTERVAL_MS);
108
- }
109
- else {
110
- initTelemetryUpload({ enabled: false });
111
- }
112
- }
113
- function emitTelemetryUploadStats(force) {
114
- if (!telemetryEnabled)
115
- return;
116
- const stats = getTelemetryUploadStats();
117
- const signature = JSON.stringify(stats);
118
- if (!force && signature === lastTelemetryStatsSignature)
119
- return;
120
- lastTelemetryStatsSignature = signature;
121
- emitStructuredEvent('Telemetry', 'telemetry.upload.stats', stats);
122
80
  }
123
81
  function write(level, tag, msg, ...args) {
124
82
  if (LOG_LEVELS[level] < minLevel)
@@ -139,7 +97,6 @@ export function createLogger(tag) {
139
97
  warn: (msg, ...args) => write('WARN', tag, msg, ...args),
140
98
  error: (msg, ...args) => write('ERROR', tag, msg, ...args),
141
99
  debug: (msg, ...args) => write('DEBUG', tag, msg, ...args),
142
- infoEvent: (event, data, msg) => emitStructuredEvent(tag, event, data, 'INFO', msg),
143
100
  };
144
101
  }
145
102
  /**
@@ -156,37 +113,13 @@ export function auditLog(platform, userId, action, detail) {
156
113
  };
157
114
  auditStream?.write(JSON.stringify(entry) + '\n');
158
115
  }
159
- export function emitStructuredEvent(tag, event, data, level = 'INFO', msg = '') {
160
- if (!telemetryEnabled)
161
- return;
162
- const payload = {
163
- v: 1,
164
- ts: new Date().toISOString(),
165
- level,
166
- tag,
167
- event,
168
- msg,
169
- data: sanitizeTelemetryData(data),
170
- };
171
- const line = `${JSON.stringify(payload)}\n`;
172
- eventsStream?.write(line);
173
- enqueueTelemetryLine(line);
174
- }
175
- export async function shutdownLoggerTelemetry() {
176
- emitTelemetryUploadStats(true);
177
- if (telemetryStatsTimer) {
178
- clearInterval(telemetryStatsTimer);
179
- telemetryStatsTimer = null;
180
- }
181
- await shutdownTelemetryUpload();
182
- }
183
116
  export async function closeLogger() {
184
- if (eventsStream) {
185
- const es = eventsStream;
186
- eventsStream = undefined;
187
- es.end();
117
+ if (auditStream) {
118
+ const as = auditStream;
119
+ auditStream = undefined;
120
+ as.end();
188
121
  try {
189
- await finished(es);
122
+ await finished(as);
190
123
  }
191
124
  catch {
192
125
  /* ignore */
@@ -4,9 +4,7 @@
4
4
  import { resolvePlatformAiCommand } from '../config.js';
5
5
  import { captureError } from './sentry.js';
6
6
  import { formatToolStats, formatToolCallNotification, getContextWarning, getAIToolDisplayName, toReplyPlainText, } from './utils.js';
7
- import { createLogger, emitStructuredEvent } from '../logger.js';
8
- import { hashUserId } from '../telemetry/hash-user.js';
9
- import { sanitize } from '../sanitize.js';
7
+ import { createLogger } from '../logger.js';
10
8
  const log = createLogger('AITask');
11
9
  function isUsageLimitError(error) {
12
10
  return /usage limit/i.test(error) || /try again at\s+\d{1,2}:\d{2}\s*(AM|PM)/i.test(error);
@@ -154,12 +152,6 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
154
152
  const aiCommand = resolvePlatformAiCommand(config, ctx.platform);
155
153
  const startRun = () => {
156
154
  log.info(`[AITask] Starting: userId=${ctx.userId}, initialSessionId=${currentSessionId ?? 'new'}, prompt="${prompt.slice(0, 50)}..."`);
157
- emitStructuredEvent('AITask', 'ai.task.start', {
158
- platform: ctx.platform,
159
- taskKey: hashUserId(ctx.taskKey),
160
- userKey: hashUserId(ctx.userId),
161
- toolId: aiCommand,
162
- });
163
155
  activeHandle = toolAdapter.run(prompt, currentSessionId, ctx.workDir, {
164
156
  onSessionId: (id) => {
165
157
  log.info(`[AITask] SessionId callback: old=${currentSessionId ?? 'none'}, new=${id}, aiCommand=${aiCommand}, userId=${ctx.userId}`);
@@ -218,17 +210,6 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
218
210
  log.info(`[AITask] onComplete fired: settled=${settled}, success=${result.success}, platform=${ctx.platform}, taskKey=${ctx.taskKey}`);
219
211
  if (settled)
220
212
  return;
221
- emitStructuredEvent('AITask', 'ai.task.complete', {
222
- platform: ctx.platform,
223
- taskKey: hashUserId(ctx.taskKey),
224
- userKey: hashUserId(ctx.userId),
225
- toolId: aiCommand,
226
- durationMs: result.durationMs,
227
- success: result.success,
228
- numTurns: result.numTurns,
229
- model: result.model,
230
- toolStats: result.toolStats,
231
- });
232
213
  settled = true;
233
214
  if (pendingUpdate) {
234
215
  clearTimeout(pendingUpdate);
@@ -282,15 +263,6 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
282
263
  }
283
264
  settled = true;
284
265
  log.error(`Task error for user ${ctx.userId}: ${error}`);
285
- emitStructuredEvent('AITask', 'ai.task.error', {
286
- platform: ctx.platform,
287
- taskKey: hashUserId(ctx.taskKey),
288
- userKey: hashUserId(ctx.userId),
289
- toolId: aiCommand,
290
- durationMs: Date.now() - taskState.startedAt,
291
- errorSnippet: sanitize(String(error).slice(0, 400)),
292
- errorType: classifyErrorType(String(error)),
293
- });
294
266
  if (isUsageLimitError(error)) {
295
267
  // Usage limit errors: keep session for all tools (user can retry later)
296
268
  log.warn(`Keeping ${aiCommand} session for user ${ctx.userId} after usage limit error`);
@@ -330,15 +302,6 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
330
302
  handle: {
331
303
  abort: () => {
332
304
  if (!settled) {
333
- emitStructuredEvent('AITask', 'ai.task.error', {
334
- platform: ctx.platform,
335
- taskKey: hashUserId(ctx.taskKey),
336
- userKey: hashUserId(ctx.userId),
337
- toolId: aiCommand,
338
- durationMs: Date.now() - taskState.startedAt,
339
- errorSnippet: 'aborted',
340
- errorType: 'aborted',
341
- });
342
305
  // 用户取消(/new、/resume、队列超时、stale 清理):把「思考中…」占位卡片编辑为终态,
343
306
  // 避免卡片卡在转圈。停按钮路径会先 settle() 再 abort,settled=true 时此处跳过,不会双发。
344
307
  void platformAdapter.sendError('⏹️ 已取消').catch(() => {
@@ -354,9 +317,9 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
354
317
  settle,
355
318
  startedAt: Date.now(),
356
319
  toolId: aiCommand,
357
- taskKey: hashUserId(ctx.taskKey),
320
+ taskKey: ctx.taskKey,
358
321
  platform: ctx.platform,
359
- userKey: hashUserId(ctx.userId),
322
+ userKey: ctx.userId,
360
323
  };
361
324
  try {
362
325
  startRun();
@@ -371,15 +334,6 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
371
334
  userId: ctx.userId,
372
335
  aiCommand,
373
336
  });
374
- emitStructuredEvent('AITask', 'ai.task.error', {
375
- platform: ctx.platform,
376
- taskKey: hashUserId(ctx.taskKey),
377
- userKey: hashUserId(ctx.userId),
378
- toolId: aiCommand,
379
- durationMs: 0,
380
- errorSnippet: sanitize(String(err).slice(0, 400)),
381
- errorType: classifyErrorType(String(err)),
382
- });
383
337
  platformAdapter
384
338
  .sendError(`内部错误:${err instanceof Error ? err.message : String(err)}`)
385
339
  .catch(() => {
@@ -4,7 +4,7 @@
4
4
  * Tasks older than 30 minutes are aborted and removed from the running-tasks
5
5
  * map so they never accumulate indefinitely.
6
6
  */
7
- import { createLogger, emitStructuredEvent } from '../logger.js';
7
+ import { createLogger } from '../logger.js';
8
8
  const log = createLogger('TaskCleanup');
9
9
  const CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
10
10
  const STALE_THRESHOLD_MS = 30 * 60 * 1000; // 30 minutes
@@ -46,17 +46,7 @@ export function startTaskCleanup(runningTasks) {
46
46
  export function emitInterruptedTerminals(runningTasks) {
47
47
  if (runningTasks.size === 0)
48
48
  return;
49
- const now = Date.now();
50
49
  for (const state of runningTasks.values()) {
51
- emitStructuredEvent('AITask', 'ai.task.error', {
52
- platform: state.platform,
53
- taskKey: state.taskKey,
54
- userKey: state.userKey,
55
- toolId: state.toolId,
56
- durationMs: now - state.startedAt,
57
- errorSnippet: 'interrupted',
58
- errorType: 'interrupted',
59
- });
60
50
  // 标记已结算,使随后 shutdown 的 abort() 跳过重复的 aborted 事件
61
51
  state.settle();
62
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.11.2-beta.1",
3
+ "version": "1.11.2-beta.11",
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",