triflux 3.3.0-dev.3 → 3.3.0-dev.6

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