fifony 0.1.47 → 0.1.48

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 (89) hide show
  1. package/README.md +78 -0
  2. package/app/dist/assets/CommandPalette-CZDG20HW.js +1 -0
  3. package/app/dist/assets/{KeyboardShortcutsHelp-CqEFfGcE.js → KeyboardShortcutsHelp-TYhQc4aA.js} +1 -1
  4. package/app/dist/assets/OnboardingWizard-CQ9YmVIT.js +1 -0
  5. package/app/dist/assets/agents.lazy-CgakDm_P.js +1 -0
  6. package/app/dist/assets/analytics.lazy-C0rw3sov.js +1 -0
  7. package/app/dist/assets/{createLucideIcon-luywpIq4.js → createLucideIcon-B3bah5lk.js} +1 -1
  8. package/app/dist/assets/hooks-CNPue7d7.js +1 -0
  9. package/app/dist/assets/index-B8XCmr0-.css +1 -0
  10. package/app/dist/assets/index-Dfn02uW3.js +47 -0
  11. package/app/dist/assets/index.lazy-JdqhBwPd.js +44 -0
  12. package/app/dist/assets/services-CHpVij2M.css +1 -0
  13. package/app/dist/assets/services.lazy-BShKUCOt.js +12 -0
  14. package/app/dist/assets/vendor-X6HTElZW.js +9 -0
  15. package/app/dist/assets/viz-Dsh_q2DK.js +4 -0
  16. package/app/dist/index.html +5 -4
  17. package/app/dist/service-worker.js +32 -1
  18. package/dist/agent/run-local.js +89 -76
  19. package/dist/{agent-DFSFG6DG.js → agent-DJ4SCNBZ.js} +22 -17
  20. package/dist/{analytics-broadcaster-O4AE3RUK.js → analytics-broadcaster-INNYWHDJ.js} +25 -20
  21. package/dist/approve-plan.command-WE2CO3H2.js +21 -0
  22. package/dist/{chunk-HOIOVUHI.js → chunk-5M7PBFMZ.js} +8 -6
  23. package/dist/chunk-7R7XFXJM.js +1247 -0
  24. package/dist/{chunk-2PRRKBG6.js → chunk-A4P2MYJF.js} +22 -9
  25. package/dist/chunk-AFOV3ZAF.js +722 -0
  26. package/dist/chunk-AFP36N23.js +134 -0
  27. package/dist/{chunk-AAZKYWOY.js → chunk-AFYKGVSP.js} +103 -8
  28. package/dist/chunk-APJOZXRP.js +737 -0
  29. package/dist/chunk-DLSPRIQL.js +241 -0
  30. package/dist/{chunk-5AMWD66T.js → chunk-EDIPHR5B.js} +6 -4
  31. package/dist/{chunk-K36BWMUV.js → chunk-JU3MF3MW.js} +2526 -736
  32. package/dist/{chunk-7TXZYZR5.js → chunk-N5HCNY4O.js} +7 -5
  33. package/dist/{chunk-JRLWLZOD.js → chunk-NKMZYPIS.js} +31 -23
  34. package/dist/{chunk-PI7Y77R3.js → chunk-OFIVTM2E.js} +17 -7
  35. package/dist/{chunk-QH6VCTET.js → chunk-RCSJFMQG.js} +909 -98
  36. package/dist/{chunk-AAVROEQC.js → chunk-UR7T7IA6.js} +253 -349
  37. package/dist/{chunk-QHISYRXJ.js → chunk-VOYLU3MI.js} +57 -3
  38. package/dist/{chunk-EBCSQFPR.js → chunk-W5IULOWV.js} +2 -3
  39. package/dist/chunk-X37RNTWU.js +193 -0
  40. package/dist/{chunk-PACI3T4I.js → chunk-XY2APMDE.js} +13 -5
  41. package/dist/chunk-Z6ZWNWWR.js +34 -0
  42. package/dist/cli.js +45 -17
  43. package/dist/constants-AAP7ZGCX.js +124 -0
  44. package/dist/create-issue.command-SX3AXXIC.js +29 -0
  45. package/dist/fsm-agent-JGV22WK4.js +59 -0
  46. package/dist/{fsm-issue-EHTSKMFN.js → fsm-issue-LHIJM5VB.js} +12 -8
  47. package/dist/{fsm-service-7O4AJG2R.js → fsm-service-GGDKUTWS.js} +13 -4
  48. package/dist/{helpers-ON2S7UEF.js → helpers-AENVYEZJ.js} +6 -2
  49. package/dist/{issue-log-broadcaster-FZGVEEIX.js → issue-log-broadcaster-QQWM7LOV.js} +29 -18
  50. package/dist/{issues-3YNNTB4U.js → issues-RXFKKSXB.js} +10 -7
  51. package/dist/{log-analyzer-EIX6R6PP.js → log-analyzer-4LNXQISY.js} +30 -20
  52. package/dist/{logger-IFLXTQPS.js → logger-4F6ATWNA.js} +2 -1
  53. package/dist/mcp/server.js +6 -2
  54. package/dist/merge-workspace.command-ZNGIZC4O.js +29 -0
  55. package/dist/{parallel-executor-DWESCNX3.js → parallel-executor-OL5CB33L.js} +78 -19
  56. package/dist/{pid-manager-UBWXVSMD.js → pid-manager-EDT4DHAU.js} +2 -1
  57. package/dist/queue-workers-NSKIIMQ2.js +43 -0
  58. package/dist/replan-issue.command-73PETERX.js +21 -0
  59. package/dist/retry-issue.command-DIDP4OCS.js +21 -0
  60. package/dist/reverse-proxy-server-QSS3H4UH.js +97 -0
  61. package/dist/scheduler-5YORYECF.js +37 -0
  62. package/dist/service-log-broadcaster-JIUP2L3D.js +21 -0
  63. package/dist/{settings-SOTIS6ZD.js → settings-ZNDXYL46.js} +34 -23
  64. package/dist/settings.resource-OKUHXICJ.js +35 -0
  65. package/dist/{store-S3NAYZ3S.js → store-P3ACO6YA.js} +22 -17
  66. package/dist/telemetry-KVUFHDQS.js +828 -0
  67. package/dist/template-variants-HEPLYKMP.js +24 -0
  68. package/dist/trace-bundle-IJOV7IWH.js +41 -0
  69. package/dist/{web-push-QCTLS7EJ.js → web-push-X2LLMQ4M.js} +2 -1
  70. package/dist/websocket-Q2TUCIC2.js +103 -0
  71. package/dist/{workspace-OS7GPMCN.js → workspace-TDX3NJCX.js} +10 -6
  72. package/package.json +12 -9
  73. package/app/dist/assets/CommandPalette-CL8p78lG.js +0 -1
  74. package/app/dist/assets/OnboardingWizard-BmI50ZUv.js +0 -1
  75. package/app/dist/assets/analytics.lazy-CXGjZabc.js +0 -1
  76. package/app/dist/assets/index-CEaccpYh.js +0 -96
  77. package/app/dist/assets/index-CzzWGzux.css +0 -1
  78. package/app/dist/assets/vendor-uqBx3VSC.js +0 -9
  79. package/dist/approve-plan.command-QGQZZXTQ.js +0 -17
  80. package/dist/chunk-N4KFNX2G.js +0 -370
  81. package/dist/chunk-VM5QAYP5.js +0 -404
  82. package/dist/create-issue.command-VAKYRECC.js +0 -24
  83. package/dist/merge-workspace.command-T2NIGR4M.js +0 -24
  84. package/dist/queue-workers-V57BYXAY.js +0 -38
  85. package/dist/replan-issue.command-2GQ3QXCR.js +0 -17
  86. package/dist/retry-issue.command-GJBUUYDJ.js +0 -17
  87. package/dist/scheduler-KYILMWLD.js +0 -32
  88. package/dist/settings.resource-JMD3JQOS.js +0 -30
  89. package/dist/websocket-T2Y3BY4B.js +0 -61
