triflux 10.2.1 → 10.3.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.
package/hub/bridge.mjs CHANGED
@@ -1,23 +1,30 @@
1
1
  #!/usr/bin/env node
2
+
2
3
  // hub/bridge.mjs — tfx-route.sh ↔ tfx-hub 브릿지 CLI
3
4
  //
4
5
  // Named Pipe/Unix Socket 제어 채널을 우선 사용하고,
5
6
  // 연결이 없을 때만 HTTP /bridge/* 엔드포인트로 내려간다.
6
7
 
7
- import net from 'node:net';
8
- import { readFileSync, writeFileSync, existsSync, mkdirSync, openSync } from 'node:fs';
9
- import { join } from 'node:path';
10
- import { homedir } from 'node:os';
11
- import { spawn } from 'node:child_process';
12
- import { parseArgs as nodeParseArgs } from 'node:util';
13
- import { randomUUID } from 'node:crypto';
14
- import { fileURLToPath } from 'node:url';
15
-
16
- import { getPipelineStateDbPath } from './pipeline/state.mjs';
17
-
18
- const HUB_PID_FILE = join(homedir(), '.claude', 'cache', 'tfx-hub', 'hub.pid');
19
- const HUB_TOKEN_FILE = join(homedir(), '.claude', '.tfx-hub-token');
20
- const PROJECT_ROOT = fileURLToPath(new URL('..', import.meta.url));
8
+ import { spawn } from "node:child_process";
9
+ import { randomUUID } from "node:crypto";
10
+ import {
11
+ existsSync,
12
+ mkdirSync,
13
+ openSync,
14
+ readFileSync,
15
+ writeFileSync,
16
+ } from "node:fs";
17
+ import net from "node:net";
18
+ import { homedir } from "node:os";
19
+ import { join } from "node:path";
20
+ import { fileURLToPath } from "node:url";
21
+ import { parseArgs as nodeParseArgs } from "node:util";
22
+
23
+ import { getPipelineStateDbPath } from "./pipeline/state.mjs";
24
+
25
+ const HUB_PID_FILE = join(homedir(), ".claude", "cache", "tfx-hub", "hub.pid");
26
+ const HUB_TOKEN_FILE = join(homedir(), ".claude", ".tfx-hub-token");
27
+ const PROJECT_ROOT = fileURLToPath(new URL("..", import.meta.url));
21
28
 
