u-foo 2.5.1 → 2.5.2

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.
@@ -13,6 +13,8 @@ const {
13
13
  const NicknameManager = require("./nickname");
14
14
  const { buildMessageData } = require("./messageMeta");
15
15
  const { getUfooPaths } = require("../state/paths");
16
+ const { applyProjectNicknamePrefix } = require("../../runtime/daemon/nicknameScope");
17
+ const { QUEUE_TYPES, normalizeQueueEnvelope } = require("./deliveryQueue");
16
18
 
17
19
  const SEQ_LOCK_TIMEOUT_MS = 5000;
18
20
  const SEQ_LOCK_POLL_MS = 25;
@@ -222,7 +224,19 @@ class MessageManager {
222
224
  return [byNickname];
223
225
  }
224
226
 
225
- // 3. 尝试作为代理类型(匹配所有该类型的订阅者)
227
+ // 3. Try the project-scoped nickname form. Group prompts intentionally show
228
+ // short names, while older/runtime metadata may only contain scoped names.
229
+ if (this.projectRoot) {
230
+ const scopedTarget = applyProjectNicknamePrefix(this.projectRoot, target);
231
+ if (scopedTarget && scopedTarget !== target) {
232
+ const byScopedNickname = nicknameManager.resolveNickname(scopedTarget);
233
+ if (byScopedNickname) {
234
+ return [byScopedNickname];
235
+ }
236
+ }
237
+ }
238
+
239
+ // 4. 尝试作为代理类型(匹配所有该类型的订阅者)
226
240
  const isActive = (meta) => !meta || meta.status === "active";
227
241
 
228
242
  const byType = Object.entries(subscribers)
@@ -233,7 +247,7 @@ class MessageManager {
233
247
  return byType;
234
248
  }
235
249
 
236
- // 4. 通配符(所有活跃订阅者)
250
+ // 5. 通配符(所有活跃订阅者)
237
251
  if (target === "*") {
238
252
  return Object.entries(subscribers)
239
253
  .filter(([, meta]) => isActive(meta))
@@ -257,7 +271,14 @@ class MessageManager {
257
271
  if (meta && normalizedTarget === normalizeAgentTypeAlias(meta.agent_type)) return true;
258
272
 
259
273
  // 昵称匹配
260
- if (meta && (target === meta.nickname || target === meta.scoped_nickname)) return true;
274
+ if (meta) {
275
+ const candidates = [target];
276
+ if (this.projectRoot) {
277
+ const scopedTarget = applyProjectNicknamePrefix(this.projectRoot, target);
278
+ if (scopedTarget && scopedTarget !== target) candidates.push(scopedTarget);
279
+ }
280
+ if (candidates.includes(meta.nickname) || candidates.includes(meta.scoped_nickname)) return true;
281
+ }
261
282
 
262
283
  // 通配符
263
284
  if (target === "*") return true;
@@ -426,17 +447,22 @@ class MessageManager {
426
447
  target,
427
448
  data,
428
449
  };
450
+ const queueEvent = normalizeQueueEnvelope(event, {
451
+ queueType: QUEUE_TYPES.AGENT_MESSAGE,
452
+ delivery: { mode: "inject", gate: "idle", max_inflight: 1 },
453
+ ack: { policy: "on_delivery" },
454
+ });
429
455
 
430
456
  // 写入事件日志
431
457
  const eventFile = path.join(this.eventsDir, `${date}.jsonl`);
432
- appendJSONL(eventFile, event);
458
+ appendJSONL(eventFile, queueEvent);
433
459
 
434
460
  // 为每个目标订阅者添加到待处理队列
435
461
  for (const targetSubscriber of targets) {
436
462
  // 检查订阅者的 offset,如果已经消费过这个 seq,不再添加
437
463
  const offset = await this.queueManager.getOffset(targetSubscriber);
438
464
  if (seq > offset) {
439
- await this.queueManager.appendPending(targetSubscriber, event);
465
+ await this.queueManager.appendPending(targetSubscriber, queueEvent);
440
466
  }
441
467
  }
442
468
 
@@ -473,14 +499,15 @@ class MessageManager {
473
499
  target,
474
500
  data,
475
501
  };
502
+ const queueEvent = normalizeQueueEnvelope(event);
476
503
 
477
504
  const eventFile = path.join(this.eventsDir, `${date}.jsonl`);
478
- appendJSONL(eventFile, event);
505
+ appendJSONL(eventFile, queueEvent);
479
506
 
480
507
  for (const targetSubscriber of targets) {
481
508
  const offset = await this.queueManager.getOffset(targetSubscriber);
482
509
  if (seq > offset) {
483
- await this.queueManager.appendPending(targetSubscriber, event);
510
+ await this.queueManager.appendPending(targetSubscriber, queueEvent);
484
511
  }
485
512
  }
486
513
 
@@ -499,14 +526,7 @@ class MessageManager {
499
526
  * 确认消息(清空待处理队列)
500
527
  */
501
528
  async ack(subscriber) {
502
- const pending = await this.queueManager.readPending(subscriber);
503
- const count = pending.length;
504
-
505
- if (count > 0) {
506
- await this.queueManager.clearPending(subscriber);
507
- }
508
-
509
- return count;
529
+ return this.queueManager.ackPending(subscriber);
510
530
  }
511
531
 
512
532
  /**
@@ -3,10 +3,9 @@ const path = require("path");
3
3
  const {
4
4
  subscriberToSafeName,
5
5
  ensureDir,
6
- readJSONL,
7
- appendJSONL,
8
6
  truncateFile,
9
7
  } = require("./utils");
8
+ const { DeliveryQueue, stripQueueEnvelope } = require("./deliveryQueue");
10
9
 
11
10
  /**
12
11
  * 队列管理器
@@ -49,6 +48,10 @@ class QueueManager {
49
48
  return path.join(this.getQueueDir(subscriber), "pending.jsonl");
50
49
  }
51
50
 
51
+ getDeliveryQueue(subscriber) {
52
+ return new DeliveryQueue(this.getPendingPath(subscriber));
53
+ }
54
+
52
55
  /**
53
56
  * 获取 tty 文件路径
54
57
  */
@@ -81,17 +84,15 @@ class QueueManager {
81
84
  * 读取待处理消息
82
85
  */
83
86
  async readPending(subscriber) {
84
- const pendingPath = this.getPendingPath(subscriber);
85
- return readJSONL(pendingPath);
87
+ return this.getDeliveryQueue(subscriber).readPending().map(stripQueueEnvelope);
86
88
  }
87
89
 
88
90
  /**
89
91
  * 追加待处理消息
90
92
  */
91
93
  async appendPending(subscriber, event) {
92
- this.ensureQueueDir(subscriber);
93
- const pendingPath = this.getPendingPath(subscriber);
94
- appendJSONL(pendingPath, event);
94
+ const deliveryQueue = this.getDeliveryQueue(subscriber);
95
+ deliveryQueue.append(event);
95
96
  if (event && event.event === "wake") {
96
97
  const wakePath = path.join(this.getQueueDir(subscriber), "wake");
97
98
  fs.writeFileSync(wakePath, String(event.seq || Date.now()), "utf8");
@@ -106,6 +107,21 @@ class QueueManager {
106
107
  truncateFile(pendingPath);
107
108
  }
108
109
 
110
+ /**
111
+ * 确认待处理消息
112
+ */
113
+ async ackPending(subscriber) {
114
+ const deliveryQueue = this.getDeliveryQueue(subscriber);
115
+ let count = 0;
116
+ while (true) {
117
+ const claim = deliveryQueue.claimNext();
118
+ if (!claim) break;
119
+ deliveryQueue.completeClaim(claim);
120
+ count += 1;
121
+ }
122
+ return count;
123
+ }
124
+
109
125
  /**
110
126
  * 检查是否有待处理消息
111
127
  */
@@ -0,0 +1,205 @@
1
+ const fs = require("fs");
2
+ const { getUfooPaths } = require("../../coordination/state/paths");
3
+ const { DeliveryQueue } = require("../../coordination/bus/deliveryQueue");
4
+ const Injector = require("../../coordination/bus/inject");
5
+ const { buildPromptInjectionText } = require("../../coordination/bus/promptEnvelope");
6
+ const { createTerminalAdapterRouter } = require("../terminal/adapterRouter");
7
+ const { normalizeQueueEnvelope } = require("../../coordination/bus/deliveryQueue");
8
+
9
+ function asState(value = "") {
10
+ return String(value || "").trim().toLowerCase();
11
+ }
12
+
13
+ function isDeliverableActivityState(value = "") {
14
+ const state = asState(value);
15
+ return state === "idle" || state === "ready";
16
+ }
17
+
18
+ function readAgentsFile(agentsFile) {
19
+ try {
20
+ if (!agentsFile || !fs.existsSync(agentsFile)) return { agents: {} };
21
+ const parsed = JSON.parse(fs.readFileSync(agentsFile, "utf8"));
22
+ if (!parsed || typeof parsed !== "object") return { agents: {} };
23
+ if (!parsed.agents || typeof parsed.agents !== "object") parsed.agents = {};
24
+ return parsed;
25
+ } catch {
26
+ return { agents: {} };
27
+ }
28
+ }
29
+
30
+ class DeliveryScheduler {
31
+ constructor(projectRoot, options = {}) {
32
+ this.projectRoot = projectRoot;
33
+ this.paths = getUfooPaths(projectRoot);
34
+ this.injector = options.injector || new Injector(this.paths.busDir, this.paths.agentsFile);
35
+ this.queueFactory = typeof options.queueFactory === "function"
36
+ ? options.queueFactory
37
+ : (subscriber) => DeliveryQueue.forSubscriber(this.paths.busDir, subscriber);
38
+ this.buildInjectionText = typeof options.buildInjectionText === "function"
39
+ ? options.buildInjectionText
40
+ : buildPromptInjectionText;
41
+ this.readAgents = typeof options.readAgents === "function"
42
+ ? options.readAgents
43
+ : () => readAgentsFile(this.paths.agentsFile);
44
+ this.emitDelivery = typeof options.emitDelivery === "function"
45
+ ? options.emitDelivery
46
+ : async () => {};
47
+ this.log = typeof options.log === "function" ? options.log : () => {};
48
+ this.intervalMs = Number.isFinite(options.intervalMs) && options.intervalMs > 0
49
+ ? options.intervalMs
50
+ : 1000;
51
+ this.adapterRouter = options.adapterRouter || createTerminalAdapterRouter();
52
+ this.locks = new Set();
53
+ this.timer = null;
54
+ this.running = false;
55
+ }
56
+
57
+ getAgentMeta(subscriber) {
58
+ const data = this.readAgents() || { agents: {} };
59
+ const agents = data.agents && typeof data.agents === "object" ? data.agents : {};
60
+ return {
61
+ agents,
62
+ meta: agents[subscriber] || null,
63
+ };
64
+ }
65
+
66
+ shouldDeliver(subscriber) {
67
+ const { meta } = this.getAgentMeta(subscriber);
68
+ if (!meta || meta.status === "inactive") {
69
+ return { ok: false, reason: "missing_or_inactive" };
70
+ }
71
+ const launchMode = String(meta.launch_mode || "").trim();
72
+ const adapter = this.adapterRouter.getAdapter({ launchMode, agentId: subscriber, meta });
73
+ if (!adapter.capabilities.supportsNotifierInjector) {
74
+ return { ok: false, reason: launchMode ? "unsupported_launch_mode" : "missing_launch_mode" };
75
+ }
76
+ const activityState = asState(meta.activity_state);
77
+ if (!isDeliverableActivityState(activityState)) {
78
+ return { ok: false, reason: activityState || "unknown_activity_state" };
79
+ }
80
+ return { ok: true, reason: "deliverable" };
81
+ }
82
+
83
+ async deliverSubscriber(subscriber) {
84
+ if (!subscriber) return { ok: false, delivered: 0, reason: "missing_subscriber" };
85
+ if (this.locks.has(subscriber)) {
86
+ return { ok: true, delivered: 0, deferred: true, reason: "locked" };
87
+ }
88
+
89
+ this.locks.add(subscriber);
90
+ try {
91
+ const gate = this.shouldDeliver(subscriber);
92
+ if (!gate.ok) {
93
+ return { ok: true, delivered: 0, deferred: true, reason: gate.reason };
94
+ }
95
+
96
+ const queue = this.queueFactory(subscriber);
97
+ const claim = queue.claimNext();
98
+ if (!claim) {
99
+ return { ok: true, delivered: 0, reason: "empty" };
100
+ }
101
+
102
+ const evt = claim.event;
103
+ const envelope = normalizeQueueEnvelope(evt || {});
104
+ const delivery = envelope.delivery || {};
105
+ if (delivery.mode !== "inject") {
106
+ queue.completeClaim(claim);
107
+ return { ok: true, delivered: 0, skipped: true, reason: "unsupported_delivery_mode" };
108
+ }
109
+ if (!envelope || envelope.event !== "message" || !envelope.data || typeof envelope.data.message !== "string") {
110
+ queue.completeClaim(claim);
111
+ return { ok: true, delivered: 0, skipped: true, reason: "unsupported_event" };
112
+ }
113
+
114
+ if (delivery.gate === "idle") {
115
+ const secondGate = this.shouldDeliver(subscriber);
116
+ if (!secondGate.ok) {
117
+ queue.restoreClaim(claim);
118
+ return { ok: true, delivered: 0, deferred: true, reason: secondGate.reason };
119
+ }
120
+ }
121
+
122
+ const { agents } = this.getAgentMeta(subscriber);
123
+ const injectionText = this.buildInjectionText(envelope, subscriber, agents);
124
+ try {
125
+ await this.injector.inject(subscriber, injectionText);
126
+ queue.completeClaim(claim);
127
+ await this.emitDelivery({
128
+ subscriber,
129
+ event: envelope,
130
+ status: "ok",
131
+ });
132
+ return { ok: true, delivered: 1, event: envelope };
133
+ } catch (err) {
134
+ queue.restoreClaim(claim);
135
+ await this.emitDelivery({
136
+ subscriber,
137
+ event: envelope,
138
+ status: "error",
139
+ error: err && err.message ? err.message : String(err || "inject failed"),
140
+ });
141
+ return {
142
+ ok: false,
143
+ delivered: 0,
144
+ reason: "inject_failed",
145
+ error: err && err.message ? err.message : String(err || "inject failed"),
146
+ };
147
+ }
148
+ } finally {
149
+ this.locks.delete(subscriber);
150
+ }
151
+ }
152
+
153
+ async deliverTargets(targets = []) {
154
+ const unique = Array.from(new Set((Array.isArray(targets) ? targets : [targets]).filter(Boolean)));
155
+ const results = [];
156
+ for (const subscriber of unique) {
157
+ // eslint-disable-next-line no-await-in-loop
158
+ results.push(await this.deliverSubscriber(subscriber));
159
+ }
160
+ return results;
161
+ }
162
+
163
+ listPendingSubscribers() {
164
+ const data = this.readAgents() || { agents: {} };
165
+ const agents = data.agents && typeof data.agents === "object" ? data.agents : {};
166
+ const subscribers = [];
167
+ for (const [subscriber, meta] of Object.entries(agents)) {
168
+ if (!meta || meta.status === "inactive") continue;
169
+ const queue = this.queueFactory(subscriber);
170
+ if (queue.readPending().length > 0) subscribers.push(subscriber);
171
+ }
172
+ return subscribers;
173
+ }
174
+
175
+ async tick() {
176
+ const subscribers = this.listPendingSubscribers();
177
+ return this.deliverTargets(subscribers);
178
+ }
179
+
180
+ start() {
181
+ if (this.running) return;
182
+ this.running = true;
183
+ const run = () => {
184
+ this.tick().catch((err) => {
185
+ this.log(`delivery scheduler tick failed: ${err && err.message ? err.message : String(err)}`);
186
+ });
187
+ };
188
+ run();
189
+ this.timer = setInterval(run, this.intervalMs);
190
+ if (this.timer && typeof this.timer.unref === "function") this.timer.unref();
191
+ }
192
+
193
+ stop() {
194
+ this.running = false;
195
+ if (this.timer) {
196
+ clearInterval(this.timer);
197
+ this.timer = null;
198
+ }
199
+ }
200
+ }
201
+
202
+ module.exports = {
203
+ DeliveryScheduler,
204
+ isDeliverableActivityState,
205
+ };
@@ -8,6 +8,7 @@ const { buildStatus } = require("./status");
8
8
  const EventBus = require("../../coordination/bus");
9
9
  const { AgentProcessManager } = require("./agentProcessManager");
10
10
  const NicknameManager = require("../../coordination/bus/nickname");
11
+ const { DeliveryQueue } = require("../../coordination/bus/deliveryQueue");
11
12
  const { generateInstanceId, subscriberToSafeName } = require("../../coordination/bus/utils");
12
13
  const { createDaemonIpcServer } = require("./ipcServer");
13
14
  const { IPC_REQUEST_TYPES, IPC_RESPONSE_TYPES, BUS_STATUS_PHASES } = require("../contracts/eventContract");
@@ -17,6 +18,7 @@ const { scheduleProviderSessionResolve, resolveSessionFromFile, persistProviderS
17
18
  const { createTerminalAdapterRouter } = require("../terminal/adapterRouter");
18
19
  const { createDaemonCronController } = require("./cronOps");
19
20
  const { createGroupOrchestrator } = require("./groupOrchestrator");
21
+ const { DeliveryScheduler } = require("./deliveryScheduler");
20
22
  const { normalizeFormat, renderGroupDiagramFromTemplate, renderGroupDiagramFromRuntime } = require("../../orchestration/groups/diagram");
21
23
  const { runPromptWithAssistant } = require("./promptLoop");
22
24
  const { handlePromptRequest } = require("./promptRequest");
@@ -1064,46 +1066,23 @@ function startBusBridge(projectRoot, provider, onEvent, onStatus, shouldDrain, o
1064
1066
 
1065
1067
  function pollQueue() {
1066
1068
  if (!state.queueFile) return;
1067
- if (!fs.existsSync(state.queueFile)) return;
1068
- let content = "";
1069
- let readOk = false;
1070
- const processingFile = `${state.queueFile}.processing.${process.pid}.${Date.now()}`;
1071
- try {
1072
- fs.renameSync(state.queueFile, processingFile);
1073
- content = fs.readFileSync(processingFile, "utf8");
1074
- readOk = true;
1075
- } catch {
1076
- try {
1077
- if (fs.existsSync(processingFile)) {
1078
- fs.renameSync(processingFile, state.queueFile);
1079
- }
1080
- } catch {
1081
- // ignore rollback errors
1082
- }
1083
- return;
1084
- } finally {
1085
- if (readOk) {
1086
- try {
1087
- if (fs.existsSync(processingFile)) {
1088
- fs.rmSync(processingFile, { force: true });
1089
- }
1090
- } catch {
1091
- // ignore cleanup errors
1092
- }
1069
+ const queue = new DeliveryQueue(state.queueFile);
1070
+ queue.recover();
1071
+ while (true) {
1072
+ const claim = queue.claimNext();
1073
+ if (!claim) break;
1074
+ const evt = claim.event;
1075
+ if (!evt) {
1076
+ queue.completeClaim(claim);
1077
+ continue;
1093
1078
  }
1094
- }
1095
-
1096
- const lines = content.split(/\r?\n/).filter(Boolean);
1097
- if (!lines.length) return;
1098
- for (const line of lines) {
1099
- let evt;
1100
1079
  try {
1101
- evt = JSON.parse(line);
1080
+ emitBusEvent(evt);
1081
+ queue.completeClaim(claim);
1102
1082
  } catch {
1083
+ queue.restoreClaim(claim);
1103
1084
  continue;
1104
1085
  }
1105
- if (!evt) continue;
1106
- emitBusEvent(evt);
1107
1086
  if (evt.publisher && state.pending.has(evt.publisher)) {
1108
1087
  state.pending.delete(evt.publisher);
1109
1088
  if (onStatus) {
@@ -1361,6 +1340,8 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
1361
1340
  log(`report bus event failed request=${meta.requestId || ""} error=${err.message || String(err)}`);
1362
1341
  }
1363
1342
  });
1343
+ const deliveryScheduler = new DeliveryScheduler(projectRoot, { log });
1344
+ deliveryScheduler.start();
1364
1345
 
1365
1346
  handleIpcRequest = async (req, socket) => {
1366
1347
  if (!req || typeof req !== "object") return;
@@ -1493,12 +1474,19 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
1493
1474
  try {
1494
1475
  const publisher = busBridge.getSubscriber() || "ufoo-agent";
1495
1476
  const eventBus = new EventBus(projectRoot);
1496
- await eventBus.send(target, message, publisher, {
1477
+ const result = await eventBus.send(target, message, publisher, {
1497
1478
  injectionMode: injection_mode,
1498
1479
  source,
1499
1480
  });
1500
- busBridge.markPending(target);
1501
- log(`bus_send target=${target} publisher=${publisher}`);
1481
+ const resolvedTargets = Array.isArray(result && result.targets) ? result.targets : [];
1482
+ for (const resolvedTarget of resolvedTargets) {
1483
+ busBridge.markPending(resolvedTarget);
1484
+ }
1485
+ if (resolvedTargets.length === 0) busBridge.markPending(target);
1486
+ deliveryScheduler.deliverTargets(resolvedTargets).catch((err) => {
1487
+ log(`delivery scheduler send trigger failed target=${target} error=${err.message || String(err)}`);
1488
+ });
1489
+ log(`bus_send target=${target} resolved=${resolvedTargets.join(",")} publisher=${publisher}`);
1502
1490
  socket.write(
1503
1491
  `${JSON.stringify({
1504
1492
  type: IPC_RESPONSE_TYPES.BUS_SEND_OK,
@@ -2595,6 +2583,7 @@ function startDaemon({ projectRoot, provider, model, resumeMode = "auto" }) {
2595
2583
  processManager.cleanup();
2596
2584
 
2597
2585
  ipcServer.stop();
2586
+ deliveryScheduler.stop();
2598
2587
  busBridge.stop();
2599
2588
  removeSocket(projectRoot);
2600
2589
  removePidIfOwned(projectRoot);
@@ -1,13 +1,16 @@
1
1
  "use strict";
2
2
 
3
- const fs = require("fs");
4
3
  const path = require("path");
5
4
  const { getUfooPaths } = require("../../coordination/state/paths");
6
5
  const {
7
- appendJSONL,
8
6
  ensureDir,
9
7
  generateInstanceId,
10
8
  } = require("../../coordination/bus/utils");
9
+ const {
10
+ DeliveryQueue,
11
+ QUEUE_TYPES,
12
+ normalizeQueueEnvelope,
13
+ } = require("../../coordination/bus/deliveryQueue");
11
14
 
12
15
  const REPORT_CONTROL_TARGET = "ufoo-agent";
13
16
  const REPORT_CONTROL_EVENT = "agent_report";
@@ -58,8 +61,12 @@ function buildReportControlEvent(report = {}, options = {}) {
58
61
  async function enqueueAgentReport(projectRoot, report, options = {}) {
59
62
  ensureReportControlQueue(projectRoot);
60
63
 
61
- const event = buildReportControlEvent(report, options);
62
- appendJSONL(getReportControlQueueFile(projectRoot), event);
64
+ const event = normalizeQueueEnvelope(buildReportControlEvent(report, options), {
65
+ queueType: QUEUE_TYPES.REPORT,
66
+ delivery: { mode: "daemon_consume", gate: "none", max_inflight: 1 },
67
+ ack: { policy: "on_consume" },
68
+ });
69
+ new DeliveryQueue(getReportControlQueueFile(projectRoot)).append(event);
63
70
 
64
71
  return {
65
72
  queued: true,
@@ -72,46 +79,16 @@ async function enqueueAgentReport(projectRoot, report, options = {}) {
72
79
 
73
80
  function takeReportControlEvents(projectRoot) {
74
81
  const queueFile = getReportControlQueueFile(projectRoot);
75
- if (!fs.existsSync(queueFile)) return [];
76
-
77
- const processingFile = `${queueFile}.processing.${process.pid}.${Date.now()}.${generateInstanceId()}`;
78
- let content = "";
79
- let readOk = false;
80
- try {
81
- fs.renameSync(queueFile, processingFile);
82
- content = fs.readFileSync(processingFile, "utf8");
83
- readOk = true;
84
- } catch {
85
- try {
86
- if (fs.existsSync(processingFile)) {
87
- fs.renameSync(processingFile, queueFile);
88
- }
89
- } catch {
90
- // ignore rollback errors
91
- }
92
- return [];
93
- } finally {
94
- if (readOk) {
95
- try {
96
- if (fs.existsSync(processingFile)) {
97
- fs.rmSync(processingFile, { force: true });
98
- }
99
- } catch {
100
- // ignore cleanup errors
101
- }
102
- }
82
+ const queue = new DeliveryQueue(queueFile);
83
+ const events = [];
84
+ queue.recover();
85
+ while (true) {
86
+ const claim = queue.claimNext();
87
+ if (!claim) break;
88
+ events.push(claim.event);
89
+ queue.completeClaim(claim);
103
90
  }
104
-
105
- return content.split(/\r?\n/)
106
- .filter(Boolean)
107
- .map((line) => {
108
- try {
109
- return JSON.parse(line);
110
- } catch {
111
- return null;
112
- }
113
- })
114
- .filter(Boolean);
91
+ return events;
115
92
  }
116
93
 
117
94
  function isAgentReportControlEvent(evt) {