@triflux/remote 10.0.1 → 10.25.1

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 (155) hide show
  1. package/hub/pipe.mjs +239 -121
  2. package/hub/public/dashboard.html +68 -7
  3. package/hub/server.mjs +2181 -701
  4. package/hub/store-adapter.mjs +101 -37
  5. package/hub/store.mjs +356 -153
  6. package/hub/team/agent-map.json +26 -7
  7. package/hub/team/ansi.mjs +245 -146
  8. package/hub/team/backend.mjs +84 -17
  9. package/hub/team/build-worker-prompt.mjs +42 -0
  10. package/hub/team/check-mcp-hub.mjs +49 -0
  11. package/hub/team/claude-daemon-control.mjs +219 -0
  12. package/hub/team/claude-native-bridge.mjs +784 -0
  13. package/hub/team/claude-session-projection.mjs +65 -0
  14. package/hub/team/cli/commands/attach.mjs +57 -37
  15. package/hub/team/cli/commands/control.mjs +29 -8
  16. package/hub/team/cli/commands/debug.mjs +95 -74
  17. package/hub/team/cli/commands/focus.mjs +80 -53
  18. package/hub/team/cli/commands/interrupt.mjs +18 -6
  19. package/hub/team/cli/commands/kill.mjs +16 -5
  20. package/hub/team/cli/commands/list.mjs +31 -24
  21. package/hub/team/cli/commands/send.mjs +19 -6
  22. package/hub/team/cli/commands/start/index.mjs +154 -33
  23. package/hub/team/cli/commands/start/parse-args.mjs +89 -12
  24. package/hub/team/cli/commands/start/start-headless.mjs +127 -37
  25. package/hub/team/cli/commands/start/start-in-process.mjs +50 -40
  26. package/hub/team/cli/commands/start/start-mux.mjs +122 -73
  27. package/hub/team/cli/commands/start/start-wt.mjs +86 -69
  28. package/hub/team/cli/commands/status.mjs +43 -14
  29. package/hub/team/cli/commands/stop.mjs +11 -4
  30. package/hub/team/cli/commands/task.mjs +8 -3
  31. package/hub/team/cli/commands/tasks.mjs +13 -13
  32. package/hub/team/cli/index.mjs +2 -2
  33. package/hub/team/cli/manifest.mjs +38 -8
  34. package/hub/team/cli/render.mjs +52 -30
  35. package/hub/team/cli/services/attach-fallback.mjs +74 -54
  36. package/hub/team/cli/services/hub-client.mjs +45 -17
  37. package/hub/team/cli/services/member-selector.mjs +37 -30
  38. package/hub/team/cli/services/native-control.mjs +144 -116
  39. package/hub/team/cli/services/runtime-mode.mjs +55 -1
  40. package/hub/team/cli/services/state-store.mjs +66 -9
  41. package/hub/team/cli/services/task-model.mjs +40 -30
  42. package/hub/team/codex-review.mjs +521 -0
  43. package/hub/team/conductor-mesh-bridge.mjs +25 -24
  44. package/hub/team/conductor-registry.mjs +121 -0
  45. package/hub/team/conductor.mjs +1379 -671
  46. package/hub/team/consensus-meta.mjs +176 -0
  47. package/hub/team/dashboard-anchor.mjs +4 -5
  48. package/hub/team/dashboard-layout.mjs +3 -1
  49. package/hub/team/dashboard-open.mjs +21 -119
  50. package/hub/team/dashboard.mjs +76 -28
  51. package/hub/team/event-log.mjs +18 -10
  52. package/hub/team/execution-mode.mjs +241 -0
  53. package/hub/team/extract-completion-payload.mjs +110 -0
  54. package/hub/team/git-preflight.mjs +286 -0
  55. package/hub/team/handoff.mjs +49 -18
  56. package/hub/team/headless-bridge-session.mjs +17 -0
  57. package/hub/team/headless.mjs +2065 -1156
  58. package/hub/team/health-probe.mjs +232 -69
  59. package/hub/team/launcher-template.mjs +23 -13
  60. package/hub/team/lead-control.mjs +91 -4
  61. package/hub/team/mcp-selector.mjs +145 -0
  62. package/hub/team/native-supervisor.mjs +90 -26
  63. package/hub/team/native.mjs +74 -35
  64. package/hub/team/nativeProxy.mjs +184 -113
  65. package/hub/team/notify.mjs +147 -78
  66. package/hub/team/orchestrator.mjs +173 -161
  67. package/hub/team/pane.mjs +43 -15
  68. package/hub/team/process-cleanup.mjs +69 -25
  69. package/hub/team/psmux.mjs +1067 -264
  70. package/hub/team/recovery-store.mjs +149 -0
  71. package/hub/team/remote-probe.mjs +69 -53
  72. package/hub/team/remote-session.mjs +131 -59
  73. package/hub/team/remote-watcher.mjs +62 -34
  74. package/hub/team/retry-state-machine.mjs +267 -0
  75. package/hub/team/routing.mjs +51 -25
  76. package/hub/team/runtime-strategy.mjs +200 -0
  77. package/hub/team/schemas/worker-completion.json +39 -0
  78. package/hub/team/sentinel-capture.mjs +127 -0
  79. package/hub/team/session-sync.mjs +153 -6
  80. package/hub/team/session.mjs +605 -611
  81. package/hub/team/shared.mjs +13 -13
  82. package/hub/team/staleState.mjs +72 -30
  83. package/hub/team/swarm-cli.mjs +390 -0
  84. package/hub/team/swarm-hypervisor.mjs +1526 -198
  85. package/hub/team/swarm-intent.mjs +216 -0
  86. package/hub/team/swarm-locks.mjs +140 -25
  87. package/hub/team/swarm-planner.mjs +121 -21
  88. package/hub/team/swarm-preflight.mjs +317 -0
  89. package/hub/team/swarm-reconciler.mjs +128 -23
  90. package/hub/team/synapse-cli.mjs +458 -0
  91. package/hub/team/synapse-http.mjs +60 -0
  92. package/hub/team/synapse-registry.mjs +329 -0
  93. package/hub/team/terminal-opener.mjs +216 -0
  94. package/hub/team/tui-core.mjs +288 -0
  95. package/hub/team/tui-lite.mjs +234 -146
  96. package/hub/team/tui-remote-adapter.mjs +21 -37
  97. package/hub/team/tui-synapse.mjs +223 -0
  98. package/hub/team/tui-viewer.mjs +104 -45
  99. package/hub/team/tui-widgets.mjs +262 -0
  100. package/hub/team/tui.mjs +936 -449
  101. package/hub/team/worker-completion-validator.mjs +84 -0
  102. package/hub/team/worker-sandbox.mjs +93 -0
  103. package/hub/team/worker-signal.mjs +263 -0
  104. package/hub/team/worker-signal.types.d.ts +52 -0
  105. package/hub/team/worktree-lifecycle.mjs +518 -64
  106. package/hub/team/wt-manager.mjs +297 -26
  107. package/hub/tools.mjs +474 -252
  108. package/hub/tray.mjs +411 -368
  109. package/hub/workers/claude-worker.mjs +162 -118
  110. package/hub/workers/codex-app-server-worker.mjs +1015 -0
  111. package/hub/workers/codex-mcp.mjs +724 -507
  112. package/hub/workers/delegator-mcp.mjs +663 -337
  113. package/hub/workers/factory.mjs +100 -21
  114. package/hub/workers/gemini-worker.mjs +179 -86
  115. package/hub/workers/interface.mjs +23 -2
  116. package/hub/workers/lib/jsonrpc-stdio.mjs +463 -0
  117. package/hub/workers/worker-utils.mjs +22 -16
  118. package/package.json +4 -3
  119. package/scripts/lib/agent-json.mjs +27 -0
  120. package/scripts/lib/async.mjs +174 -0
  121. package/scripts/lib/claudemd-scanner.mjs +223 -0
  122. package/scripts/lib/cli-agy.mjs +62 -0
  123. package/scripts/lib/cli-claude.mjs +78 -0
  124. package/scripts/lib/cli-codex.mjs +199 -0
  125. package/scripts/lib/cli-gemini.mjs +67 -0
  126. package/scripts/lib/codex-recovery.sh +50 -0
  127. package/scripts/lib/context.mjs +67 -0
  128. package/scripts/lib/cross-review-utils.mjs +59 -0
  129. package/scripts/lib/doctor-env-checks.mjs +121 -0
  130. package/scripts/lib/dynamic-route-cli.mjs +107 -0
  131. package/scripts/lib/env-probe.mjs +269 -0
  132. package/scripts/lib/env.mjs +14 -0
  133. package/scripts/lib/gemini-profiles.mjs +119 -0
  134. package/scripts/lib/handoff.mjs +187 -0
  135. package/scripts/lib/hook-utils.mjs +16 -0
  136. package/scripts/lib/hub.mjs +48 -0
  137. package/scripts/lib/keyword-rules.mjs +195 -0
  138. package/scripts/lib/logger.mjs +105 -0
  139. package/scripts/lib/mcp-filter.mjs +883 -0
  140. package/scripts/lib/mcp-gateway-servers.mjs +70 -0
  141. package/scripts/lib/mcp-guard-engine.mjs +2120 -0
  142. package/scripts/lib/mcp-health.mjs +773 -0
  143. package/scripts/lib/mcp-manifest.mjs +89 -0
  144. package/scripts/lib/mcp-server-catalog.mjs +355 -0
  145. package/scripts/lib/pid.mjs +63 -0
  146. package/scripts/lib/process-utils.mjs +26 -0
  147. package/scripts/lib/psmux-info.mjs +179 -0
  148. package/scripts/lib/quota.mjs +47 -0
  149. package/scripts/lib/remote-spawn-transfer.mjs +226 -0
  150. package/scripts/lib/skill-state.mjs +220 -0
  151. package/scripts/lib/skill-template.mjs +375 -0
  152. package/scripts/lib/team.mjs +75 -0
  153. package/scripts/lib/timeout.mjs +66 -0
  154. package/scripts/lib/tmp.mjs +26 -0
  155. package/scripts/lib/toml.mjs +113 -0
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 '@triflux/core/hub/team-bridge.mjs';
8
- import { createPipeline } from '@triflux/core/hub/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 "@triflux/core/hub/pipeline/index.mjs";
9
8
  import {
10
9
  ensurePipelineTable,
11
10
  initPipelineState,
12
11
  listPipelineStates,
13
12
  readPipelineState,
14
- } from '@triflux/core/hub/pipeline/state.mjs';
15
- import { IS_WINDOWS, pipePath } from '@triflux/core/hub/platform.mjs';
16
- import { safeJsonParse } from './workers/worker-utils.mjs';
13
+ } from "@triflux/core/hub/pipeline/state.mjs";
14
+ import { IS_WINDOWS, pipePath } from "@triflux/core/hub/platform.mjs";
15
+ import { sendInputToConductorSession } from "./team/conductor-registry.mjs";
16
+ import { getTeamBridge } from "@triflux/core/hub/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,11 @@ export function createPipeServer({
125
124
  sessionId = process.pid,
