@wu529778790/open-im 1.11.1 → 1.11.2-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.
package/dist/index.js CHANGED
@@ -38,6 +38,7 @@ import { createRequire } from "node:module";
38
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
+ import { initSentry, captureError, flushSentry } from "./shared/sentry.js";
41
42
  const require = createRequire(import.meta.url);
42
43
  const { version: APP_VERSION } = require("../package.json");
43
44
  const log = createLogger("Main");
@@ -182,6 +183,7 @@ export async function main() {
182
183
  logLevel: config.logLevel,
183
184
  telemetry: config.telemetry,
184
185
  });
186
+ initSentry(config.telemetry.enabled);
185
187
  applyOpenImGitCoauthorToProcessEnv();
186
188
  }
187
189
  catch (err) {
@@ -327,6 +329,7 @@ export async function main() {
327
329
  cleanupAdapters();
328
330
  flushActiveChats();
329
331
  await shutdownLoggerTelemetry();
332
+ await flushSentry();
330
333
  await closeLogger();
331
334
  process.exit(0);
332
335
  };
@@ -354,6 +357,7 @@ export async function main() {
354
357
  // Global error handlers to prevent unhandled crashes
355
358
  process.on("unhandledRejection", (reason) => {
356
359
  log.error("Unhandled Promise rejection (this indicates a bug — the affected request may hang without a response):", reason);
360
+ captureError(reason instanceof Error ? reason : new Error(String(reason)), { source: 'unhandledRejection' });
357
361
  });
358
362
  process.on("uncaughtException", (err) => {
359
363
  const msg = err?.message ?? String(err);
@@ -364,6 +368,7 @@ export async function main() {
364
368
  return;
365
369
  }
366
370
  log.error("Uncaught exception (process will exit):", err);
371
+ captureError(err, { fatal: true, source: 'uncaughtException' });
367
372
  // 进程即将因未捕获异常退出:补发在途任务的终态,避免 miss
368
373
  for (const platform of successfulPlatforms) {
369
374
  const handle = activeHandles.get(platform);
@@ -2,6 +2,7 @@
2
2
  * 共享 AI 任务执行层,支持多 ToolAdapter。
3
3
  */
4
4
  import { resolvePlatformAiCommand } from '../config.js';
5
+ import { captureError } from './sentry.js';
5
6
  import { formatToolStats, formatToolCallNotification, getContextWarning, getAIToolDisplayName, toReplyPlainText, } from './utils.js';
6
7
  import { createLogger, emitStructuredEvent } from '../logger.js';
7
8
  import { hashUserId } from '../telemetry/hash-user.js';
@@ -365,6 +366,11 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
365
366
  settled = true;
366
367
  cleanup();
367
368
  log.error(`[AITask] Synchronous error in startRun: ${err}`);
369
+ captureError(err instanceof Error ? err : new Error(String(err)), {
370
+ platform: ctx.platform,
371
+ userId: ctx.userId,
372
+ aiCommand,
373
+ });
368
374
  emitStructuredEvent('AITask', 'ai.task.error', {
369
375
  platform: ctx.platform,
370
376
  taskKey: hashUserId(ctx.taskKey),
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Sentry 错误追踪集成
3
+ *
4
+ * - 默认启用,收集错误用于改进产品质量
5
+ * - 用户可通过 OPEN_IM_TELEMETRY=false 或 config.json opt-out
6
+ * - 只收集错误类型和堆栈,不收集用户数据(PII)
7
+ */
8
+ /**
9
+ * 初始化 Sentry
10
+ * - 默认启用(收集错误改进产品)
11
+ * - 用户可通过 OPEN_IM_TELEMETRY=false 或 telemetry.enabled=false opt-out
12
+ */
13
+ export declare function initSentry(telemetryEnabled?: boolean): void;
14
+ /**
15
+ * 上报错误到 Sentry(自动清理 PII)
16
+ */
17
+ export declare function captureError(error: Error | unknown, context?: Record<string, unknown>): void;
18
+ /**
19
+ * 刷新并关闭 Sentry(优雅关闭时调用)
20
+ */
21
+ export declare function flushSentry(): Promise<void>;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Sentry 错误追踪集成
3
+ *
4
+ * - 默认启用,收集错误用于改进产品质量
5
+ * - 用户可通过 OPEN_IM_TELEMETRY=false 或 config.json opt-out
6
+ * - 只收集错误类型和堆栈,不收集用户数据(PII)
7
+ */
8
+ import * as Sentry from '@sentry/node';
9
+ import { createLogger } from '../logger.js';
10
+ const log = createLogger('Sentry');
11
+ // 开发者的 Sentry DSN(所有 open-im 实例共享)
12
+ const DEFAULT_DSN = 'https://cc5ad094c1229b2a2ff23ab54b0fd807@o4508612762861568.ingest.us.sentry.io/4511583989727232';
13
+ let initialized = false;
14
+ /**
15
+ * 清理 PII(用户数据)
16
+ * 只保留错误信息,移除敏感内容
17
+ */
18
+ function sanitizeContext(context) {
19
+ const clean = {};
20
+ for (const [key, value] of Object.entries(context)) {
21
+ // 跳过敏感字段
22
+ if (/token|secret|password|key|api_key|apikey|auth/i.test(key))
23
+ continue;
24
+ // 跳过用户 ID(只保留平台信息)
25
+ if (/userId|user_id|userKey|user_key/i.test(key)) {
26
+ clean[key] = '[redacted]';
27
+ continue;
28
+ }
29
+ // 截断长字符串
30
+ if (typeof value === 'string' && value.length > 200) {
31
+ clean[key] = value.substring(0, 200) + '...';
32
+ }
33
+ else {
34
+ clean[key] = value;
35
+ }
36
+ }
37
+ return clean;
38
+ }
39
+ /**
40
+ * 初始化 Sentry
41
+ * - 默认启用(收集错误改进产品)
42
+ * - 用户可通过 OPEN_IM_TELEMETRY=false 或 telemetry.enabled=false opt-out
43
+ */
44
+ export function initSentry(telemetryEnabled = true) {
45
+ // 检查 opt-out
46
+ const envOptOut = process.env.OPEN_IM_TELEMETRY?.trim().toLowerCase();
47
+ if (envOptOut === 'false' || envOptOut === '0' || envOptOut === 'no' || !telemetryEnabled) {
48
+ log.debug('Sentry disabled (user opt-out)');
49
+ return;
50
+ }
51
+ // 优先使用用户自定义 DSN,否则用默认 DSN
52
+ const dsn = process.env.OPEN_IM_SENTRY_DSN || DEFAULT_DSN;
53
+ try {
54
+ Sentry.init({
55
+ dsn,
56
+ // 只捕获 error 级别
57
+ beforeSend(event) {
58
+ if (event.level !== 'error' && event.level !== 'fatal')
59
+ return null;
60
+ // 清理 PII
61
+ if (event.extra) {
62
+ event.extra = sanitizeContext(event.extra);
63
+ }
64
+ if (event.contexts) {
65
+ for (const [key, value] of Object.entries(event.contexts)) {
66
+ if (typeof value === 'object' && value !== null) {
67
+ event.contexts[key] = sanitizeContext(value);
68
+ }
69
+ }
70
+ }
71
+ return event;
72
+ },
73
+ // 不开启 performance monitoring
74
+ tracesSampleRate: 0,
75
+ // 环境标识
76
+ environment: process.env.NODE_ENV ?? 'production',
77
+ // 附加面包屑
78
+ beforeBreadcrumb(breadcrumb) {
79
+ if (breadcrumb.category === 'console' || breadcrumb.category === 'http') {
80
+ return breadcrumb;
81
+ }
82
+ return null;
83
+ },
84
+ });
85
+ initialized = true;
86
+ log.info('Sentry initialized (error tracking enabled)');
87
+ }
88
+ catch (err) {
89
+ log.warn('Sentry init failed:', err);
90
+ }
91
+ }
92
+ /**
93
+ * 上报错误到 Sentry(自动清理 PII)
94
+ */
95
+ export function captureError(error, context) {
96
+ if (!initialized)
97
+ return;
98
+ try {
99
+ Sentry.withScope((scope) => {
100
+ if (context) {
101
+ scope.setExtras(sanitizeContext(context));
102
+ }
103
+ if (error instanceof Error) {
104
+ scope.setTag('error.name', error.name);
105
+ scope.setTag('error.message', error.message.substring(0, 200));
106
+ }
107
+ Sentry.captureException(error);
108
+ });
109
+ }
110
+ catch {
111
+ // Sentry 本身不应影响业务
112
+ }
113
+ }
114
+ /**
115
+ * 刷新并关闭 Sentry(优雅关闭时调用)
116
+ */
117
+ export async function flushSentry() {
118
+ if (!initialized)
119
+ return;
120
+ try {
121
+ await Sentry.flush(3000);
122
+ }
123
+ catch {
124
+ // ignore
125
+ }
126
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.11.1",
3
+ "version": "1.11.2-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",
@@ -55,6 +55,7 @@
55
55
  "@anthropic-ai/sdk": "^0.104.2",
56
56
  "@aws-sdk/client-s3": "^3.1035.0",
57
57
  "@larksuiteoapi/node-sdk": "^1.59.0",
58
+ "@sentry/node": "^10.58.0",
58
59
  "centrifuge": "^5.5.3",
59
60
  "dingtalk-stream": "^2.1.4",
60
61
  "prompts": "^2.4.2",