22
29
  function normalizeToken(raw) {
23
30
  if (raw == null) return null;
@@ -30,25 +37,26 @@ function readHubToken() {
30
37
  const envToken = normalizeToken(process.env.TFX_HUB_TOKEN);
31
38
  if (envToken) return envToken;
32
39
  try {
33
- return normalizeToken(readFileSync(HUB_TOKEN_FILE, 'utf8'));
40
+ return normalizeToken(readFileSync(HUB_TOKEN_FILE, "utf8"));
34
41
  } catch {
35
42
  return null;
36
43
  }
37
44
  }
38
45
 
39
46
  export function getHubUrl() {
40
- if (process.env.TFX_HUB_URL) return process.env.TFX_HUB_URL.replace(/\/mcp$/, '');
47
+ if (process.env.TFX_HUB_URL)
48
+ return process.env.TFX_HUB_URL.replace(/\/mcp$/, "");
41
49
 
42
50
  if (existsSync(HUB_PID_FILE)) {
43
51
  try {
44
- const info = JSON.parse(readFileSync(HUB_PID_FILE, 'utf8'));
45
- return `http://${info.host || '127.0.0.1'}:${info.port || 27888}`;
52
+ const info = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
53
+ return `http://${info.host || "127.0.0.1"}:${info.port || 27888}`;
46
54
  } catch {
47
55
  // 무시
48
56
  }
49
57
  }
50
58
 
51
- const port = process.env.TFX_HUB_PORT || '27888';
59
+ const port = process.env.TFX_HUB_PORT || "27888";
52
60
  return `http://127.0.0.1:${port}`;
53
61
  }
54
62
 
@@ -57,7 +65,7 @@ export function getHubPipePath() {
57
65
 
58
66
  if (!existsSync(HUB_PID_FILE)) return null;
59
67
  try {
60
- const info = JSON.parse(readFileSync(HUB_PID_FILE, 'utf8'));
68
+ const info = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
61
69
  return info.pipe_path || info.pipePath || null;
62
70
  } catch {
63
71
  return null;
@@ -65,30 +73,144 @@ export function getHubPipePath() {
65
73
  }
66
74
 
67
75
  const HUB_OPERATIONS = Object.freeze({
68
- register: { transport: 'command', action: 'register', httpPath: '/bridge/register' },
69
- result: { transport: 'command', action: 'result', httpPath: '/bridge/result' },
70
- control: { transport: 'command', action: 'control', httpPath: '/bridge/control' },
71
- context: { transport: 'query', action: 'drain', httpPath: '/bridge/context' },
72
- deregister: { transport: 'command', action: 'deregister', httpPath: '/bridge/deregister' },
73
- assignAsync: { transport: 'command', action: 'assign', httpPath: '/bridge/assign/async' },
74
- assignResult: { transport: 'command', action: 'assign_result', httpPath: '/bridge/assign/result' },
75
- assignStatus: { transport: 'query', action: 'assign_status', httpPath: '/bridge/assign/status' },
76
- assignRetry: { transport: 'command', action: 'assign_retry', httpPath: '/bridge/assign/retry' },
77
- teamInfo: { transport: 'query', action: 'team_info', httpPath: '/bridge/team/info' },
78
- teamTaskList: { transport: 'query', action: 'team_task_list', httpPath: '/bridge/team/task-list' },
79
- teamTaskUpdate: { transport: 'command', action: 'team_task_update', httpPath: '/bridge/team/task-update' },
80
- teamSendMessage: { transport: 'command', action: 'team_send_message', httpPath: '/bridge/team/send-message' },
81
- pipelineState: { transport: 'query', action: 'pipeline_state', httpPath: '/bridge/pipeline/state' },
82
- pipelineAdvance: { transport: 'command', action: 'pipeline_advance', httpPath: '/bridge/pipeline/advance' },
83
- pipelineInit: { transport: 'command', action: 'pipeline_init', httpPath: '/bridge/pipeline/init' },
84
- pipelineList: { transport: 'query', action: 'pipeline_list', httpPath: '/bridge/pipeline/list' },
85
- hubStatus: { transport: 'query', action: 'status', httpPath: '/status', httpMethod: 'GET' },
86
- delegatorDelegate: { transport: 'command', action: 'delegator_delegate', httpPath: '/bridge/delegator/delegate' },
87
- delegatorReply: { transport: 'command', action: 'delegator_reply', httpPath: '/bridge/delegator/reply' },
88
- delegatorStatus: { transport: 'query', action: 'delegator_status', httpPath: '/bridge/delegator/status' },
76
+ register: {
77
+ transport: "command",
78
+ action: "register",
79
+ httpPath: "/bridge/register",
80
+ },
81
+ result: {
82
+ transport: "command",
83
+ action: "result",
84
+ httpPath: "/bridge/result",
85
+ },
86
+ control: {
87
+ transport: "command",
88
+ action: "control",
89
+ httpPath: "/bridge/control",
90
+ },
91
+ handoff: {
92
+ transport: "command",
93
+ action: "handoff",
94
+ httpPath: "/bridge/handoff",
95
+ },
96
+ publish: {
97
+ transport: "command",
98
+ action: "publish",
99
+ httpPath: "/bridge/publish",
100
+ },
101
+ sendInput: {
102
+ transport: "command",
103
+ action: "send_input",
104
+ httpPath: "/bridge/send-input",
105
+ },
106
+ context: { transport: "query", action: "drain", httpPath: "/bridge/context" },
107
+ deregister: {
108
+ transport: "command",
109
+ action: "deregister",
110
+ httpPath: "/bridge/deregister",
111
+ },
112
+ assignAsync: {
113
+ transport: "command",
114
+ action: "assign",
115
+ httpPath: "/bridge/assign/async",
116
+ },
117
+ assignResult: {
118
+ transport: "command",
119
+ action: "assign_result",
120
+ httpPath: "/bridge/assign/result",
121
+ },
122
+ assignStatus: {
123
+ transport: "query",
124
+ action: "assign_status",
125
+ httpPath: "/bridge/assign/status",
126
+ },
127
+ assignRetry: {
128
+ transport: "command",
129
+ action: "assign_retry",
130
+ httpPath: "/bridge/assign/retry",
131
+ },
132
+ teamInfo: {
133
+ transport: "query",
134
+ action: "team_info",
135
+ httpPath: "/bridge/team/info",
136
+ },
137
+ teamTaskList: {
138
+ transport: "query",
139
+ action: "team_task_list",
140
+ httpPath: "/bridge/team/task-list",
141
+ },
142
+ teamTaskUpdate: {
143
+ transport: "command",
144
+ action: "team_task_update",
145
+ httpPath: "/bridge/team/task-update",
146
+ },
147
+ teamSendMessage: {
148
+ transport: "command",
149
+ action: "team_send_message",
150
+ httpPath: "/bridge/team/send-message",
151
+ },
152
+ pipelineState: {
153
+ transport: "query",
154
+ action: "pipeline_state",
155
+ httpPath: "/bridge/pipeline/state",
156
+ },
157
+ pipelineAdvance: {
158
+ transport: "command",
159
+ action: "pipeline_advance",
160
+ httpPath: "/bridge/pipeline/advance",
161
+ },
162
+ pipelineInit: {
163
+ transport: "command",
164
+ action: "pipeline_init",
165
+ httpPath: "/bridge/pipeline/init",
166
+ },
167
+ pipelineList: {
168
+ transport: "query",
169
+ action: "pipeline_list",
170
+ httpPath: "/bridge/pipeline/list",
171
+ },
172
+ hubStatus: {
173
+ transport: "query",
174
+ action: "status",
175
+ httpPath: "/status",
176
+ httpMethod: "GET",
177
+ },
178
+ delegatorDelegate: {
179
+ transport: "command",
180
+ action: "delegator_delegate",
181
+ httpPath: "/bridge/delegator/delegate",
182
+ },
183
+ delegatorReply: {
184
+ transport: "command",
185
+ action: "delegator_reply",
186
+ httpPath: "/bridge/delegator/reply",
187
+ },
188
+ delegatorStatus: {
189
+ transport: "query",
190
+ action: "delegator_status",
191
+ httpPath: "/bridge/delegator/status",
192
+ },
193
+ "hitl-request": {
194
+ transport: "command",
195
+ action: "hitl_request",
196
+ httpPath: "/bridge/hitl/request",
197
+ },
198
+ "hitl-submit": {
199
+ transport: "command",
200
+ action: "hitl_submit",
201
+ httpPath: "/bridge/hitl/submit",
202
+ },
203
+ "hitl-pending": {
204
+ transport: "query",
205
+ action: "hitl_pending",
206
+ httpPath: "/bridge/hitl/pending",
207
+ },
89
208
  });
90
209
 
91
- export async function requestJson(path, { method = 'POST', body, timeoutMs = 5000 } = {}) {
210
+ export async function requestJson(
211
+ path,
212
+ { method = "POST", body, timeoutMs = 5000 } = {},
213
+ ) {
92
214
  const controller = new AbortController();
93
215
  const timer = setTimeout(() => controller.abort(), timeoutMs);
94
216
 
@@ -99,7 +221,7 @@ export async function requestJson(path, { method = 'POST', body, timeoutMs = 500
99
221
  headers.Authorization = `Bearer ${token}`;
100
222
  }
101
223
  if (body !== undefined) {
102
- headers['Content-Type'] = 'application/json';
224
+ headers["Content-Type"] = "application/json";
103
225
  }
104
226
 
105
227
  const res = await fetch(`${getHubUrl()}${path}`, {
@@ -117,7 +239,7 @@ export async function requestJson(path, { method = 'POST', body, timeoutMs = 500
117
239
  }
118
240
 
119
241
  export async function post(path, body, timeoutMs = 5000) {
120
- return await requestJson(path, { method: 'POST', body, timeoutMs });
242
+ return await requestJson(path, { method: "POST", body, timeoutMs });
121
243
  }
122
244
 
123
245
  export async function connectPipe(timeoutMs = 1200) {
@@ -127,19 +249,23 @@ export async function connectPipe(timeoutMs = 1200) {
127
249
  return await new Promise((resolve) => {
128
250
  const socket = net.createConnection(pipePath);
129
251
  const timer = setTimeout(() => {
130
- try { socket.destroy(); } catch {}
252
+ try {
253
+ socket.destroy();
254
+ } catch {}
131
255
  resolve(null);
132
256
  }, timeoutMs);
133
257
 
134
- socket.once('connect', () => {
258
+ socket.once("connect", () => {
135
259
  clearTimeout(timer);
136
- socket.setEncoding('utf8');
260
+ socket.setEncoding("utf8");
137
261
  resolve(socket);
138
262
  });
139
263
 
140
- socket.once('error', () => {
264
+ socket.once("error", () => {
141
265
  clearTimeout(timer);
142
- try { socket.destroy(); } catch {}
266
+ try {
267
+ socket.destroy();
268
+ } catch {}
143
269
  resolve(null);
144
270
  });
145
271
  });
@@ -151,7 +277,7 @@ async function pipeRequest(type, action, payload, timeoutMs = 3000) {
151
277
 
152
278
  return await new Promise((resolve) => {
153
279
  const requestId = randomUUID();
154
- let buffer = '';
280
+ let buffer = "";
155
281
  let settled = false;
156
282
  const timer = setTimeout(() => {
157
283
  finish(null);
@@ -161,17 +287,19 @@ async function pipeRequest(type, action, payload, timeoutMs = 3000) {
161
287
  if (settled) return;
162
288
  settled = true;
163
289
  clearTimeout(timer);
164
- try { socket.end(); } catch {}
290
+ try {
291
+ socket.end();
292
+ } catch {}
165
293
  resolve(result);
166
294
  };
167
295
 
168
- socket.on('data', (chunk) => {
296
+ socket.on("data", (chunk) => {
169
297
  buffer += chunk;
170
- let newlineIndex = buffer.indexOf('\n');
298
+ let newlineIndex = buffer.indexOf("\n");
171
299
  while (newlineIndex >= 0) {
172
300
  const line = buffer.slice(0, newlineIndex).trim();
173
301
  buffer = buffer.slice(newlineIndex + 1);
174
- newlineIndex = buffer.indexOf('\n');
302
+ newlineIndex = buffer.indexOf("\n");
175
303
  if (!line) continue;
176
304
 
177
305
  let frame;
@@ -181,7 +309,8 @@ async function pipeRequest(type, action, payload, timeoutMs = 3000) {
181
309
  continue;
182
310
  }
183
311
 
184
- if (frame?.type !== 'response' || frame.request_id !== requestId) continue;
312
+ if (frame?.type !== "response" || frame.request_id !== requestId)
313
+ continue;
185
314
  finish({
186
315
  ok: frame.ok,
187
316
  error: frame.error,
@@ -191,89 +320,99 @@ async function pipeRequest(type, action, payload, timeoutMs = 3000) {
191
320
  }
192
321
  });
193
322
 
194
- socket.on('error', () => finish(null));
195
- socket.write(JSON.stringify({
196
- type,
197
- request_id: requestId,
198
- payload: { action, ...payload },
199
- }) + '\n');
323
+ socket.on("error", () => finish(null));
324
+ socket.write(
325
+ JSON.stringify({
326
+ type,
327
+ request_id: requestId,
328
+ payload: { action, ...payload },
329
+ }) + "\n",
330
+ );
200
331
  });
201
332
  }
202
333
 
203
334
  async function pipeCommand(action, payload, timeoutMs = 3000) {
204
- return await pipeRequest('command', action, payload, timeoutMs);
335
+ return await pipeRequest("command", action, payload, timeoutMs);
205
336
  }
206
337
 
207
338
  async function pipeQuery(action, payload, timeoutMs = 3000) {
208
- return await pipeRequest('query', action, payload, timeoutMs);
339
+ return await pipeRequest("query", action, payload, timeoutMs);
209
340
  }
210
341
 
211
342
  export function parseArgs(argv) {
212
- const { values } = nodeParseArgs({
343
+ const { values, positionals } = nodeParseArgs({
213
344
  args: argv,
345
+ allowPositionals: true,
214
346
  options: {
215
- agent: { type: 'string' },
216
- cli: { type: 'string' },
217
- timeout: { type: 'string' },
218
- topics: { type: 'string' },
219
- capabilities: { type: 'string' },
220
- file: { type: 'string' },
221
- payload: { type: 'string' },
222
- topic: { type: 'string' },
223
- trace: { type: 'string' },
224
- correlation: { type: 'string' },
225
- 'exit-code': { type: 'string' },
226
- max: { type: 'string' },
227
- out: { type: 'string' },
228
- team: { type: 'string' },
229
- 'task-id': { type: 'string' },
230
- 'job-id': { type: 'string' },
231
- owner: { type: 'string' },
232
- status: { type: 'string' },
233
- statuses: { type: 'string' },
234
- claim: { type: 'boolean' },
235
- actor: { type: 'string' },
236
- command: { type: 'string' },
237
- reason: { type: 'string' },
238
- from: { type: 'string' },
239
- to: { type: 'string' },
240
- text: { type: 'string' },
241
- task: { type: 'string' },
242
- 'supervisor-agent': { type: 'string' },
243
- 'worker-agent': { type: 'string' },
244
- priority: { type: 'string' },
245
- 'ttl-ms': { type: 'string' },
246
- 'timeout-ms': { type: 'string' },
247
- 'max-retries': { type: 'string' },
248
- attempt: { type: 'string' },
249
- result: { type: 'string' },
250
- error: { type: 'string' },
251
- metadata: { type: 'string' },
252
- 'requested-by': { type: 'string' },
253
- summary: { type: 'string' },
254
- color: { type: 'string' },
255
- limit: { type: 'string' },
256
- 'include-internal': { type: 'boolean' },
257
- subject: { type: 'string' },
258
- description: { type: 'string' },
259
- 'fix-max': { type: 'string' },
260
- 'ralph-max': { type: 'string' },
261
- 'active-form': { type: 'string' },
262
- 'add-blocks': { type: 'string' },
263
- 'add-blocked-by': { type: 'string' },
264
- 'metadata-patch': { type: 'string' },
265
- 'if-match-mtime-ms': { type: 'string' },
266
- provider: { type: 'string' },
267
- mode: { type: 'string' },
268
- prompt: { type: 'string' },
269
- reply: { type: 'string' },
270
- done: { type: 'boolean' },
271
- 'mcp-profile': { type: 'string' },
272
- 'session-key': { type: 'string' },
347
+ agent: { type: "string" },
348
+ cli: { type: "string" },
349
+ timeout: { type: "string" },
350
+ topics: { type: "string" },
351
+ capabilities: { type: "string" },
352
+ file: { type: "string" },
353
+ payload: { type: "string" },
354
+ topic: { type: "string" },
355
+ trace: { type: "string" },
356
+ correlation: { type: "string" },
357
+ "exit-code": { type: "string" },
358
+ max: { type: "string" },
359
+ out: { type: "string" },
360
+ team: { type: "string" },
361
+ "task-id": { type: "string" },
362
+ "job-id": { type: "string" },
363
+ owner: { type: "string" },
364
+ status: { type: "string" },
365
+ statuses: { type: "string" },
366
+ claim: { type: "boolean" },
367
+ actor: { type: "string" },
368
+ command: { type: "string" },
369
+ "session-id": { type: "string" },
370
+ reason: { type: "string" },
371
+ type: { type: "string" },
372
+ from: { type: "string" },
373
+ to: { type: "string" },
374
+ text: { type: "string" },
375
+ task: { type: "string" },
376
+ "supervisor-agent": { type: "string" },
377
+ "worker-agent": { type: "string" },
378
+ priority: { type: "string" },
379
+ "ttl-ms": { type: "string" },
380
+ "timeout-ms": { type: "string" },
381
+ "max-retries": { type: "string" },
382
+ attempt: { type: "string" },
383
+ result: { type: "string" },
384
+ error: { type: "string" },
385
+ metadata: { type: "string" },
386
+ "requested-by": { type: "string" },
387
+ summary: { type: "string" },
388
+ color: { type: "string" },
389
+ limit: { type: "string" },
390
+ "include-internal": { type: "boolean" },
391
+ subject: { type: "string" },
392
+ description: { type: "string" },
393
+ "fix-max": { type: "string" },
394
+ "ralph-max": { type: "string" },
395
+ "active-form": { type: "string" },
396
+ "add-blocks": { type: "string" },
397
+ "add-blocked-by": { type: "string" },
398
+ "metadata-patch": { type: "string" },
399
+ "if-match-mtime-ms": { type: "string" },
400
+ provider: { type: "string" },
401
+ mode: { type: "string" },
402
+ prompt: { type: "string" },
403
+ reply: { type: "string" },
404
+ done: { type: "boolean" },
405
+ "mcp-profile": { type: "string" },
406
+ "session-key": { type: "string" },
273
407
  },
408
+ allowPositionals: true,
274
409
  strict: false,
275
410
  });
276
- return values;
411
+ const parsed = { ...values, _: positionals };
412
+ positionals.forEach((value, index) => {
413
+ parsed[index + 1] = value;
414
+ });
415
+ return parsed;
277
416
  }
278
417
 
279
418
  export function parseJsonSafe(raw, fallback = null) {
@@ -285,29 +424,63 @@ export function parseJsonSafe(raw, fallback = null) {
285
424
  }
286
425
  }
287
426
 
427
+ function normalizeBridgePayload(payload) {
428
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
429
+ return {};
430
+ }
431
+ return payload;
432
+ }
433
+
434
+ function buildHandoffBody(from, to, payload) {
435
+ const normalizedPayload = normalizeBridgePayload(payload);
436
+ return {
437
+ ...normalizedPayload,
438
+ from,
439
+ to,
440
+ payload: normalizedPayload,
441
+ };
442
+ }
443
+
444
+ function buildPublishBody(from, to, type, payload) {
445
+ const normalizedPayload = normalizeBridgePayload(payload);
446
+ return {
447
+ ...normalizedPayload,
448
+ from,
449
+ to,
450
+ type,
451
+ message_type: type,
452
+ payload: normalizedPayload,
453
+ };
454
+ }
455
+
288
456
  // Hub 자동 재시작 (Pipe+HTTP 모두 실패 시 1회 시도, 최대 4초 대기)
289
457
  async function tryRestartHub() {
290
- const serverPath = join(PROJECT_ROOT, 'hub', 'server.mjs');
458
+ const serverPath = join(PROJECT_ROOT, "hub", "server.mjs");
291
459
  if (!existsSync(serverPath)) return false;
292
460
 
293
461
  if (existsSync(HUB_PID_FILE)) {
294
462
  try {
295
- const info = JSON.parse(readFileSync(HUB_PID_FILE, 'utf8'));
463
+ const info = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
296
464
  if (info.pid) {
297
- try { process.kill(info.pid, 0); return false; } // still alive
298
- catch (e) { if (e.code === 'EPERM') return false; } // alive, no permission
465
+ try {
466
+ process.kill(info.pid, 0);
467
+ return false;
468
+ } catch (e) {
469
+ // still alive
470
+ if (e.code === "EPERM") return false;
471
+ } // alive, no permission
299
472
  }
300
473
  } catch {} // corrupt PID file, proceed with restart
301
474
  }
302
475
 
303
476
  try {
304
- const logDir = join(process.cwd(), '.tfx', 'logs');
477
+ const logDir = join(process.cwd(), ".tfx", "logs");
305
478
  if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
306
- const logFile = join(logDir, 'hub-restart.log');
307
- const logFd = openSync(logFile, 'a');
479
+ const logFile = join(logDir, "hub-restart.log");
480
+ const logFd = openSync(logFile, "a");
308
481
  const child = spawn(process.execPath, [serverPath], {
309
482
  detached: true,
310
- stdio: ['ignore', 'ignore', logFd],
483
+ stdio: ["ignore", "ignore", logFd],
311
484
  windowsHide: true,
312
485
  });
313
486
  child.unref();
@@ -323,7 +496,7 @@ async function tryRestartHub() {
323
496
  });
324
497
  if (res.ok) {
325
498
  const data = await res.json();
326
- if (data?.hub?.state === 'healthy') return true;
499
+ if (data?.hub?.state === "healthy") return true;
327
500
  }
328
501
  } catch {}
329
502
  }
@@ -331,52 +504,54 @@ async function tryRestartHub() {
331
504
  }
332
505
 
333
506
  async function requestHub(operation, body, timeoutMs = 3000, fallback = null) {
334
- const viaPipe = operation.transport === 'command'
335
- ? await pipeCommand(operation.action, body, timeoutMs)
336
- : await pipeQuery(operation.action, body, timeoutMs);
507
+ const viaPipe =
508
+ operation.transport === "command"
509
+ ? await pipeCommand(operation.action, body, timeoutMs)
510
+ : await pipeQuery(operation.action, body, timeoutMs);
337
511
  if (viaPipe) {
338
- return { transport: 'pipe', result: viaPipe };
512
+ return { transport: "pipe", result: viaPipe };
339
513
  }
340
514
 
341
515
  const viaHttp = operation.httpPath
342
516
  ? await requestJson(operation.httpPath, {
343
- method: operation.httpMethod || 'POST',
344
- body: operation.httpMethod === 'GET' ? undefined : body,
345
- timeoutMs: Math.max(timeoutMs, 5000),
346
- })
517
+ method: operation.httpMethod || "POST",
518
+ body: operation.httpMethod === "GET" ? undefined : body,
519
+ timeoutMs: Math.max(timeoutMs, 5000),
520
+ })
347
521
  : null;
348
522
  if (viaHttp) {
349
- return { transport: 'http', result: viaHttp };
523
+ return { transport: "http", result: viaHttp };
350
524
  }
351
525
 
352
526
  // Hub 재시작 시도 → Pipe/HTTP 재시도
353
527
  if (await tryRestartHub()) {
354
- const retryPipe = operation.transport === 'command'
355
- ? await pipeCommand(operation.action, body, timeoutMs)
356
- : await pipeQuery(operation.action, body, timeoutMs);
528
+ const retryPipe =
529
+ operation.transport === "command"
530
+ ? await pipeCommand(operation.action, body, timeoutMs)
531
+ : await pipeQuery(operation.action, body, timeoutMs);
357
532
  if (retryPipe) {
358
- return { transport: 'pipe', result: retryPipe };
533
+ return { transport: "pipe", result: retryPipe };
359
534
  }
360
535
  const retryHttp = operation.httpPath
361
536
  ? await requestJson(operation.httpPath, {
362
- method: operation.httpMethod || 'POST',
363
- body: operation.httpMethod === 'GET' ? undefined : body,
364
- timeoutMs: Math.max(timeoutMs, 5000),
365
- })
537
+ method: operation.httpMethod || "POST",
538
+ body: operation.httpMethod === "GET" ? undefined : body,
539
+ timeoutMs: Math.max(timeoutMs, 5000),
540
+ })
366
541
  : null;
367
542
  if (retryHttp) {
368
- return { transport: 'http', result: retryHttp };
543
+ return { transport: "http", result: retryHttp };
369
544
  }
370
545
  }
371
546
 
372
547
  if (!fallback) return null;
373
548
  const viaFallback = await fallback();
374
549
  if (!viaFallback) return null;
375
- return { transport: 'fallback', result: viaFallback };
550
+ return { transport: "fallback", result: viaFallback };
376
551
  }
377
552
 
378
553
  function unavailableResult() {
379
- return { ok: false, reason: 'hub_unavailable' };
554
+ return { ok: false, reason: "hub_unavailable" };
380
555
  }
381
556
 
382
557
  function emitJson(payload) {
@@ -388,14 +563,14 @@ function emitJson(payload) {
388
563
 
389
564
  async function cmdRegister(args) {
390
565
  const agentId = args.agent;
391
- const timeoutSec = parseInt(args.timeout || '600', 10);
566
+ const timeoutSec = parseInt(args.timeout || "600", 10);
392
567
  const outcome = await requestHub(HUB_OPERATIONS.register, {
393
568
  agent_id: agentId,
394
- cli: args.cli || 'other',
569
+ cli: args.cli || "other",
395
570
  timeout_sec: timeoutSec,
396
571
  heartbeat_ttl_ms: (timeoutSec + 120) * 1000,
397
- topics: args.topics ? args.topics.split(',') : [],
398
- capabilities: args.capabilities ? args.capabilities.split(',') : ['code'],
572
+ topics: args.topics ? args.topics.split(",") : [],
573
+ capabilities: args.capabilities ? args.capabilities.split(",") : ["code"],
399
574
  metadata: {
400
575
  pid: process.ppid,
401
576
  registered_at: Date.now(),
@@ -416,14 +591,14 @@ async function cmdRegister(args) {
416
591
  }
417
592
 
418
593
  async function cmdResult(args) {
419
- let output = '';
594
+ let output = "";
420
595
  if (args.file && existsSync(args.file)) {
421
- output = readFileSync(args.file, 'utf8').slice(0, 49152);
596
+ output = readFileSync(args.file, "utf8").slice(0, 49152);
422
597
  }
423
598
 
424
599
  const defaultPayload = {
425
600
  agent_id: args.agent,
426
- exit_code: parseInt(args['exit-code'] || '0', 10),
601
+ exit_code: parseInt(args["exit-code"] || "0", 10),
427
602
  output_length: output.length,
428
603
  output_preview: output.slice(0, 4096),
429
604
  output_file: args.file || null,
@@ -432,8 +607,10 @@ async function cmdResult(args) {
432
607
 
433
608
  const outcome = await requestHub(HUB_OPERATIONS.result, {
434
609
  agent_id: args.agent,
435
- topic: args.topic || 'task.result',
436
- payload: args.payload ? parseJsonSafe(args.payload, defaultPayload) : defaultPayload,
610
+ topic: args.topic || "task.result",
611
+ payload: args.payload
612
+ ? parseJsonSafe(args.payload, defaultPayload)
613
+ : defaultPayload,
437
614
  trace_id: args.trace || undefined,
438
615
  correlation_id: args.correlation || undefined,
439
616
  });
@@ -448,14 +625,48 @@ async function cmdResult(args) {
448
625
 
449
626
  async function cmdControl(args) {
450
627
  const outcome = await requestHub(HUB_OPERATIONS.control, {
451
- from_agent: args.from || 'lead',
628
+ from_agent: args.from || "lead",
452
629
  to_agent: args.to,
453
630
  command: args.command,
454
- reason: args.reason || '',
631
+ reason: args.reason || "",
455
632
  payload: args.payload ? parseJsonSafe(args.payload, {}) : {},
456
633
  trace_id: args.trace || undefined,
457
634
  correlation_id: args.correlation || undefined,
458
- ttl_ms: args['ttl-ms'] != null ? Number(args['ttl-ms']) : undefined,
635
+ ttl_ms: args["ttl-ms"] != null ? Number(args["ttl-ms"]) : undefined,
636
+ });
637
+ const result = outcome?.result;
638
+ return emitJson(result || unavailableResult());
639
+ }
640
+
641
+ async function cmdHandoff(args) {
642
+ const from = args.from || args[1];
643
+ const to = args.to || args[2];
644
+ const payload = parseJsonSafe(args.payload || args[3] || "{}", {});
645
+ const outcome = await requestHub(
646
+ HUB_OPERATIONS.handoff,
647
+ buildHandoffBody(from, to, payload),
648
+ );
649
+ const result = outcome?.result;
650
+ return emitJson(result || unavailableResult());
651
+ }
652
+
653
+ async function cmdPublish(args) {
654
+ const from = args.from || args[1];
655
+ const to = args.to || args[2];
656
+ const type = args.type || args[3] || "event";
657
+ const payload = parseJsonSafe(args.payload || args[4] || "{}", {});
658
+ const outcome = await requestHub(
659
+ HUB_OPERATIONS.publish,
660
+ buildPublishBody(from, to, type, payload),
661
+ );
662
+ const result = outcome?.result;
663
+ return emitJson(result || unavailableResult());
664
+ }
665
+
666
+ async function cmdSendInput(args) {
667
+ const outcome = await requestHub(HUB_OPERATIONS.sendInput, {
668
+ session_id: args["session-id"],
669
+ text: args.text,
459
670
  });
460
671
  const result = outcome?.result;
461
672
  return emitJson(result || unavailableResult());
@@ -464,26 +675,35 @@ async function cmdControl(args) {
464
675
  async function cmdContext(args) {
465
676
  const outcome = await requestHub(HUB_OPERATIONS.context, {
466
677
  agent_id: args.agent,
467
- topics: args.topics ? args.topics.split(',') : undefined,
468
- max_messages: parseInt(args.max || '10', 10),
678
+ topics: args.topics ? args.topics.split(",") : undefined,
679
+ max_messages: parseInt(args.max || "10", 10),
469
680
  auto_ack: true,
470
681
  });
471
682
  const result = outcome?.result;
472
683
 
473
684
  if (result?.ok && result.data?.messages?.length) {
474
685
  const parts = result.data.messages.map((message, index) => {
475
- const payload = typeof message.payload === 'string'
476
- ? message.payload
477
- : JSON.stringify(message.payload, null, 2);
478
- return `=== Context ${index + 1}: ${message.from_agent || 'unknown'} (${message.topic || 'unknown'}) ===\n${payload}`;
686
+ const payload =
687
+ typeof message.payload === "string"
688
+ ? message.payload
689
+ : JSON.stringify(message.payload, null, 2);
690
+ return `=== Context ${index + 1}: ${message.from_agent || "unknown"} (${message.topic || "unknown"}) ===\n${payload}`;
479
691
  });
480
- const combined = parts.join('\n\n');
692
+ const combined = parts.join("\n\n");
481
693
 
482
694
  if (args.out) {
483
- writeFileSync(args.out, combined, 'utf8');
484
- return emitJson({ ok: true, count: result.data.messages.length, file: args.out });
695
+ writeFileSync(args.out, combined, "utf8");
696
+ return emitJson({
697
+ ok: true,
698
+ count: result.data.messages.length,
699
+ file: args.out,
700
+ });
485
701
  } else {
486
- return emitJson({ ok: true, count: result.data.messages.length, context: combined });
702
+ return emitJson({
703
+ ok: true,
704
+ count: result.data.messages.length,
705
+ context: combined,
706
+ });
487
707
  }
488
708
  }
489
709
 
@@ -491,7 +711,7 @@ async function cmdContext(args) {
491
711
  if (args.out) {
492
712
  return emitJson({ ok: true, count: 0 });
493
713
  }
494
- return emitJson({ ok: true, count: 0, context: '' });
714
+ return emitJson({ ok: true, count: 0, context: "" });
495
715
  }
496
716
 
497
717
  return emitJson(result || unavailableResult());
@@ -504,7 +724,7 @@ async function cmdDeregister(args) {
504
724
  const result = outcome?.result;
505
725
 
506
726
  if (result?.ok) {
507
- return emitJson({ ok: true, agent_id: args.agent, status: 'offline' });
727
+ return emitJson({ ok: true, agent_id: args.agent, status: "offline" });
508
728
  }
509
729
 
510
730
  return emitJson(result || unavailableResult());
@@ -512,15 +732,17 @@ async function cmdDeregister(args) {
512
732
 
513
733
  async function cmdAssignAsync(args) {
514
734
  const outcome = await requestHub(HUB_OPERATIONS.assignAsync, {
515
- supervisor_agent: args['supervisor-agent'],
516
- worker_agent: args['worker-agent'],
735
+ supervisor_agent: args["supervisor-agent"],
736
+ worker_agent: args["worker-agent"],
517
737
  task: args.task,
518
- topic: args.topic || 'assign.job',
738
+ topic: args.topic || "assign.job",
519
739
  payload: args.payload ? parseJsonSafe(args.payload, {}) : {},
520
740
  priority: args.priority != null ? Number(args.priority) : undefined,
521
- ttl_ms: args['ttl-ms'] != null ? Number(args['ttl-ms']) : undefined,
522
- timeout_ms: args['timeout-ms'] != null ? Number(args['timeout-ms']) : undefined,
523
- max_retries: args['max-retries'] != null ? Number(args['max-retries']) : undefined,
741
+ ttl_ms: args["ttl-ms"] != null ? Number(args["ttl-ms"]) : undefined,
742
+ timeout_ms:
743
+ args["timeout-ms"] != null ? Number(args["timeout-ms"]) : undefined,
744
+ max_retries:
745
+ args["max-retries"] != null ? Number(args["max-retries"]) : undefined,
524
746
  trace_id: args.trace || undefined,
525
747
  correlation_id: args.correlation || undefined,
526
748
  });
@@ -530,8 +752,8 @@ async function cmdAssignAsync(args) {
530
752
 
531
753
  async function cmdAssignResult(args) {
532
754
  const outcome = await requestHub(HUB_OPERATIONS.assignResult, {
533
- job_id: args['job-id'],
534
- worker_agent: args['worker-agent'],
755
+ job_id: args["job-id"],
756
+ worker_agent: args["worker-agent"],
535
757
  status: args.status,
536
758
  attempt: args.attempt != null ? Number(args.attempt) : undefined,
537
759
  result: args.result ? parseJsonSafe(args.result, null) : undefined,
@@ -545,7 +767,7 @@ async function cmdAssignResult(args) {
545
767
 
546
768
  async function cmdAssignStatus(args) {
547
769
  const outcome = await requestHub(HUB_OPERATIONS.assignStatus, {
548
- job_id: args['job-id'],
770
+ job_id: args["job-id"],
549
771
  });
550
772
  const result = outcome?.result;
551
773
  return emitJson(result || unavailableResult());
@@ -553,9 +775,9 @@ async function cmdAssignStatus(args) {
553
775
 
554
776
  async function cmdAssignRetry(args) {
555
777
  const outcome = await requestHub(HUB_OPERATIONS.assignRetry, {
556
- job_id: args['job-id'],
778
+ job_id: args["job-id"],
557
779
  reason: args.reason,
558
- requested_by: args['requested-by'],
780
+ requested_by: args["requested-by"],
559
781
  });
560
782
  const result = outcome?.result;
561
783
  return emitJson(result || unavailableResult());
@@ -567,10 +789,15 @@ async function cmdTeamInfo(args) {
567
789
  include_members: true,
568
790
  include_paths: true,
569
791
  };
570
- const outcome = await requestHub(HUB_OPERATIONS.teamInfo, body, 3000, async () => {
571
- const { teamInfo } = await import('./team/nativeProxy.mjs');
572
- return await teamInfo(body);
573
- });
792
+ const outcome = await requestHub(
793
+ HUB_OPERATIONS.teamInfo,
794
+ body,
795
+ 3000,
796
+ async () => {
797
+ const { teamInfo } = await import("./team/nativeProxy.mjs");
798
+ return await teamInfo(body);
799
+ },
800
+ );
574
801
  const result = outcome?.result;
575
802
  return emitJson(result || unavailableResult());
576
803
  }
@@ -579,14 +806,24 @@ async function cmdTeamTaskList(args) {
579
806
  const body = {
580
807
  team_name: args.team,
581
808
  owner: args.owner,
582
- statuses: args.statuses ? args.statuses.split(',').map((status) => status.trim()).filter(Boolean) : [],
583
- include_internal: !!args['include-internal'],
584
- limit: parseInt(args.limit || '200', 10),
809
+ statuses: args.statuses
810
+ ? args.statuses
811
+ .split(",")
812
+ .map((status) => status.trim())
813
+ .filter(Boolean)
814
+ : [],
815
+ include_internal: !!args["include-internal"],
816
+ limit: parseInt(args.limit || "200", 10),
585
817
  };
586
- const outcome = await requestHub(HUB_OPERATIONS.teamTaskList, body, 3000, async () => {
587
- const { teamTaskList } = await import('./team/nativeProxy.mjs');
588
- return await teamTaskList(body);
589
- });
818
+ const outcome = await requestHub(
819
+ HUB_OPERATIONS.teamTaskList,
820
+ body,
821
+ 3000,
822
+ async () => {
823
+ const { teamTaskList } = await import("./team/nativeProxy.mjs");
824
+ return await teamTaskList(body);
825
+ },
826
+ );
590
827
  const result = outcome?.result;
591
828
  return emitJson(result || unavailableResult());
592
829
  }
@@ -594,23 +831,43 @@ async function cmdTeamTaskList(args) {
594
831
  async function cmdTeamTaskUpdate(args) {
595
832
  const body = {
596
833
  team_name: args.team,
597
- task_id: args['task-id'],
834
+ task_id: args["task-id"],
598
835
  claim: !!args.claim,
599
836
  owner: args.owner,
600
837
  status: args.status,
601
838
  subject: args.subject,
602
839
  description: args.description,
603
- activeForm: args['active-form'],
604
- add_blocks: args['add-blocks'] ? args['add-blocks'].split(',').map((value) => value.trim()).filter(Boolean) : undefined,
605
- add_blocked_by: args['add-blocked-by'] ? args['add-blocked-by'].split(',').map((value) => value.trim()).filter(Boolean) : undefined,
606
- metadata_patch: args['metadata-patch'] ? parseJsonSafe(args['metadata-patch'], null) : undefined,
607
- if_match_mtime_ms: args['if-match-mtime-ms'] != null ? Number(args['if-match-mtime-ms']) : undefined,
840
+ activeForm: args["active-form"],
841
+ add_blocks: args["add-blocks"]
842
+ ? args["add-blocks"]
843
+ .split(",")
844
+ .map((value) => value.trim())
845
+ .filter(Boolean)
846
+ : undefined,
847
+ add_blocked_by: args["add-blocked-by"]
848
+ ? args["add-blocked-by"]
849
+ .split(",")
850
+ .map((value) => value.trim())
851
+ .filter(Boolean)
852
+ : undefined,
853
+ metadata_patch: args["metadata-patch"]
854
+ ? parseJsonSafe(args["metadata-patch"], null)
855
+ : undefined,
856
+ if_match_mtime_ms:
857
+ args["if-match-mtime-ms"] != null
858
+ ? Number(args["if-match-mtime-ms"])
859
+ : undefined,
608
860
  actor: args.actor,
609
861
  };
610
- const outcome = await requestHub(HUB_OPERATIONS.teamTaskUpdate, body, 3000, async () => {
611
- const { teamTaskUpdate } = await import('./team/nativeProxy.mjs');
612
- return await teamTaskUpdate(body);
613
- });
862
+ const outcome = await requestHub(
863
+ HUB_OPERATIONS.teamTaskUpdate,
864
+ body,
865
+ 3000,
866
+ async () => {
867
+ const { teamTaskUpdate } = await import("./team/nativeProxy.mjs");
868
+ return await teamTaskUpdate(body);
869
+ },
870
+ );
614
871
  const result = outcome?.result;
615
872
  return emitJson(result || unavailableResult());
616
873
  }
@@ -619,15 +876,20 @@ async function cmdTeamSendMessage(args) {
619
876
  const body = {
620
877
  team_name: args.team,
621
878
  from: args.from,
622
- to: args.to || 'team-lead',
879
+ to: args.to || "team-lead",
623
880
  text: args.text,
624
881
  summary: args.summary,
625
- color: args.color || 'blue',
882
+ color: args.color || "blue",
626
883
  };
627
- const outcome = await requestHub(HUB_OPERATIONS.teamSendMessage, body, 3000, async () => {
628
- const { teamSendMessage } = await import('./team/nativeProxy.mjs');
629
- return await teamSendMessage(body);
630
- });
884
+ const outcome = await requestHub(
885
+ HUB_OPERATIONS.teamSendMessage,
886
+ body,
887
+ 3000,
888
+ async () => {
889
+ const { teamSendMessage } = await import("./team/nativeProxy.mjs");
890
+ return await teamSendMessage(body);
891
+ },
892
+ );
631
893
  const result = outcome?.result;
632
894
  return emitJson(result || unavailableResult());
633
895
  }
@@ -637,25 +899,32 @@ function getHubDbPath() {
637
899
  }
638
900
 
639
901
  async function cmdPipelineState(args) {
640
- const outcome = await requestHub(HUB_OPERATIONS.pipelineState, { team_name: args.team }, 3000, async () => {
641
- try {
642
- const { default: Database } = await import('better-sqlite3');
643
- const { ensurePipelineTable, readPipelineState } = await import('./pipeline/state.mjs');
644
- const dbPath = getHubDbPath();
645
- if (!existsSync(dbPath)) {
646
- return { ok: false, error: 'hub_db_not_found' };
902
+ const outcome = await requestHub(
903
+ HUB_OPERATIONS.pipelineState,
904
+ { team_name: args.team },
905
+ 3000,
906
+ async () => {
907
+ try {
908
+ const { default: Database } = await import("better-sqlite3");
909
+ const { ensurePipelineTable, readPipelineState } = await import(
910
+ "./pipeline/state.mjs"
911
+ );
912
+ const dbPath = getHubDbPath();
913
+ if (!existsSync(dbPath)) {
914
+ return { ok: false, error: "hub_db_not_found" };
915
+ }
916
+ const db = new Database(dbPath, { readonly: true });
917
+ ensurePipelineTable(db);
918
+ const state = readPipelineState(db, args.team);
919
+ db.close();
920
+ return state
921
+ ? { ok: true, data: state }
922
+ : { ok: false, error: "pipeline_not_found" };
923
+ } catch (e) {
924
+ return { ok: false, error: e.message };
647
925
  }
648
- const db = new Database(dbPath, { readonly: true });
649
- ensurePipelineTable(db);
650
- const state = readPipelineState(db, args.team);
651
- db.close();
652
- return state
653
- ? { ok: true, data: state }
654
- : { ok: false, error: 'pipeline_not_found' };
655
- } catch (e) {
656
- return { ok: false, error: e.message };
657
- }
658
- });
926
+ },
927
+ );
659
928
  const result = outcome?.result;
660
929
  return emitJson(result || unavailableResult());
661
930
  }
@@ -665,23 +934,28 @@ async function cmdPipelineAdvance(args) {
665
934
  team_name: args.team,
666
935
  phase: args.status, // --status를 phase로 재활용
667
936
  };
668
- const outcome = await requestHub(HUB_OPERATIONS.pipelineAdvance, body, 3000, async () => {
669
- try {
670
- const { default: Database } = await import('better-sqlite3');
671
- const { createPipeline } = await import('./pipeline/index.mjs');
672
- const dbPath = getHubDbPath();
673
- if (!existsSync(dbPath)) {
674
- return { ok: false, error: 'hub_db_not_found' };
937
+ const outcome = await requestHub(
938
+ HUB_OPERATIONS.pipelineAdvance,
939
+ body,
940
+ 3000,
941
+ async () => {
942
+ try {
943
+ const { default: Database } = await import("better-sqlite3");
944
+ const { createPipeline } = await import("./pipeline/index.mjs");
945
+ const dbPath = getHubDbPath();
946
+ if (!existsSync(dbPath)) {
947
+ return { ok: false, error: "hub_db_not_found" };
948
+ }
949
+ const db = new Database(dbPath);
950
+ const pipeline = createPipeline(db, args.team);
951
+ const advanceResult = pipeline.advance(args.status);
952
+ db.close();
953
+ return advanceResult;
954
+ } catch (e) {
955
+ return { ok: false, error: e.message };
675
956
  }
676
- const db = new Database(dbPath);
677
- const pipeline = createPipeline(db, args.team);
678
- const advanceResult = pipeline.advance(args.status);
679
- db.close();
680
- return advanceResult;
681
- } catch (e) {
682
- return { ok: false, error: e.message };
683
- }
684
- });
957
+ },
958
+ );
685
959
  const result = outcome?.result;
686
960
  return emitJson(result || unavailableResult());
687
961
  }
@@ -689,8 +963,9 @@ async function cmdPipelineAdvance(args) {
689
963
  async function cmdPipelineInit(args) {
690
964
  const outcome = await requestHub(HUB_OPERATIONS.pipelineInit, {
691
965
  team_name: args.team,
692
- fix_max: args['fix-max'] != null ? Number(args['fix-max']) : undefined,
693
- ralph_max: args['ralph-max'] != null ? Number(args['ralph-max']) : undefined,
966
+ fix_max: args["fix-max"] != null ? Number(args["fix-max"]) : undefined,
967
+ ralph_max:
968
+ args["ralph-max"] != null ? Number(args["ralph-max"]) : undefined,
694
969
  });
695
970
  const result = outcome?.result;
696
971
  return emitJson(result || unavailableResult());
@@ -703,25 +978,29 @@ async function cmdPipelineList() {
703
978
  }
704
979
 
705
980
  async function cmdPing() {
706
- const outcome = await requestHub(HUB_OPERATIONS.hubStatus, { scope: 'hub' }, 2000);
981
+ const outcome = await requestHub(
982
+ HUB_OPERATIONS.hubStatus,
983
+ { scope: "hub" },
984
+ 2000,
985
+ );
707
986
 
708
- if (outcome?.transport === 'pipe' && outcome.result?.ok) {
987
+ if (outcome?.transport === "pipe" && outcome.result?.ok) {
709
988
  return emitJson({
710
989
  ok: true,
711
- hub: outcome.result.data?.hub?.state || 'healthy',
990
+ hub: outcome.result.data?.hub?.state || "healthy",
712
991
  pipe_path: getHubPipePath(),
713
- transport: 'pipe',
992
+ transport: "pipe",
714
993
  });
715
994
  }
716
995
 
717
- if (outcome?.transport === 'http' && outcome.result) {
996
+ if (outcome?.transport === "http" && outcome.result) {
718
997
  const data = outcome.result;
719
998
  return emitJson({
720
999
  ok: true,
721
1000
  hub: data.hub?.state,
722
1001
  sessions: data.sessions,
723
1002
  pipe_path: data.pipe?.path || data.pipe_path || null,
724
- transport: 'http',
1003
+ transport: "http",
725
1004
  });
726
1005
  }
727
1006
 
@@ -731,21 +1010,26 @@ async function cmdPing() {
731
1010
  async function cmdDelegatorDelegate(args) {
732
1011
  const body = {
733
1012
  prompt: args.text || args.prompt,
734
- provider: args.provider || 'auto',
735
- mode: args.mode || 'sync',
736
- agent_type: args.agent || 'executor',
737
- mcp_profile: args['mcp-profile'] || 'auto',
738
- session_key: args['session-key'] || undefined,
739
- timeout_ms: args['timeout-ms'] != null ? Number(args['timeout-ms']) : undefined,
1013
+ provider: args.provider || "auto",
1014
+ mode: args.mode || "sync",
1015
+ agent_type: args.agent || "executor",
1016
+ mcp_profile: args["mcp-profile"] || "auto",
1017
+ session_key: args["session-key"] || undefined,
1018
+ timeout_ms:
1019
+ args["timeout-ms"] != null ? Number(args["timeout-ms"]) : undefined,
740
1020
  };
741
- const timeoutMs = body.mode === 'async' ? 10000 : 120000;
742
- const outcome = await requestHub(HUB_OPERATIONS.delegatorDelegate, body, timeoutMs);
1021
+ const timeoutMs = body.mode === "async" ? 10000 : 120000;
1022
+ const outcome = await requestHub(
1023
+ HUB_OPERATIONS.delegatorDelegate,
1024
+ body,
1025
+ timeoutMs,
1026
+ );
743
1027
  return emitJson(outcome?.result || unavailableResult());
744
1028
  }
745
1029
 
746
1030
  async function cmdDelegatorReply(args) {
747
1031
  const body = {
748
- job_id: args['job-id'],
1032
+ job_id: args["job-id"],
749
1033
  reply: args.text || args.reply,
750
1034
  done: !!args.done,
751
1035
  };
@@ -755,45 +1039,109 @@ async function cmdDelegatorReply(args) {
755
1039
 
756
1040
  async function cmdDelegatorStatus(args) {
757
1041
  const body = {
758
- job_id: args['job-id'],
1042
+ job_id: args["job-id"],
759
1043
  };
760
1044
  const outcome = await requestHub(HUB_OPERATIONS.delegatorStatus, body, 5000);
761
1045
  return emitJson(outcome?.result || unavailableResult());
762
1046
  }
763
1047
 
1048
+ async function cmdHitlRequest(args) {
1049
+ const body = {
1050
+ kind: args[1],
1051
+ prompt: args[2],
1052
+ requester_agent: args[3] || "cli",
1053
+ };
1054
+ const outcome = await requestHub(
1055
+ HUB_OPERATIONS["hitl-request"],
1056
+ body,
1057
+ 120000,
1058
+ );
1059
+ return emitJson(outcome?.result || unavailableResult());
1060
+ }
1061
+
1062
+ async function cmdHitlSubmit(args) {
1063
+ const body = {
1064
+ request_id: args[1],
1065
+ action: args[2] || "accept",
1066
+ content: args[3],
1067
+ };
1068
+ const outcome = await requestHub(HUB_OPERATIONS["hitl-submit"], body, 120000);
1069
+ return emitJson(outcome?.result || unavailableResult());
1070
+ }
1071
+
1072
+ async function cmdHitlPending() {
1073
+ const outcome = await requestHub(HUB_OPERATIONS["hitl-pending"], {}, 5000);
1074
+ return emitJson(outcome?.result || unavailableResult());
1075
+ }
1076
+
764
1077
  export async function main(argv = process.argv.slice(2)) {
765
1078
  const cmd = argv[0];
766
1079
  const args = parseArgs(argv.slice(1));
767
1080
 
768
1081
  switch (cmd) {
769
- case 'register': return await cmdRegister(args);
770
- case 'result': return await cmdResult(args);
771
- case 'control': return await cmdControl(args);
772
- case 'context': return await cmdContext(args);
773
- case 'deregister': return await cmdDeregister(args);
774
- case 'assign-async': return await cmdAssignAsync(args);
775
- case 'assign-result': return await cmdAssignResult(args);
776
- case 'assign-status': return await cmdAssignStatus(args);
777
- case 'assign-retry': return await cmdAssignRetry(args);
778
- case 'team-info': return await cmdTeamInfo(args);
779
- case 'team-task-list': return await cmdTeamTaskList(args);
780
- case 'team-task-update': return await cmdTeamTaskUpdate(args);
781
- case 'team-send-message': return await cmdTeamSendMessage(args);
782
- case 'pipeline-state': return await cmdPipelineState(args);
783
- case 'pipeline-advance': return await cmdPipelineAdvance(args);
784
- case 'pipeline-init': return await cmdPipelineInit(args);
785
- case 'pipeline-list': return await cmdPipelineList(args);
786
- case 'ping': return await cmdPing(args);
787
- case 'delegator-delegate': return await cmdDelegatorDelegate(args);
788
- case 'delegator-reply': return await cmdDelegatorReply(args);
789
- case 'delegator-status': return await cmdDelegatorStatus(args);
1082
+ case "register":
1083
+ return await cmdRegister(args);
1084
+ case "result":
1085
+ return await cmdResult(args);
1086
+ case "control":
1087
+ return await cmdControl(args);
1088
+ case "handoff":
1089
+ return await cmdHandoff(args);
1090
+ case "publish":
1091
+ return await cmdPublish(args);
1092
+ case "send-input":
1093
+ return await cmdSendInput(args);
1094
+ case "context":
1095
+ return await cmdContext(args);
1096
+ case "deregister":
1097
+ return await cmdDeregister(args);
1098
+ case "assign-async":
1099
+ return await cmdAssignAsync(args);
1100
+ case "assign-result":
1101
+ return await cmdAssignResult(args);
1102
+ case "assign-status":
1103
+ return await cmdAssignStatus(args);
1104
+ case "assign-retry":
1105
+ return await cmdAssignRetry(args);
1106
+ case "team-info":
1107
+ return await cmdTeamInfo(args);
1108
+ case "team-task-list":
1109
+ return await cmdTeamTaskList(args);
1110
+ case "team-task-update":
1111
+ return await cmdTeamTaskUpdate(args);
1112
+ case "team-send-message":
1113
+ return await cmdTeamSendMessage(args);
1114
+ case "pipeline-state":
1115
+ return await cmdPipelineState(args);
1116
+ case "pipeline-advance":
1117
+ return await cmdPipelineAdvance(args);
1118
+ case "pipeline-init":
1119
+ return await cmdPipelineInit(args);
1120
+ case "pipeline-list":
1121
+ return await cmdPipelineList(args);
1122
+ case "ping":
1123
+ return await cmdPing(args);
1124
+ case "delegator-delegate":
1125
+ return await cmdDelegatorDelegate(args);
1126
+ case "delegator-reply":
1127
+ return await cmdDelegatorReply(args);
1128
+ case "delegator-status":
1129
+ return await cmdDelegatorStatus(args);
1130
+ case "hitl-request":
1131
+ return await cmdHitlRequest(args);
1132
+ case "hitl-submit":
1133
+ return await cmdHitlSubmit(args);
1134
+ case "hitl-pending":
1135
+ return await cmdHitlPending(args);
790
1136
  default:
791
- console.error('사용법: bridge.mjs <register|result|control|context|deregister|assign-async|assign-result|assign-status|assign-retry|team-info|team-task-list|team-task-update|team-send-message|pipeline-state|pipeline-advance|pipeline-init|pipeline-list|ping|delegator-delegate|delegator-reply|delegator-status> [--옵션]');
1137
+ console.error(
1138
+ "사용법: bridge.mjs <register|result|control|handoff|publish|send-input|context|deregister|assign-async|assign-result|assign-status|assign-retry|team-info|team-task-list|team-task-update|team-send-message|pipeline-state|pipeline-advance|pipeline-init|pipeline-list|ping|delegator-delegate|delegator-reply|delegator-status|hitl-request|hitl-submit|hitl-pending> [--옵션]",
1139
+ );
792
1140
  process.exit(1);
793
1141
  }
794
1142
  }
795
1143
 
796
- const selfRun = process.argv[1]?.replace(/\\/g, '/').endsWith('hub/bridge.mjs');
1144
+ const selfRun = process.argv[1]?.replace(/\\/g, "/").endsWith("hub/bridge.mjs");
797
1145
  if (selfRun) {
798
- process.exitCode = await main() ? 0 : 1;
1146
+ process.exitCode = (await main()) ? 0 : 1;
799
1147
  }