ylib-syim 0.0.5 → 0.0.7

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/bridges/logger.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { format } from "node:util";
4
+ import type { WriteStream } from "node:fs";
4
5
 
5
6
  type ConsoleMethod = (...args: unknown[]) => void;
6
7
 
@@ -27,21 +28,130 @@ function patchConsole(writeLine: (line: string) => void): void {
27
28
  const original = console[method] as ConsoleMethod;
28
29
  console[method] = (...args: unknown[]) => {
29
30
  const line = format(...args);
30
- writeLine(`[${new Date().toISOString()}] [${method}] ${line}`);
31
+ try {
32
+ writeLine(`[${new Date().toISOString()}] [${method}] ${line}`);
33
+ } catch {
34
+ // logger 必须永不影响主进程
35
+ }
31
36
  original(...args);
32
37
  };
33
38
  }
34
39
  }
35
40
 
41
+ function parsePositiveInt(value: unknown, fallback: number): number {
42
+ const n = Number(String(value ?? "").trim());
43
+ if (!Number.isFinite(n)) return fallback;
44
+ if (n <= 0) return fallback;
45
+ return Math.floor(n);
46
+ }
47
+
36
48
  export function setupBridgeLogger(prefix: string): string {
37
49
  const exists = globalThis.__imAgentHubBridgeLogger;
38
50
  if (exists?.enabled) return exists.logFilePath;
39
51
 
40
- const logFilePath = buildLogFilePath(prefix);
41
- const stream = fs.createWriteStream(logFilePath, { flags: "a" });
42
- stream.write(`[${new Date().toISOString()}] [system] bridge logger started\n`);
52
+ try {
53
+ const logFilePath = buildLogFilePath(prefix);
54
+ const maxLines = parsePositiveInt(
55
+ process.env.IM_AGENT_HUB_LOG_MAX_LINES,
56
+ 20000,
57
+ );
58
+ const trimBatch = parsePositiveInt(
59
+ process.env.IM_AGENT_HUB_LOG_TRIM_BATCH,
60
+ 1000,
61
+ );
62
+
63
+ let stream: WriteStream = fs.createWriteStream(logFilePath, { flags: "a" });
64
+ stream.on("error", () => {
65
+ // logger 必须永不影响主进程;出现 stream 错误后,后续写入直接丢弃
66
+ });
67
+ let streamBroken = false;
68
+ stream.on("error", () => {
69
+ streamBroken = true;
70
+ });
71
+ let writtenLines = 0;
72
+ let trimming = false;
73
+ let paused = false;
74
+ const buffer: string[] = [];
75
+
76
+ function writeRaw(lineWithNewline: string): void {
77
+ try {
78
+ if (streamBroken) return;
79
+ if (paused) {
80
+ buffer.push(lineWithNewline);
81
+ return;
82
+ }
83
+ stream.write(lineWithNewline);
84
+ } catch {
85
+ // swallow
86
+ }
87
+ }
43
88
 
44
- patchConsole((line) => stream.write(`${line}\n`));
45
- globalThis.__imAgentHubBridgeLogger = { enabled: true, logFilePath };
46
- return logFilePath;
89
+ async function trimToLastLines(): Promise<void> {
90
+ if (trimming) return;
91
+ trimming = true;
92
+ paused = true;
93
+ try {
94
+ await new Promise<void>((resolve) => {
95
+ try {
96
+ stream.end(() => resolve());
97
+ } catch {
98
+ resolve();
99
+ }
100
+ });
101
+ const text = fs.readFileSync(logFilePath, "utf-8");
102
+ const lines = text.split("\n");
103
+ while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
104
+ const kept = lines.slice(-maxLines);
105
+ fs.writeFileSync(
106
+ logFilePath,
107
+ kept.join("\n") + (kept.length ? "\n" : ""),
108
+ "utf-8",
109
+ );
110
+ stream = fs.createWriteStream(logFilePath, { flags: "a" });
111
+ streamBroken = false;
112
+ stream.on("error", () => {
113
+ streamBroken = true;
114
+ });
115
+ for (const item of buffer) {
116
+ try {
117
+ if (streamBroken) break;
118
+ stream.write(item);
119
+ } catch {
120
+ break;
121
+ }
122
+ }
123
+ buffer.length = 0;
124
+ writtenLines = Math.min(maxLines, kept.length);
125
+ } catch {
126
+ try {
127
+ stream = fs.createWriteStream(logFilePath, { flags: "a" });
128
+ streamBroken = false;
129
+ stream.on("error", () => {
130
+ streamBroken = true;
131
+ });
132
+ } catch {
133
+ streamBroken = true;
134
+ }
135
+ } finally {
136
+ paused = false;
137
+ trimming = false;
138
+ }
139
+ }
140
+
141
+ writeRaw(`[${new Date().toISOString()}] [system] bridge logger started\n`);
142
+ writtenLines += 1;
143
+
144
+ patchConsole((line) => {
145
+ writeRaw(`${line}\n`);
146
+ writtenLines += 1;
147
+ if (maxLines > 0 && writtenLines > maxLines + trimBatch && !trimming) {
148
+ void trimToLastLines();
149
+ }
150
+ });
151
+ globalThis.__imAgentHubBridgeLogger = { enabled: true, logFilePath };
152
+ return logFilePath;
153
+ } catch {
154
+ globalThis.__imAgentHubBridgeLogger = { enabled: false, logFilePath: "" };
155
+ return "";
156
+ }
47
157
  }
