triflux 10.2.0 → 10.3.0

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/hub/pipe.mjs CHANGED
@@ -1,38 +1,37 @@
1
1
  // hub/pipe.mjs — Named Pipe/Unix socket 제어 채널
2
2
  // NDJSON 프로토콜로 에이전트 실시간 제어/이벤트 푸시를 처리한다.
3
3
 
4
- import net from 'node:net';
5
- import { existsSync, unlinkSync } from 'node:fs';
6
- import { randomUUID } from 'node:crypto';
7
- import { getTeamBridge } from './team-bridge.mjs';
8
- import { createPipeline } from './pipeline/index.mjs';
4
+ import { randomUUID } from "node:crypto";
5
+ import { existsSync, unlinkSync } from "node:fs";
6
+ import net from "node:net";
7
+ import { createPipeline } from "./pipeline/index.mjs";
9
8
  import {
10
9
  ensurePipelineTable,
11
10
  initPipelineState,
12
11
  listPipelineStates,
13
12
  readPipelineState,
14
- } from './pipeline/state.mjs';
15
- import { IS_WINDOWS, pipePath } from './platform.mjs';
16
- import { safeJsonParse } from './workers/worker-utils.mjs';
13
+ } from "./pipeline/state.mjs";
14
+ import { IS_WINDOWS, pipePath } from "./platform.mjs";
15
+ import { sendInputToConductorSession } from "./team/conductor-registry.mjs";
16
+ import { getTeamBridge } from "./team-bridge.mjs";
17
+ import { safeJsonParse } from "./workers/worker-utils.mjs";
17
18
 
18
19
  const DEFAULT_HEARTBEAT_TTL_MS = 60000;
19
- const TEAM_BRIDGE_NOT_REGISTERED = 'bridge_not_registered';
20
+ const TEAM_BRIDGE_NOT_REGISTERED = "bridge_not_registered";
20
21
 
21
22
  /** 플랫폼별 pipe 경로 계산 */
22
23
  export function getPipePath(sessionId = process.pid) {
23
- return pipePath('triflux', sessionId);
24
+ return pipePath("triflux", sessionId);
24
25
  }
25
26
 
26
27
  function normalizeTopics(topics) {
27
28
  if (!Array.isArray(topics)) return [];
28
- return topics
29
- .map((topic) => String(topic || '').trim())
30
- .filter(Boolean);
29
+ return topics.map((topic) => String(topic || "").trim()).filter(Boolean);
31
30
  }
32
31
 
33
32
  function getTeamBridgeMethod(methodName) {
34
33
  const method = getTeamBridge()?.[methodName];
35
- return typeof method === 'function' ? method : null;
34
+ return typeof method === "function" ? method : null;
36
35
  }
37
36
 
