u-foo 1.0.3 → 1.1.9

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.
Files changed (179) hide show
  1. package/README.md +110 -11
  2. package/README.zh-CN.md +9 -7
  3. package/SKILLS/ufoo/SKILL.md +132 -0
  4. package/SKILLS/uinit/SKILL.md +78 -0
  5. package/SKILLS/ustatus/SKILL.md +36 -0
  6. package/bin/uclaude.js +13 -0
  7. package/bin/ucode-core.js +15 -0
  8. package/bin/ucode.js +125 -0
  9. package/bin/ucodex.js +13 -0
  10. package/bin/ufoo +9 -31
  11. package/bin/ufoo-assistant-agent.js +5 -0
  12. package/bin/ufoo-engine.js +25 -0
  13. package/bin/ufoo.js +17 -0
  14. package/modules/AGENTS.template.md +29 -11
  15. package/modules/bus/README.md +33 -25
  16. package/modules/bus/SKILLS/ubus/SKILL.md +19 -8
  17. package/modules/context/README.md +18 -40
  18. package/modules/context/SKILLS/uctx/SKILL.md +63 -1
  19. package/modules/online/SKILLS/ufoo-online/SKILL.md +144 -0
  20. package/package.json +25 -4
  21. package/scripts/import-pi-mono.js +124 -0
  22. package/scripts/postinstall.js +30 -0
  23. package/scripts/sync-claude-skills.sh +21 -0
  24. package/src/agent/cliRunner.js +554 -33
  25. package/src/agent/internalRunner.js +150 -56
  26. package/src/agent/launcher.js +754 -0
  27. package/src/agent/normalizeOutput.js +1 -1
  28. package/src/agent/notifier.js +340 -0
  29. package/src/agent/ptyRunner.js +847 -0
  30. package/src/agent/ptyWrapper.js +379 -0
  31. package/src/agent/readyDetector.js +175 -0
  32. package/src/agent/ucode.js +443 -0
  33. package/src/agent/ucodeBootstrap.js +113 -0
  34. package/src/agent/ucodeBuild.js +67 -0
  35. package/src/agent/ucodeDoctor.js +184 -0
  36. package/src/agent/ucodeRuntimeConfig.js +129 -0
  37. package/src/agent/ufooAgent.js +46 -42
  38. package/src/assistant/agent.js +260 -0
  39. package/src/assistant/bridge.js +172 -0
  40. package/src/assistant/engine.js +252 -0
  41. package/src/assistant/stdio.js +58 -0
  42. package/src/assistant/ufooEngineCli.js +306 -0
  43. package/src/bus/activate.js +172 -0
  44. package/src/bus/daemon.js +436 -0
  45. package/src/bus/index.js +842 -0
  46. package/src/bus/inject.js +315 -0
  47. package/src/bus/message.js +430 -0
  48. package/src/bus/nickname.js +88 -0
  49. package/src/bus/queue.js +136 -0
  50. package/src/bus/shake.js +26 -0
  51. package/src/bus/store.js +189 -0
  52. package/src/bus/subscriber.js +312 -0
  53. package/src/bus/utils.js +363 -0
  54. package/src/chat/agentBar.js +117 -0
  55. package/src/chat/agentDirectory.js +88 -0
  56. package/src/chat/agentSockets.js +225 -0
  57. package/src/chat/agentViewController.js +298 -0
  58. package/src/chat/chatLogController.js +115 -0
  59. package/src/chat/commandExecutor.js +700 -0
  60. package/src/chat/commands.js +132 -0
  61. package/src/chat/completionController.js +414 -0
  62. package/src/chat/cronScheduler.js +160 -0
  63. package/src/chat/daemonConnection.js +166 -0
  64. package/src/chat/daemonCoordinator.js +64 -0
  65. package/src/chat/daemonMessageRouter.js +257 -0
  66. package/src/chat/daemonReconnect.js +41 -0
  67. package/src/chat/daemonTransport.js +36 -0
  68. package/src/chat/daemonTransportDefaults.js +10 -0
  69. package/src/chat/dashboardKeyController.js +480 -0
  70. package/src/chat/dashboardView.js +154 -0
  71. package/src/chat/index.js +1011 -1392
  72. package/src/chat/inputHistoryController.js +105 -0
  73. package/src/chat/inputListenerController.js +304 -0
  74. package/src/chat/inputMath.js +104 -0
  75. package/src/chat/inputSubmitHandler.js +171 -0
  76. package/src/chat/layout.js +165 -0
  77. package/src/chat/pasteController.js +81 -0
  78. package/src/chat/rawKeyMap.js +42 -0
  79. package/src/chat/settingsController.js +132 -0
  80. package/src/chat/statusLineController.js +177 -0
  81. package/src/chat/streamTracker.js +138 -0
  82. package/src/chat/text.js +70 -0
  83. package/src/chat/transport.js +61 -0
  84. package/src/cli/busCoreCommands.js +59 -0
  85. package/src/cli/ctxCoreCommands.js +199 -0
  86. package/src/cli/onlineCoreCommands.js +379 -0
  87. package/src/cli.js +1162 -96
  88. package/src/code/README.md +29 -0
  89. package/src/code/UCODE_PROMPT.md +32 -0
  90. package/src/code/agent.js +1651 -0
  91. package/src/code/cli.js +158 -0
  92. package/src/code/config +0 -0
  93. package/src/code/dispatch.js +42 -0
  94. package/src/code/index.js +70 -0
  95. package/src/code/nativeRunner.js +1213 -0
  96. package/src/code/runtime.js +154 -0
  97. package/src/code/sessionStore.js +162 -0
  98. package/src/code/taskDecomposer.js +269 -0
  99. package/src/code/tools/bash.js +53 -0
  100. package/src/code/tools/common.js +42 -0
  101. package/src/code/tools/edit.js +70 -0
  102. package/src/code/tools/read.js +44 -0
  103. package/src/code/tools/write.js +35 -0
  104. package/src/code/tui.js +1580 -0
  105. package/src/config.js +56 -3
  106. package/src/context/decisions.js +324 -0
  107. package/src/context/doctor.js +183 -0
  108. package/src/context/index.js +55 -0
  109. package/src/context/sync.js +127 -0
  110. package/src/daemon/agentProcessManager.js +74 -0
  111. package/src/daemon/cronOps.js +241 -0
  112. package/src/daemon/index.js +998 -170
  113. package/src/daemon/ipcServer.js +99 -0
  114. package/src/daemon/ops.js +630 -48
  115. package/src/daemon/promptLoop.js +319 -0
  116. package/src/daemon/promptRequest.js +101 -0
  117. package/src/daemon/providerSessions.js +306 -0
  118. package/src/daemon/reporting.js +90 -0
  119. package/src/daemon/run.js +31 -1
  120. package/src/daemon/status.js +48 -8
  121. package/src/doctor/index.js +50 -0
  122. package/src/init/index.js +318 -0
  123. package/src/online/bridge.js +663 -0
  124. package/src/online/client.js +245 -0
  125. package/src/online/runner.js +253 -0
  126. package/src/online/server.js +992 -0
  127. package/src/online/tokens.js +103 -0
  128. package/src/report/store.js +331 -0
  129. package/src/shared/eventContract.js +35 -0
  130. package/src/shared/ptySocketContract.js +21 -0
  131. package/src/skills/index.js +159 -0
  132. package/src/status/index.js +285 -0
  133. package/src/terminal/adapterContract.js +87 -0
  134. package/src/terminal/adapterRouter.js +84 -0
  135. package/src/terminal/adapters/externalAdapter.js +14 -0
  136. package/src/terminal/adapters/internalAdapter.js +13 -0
  137. package/src/terminal/adapters/internalPtyAdapter.js +42 -0
  138. package/src/terminal/adapters/internalQueueAdapter.js +37 -0
  139. package/src/terminal/adapters/terminalAdapter.js +31 -0
  140. package/src/terminal/adapters/tmuxAdapter.js +30 -0
  141. package/src/terminal/detect.js +64 -0
  142. package/src/terminal/index.js +8 -0
  143. package/src/terminal/iterm2.js +126 -0
  144. package/src/ufoo/agentsStore.js +107 -0
  145. package/src/ufoo/paths.js +46 -0
  146. package/src/utils/banner.js +76 -0
  147. package/bin/uclaude +0 -65
  148. package/bin/ucodex +0 -65
  149. package/modules/bus/scripts/bus-alert.sh +0 -185
  150. package/modules/bus/scripts/bus-listen.sh +0 -117
  151. package/modules/context/ASSUMPTIONS.md +0 -7
  152. package/modules/context/CONSTRAINTS.md +0 -7
  153. package/modules/context/CONTEXT-STRUCTURE.md +0 -49
  154. package/modules/context/DECISION-PROTOCOL.md +0 -62
  155. package/modules/context/HANDOFF.md +0 -33
  156. package/modules/context/RULES.md +0 -15
  157. package/modules/context/SKILLS/README.md +0 -14
  158. package/modules/context/SYSTEM.md +0 -18
  159. package/modules/context/TEMPLATES/assumptions.md +0 -4
  160. package/modules/context/TEMPLATES/constraints.md +0 -4
  161. package/modules/context/TEMPLATES/decision.md +0 -16
  162. package/modules/context/TEMPLATES/project-context-readme.md +0 -6
  163. package/modules/context/TEMPLATES/system.md +0 -3
  164. package/modules/context/TEMPLATES/terminology.md +0 -4
  165. package/modules/context/TERMINOLOGY.md +0 -10
  166. package/scripts/banner.sh +0 -89
  167. package/scripts/bus-alert.sh +0 -6
  168. package/scripts/bus-autotrigger.sh +0 -6
  169. package/scripts/bus-daemon.sh +0 -231
  170. package/scripts/bus-inject.sh +0 -144
  171. package/scripts/bus-listen.sh +0 -6
  172. package/scripts/bus.sh +0 -984
  173. package/scripts/context-decisions.sh +0 -167
  174. package/scripts/context-doctor.sh +0 -72
  175. package/scripts/context-lint.sh +0 -110
  176. package/scripts/doctor.sh +0 -22
  177. package/scripts/init.sh +0 -247
  178. package/scripts/skills.sh +0 -113
  179. package/scripts/status.sh +0 -125