@@ -0,0 +1,722 @@
1
+ import {
2
+ computeMetrics
3
+ } from "./chunk-VOYLU3MI.js";
4
+ import {
5
+ serviceLogPath
6
+ } from "./chunk-UR7T7IA6.js";
7
+ import {
8
+ init_helpers,
9
+ now
10
+ } from "./chunk-DLSPRIQL.js";
11
+ import {
12
+ STATE_ROOT,
13
+ init_constants
14
+ } from "./chunk-X37RNTWU.js";
15
+ import {
16
+ logger
17
+ } from "./chunk-PXTIWKLQ.js";
18
+
19
+ // src/persistence/plugins/service-log-broadcaster.ts
20
+ import { existsSync, statSync } from "fs";
21
+ import TailFile from "@logdna/tail-file";
22
+
23
+ // src/routes/websocket.ts
24
+ init_helpers();
25
+ init_constants();
26
+ var meshSnapshotProvider = null;
27
+ var servicesSnapshotProvider = null;
28
+ var reverseProxySnapshotProvider = null;
29
+ var meshSnapshotSeq = 0;
30
+ var reverseProxySnapshotSeq = 0;
31
+ var wsClientHandlers = /* @__PURE__ */ new Map();
32
+ var wsClientTypeGuards = {
33
+ ping: () => true,
34
+ "service:log:subscribe": (msg) => typeof msg?.id === "string",
35
+ "service:log:unsubscribe": (msg) => typeof msg?.id === "string",
36
+ "services:subscribe": () => true,
37
+ "services:unsubscribe": () => true,
38
+ "analytics:subscribe": (msg) => typeof msg?.topic === "string",
39
+ "analytics:unsubscribe": (msg) => typeof msg?.topic === "string",
40
+ "issue:log:subscribe": (msg) => typeof msg?.id === "string",
41
+ "issue:log:unsubscribe": (msg) => typeof msg?.id === "string",
42
+ "mesh:subscribe": () => true,
43
+ "mesh:unsubscribe": () => true,
44
+ "proxy:reverse:subscribe": () => true,
45
+ "proxy:reverse:unsubscribe": () => true
46
+ };
47
+ var wsTelemetry = {
48
+ startedAt: now(),
49
+ connectionAttempts: 0,
50
+ successfulConnections: 0,
51
+ disconnections: 0,
52
+ connectionErrors: 0,
53
+ inboundMessages: 0,
54
+ outboundMessages: 0,
55
+ inboundByType: /* @__PURE__ */ Object.create(null),
56
+ outboundByType: /* @__PURE__ */ Object.create(null),
57
+ invalidMessages: 0,
58
+ unknownCommands: 0,
59
+ invalidCommandPayloads: 0,
60
+ commandErrors: 0,
61
+ lastInboundAt: null,
62
+ lastOutboundAt: null,
63
+ lastConnectedAt: null,
64
+ lastDisconnectedAt: null
65
+ };
66
+ function wsNow() {
67
+ return now();
68
+ }
69
+ function normalizeWsMessageType(value) {
70
+ return typeof value === "string" && value.length > 0 ? value : "__unknown__";
71
+ }
72
+ function incrementRecord(target, key) {
73
+ target[key] = (target[key] ?? 0) + 1;
74
+ }
75
+ function resolvePayloadType(payload) {
76
+ if (typeof payload === "string") {
77
+ try {
78
+ const parsed = JSON.parse(payload);
79
+ if (parsed && typeof parsed === "object" && typeof parsed.type === "string") {
80
+ return normalizeWsMessageType(parsed.type);
81
+ }
82
+ } catch {
83
+ return "__invalid-json__";
84
+ }
85
+ } else if (payload.type !== void 0) {
86
+ return normalizeWsMessageType(payload.type);
87
+ }
88
+ return "__unknown__";
89
+ }
90
+ function trackOutboundMessage(payload) {
91
+ const type = resolvePayloadType(payload);
92
+ wsTelemetry.outboundMessages += 1;
93
+ wsTelemetry.lastOutboundAt = wsNow();
94
+ incrementRecord(wsTelemetry.outboundByType, type);
95
+ }
96
+ function trackInboundMessage(message) {
97
+ const type = normalizeWsMessageType(message?.type);
98
+ wsTelemetry.inboundMessages += 1;
99
+ wsTelemetry.lastInboundAt = wsNow();
100
+ incrementRecord(wsTelemetry.inboundByType, type);
101
+ }
102
+ function cleanupStaleSocket(socketId) {
103
+ unsubscribeFromAllRooms(socketId);
104
+ wsClients.delete(socketId);
105
+ }
106
+ function sendToSocket(socketId, payload) {
107
+ const send = wsClients.get(socketId);
108
+ if (!send) return false;
109
+ const encoded = typeof payload === "string" ? payload : JSON.stringify(payload);
110
+ try {
111
+ send(encoded);
112
+ trackOutboundMessage(encoded);
113
+ return true;
114
+ } catch {
115
+ wsTelemetry.connectionErrors += 1;
116
+ cleanupStaleSocket(socketId);
117
+ return false;
118
+ }
119
+ }
120
+ function sendToSocketList(socketIds, payload) {
121
+ const safePayload = typeof payload === "string" ? payload : JSON.stringify(payload);
122
+ for (const socketId of [...socketIds]) {
123
+ const send = wsClients.get(socketId);
124
+ if (!send) {
125
+ socketIds.delete(socketId);
126
+ continue;
127
+ }
128
+ try {
129
+ send(safePayload);
130
+ trackOutboundMessage(safePayload);
131
+ } catch {
132
+ wsTelemetry.connectionErrors += 1;
133
+ cleanupStaleSocket(socketId);
134
+ socketIds.delete(socketId);
135
+ }
136
+ }
137
+ }
138
+ var wsClients = /* @__PURE__ */ new Map();
139
+ var broadcastSeq = 0;
140
+ var servicesSnapshotSeq = 0;
141
+ var lastBroadcastIssueSnapshot = /* @__PURE__ */ new Map();
142
+ function setMeshSnapshotProvider(fn) {
143
+ meshSnapshotProvider = fn;
144
+ }
145
+ function setServicesSnapshotProvider(fn) {
146
+ servicesSnapshotProvider = fn;
147
+ }
148
+ function notifyServicesSnapshot() {
149
+ const payload = servicesSnapshotProvider?.();
150
+ if (!payload || wsClients.size === 0) return;
151
+ servicesSnapshotSeq += 1;
152
+ sendToServicesRoom({
153
+ type: "services:snapshot",
154
+ ...payload,
155
+ seq: servicesSnapshotSeq,
156
+ timestamp: now()
157
+ });
158
+ }
159
+ function notifyMeshSnapshot() {
160
+ const payload = meshSnapshotProvider?.();
161
+ if (!payload || !meshRoomHasSubscribers()) return;
162
+ meshSnapshotSeq += 1;
163
+ sendToMeshRoom({
164
+ type: "mesh:snapshot",
165
+ ...payload,
166
+ seq: meshSnapshotSeq,
167
+ timestamp: now()
168
+ });
169
+ }
170
+ var serviceLogRooms = /* @__PURE__ */ new Map();
171
+ function subscribeServiceLogRoom(socketId, serviceId) {
172
+ if (!serviceLogRooms.has(serviceId)) serviceLogRooms.set(serviceId, /* @__PURE__ */ new Set());
173
+ serviceLogRooms.get(serviceId).add(socketId);
174
+ }
175
+ function unsubscribeServiceLogRoom(socketId, serviceId) {
176
+ serviceLogRooms.get(serviceId)?.delete(socketId);
177
+ }
178
+ var analyticsRooms = /* @__PURE__ */ new Map();
179
+ var analyticsOnSubscribeFn = null;
180
+ function setAnalyticsOnSubscribeFn(fn) {
181
+ analyticsOnSubscribeFn = fn;
182
+ }
183
+ function subscribeAnalyticsRoom(socketId, topic) {
184
+ if (!analyticsRooms.has(topic)) analyticsRooms.set(topic, /* @__PURE__ */ new Set());
185
+ analyticsRooms.get(topic).add(socketId);
186
+ analyticsOnSubscribeFn?.(socketId, topic);
187
+ }
188
+ function unsubscribeAnalyticsRoom(socketId, topic) {
189
+ analyticsRooms.get(topic)?.delete(socketId);
190
+ }
191
+ function analyticsRoomHasSubscribers(topic) {
192
+ return (analyticsRooms.get(topic)?.size ?? 0) > 0;
193
+ }
194
+ function sendToAnalyticsRoom(topic, data) {
195
+ const room = analyticsRooms.get(topic);
196
+ if (!room || room.size === 0) return;
197
+ const msg = JSON.stringify({ type: "analytics:update", topic, data });
198
+ sendToSocketList(room, msg);
199
+ }
200
+ var issueLogRooms = /* @__PURE__ */ new Map();
201
+ function subscribeIssueLogRoom(socketId, issueId) {
202
+ if (!issueLogRooms.has(issueId)) issueLogRooms.set(issueId, /* @__PURE__ */ new Set());
203
+ issueLogRooms.get(issueId).add(socketId);
204
+ }
205
+ function unsubscribeIssueLogRoom(socketId, issueId) {
206
+ issueLogRooms.get(issueId)?.delete(socketId);
207
+ }
208
+ function issueLogRoomSize(issueId) {
209
+ return issueLogRooms.get(issueId)?.size ?? 0;
210
+ }
211
+ function sendToIssueLogRoom(issueId, data) {
212
+ const room = issueLogRooms.get(issueId);
213
+ if (!room || room.size === 0) return;
214
+ sendToSocketList(room, data);
215
+ }
216
+ var servicesRoom = /* @__PURE__ */ new Set();
217
+ function subscribeServicesRoom(socketId) {
218
+ servicesRoom.add(socketId);
219
+ }
220
+ function unsubscribeServicesRoom(socketId) {
221
+ servicesRoom.delete(socketId);
222
+ }
223
+ function servicesRoomHasSubscribers() {
224
+ return servicesRoom.size > 0;
225
+ }
226
+ function sendToServicesRoom(data) {
227
+ if (servicesRoom.size === 0) return;
228
+ const msg = JSON.stringify(data);
229
+ sendToSocketList(servicesRoom, msg);
230
+ }
231
+ var meshRoom = /* @__PURE__ */ new Set();
232
+ function subscribeMeshRoom(socketId) {
233
+ meshRoom.add(socketId);
234
+ }
235
+ function unsubscribeMeshRoom(socketId) {
236
+ meshRoom.delete(socketId);
237
+ }
238
+ function sendToMeshRoom(data) {
239
+ if (meshRoom.size === 0) return;
240
+ const msg = JSON.stringify(data);
241
+ sendToSocketList(meshRoom, msg);
242
+ }
243
+ function meshRoomHasSubscribers() {
244
+ return meshRoom.size > 0;
245
+ }
246
+ var reverseProxyRoom = /* @__PURE__ */ new Set();
247
+ function setReverseProxySnapshotProvider(fn) {
248
+ reverseProxySnapshotProvider = fn;
249
+ }
250
+ function subscribeReverseProxyRoom(socketId) {
251
+ reverseProxyRoom.add(socketId);
252
+ }
253
+ function unsubscribeReverseProxyRoom(socketId) {
254
+ reverseProxyRoom.delete(socketId);
255
+ }
256
+ function reverseProxyRoomHasSubscribers() {
257
+ return reverseProxyRoom.size > 0;
258
+ }
259
+ function sendToReverseProxyRoom(data) {
260
+ if (reverseProxyRoom.size === 0) return;
261
+ sendToSocketList(reverseProxyRoom, JSON.stringify(data));
262
+ }
263
+ function notifyReverseProxySnapshot() {
264
+ const payload = reverseProxySnapshotProvider?.();
265
+ if (!payload || reverseProxyRoom.size === 0) return;
266
+ reverseProxySnapshotSeq += 1;
267
+ sendToReverseProxyRoom({
268
+ type: "proxy:reverse:snapshot",
269
+ ...payload,
270
+ seq: reverseProxySnapshotSeq,
271
+ timestamp: now()
272
+ });
273
+ }
274
+ function unsubscribeFromAllRooms(socketId) {
275
+ for (const room of serviceLogRooms.values()) room.delete(socketId);
276
+ for (const room of analyticsRooms.values()) room.delete(socketId);
277
+ for (const room of issueLogRooms.values()) room.delete(socketId);
278
+ servicesRoom.delete(socketId);
279
+ meshRoom.delete(socketId);
280
+ reverseProxyRoom.delete(socketId);
281
+ }
282
+ function serviceLogRoomSize(serviceId) {
283
+ return serviceLogRooms.get(serviceId)?.size ?? 0;
284
+ }
285
+ function sendToServiceLogRoom(serviceId, data) {
286
+ const room = serviceLogRooms.get(serviceId);
287
+ if (!room || room.size === 0) return;
288
+ sendToSocketList(room, data);
289
+ }
290
+ function sendToAllClients(data) {
291
+ for (const socketId of [...wsClients.keys()]) {
292
+ if (!sendToSocket(socketId, data)) {
293
+ logger.debug(`WebSocket send failed for ${socketId}, removing (remaining: ${wsClients.size - 1})`);
294
+ }
295
+ }
296
+ }
297
+ function parseWsClientMessage(raw) {
298
+ if (typeof raw !== "string" && !Buffer.isBuffer(raw)) return null;
299
+ const payload = typeof raw === "string" ? raw : raw.toString("utf8");
300
+ try {
301
+ const msg = JSON.parse(payload);
302
+ return msg && typeof msg === "object" ? msg : null;
303
+ } catch {
304
+ return null;
305
+ }
306
+ }
307
+ function registerWsCommandHandlers() {
308
+ if (wsClientHandlers.size > 0) return;
309
+ wsClientHandlers.set("ping", (_socketId, _payload) => {
310
+ const payload = _payload;
311
+ const seq = typeof payload.seq === "number" ? payload.seq : void 0;
312
+ const clientTs = typeof payload.clientTs === "number" || typeof payload.clientTs === "string" ? payload.clientTs : void 0;
313
+ sendToSocket(_socketId, {
314
+ type: "pong",
315
+ seq,
316
+ clientTs,
317
+ timestamp: now()
318
+ });
319
+ });
320
+ wsClientHandlers.set("service:log:subscribe", (socketId, payload) => {
321
+ const serviceId = payload.id;
322
+ if (typeof serviceId !== "string") return;
323
+ subscribeServiceLogRoom(socketId, serviceId);
324
+ startServiceLogBroadcasting(serviceId, STATE_ROOT);
325
+ logger.debug({ socketId, serviceId }, "[WebSocket] Subscribed to service log room");
326
+ });
327
+ wsClientHandlers.set("service:log:unsubscribe", (socketId, payload) => {
328
+ const serviceId = payload.id;
329
+ if (typeof serviceId !== "string") return;
330
+ unsubscribeServiceLogRoom(socketId, serviceId);
331
+ logger.debug({ socketId, serviceId }, "[WebSocket] Unsubscribed from service log room");
332
+ });
333
+ wsClientHandlers.set("services:subscribe", (socketId) => {
334
+ subscribeServicesRoom(socketId);
335
+ const snapshot = servicesSnapshotProvider?.();
336
+ if (snapshot) {
337
+ sendToSocket(socketId, {
338
+ type: "services:snapshot",
339
+ ...snapshot,
340
+ seq: servicesSnapshotSeq,
341
+ timestamp: now()
342
+ });
343
+ }
344
+ logger.debug({ socketId }, "[WebSocket] Subscribed to services room");
345
+ });
346
+ wsClientHandlers.set("services:unsubscribe", (socketId) => {
347
+ unsubscribeServicesRoom(socketId);
348
+ logger.debug({ socketId }, "[WebSocket] Unsubscribed from services room");
349
+ });
350
+ wsClientHandlers.set("analytics:subscribe", (socketId, payload) => {
351
+ const topic = payload.topic;
352
+ if (typeof topic !== "string") return;
353
+ subscribeAnalyticsRoom(socketId, topic);
354
+ logger.debug({ socketId, topic }, "[WebSocket] Subscribed to analytics room");
355
+ });
356
+ wsClientHandlers.set("analytics:unsubscribe", (socketId, payload) => {
357
+ const topic = payload.topic;
358
+ if (typeof topic !== "string") return;
359
+ unsubscribeAnalyticsRoom(socketId, topic);
360
+ logger.debug({ socketId, topic }, "[WebSocket] Unsubscribed from analytics room");
361
+ });
362
+ wsClientHandlers.set("issue:log:subscribe", (socketId, payload) => {
363
+ const issueId = payload.id;
364
+ if (typeof issueId !== "string") return;
365
+ subscribeIssueLogRoom(socketId, issueId);
366
+ logger.debug({ socketId, issueId }, "[WebSocket] Subscribed to issue log room");
367
+ });
368
+ wsClientHandlers.set("issue:log:unsubscribe", (socketId, payload) => {
369
+ const issueId = payload.id;
370
+ if (typeof issueId !== "string") return;
371
+ unsubscribeIssueLogRoom(socketId, issueId);
372
+ logger.debug({ socketId, issueId }, "[WebSocket] Unsubscribed from issue log room");
373
+ });
374
+ wsClientHandlers.set("mesh:subscribe", (socketId) => {
375
+ subscribeMeshRoom(socketId);
376
+ const snapshot = meshSnapshotProvider?.();
377
+ if (snapshot) {
378
+ sendToSocket(socketId, {
379
+ type: "mesh:snapshot",
380
+ ...snapshot,
381
+ seq: meshSnapshotSeq,
382
+ timestamp: now()
383
+ });
384
+ }
385
+ logger.debug({ socketId }, "[WebSocket] Subscribed to mesh traffic room");
386
+ });
387
+ wsClientHandlers.set("mesh:unsubscribe", (socketId) => {
388
+ unsubscribeMeshRoom(socketId);
389
+ logger.debug({ socketId }, "[WebSocket] Unsubscribed from mesh traffic room");
390
+ });
391
+ wsClientHandlers.set("proxy:reverse:subscribe", (socketId) => {
392
+ subscribeReverseProxyRoom(socketId);
393
+ const snapshot = reverseProxySnapshotProvider?.();
394
+ if (snapshot) {
395
+ reverseProxySnapshotSeq += 1;
396
+ sendToSocket(socketId, {
397
+ type: "proxy:reverse:snapshot",
398
+ ...snapshot,
399
+ seq: reverseProxySnapshotSeq,
400
+ timestamp: now()
401
+ });
402
+ }
403
+ logger.debug({ socketId }, "[WebSocket] Subscribed to reverse proxy room");
404
+ });
405
+ wsClientHandlers.set("proxy:reverse:unsubscribe", (socketId) => {
406
+ unsubscribeReverseProxyRoom(socketId);
407
+ logger.debug({ socketId }, "[WebSocket] Unsubscribed from reverse proxy room");
408
+ });
409
+ }
410
+ function handleWsClientMessage(socketId, rawMessage, send) {
411
+ const msg = parseWsClientMessage(rawMessage);
412
+ if (!msg) {
413
+ wsTelemetry.invalidMessages += 1;
414
+ return;
415
+ }
416
+ trackInboundMessage(msg);
417
+ const type = msg.type;
418
+ if (typeof type !== "string") {
419
+ wsTelemetry.invalidMessages += 1;
420
+ return;
421
+ }
422
+ const handler = wsClientHandlers.get(type);
423
+ if (!handler) {
424
+ wsTelemetry.unknownCommands += 1;
425
+ return;
426
+ }
427
+ const guard = wsClientTypeGuards[type];
428
+ if (guard && !guard(msg)) {
429
+ wsTelemetry.invalidCommandPayloads += 1;
430
+ return;
431
+ }
432
+ try {
433
+ handler(socketId, msg, send);
434
+ } catch (error) {
435
+ wsTelemetry.commandErrors += 1;
436
+ logger.debug({ err: String(error), type, socketId }, "[WebSocket] Command handler failed");
437
+ }
438
+ }
439
+ function broadcastIssueProgress(progress) {
440
+ if (wsClients.size === 0) return;
441
+ const data = JSON.stringify({ type: "issue:progress", ...progress });
442
+ sendToAllClients(data);
443
+ }
444
+ function broadcastIssueTransition(issue) {
445
+ if (wsClients.size === 0) return;
446
+ const data = JSON.stringify({
447
+ type: "issue:transition",
448
+ id: issue.id,
449
+ state: issue.state,
450
+ issue
451
+ });
452
+ sendToAllClients(data);
453
+ }
454
+ function broadcastToWebSocketClients(message) {
455
+ if (wsClients.size === 0) return;
456
+ broadcastSeq++;
457
+ logger.debug({ seq: broadcastSeq, type: message.type, clientCount: wsClients.size }, "[WebSocket] Broadcasting state update");
458
+ const issues = message.issues;
459
+ if (issues && lastBroadcastIssueSnapshot.size > 0) {
460
+ const currentIds = /* @__PURE__ */ new Set();
461
+ const changedIssues = [];
462
+ for (const issue of issues) {
463
+ const id = issue.id;
464
+ currentIds.add(id);
465
+ const serialized = JSON.stringify(issue);
466
+ if (lastBroadcastIssueSnapshot.get(id) !== serialized) {
467
+ changedIssues.push(issue);
468
+ }
469
+ }
470
+ const removedIds = [];
471
+ for (const prevId of lastBroadcastIssueSnapshot.keys()) {
472
+ if (!currentIds.has(prevId)) {
473
+ removedIds.push(prevId);
474
+ }
475
+ }
476
+ lastBroadcastIssueSnapshot = new Map(
477
+ issues.map((issue) => [issue.id, JSON.stringify(issue)])
478
+ );
479
+ if (changedIssues.length < issues.length / 2 || changedIssues.length <= 3) {
480
+ const delta = {
481
+ type: "state:delta",
482
+ seq: broadcastSeq,
483
+ metrics: message.metrics,
484
+ milestones: message.milestones,
485
+ updatedAt: message.updatedAt,
486
+ issuesDelta: changedIssues,
487
+ issuesRemoved: removedIds,
488
+ events: message.events
489
+ };
490
+ sendToAllClients(JSON.stringify(delta));
491
+ return;
492
+ }
493
+ }
494
+ if (issues) {
495
+ lastBroadcastIssueSnapshot = new Map(
496
+ issues.map((issue) => [issue.id, JSON.stringify(issue)])
497
+ );
498
+ }
499
+ sendToAllClients(JSON.stringify({
500
+ ...message,
501
+ seq: broadcastSeq
502
+ }));
503
+ }
504
+ function makeWebSocketConfig(state) {
505
+ registerWsCommandHandlers();
506
+ return {
507
+ enabled: true,
508
+ path: "/ws",
509
+ maxPayloadBytes: 512e3,
510
+ onConnection: (socketId, send) => {
511
+ wsClients.set(socketId, send);
512
+ wsTelemetry.connectionAttempts += 1;
513
+ wsTelemetry.successfulConnections += 1;
514
+ wsTelemetry.lastConnectedAt = wsNow();
515
+ logger.debug(`WebSocket client connected: ${socketId} (total: ${wsClients.size})`);
516
+ try {
517
+ subscribeServicesRoom(socketId);
518
+ sendToSocket(socketId, JSON.stringify({
519
+ type: "connected",
520
+ seq: broadcastSeq,
521
+ timestamp: now(),
522
+ metrics: computeMetrics(state.issues),
523
+ milestones: state.milestones,
524
+ issues: state.issues,
525
+ events: state.events.slice(0, 50)
526
+ }));
527
+ const servicesSnapshot = servicesSnapshotProvider?.();
528
+ if (servicesSnapshot) {
529
+ sendToSocket(socketId, {
530
+ type: "services:snapshot",
531
+ ...servicesSnapshot,
532
+ seq: servicesSnapshotSeq,
533
+ timestamp: now()
534
+ });
535
+ }
536
+ const meshSnapshot = meshSnapshotProvider?.();
537
+ if (meshSnapshot) {
538
+ sendToSocket(socketId, {
539
+ type: "mesh:snapshot",
540
+ ...meshSnapshot,
541
+ seq: meshSnapshotSeq,
542
+ timestamp: now()
543
+ });
544
+ }
545
+ const reverseProxySnapshot = reverseProxySnapshotProvider?.();
546
+ if (reverseProxySnapshot) {
547
+ sendToSocket(socketId, {
548
+ type: "proxy:reverse:snapshot",
549
+ ...reverseProxySnapshot,
550
+ seq: reverseProxySnapshotSeq,
551
+ timestamp: now()
552
+ });
553
+ }
554
+ } catch (error) {
555
+ logger.debug(`WebSocket initial send failed for ${socketId}: ${String(error)}`);
556
+ }
557
+ },
558
+ onMessage: (socketId, message, send) => {
559
+ handleWsClientMessage(socketId, message, send);
560
+ },
561
+ onClose: (socketId) => {
562
+ wsClients.delete(socketId);
563
+ wsTelemetry.disconnections += 1;
564
+ wsTelemetry.lastDisconnectedAt = wsNow();
565
+ unsubscribeFromAllRooms(socketId);
566
+ logger.debug(`WebSocket client disconnected: ${socketId} (total: ${wsClients.size})`);
567
+ }
568
+ };
569
+ }
570
+ function getWsTelemetry() {
571
+ return {
572
+ ...wsTelemetry,
573
+ activeConnections: wsClients.size,
574
+ inboundByType: { ...wsTelemetry.inboundByType },
575
+ outboundByType: { ...wsTelemetry.outboundByType },
576
+ startedAt: wsTelemetry.startedAt,
577
+ lastInboundAt: wsTelemetry.lastInboundAt,
578
+ lastOutboundAt: wsTelemetry.lastOutboundAt,
579
+ lastConnectedAt: wsTelemetry.lastConnectedAt,
580
+ lastDisconnectedAt: wsTelemetry.lastDisconnectedAt
581
+ };
582
+ }
583
+ function resetWsTelemetry() {
584
+ wsTelemetry.startedAt = now();
585
+ wsTelemetry.connectionAttempts = 0;
586
+ wsTelemetry.successfulConnections = 0;
587
+ wsTelemetry.disconnections = 0;
588
+ wsTelemetry.connectionErrors = 0;
589
+ wsTelemetry.inboundMessages = 0;
590
+ wsTelemetry.outboundMessages = 0;
591
+ wsTelemetry.inboundByType = /* @__PURE__ */ Object.create(null);
592
+ wsTelemetry.outboundByType = /* @__PURE__ */ Object.create(null);
593
+ wsTelemetry.invalidMessages = 0;
594
+ wsTelemetry.unknownCommands = 0;
595
+ wsTelemetry.invalidCommandPayloads = 0;
596
+ wsTelemetry.commandErrors = 0;
597
+ wsTelemetry.lastInboundAt = null;
598
+ wsTelemetry.lastOutboundAt = null;
599
+ wsTelemetry.lastConnectedAt = null;
600
+ wsTelemetry.lastDisconnectedAt = null;
601
+ return getWsTelemetry();
602
+ }
603
+
604
+ // src/persistence/plugins/service-log-broadcaster.ts
605
+ var MAX_CHUNK_BYTES = 16384;
606
+ var active = /* @__PURE__ */ new Map();
607
+ function startServiceLogBroadcasting(id, fifonyDir) {
608
+ if (active.has(id)) return;
609
+ const logPath = serviceLogPath(fifonyDir, id);
610
+ if (!existsSync(logPath)) return;
611
+ const tail = new TailFile(logPath, {
612
+ startPos: null,
613
+ // null = start from EOF
614
+ pollFileIntervalMs: 250,
615
+ // 250ms poll — fast enough for real-time feel
616
+ maxPollFailures: 30,
617
+ // tolerate temporary file absence (rotation)
618
+ encoding: "utf8"
619
+ });
620
+ const entry = { tail, buffer: "", position: 0 };
621
+ try {
622
+ entry.position = statSync(logPath).size;
623
+ } catch {
624
+ }
625
+ tail.on("data", (chunk) => {
626
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
627
+ if (!text) return;
628
+ entry.position += Buffer.byteLength(text, "utf8");
629
+ if (serviceLogRoomSize(id) === 0) {
630
+ entry.buffer += text;
631
+ if (entry.buffer.length > MAX_CHUNK_BYTES) {
632
+ entry.buffer = entry.buffer.slice(-MAX_CHUNK_BYTES);
633
+ }
634
+ return;
635
+ }
636
+ let payload = entry.buffer + text;
637
+ entry.buffer = "";
638
+ if (payload.length > MAX_CHUNK_BYTES) {
639
+ payload = payload.slice(-MAX_CHUNK_BYTES);
640
+ }
641
+ sendToServiceLogRoom(id, JSON.stringify({ type: "service:log", id, chunk: payload }));
642
+ });
643
+ tail.on("truncated", (info) => {
644
+ logger.debug({ id, info }, "[ServiceLogBroadcaster] File truncated (rotation/restart)");
645
+ entry.position = 0;
646
+ entry.buffer = "";
647
+ });
648
+ tail.on("renamed", (info) => {
649
+ logger.debug({ id, info }, "[ServiceLogBroadcaster] File renamed (log rotation)");
650
+ });
651
+ tail.on("tail_error", (err) => {
652
+ logger.warn({ id, err }, "[ServiceLogBroadcaster] Tail error");
653
+ });
654
+ active.set(id, entry);
655
+ tail.start().then(() => {
656
+ logger.debug({ id, logPath }, "[ServiceLogBroadcaster] Started tailing");
657
+ }).catch((err) => {
658
+ logger.warn({ id, err }, "[ServiceLogBroadcaster] Failed to start tail");
659
+ active.delete(id);
660
+ });
661
+ }
662
+ function stopServiceLogBroadcasting(id) {
663
+ const entry = active.get(id);
664
+ if (!entry) return;
665
+ active.delete(id);
666
+ entry.tail.quit().catch(() => {
667
+ });
668
+ }
669
+ function stopAllServiceLogBroadcasting() {
670
+ for (const id of [...active.keys()]) stopServiceLogBroadcasting(id);
671
+ }
672
+
673
+ export {
674
+ startServiceLogBroadcasting,
675
+ stopServiceLogBroadcasting,
676
+ stopAllServiceLogBroadcasting,
677
+ wsClients,
678
+ broadcastSeq,
679
+ lastBroadcastIssueSnapshot,
680
+ setMeshSnapshotProvider,
681
+ setServicesSnapshotProvider,
682
+ notifyServicesSnapshot,
683
+ notifyMeshSnapshot,
684
+ subscribeServiceLogRoom,
685
+ unsubscribeServiceLogRoom,
686
+ setAnalyticsOnSubscribeFn,
687
+ subscribeAnalyticsRoom,
688
+ unsubscribeAnalyticsRoom,
689
+ analyticsRoomHasSubscribers,
690
+ sendToAnalyticsRoom,
691
+ subscribeIssueLogRoom,
692
+ unsubscribeIssueLogRoom,
693
+ issueLogRoomSize,
694
+ sendToIssueLogRoom,
695
+ subscribeServicesRoom,
696
+ unsubscribeServicesRoom,
697
+ servicesRoomHasSubscribers,
698
+ sendToServicesRoom,
699
+ subscribeMeshRoom,
700
+ unsubscribeMeshRoom,
701
+ sendToMeshRoom,
702
+ meshRoomHasSubscribers,
703
+ setReverseProxySnapshotProvider,
704
+ subscribeReverseProxyRoom,
705
+ unsubscribeReverseProxyRoom,
706
+ reverseProxyRoomHasSubscribers,
707
+ sendToReverseProxyRoom,
708
+ notifyReverseProxySnapshot,
709
+ unsubscribeFromAllRooms,
710
+ serviceLogRoomSize,
711
+ sendToServiceLogRoom,
712
+ sendToAllClients,
713
+ parseWsClientMessage,
714
+ handleWsClientMessage,
715
+ broadcastIssueProgress,
716
+ broadcastIssueTransition,
717
+ broadcastToWebSocketClients,
718
+ makeWebSocketConfig,
719
+ getWsTelemetry,
720
+ resetWsTelemetry
721
+ };
722
+ //# sourceMappingURL=chunk-AFOV3ZAF.js.map