package/bridges/main.ts CHANGED
@@ -106,11 +106,7 @@ function applyRemoteRuntimeConfigToPluginHome(): void {
106
106
  } else {
107
107
  console.log(`[bridges/main] plugin temp HOME syim dir exists: ${syimDir}`);
108
108
  }
109
- if (!fs.existsSync(syimPath)) {
110
- fs.writeFileSync(syimPath, payload, "utf-8");
111
- } else {
112
- console.log(`[bridges/main] plugin temp HOME syim dir exists: ${syimPath}`);
113
- }
109
+ fs.writeFileSync(syimPath, payload, "utf-8");
114
110
  const openclawDir = path.join(pluginTempHomeDir, ".openclaw");
115
111
  const openclawPath = path.join(openclawDir, "openclaw.json");
116
112
  if (!fs.existsSync(openclawDir)) {
@@ -118,11 +114,7 @@ function applyRemoteRuntimeConfigToPluginHome(): void {
118
114
  } else {
119
115
  console.log(`[bridges/main] plugin temp HOME openclaw dir exists: ${openclawDir}`);
120
116
  }
121
- if (!fs.existsSync(openclawPath)) {
122
- fs.writeFileSync(openclawPath, payload, "utf-8");
123
- } else {
124
- console.log(`[bridges/main] plugin temp HOME openclaw dir exists: ${openclawPath}`);
125
- }
117
+ fs.writeFileSync(openclawPath, payload, "utf-8");
126
118
  process.env.HOME = pluginTempHomeDir;
127
119
  if (process.platform === "win32") process.env.USERPROFILE = pluginTempHomeDir;
128
120
  console.log(
@@ -979,14 +971,18 @@ async function pullRuntimeConfigFromPython(forceFull = false): Promise<{
979
971
  function startRuntimeConfigPollLoop(): void {
980
972
  if (!runtimeConfigPullUrl) return;
981
973
  if (runtimeConfigPollIntervalMs <= 0) return;
974
+ let recoveryHandled = false;
982
975
  const timer = setInterval(() => {
983
976
  void pullRuntimeConfigFromPython(false).then((result) => {
984
- if (result.ok) {
985
- console.log(
986
- "[bridges/main] poll pull recovered success, stop polling and wait admin restart/start for next config apply",
987
- );
988
- clearInterval(timer);
989
- }
977
+ if (!result.ok) return;
978
+ if (recoveryHandled) return;
979
+ recoveryHandled = true;
980
+ console.log(
981
+ "[bridges/main] poll pull recovered success, stop polling and exit for hard restart",
982
+ );
983
+ clearInterval(timer);
984
+ // 配置源(Python)恢复后硬重启:退出进程,由 PM2/系统守护重新拉起,下次启动走正常拉配置与起 bridge 流程。
985
+ setTimeout(() => process.exit(0), 100);
990
986
  });
991
987
  }, runtimeConfigPollIntervalMs);
992
988
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ylib-syim",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "多 IM / 多 Agent 的会话路由与上下文管理(支持 /new)",
5
5
  "type": "module",
6
6
  "exports": {
@@ -41,7 +41,7 @@
41
41
  "dependencies": {
42
42
  "@ffmpeg-installer/ffmpeg": "^1.1.0",
43
43
  "ylib-dingtalk-connector": "0.7.10-beata.2",
44
- "ylib-openclaw-lark": "2026.3.17-beata.4",
44
+ "ylib-openclaw-lark": "2026.3.17-beata.6",
45
45
  "axios": "^1.6.0",
46
46
  "dingtalk-stream": "^2.1.4",
47
47
  "fluent-ffmpeg": "^2.1.3",