chatccc 0.2.232 → 0.2.233
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/package.json +1 -1
- package/src/__tests__/crash-logging.test.ts +95 -1
- package/src/__tests__/restart.test.ts +177 -132
- package/src/index.ts +6 -1
- package/src/orchestrator.ts +2517 -2494
- package/src/shared.ts +44 -0
package/src/shared.ts
CHANGED
|
@@ -340,6 +340,39 @@ export interface InstallCrashLoggingResult {
|
|
|
340
340
|
cleanup: () => void;
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
+
/**
|
|
344
|
+
* 常驻服务不能因 stdout/stderr 管道读端消失而 EPIPE 崩溃。
|
|
345
|
+
*
|
|
346
|
+
* 背景:`/restart` 后旧进程退出会关闭 stderr pipe 读端,但新进程(尤其经过
|
|
347
|
+
* tsx 包装层)的 stderr 仍指向该管道写端;一旦第三方 SDK(如飞书)打
|
|
348
|
+
* console.warn 就会 EPIPE。Node 对 stdout/stderr 的 EPIPE 若无 error 监听会
|
|
349
|
+
* 抛成 uncaughtException → 默认 onFatal 直接 process.exit(1) 杀死整个服务。
|
|
350
|
+
*
|
|
351
|
+
* 这里挂上 error 监听把这类 IO 错误降级为一条同步 trace(写磁盘,不走
|
|
352
|
+
* stdout/stderr,不会递归崩溃),服务继续运行。返回 cleanup 用于移除监听。
|
|
353
|
+
*/
|
|
354
|
+
export interface EpipeGuardOptions {
|
|
355
|
+
/** 用于写入诊断的同步函数,默认 appendStartupTrace */
|
|
356
|
+
tracer?: (message: string, extra?: Record<string, unknown>) => void;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export function installEpipeGuard(
|
|
360
|
+
streams: NodeJS.WriteStream[] = [process.stdout, process.stderr],
|
|
361
|
+
options: EpipeGuardOptions = {},
|
|
362
|
+
): () => void {
|
|
363
|
+
const tracer = options.tracer ?? appendStartupTrace;
|
|
364
|
+
const onError = (err: NodeJS.ErrnoException): void => {
|
|
365
|
+
safeCall(tracer, "stdio write error (non-fatal)", {
|
|
366
|
+
code: err.code ?? "",
|
|
367
|
+
message: (err.message ?? String(err)).slice(0, 200),
|
|
368
|
+
});
|
|
369
|
+
};
|
|
370
|
+
for (const stream of streams) stream.on("error", onError);
|
|
371
|
+
return () => {
|
|
372
|
+
for (const stream of streams) stream.off("error", onError);
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
343
376
|
/**
|
|
344
377
|
* 把崩溃黑匣子 handler 装到 process 上,返回 cleanup。
|
|
345
378
|
*
|
|
@@ -389,6 +422,7 @@ export function setupFileLogging(logDir: string, prefix: string): { logPath: str
|
|
|
389
422
|
writeFileSync(logPath, "", { flag: "a", encoding: "utf8" });
|
|
390
423
|
const origConsoleLog = console.log.bind(console);
|
|
391
424
|
const origConsoleError = console.error.bind(console);
|
|
425
|
+
const origConsoleWarn = console.warn.bind(console);
|
|
392
426
|
const formatArg = (arg: unknown): string => {
|
|
393
427
|
if (typeof arg === "string") return arg;
|
|
394
428
|
if (arg instanceof Error) return arg.stack ?? arg.message;
|
|
@@ -422,6 +456,16 @@ export function setupFileLogging(logDir: string, prefix: string): { logPath: str
|
|
|
422
456
|
// 控制台输出失败也不能拖垮服务
|
|
423
457
|
}
|
|
424
458
|
};
|
|
459
|
+
// warn 同样落盘并兜底:飞书 SDK 等第三方库内部用 console.warn 打日志,
|
|
460
|
+
// 若不走这里(直接写 stderr),restart 后管道断开时会 EPIPE 崩溃。
|
|
461
|
+
console.warn = (...args: unknown[]) => {
|
|
462
|
+
writeLine("WARN", args);
|
|
463
|
+
try {
|
|
464
|
+
origConsoleWarn(...args);
|
|
465
|
+
} catch {
|
|
466
|
+
// 控制台输出失败也不能拖垮服务
|
|
467
|
+
}
|
|
468
|
+
};
|
|
425
469
|
const flush = () => {
|
|
426
470
|
try {
|
|
427
471
|
appendFileSync(logPath, "", "utf8");
|