@wu529778790/open-im 1.11.2-beta.2 → 1.11.2-beta.20

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,108 +1,127 @@
1
1
  # open-im
2
2
 
3
- **English** · [中文](./README.zh-CN.md)
3
+ > 你的 AI 编程助手,在每个聊天 App 里。
4
4
 
5
- > Your AI coding assistant, in every chat app.
5
+ open-im Claude Code、Codex、CodeBuddy 接入 Telegram、飞书、企业微信、钉钉、QQ 机器人、微信助理、微信客服号。手机发条消息,电脑上就写好代码。
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
8
8
 
9
- ## Architecture
9
+ - **随时随地** — 通勤、排队、躺沙发上,手机发消息就能让 AI 干活
10
+ - **无缝接力** — 和 Claude Code CLI 共享 session,手机聊一半,电脑接着来
11
+ - **完整能力** — 流式输出、会话管理、模型切换,全靠聊天命令
12
+ - **一个桥接,多个平台** — 同一个 bot 支持 7 个 IM 平台
10
13
 
11
- ![Open-IM Architecture](./diagram/architecture/open-im-architecture.svg)
14
+ ## 快速开始
12
15
 
13
- ## Why
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
19
-
20
- ## Features
21
-
22
- ### Chat commands
16
+ ```bash
17
+ # 安装
18
+ npm install -g @wu529778790/open-im
23
19
 
24
- | Command | Description |
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 |
20
+ # 配置(交互式向导)
21
+ open-im init
39
22
 
40
- ### Session continuity
23
+ # 启动
24
+ open-im start
25
+ ```
41
26
 
42
- open-im and Claude Code CLI share the same session storage. In the same directory, you can seamlessly switch between phone and computer.
27
+ 或直接用 npx:
43
28
 
29
+ ```bash
30
+ npx @wu529778790/open-im init
31
+ npx @wu529778790/open-im start
44
32
  ```
45
- # On computer
46
- cd /my-project && claude # work as usual, then Ctrl+C
47
33
 
48
- # On phone (via IM)
49
- "help me fix the login bug" # open-im auto-resumes the same session
34
+ ### 最小配置
50
35
 
51
- # Back on computer
52
- claude -c # continues the phone conversation
36
+ ```json
37
+ {
38
+ "tools": {
39
+ "claude": { "workDir": "/path/to/project" }
40
+ },
41
+ "platforms": {
42
+ "telegram": { "enabled": true, "botToken": "YOUR_TOKEN" }
43
+ }
44
+ }
53
45
  ```
54
46
 
