@wu529778790/open-im 1.11.1 → 1.11.2-beta.0
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 +5 -0
- package/dist/shared/ai-task.js +6 -0
- package/dist/shared/sentry.d.ts +22 -0
- package/dist/shared/sentry.js +101 -0
- package/package.json +2 -1
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();
|
|
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);
|
package/dist/shared/ai-task.js
CHANGED
|
@@ -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,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentry 错误追踪集成
|
|
3
|
+
*
|
|
4
|
+
* 仅捕获错误,不开启性能监控(免费版 5K errors/月 够用)。
|
|
5
|
+
* 通过 OPEN_IM_SENTRY_DSN 环境变量启用。
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* 初始化 Sentry。无 DSN 则静默跳过。
|
|
9
|
+
*/
|
|
10
|
+
export declare function initSentry(): void;
|
|
11
|
+
/**
|
|
12
|
+
* 上报错误到 Sentry
|
|
13
|
+
*/
|
|
14
|
+
export declare function captureError(error: Error | unknown, context?: Record<string, unknown>): void;
|
|
15
|
+
/**
|
|
16
|
+
* 添加面包屑(错误发生前的上下文)
|
|
17
|
+
*/
|
|
18
|
+
export declare function addBreadcrumb(category: string, message: string, data?: Record<string, unknown>): void;
|
|
19
|
+
/**
|
|
20
|
+
* 刷新并关闭 Sentry(优雅关闭时调用)
|
|
21
|
+
*/
|
|
22
|
+
export declare function flushSentry(): Promise<void>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentry 错误追踪集成
|
|
3
|
+
*
|
|
4
|
+
* 仅捕获错误,不开启性能监控(免费版 5K errors/月 够用)。
|
|
5
|
+
* 通过 OPEN_IM_SENTRY_DSN 环境变量启用。
|
|
6
|
+
*/
|
|
7
|
+
import * as Sentry from '@sentry/node';
|
|
8
|
+
import { createLogger } from '../logger.js';
|
|
9
|
+
const log = createLogger('Sentry');
|
|
10
|
+
let initialized = false;
|
|
11
|
+
/**
|
|
12
|
+
* 初始化 Sentry。无 DSN 则静默跳过。
|
|
13
|
+
*/
|
|
14
|
+
export function initSentry() {
|
|
15
|
+
const dsn = process.env.OPEN_IM_SENTRY_DSN;
|
|
16
|
+
if (!dsn) {
|
|
17
|
+
log.debug('Sentry disabled (no OPEN_IM_SENTRY_DSN)');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
Sentry.init({
|
|
22
|
+
dsn,
|
|
23
|
+
// 只捕获 error 级别,不捕获 warning/info
|
|
24
|
+
beforeSend(event) {
|
|
25
|
+
if (event.level !== 'error' && event.level !== 'fatal')
|
|
26
|
+
return null;
|
|
27
|
+
return event;
|
|
28
|
+
},
|
|
29
|
+
// 不开启 performance monitoring
|
|
30
|
+
tracesSampleRate: 0,
|
|
31
|
+
// 环境标识
|
|
32
|
+
environment: process.env.NODE_ENV ?? 'production',
|
|
33
|
+
// 附加面包屑(错误发生前的日志)
|
|
34
|
+
beforeBreadcrumb(breadcrumb) {
|
|
35
|
+
// 只保留关键 breadcrumb
|
|
36
|
+
if (breadcrumb.category === 'console' || breadcrumb.category === 'http') {
|
|
37
|
+
return breadcrumb;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
initialized = true;
|
|
43
|
+
log.info('Sentry initialized');
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
log.warn('Sentry init failed:', err);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 上报错误到 Sentry
|
|
51
|
+
*/
|
|
52
|
+
export function captureError(error, context) {
|
|
53
|
+
if (!initialized)
|
|
54
|
+
return;
|
|
55
|
+
try {
|
|
56
|
+
Sentry.withScope((scope) => {
|
|
57
|
+
if (context) {
|
|
58
|
+
scope.setExtras(context);
|
|
59
|
+
}
|
|
60
|
+
if (error instanceof Error) {
|
|
61
|
+
scope.setTag('error.name', error.name);
|
|
62
|
+
scope.setTag('error.message', error.message.substring(0, 200));
|
|
63
|
+
}
|
|
64
|
+
Sentry.captureException(error);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Sentry 本身不应影响业务
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 添加面包屑(错误发生前的上下文)
|
|
73
|
+
*/
|
|
74
|
+
export function addBreadcrumb(category, message, data) {
|
|
75
|
+
if (!initialized)
|
|
76
|
+
return;
|
|
77
|
+
try {
|
|
78
|
+
Sentry.addBreadcrumb({
|
|
79
|
+
category,
|
|
80
|
+
message: message.substring(0, 200),
|
|
81
|
+
data,
|
|
82
|
+
level: 'info',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// ignore
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* 刷新并关闭 Sentry(优雅关闭时调用)
|
|
91
|
+
*/
|
|
92
|
+
export async function flushSentry() {
|
|
93
|
+
if (!initialized)
|
|
94
|
+
return;
|
|
95
|
+
try {
|
|
96
|
+
await Sentry.flush(3000);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// ignore
|
|
100
|
+
}
|
|
101
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wu529778790/open-im",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.2-beta.0",
|
|
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",
|