triflux 3.3.0-dev.3 → 3.3.0-dev.5

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
@@ -16,6 +16,9 @@ const HUB_TOKEN_FILE = join(homedir(), '.claude', '.tfx-hub-token');
16
16
 
17
17
  // Hub 인증 토큰 읽기 (파일 없으면 null → 하위 호환)
18
18
  function readHubToken() {
19
+ if (process.env.TFX_HUB_TOKEN) {
20
+ return String(process.env.TFX_HUB_TOKEN).trim();
21
+ }
19
22
  try {
20
23
  return readFileSync(HUB_TOKEN_FILE, 'utf8').trim();
21
24
  } catch {
@@ -51,21 +54,45 @@ export function getHubPipePath() {
51
54
  }
52
55
  }
53
56
 
54
- export async function post(path, body, timeoutMs = 5000) {
57
+ const HUB_OPERATIONS = Object.freeze({
58
+ register: { transport: 'command', action: 'register', httpPath: '/bridge/register' },
59
+ result: { transport: 'command', action: 'result', httpPath: '/bridge/result' },
60
+ control: { transport: 'command', action: 'control', httpPath: '/bridge/control' },
61
+ context: { transport: 'query', action: 'drain', httpPath: '/bridge/context' },
62
+ deregister: { transport: 'command', action: 'deregister', httpPath: '/bridge/deregister' },
63
+ assignAsync: { transport: 'command', action: 'assign', httpPath: '/bridge/assign/async' },
64
+ assignResult: { transport: 'command', action: 'assign_result', httpPath: '/bridge/assign/result' },
65
+ assignStatus: { transport: 'query', action: 'assign_status', httpPath: '/bridge/assign/status' },
66
+ assignRetry: { transport: 'command', action: 'assign_retry', httpPath: '/bridge/assign/retry' },
67
+ teamInfo: { transport: 'query', action: 'team_info', httpPath: '/bridge/team/info' },
68
+ teamTaskList: { transport: 'query', action: 'team_task_list', httpPath: '/bridge/team/task-list' },
69
+ teamTaskUpdate: { transport: 'command', action: 'team_task_update', httpPath: '/bridge/team/task-update' },
70
+ teamSendMessage: { transport: 'command', action: 'team_send_message', httpPath: '/bridge/team/send-message' },
71
+ pipelineState: { transport: 'query', action: 'pipeline_state', httpPath: '/bridge/pipeline/state' },
72
+ pipelineAdvance: { transport: 'command', action: 'pipeline_advance', httpPath: '/bridge/pipeline/advance' },
73
+ pipelineInit: { transport: 'command', action: 'pipeline_init', httpPath: '/bridge/pipeline/init' },
74
+ pipelineList: { transport: 'query', action: 'pipeline_list', httpPath: '/bridge/pipeline/list' },
75
+ hubStatus: { transport: 'query', action: 'status', httpPath: '/status', httpMethod: 'GET' },
76
+ });
77
+
78
+ export async function requestJson(path, { method = 'POST', body, timeoutMs = 5000 } = {}) {
55
79
  const controller = new AbortController();
56
80
  const timer = setTimeout(() => controller.abort(), timeoutMs);
57
81
 
58
82
  try {
59
- const headers = { 'Content-Type': 'application/json' };
83
+ const headers = {};
60
84
  const token = readHubToken();
61
85
  if (token) {
62
86
  headers['Authorization'] = `Bearer ${token}`;
63
87
  }
88
+ if (body !== undefined) {
89
+ headers['Content-Type'] = 'application/json';
90
+ }
64
91
 
65
92
  const res = await fetch(`${getHubUrl()}${path}`, {
66
- method: 'POST',
93
+ method,
67
94
  headers,
68
- body: JSON.stringify(body),
95
+ body: body === undefined ? undefined : JSON.stringify(body),
69
96
  signal: controller.signal,
70
97
  });
71
98
  clearTimeout(timer);
@@ -76,6 +103,10 @@ export async function post(path, body, timeoutMs = 5000) {
76
103
  }
77
104
  }
78
105
 
106
+ export async function post(path, body, timeoutMs = 5000) {
107
+ return await requestJson(path, { method: 'POST', body, timeoutMs });
108
+ }
109
+
79
110
  export async function connectPipe(timeoutMs = 1200) {
80
111
  const pipePath = getHubPipePath();
81
112
  if (!pipePath) return null;
@@ -108,12 +139,14 @@ async function pipeRequest(type, action, payload, timeoutMs = 3000) {
108
139
  return await new Promise((resolve) => {
109
140
  const requestId = randomUUID();
110
141
  let buffer = '';
142
+ let settled = false;
111
143
  const timer = setTimeout(() => {
112
- try { socket.destroy(); } catch {}
113
- resolve(null);
144
+ finish(null);
114
145
  }, timeoutMs);
115
146
 
116
147
  const finish = (result) => {
148
+ if (settled) return;
149
+ settled = true;
117
150
  clearTimeout(timer);
118
151
  try { socket.end(); } catch {}
119
152
  resolve(result);
@@ -172,6 +205,7 @@ export function parseArgs(argv) {
172
205
  topics: { type: 'string' },
173
206
  capabilities: { type: 'string' },
174
207
  file: { type: 'string' },
208
+ payload: { type: 'string' },
175
209
  topic: { type: 'string' },
176
210
  trace: { type: 'string' },
177
211
  correlation: { type: 'string' },
@@ -180,20 +214,37 @@ export function parseArgs(argv) {
180
214
  out: { type: 'string' },
181
215
  team: { type: 'string' },
182
216
  'task-id': { type: 'string' },
217
+ 'job-id': { type: 'string' },
183
218
  owner: { type: 'string' },
184
219
  status: { type: 'string' },
185
220
  statuses: { type: 'string' },
186
221
  claim: { type: 'boolean' },
187
222
  actor: { type: 'string' },
223
+ command: { type: 'string' },
224
+ reason: { type: 'string' },
188
225
  from: { type: 'string' },
189
226
  to: { type: 'string' },
190
227
  text: { type: 'string' },
228
+ task: { type: 'string' },
229
+ 'supervisor-agent': { type: 'string' },
230
+ 'worker-agent': { type: 'string' },
231
+ priority: { type: 'string' },
232
+ 'ttl-ms': { type: 'string' },
233
+ 'timeout-ms': { type: 'string' },
234
+ 'max-retries': { type: 'string' },
235
+ attempt: { type: 'string' },
236
+ result: { type: 'string' },
237
+ error: { type: 'string' },
238
+ metadata: { type: 'string' },
239
+ 'requested-by': { type: 'string' },
191
240
  summary: { type: 'string' },
192
241
  color: { type: 'string' },
193
242
  limit: { type: 'string' },
194
243
  'include-internal': { type: 'boolean' },
195
244
  subject: { type: 'string' },
196
245
  description: { type: 'string' },
246
+ 'fix-max': { type: 'string' },
247
+ 'ralph-max': { type: 'string' },
197
248
  'active-form': { type: 'string' },
198
249
  'add-blocks': { type: 'string' },
199
250
  'add-blocked-by': { type: 'string' },
@@ -214,18 +265,35 @@ export function parseJsonSafe(raw, fallback = null) {
214
265
  }
215
266
  }
216
267
 
217
- async function runPipeFirst(commandName, queryName, httpPath, body, timeoutMs = 3000) {
218
- const viaPipe = commandName
219
- ? await pipeCommand(commandName, body, timeoutMs)
220
- : await pipeQuery(queryName, body, timeoutMs);
221
- if (viaPipe) return viaPipe;
222
- return await post(httpPath, body, Math.max(timeoutMs, 5000));
268
+ async function requestHub(operation, body, timeoutMs = 3000, fallback = null) {
269
+ const viaPipe = operation.transport === 'command'
270
+ ? await pipeCommand(operation.action, body, timeoutMs)
271
+ : await pipeQuery(operation.action, body, timeoutMs);
272
+ if (viaPipe) {
273
+ return { transport: 'pipe', result: viaPipe };
274
+ }
275
+
276
+ const viaHttp = operation.httpPath
277
+ ? await requestJson(operation.httpPath, {
278
+ method: operation.httpMethod || 'POST',
279
+ body: operation.httpMethod === 'GET' ? undefined : body,
280
+ timeoutMs: Math.max(timeoutMs, 5000),
281
+ })
282
+ : null;
283
+ if (viaHttp) {
284
+ return { transport: 'http', result: viaHttp };
285
+ }
286
+
287
+ if (!fallback) return null;
288
+ const viaFallback = await fallback();
289
+ if (!viaFallback) return null;
290
+ return { transport: 'fallback', result: viaFallback };
223
291
  }
224
292
 
225
293
  async function cmdRegister(args) {
226
294
  const agentId = args.agent;
227
295
  const timeoutSec = parseInt(args.timeout || '600', 10);
228
- const result = await runPipeFirst('register', null, '/bridge/register', {
296
+ const outcome = await requestHub(HUB_OPERATIONS.register, {
229
297
  agent_id: agentId,
230
298
  cli: args.cli || 'other',
231
299
  timeout_sec: timeoutSec,
@@ -237,6 +305,7 @@ async function cmdRegister(args) {
237
305
  registered_at: Date.now(),
238
306
  },
239
307
  });
308
+ const result = outcome?.result;
240
309
 
241
310
  if (result?.ok) {
242
311
  console.log(JSON.stringify({
@@ -256,20 +325,23 @@ async function cmdResult(args) {
256
325
  output = readFileSync(args.file, 'utf8').slice(0, 49152);
257
326
  }
258
327
 
259
- const result = await runPipeFirst('result', null, '/bridge/result', {
328
+ const defaultPayload = {
329
+ agent_id: args.agent,
330
+ exit_code: parseInt(args['exit-code'] || '0', 10),
331
+ output_length: output.length,
332
+ output_preview: output.slice(0, 4096),
333
+ output_file: args.file || null,
334
+ completed_at: Date.now(),
335
+ };
336
+
337
+ const outcome = await requestHub(HUB_OPERATIONS.result, {
260
338
  agent_id: args.agent,
261
339
  topic: args.topic || 'task.result',
262
- payload: {
263
- agent_id: args.agent,
264
- exit_code: parseInt(args['exit-code'] || '0', 10),
265
- output_length: output.length,
266
- output_preview: output.slice(0, 4096),
267
- output_file: args.file || null,
268
- completed_at: Date.now(),
269
- },
340
+ payload: args.payload ? parseJsonSafe(args.payload, defaultPayload) : defaultPayload,
270
341
  trace_id: args.trace || undefined,
271
342
  correlation_id: args.correlation || undefined,
272
343
  });
344
+ const result = outcome?.result;
273
345
 
274
346
  if (result?.ok) {
275
347
  console.log(JSON.stringify({ ok: true, message_id: result.data?.message_id }));
@@ -278,13 +350,33 @@ async function cmdResult(args) {
278
350
  }
279
351
  }
280
352
 
353
+ async function cmdControl(args) {
354
+ const outcome = await requestHub(HUB_OPERATIONS.control, {
355
+ from_agent: args.from || 'lead',
356
+ to_agent: args.to,
357
+ command: args.command,
358
+ reason: args.reason || '',
359
+ payload: args.payload ? parseJsonSafe(args.payload, {}) : {},
360
+ trace_id: args.trace || undefined,
361
+ correlation_id: args.correlation || undefined,
362
+ ttl_ms: args['ttl-ms'] != null ? Number(args['ttl-ms']) : undefined,
363
+ });
364
+ const result = outcome?.result;
365
+ if (result) {
366
+ console.log(JSON.stringify(result));
367
+ } else {
368
+ console.log(JSON.stringify({ ok: false, reason: 'hub_unavailable' }));
369
+ }
370
+ }
371
+
281
372
  async function cmdContext(args) {
282
- const result = await runPipeFirst(null, 'drain', '/bridge/context', {
373
+ const outcome = await requestHub(HUB_OPERATIONS.context, {
283
374
  agent_id: args.agent,
284
375
  topics: args.topics ? args.topics.split(',') : undefined,
285
376
  max_messages: parseInt(args.max || '10', 10),
286
377
  auto_ack: true,
287
378
  });
379
+ const result = outcome?.result;
288
380
 
289
381
  if (result?.ok && result.data?.messages?.length) {
290
382
  const parts = result.data.messages.map((message, index) => {
@@ -308,9 +400,10 @@ async function cmdContext(args) {
308
400
  }
309
401
 
310
402
  async function cmdDeregister(args) {
311
- const result = await runPipeFirst('deregister', null, '/bridge/deregister', {
403
+ const outcome = await requestHub(HUB_OPERATIONS.deregister, {
312
404
  agent_id: args.agent,
313
405
  });
406
+ const result = outcome?.result;
314
407
 
315
408
  if (result?.ok) {
316
409
  console.log(JSON.stringify({ ok: true, agent_id: args.agent, status: 'offline' }));
@@ -319,20 +412,87 @@ async function cmdDeregister(args) {
319
412
  }
320
413
  }
321
414
 
415
+ async function cmdAssignAsync(args) {
416
+ const outcome = await requestHub(HUB_OPERATIONS.assignAsync, {
417
+ supervisor_agent: args['supervisor-agent'],
418
+ worker_agent: args['worker-agent'],
419
+ task: args.task,
420
+ topic: args.topic || 'assign.job',
421
+ payload: args.payload ? parseJsonSafe(args.payload, {}) : {},
422
+ priority: args.priority != null ? Number(args.priority) : undefined,
423
+ ttl_ms: args['ttl-ms'] != null ? Number(args['ttl-ms']) : undefined,
424
+ timeout_ms: args['timeout-ms'] != null ? Number(args['timeout-ms']) : undefined,
425
+ max_retries: args['max-retries'] != null ? Number(args['max-retries']) : undefined,
426
+ trace_id: args.trace || undefined,
427
+ correlation_id: args.correlation || undefined,
428
+ });
429
+ const result = outcome?.result;
430
+ if (result) {
431
+ console.log(JSON.stringify(result));
432
+ } else {
433
+ console.log(JSON.stringify({ ok: false, reason: 'hub_unavailable' }));
434
+ }
435
+ }
436
+
437
+ async function cmdAssignResult(args) {
438
+ const outcome = await requestHub(HUB_OPERATIONS.assignResult, {
439
+ job_id: args['job-id'],
440
+ worker_agent: args['worker-agent'],
441
+ status: args.status,
442
+ attempt: args.attempt != null ? Number(args.attempt) : undefined,
443
+ result: args.result ? parseJsonSafe(args.result, null) : undefined,
444
+ error: args.error ? parseJsonSafe(args.error, null) : undefined,
445
+ payload: args.payload ? parseJsonSafe(args.payload, {}) : {},
446
+ metadata: args.metadata ? parseJsonSafe(args.metadata, {}) : {},
447
+ });
448
+ const result = outcome?.result;
449
+ if (result) {
450
+ console.log(JSON.stringify(result));
451
+ } else {
452
+ console.log(JSON.stringify({ ok: false, reason: 'hub_unavailable' }));
453
+ }
454
+ }
455
+
456
+ async function cmdAssignStatus(args) {
457
+ const outcome = await requestHub(HUB_OPERATIONS.assignStatus, {
458
+ job_id: args['job-id'],
459
+ });
460
+ const result = outcome?.result;
461
+ if (result) {
462
+ console.log(JSON.stringify(result));
463
+ } else {
464
+ console.log(JSON.stringify({ ok: false, reason: 'hub_unavailable' }));
465
+ }
466
+ }
467
+
468
+ async function cmdAssignRetry(args) {
469
+ const outcome = await requestHub(HUB_OPERATIONS.assignRetry, {
470
+ job_id: args['job-id'],
471
+ reason: args.reason,
472
+ requested_by: args['requested-by'],
473
+ });
474
+ const result = outcome?.result;
475
+ if (result) {
476
+ console.log(JSON.stringify(result));
477
+ } else {
478
+ console.log(JSON.stringify({ ok: false, reason: 'hub_unavailable' }));
479
+ }
480
+ }
481
+
322
482
  async function cmdTeamInfo(args) {
323
483
  const body = {
324
484
  team_name: args.team,
325
485
  include_members: true,
326
486
  include_paths: true,
327
487
  };
328
- const result = await post('/bridge/team/info', body);
488
+ const outcome = await requestHub(HUB_OPERATIONS.teamInfo, body, 3000, async () => {
489
+ const { teamInfo } = await import('./team/nativeProxy.mjs');
490
+ return await teamInfo(body);
491
+ });
492
+ const result = outcome?.result;
329
493
  if (result) {
330
494
  console.log(JSON.stringify(result));
331
- return;
332
495
  }
333
- // Hub 미실행 fallback — nativeProxy 직접 호출
334
- const { teamInfo } = await import('./team/nativeProxy.mjs');
335
- console.log(JSON.stringify(await teamInfo(body)));
336
496
  }
337
497
 
338
498
  async function cmdTeamTaskList(args) {
@@ -343,14 +503,14 @@ async function cmdTeamTaskList(args) {
343
503
  include_internal: !!args['include-internal'],
344
504
  limit: parseInt(args.limit || '200', 10),
345
505
  };
346
- const result = await post('/bridge/team/task-list', body);
506
+ const outcome = await requestHub(HUB_OPERATIONS.teamTaskList, body, 3000, async () => {
507
+ const { teamTaskList } = await import('./team/nativeProxy.mjs');
508
+ return await teamTaskList(body);
509
+ });
510
+ const result = outcome?.result;
347
511
  if (result) {
348
512
  console.log(JSON.stringify(result));
349
- return;
350
513
  }
351
- // Hub 미실행 fallback — nativeProxy 직접 호출
352
- const { teamTaskList } = await import('./team/nativeProxy.mjs');
353
- console.log(JSON.stringify(await teamTaskList(body)));
354
514
  }
355
515
 
356
516
  async function cmdTeamTaskUpdate(args) {
@@ -369,14 +529,14 @@ async function cmdTeamTaskUpdate(args) {
369
529
  if_match_mtime_ms: args['if-match-mtime-ms'] != null ? Number(args['if-match-mtime-ms']) : undefined,
370
530
  actor: args.actor,
371
531
  };
372
- const result = await post('/bridge/team/task-update', body);
532
+ const outcome = await requestHub(HUB_OPERATIONS.teamTaskUpdate, body, 3000, async () => {
533
+ const { teamTaskUpdate } = await import('./team/nativeProxy.mjs');
534
+ return await teamTaskUpdate(body);
535
+ });
536
+ const result = outcome?.result;
373
537
  if (result) {
374
538
  console.log(JSON.stringify(result));
375
- return;
376
539
  }
377
- // Hub 미실행 fallback — nativeProxy 직접 호출
378
- const { teamTaskUpdate } = await import('./team/nativeProxy.mjs');
379
- console.log(JSON.stringify(await teamTaskUpdate(body)));
380
540
  }
381
541
 
382
542
  async function cmdTeamSendMessage(args) {
@@ -388,14 +548,14 @@ async function cmdTeamSendMessage(args) {
388
548
  summary: args.summary,
389
549
  color: args.color || 'blue',
390
550
  };
391
- const result = await post('/bridge/team/send-message', body);
551
+ const outcome = await requestHub(HUB_OPERATIONS.teamSendMessage, body, 3000, async () => {
552
+ const { teamSendMessage } = await import('./team/nativeProxy.mjs');
553
+ return await teamSendMessage(body);
554
+ });
555
+ const result = outcome?.result;
392
556
  if (result) {
393
557
  console.log(JSON.stringify(result));
394
- return;
395
558
  }
396
- // Hub 미실행 fallback — nativeProxy 직접 호출
397
- const { teamSendMessage } = await import('./team/nativeProxy.mjs');
398
- console.log(JSON.stringify(await teamSendMessage(body)));
399
559
  }
400
560
 
401
561
  function getHubDbPath() {
@@ -403,80 +563,98 @@ function getHubDbPath() {
403
563
  }
404
564
 
405
565
  async function cmdPipelineState(args) {
406
- // HTTP 우선
407
- const result = await post('/bridge/pipeline/state', { team_name: args.team });
566
+ const outcome = await requestHub(HUB_OPERATIONS.pipelineState, { team_name: args.team }, 3000, async () => {
567
+ try {
568
+ const { default: Database } = await import('better-sqlite3');
569
+ const { ensurePipelineTable, readPipelineState } = await import('./pipeline/state.mjs');
570
+ const dbPath = getHubDbPath();
571
+ if (!existsSync(dbPath)) {
572
+ return { ok: false, error: 'hub_db_not_found' };
573
+ }
574
+ const db = new Database(dbPath, { readonly: true });
575
+ ensurePipelineTable(db);
576
+ const state = readPipelineState(db, args.team);
577
+ db.close();
578
+ return state
579
+ ? { ok: true, data: state }
580
+ : { ok: false, error: 'pipeline_not_found' };
581
+ } catch (e) {
582
+ return { ok: false, error: e.message };
583
+ }
584
+ });
585
+ const result = outcome?.result;
408
586
  if (result) {
409
587
  console.log(JSON.stringify(result));
410
- return;
411
- }
412
- // Hub 미실행 fallback — 직접 SQLite 접근
413
- try {
414
- const { default: Database } = await import('better-sqlite3');
415
- const { ensurePipelineTable, readPipelineState } = await import('./pipeline/state.mjs');
416
- const dbPath = getHubDbPath();
417
- if (!existsSync(dbPath)) {
418
- console.log(JSON.stringify({ ok: false, error: 'hub_db_not_found' }));
419
- return;
420
- }
421
- const db = new Database(dbPath, { readonly: true });
422
- ensurePipelineTable(db);
423
- const state = readPipelineState(db, args.team);
424
- db.close();
425
- console.log(JSON.stringify(state
426
- ? { ok: true, data: state }
427
- : { ok: false, error: 'pipeline_not_found' }));
428
- } catch (e) {
429
- console.log(JSON.stringify({ ok: false, error: e.message }));
430
588
  }
431
589
  }
432
590
 
433
591
  async function cmdPipelineAdvance(args) {
434
- // HTTP 우선
435
- const result = await post('/bridge/pipeline/advance', {
592
+ const body = {
436
593
  team_name: args.team,
437
594
  phase: args.status, // --status를 phase로 재활용
595
+ };
596
+ const outcome = await requestHub(HUB_OPERATIONS.pipelineAdvance, body, 3000, async () => {
597
+ try {
598
+ const { default: Database } = await import('better-sqlite3');
599
+ const { createPipeline } = await import('./pipeline/index.mjs');
600
+ const dbPath = getHubDbPath();
601
+ if (!existsSync(dbPath)) {
602
+ return { ok: false, error: 'hub_db_not_found' };
603
+ }
604
+ const db = new Database(dbPath);
605
+ const pipeline = createPipeline(db, args.team);
606
+ const advanceResult = pipeline.advance(args.status);
607
+ db.close();
608
+ return advanceResult;
609
+ } catch (e) {
610
+ return { ok: false, error: e.message };
611
+ }
438
612
  });
613
+ const result = outcome?.result;
439
614
  if (result) {
440
615
  console.log(JSON.stringify(result));
441
- return;
442
616
  }
443
- // Hub 미실행 fallback — 직접 SQLite 접근
444
- try {
445
- const { default: Database } = await import('better-sqlite3');
446
- const { createPipeline } = await import('./pipeline/index.mjs');
447
- const dbPath = getHubDbPath();
448
- if (!existsSync(dbPath)) {
449
- console.log(JSON.stringify({ ok: false, error: 'hub_db_not_found' }));
450
- return;
451
- }
452
- const db = new Database(dbPath);
453
- const pipeline = createPipeline(db, args.team);
454
- const advanceResult = pipeline.advance(args.status);
455
- db.close();
456
- console.log(JSON.stringify(advanceResult));
457
- } catch (e) {
458
- console.log(JSON.stringify({ ok: false, error: e.message }));
617
+ }
618
+
619
+ async function cmdPipelineInit(args) {
620
+ const outcome = await requestHub(HUB_OPERATIONS.pipelineInit, {
621
+ team_name: args.team,
622
+ fix_max: args['fix-max'] != null ? Number(args['fix-max']) : undefined,
623
+ ralph_max: args['ralph-max'] != null ? Number(args['ralph-max']) : undefined,
624
+ });
625
+ const result = outcome?.result;
626
+ if (result) {
627
+ console.log(JSON.stringify(result));
628
+ } else {
629
+ console.log(JSON.stringify({ ok: false, reason: 'hub_unavailable' }));
630
+ }
631
+ }
632
+
633
+ async function cmdPipelineList() {
634
+ const outcome = await requestHub(HUB_OPERATIONS.pipelineList, {});
635
+ const result = outcome?.result;
636
+ if (result) {
637
+ console.log(JSON.stringify(result));
638
+ } else {
639
+ console.log(JSON.stringify({ ok: false, reason: 'hub_unavailable' }));
459
640
  }
460
641
  }
461
642
 
462
643
  async function cmdPing() {
463
- const viaPipe = await pipeQuery('status', { scope: 'hub' }, 2000);
464
- if (viaPipe?.ok) {
644
+ const outcome = await requestHub(HUB_OPERATIONS.hubStatus, { scope: 'hub' }, 2000);
645
+
646
+ if (outcome?.transport === 'pipe' && outcome.result?.ok) {
465
647
  console.log(JSON.stringify({
466
648
  ok: true,
467
- hub: viaPipe.data?.hub?.state || 'healthy',
649
+ hub: outcome.result.data?.hub?.state || 'healthy',
468
650
  pipe_path: getHubPipePath(),
469
651
  transport: 'pipe',
470
652
  }));
471
653
  return;
472
654
  }
473
655
 
474
- try {
475
- const controller = new AbortController();
476
- const timer = setTimeout(() => controller.abort(), 3000);
477
- const res = await fetch(`${getHubUrl()}/status`, { signal: controller.signal });
478
- clearTimeout(timer);
479
- const data = await res.json();
656
+ if (outcome?.transport === 'http' && outcome.result) {
657
+ const data = outcome.result;
480
658
  console.log(JSON.stringify({
481
659
  ok: true,
482
660
  hub: data.hub?.state,
@@ -484,9 +662,10 @@ async function cmdPing() {
484
662
  pipe_path: data.pipe?.path || data.pipe_path || null,
485
663
  transport: 'http',
486
664
  }));
487
- } catch {
488
- console.log(JSON.stringify({ ok: false, reason: 'hub_unavailable' }));
665
+ return;
489
666
  }
667
+
668
+ console.log(JSON.stringify({ ok: false, reason: 'hub_unavailable' }));
490
669
  }
491
670
 
492
671
  export async function main(argv = process.argv.slice(2)) {
@@ -496,17 +675,24 @@ export async function main(argv = process.argv.slice(2)) {
496
675
  switch (cmd) {
497
676
  case 'register': await cmdRegister(args); break;
498
677
  case 'result': await cmdResult(args); break;
678
+ case 'control': await cmdControl(args); break;
499
679
  case 'context': await cmdContext(args); break;
500
680
  case 'deregister': await cmdDeregister(args); break;
681
+ case 'assign-async': await cmdAssignAsync(args); break;
682
+ case 'assign-result': await cmdAssignResult(args); break;
683
+ case 'assign-status': await cmdAssignStatus(args); break;
684
+ case 'assign-retry': await cmdAssignRetry(args); break;
501
685
  case 'team-info': await cmdTeamInfo(args); break;
502
686
  case 'team-task-list': await cmdTeamTaskList(args); break;
503
687
  case 'team-task-update': await cmdTeamTaskUpdate(args); break;
504
688
  case 'team-send-message': await cmdTeamSendMessage(args); break;
505
689
  case 'pipeline-state': await cmdPipelineState(args); break;
506
690
  case 'pipeline-advance': await cmdPipelineAdvance(args); break;
691
+ case 'pipeline-init': await cmdPipelineInit(args); break;
692
+ case 'pipeline-list': await cmdPipelineList(args); break;
507
693
  case 'ping': await cmdPing(args); break;
508
694
  default:
509
- console.error('사용법: bridge.mjs <register|result|context|deregister|team-info|team-task-list|team-task-update|team-send-message|pipeline-state|pipeline-advance|ping> [--옵션]');
695
+ 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> [--옵션]');
510
696
  process.exit(1);
511
697
  }
512
698
  }