126
125
  heartbeatTtlMs = DEFAULT_HEARTBEAT_TTL_MS,
127
126
  delegatorService = null,
127
+ hitlManager = null,
128
+ onActivity = null,
128
129
  } = {}) {
129
130
  if (!router) {
130
- throw new Error('router is required');
131
+ throw new Error("router is required");
131
132
  }
132
133
 
133
134
  const pipePath = getPipePath(sessionId);
@@ -146,24 +147,31 @@ export function createPipeServer({
146
147
  }
147
148
 
148
149
  function sendResponse(client, requestId, result) {
149
- return sendFrame(client, { type: 'response', request_id: requestId, ...result });
150
+ return sendFrame(client, {
151
+ type: "response",
152
+ request_id: requestId,
153
+ ...result,
154
+ });
150
155
  }
151
156
 
152
157
  function closeClient(client) {
153
158
  if (!client || client.closed) return;
154
159
  client.closed = true;
155
160
  clients.delete(client.id);
156
- try { client.socket.destroy(); } catch {}
161
+ try {
162
+ client.socket.destroy();
163
+ } catch {}
157
164
  }
158
165
 
159
166
  function touchClient(client) {
160
167
  client.lastHeartbeatMs = Date.now();
168
+ if (onActivity) onActivity();
161
169
  }
162
170
 
163
171
  function resolveAgentId(client, payload) {
164
172
  const agentId = payload?.agent_id || client?.agentId;
165
173
  if (!agentId) {
166
- throw new Error('agent_id required');
174
+ throw new Error("agent_id required");
167
175
  }
168
176
  return agentId;
169
177
  }
@@ -172,11 +180,13 @@ export function createPipeServer({
172
180
  let delivered = false;
173
181
  for (const client of clients.values()) {
174
182
  if (client.agentId !== agentId) continue;
175
- if (sendFrame(client, {
176
- type: 'event',
177
- event: 'message',
178
- payload: { agent_id: agentId, message },
179
- })) {
183
+ if (
184
+ sendFrame(client, {
185
+ type: "event",
186
+ event: "message",
187
+ payload: { agent_id: agentId, message },
188
+ })
189
+ ) {
180
190
  delivered = true;
181
191
  }
182
192
  }
@@ -199,18 +209,20 @@ export function createPipeServer({
199
209
 
200
210
  async function processCommand(client, action, payload = {}) {
201
211
  switch (action) {
202
- case 'register': {
212
+ case "register": {
203
213
  const result = router.registerAgent(payload);
204
214
  if (client) {
205
215
  client.agentId = payload.agent_id;
206
- client.subscriptions = new Set(router.getSubscribedTopics(client.agentId));
216
+ client.subscriptions = new Set(
217
+ router.getSubscribedTopics(client.agentId),
218
+ );
207
219
  touchClient(client);
208
220
  pushPendingMessages(client.agentId);
209
221
  }
210
222
  return { ok: true, data: { ...result, pipe_path: pipePath } };
211
223
  }
212
224
 
213
- case 'subscribe': {
225
+ case "subscribe": {
214
226
  const agentId = resolveAgentId(client, payload);
215
227
  const topics = normalizeTopics(payload.topics);
216
228
  const result = router.subscribeAgent(agentId, topics, {
@@ -228,55 +240,61 @@ export function createPipeServer({
228
240
  };
229
241
  }
230
242
 
231
- case 'ack': {
243
+ case "ack": {
232
244
  const agentId = resolveAgentId(client, payload);
233
- const acked = router.ackMessages(payload.message_ids || payload.ack_ids || [], agentId);
245
+ const acked = router.ackMessages(
246
+ payload.message_ids || payload.ack_ids || [],
247
+ agentId,
248
+ );
234
249
  if (client) touchClient(client);
235
250
  return { ok: true, data: { agent_id: agentId, acked_count: acked } };
236
251
  }
237
252
 
238
- case 'heartbeat': {
253
+ case "heartbeat": {
239
254
  const agentId = resolveAgentId(client, payload);
240
- const result = router.refreshAgentLease(agentId, payload.heartbeat_ttl_ms || heartbeatTtlMs);
255
+ const result = router.refreshAgentLease(
256
+ agentId,
257
+ payload.heartbeat_ttl_ms || heartbeatTtlMs,
258
+ );
241
259
  if (client) touchClient(client);
242
260
  return { ok: true, data: result };
243
261
  }
244
262
 
245
- case 'publish': {
263
+ case "publish": {
246
264
  const result = router.handlePublish(payload);
247
265
  if (client) touchClient(client);
248
266
  return result;
249
267
  }
250
268
 
251
- case 'handoff': {
269
+ case "handoff": {
252
270
  const result = router.handleHandoff(payload);
253
271
  if (client) touchClient(client);
254
272
  return result;
255
273
  }
256
274
 
257
- case 'assign': {
275
+ case "assign": {
258
276
  const result = router.assignAsync(payload);
259
277
  if (client) touchClient(client);
260
278
  return result;
261
279
  }
262
280
 
263
- case 'assign_result': {
281
+ case "assign_result": {
264
282
  const result = router.reportAssignResult(payload);
265
283
  if (client) touchClient(client);
266
284
  return result;
267
285
  }
268
286
 
269
- case 'assign_retry': {
287
+ case "assign_retry": {
270
288
  const result = router.retryAssign(payload.job_id, payload);
271
289
  if (client) touchClient(client);
272
290
  return result;
273
291
  }
274
292
 
275
- case 'result': {
293
+ case "result": {
276
294
  const result = router.handlePublish({
277
295
  from: payload.agent_id,
278
- to: `topic:${payload.topic || 'task.result'}`,
279
- topic: payload.topic || 'task.result',
296
+ to: `topic:${payload.topic || "task.result"}`,
297
+ topic: payload.topic || "task.result",
280
298
  payload: payload.payload || {},
281
299
  priority: 5,
282
300
  ttl_ms: 3600000,
@@ -287,19 +305,22 @@ export function createPipeServer({
287
305
  return result;
288
306
  }
289
307
 
290
- case 'control': {
308
+ case "control": {
291
309
  const result = router.handlePublish({
292
- from: payload.from_agent || 'lead',
310
+ from: payload.from_agent || "lead",
293
311
  to: payload.to_agent,
294
- topic: 'lead.control',
312
+ topic: "lead.control",
295
313
  payload: {
296
314
  command: payload.command,
297
- reason: payload.reason || '',
315
+ reason: payload.reason || "",
298
316
  ...(payload.payload || {}),
299
317
  issued_at: Date.now(),
300
318
  },
301
319
  priority: 8,
302
- ttl_ms: Math.max(1000, Math.min(Number(payload.ttl_ms) || 3600000, 86400000)),
320
+ ttl_ms: Math.max(
321
+ 1000,
322
+ Math.min(Number(payload.ttl_ms) || 3600000, 86400000),
323
+ ),
303
324
  trace_id: payload.trace_id,
304
325
  correlation_id: payload.correlation_id,
305
326
  });
@@ -307,18 +328,23 @@ export function createPipeServer({
307
328
  return result;
308
329
  }
309
330
 
310
- case 'deregister': {
331
+ case "deregister": {
311
332
  const agentId = resolveAgentId(client, payload);
312
- router.updateAgentStatus(agentId, 'offline');
333
+ router.updateAgentStatus(agentId, "offline");
313
334
  if (client) touchClient(client);
314
335
  return {
315
336
  ok: true,
316
- data: { agent_id: agentId, status: 'offline' },
337
+ data: { agent_id: agentId, status: "offline" },
317
338
  };
318
339
  }
319
340
 
320
- case 'team_task_update': {
321
- const teamTaskUpdate = getTeamBridgeMethod('teamTaskUpdate');
341
+ case "send_input": {
342
+ if (client) touchClient(client);
343
+ return sendInputToConductorSession(payload.session_id, payload.text);
344
+ }
345
+
346
+ case "team_task_update": {
347
+ const teamTaskUpdate = getTeamBridgeMethod("teamTaskUpdate");
322
348
  const result = teamTaskUpdate
323
349
  ? await teamTaskUpdate(payload)
324
350
  : teamTaskUpdateFallback(payload);
@@ -326,8 +352,8 @@ export function createPipeServer({
326
352
  return result;
327
353
  }
328
354
 
329
- case 'team_send_message': {
330
- const teamSendMessage = getTeamBridgeMethod('teamSendMessage');
355
+ case "team_send_message": {
356
+ const teamSendMessage = getTeamBridgeMethod("teamSendMessage");
331
357
  const result = teamSendMessage
332
358
  ? await teamSendMessage(payload)
333
359
  : teamSendMessageFallback(payload);
@@ -335,20 +361,20 @@ export function createPipeServer({
335
361
  return result;
336
362
  }
337
363
 
338
- case 'pipeline_advance': {
364
+ case "pipeline_advance": {
339
365
  if (client) touchClient(client);
340
366
  if (!store?.db) {
341
- return { ok: false, error: 'hub_db_not_found' };
367
+ return { ok: false, error: "hub_db_not_found" };
342
368
  }
343
369
  ensurePipelineTable(store.db);
344
370
  const pipeline = createPipeline(store.db, payload.team_name);
345
371
  return pipeline.advance(payload.phase);
346
372
  }
347
373
 
348
- case 'pipeline_init': {
374
+ case "pipeline_init": {
349
375
  if (client) touchClient(client);
350
376
  if (!store?.db) {
351
- return { ok: false, error: 'hub_db_not_found' };
377
+ return { ok: false, error: "hub_db_not_found" };
352
378
  }
353
379
  ensurePipelineTable(store.db);
354
380
  const state = initPipelineState(store.db, payload.team_name, {
@@ -358,18 +384,46 @@ export function createPipeServer({
358
384
  return { ok: true, data: state };
359
385
  }
360
386
 
361
- case 'delegator_delegate': {
387
+ case "hitl_request": {
388
+ if (!hitlManager) {
389
+ return { ok: false, error: "hitl not available" };
390
+ }
391
+ if (client) touchClient(client);
392
+ return hitlManager.requestHumanInput(payload);
393
+ }
394
+
395
+ case "hitl_submit": {
396
+ if (!hitlManager) {
397
+ return { ok: false, error: "hitl not available" };
398
+ }
399
+ if (client) touchClient(client);
400
+ return hitlManager.submitHumanInput(payload);
401
+ }
402
+
403
+ case "delegator_delegate": {
362
404
  if (!delegatorService) {
363
- return { ok: false, error: { code: 'DELEGATOR_NOT_AVAILABLE', message: 'Delegator service가 초기화되지 않았습니다' } };
405
+ return {
406
+ ok: false,
407
+ error: {
408
+ code: "DELEGATOR_NOT_AVAILABLE",
409
+ message: "Delegator service가 초기화되지 않았습니다",
410
+ },
411
+ };
364
412
  }
365
413
  if (client) touchClient(client);
366
414
  const result = await delegatorService.delegate(payload);
367
415
  return { ok: result?.ok !== false, data: result };
368
416
  }
369
417
 
370
- case 'delegator_reply': {
418
+ case "delegator_reply": {
371
419
  if (!delegatorService) {
372
- return { ok: false, error: { code: 'DELEGATOR_NOT_AVAILABLE', message: 'Delegator service가 초기화되지 않았습니다' } };
420
+ return {
421
+ ok: false,
422
+ error: {
423
+ code: "DELEGATOR_NOT_AVAILABLE",
424
+ message: "Delegator service가 초기화되지 않았습니다",
425
+ },
426
+ };
373
427
  }
374
428
  if (client) touchClient(client);
375
429
  const result = await delegatorService.reply(payload);
@@ -379,13 +433,19 @@ export function createPipeServer({
379
433
  default:
380
434
  return {
381
435
  ok: false,
382
- error: { code: 'UNKNOWN_PIPE_COMMAND', message: `지원하지 않는 command: ${action}` },
436
+ error: {
437
+ code: "UNKNOWN_PIPE_COMMAND",
438
+ message: `지원하지 않는 command: ${action}`,
439
+ },
383
440
  };
384
441
  }
385
442
  }
386
443
 
387
444
  function buildReplayMessages(agentId, payload = {}) {
388
- const maxMessages = Math.max(1, Math.min(Number(payload.max_messages) || 20, 100));
445
+ const maxMessages = Math.max(
446
+ 1,
447
+ Math.min(Number(payload.max_messages) || 20, 100),
448
+ );
389
449
  const pending = router.getPendingMessages(agentId, {
390
450
  max_messages: maxMessages,
391
451
  include_topics: payload.topics,
@@ -410,7 +470,7 @@ export function createPipeServer({
410
470
 
411
471
  async function processQuery(client, action, payload = {}) {
412
472
  switch (action) {
413
- case 'drain': {
473
+ case "drain": {
414
474
  const agentId = resolveAgentId(client, payload);
415
475
  const messages = router.drainAgent(agentId, {
416
476
  max_messages: payload.max_messages,
@@ -420,69 +480,95 @@ export function createPipeServer({
420
480
  if (client) touchClient(client);
421
481
  return {
422
482
  ok: true,
423
- data: { messages, count: messages.length, server_time_ms: Date.now() },
483
+ data: {
484
+ messages,
485
+ count: messages.length,
486
+ server_time_ms: Date.now(),
487
+ },
424
488
  };
425
489
  }
426
490
 
427
- case 'context': {
491
+ case "context": {
428
492
  const agentId = resolveAgentId(client, payload);
429
493
  const messages = buildReplayMessages(agentId, payload);
430
494
  if (client) touchClient(client);
431
495
  return {
432
496
  ok: true,
433
- data: { messages, count: messages.length, server_time_ms: Date.now() },
497
+ data: {
498
+ messages,
499
+ count: messages.length,
500
+ server_time_ms: Date.now(),
501
+ },
434
502
  };
435
503
  }
436
504
 
437
- case 'status': {
438
- const scope = payload.scope || 'hub';
505
+ case "status": {
506
+ const scope = payload.scope || "hub";
439
507
  if (client) touchClient(client);
440
508
  return router.getStatus(scope, payload);
441
509
  }
442
510
 
443
- case 'assign_status': {
511
+ case "assign_status": {
444
512
  if (client) touchClient(client);
445
513
  return router.getAssignStatus(payload);
446
514
  }
447
515
 
448
- case 'team_info': {
449
- const teamInfo = getTeamBridgeMethod('teamInfo');
450
- const result = teamInfo ? await teamInfo(payload) : teamInfoFallback(payload);
516
+ case "team_info": {
517
+ const teamInfo = getTeamBridgeMethod("teamInfo");
518
+ const result = teamInfo
519
+ ? await teamInfo(payload)
520
+ : teamInfoFallback(payload);
451
521
  if (client) touchClient(client);
452
522
  return result;
453
523
  }
454
524
 
455
- case 'team_task_list': {
456
- const teamTaskList = getTeamBridgeMethod('teamTaskList');
457
- const result = teamTaskList ? await teamTaskList(payload) : teamTaskListFallback(payload);
525
+ case "team_task_list": {
526
+ const teamTaskList = getTeamBridgeMethod("teamTaskList");
527
+ const result = teamTaskList
528
+ ? await teamTaskList(payload)
529
+ : teamTaskListFallback(payload);
458
530
  if (client) touchClient(client);
459
531
  return result;
460
532
  }
461
533
 
462
- case 'pipeline_state': {
534
+ case "pipeline_state": {
463
535
  if (client) touchClient(client);
464
536
  if (!store?.db) {
465
- return { ok: false, error: 'hub_db_not_found' };
537
+ return { ok: false, error: "hub_db_not_found" };
466
538
  }
467
539
  ensurePipelineTable(store.db);
468
540
  const state = readPipelineState(store.db, payload.team_name);
469
541
  return state
470
542
  ? { ok: true, data: state }
471
- : { ok: false, error: 'pipeline_not_found' };
543
+ : { ok: false, error: "pipeline_not_found" };
472
544
  }
473
545
 
474
- case 'pipeline_list': {
546
+ case "pipeline_list": {
475
547
  if (client) touchClient(client);
476
548
  if (!store?.db) {
477
- return { ok: false, error: 'hub_db_not_found' };
549
+ return { ok: false, error: "hub_db_not_found" };
478
550
  }
479
551
  ensurePipelineTable(store.db);
480
552
  return { ok: true, data: listPipelineStates(store.db) };
481
553
  }
482
554
 
483
- case 'delegator_status': {
555
+ case "hitl_pending": {
556
+ if (client) touchClient(client);
557
+ if (!hitlManager) {
558
+ return { ok: false, error: "hitl not available" };
559
+ }
560
+ return { ok: true, data: hitlManager.getPendingRequests() };
561
+ }
562
+
563
+ case "delegator_status": {
484
564
  if (!delegatorService) {
485
- return { ok: false, error: { code: 'DELEGATOR_NOT_AVAILABLE', message: 'Delegator service가 초기화되지 않았습니다' } };
565
+ return {
566
+ ok: false,
567
+ error: {
568
+ code: "DELEGATOR_NOT_AVAILABLE",
569
+ message: "Delegator service가 초기화되지 않았습니다",
570
+ },
571
+ };
486
572
  }
487
573
  if (client) touchClient(client);
488
574
  const result = await delegatorService.status(payload);
@@ -492,7 +578,10 @@ export function createPipeServer({
492
578
  default:
493
579
  return {
494
580
  ok: false,
495
- error: { code: 'UNKNOWN_PIPE_QUERY', message: `지원하지 않는 query: ${action}` },
581
+ error: {
582
+ code: "UNKNOWN_PIPE_QUERY",
583
+ message: `지원하지 않는 query: ${action}`,
584
+ },
496
585
  };
497
586
  }
498
587
  }
@@ -507,41 +596,56 @@ export function createPipeServer({
507
596
  }
508
597
 
509
598
  async function handleFrame(client, frame) {
510
- if (!frame || typeof frame !== 'object') {
599
+ if (!frame || typeof frame !== "object") {
511
600
  return sendResponse(client, null, {
512
601
  ok: false,
513
- error: { code: 'INVALID_FRAME', message: 'JSON object frame required' },
602
+ error: { code: "INVALID_FRAME", message: "JSON object frame required" },
514
603
  });
515
604
  }
516
605
 
517
606
  if (!frame.type) {
518
607
  return sendResponse(client, frame.request_id || null, {
519
608
  ok: false,
520
- error: { code: 'INVALID_FRAME', message: 'type required' },
609
+ error: { code: "INVALID_FRAME", message: "type required" },
521
610
  });
522
611
  }
523
612
 
524
613
  touchClient(client);
525
614
 
526
615
  try {
527
- if (frame.type === 'command') {
616
+ if (frame.type === "command") {
528
617
  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') {
618
+ const result = await processCommand(
619
+ client,
620
+ action,
621
+ frame.payload || {},
622
+ );
623
+ return sendResponse(
624
+ client,
625
+ frame.payload?.request_id || frame.request_id || null,
626
+ result,
627
+ );
628
+ }
629
+ if (frame.type === "query") {
533
630
  const action = frame.payload?.action || frame.payload?.query;
534
631
  const result = await processQuery(client, action, frame.payload || {});
535
- return sendResponse(client, frame.payload?.request_id || frame.request_id || null, result);
632
+ return sendResponse(
633
+ client,
634
+ frame.payload?.request_id || frame.request_id || null,
635
+ result,
636
+ );
536
637
  }
537
638
  return sendResponse(client, frame.request_id || null, {
538
639
  ok: false,
539
- error: { code: 'INVALID_FRAME_TYPE', message: `지원하지 않는 type: ${frame.type}` },
640
+ error: {
641
+ code: "INVALID_FRAME_TYPE",
642
+ message: `지원하지 않는 type: ${frame.type}`,
643
+ },
540
644
  });
541
645
  } catch (error) {
542
646
  return sendResponse(client, frame.request_id || null, {
543
647
  ok: false,
544
- error: { code: 'PIPE_REQUEST_FAILED', message: error.message },
648
+ error: { code: "PIPE_REQUEST_FAILED", message: error.message },
545
649
  });
546
650
  }
547
651
  }
@@ -550,7 +654,7 @@ export function createPipeServer({
550
654
  const client = {
551
655
  id: randomUUID(),
552
656
  socket,
553
- buffer: '',
657
+ buffer: "",
554
658
  agentId: null,
555
659
  subscriptions: new Set(),
556
660
  lastHeartbeatMs: Date.now(),
@@ -558,38 +662,48 @@ export function createPipeServer({
558
662
  };
559
663
  clients.set(client.id, client);
560
664
 
561
- socket.setEncoding('utf8');
562
- socket.on('data', async (chunk) => {
665
+ socket.setEncoding("utf8");
666
+ socket.on("data", async (chunk) => {
563
667
  client.buffer += chunk;
564
- let newlineIndex = client.buffer.indexOf('\n');
668
+ let newlineIndex = client.buffer.indexOf("\n");
565
669
  while (newlineIndex >= 0) {
566
670
  const line = client.buffer.slice(0, newlineIndex).trim();
567
671
  client.buffer = client.buffer.slice(newlineIndex + 1);
568
672
  if (line) {
569
- const frame = safeJsonParse(line);
570
- await handleFrame(client, frame);
673
+ try {
674
+ const frame = safeJsonParse(line);
675
+ await handleFrame(client, frame);
676
+ } catch (err) {
677
+ pipeLog.error(
678
+ { clientId: client.id, err: String(err?.message || err) },
679
+ "pipe.frame_handler_error",
680
+ );
681
+ }
571
682
  }
572
- newlineIndex = client.buffer.indexOf('\n');
683
+ newlineIndex = client.buffer.indexOf("\n");
573
684
  }
574
685
  });
575
686
 
576
- socket.on('close', () => closeClient(client));
577
- socket.on('error', () => closeClient(client));
687
+ socket.on("close", () => closeClient(client));
688
+ socket.on("error", () => closeClient(client));
578
689
  }
579
690
 
580
691
  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)));
692
+ heartbeatTimer = setInterval(
693
+ () => {
694
+ const now = Date.now();
695
+ for (const client of clients.values()) {
696
+ if (now - client.lastHeartbeatMs <= heartbeatTtlMs) continue;
697
+ sendFrame(client, {
698
+ type: "event",
699
+ event: "disconnect",
700
+ payload: { reason: "heartbeat_timeout" },
701
+ });
702
+ closeClient(client);
703
+ }
704
+ },
705
+ Math.max(1000, Math.floor(heartbeatTtlMs / 2)),
706
+ );
593
707
  heartbeatTimer.unref();
594
708
  }
595
709
 
@@ -600,16 +714,18 @@ export function createPipeServer({
600
714
  if (server) return { path: pipePath };
601
715
 
602
716
  if (!IS_WINDOWS && existsSync(pipePath)) {
603
- try { unlinkSync(pipePath); } catch {}
717
+ try {
718
+ unlinkSync(pipePath);
719
+ } catch {}
604
720
  }
605
721
 
606
722
  server = net.createServer(attachSocket);
607
- router.deliveryEmitter.on('message', onMessage);
723
+ router.deliveryEmitter.on("message", onMessage);
608
724
 
609
725
  await new Promise((resolve, reject) => {
610
- server.once('error', reject);
726
+ server.once("error", reject);
611
727
  server.listen(pipePath, () => {
612
- server.off('error', reject);
728
+ server.off("error", reject);
613
729
  resolve();
614
730
  });
615
731
  });
@@ -624,7 +740,7 @@ export function createPipeServer({
624
740
  heartbeatTimer = null;
625
741
  }
626
742
 
627
- router.deliveryEmitter.off('message', onMessage);
743
+ router.deliveryEmitter.off("message", onMessage);
628
744
 
629
745
  for (const client of clients.values()) {
630
746
  closeClient(client);
@@ -637,14 +753,16 @@ export function createPipeServer({
637
753
  }
638
754
 
639
755
  if (!IS_WINDOWS && existsSync(pipePath)) {
640
- try { unlinkSync(pipePath); } catch {}
756
+ try {
757
+ unlinkSync(pipePath);
758
+ } catch {}
641
759
  }
642
760
  },
643
761
 
644
762
  getStatus() {
645
763
  return {
646
764
  path: pipePath,
647
- protocol: 'ndjson',
765
+ protocol: "ndjson",
648
766
  clients: clients.size,
649
767
  pending_messages: Array.from(clients.values()).reduce((sum, client) => {
650
768
  if (!client.agentId) return sum;