@@ -0,0 +1,436 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { readJSON, writeJSON, isPidAlive, isAgentPidAlive, ensureDir, safeNameToSubscriber, subscriberToSafeName } = require("./utils");
4
+ const Injector = require("./inject");
5
+ const QueueManager = require("./queue");
6
+ const MessageManager = require("./message");
7
+ const { createTerminalAdapterRouter } = require("../terminal/adapterRouter");
8
+
9
+ /**
10
+ * Bus Daemon - 监控消息并自动注入命令
11
+ */
12
+ class BusDaemon {
13
+ constructor(busDir, agentsFile, daemonDir, interval = 2000) {
14
+ this.busDir = busDir;
15
+ this.agentsFile = agentsFile;
16
+ this.interval = interval;
17
+ this.daemonDir = daemonDir;
18
+ this.pidFile = path.join(this.daemonDir, "daemon.pid");
19
+ this.logFile = path.join(this.daemonDir, "daemon.log");
20
+ this.countsDir = path.join(this.daemonDir, "counts", `${process.pid}`);
21
+ this.running = false;
22
+ this.cleanupCounter = 0;
23
+ this.cleanupInterval = 5; // 每 5 个周期清理一次
24
+
25
+ this.queueManager = new QueueManager(busDir);
26
+ this.injector = new Injector(busDir, agentsFile);
27
+ this.adapterRouter = createTerminalAdapterRouter();
28
+ }
29
+
30
+ /**
31
+ * 检查 daemon 是否正在运行
32
+ */
33
+ isRunning() {
34
+ if (!fs.existsSync(this.pidFile)) {
35
+ return false;
36
+ }
37
+
38
+ const pid = parseInt(fs.readFileSync(this.pidFile, "utf8").trim(), 10);
39
+ return isPidAlive(pid);
40
+ }
41
+
42
+ /**
43
+ * 获取运行中的 daemon PID
44
+ */
45
+ getRunningPid() {
46
+ if (!fs.existsSync(this.pidFile)) {
47
+ return null;
48
+ }
49
+
50
+ const pid = parseInt(fs.readFileSync(this.pidFile, "utf8").trim(), 10);
51
+ return isPidAlive(pid) ? pid : null;
52
+ }
53
+
54
+ /**
55
+ * 启动 daemon
56
+ */
57
+ async start(background = false) {
58
+ // 检查是否已经在运行
59
+ if (this.isRunning()) {
60
+ const pid = this.getRunningPid();
61
+ console.log(`[daemon] Already running (pid=${pid})`);
62
+ return;
63
+ }
64
+ ensureDir(this.daemonDir);
65
+ ensureDir(path.join(this.daemonDir, "counts"));
66
+
67
+ if (background) {
68
+ // 后台模式:spawn 独立进程
69
+ const { spawn } = require("child_process");
70
+ const logStream = fs.openSync(this.logFile, "a");
71
+
72
+ const child = spawn(
73
+ process.execPath,
74
+ [
75
+ path.join(__dirname, "..", "..", "bin", "ufoo.js"),
76
+ "bus",
77
+ "daemon",
78
+ "--interval",
79
+ String(this.interval / 1000),
80
+ ],
81
+ {
82
+ detached: true,
83
+ stdio: ["ignore", logStream, logStream],
84
+ cwd: process.cwd(),
85
+ }
86
+ );
87
+
88
+ child.unref();
89
+
90
+ // 等待 PID 文件创建
91
+ await new Promise((resolve) => setTimeout(resolve, 500));
92
+
93
+ const pid = this.getRunningPid();
94
+ console.log(`[daemon] Started in background (pid=${pid}, log: ${this.logFile})`);
95
+ } else {
96
+ // 前台模式
97
+ this.run();
98
+ }
99
+ }
100
+
101
+ /**
102
+ * 停止 daemon
103
+ */
104
+ stop() {
105
+ const pid = this.getRunningPid();
106
+ if (!pid) {
107
+ console.log("[daemon] Not running");
108
+ return;
109
+ }
110
+
111
+ try {
112
+ process.kill(pid, "SIGTERM");
113
+ console.log(`[daemon] Stopped (pid=${pid})`);
114
+ if (fs.existsSync(this.pidFile)) {
115
+ fs.unlinkSync(this.pidFile);
116
+ }
117
+ } catch (err) {
118
+ console.error(`[daemon] Failed to stop: ${err.message}`);
119
+ }
120
+ }
121
+
122
+ /**
123
+ * 显示 daemon 状态
124
+ */
125
+ status() {
126
+ const pid = this.getRunningPid();
127
+ if (pid) {
128
+ console.log(`[daemon] Running (pid=${pid})`);
129
+ } else {
130
+ console.log("[daemon] Not running");
131
+ // 清理过时的 PID 文件
132
+ if (fs.existsSync(this.pidFile)) {
133
+ fs.unlinkSync(this.pidFile);
134
+ }
135
+ }
136
+ }
137
+
138
+ /**
139
+ * 运行 daemon(前台)
140
+ */
141
+ run() {
142
+ // 记录 PID
143
+ ensureDir(path.dirname(this.pidFile));
144
+ fs.writeFileSync(this.pidFile, `${process.pid}\n`, "utf8");
145
+
146
+ // 设置清理钩子
147
+ const cleanup = () => {
148
+ this.running = false;
149
+ if (fs.existsSync(this.pidFile)) {
150
+ fs.unlinkSync(this.pidFile);
151
+ }
152
+ if (fs.existsSync(this.countsDir)) {
153
+ fs.rmSync(this.countsDir, { recursive: true, force: true });
154
+ }
155
+ };
156
+
157
+ process.on("SIGTERM", cleanup);
158
+ process.on("SIGINT", cleanup);
159
+ process.on("exit", cleanup);
160
+
161
+ // 创建计数目录
162
+ ensureDir(this.countsDir);
163
+
164
+ console.log(`[daemon] Started (pid=${process.pid}, interval=${this.interval / 1000}s)`);
165
+ console.log(`[daemon] Watching: ${this.busDir}/queues/*/pending.jsonl`);
166
+
167
+ this.running = true;
168
+ this.watchLoop();
169
+ }
170
+
171
+ /**
172
+ * 主监控循环
173
+ */
174
+ async watchLoop() {
175
+ while (this.running) {
176
+ try {
177
+ // 定期清理死掉的 agent
178
+ this.cleanupCounter++;
179
+ if (this.cleanupCounter >= this.cleanupInterval) {
180
+ await this.cleanupDeadAgents();
181
+ this.cleanupCounter = 0;
182
+ }
183
+
184
+ // 检查所有订阅者的队列
185
+ await this.checkQueues();
186
+ } catch (err) {
187
+ console.error(`[daemon] Error: ${err.message}`);
188
+ }
189
+
190
+ // 等待下一个周期
191
+ await new Promise((resolve) => setTimeout(resolve, this.interval));
192
+ }
193
+ }
194
+
195
+ /**
196
+ * 检查所有队列
197
+ */
198
+ async checkQueues() {
199
+ const queuesDir = path.join(this.busDir, "queues");
200
+ if (!fs.existsSync(queuesDir)) {
201
+ return;
202
+ }
203
+
204
+ const busData = readJSON(this.agentsFile) || { agents: {} };
205
+ const messageManager = new MessageManager(this.busDir, busData, this.queueManager);
206
+ const subscribers = fs.readdirSync(queuesDir);
207
+
208
+ for (const safeName of subscribers) {
209
+ const pendingFile = path.join(queuesDir, safeName, "pending.jsonl");
210
+ if (!fs.existsSync(pendingFile)) {
211
+ continue;
212
+ }
213
+
214
+ const subscriber = safeNameToSubscriber(safeName);
215
+ const meta = busData.agents?.[subscriber];
216
+ const launchMode = meta?.launch_mode || "";
217
+ // Delivery ownership:
218
+ // - notifier/injector: terminal/tmux
219
+ // - internal queue loop: internal/internal-pty
220
+ // Bus daemon only handles legacy/unknown launch modes.
221
+ const adapter = this.adapterRouter.getAdapter({ launchMode, agentId: subscriber });
222
+ const { supportsNotifierInjector, supportsInternalQueueLoop } = adapter.capabilities;
223
+ if (launchMode && (supportsNotifierInjector || supportsInternalQueueLoop)) {
224
+ continue;
225
+ }
226
+
227
+ // 获取当前消息数
228
+ let count = 0;
229
+ if (fs.statSync(pendingFile).size > 0) {
230
+ const content = fs.readFileSync(pendingFile, "utf8").trim();
231
+ count = content ? content.split("\n").length : 0;
232
+ }
233
+
234
+ // 获取上次的消息数
235
+ const lastCount = this.getLastCount(safeName);
236
+
237
+ // 如果有新消息,注入命令
238
+ const wakePath = path.join(queuesDir, safeName, "wake");
239
+ const wakeActive = fs.existsSync(wakePath);
240
+
241
+ if (count > 0 || wakeActive) {
242
+ const now = new Date().toISOString().split("T")[1].slice(0, 8);
243
+ const note = wakeActive && count <= lastCount ? " (wake)" : "";
244
+ console.log(`[daemon] ${now} New message for ${subscriber} (${lastCount} -> ${count})${note}`);
245
+
246
+ try {
247
+ const agentType = String((meta && meta.agent_type) || "").trim().toLowerCase();
248
+ const isUfooCode = subscriber.startsWith("ufoo-code:")
249
+ || agentType === "ufoo-code"
250
+ || agentType === "ucode"
251
+ || agentType === "ufoo";
252
+ if (isUfooCode) {
253
+ // ufoo-code queue is consumed internally by ucode itself.
254
+ // Bus daemon should not inject any command/text into terminal.
255
+ if (wakeActive) fs.rmSync(wakePath, { force: true });
256
+ this.setLastCount(safeName, count);
257
+ continue;
258
+ }
259
+
260
+ const events = this.drainPending(pendingFile);
261
+ const failed = [];
262
+ for (const evt of events) {
263
+ if (!evt || evt.event !== "message" || !evt.data || typeof evt.data.message !== "string") {
264
+ continue;
265
+ }
266
+ try {
267
+ // eslint-disable-next-line no-await-in-loop
268
+ await this.injector.inject(subscriber, String(evt.data.message));
269
+ } catch (err) {
270
+ failed.push(evt);
271
+ try {
272
+ const pub = typeof evt.publisher === "object" && evt.publisher
273
+ ? (evt.publisher.subscriber || evt.publisher.nickname || "")
274
+ : (evt.publisher || "");
275
+ if (pub) {
276
+ // eslint-disable-next-line no-await-in-loop
277
+ await messageManager.emit(pub, "delivery", {
278
+ target: subscriber,
279
+ seq: evt.seq,
280
+ status: "error",
281
+ message: `delivery failed to ${meta?.nickname || subscriber}: ${err.message || "inject failed"}`,
282
+ }, subscriber, "status/delivery");
283
+ }
284
+ } catch {
285
+ // ignore delivery emit errors
286
+ }
287
+ continue;
288
+ }
289
+ try {
290
+ // Emit delivery status back to publisher (best-effort)
291
+ const pub = typeof evt.publisher === "object" && evt.publisher
292
+ ? (evt.publisher.subscriber || evt.publisher.nickname || "")
293
+ : (evt.publisher || "");
294
+ if (pub) {
295
+ // eslint-disable-next-line no-await-in-loop
296
+ await messageManager.emit(pub, "delivery", {
297
+ target: subscriber,
298
+ seq: evt.seq,
299
+ status: "ok",
300
+ message: `delivered to ${meta?.nickname || subscriber}`,
301
+ }, subscriber, "status/delivery");
302
+ }
303
+ } catch {
304
+ // ignore delivery emit errors
305
+ }
306
+ }
307
+ if (failed.length > 0) {
308
+ try {
309
+ const content = failed.map((e) => JSON.stringify(e)).join("\n") + "\n";
310
+ fs.appendFileSync(pendingFile, content, "utf8");
311
+ } catch {
312
+ // ignore requeue failures
313
+ }
314
+ }
315
+ console.log(`[daemon] Delivered ${events.length} message(s) to ${subscriber}`);
316
+ if (wakeActive) fs.rmSync(wakePath, { force: true });
317
+ } catch (err) {
318
+ console.error(`[daemon] Failed to inject: ${err.message}`);
319
+ }
320
+ }
321
+
322
+ // 更新计数
323
+ this.setLastCount(safeName, count);
324
+ }
325
+ }
326
+
327
+ drainPending(pendingFile) {
328
+ if (!fs.existsSync(pendingFile)) return [];
329
+ const processingFile = `${pendingFile}.processing.${process.pid}.${Date.now()}`;
330
+ let content = "";
331
+ let readOk = false;
332
+ try {
333
+ fs.renameSync(pendingFile, processingFile);
334
+ content = fs.readFileSync(processingFile, "utf8");
335
+ readOk = true;
336
+ } catch {
337
+ try {
338
+ if (fs.existsSync(processingFile)) {
339
+ fs.renameSync(processingFile, pendingFile);
340
+ }
341
+ } catch {
342
+ // ignore rollback errors
343
+ }
344
+ return [];
345
+ } finally {
346
+ if (readOk) {
347
+ try {
348
+ if (fs.existsSync(processingFile)) {
349
+ fs.rmSync(processingFile, { force: true });
350
+ }
351
+ } catch {
352
+ // ignore cleanup errors
353
+ }
354
+ }
355
+ }
356
+ if (!content.trim()) return [];
357
+ return content.split(/\r?\n/).filter(Boolean).map((line) => {
358
+ try {
359
+ return JSON.parse(line);
360
+ } catch {
361
+ return null;
362
+ }
363
+ }).filter(Boolean);
364
+ }
365
+
366
+ /**
367
+ * 获取上次的消息计数
368
+ */
369
+ getLastCount(safeName) {
370
+ const countFile = path.join(this.countsDir, safeName);
371
+ if (!fs.existsSync(countFile)) {
372
+ return 0;
373
+ }
374
+ const content = fs.readFileSync(countFile, "utf8").trim();
375
+ return parseInt(content, 10) || 0;
376
+ }
377
+
378
+ /**
379
+ * 设置消息计数
380
+ */
381
+ setLastCount(safeName, count) {
382
+ const countFile = path.join(this.countsDir, safeName);
383
+ ensureDir(path.dirname(countFile));
384
+ fs.writeFileSync(countFile, `${count}\n`, "utf8");
385
+ }
386
+
387
+ /**
388
+ * 清理死掉的 agent
389
+ */
390
+ async cleanupDeadAgents() {
391
+ const agentsFile = this.agentsFile;
392
+ if (!fs.existsSync(agentsFile)) {
393
+ return;
394
+ }
395
+
396
+ const busData = readJSON(agentsFile);
397
+ if (!busData || !busData.agents) {
398
+ return;
399
+ }
400
+
401
+ let changed = false;
402
+
403
+ for (const [subscriber, meta] of Object.entries(busData.agents)) {
404
+ if (meta.status !== "active") {
405
+ continue;
406
+ }
407
+
408
+ // 检查 PID 是否仍然存活
409
+ if (meta.pid && !isAgentPidAlive(meta.pid)) {
410
+ const now = new Date().toISOString().split("T")[1].slice(0, 8);
411
+ console.log(`[daemon] ${now} Agent ${subscriber} (pid=${meta.pid}) is dead, marking inactive`);
412
+
413
+ meta.status = "inactive";
414
+ changed = true;
415
+
416
+ // 清理队列目录和 offset
417
+ const safeName = subscriberToSafeName(subscriber);
418
+ const queueDir = path.join(this.busDir, "queues", safeName);
419
+ const offsetFile = path.join(this.busDir, "offsets", `${safeName}.offset`);
420
+
421
+ if (fs.existsSync(queueDir)) {
422
+ fs.rmSync(queueDir, { recursive: true, force: true });
423
+ }
424
+ if (fs.existsSync(offsetFile)) {
425
+ fs.unlinkSync(offsetFile);
426
+ }
427
+ }
428
+ }
429
+
430
+ if (changed) {
431
+ writeJSON(agentsFile, busData);
432
+ }
433
+ }
434
+ }
435
+
436
+ module.exports = BusDaemon;