55
- > Only one side can be active at a time. Exit the CLI before sending messages from the phone, and vice versa.
56
-
57
- ### Platform support
47
+ ## 平台支持
48
+
49
+ | 平台 | 流式输出 | 接入指南 |
50
+ |------|---------|---------|
51
+ | Telegram | ✅ | [Bot 文档](https://core.telegram.org/bots#creating-a-new-bot) |
52
+ | 飞书 | ✅ | [开放平台](https://open.feishu.cn/) |
53
+ | QQ 机器人 | ✅ | [开放平台](https://bot.q.qq.com/) |
54
+ | 企业微信 | ✅ | [管理后台](https://work.weixin.qq.com/) |
55
+ | 钉钉机器人 | ⚠️ 部分 | [开放平台](https://open-dev.dingtalk.com/) |
56
+ | 微信助理(WorkBuddy) | ✅ | [接入指南](https://www.codebuddy.cn/docs/workbuddy/Claw) |
57
+ | 微信客服号(ClawBot) | ✅ | [接入指南](https://www.codebuddy.cn/docs/workbuddy/Claw) |
58
+
59
+ 每个平台可单独配置 AI 后端(`claude` / `codex` / `codebuddy`),默认 `claude`。
60
+
61
+ ## 聊天命令
62
+
63
+ | 命令 | 说明 |
64
+ |------|------|
65
+ | `/help` | 显示所有命令 |
66
+ | `/new` | 开启新 AI 会话 |
67
+ | `/sessions` | 浏览历史会话 |
68
+ | `/resume [序号]` | 恢复会话 |
69
+ | `/history [序号]` | 查看对话记录 |
70
+ | `/delete <序号>` | 删除会话 |
71
+ | `/rename <标题>` | 重命名会话 |
72
+ | `/fork [序号]` | 分支会话 |
73
+ | `/models` | 查看可用模型 |
74
+ | `/context` | 查看上下文用量 |
75
+ | `/status` | 显示状态信息 |
76
+ | `/cd <路径>` / `/pwd` | 切换/查看工作目录 |
77
+ | `/plugins` | 查看已安装插件 |
78
+ | `/allow` `/y` / `/deny` `/n` | 权限确认 |
79
+
80
+ ## 会话接力
81
+
82
+ open-im 和 Claude Code CLI 共享 session 存储。同一目录下,手机和电脑无缝切换:
58
83
 
59
- Seven IM platforms, three AI backends, per-platform override:
84
+ ```bash
85
+ # 电脑端
86
+ cd /my-project && claude
60
87
 
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 |
88
+ # 手机端
89
+ "帮我修复登录 bug" # 自动接续同一个 session
70
90
 
71
- Set `platforms.<name>.aiCommand` (`claude` / `codex` / `codebuddy`) per channel. Default: `claude`.
91
+ # 回到电脑端
92
+ claude -c # 接上手机端的对话
93
+ ```
72
94
 
73
- ### Web dashboard
95
+ > 同一时刻只能有一端活跃。发消息前先退出 CLI,反之亦然。
74
96
 
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`.
97
+ ## Web 控制台
76
98
 
77
- ## Quick start
99
+ `open-im start` 在 `http://127.0.0.1:39282` 提供管理界面:
78
100
 
79
- ```bash
80
- npx @wu529778790/open-im init # interactive setup
81
- npx @wu529778790/open-im start # run the bridge
82
- ```
101
+ - 配置所有平台凭证
102
+ - 启动/停止桥接服务
103
+ - 编辑配置文件
104
+ - 首次运行自动弹出设置向导
83
105
 
84
- Or install globally: `npm install -g @wu529778790/open-im` then `open-im start`.
106
+ 局域网访问:`export OPEN_IM_WEB_HOST=0.0.0.0`
85
107
 
86
- Config file: **`~/.open-im/config.json`**
108
+ ## CLI 命令
87
109
 
88
- ### Minimal config
110
+ | 命令 | 说明 |
111
+ |------|------|
112
+ | `open-im init` | 交互式配置 |
113
+ | `open-im start` | 后台运行 |
114
+ | `open-im stop` | 停止服务 |
115
+ | `open-im restart` | 重启 |
116
+ | `open-im dashboard` | 仅启动 Web 配置服务 |
89
117
 
90
- ```json
91
- {
92
- "tools": {
93
- "claude": { "workDir": "/path/to/project", "skipPermissions": true }
94
- },
95
- "platforms": {
96
- "telegram": { "enabled": true, "botToken": "YOUR_TELEGRAM_BOT_TOKEN" }
97
- }
98
- }
99
- ```
118
+ ## 配置
100
119
 
101
- Run `open-im init` for a full template with all platforms.
120
+ 配置文件:`~/.open-im/config.json`
102
121
 
103
- ### Claude (Agent SDK)
122
+ ### ClaudeAgent SDK
104
123
 
105
- No local `claude` binary required. Supports third-party / compatible APIs:
124
+ 无需本地 `claude` 可执行文件。支持第三方兼容接口:
106
125
 
107
126
  ```json
108
127
  {
@@ -111,43 +130,32 @@ No local `claude` binary required. Supports third-party / compatible APIs:
111
130
  "env": {
112
131
  "ANTHROPIC_AUTH_TOKEN": "your-token",
113
132
  "ANTHROPIC_BASE_URL": "https://your-api-endpoint",
114
- "ANTHROPIC_MODEL": "glm-4.7"
133
+ "ANTHROPIC_MODEL": "model-name"
115
134
  }
116
135
  }
117
136
  }
118
137
  }
119
138
  ```
120
139
 
121
- ### CLI reference
122
-
123
- | Command | Description |
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) |
130
-
131
- ### Environment variables
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.
134
-
135
- ### Git co-authors
140
+ ### 环境变量
136
141
 
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.
142
+ - **`ANTHROPIC_*`** Claude API 配置
143
+ - **`TELEGRAM_BOT_TOKEN`** — Telegram Bot Token
144
+ - **`OPEN_IM_WEB_PORT`** — Web 控制台端口(默认 39282)
145
+ - **`OPEN_IM_WEB_HOST`** — Web 控制台监听地址
138
146
 
139
- ### Privacy
147
+ ### 隐私
140
148
 
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`.
149
+ 匿名运行信息用于改进稳定性(不含聊天内容)。关闭:`OPEN_IM_TELEMETRY=false`
142
150
 
143
- ## Platform setup & troubleshooting
151
+ ## 平台配置详情
144
152
 
145
- See **[docs/platforms.md](./docs/platforms.md)** for detailed per-platform configuration, credential setup, and troubleshooting.
153
+ 详见 [docs/platforms.md](./docs/platforms.md)
146
154
 
147
- ## Requirements
155
+ ## 环境要求
148
156
 
149
157
  - Node.js >= 20
150
- - At least one IM platform configured + credentials for your AI tool
158
+ - 至少配置一个 IM 平台 + AI 凭证
151
159
 
152
160
  ## License
153
161
 
@@ -23,8 +23,10 @@ export function setupClawbotHandlers(config, sessionManager) {
23
23
  });
24
24
  const stopTaskCleanup = startTaskCleanup(ctx.runningTasks);
25
25
  const platformSender = {
26
- sendThinkingMessage: async (_chatId, _replyToMessageId, _toolId) => {
27
- return 'clawbot_no_thinking';
26
+ sendThinkingMessage: async (chatId, _replyToMessageId, _toolId) => {
27
+ // ClawBot 不支持 typing indicator,先发一条"思考中"消息给用户反馈
28
+ await sendTextReply(chatId, '🤔 正在处理...');
29
+ return 'clawbot_thinking';
28
30
  },
29
31
  sendTextReply: async (chatId, text) => {
30
32
  await sendTextReply(chatId, text);
@@ -52,11 +52,11 @@ export declare const PAGE_TEXTS: {
52
52
  readonly credentialProgress: "{done}/{total} credentials";
53
53
  readonly telegramSummary: "Bot token and optional proxy.";
54
54
  readonly feishuSummary: "App ID, App Secret, and allowed user scope.";
55
- readonly qqSummary: "App ID and secret for bot access.";
56
- readonly weworkSummary: "Corp ID and secret for enterprise delivery.";
55
+ readonly qqSummary: "App ID and secret for QQ robot.";
56
+ readonly weworkSummary: "Corp ID and secret for enterprise WeChat.";
57
57
  readonly dingtalkSummary: "Client credentials plus optional card template.";
58
- readonly workbuddySummary: "CodeBuddy OAuth for WeChat customer service.";
59
- readonly clawbotSummary: "WeChat iLink API via ClawBot.";
58
+ readonly workbuddySummary: "WeChat assistant via CodeBuddy OAuth.";
59
+ readonly clawbotSummary: "WeChat customer service via ClawBot.";
60
60
  readonly clawbotApiUrl: "API URL";
61
61
  readonly clawbotApiToken: "API Token (Bearer)";
62
62
  readonly clawbotHelp: "Click \"Scan QR Login\" to authenticate via WeChat, or paste token manually.";
@@ -86,7 +86,7 @@ export declare const PAGE_TEXTS: {
86
86
  readonly clientId: "Client ID / AppKey";
87
87
  readonly clientSecret: "Client Secret / AppSecret";
88
88
  readonly dingtalkHelp: "Get credentials: Create an enterprise internal app on <a href=\"https://open-dev.dingtalk.com/\" target=\"_blank\">DingTalk Open Platform</a>, enable Stream Mode, and get Client ID / Client Secret";
89
- readonly workbuddyHelp: "Get credentials: Login via CodeBuddy OAuth to get access/refresh tokens. WorkBuddy connects WeChat customer service through Centrifuge WebSocket.";
89
+ readonly workbuddyHelp: "Get credentials: Login via CodeBuddy OAuth to get access/refresh tokens. Connects WeChat assistant through Centrifuge WebSocket.";
90
90
  readonly secret: "Secret";
91
91
  readonly cardTemplateId: "Card template ID";
92
92
  readonly workbuddyAccessToken: "Access Token";
@@ -266,8 +266,8 @@ export declare const PAGE_TEXTS: {
266
266
  readonly qqSummary: "QQ 机器人 App ID 与 Secret。";
267
267
  readonly weworkSummary: "企业微信 Corp ID 与 Secret。";
268
268
  readonly dingtalkSummary: "钉钉 Client 凭证,可选配置卡片模板 ID。";
269
- readonly workbuddySummary: "CodeBuddy OAuth 连接微信客服。";
270
- readonly clawbotSummary: "通过 ClawBot 连接微信 iLink API。";
269
+ readonly workbuddySummary: "通过 CodeBuddy OAuth 连接微信助理。";
270
+ readonly clawbotSummary: "通过 ClawBot 连接微信客服号。";
271
271
  readonly clawbotApiUrl: "API 地址";
272
272
  readonly clawbotApiToken: "API Token (Bearer)";
273
273
  readonly clawbotHelp: "点击“扫码登录”通过微信认证,或手动粘贴 Token。";
@@ -52,11 +52,11 @@ export const PAGE_TEXTS = {
52
52
  credentialProgress: "{done}/{total} credentials",
53
53
  telegramSummary: "Bot token and optional proxy.",
54
54
  feishuSummary: "App ID, App Secret, and allowed user scope.",
55
- qqSummary: "App ID and secret for bot access.",
56
- weworkSummary: "Corp ID and secret for enterprise delivery.",
55
+ qqSummary: "App ID and secret for QQ robot.",
56
+ weworkSummary: "Corp ID and secret for enterprise WeChat.",
57
57
  dingtalkSummary: "Client credentials plus optional card template.",
58
- workbuddySummary: "CodeBuddy OAuth for WeChat customer service.",
59
- clawbotSummary: "WeChat iLink API via ClawBot.",
58
+ workbuddySummary: "WeChat assistant via CodeBuddy OAuth.",
59
+ clawbotSummary: "WeChat customer service via ClawBot.",
60
60
  clawbotApiUrl: "API URL",
61
61
  clawbotApiToken: "API Token (Bearer)",
62
62
  clawbotHelp: 'Click "Scan QR Login" to authenticate via WeChat, or paste token manually.',
@@ -86,7 +86,7 @@ export const PAGE_TEXTS = {
86
86
  clientId: "Client ID / AppKey",
87
87
  clientSecret: "Client Secret / AppSecret",
88
88
  dingtalkHelp: 'Get credentials: Create an enterprise internal app on <a href="https://open-dev.dingtalk.com/" target="_blank">DingTalk Open Platform</a>, enable Stream Mode, and get Client ID / Client Secret',
89
- workbuddyHelp: 'Get credentials: Login via CodeBuddy OAuth to get access/refresh tokens. WorkBuddy connects WeChat customer service through Centrifuge WebSocket.',
89
+ workbuddyHelp: 'Get credentials: Login via CodeBuddy OAuth to get access/refresh tokens. Connects WeChat assistant through Centrifuge WebSocket.',
90
90
  secret: "Secret",
91
91
  cardTemplateId: "Card template ID",
92
92
  workbuddyAccessToken: "Access Token",
@@ -267,8 +267,8 @@ export const PAGE_TEXTS = {
267
267
  qqSummary: "QQ \u673a\u5668\u4eba App ID \u4e0e Secret\u3002",
268
268
  weworkSummary: "\u4f01\u4e1a\u5fae\u4fe1 Corp ID \u4e0e Secret\u3002",
269
269
  dingtalkSummary: "\u9489\u9489 Client \u51ed\u8bc1\uff0c\u53ef\u9009\u914d\u7f6e\u5361\u7247\u6a21\u677f ID\u3002",
270
- workbuddySummary: "CodeBuddy OAuth \u8fde\u63a5\u5fae\u4fe1\u5ba2\u670d\u3002",
271
- clawbotSummary: "\u901a\u8fc7 ClawBot \u8fde\u63a5\u5fae\u4fe1 iLink API\u3002",
270
+ workbuddySummary: "\u901a\u8fc7 CodeBuddy OAuth \u8fde\u63a5\u5fae\u4fe1\u52a9\u7406\u3002",
271
+ clawbotSummary: "\u901a\u8fc7 ClawBot \u8fde\u63a5\u5fae\u4fe1\u5ba2\u670d\u53f7\u3002",
272
272
  clawbotApiUrl: "API \u5730\u5740",
273
273
  clawbotApiToken: "API Token (Bearer)",
274
274
  clawbotHelp: '\u70b9\u51fb\u201c\u626b\u7801\u767b\u5f55\u201d\u901a\u8fc7\u5fae\u4fe1\u8ba4\u8bc1\uff0c\u6216\u624b\u52a8\u7c98\u8d34 Token\u3002',
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";
@@ -137,6 +137,55 @@ const PLATFORM_DISPLAY_NAMES = {
137
137
  workbuddy: '微信',
138
138
  clawbot: '微信 (ClawBot)',
139
139
  };
140
+ /** 读取已启用的插件列表 */
141
+ function getEnabledPlugins() {
142
+ try {
143
+ const { readFileSync, existsSync } = require("fs");
144
+ const { join } = require("path");
145
+ const { homedir } = require("os");
146
+ const settingsPath = join(homedir(), ".claude", "settings.json");
147
+ if (!existsSync(settingsPath))
148
+ return [];
149
+ const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
150
+ const plugins = settings.enabledPlugins ?? {};
151
+ return Object.entries(plugins)
152
+ .filter(([, v]) => v === true)
153
+ .map(([k]) => k.split("@")[0]);
154
+ }
155
+ catch {
156
+ return [];
157
+ }
158
+ }
159
+ /**
160
+ * 统一通知格式模板
161
+ * 所有发送给 IM 的通知都使用这个格式,保持一致性
162
+ */
163
+ function buildNotification(opts) {
164
+ const lines = [];
165
+ // 标题行
166
+ lines.push(`${opts.emoji} ${opts.title}`);
167
+ // 详情行
168
+ const details = [];
169
+ if (opts.platform)
170
+ details.push(`📱 平台: ${opts.platform}`);
171
+ if (opts.aiCommand)
172
+ details.push(`🤖 AI: ${opts.aiCommand}`);
173
+ if (opts.dir)
174
+ details.push(`📁 目录: ${opts.dir}`);
175
+ const plugins = getEnabledPlugins();
176
+ if (plugins.length > 0)
177
+ details.push(`🧩 插件: ${plugins.join(", ")}`);
178
+ if (opts.uptime)
179
+ details.push(`⏱️ 运行: ${opts.uptime}`);
180
+ if (details.length > 0) {
181
+ lines.push("", ...details);
182
+ }
183
+ // 额外信息
184
+ if (opts.extra?.length) {
185
+ lines.push("", ...opts.extra);
186
+ }
187
+ return lines.join("\n");
188
+ }
140
189
  function buildStartupMessage(platform, appVersion, aiCommand, defaultWorkDir, sessionManager) {
141
190
  let sessionDir;
142
191
  // Telegram 私聊、企业微信当前实现里,活跃 chatId 可直接对应到 session userId。
@@ -149,21 +198,21 @@ function buildStartupMessage(platform, appVersion, aiCommand, defaultWorkDir, se
149
198
  const platformName = PLATFORM_DISPLAY_NAMES[platform] ?? platform;
150
199
  const toolName = getAIToolDisplayName(aiCommand);
151
200
  const dir = sessionDir ? escapePathForMarkdown(sessionDir) : '发送 `/pwd` 查看';
152
- return [
153
- `✅ open-im v${appVersion} 已就绪`,
154
- "",
155
- `📱 平台: ${platformName}`,
156
- `🤖 AI: ${toolName}`,
157
- `📁 目录: ${dir}`,
158
- ].join("\n\n");
201
+ return buildNotification({
202
+ emoji: "✅",
203
+ title: `open-im v${appVersion} 已就绪`,
204
+ platform: platformName,
205
+ aiCommand: toolName,
206
+ dir,
207
+ });
159
208
  }
160
209
  function buildShutdownMessage(uptimeMinutes) {
161
- const uptime = uptimeMinutes < 1 ? '< 1' : String(uptimeMinutes);
162
- return [
163
- `🛑 open-im 正在关闭`,
164
- "",
165
- `⏱️ 运行时长: ${uptime} 分钟`,
166
- ].join("\n\n");
210
+ const uptimeStr = uptimeMinutes < 1 ? '< 1 分钟' : `${uptimeMinutes} 分钟`;
211
+ return buildNotification({
212
+ emoji: "🛑",
213
+ title: "open-im 正在关闭",
214
+ uptime: uptimeStr,
215
+ });
167
216
  }
168
217
  export async function main() {
169
218
  const startupCwd = process.cwd();
@@ -267,10 +316,6 @@ export async function main() {
267
316
  }
268
317
  log.info("Service is running. Press Ctrl+C to stop.");
269
318
  log.info(`Successfully initialized platforms: ${successfulPlatforms.join(", ")}`);
270
- emitStructuredEvent("Main", "service.platform.init", {
271
- platforms: successfulPlatforms,
272
- version: APP_VERSION,
273
- });
274
319
  // Send notification only to successfully initialized platforms
275
320
  for (const platform of successfulPlatforms) {
276
321
  const startupMsg = buildStartupMessage(platform, APP_VERSION, resolvePlatformAiCommand(config, platform), startupCwd, sessionManager);
@@ -328,7 +373,6 @@ export async function main() {
328
373
  sessionManager.destroy();
329
374
  cleanupAdapters();
330
375
  flushActiveChats();
331
- await shutdownLoggerTelemetry();
332
376
  await flushSentry();
333
377
  await closeLogger();
334
378
  process.exit(0);
@@ -381,8 +425,7 @@ export async function main() {
381
425
  }
382
426
  }
383
427
  }
384
- void shutdownLoggerTelemetry()
385
- .then(() => closeLogger())
428
+ void closeLogger()
386
429
  .finally(() => process.exit(1));
387
430
  });
388
431
  }
@@ -391,8 +434,7 @@ const isEntry = process.argv[1]?.replace(/\\/g, "/").endsWith("/index.js") ||
391
434
  if (isEntry) {
392
435
  main().catch((err) => {
393
436
  log.error("Fatal error:", err);
394
- void shutdownLoggerTelemetry()
395
- .then(() => closeLogger())
437
+ void closeLogger()
396
438
  .finally(() => process.exit(1));
397
439
  });
398
440
  }
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,16 +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
6
  const DEFAULT_LOG_DIR = join(APP_HOME, 'logs');
8
7
  const MAX_LOG_FILES = 10;
9
8
  const LOG_LEVELS = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 };
10
9
  let logDir = DEFAULT_LOG_DIR;
11
10
  let minLevel = LOG_LEVELS.DEBUG;
12
11
  let logStream;
13
- let eventsStream;
14
12
  let auditStream;
15
- let telemetryEnabled = false;
16
13
  function pad(n) {
17
14
  return String(n).padStart(2, '0');
18
15
  }
@@ -80,15 +77,6 @@ export function initLogger(dirOrOpts, level, telemetry) {
80
77
  auditStream = undefined;
81
78
  }
82
79
  auditStream = createWriteStream(join(logDir, 'audit.log'), { flags: 'a' });
83
- telemetryEnabled = !!tel?.enabled;
84
- if (eventsStream) {
85
- eventsStream.end();
86
- eventsStream = undefined;
87
- }
88
- if (telemetryEnabled) {
89
- rotateOldJsonl();
90
- eventsStream = createWriteStream(join(logDir, getEventsFileName()), { flags: 'a' });
91
- }
92
80
  }
93
81
  function write(level, tag, msg, ...args) {
94
82
  if (LOG_LEVELS[level] < minLevel)
@@ -109,7 +97,6 @@ export function createLogger(tag) {
109
97
  warn: (msg, ...args) => write('WARN', tag, msg, ...args),
110
98
  error: (msg, ...args) => write('ERROR', tag, msg, ...args),
111
99
  debug: (msg, ...args) => write('DEBUG', tag, msg, ...args),
112
- infoEvent: (event, data, msg) => emitStructuredEvent(tag, event, data, 'INFO', msg),
113
100
  };
114
101
  }
115
102
  /**
@@ -126,31 +113,13 @@ export function auditLog(platform, userId, action, detail) {
126
113
  };
127
114
  auditStream?.write(JSON.stringify(entry) + '\n');
128
115
  }
129
- export function emitStructuredEvent(tag, event, data, level = 'INFO', msg = '') {
130
- if (!telemetryEnabled)
131
- return;
132
- const payload = {
133
- v: 1,
134
- ts: new Date().toISOString(),
135
- level,
136
- tag,
137
- event,
138
- msg,
139
- data: sanitizeTelemetryData(data),
140
- };
141
- const line = `${JSON.stringify(payload)}\n`;
142
- eventsStream?.write(line);
143
- }
144
- export async function shutdownLoggerTelemetry() {
145
- // Local event logging only — no upload to clean up
146
- }
147
116
  export async function closeLogger() {
148
- if (eventsStream) {
149
- const es = eventsStream;
150
- eventsStream = undefined;
151
- es.end();
117
+ if (auditStream) {
118
+ const as = auditStream;
119
+ auditStream = undefined;
120
+ as.end();
152
121
  try {
153
- await finished(es);
122
+ await finished(as);
154
123
  }
155
124
  catch {
156
125
  /* ignore */