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/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");