38
37
  function teamInfoFallback(payload = {}) {
@@ -101,7 +100,7 @@ function teamSendMessageFallback(payload = {}) {
101
100
  ok: true,
102
101
  data: {
103
102
  message_id: null,
104
- recipient: payload.to ?? 'team-lead',
103
+ recipient: payload.to ?? "team-lead",
105
104
  inbox_file: null,
106
105
  queued_at: null,
107
106
  unread_count: 0,
@@ -125,9 +124,10 @@ export function createPipeServer({
125
124
  sessionId = process.pid,
126
125
  heartbeatTtlMs = DEFAULT_HEARTBEAT_TTL_MS,
127
126
  delegatorService = null,
127
+ hitlManager = null,
128
128
  } = {}) {
129
129
  if (!router) {
130
- throw new Error('router is required');
130
+ throw new Error("router is required");
131
131
  }
132
132
 
133
133
  const pipePath = getPipePath(sessionId);
@@ -146,14 +146,20 @@ export function createPipeServer({
146
146
  }
147
147
 
148
148
  function sendResponse(client, requestId, result) {
149
- return sendFrame(client, { type: 'response', request_id: requestId, ...result });
149
+ return sendFrame(client, {
150
+ type: "response",
151
+ request_id: requestId,
152
+ ...result,
153
+ });
150
154
  }
151
155
 
152
156
  function closeClient(client) {
153
157
  if (!client || client.closed) return;
154
158
  client.closed = true;
155
159
  clients.delete(client.id);
156
- try { client.socket.destroy(); } catch {}
160
+ try {
161
+ client.socket.destroy();
162
+ } catch {}
157
163
  }
158
164
 
159
165
  function touchClient(client) {
@@ -163,7 +169,7 @@ export function createPipeServer({
163
169
  function resolveAgentId(client, payload) {
164
170
  const agentId = payload?.agent_id || client?.agentId;
165
171
  if (!agentId) {
166
- throw new Error('agent_id required');
172
+ throw new Error("agent_id required");
167
173
  }
168
174
  return agentId;
169
175
  }
@@ -172,11 +178,13 @@ export function createPipeServer({
172
178
  let delivered = false;
173
179
  for (const client of clients.values()) {
174
180
  if (client.agentId !== agentId) continue;
175
- if (sendFrame(client, {
176
- type: 'event',
177
- event: 'message',
178
- payload: { agent_id: agentId, message },
179
- })) {
181
+ if (
182
+ sendFrame(client, {
183
+ type: "event",
184
+ event: "message",
185
+ payload: { agent_id: agentId, message },
186
+ })
187
+ ) {
180
188
  delivered = true;
181
189
  }
182
190
  }
@@ -199,18 +207,20 @@ export function createPipeServer({
199
207
 
200
208
  async function processCommand(client, action, payload = {}) {
201
209
  switch (action) {
202
- case 'register': {
210
+ case "register": {
203
211
  const result = router.registerAgent(payload);
204
212
  if (client) {
205
213
  client.agentId = payload.agent_id;
206
- client.subscriptions = new Set(router.getSubscribedTopics(client.agentId));
214
+ client.subscriptions = new Set(
215
+ router.getSubscribedTopics(client.agentId),
216
+ );
207
217
  touchClient(client);
208
218
  pushPendingMessages(client.agentId);
209
219
  }
210
220
  return { ok: true, data: { ...result, pipe_path: pipePath } };
211
221
  }
212
222
 
213
- case 'subscribe': {
223
+ case "subscribe": {
214
224
  const agentId = resolveAgentId(client, payload);
215
225
  const topics = normalizeTopics(payload.topics);
216
226
  const result = router.subscribeAgent(agentId, topics, {
@@ -228,55 +238,61 @@ export function createPipeServer({
228
238
  };
229
239
  }
230
240
 
231
- case 'ack': {
241
+ case "ack": {
232
242
  const agentId = resolveAgentId(client, payload);
233
- const acked = router.ackMessages(payload.message_ids || payload.ack_ids || [], agentId);
243
+ const acked = router.ackMessages(
244
+ payload.message_ids || payload.ack_ids || [],
245
+ agentId,
246
+ );
234
247
  if (client) touchClient(client);
235
248
  return { ok: true, data: { agent_id: agentId, acked_count: acked } };
236
249
  }
237
250
 
238
- case 'heartbeat': {
251
+ case "heartbeat": {
239
252
  const agentId = resolveAgentId(client, payload);
240
- const result = router.refreshAgentLease(agentId, payload.heartbeat_ttl_ms || heartbeatTtlMs);
253
+ const result = router.refreshAgentLease(
254
+ agentId,
255
+ payload.heartbeat_ttl_ms || heartbeatTtlMs,
256
+ );
241
257
  if (client) touchClient(client);
242
258
  return { ok: true, data: result };
243
259
  }
244
260
 
245
- case 'publish': {
261
+ case "publish": {
246
262
  const result = router.handlePublish(payload);
247
263
  if (client) touchClient(client);
248
264
  return result;
249
265
  }
250
266
 
251
- case 'handoff': {
267
+ case "handoff": {
252
268
  const result = router.handleHandoff(payload);
253
269
  if (client) touchClient(client);
254
270
  return result;
255
271
  }
256
272
 
257
- case 'assign': {
273
+ case "assign": {
258
274
  const result = router.assignAsync(payload);
259
275
  if (client) touchClient(client);
260
276
  return result;
261
277
  }
262
278
 
263
- case 'assign_result': {
279
+ case "assign_result": {
264
280
  const result = router.reportAssignResult(payload);
265
281
  if (client) touchClient(client);
266
282
  return result;
267
283
  }
268
284
 
269
- case 'assign_retry': {
285
+ case "assign_retry": {
270
286
  const result = router.retryAssign(payload.job_id, payload);
271
287
  if (client) touchClient(client);
272
288
  return result;
273
289
  }
274
290
 
275
- case 'result': {
291
+ case "result": {
276
292
  const result = router.handlePublish({
277
293
  from: payload.agent_id,
278
- to: `topic:${payload.topic || 'task.result'}`,
279
- topic: payload.topic || 'task.result',
294
+ to: `topic:${payload.topic || "task.result"}`,
295
+ topic: payload.topic || "task.result",
280
296
  payload: payload.payload || {},
281
297
  priority: 5,
282
298
  ttl_ms: 3600000,
@@ -287,19 +303,22 @@ export function createPipeServer({
287
303
  return result;
288
304
  }
289
305
 
290
- case 'control': {
306
+ case "control": {
291
307
  const result = router.handlePublish({
292
- from: payload.from_agent || 'lead',
308
+ from: payload.from_agent || "lead",
293
309
  to: payload.to_agent,
294
- topic: 'lead.control',
310
+ topic: "lead.control",
295
311
  payload: {
296
312
  command: payload.command,
297
- reason: payload.reason || '',
313
+ reason: payload.reason || "",
298
314
  ...(payload.payload || {}),
299
315
  issued_at: Date.now(),
300
316
  },
301
317
  priority: 8,
302
- ttl_ms: Math.max(1000, Math.min(Number(payload.ttl_ms) || 3600000, 86400000)),
318
+ ttl_ms: Math.max(
319
+ 1000,
320
+ Math.min(Number(payload.ttl_ms) || 3600000, 86400000),
321
+ ),
303
322
  trace_id: payload.trace_id,
304
323
  correlation_id: payload.correlation_id,
305
324
  });
@@ -307,18 +326,23 @@ export function createPipeServer({
307
326
  return result;
308
327
  }
309
328
 
310
- case 'deregister': {
329
+ case "deregister": {
311
330
  const agentId = resolveAgentId(client, payload);
312
- router.updateAgentStatus(agentId, 'offline');
331
+ router.updateAgentStatus(agentId, "offline");
313
332
  if (client) touchClient(client);
314
333
  return {
315
334
  ok: true,
316
- data: { agent_id: agentId, status: 'offline' },
335
+ data: { agent_id: agentId, status: "offline" },
317
336
  };
318
337
  }
319
338
 
320
- case 'team_task_update': {
321
- const teamTaskUpdate = getTeamBridgeMethod('teamTaskUpdate');
339
+ case "send_input": {
340
+ if (client) touchClient(client);
341
+ return sendInputToConductorSession(payload.session_id, payload.text);
342
+ }
343
+
344
+ case "team_task_update": {
345
+ const teamTaskUpdate = getTeamBridgeMethod("teamTaskUpdate");
322
346
  const result = teamTaskUpdate
323
347
  ? await teamTaskUpdate(payload)
324
348
  : teamTaskUpdateFallback(payload);
@@ -326,8 +350,8 @@ export function createPipeServer({
326
350
  return result;
327
351
  }
328
352
 
329
- case 'team_send_message': {
330
- const teamSendMessage = getTeamBridgeMethod('teamSendMessage');
353
+ case "team_send_message": {
354
+ const teamSendMessage = getTeamBridgeMethod("teamSendMessage");
331
355
  const result = teamSendMessage
332
356
  ? await teamSendMessage(payload)
333
357
  : teamSendMessageFallback(payload);
@@ -335,20 +359,20 @@ export function createPipeServer({
335
359
  return result;
336
360
  }
337
361
 
338
- case 'pipeline_advance': {
362
+ case "pipeline_advance": {
339
363
  if (client) touchClient(client);
340
364
  if (!store?.db) {
341
- return { ok: false, error: 'hub_db_not_found' };
365
+ return { ok: false, error: "hub_db_not_found" };
342
366
  }
343
367
  ensurePipelineTable(store.db);
344
368
  const pipeline = createPipeline(store.db, payload.team_name);
345
369
  return pipeline.advance(payload.phase);
346
370
  }
347
371
 
348
- case 'pipeline_init': {
372
+ case "pipeline_init": {
349
373
  if (client) touchClient(client);
350
374
  if (!store?.db) {
351
- return { ok: false, error: 'hub_db_not_found' };
375
+ return { ok: false, error: "hub_db_not_found" };
352
376
  }
353
377
  ensurePipelineTable(store.db);
354
378
  const state = initPipelineState(store.db, payload.team_name, {
@@ -358,18 +382,46 @@ export function createPipeServer({
358
382
  return { ok: true, data: state };
359
383
  }
360
384
 
361
- case 'delegator_delegate': {
385
+ case "hitl_request": {
386
+ if (!hitlManager) {
387
+ return { ok: false, error: "hitl not available" };
388
+ }
389
+ if (client) touchClient(client);
390
+ return hitlManager.requestHumanInput(payload);
391
+ }
392
+
393
+ case "hitl_submit": {
394
+ if (!hitlManager) {
395
+ return { ok: false, error: "hitl not available" };
396
+ }
397
+ if (client) touchClient(client);
398
+ return hitlManager.submitHumanInput(payload);
399
+ }
400
+
401
+ case "delegator_delegate": {
362
402
  if (!delegatorService) {
363
- return { ok: false, error: { code: 'DELEGATOR_NOT_AVAILABLE', message: 'Delegator service가 초기화되지 않았습니다' } };
403
+ return {
404
+ ok: false,
405
+ error: {
406
+ code: "DELEGATOR_NOT_AVAILABLE",
407
+ message: "Delegator service가 초기화되지 않았습니다",
408
+ },
409
+ };
364
410
  }
365
411
  if (client) touchClient(client);
366
412
  const result = await delegatorService.delegate(payload);
367
413
  return { ok: result?.ok !== false, data: result };
368
414
  }
369
415
 
370
- case 'delegator_reply': {
416
+ case "delegator_reply": {
371
417
  if (!delegatorService) {
372
- return { ok: false, error: { code: 'DELEGATOR_NOT_AVAILABLE', message: 'Delegator service가 초기화되지 않았습니다' } };
418
+ return {
419
+ ok: false,
420
+ error: {
421
+ code: "DELEGATOR_NOT_AVAILABLE",
422
+ message: "Delegator service가 초기화되지 않았습니다",
423
+ },
424
+ };
373
425
  }
374
426
  if (client) touchClient(client);
375
427
  const result = await delegatorService.reply(payload);
@@ -379,13 +431,19 @@ export function createPipeServer({
379
431
  default:
380
432
  return {
381
433
  ok: false,
382
- error: { code: 'UNKNOWN_PIPE_COMMAND', message: `지원하지 않는 command: ${action}` },
434
+ error: {
435
+ code: "UNKNOWN_PIPE_COMMAND",
436
+ message: `지원하지 않는 command: ${action}`,
437
+ },
383
438
  };
384
439
  }
385
440
  }
386
441
 
387
442
  function buildReplayMessages(agentId, payload = {}) {
388
- const maxMessages = Math.max(1, Math.min(Number(payload.max_messages) || 20, 100));
443
+ const maxMessages = Math.max(
444
+ 1,
445
+ Math.min(Number(payload.max_messages) || 20, 100),
446
+ );
389
447
  const pending = router.getPendingMessages(agentId, {
390
448
  max_messages: maxMessages,
391
449
  include_topics: payload.topics,
@@ -410,7 +468,7 @@ export function createPipeServer({
410
468
 
411
469
  async function processQuery(client, action, payload = {}) {
412
470
  switch (action) {
413
- case 'drain': {
471
+ case "drain": {
414
472
  const agentId = resolveAgentId(client, payload);
415
473
  const messages = router.drainAgent(agentId, {
416
474
  max_messages: payload.max_messages,
@@ -420,69 +478,95 @@ export function createPipeServer({
420
478
  if (client) touchClient(client);
421
479
  return {
422
480
  ok: true,
423
- data: { messages, count: messages.length, server_time_ms: Date.now() },
481
+ data: {
482
+ messages,
483
+ count: messages.length,
484
+ server_time_ms: Date.now(),
485
+ },
424
486
  };
425
487
  }
426
488
 
427
- case 'context': {
489
+ case "context": {
428
490
  const agentId = resolveAgentId(client, payload);
429
491
  const messages = buildReplayMessages(agentId, payload);
430
492
  if (client) touchClient(client);
431
493
  return {
432
494
  ok: true,
433
- data: { messages, count: messages.length, server_time_ms: Date.now() },
495
+ data: {
496
+ messages,
497
+ count: messages.length,
498
+ server_time_ms: Date.now(),
499
+ },
434
500
  };
435
501
  }
436
502
 
437
- case 'status': {
438
- const scope = payload.scope || 'hub';
503
+ case "status": {
504
+ const scope = payload.scope || "hub";
439
505
  if (client) touchClient(client);
440
506
  return router.getStatus(scope, payload);
441
507
  }
442
508
 
443
- case 'assign_status': {
509
+ case "assign_status": {
444
510
  if (client) touchClient(client);
445
511
  return router.getAssignStatus(payload);
446
512
  }
447
513
 
448
- case 'team_info': {
449
- const teamInfo = getTeamBridgeMethod('teamInfo');
450
- const result = teamInfo ? await teamInfo(payload) : teamInfoFallback(payload);
514
+ case "team_info": {
515
+ const teamInfo = getTeamBridgeMethod("teamInfo");
516
+ const result = teamInfo
517
+ ? await teamInfo(payload)
518
+ : teamInfoFallback(payload);
451
519
  if (client) touchClient(client);
452
520
  return result;
453
521
  }
454
522
 
455
- case 'team_task_list': {
456
- const teamTaskList = getTeamBridgeMethod('teamTaskList');
457
- const result = teamTaskList ? await teamTaskList(payload) : teamTaskListFallback(payload);
523
+ case "team_task_list": {
524
+ const teamTaskList = getTeamBridgeMethod("teamTaskList");
525
+ const result = teamTaskList
526
+ ? await teamTaskList(payload)
527
+ : teamTaskListFallback(payload);
458
528
  if (client) touchClient(client);
459
529
  return result;
460
530
  }
461
531
 
462
- case 'pipeline_state': {
532
+ case "pipeline_state": {
463
533
  if (client) touchClient(client);
464
534
  if (!store?.db) {
465
- return { ok: false, error: 'hub_db_not_found' };
535
+ return { ok: false, error: "hub_db_not_found" };
466
536
  }
467
537
  ensurePipelineTable(store.db);
468
538
  const state = readPipelineState(store.db, payload.team_name);
469
539
  return state
470
540
  ? { ok: true, data: state }
471
- : { ok: false, error: 'pipeline_not_found' };
541
+ : { ok: false, error: "pipeline_not_found" };
472
542
  }
473
543
 
474
- case 'pipeline_list': {
544
+ case "pipeline_list": {
475
545
  if (client) touchClient(client);
476
546
  if (!store?.db) {
477
- return { ok: false, error: 'hub_db_not_found' };
547
+ return { ok: false, error: "hub_db_not_found" };
478
548
  }
479
549
  ensurePipelineTable(store.db);
480
550
  return { ok: true, data: listPipelineStates(store.db) };
481
551
  }
482
552
 
483
- case 'delegator_status': {
553
+ case "hitl_pending": {
554
+ if (client) touchClient(client);
555
+ if (!hitlManager) {
556
+ return { ok: false, error: "hitl not available" };
557
+ }
558
+ return { ok: true, data: hitlManager.getPendingRequests() };
559
+ }
560
+
561
+ case "delegator_status": {
484
562
  if (!delegatorService) {
485
- return { ok: false, error: { code: 'DELEGATOR_NOT_AVAILABLE', message: 'Delegator service가 초기화되지 않았습니다' } };
563
+ return {
564
+ ok: false,
565
+ error: {
566
+ code: "DELEGATOR_NOT_AVAILABLE",
567
+ message: "Delegator service가 초기화되지 않았습니다",
568
+ },
569
+ };
486
570
  }
487
571
  if (client) touchClient(client);
488
572
  const result = await delegatorService.status(payload);
@@ -492,7 +576,10 @@ export function createPipeServer({
492
576
  default:
493
577
  return {
494
578
  ok: false,
495
- error: { code: 'UNKNOWN_PIPE_QUERY', message: `지원하지 않는 query: ${action}` },
579
+ error: {
580
+ code: "UNKNOWN_PIPE_QUERY",
581
+ message: `지원하지 않는 query: ${action}`,
582
+ },
496
583
  };
497
584
  }
498
585
  }
@@ -507,41 +594,56 @@ export function createPipeServer({
507
594
  }
508
595
 
509
596
  async function handleFrame(client, frame) {
510
- if (!frame || typeof frame !== 'object') {
597
+ if (!frame || typeof frame !== "object") {
511
598
  return sendResponse(client, null, {
512
599
  ok: false,
513
- error: { code: 'INVALID_FRAME', message: 'JSON object frame required' },
600
+ error: { code: "INVALID_FRAME", message: "JSON object frame required" },
514
601
  });
515
602
  }
516
603
 
517
604
  if (!frame.type) {
518
605
  return sendResponse(client, frame.request_id || null, {
519
606
  ok: false,
520
- error: { code: 'INVALID_FRAME', message: 'type required' },
607
+ error: { code: "INVALID_FRAME", message: "type required" },
521
608
  });
522
609
  }
523
610
 
524
611
  touchClient(client);
525
612
 
526
613
  try {
527
- if (frame.type === 'command') {
614
+ if (frame.type === "command") {
528
615
  const action = frame.payload?.action || frame.payload?.command;
529
- const result = await processCommand(client, action, frame.payload || {});
530
- return sendResponse(client, frame.payload?.request_id || frame.request_id || null, result);
531
- }
532
- if (frame.type === 'query') {
616
+ const result = await processCommand(
617
+ client,
618
+ action,
619
+ frame.payload || {},
620
+ );
621
+ return sendResponse(
622
+ client,
623
+ frame.payload?.request_id || frame.request_id || null,
624
+ result,
625
+ );
626
+ }
627
+ if (frame.type === "query") {
533
628
  const action = frame.payload?.action || frame.payload?.query;
534
629
  const result = await processQuery(client, action, frame.payload || {});
535
- return sendResponse(client, frame.payload?.request_id || frame.request_id || null, result);
630
+ return sendResponse(
631
+ client,
632
+ frame.payload?.request_id || frame.request_id || null,
633
+ result,
634
+ );
536
635
  }
537
636
  return sendResponse(client, frame.request_id || null, {
538
637
  ok: false,
539
- error: { code: 'INVALID_FRAME_TYPE', message: `지원하지 않는 type: ${frame.type}` },
638
+ error: {
639
+ code: "INVALID_FRAME_TYPE",
640
+ message: `지원하지 않는 type: ${frame.type}`,
641
+ },
540
642
  });
541
643
  } catch (error) {
542
644
  return sendResponse(client, frame.request_id || null, {
543
645
  ok: false,
544
- error: { code: 'PIPE_REQUEST_FAILED', message: error.message },
646
+ error: { code: "PIPE_REQUEST_FAILED", message: error.message },
545
647
  });
546
648
  }
547
649
  }
@@ -550,7 +652,7 @@ export function createPipeServer({
550
652
  const client = {
551
653
  id: randomUUID(),
552
654
  socket,
553
- buffer: '',
655
+ buffer: "",
554
656
  agentId: null,
555
657
  subscriptions: new Set(),
556
658
  lastHeartbeatMs: Date.now(),
@@ -558,10 +660,10 @@ export function createPipeServer({
558
660
  };
559
661
  clients.set(client.id, client);
560
662
 
561
- socket.setEncoding('utf8');
562
- socket.on('data', async (chunk) => {
663
+ socket.setEncoding("utf8");
664
+ socket.on("data", async (chunk) => {
563
665
  client.buffer += chunk;
564
- let newlineIndex = client.buffer.indexOf('\n');
666
+ let newlineIndex = client.buffer.indexOf("\n");
565
667
  while (newlineIndex >= 0) {
566
668
  const line = client.buffer.slice(0, newlineIndex).trim();
567
669
  client.buffer = client.buffer.slice(newlineIndex + 1);
@@ -569,27 +671,30 @@ export function createPipeServer({
569
671
  const frame = safeJsonParse(line);
570
672
  await handleFrame(client, frame);
571
673
  }
572
- newlineIndex = client.buffer.indexOf('\n');
674
+ newlineIndex = client.buffer.indexOf("\n");
573
675
  }
574
676
  });
575
677
 
576
- socket.on('close', () => closeClient(client));
577
- socket.on('error', () => closeClient(client));
678
+ socket.on("close", () => closeClient(client));
679
+ socket.on("error", () => closeClient(client));
578
680
  }
579
681
 
580
682
  function startHeartbeatMonitor() {
581
- heartbeatTimer = setInterval(() => {
582
- const now = Date.now();
583
- for (const client of clients.values()) {
584
- if (now - client.lastHeartbeatMs <= heartbeatTtlMs) continue;
585
- sendFrame(client, {
586
- type: 'event',
587
- event: 'disconnect',
588
- payload: { reason: 'heartbeat_timeout' },
589
- });
590
- closeClient(client);
591
- }
592
- }, Math.max(1000, Math.floor(heartbeatTtlMs / 2)));
683
+ heartbeatTimer = setInterval(
684
+ () => {
685
+ const now = Date.now();
686
+ for (const client of clients.values()) {
687
+ if (now - client.lastHeartbeatMs <= heartbeatTtlMs) continue;
688
+ sendFrame(client, {
689
+ type: "event",
690
+ event: "disconnect",
691
+ payload: { reason: "heartbeat_timeout" },
692
+ });
693
+ closeClient(client);
694
+ }
695
+ },
696
+ Math.max(1000, Math.floor(heartbeatTtlMs / 2)),
697
+ );
593
698
  heartbeatTimer.unref();
594
699
  }
595
700
 
@@ -600,16 +705,18 @@ export function createPipeServer({
600
705
  if (server) return { path: pipePath };
601
706
 
602
707
  if (!IS_WINDOWS && existsSync(pipePath)) {
603
- try { unlinkSync(pipePath); } catch {}
708
+ try {
709
+ unlinkSync(pipePath);
710
+ } catch {}
604
711
  }
605
712
 
606
713
  server = net.createServer(attachSocket);
607
- router.deliveryEmitter.on('message', onMessage);
714
+ router.deliveryEmitter.on("message", onMessage);
608
715
 
609
716
  await new Promise((resolve, reject) => {
610
- server.once('error', reject);
717
+ server.once("error", reject);
611
718
  server.listen(pipePath, () => {
612
- server.off('error', reject);
719
+ server.off("error", reject);
613
720
  resolve();
614
721
  });
615
722
  });
@@ -624,7 +731,7 @@ export function createPipeServer({
624
731
  heartbeatTimer = null;
625
732
  }
626
733
 
627
- router.deliveryEmitter.off('message', onMessage);
734
+ router.deliveryEmitter.off("message", onMessage);
628
735
 
629
736
  for (const client of clients.values()) {
630
737
  closeClient(client);
@@ -637,14 +744,16 @@ export function createPipeServer({
637
744
  }
638
745
 
639
746
  if (!IS_WINDOWS && existsSync(pipePath)) {
640
- try { unlinkSync(pipePath); } catch {}
747
+ try {
748
+ unlinkSync(pipePath);
749
+ } catch {}
641
750
  }
642
751
  },
643
752
 
644
753
  getStatus() {
645
754
  return {
646
755
  path: pipePath,
647
- protocol: 'ndjson',
756
+ protocol: "ndjson",
648
757
  clients: clients.size,
649
758
  pending_messages: Array.from(clients.values()).reduce((sum, client) => {
650
759
  if (!client.agentId) return sum;