@triflux/core 10.1.0 → 10.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/hooks/agent-route-guard.mjs +14 -10
  2. package/hooks/cross-review-tracker.mjs +21 -8
  3. package/hooks/error-context.mjs +20 -8
  4. package/hooks/hook-adaptive-collector.mjs +18 -16
  5. package/hooks/hook-manager.mjs +93 -32
  6. package/hooks/hook-orchestrator.mjs +267 -35
  7. package/hooks/hook-registry.json +343 -256
  8. package/hooks/hooks.json +49 -1
  9. package/hooks/keyword-rules.json +183 -23
  10. package/hooks/lib/resolve-root.mjs +1 -1
  11. package/hooks/mcp-config-watcher.mjs +6 -2
  12. package/hooks/permission-safe-allow.mjs +233 -0
  13. package/hooks/pipeline-stop.mjs +369 -22
  14. package/hooks/post-tool-tips.mjs +306 -0
  15. package/hooks/pre-compact-snapshot.mjs +181 -0
  16. package/hooks/safety-guard.mjs +254 -33
  17. package/hooks/session-end-cleanup.mjs +345 -0
  18. package/hooks/session-start-fast.mjs +214 -0
  19. package/hooks/subagent-tracker.mjs +267 -0
  20. package/hooks/subagent-verifier.mjs +9 -4
  21. package/hub/account-broker.mjs +1036 -66
  22. package/hub/adaptive-diagnostic.mjs +75 -48
  23. package/hub/adaptive-inject.mjs +96 -58
  24. package/hub/adaptive-memory.mjs +156 -42
  25. package/hub/adaptive.mjs +60 -31
  26. package/hub/assign-callbacks.mjs +163 -126
  27. package/hub/bridge.mjs +831 -290
  28. package/hub/cli-adapter-base.mjs +321 -97
  29. package/hub/codex-adapter.mjs +179 -93
  30. package/hub/codex-compat.mjs +4 -4
  31. package/hub/codex-preflight.mjs +113 -37
  32. package/hub/delegator/contracts.mjs +19 -23
  33. package/hub/delegator/index.mjs +14 -14
  34. package/hub/delegator/service.mjs +88 -64
  35. package/hub/delegator/tool-definitions.mjs +35 -35
  36. package/hub/dynamic-routing-engine.mjs +511 -0
  37. package/hub/fullcycle.mjs +34 -18
  38. package/hub/gemini-adapter.mjs +77 -95
  39. package/hub/hitl.mjs +202 -143
  40. package/hub/intent.mjs +167 -38
  41. package/hub/lib/bash-path.mjs +73 -0
  42. package/hub/lib/cache-guard.mjs +43 -17
  43. package/hub/lib/env-detect.mjs +145 -0
  44. package/hub/lib/hosts-compat.mjs +319 -0
  45. package/hub/lib/mcp-response-cache.mjs +239 -0
  46. package/hub/lib/memory-store.mjs +285 -111
  47. package/hub/lib/path-utils.mjs +165 -0
  48. package/hub/lib/phase-manager.mjs +493 -0
  49. package/hub/lib/process-utils.mjs +797 -104
  50. package/hub/lib/prompt-tmp.mjs +33 -0
  51. package/hub/lib/risk-tier.mjs +53 -0
  52. package/hub/lib/spawn-trace.mjs +561 -0
  53. package/hub/lib/ssh-command.mjs +44 -24
  54. package/hub/lib/ssh-retry.mjs +5 -1
  55. package/hub/lib/staged-file-detect.mjs +43 -0
  56. package/hub/lib/state-snapshot.mjs +293 -0
  57. package/hub/lib/tfx-route-args.mjs +309 -0
  58. package/hub/lib/timeline-adapter.mjs +190 -0
  59. package/hub/lib/trace-recorder.mjs +153 -0
  60. package/hub/lib/uuidv7.mjs +4 -3
  61. package/hub/memory-doctor.mjs +266 -106
  62. package/hub/middleware/quota-middleware.mjs +70 -0
  63. package/hub/middleware/request-logger.mjs +61 -34
  64. package/hub/paths.mjs +9 -9
  65. package/hub/pipeline/gates/confidence.mjs +34 -15
  66. package/hub/pipeline/gates/consensus.mjs +27 -15
  67. package/hub/pipeline/gates/index.mjs +7 -3
  68. package/hub/pipeline/gates/selfcheck.mjs +57 -19
  69. package/hub/pipeline/index.mjs +77 -42
  70. package/hub/pipeline/state.mjs +10 -10
  71. package/hub/pipeline/transitions.mjs +40 -23
  72. package/hub/platform.mjs +65 -48
  73. package/hub/quality/deslop.mjs +70 -49
  74. package/hub/reflexion.mjs +87 -13
  75. package/hub/research.mjs +33 -25
  76. package/hub/router.mjs +941 -791
  77. package/hub/routing/complexity.mjs +132 -29
  78. package/hub/routing/index.mjs +17 -12
  79. package/hub/routing/q-learning.mjs +76 -28
  80. package/hub/routing-snapshot.mjs +261 -0
  81. package/hub/session-fingerprint.mjs +127 -61
  82. package/hub/state.mjs +86 -44
  83. package/hub/team/retry-state-machine.mjs +267 -0
  84. package/hub/token-mode.mjs +72 -71
  85. package/hub/workers/worker-utils.mjs +22 -16
  86. package/hud/colors.mjs +27 -9
  87. package/hud/constants.mjs +184 -27
  88. package/hud/context-monitor.mjs +133 -44
  89. package/hud/hud-qos-status.mjs +153 -50
  90. package/hud/mission-board.mjs +56 -0
  91. package/hud/providers/claude.mjs +398 -114
  92. package/hud/providers/codex.mjs +96 -21
  93. package/hud/providers/gemini.mjs +184 -51
  94. package/hud/renderers.mjs +413 -123
  95. package/hud/terminal.mjs +101 -31
  96. package/hud/utils.mjs +81 -39
  97. package/mesh/index.mjs +69 -0
  98. package/mesh/mesh-budget.mjs +137 -0
  99. package/mesh/mesh-heartbeat.mjs +100 -0
  100. package/mesh/mesh-protocol.mjs +96 -0
  101. package/mesh/mesh-queue.mjs +163 -0
  102. package/mesh/mesh-registry.mjs +78 -0
  103. package/mesh/mesh-router.mjs +77 -0
  104. package/package.json +5 -1
  105. package/scripts/lib/agent-json.mjs +27 -0
  106. package/scripts/lib/async.mjs +174 -0
  107. package/scripts/lib/claudemd-scanner.mjs +6 -1
  108. package/scripts/lib/cli-agy.mjs +62 -0
  109. package/scripts/lib/cli-claude.mjs +78 -0
  110. package/scripts/lib/cli-codex.mjs +199 -0
  111. package/scripts/lib/cli-gemini.mjs +67 -0
  112. package/scripts/lib/codex-recovery.sh +50 -0
  113. package/scripts/lib/context.mjs +3 -3
  114. package/scripts/lib/cross-review-utils.mjs +13 -5
  115. package/scripts/lib/doctor-env-checks.mjs +121 -0
  116. package/scripts/lib/dynamic-route-cli.mjs +107 -0
  117. package/scripts/lib/env-probe.mjs +58 -30
  118. package/scripts/lib/env.mjs +14 -0
  119. package/scripts/lib/gemini-profiles.mjs +44 -10
  120. package/scripts/lib/handoff.mjs +33 -17
  121. package/scripts/lib/hook-utils.mjs +8 -6
  122. package/scripts/lib/hub.mjs +48 -0
  123. package/scripts/lib/keyword-rules.mjs +48 -19
  124. package/scripts/lib/logger.mjs +24 -24
  125. package/scripts/lib/mcp-filter.mjs +391 -247
  126. package/scripts/lib/mcp-gateway-health-check.mjs +159 -0
  127. package/scripts/lib/mcp-gateway-servers.mjs +70 -0
  128. package/scripts/lib/mcp-gateway-wrapper-check.mjs +35 -0
  129. package/scripts/lib/mcp-guard-engine.mjs +1281 -115
  130. package/scripts/lib/mcp-health.mjs +773 -0
  131. package/scripts/lib/mcp-manifest.mjs +24 -14
  132. package/scripts/lib/mcp-server-catalog.mjs +355 -118
  133. package/scripts/lib/pid.mjs +63 -0
  134. package/scripts/lib/process-utils.mjs +26 -0
  135. package/scripts/lib/psmux-info.mjs +70 -10
  136. package/scripts/lib/quota.mjs +47 -0
  137. package/scripts/lib/remote-spawn-transfer.mjs +44 -14
  138. package/scripts/lib/skill-state.mjs +220 -0
  139. package/scripts/lib/skill-template.mjs +32 -7
  140. package/scripts/lib/team.mjs +75 -0
  141. package/scripts/lib/timeout.mjs +66 -0
  142. package/scripts/lib/tmp.mjs +26 -0
  143. package/scripts/lib/toml.mjs +113 -0
package/hub/bridge.mjs CHANGED
@@ -1,23 +1,47 @@
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';
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
+ import {
25
+ createRetryStateMachine,
26
+ loadSnapshot,
27
+ saveSnapshot,
28
+ } from "./team/retry-state-machine.mjs";
29
+
30
+ const HUB_PID_FILE = join(homedir(), ".claude", "cache", "tfx-hub", "hub.pid");
31
+ const HUB_TOKEN_FILE = join(homedir(), ".claude", ".tfx-hub-token");
32
+ const PROJECT_ROOT = fileURLToPath(new URL("..", import.meta.url));
33
+ const HUB_DEFAULT_PORT = 27888;
34
+ const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1"]);
35
+
36
+ function formatHostForUrl(host) {
37
+ return host.includes(":") ? `[${host}]` : host;
38
+ }
17
39
 
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));
40
+ function normalizeLoopbackHost(host) {
41
+ if (typeof host !== "string") return "127.0.0.1";
42
+ const candidate = host.trim();
43
+ return LOOPBACK_HOSTS.has(candidate) ? candidate : "127.0.0.1";
44
+ }
21
45
 
22
46
  function normalizeToken(raw) {
23
47
  if (raw == null) return null;
@@ -30,26 +54,31 @@ function readHubToken() {
30
54
  const envToken = normalizeToken(process.env.TFX_HUB_TOKEN);
31
55
  if (envToken) return envToken;
32
56
  try {
33
- return normalizeToken(readFileSync(HUB_TOKEN_FILE, 'utf8'));
57
+ return normalizeToken(readFileSync(HUB_TOKEN_FILE, "utf8"));
34
58
  } catch {
35
59
  return null;
36
60
  }
37
61
  }
38
62
 
39
63
  export function getHubUrl() {
40
- if (process.env.TFX_HUB_URL) return process.env.TFX_HUB_URL.replace(/\/mcp$/, '');
64
+ if (process.env.TFX_HUB_URL)
65
+ return process.env.TFX_HUB_URL.replace(/\/mcp$/, "");
66
+
67
+ const envPort = Number.parseInt(String(process.env.TFX_HUB_PORT ?? ""), 10);
68
+ const port =
69
+ Number.isFinite(envPort) && envPort > 0 ? envPort : HUB_DEFAULT_PORT;
70
+ let host = "127.0.0.1";
41
71
 
42
72
  if (existsSync(HUB_PID_FILE)) {
43
73
  try {
44
- const info = JSON.parse(readFileSync(HUB_PID_FILE, 'utf8'));
45
- return `http://${info.host || '127.0.0.1'}:${info.port || 27888}`;
74
+ const info = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
75
+ host = normalizeLoopbackHost(info?.host);
46
76
  } catch {
47
77
  // 무시
48
78
  }
49
79
  }
50
80
 
51
- const port = process.env.TFX_HUB_PORT || '27888';
52
- return `http://127.0.0.1:${port}`;
81
+ return `http://${formatHostForUrl(host)}:${port}`;
53
82
  }
54
83
 
55
84
  export function getHubPipePath() {
@@ -57,7 +86,7 @@ export function getHubPipePath() {
57
86
 
58
87
  if (!existsSync(HUB_PID_FILE)) return null;
59
88
  try {
60
- const info = JSON.parse(readFileSync(HUB_PID_FILE, 'utf8'));
89
+ const info = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
61
90
  return info.pipe_path || info.pipePath || null;
62
91
  } catch {
63
92
  return null;
@@ -65,30 +94,144 @@ export function getHubPipePath() {
65
94
  }
66
95
 
67
96
  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' },
97
+ register: {
98
+ transport: "command",
99
+ action: "register",
100
+ httpPath: "/bridge/register",
101
+ },
102
+ result: {
103
+ transport: "command",
104
+ action: "result",
105
+ httpPath: "/bridge/result",
106
+ },
107
+ control: {
108
+ transport: "command",
109
+ action: "control",
110
+ httpPath: "/bridge/control",
111
+ },
112
+ handoff: {
113
+ transport: "command",
114
+ action: "handoff",
115
+ httpPath: "/bridge/handoff",
116
+ },
117
+ publish: {
118
+ transport: "command",
119
+ action: "publish",
120
+ httpPath: "/bridge/publish",
121
+ },
122
+ sendInput: {
123
+ transport: "command",
124
+ action: "send_input",
125
+ httpPath: "/bridge/send-input",
126
+ },
127
+ context: { transport: "query", action: "drain", httpPath: "/bridge/context" },
128
+ deregister: {
129
+ transport: "command",
130
+ action: "deregister",
131
+ httpPath: "/bridge/deregister",
132
+ },
133
+ assignAsync: {
134
+ transport: "command",
135
+ action: "assign",
136
+ httpPath: "/bridge/assign/async",
137
+ },
138
+ assignResult: {
139
+ transport: "command",
140
+ action: "assign_result",
141
+ httpPath: "/bridge/assign/result",
142
+ },
143
+ assignStatus: {
144
+ transport: "query",
145
+ action: "assign_status",
146
+ httpPath: "/bridge/assign/status",
147
+ },
148
+ assignRetry: {
149
+ transport: "command",
150
+ action: "assign_retry",
151
+ httpPath: "/bridge/assign/retry",
152
+ },
153
+ teamInfo: {
154
+ transport: "query",
155
+ action: "team_info",
156
+ httpPath: "/bridge/team/info",
157
+ },
158
+ teamTaskList: {
159
+ transport: "query",
160
+ action: "team_task_list",
161
+ httpPath: "/bridge/team/task-list",
162
+ },
163
+ teamTaskUpdate: {
164
+ transport: "command",
165
+ action: "team_task_update",
166
+ httpPath: "/bridge/team/task-update",
167
+ },
168
+ teamSendMessage: {
169
+ transport: "command",
170
+ action: "team_send_message",
171
+ httpPath: "/bridge/team/send-message",
172
+ },
173
+ pipelineState: {
174
+ transport: "query",
175
+ action: "pipeline_state",
176
+ httpPath: "/bridge/pipeline/state",
177
+ },
178
+ pipelineAdvance: {
179
+ transport: "command",
180
+ action: "pipeline_advance",
181
+ httpPath: "/bridge/pipeline/advance",
182
+ },
183
+ pipelineInit: {
184
+ transport: "command",
185
+ action: "pipeline_init",
186
+ httpPath: "/bridge/pipeline/init",
187
+ },
188
+ pipelineList: {
189
+ transport: "query",
190
+ action: "pipeline_list",
191
+ httpPath: "/bridge/pipeline/list",
192
+ },
193
+ hubStatus: {
194
+ transport: "query",
195
+ action: "status",
196
+ httpPath: "/status",
197
+ httpMethod: "GET",
198
+ },
199
+ delegatorDelegate: {
200
+ transport: "command",
201
+ action: "delegator_delegate",
202
+ httpPath: "/bridge/delegator/delegate",
203
+ },
204
+ delegatorReply: {
205
+ transport: "command",
206
+ action: "delegator_reply",
207
+ httpPath: "/bridge/delegator/reply",
208
+ },
209
+ delegatorStatus: {
210
+ transport: "query",
211
+ action: "delegator_status",
212
+ httpPath: "/bridge/delegator/status",
213
+ },
214
+ "hitl-request": {
215
+ transport: "command",
216
+ action: "hitl_request",
217
+ httpPath: "/bridge/hitl/request",
218
+ },
219
+ "hitl-submit": {
220
+ transport: "command",
221
+ action: "hitl_submit",
222
+ httpPath: "/bridge/hitl/submit",
223
+ },
224
+ "hitl-pending": {
225
+ transport: "query",
226
+ action: "hitl_pending",
227
+ httpPath: "/bridge/hitl/pending",
228
+ },
89
229
  });
90
230
 
91
- export async function requestJson(path, { method = 'POST', body, timeoutMs = 5000 } = {}) {
231
+ export async function requestJson(
232
+ path,
233
+ { method = "POST", body, timeoutMs = 5000 } = {},
234
+ ) {
92
235
  const controller = new AbortController();
93
236
  const timer = setTimeout(() => controller.abort(), timeoutMs);
94
237
 
@@ -99,7 +242,7 @@ export async function requestJson(path, { method = 'POST', body, timeoutMs = 500
99
242
  headers.Authorization = `Bearer ${token}`;
100
243
  }
101
244
  if (body !== undefined) {
102
- headers['Content-Type'] = 'application/json';
245
+ headers["Content-Type"] = "application/json";
103
246
  }
104
247
 
105
248
  const res = await fetch(`${getHubUrl()}${path}`, {
@@ -117,7 +260,7 @@ export async function requestJson(path, { method = 'POST', body, timeoutMs = 500
117
260
  }
118
261
 
119
262
  export async function post(path, body, timeoutMs = 5000) {
120
- return await requestJson(path, { method: 'POST', body, timeoutMs });
263
+ return await requestJson(path, { method: "POST", body, timeoutMs });
121
264
  }
122
265
 
123
266
  export async function connectPipe(timeoutMs = 1200) {
@@ -127,19 +270,23 @@ export async function connectPipe(timeoutMs = 1200) {
127
270
  return await new Promise((resolve) => {
128
271
  const socket = net.createConnection(pipePath);
129
272
  const timer = setTimeout(() => {
130
- try { socket.destroy(); } catch {}
273
+ try {
274
+ socket.destroy();
275
+ } catch {}
131
276
  resolve(null);
132
277
  }, timeoutMs);
133
278
 
134
- socket.once('connect', () => {
279
+ socket.once("connect", () => {
135
280
  clearTimeout(timer);
136
- socket.setEncoding('utf8');
281
+ socket.setEncoding("utf8");
137
282
  resolve(socket);
138
283
  });
139
284
 
140
- socket.once('error', () => {
285
+ socket.once("error", () => {
141
286
  clearTimeout(timer);
142
- try { socket.destroy(); } catch {}
287
+ try {
288
+ socket.destroy();
289
+ } catch {}
143
290
  resolve(null);
144
291
  });
145
292
  });
@@ -151,7 +298,7 @@ async function pipeRequest(type, action, payload, timeoutMs = 3000) {
151
298
 
152
299
  return await new Promise((resolve) => {
153
300
  const requestId = randomUUID();
154
- let buffer = '';
301
+ let buffer = "";
155
302
  let settled = false;
156
303
  const timer = setTimeout(() => {
157
304
  finish(null);
@@ -161,17 +308,19 @@ async function pipeRequest(type, action, payload, timeoutMs = 3000) {
161
308
  if (settled) return;
162
309
  settled = true;
163
310
  clearTimeout(timer);
164
- try { socket.end(); } catch {}
311
+ try {
312
+ socket.end();
313
+ } catch {}
165
314
  resolve(result);
166
315
  };
167
316
 
168
- socket.on('data', (chunk) => {
317
+ socket.on("data", (chunk) => {
169
318
  buffer += chunk;
170
- let newlineIndex = buffer.indexOf('\n');
319
+ let newlineIndex = buffer.indexOf("\n");
171
320
  while (newlineIndex >= 0) {
172
321
  const line = buffer.slice(0, newlineIndex).trim();
173
322
  buffer = buffer.slice(newlineIndex + 1);
174
- newlineIndex = buffer.indexOf('\n');
323
+ newlineIndex = buffer.indexOf("\n");
175
324
  if (!line) continue;
176
325
 
177
326
  let frame;
@@ -181,7 +330,8 @@ async function pipeRequest(type, action, payload, timeoutMs = 3000) {
181
330
  continue;
182
331
  }
183
332
 
184
- if (frame?.type !== 'response' || frame.request_id !== requestId) continue;
333
+ if (frame?.type !== "response" || frame.request_id !== requestId)
334
+ continue;
185
335
  finish({
186
336
  ok: frame.ok,
187
337
  error: frame.error,
@@ -191,89 +341,102 @@ async function pipeRequest(type, action, payload, timeoutMs = 3000) {
191
341
  }
192
342
  });
193
343
 
194
- socket.on('error', () => finish(null));
195
- socket.write(JSON.stringify({
196
- type,
197
- request_id: requestId,
198
- payload: { action, ...payload },
199
- }) + '\n');
344
+ socket.on("error", () => finish(null));
345
+ socket.write(
346
+ JSON.stringify({
347
+ type,
348
+ request_id: requestId,
349
+ payload: { action, ...payload },
350
+ }) + "\n",
351
+ );
200
352
  });
201
353
  }
202
354
 
203
355
  async function pipeCommand(action, payload, timeoutMs = 3000) {
204
- return await pipeRequest('command', action, payload, timeoutMs);
356
+ return await pipeRequest("command", action, payload, timeoutMs);
205
357
  }
206
358
 
207
359
  async function pipeQuery(action, payload, timeoutMs = 3000) {
208
- return await pipeRequest('query', action, payload, timeoutMs);
360
+ return await pipeRequest("query", action, payload, timeoutMs);
209
361
  }
210
362
 
211
363
  export function parseArgs(argv) {
212
- const { values } = nodeParseArgs({
364
+ const { values, positionals } = nodeParseArgs({
213
365
  args: argv,
214
366
  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' },
367
+ agent: { type: "string" },
368
+ cli: { type: "string" },
369
+ timeout: { type: "string" },
370
+ topics: { type: "string" },
371
+ capabilities: { type: "string" },
372
+ file: { type: "string" },
373
+ payload: { type: "string" },
374
+ topic: { type: "string" },
375
+ trace: { type: "string" },
376
+ correlation: { type: "string" },
377
+ "exit-code": { type: "string" },
378
+ max: { type: "string" },
379
+ out: { type: "string" },
380
+ team: { type: "string" },
381
+ "task-id": { type: "string" },
382
+ "job-id": { type: "string" },
383
+ owner: { type: "string" },
384
+ status: { type: "string" },
385
+ statuses: { type: "string" },
386
+ claim: { type: "boolean" },
387
+ actor: { type: "string" },
388
+ command: { type: "string" },
389
+ "session-id": { type: "string" },
390
+ reason: { type: "string" },
391
+ type: { type: "string" },
392
+ from: { type: "string" },
393
+ to: { type: "string" },
394
+ text: { type: "string" },
395
+ task: { type: "string" },
396
+ "supervisor-agent": { type: "string" },
397
+ "worker-agent": { type: "string" },
398
+ priority: { type: "string" },
399
+ "ttl-ms": { type: "string" },
400
+ "timeout-ms": { type: "string" },
401
+ "max-retries": { type: "string" },
402
+ attempt: { type: "string" },
403
+ result: { type: "string" },
404
+ error: { type: "string" },
405
+ metadata: { type: "string" },
406
+ "requested-by": { type: "string" },
407
+ summary: { type: "string" },
408
+ color: { type: "string" },
409
+ limit: { type: "string" },
410
+ "include-internal": { type: "boolean" },
411
+ subject: { type: "string" },
412
+ description: { type: "string" },
413
+ "fix-max": { type: "string" },
414
+ "ralph-max": { type: "string" },
415
+ "active-form": { type: "string" },
416
+ "add-blocks": { type: "string" },
417
+ "add-blocked-by": { type: "string" },
418
+ "metadata-patch": { type: "string" },
419
+ "if-match-mtime-ms": { type: "string" },
420
+ provider: { type: "string" },
421
+ mode: { type: "string" },
422
+ prompt: { type: "string" },
423
+ reply: { type: "string" },
424
+ done: { type: "boolean" },
425
+ "mcp-profile": { type: "string" },
426
+ "session-key": { type: "string" },
427
+ snapshot: { type: "string" },
428
+ "snapshot-file": { type: "string" },
429
+ event: { type: "string" },
430
+ "max-iterations": { type: "string" },
273
431
  },
432
+ allowPositionals: true,
274
433
  strict: false,
275
434
  });
276
- return values;
435
+ const parsed = { ...values, _: positionals };
436
+ positionals.forEach((value, index) => {
437
+ parsed[index + 1] = value;
438
+ });
439
+ return parsed;
277
440
  }
278
441
 
279
442
  export function parseJsonSafe(raw, fallback = null) {
@@ -285,29 +448,63 @@ export function parseJsonSafe(raw, fallback = null) {
285
448
  }
286
449
  }
287
450
 
451
+ function normalizeBridgePayload(payload) {
452
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
453
+ return {};
454
+ }
455
+ return payload;
456
+ }
457
+
458
+ function buildHandoffBody(from, to, payload) {
459
+ const normalizedPayload = normalizeBridgePayload(payload);
460
+ return {
461
+ ...normalizedPayload,
462
+ from,
463
+ to,
464
+ payload: normalizedPayload,
465
+ };
466
+ }
467
+
468
+ function buildPublishBody(from, to, type, payload) {
469
+ const normalizedPayload = normalizeBridgePayload(payload);
470
+ return {
471
+ ...normalizedPayload,
472
+ from,
473
+ to,
474
+ type,
475
+ message_type: type,
476
+ payload: normalizedPayload,
477
+ };
478
+ }
479
+
288
480
  // Hub 자동 재시작 (Pipe+HTTP 모두 실패 시 1회 시도, 최대 4초 대기)
289
481
  async function tryRestartHub() {
290
- const serverPath = join(PROJECT_ROOT, 'hub', 'server.mjs');
482
+ const serverPath = join(PROJECT_ROOT, "hub", "server.mjs");
291
483
  if (!existsSync(serverPath)) return false;
292
484
 
293
485
  if (existsSync(HUB_PID_FILE)) {
294
486
  try {
295
- const info = JSON.parse(readFileSync(HUB_PID_FILE, 'utf8'));
487
+ const info = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
296
488
  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
489
+ try {
490
+ process.kill(info.pid, 0);
491
+ return false;
492
+ } catch (e) {
493
+ // still alive
494
+ if (e.code === "EPERM") return false;
495
+ } // alive, no permission
299
496
  }
300
497
  } catch {} // corrupt PID file, proceed with restart
301
498
  }
302
499
 
303
500
  try {
304
- const logDir = join(process.cwd(), '.tfx', 'logs');
501
+ const logDir = join(process.cwd(), ".tfx", "logs");
305
502
  if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
306
- const logFile = join(logDir, 'hub-restart.log');
307
- const logFd = openSync(logFile, 'a');
503
+ const logFile = join(logDir, "hub-restart.log");
504
+ const logFd = openSync(logFile, "a");
308
505
  const child = spawn(process.execPath, [serverPath], {
309
506
  detached: true,
310
- stdio: ['ignore', 'ignore', logFd],
507
+ stdio: ["ignore", "ignore", logFd],
311
508
  windowsHide: true,
312
509
  });
313
510
  child.unref();
@@ -323,7 +520,7 @@ async function tryRestartHub() {
323
520
  });
324
521
  if (res.ok) {
325
522
  const data = await res.json();
326
- if (data?.hub?.state === 'healthy') return true;
523
+ if (data?.hub?.state === "healthy") return true;
327
524
  }
328
525
  } catch {}
329
526
  }
@@ -331,52 +528,54 @@ async function tryRestartHub() {
331
528
  }
332
529
 
333
530
  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);
531
+ const viaPipe =
532
+ operation.transport === "command"
533
+ ? await pipeCommand(operation.action, body, timeoutMs)
534
+ : await pipeQuery(operation.action, body, timeoutMs);
337
535
  if (viaPipe) {
338
- return { transport: 'pipe', result: viaPipe };
536
+ return { transport: "pipe", result: viaPipe };
339
537
  }
340
538
 
341
539
  const viaHttp = operation.httpPath
342
540
  ? await requestJson(operation.httpPath, {
343
- method: operation.httpMethod || 'POST',
344
- body: operation.httpMethod === 'GET' ? undefined : body,
345
- timeoutMs: Math.max(timeoutMs, 5000),
346
- })
541
+ method: operation.httpMethod || "POST",
542
+ body: operation.httpMethod === "GET" ? undefined : body,
543
+ timeoutMs: Math.max(timeoutMs, 5000),
544
+ })
347
545
  : null;
348
546
  if (viaHttp) {
349
- return { transport: 'http', result: viaHttp };
547
+ return { transport: "http", result: viaHttp };
350
548
  }
351
549
 
352
550
  // Hub 재시작 시도 → Pipe/HTTP 재시도
353
551
  if (await tryRestartHub()) {
354
- const retryPipe = operation.transport === 'command'
355
- ? await pipeCommand(operation.action, body, timeoutMs)
356
- : await pipeQuery(operation.action, body, timeoutMs);
552
+ const retryPipe =
553
+ operation.transport === "command"
554
+ ? await pipeCommand(operation.action, body, timeoutMs)
555
+ : await pipeQuery(operation.action, body, timeoutMs);
357
556
  if (retryPipe) {
358
- return { transport: 'pipe', result: retryPipe };
557
+ return { transport: "pipe", result: retryPipe };
359
558
  }
360
559
  const retryHttp = operation.httpPath
361
560
  ? await requestJson(operation.httpPath, {
362
- method: operation.httpMethod || 'POST',
363
- body: operation.httpMethod === 'GET' ? undefined : body,
364
- timeoutMs: Math.max(timeoutMs, 5000),
365
- })
561
+ method: operation.httpMethod || "POST",
562
+ body: operation.httpMethod === "GET" ? undefined : body,
563
+ timeoutMs: Math.max(timeoutMs, 5000),
564
+ })
366
565
  : null;
367
566
  if (retryHttp) {
368
- return { transport: 'http', result: retryHttp };
567
+ return { transport: "http", result: retryHttp };
369
568
  }
370
569
  }
371
570
 
372
571
  if (!fallback) return null;
373
572
  const viaFallback = await fallback();
374
573
  if (!viaFallback) return null;
375
- return { transport: 'fallback', result: viaFallback };
574
+ return { transport: "fallback", result: viaFallback };
376
575
  }
377
576
 
378
577
  function unavailableResult() {
379
- return { ok: false, reason: 'hub_unavailable' };
578
+ return { ok: false, reason: "hub_unavailable" };
380
579
  }
381
580
 
382
581
  function emitJson(payload) {
@@ -388,14 +587,14 @@ function emitJson(payload) {
388
587
 
389
588
  async function cmdRegister(args) {
390
589
  const agentId = args.agent;
391
- const timeoutSec = parseInt(args.timeout || '600', 10);
590
+ const timeoutSec = parseInt(args.timeout || "600", 10);
392
591
  const outcome = await requestHub(HUB_OPERATIONS.register, {
393
592
  agent_id: agentId,
394
- cli: args.cli || 'other',
593
+ cli: args.cli || "other",
395
594
  timeout_sec: timeoutSec,
396
595
  heartbeat_ttl_ms: (timeoutSec + 120) * 1000,
397
- topics: args.topics ? args.topics.split(',') : [],
398
- capabilities: args.capabilities ? args.capabilities.split(',') : ['code'],
596
+ topics: args.topics ? args.topics.split(",") : [],
597
+ capabilities: args.capabilities ? args.capabilities.split(",") : ["code"],
399
598
  metadata: {
400
599
  pid: process.ppid,
401
600
  registered_at: Date.now(),
@@ -416,14 +615,14 @@ async function cmdRegister(args) {
416
615
  }
417
616
 
418
617
  async function cmdResult(args) {
419
- let output = '';
618
+ let output = "";
420
619
  if (args.file && existsSync(args.file)) {
421
- output = readFileSync(args.file, 'utf8').slice(0, 49152);
620
+ output = readFileSync(args.file, "utf8").slice(0, 49152);
422
621
  }
423
622
 
424
623
  const defaultPayload = {
425
624
  agent_id: args.agent,
426
- exit_code: parseInt(args['exit-code'] || '0', 10),
625
+ exit_code: parseInt(args["exit-code"] || "0", 10),
427
626
  output_length: output.length,
428
627
  output_preview: output.slice(0, 4096),
429
628
  output_file: args.file || null,
@@ -432,8 +631,10 @@ async function cmdResult(args) {
432
631
 
433
632
  const outcome = await requestHub(HUB_OPERATIONS.result, {
434
633
  agent_id: args.agent,
435
- topic: args.topic || 'task.result',
436
- payload: args.payload ? parseJsonSafe(args.payload, defaultPayload) : defaultPayload,
634
+ topic: args.topic || "task.result",
635
+ payload: args.payload
636
+ ? parseJsonSafe(args.payload, defaultPayload)
637
+ : defaultPayload,
437
638
  trace_id: args.trace || undefined,
438
639
  correlation_id: args.correlation || undefined,
439
640
  });
@@ -448,14 +649,48 @@ async function cmdResult(args) {
448
649
 
449
650
  async function cmdControl(args) {
450
651
  const outcome = await requestHub(HUB_OPERATIONS.control, {
451
- from_agent: args.from || 'lead',
652
+ from_agent: args.from || "lead",
452
653
  to_agent: args.to,
453
654
  command: args.command,
454
- reason: args.reason || '',
655
+ reason: args.reason || "",
455
656
  payload: args.payload ? parseJsonSafe(args.payload, {}) : {},
456
657
  trace_id: args.trace || undefined,
457
658
  correlation_id: args.correlation || undefined,
458
- ttl_ms: args['ttl-ms'] != null ? Number(args['ttl-ms']) : undefined,
659
+ ttl_ms: args["ttl-ms"] != null ? Number(args["ttl-ms"]) : undefined,
660
+ });
661
+ const result = outcome?.result;
662
+ return emitJson(result || unavailableResult());
663
+ }
664
+
665
+ async function cmdHandoff(args) {
666
+ const from = args.from || args[1];
667
+ const to = args.to || args[2];
668
+ const payload = parseJsonSafe(args.payload || args[3] || "{}", {});
669
+ const outcome = await requestHub(
670
+ HUB_OPERATIONS.handoff,
671
+ buildHandoffBody(from, to, payload),
672
+ );
673
+ const result = outcome?.result;
674
+ return emitJson(result || unavailableResult());
675
+ }
676
+
677
+ async function cmdPublish(args) {
678
+ const from = args.from || args[1];
679
+ const to = args.to || args[2];
680
+ const type = args.type || args[3] || "event";
681
+ const payload = parseJsonSafe(args.payload || args[4] || "{}", {});
682
+ const outcome = await requestHub(
683
+ HUB_OPERATIONS.publish,
684
+ buildPublishBody(from, to, type, payload),
685
+ );
686
+ const result = outcome?.result;
687
+ return emitJson(result || unavailableResult());
688
+ }
689
+
690
+ async function cmdSendInput(args) {
691
+ const outcome = await requestHub(HUB_OPERATIONS.sendInput, {
692
+ session_id: args["session-id"],
693
+ text: args.text,
459
694
  });
460
695
  const result = outcome?.result;
461
696
  return emitJson(result || unavailableResult());
@@ -464,26 +699,35 @@ async function cmdControl(args) {
464
699
  async function cmdContext(args) {
465
700
  const outcome = await requestHub(HUB_OPERATIONS.context, {
466
701
  agent_id: args.agent,
467
- topics: args.topics ? args.topics.split(',') : undefined,
468
- max_messages: parseInt(args.max || '10', 10),
702
+ topics: args.topics ? args.topics.split(",") : undefined,
703
+ max_messages: parseInt(args.max || "10", 10),
469
704
  auto_ack: true,
470
705
  });
471
706
  const result = outcome?.result;
472
707
 
473
708
  if (result?.ok && result.data?.messages?.length) {
474
709
  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}`;
710
+ const payload =
711
+ typeof message.payload === "string"
712
+ ? message.payload
713
+ : JSON.stringify(message.payload, null, 2);
714
+ return `=== Context ${index + 1}: ${message.from_agent || "unknown"} (${message.topic || "unknown"}) ===\n${payload}`;
479
715
  });
480
- const combined = parts.join('\n\n');
716
+ const combined = parts.join("\n\n");
481
717
 
482
718
  if (args.out) {
483
- writeFileSync(args.out, combined, 'utf8');
484
- return emitJson({ ok: true, count: result.data.messages.length, file: args.out });
719
+ writeFileSync(args.out, combined, "utf8");
720
+ return emitJson({
721
+ ok: true,
722
+ count: result.data.messages.length,
723
+ file: args.out,
724
+ });
485
725
  } else {
486
- return emitJson({ ok: true, count: result.data.messages.length, context: combined });
726
+ return emitJson({
727
+ ok: true,
728
+ count: result.data.messages.length,
729
+ context: combined,
730
+ });
487
731
  }
488
732
  }
489
733
 
@@ -491,7 +735,7 @@ async function cmdContext(args) {
491
735
  if (args.out) {
492
736
  return emitJson({ ok: true, count: 0 });
493
737
  }
494
- return emitJson({ ok: true, count: 0, context: '' });
738
+ return emitJson({ ok: true, count: 0, context: "" });
495
739
  }
496
740
 
497
741
  return emitJson(result || unavailableResult());
@@ -504,7 +748,7 @@ async function cmdDeregister(args) {
504
748
  const result = outcome?.result;
505
749
 
506
750
  if (result?.ok) {
507
- return emitJson({ ok: true, agent_id: args.agent, status: 'offline' });
751
+ return emitJson({ ok: true, agent_id: args.agent, status: "offline" });
508
752
  }
509
753
 
510
754
  return emitJson(result || unavailableResult());
@@ -512,15 +756,17 @@ async function cmdDeregister(args) {
512
756
 
513
757
  async function cmdAssignAsync(args) {
514
758
  const outcome = await requestHub(HUB_OPERATIONS.assignAsync, {
515
- supervisor_agent: args['supervisor-agent'],
516
- worker_agent: args['worker-agent'],
759
+ supervisor_agent: args["supervisor-agent"],
760
+ worker_agent: args["worker-agent"],
517
761
  task: args.task,
518
- topic: args.topic || 'assign.job',
762
+ topic: args.topic || "assign.job",
519
763
  payload: args.payload ? parseJsonSafe(args.payload, {}) : {},
520
764
  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,
765
+ ttl_ms: args["ttl-ms"] != null ? Number(args["ttl-ms"]) : undefined,
766
+ timeout_ms:
767
+ args["timeout-ms"] != null ? Number(args["timeout-ms"]) : undefined,
768
+ max_retries:
769
+ args["max-retries"] != null ? Number(args["max-retries"]) : undefined,
524
770
  trace_id: args.trace || undefined,
525
771
  correlation_id: args.correlation || undefined,
526
772
  });
@@ -530,8 +776,8 @@ async function cmdAssignAsync(args) {
530
776
 
531
777
  async function cmdAssignResult(args) {
532
778
  const outcome = await requestHub(HUB_OPERATIONS.assignResult, {
533
- job_id: args['job-id'],
534
- worker_agent: args['worker-agent'],
779
+ job_id: args["job-id"],
780
+ worker_agent: args["worker-agent"],
535
781
  status: args.status,
536
782
  attempt: args.attempt != null ? Number(args.attempt) : undefined,
537
783
  result: args.result ? parseJsonSafe(args.result, null) : undefined,
@@ -545,7 +791,7 @@ async function cmdAssignResult(args) {
545
791
 
546
792
  async function cmdAssignStatus(args) {
547
793
  const outcome = await requestHub(HUB_OPERATIONS.assignStatus, {
548
- job_id: args['job-id'],
794
+ job_id: args["job-id"],
549
795
  });
550
796
  const result = outcome?.result;
551
797
  return emitJson(result || unavailableResult());
@@ -553,9 +799,9 @@ async function cmdAssignStatus(args) {
553
799
 
554
800
  async function cmdAssignRetry(args) {
555
801
  const outcome = await requestHub(HUB_OPERATIONS.assignRetry, {
556
- job_id: args['job-id'],
802
+ job_id: args["job-id"],
557
803
  reason: args.reason,
558
- requested_by: args['requested-by'],
804
+ requested_by: args["requested-by"],
559
805
  });
560
806
  const result = outcome?.result;
561
807
  return emitJson(result || unavailableResult());
@@ -567,10 +813,15 @@ async function cmdTeamInfo(args) {
567
813
  include_members: true,
568
814
  include_paths: true,
569
815
  };
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
- });
816
+ const outcome = await requestHub(
817
+ HUB_OPERATIONS.teamInfo,
818
+ body,
819
+ 3000,
820
+ async () => {
821
+ const { teamInfo } = await import("./team/nativeProxy.mjs");
822
+ return await teamInfo(body);
823
+ },
824
+ );
574
825
  const result = outcome?.result;
575
826
  return emitJson(result || unavailableResult());
576
827
  }
@@ -579,14 +830,24 @@ async function cmdTeamTaskList(args) {
579
830
  const body = {
580
831
  team_name: args.team,
581
832
  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),
833
+ statuses: args.statuses
834
+ ? args.statuses
835
+ .split(",")
836
+ .map((status) => status.trim())
837
+ .filter(Boolean)
838
+ : [],
839
+ include_internal: !!args["include-internal"],
840
+ limit: parseInt(args.limit || "200", 10),
585
841
  };
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
- });
842
+ const outcome = await requestHub(
843
+ HUB_OPERATIONS.teamTaskList,
844
+ body,
845
+ 3000,
846
+ async () => {
847
+ const { teamTaskList } = await import("./team/nativeProxy.mjs");
848
+ return await teamTaskList(body);
849
+ },
850
+ );
590
851
  const result = outcome?.result;
591
852
  return emitJson(result || unavailableResult());
592
853
  }
@@ -594,23 +855,43 @@ async function cmdTeamTaskList(args) {
594
855
  async function cmdTeamTaskUpdate(args) {
595
856
  const body = {
596
857
  team_name: args.team,
597
- task_id: args['task-id'],
858
+ task_id: args["task-id"],
598
859
  claim: !!args.claim,
599
860
  owner: args.owner,
600
861
  status: args.status,
601
862
  subject: args.subject,
602
863
  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,
864
+ activeForm: args["active-form"],
865
+ add_blocks: args["add-blocks"]
866
+ ? args["add-blocks"]
867
+ .split(",")
868
+ .map((value) => value.trim())
869
+ .filter(Boolean)
870
+ : undefined,
871
+ add_blocked_by: args["add-blocked-by"]
872
+ ? args["add-blocked-by"]
873
+ .split(",")
874
+ .map((value) => value.trim())
875
+ .filter(Boolean)
876
+ : undefined,
877
+ metadata_patch: args["metadata-patch"]
878
+ ? parseJsonSafe(args["metadata-patch"], null)
879
+ : undefined,
880
+ if_match_mtime_ms:
881
+ args["if-match-mtime-ms"] != null
882
+ ? Number(args["if-match-mtime-ms"])
883
+ : undefined,
608
884
  actor: args.actor,
609
885
  };
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
- });
886
+ const outcome = await requestHub(
887
+ HUB_OPERATIONS.teamTaskUpdate,
888
+ body,
889
+ 3000,
890
+ async () => {
891
+ const { teamTaskUpdate } = await import("./team/nativeProxy.mjs");
892
+ return await teamTaskUpdate(body);
893
+ },
894
+ );
614
895
  const result = outcome?.result;
615
896
  return emitJson(result || unavailableResult());
616
897
  }
@@ -619,15 +900,20 @@ async function cmdTeamSendMessage(args) {
619
900
  const body = {
620
901
  team_name: args.team,
621
902
  from: args.from,
622
- to: args.to || 'team-lead',
903
+ to: args.to || "team-lead",
623
904
  text: args.text,
624
905
  summary: args.summary,
625
- color: args.color || 'blue',
906
+ color: args.color || "blue",
626
907
  };
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
- });
908
+ const outcome = await requestHub(
909
+ HUB_OPERATIONS.teamSendMessage,
910
+ body,
911
+ 3000,
912
+ async () => {
913
+ const { teamSendMessage } = await import("./team/nativeProxy.mjs");
914
+ return await teamSendMessage(body);
915
+ },
916
+ );
631
917
  const result = outcome?.result;
632
918
  return emitJson(result || unavailableResult());
633
919
  }
@@ -637,62 +923,122 @@ function getHubDbPath() {
637
923
  }
638
924
 
639
925
  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' };
926
+ const outcome = await requestHub(
927
+ HUB_OPERATIONS.pipelineState,
928
+ { team_name: args.team },
929
+ 3000,
930
+ async () => {
931
+ try {
932
+ const { default: Database } = await import("better-sqlite3");
933
+ const { ensurePipelineTable, readPipelineState } = await import(
934
+ "./pipeline/state.mjs"
935
+ );
936
+ const dbPath = getHubDbPath();
937
+ if (!existsSync(dbPath)) {
938
+ return { ok: false, error: "hub_db_not_found" };
939
+ }
940
+ const db = new Database(dbPath, { readonly: true });
941
+ ensurePipelineTable(db);
942
+ const state = readPipelineState(db, args.team);
943
+ db.close();
944
+ return state
945
+ ? { ok: true, data: state }
946
+ : { ok: false, error: "pipeline_not_found" };
947
+ } catch (e) {
948
+ return { ok: false, error: e.message };
647
949
  }
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
- });
950
+ },
951
+ );
659
952
  const result = outcome?.result;
660
953
  return emitJson(result || unavailableResult());
661
954
  }
662
955
 
956
+ // Best-effort phase-manager bridge (silent on failure — pipeline 동작 영향 X)
957
+ async function writePhaseSafely(runId, phase, status) {
958
+ if (!runId) return;
959
+ try {
960
+ const mod = await import("./lib/phase-manager.mjs");
961
+ if (typeof mod?.writePhase === "function") {
962
+ await mod.writePhase(runId, phase, status);
963
+ }
964
+ if (typeof mod?.syncToGstack === "function") {
965
+ const slug = process.cwd().split(/[\\/]/).pop();
966
+ await mod.syncToGstack(runId, slug);
967
+ }
968
+ } catch {
969
+ /* silent — phase tracking is best-effort */
970
+ }
971
+ }
972
+
973
+ async function writePhaseSafelyFromLegacy(runId, legacyStatus) {
974
+ if (!runId || !legacyStatus) return;
975
+ try {
976
+ const mod = await import("./lib/phase-manager.mjs");
977
+ if (typeof mod?.coerceLegacyPhase !== "function") return;
978
+ const coerced = mod.coerceLegacyPhase(legacyStatus);
979
+ if (!coerced) return;
980
+ if (coerced === "complete") {
981
+ const current =
982
+ typeof mod.readPhase === "function" ? await mod.readPhase(runId) : null;
983
+ const lastPhase = current?.phase || current?.lastPhase;
984
+ if (lastPhase) await mod.writePhase(runId, lastPhase, "complete");
985
+ return;
986
+ }
987
+ await mod.writePhase(runId, coerced, "active");
988
+ if (typeof mod?.syncToGstack === "function") {
989
+ const slug = process.cwd().split(/[\\/]/).pop();
990
+ await mod.syncToGstack(runId, slug);
991
+ }
992
+ } catch {
993
+ /* silent */
994
+ }
995
+ }
996
+
663
997
  async function cmdPipelineAdvance(args) {
664
998
  const body = {
665
999
  team_name: args.team,
666
1000
  phase: args.status, // --status를 phase로 재활용
667
1001
  };
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' };
1002
+ const outcome = await requestHub(
1003
+ HUB_OPERATIONS.pipelineAdvance,
1004
+ body,
1005
+ 3000,
1006
+ async () => {
1007
+ try {
1008
+ const { default: Database } = await import("better-sqlite3");
1009
+ const { createPipeline } = await import("./pipeline/index.mjs");
1010
+ const dbPath = getHubDbPath();
1011
+ if (!existsSync(dbPath)) {
1012
+ return { ok: false, error: "hub_db_not_found" };
1013
+ }
1014
+ const db = new Database(dbPath);
1015
+ const pipeline = createPipeline(db, args.team);
1016
+ const advanceResult = pipeline.advance(args.status);
1017
+ db.close();
1018
+ return advanceResult;
1019
+ } catch (e) {
1020
+ return { ok: false, error: e.message };
675
1021
  }
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
- });
1022
+ },
1023
+ );
685
1024
  const result = outcome?.result;
1025
+ if (args.team && args.status && result?.ok !== false) {
1026
+ void writePhaseSafelyFromLegacy(args.team, args.status);
1027
+ }
686
1028
  return emitJson(result || unavailableResult());
687
1029
  }
688
1030
 
689
1031
  async function cmdPipelineInit(args) {
690
1032
  const outcome = await requestHub(HUB_OPERATIONS.pipelineInit, {
691
1033
  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,
1034
+ fix_max: args["fix-max"] != null ? Number(args["fix-max"]) : undefined,
1035
+ ralph_max:
1036
+ args["ralph-max"] != null ? Number(args["ralph-max"]) : undefined,
694
1037
  });
695
1038
  const result = outcome?.result;
1039
+ if (args.team && result?.ok !== false) {
1040
+ void writePhaseSafely(args.team, "Strategy", "active");
1041
+ }
696
1042
  return emitJson(result || unavailableResult());
697
1043
  }
698
1044
 
@@ -703,25 +1049,29 @@ async function cmdPipelineList() {
703
1049
  }
704
1050
 
705
1051
  async function cmdPing() {
706
- const outcome = await requestHub(HUB_OPERATIONS.hubStatus, { scope: 'hub' }, 2000);
1052
+ const outcome = await requestHub(
1053
+ HUB_OPERATIONS.hubStatus,
1054
+ { scope: "hub" },
1055
+ 2000,
1056
+ );
707
1057
 
708
- if (outcome?.transport === 'pipe' && outcome.result?.ok) {
1058
+ if (outcome?.transport === "pipe" && outcome.result?.ok) {
709
1059
  return emitJson({
710
1060
  ok: true,
711
- hub: outcome.result.data?.hub?.state || 'healthy',
1061
+ hub: outcome.result.data?.hub?.state || "healthy",
712
1062
  pipe_path: getHubPipePath(),
713
- transport: 'pipe',
1063
+ transport: "pipe",
714
1064
  });
715
1065
  }
716
1066
 
717
- if (outcome?.transport === 'http' && outcome.result) {
1067
+ if (outcome?.transport === "http" && outcome.result) {
718
1068
  const data = outcome.result;
719
1069
  return emitJson({
720
1070
  ok: true,
721
1071
  hub: data.hub?.state,
722
1072
  sessions: data.sessions,
723
1073
  pipe_path: data.pipe?.path || data.pipe_path || null,
724
- transport: 'http',
1074
+ transport: "http",
725
1075
  });
726
1076
  }
727
1077
 
@@ -731,21 +1081,26 @@ async function cmdPing() {
731
1081
  async function cmdDelegatorDelegate(args) {
732
1082
  const body = {
733
1083
  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,
1084
+ provider: args.provider || "auto",
1085
+ mode: args.mode || "sync",
1086
+ agent_type: args.agent || "executor",
1087
+ mcp_profile: args["mcp-profile"] || "auto",
1088
+ session_key: args["session-key"] || undefined,
1089
+ timeout_ms:
1090
+ args["timeout-ms"] != null ? Number(args["timeout-ms"]) : undefined,
740
1091
  };
741
- const timeoutMs = body.mode === 'async' ? 10000 : 120000;
742
- const outcome = await requestHub(HUB_OPERATIONS.delegatorDelegate, body, timeoutMs);
1092
+ const timeoutMs = body.mode === "async" ? 10000 : 120000;
1093
+ const outcome = await requestHub(
1094
+ HUB_OPERATIONS.delegatorDelegate,
1095
+ body,
1096
+ timeoutMs,
1097
+ );
743
1098
  return emitJson(outcome?.result || unavailableResult());
744
1099
  }
745
1100
 
746
1101
  async function cmdDelegatorReply(args) {
747
1102
  const body = {
748
- job_id: args['job-id'],
1103
+ job_id: args["job-id"],
749
1104
  reply: args.text || args.reply,
750
1105
  done: !!args.done,
751
1106
  };
@@ -755,45 +1110,231 @@ async function cmdDelegatorReply(args) {
755
1110
 
756
1111
  async function cmdDelegatorStatus(args) {
757
1112
  const body = {
758
- job_id: args['job-id'],
1113
+ job_id: args["job-id"],
759
1114
  };
760
1115
  const outcome = await requestHub(HUB_OPERATIONS.delegatorStatus, body, 5000);
761
1116
  return emitJson(outcome?.result || unavailableResult());
762
1117
  }
763
1118
 
1119
+ async function cmdHitlRequest(args) {
1120
+ const body = {
1121
+ kind: args[1],
1122
+ prompt: args[2],
1123
+ requester_agent: args[3] || "cli",
1124
+ };
1125
+ const outcome = await requestHub(
1126
+ HUB_OPERATIONS["hitl-request"],
1127
+ body,
1128
+ 120000,
1129
+ );
1130
+ return emitJson(outcome?.result || unavailableResult());
1131
+ }
1132
+
1133
+ async function cmdHitlSubmit(args) {
1134
+ const body = {
1135
+ request_id: args[1],
1136
+ action: args[2] || "accept",
1137
+ content: args[3],
1138
+ };
1139
+ const outcome = await requestHub(HUB_OPERATIONS["hitl-submit"], body, 120000);
1140
+ return emitJson(outcome?.result || unavailableResult());
1141
+ }
1142
+
1143
+ async function cmdHitlPending() {
1144
+ const outcome = await requestHub(HUB_OPERATIONS["hitl-pending"], {}, 5000);
1145
+ return emitJson(outcome?.result || unavailableResult());
1146
+ }
1147
+
1148
+ // ---------------------------------------------------------------------------
1149
+ // retry-run / retry-status — Phase 3 Step C2 bridge 서브커맨드.
1150
+ // retry-state-machine.mjs 를 multi-process safe 하게 외부 호출용 wrap.
1151
+ // 사용자 워크플로우:
1152
+ // 1) 첫 호출: retry-run --snapshot X --mode ralph --event start
1153
+ // → 새 SM 생성, PLANNING → EXECUTING transition, snapshot 저장
1154
+ // 2) verify 성공 시: retry-run --snapshot X --event verify-success
1155
+ // → DONE, 종료 판단 반환
1156
+ // 3) verify 실패 시: retry-run --snapshot X --event verify-fail --reason R
1157
+ // → DIAGNOSING 또는 STUCK/BUDGET_EXCEEDED, 종료 판단 반환
1158
+ // 4) 다음 iter 시작: retry-run --snapshot X --event start
1159
+ // 출력: {ok, current, iterations, done, shouldStop, reason?, cli?} JSON.
1160
+ // ---------------------------------------------------------------------------
1161
+
1162
+ function buildRetrySmFromArgs(args, snapshot) {
1163
+ const mode = args.mode || snapshot?.mode || "bounded";
1164
+ const maxIterations =
1165
+ args["max-iterations"] !== undefined
1166
+ ? Number(args["max-iterations"])
1167
+ : snapshot?.maxIterations;
1168
+ const sessionId = args["session-id"] || snapshot?.sessionId || null;
1169
+ const cliChain = snapshot?.cliChain;
1170
+
1171
+ const sm = createRetryStateMachine({
1172
+ mode,
1173
+ maxIterations,
1174
+ sessionId,
1175
+ cliChain,
1176
+ });
1177
+ if (snapshot) sm.applySnapshot(snapshot);
1178
+ return sm;
1179
+ }
1180
+
1181
+ async function cmdRetryRun(args) {
1182
+ const snapshotFile = args.snapshot || args["snapshot-file"];
1183
+ const event = args.event;
1184
+ const reason = args.reason || "";
1185
+
1186
+ if (!snapshotFile) {
1187
+ console.error("--snapshot <path> required");
1188
+ return false;
1189
+ }
1190
+ if (!event) {
1191
+ console.error("--event <start|verify-success|verify-fail> required");
1192
+ return false;
1193
+ }
1194
+
1195
+ const existing = loadSnapshot(snapshotFile);
1196
+ const sm = buildRetrySmFromArgs(args, existing);
1197
+
1198
+ let result;
1199
+ switch (event) {
1200
+ case "start":
1201
+ result = sm.startIteration();
1202
+ break;
1203
+ case "verify-success":
1204
+ result = sm.reportVerifySuccess();
1205
+ break;
1206
+ case "verify-fail":
1207
+ result = sm.reportVerifyFail(reason || "unspecified");
1208
+ break;
1209
+ default:
1210
+ console.error(`unknown --event: ${event}`);
1211
+ return false;
1212
+ }
1213
+
1214
+ const snap = sm.serialize();
1215
+ saveSnapshot(snapshotFile, snap);
1216
+
1217
+ const terminal = ["DONE", "STUCK", "BUDGET_EXCEEDED"].includes(snap.current);
1218
+ const cli = snap.cliChain?.[snap.cliIndex] || null;
1219
+ const out = {
1220
+ ok: true,
1221
+ current: snap.current,
1222
+ iterations: snap.iterations,
1223
+ cliIndex: snap.cliIndex,
1224
+ cli,
1225
+ done: snap.current === "DONE",
1226
+ shouldStop: terminal,
1227
+ stuckCounter: snap.stuckCounter,
1228
+ lastFailureReason: snap.lastFailureReason,
1229
+ transition: result,
1230
+ };
1231
+ console.log(JSON.stringify(out));
1232
+ return true;
1233
+ }
1234
+
1235
+ async function cmdRetryStatus(args) {
1236
+ const snapshotFile = args.snapshot || args["snapshot-file"];
1237
+ if (!snapshotFile) {
1238
+ console.error("--snapshot <path> required");
1239
+ return false;
1240
+ }
1241
+ const snap = loadSnapshot(snapshotFile);
1242
+ if (!snap) {
1243
+ console.log(JSON.stringify({ ok: true, exists: false }));
1244
+ return true;
1245
+ }
1246
+ const terminal = ["DONE", "STUCK", "BUDGET_EXCEEDED"].includes(snap.current);
1247
+ const cli = snap.cliChain?.[snap.cliIndex] || null;
1248
+ console.log(
1249
+ JSON.stringify({
1250
+ ok: true,
1251
+ exists: true,
1252
+ current: snap.current,
1253
+ iterations: snap.iterations,
1254
+ maxIterations: snap.maxIterations,
1255
+ cliIndex: snap.cliIndex,
1256
+ cli,
1257
+ mode: snap.mode,
1258
+ shouldStop: terminal,
1259
+ stuckCounter: snap.stuckCounter,
1260
+ lastFailureReason: snap.lastFailureReason,
1261
+ }),
1262
+ );
1263
+ return true;
1264
+ }
1265
+
764
1266
  export async function main(argv = process.argv.slice(2)) {
765
1267
  const cmd = argv[0];
766
1268
  const args = parseArgs(argv.slice(1));
767
1269
 
768
1270
  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);
1271
+ case "register":
1272
+ return await cmdRegister(args);
1273
+ case "result":
1274
+ return await cmdResult(args);
1275
+ case "control":
1276
+ return await cmdControl(args);
1277
+ case "handoff":
1278
+ return await cmdHandoff(args);
1279
+ case "publish":
1280
+ return await cmdPublish(args);
1281
+ case "send-input":
1282
+ return await cmdSendInput(args);
1283
+ case "context":
1284
+ return await cmdContext(args);
1285
+ case "deregister":
1286
+ return await cmdDeregister(args);
1287
+ case "assign-async":
1288
+ return await cmdAssignAsync(args);
1289
+ case "assign-result":
1290
+ return await cmdAssignResult(args);
1291
+ case "assign-status":
1292
+ return await cmdAssignStatus(args);
1293
+ case "assign-retry":
1294
+ return await cmdAssignRetry(args);
1295
+ case "team-info":
1296
+ return await cmdTeamInfo(args);
1297
+ case "team-task-list":
1298
+ return await cmdTeamTaskList(args);
1299
+ case "team-task-update":
1300
+ return await cmdTeamTaskUpdate(args);
1301
+ case "team-send-message":
1302
+ return await cmdTeamSendMessage(args);
1303
+ case "pipeline-state":
1304
+ return await cmdPipelineState(args);
1305
+ case "pipeline-advance":
1306
+ return await cmdPipelineAdvance(args);
1307
+ case "pipeline-init":
1308
+ return await cmdPipelineInit(args);
1309
+ case "pipeline-list":
1310
+ return await cmdPipelineList(args);
1311
+ case "ping":
1312
+ return await cmdPing(args);
1313
+ case "delegator-delegate":
1314
+ return await cmdDelegatorDelegate(args);
1315
+ case "delegator-reply":
1316
+ return await cmdDelegatorReply(args);
1317
+ case "delegator-status":
1318
+ return await cmdDelegatorStatus(args);
1319
+ case "hitl-request":
1320
+ return await cmdHitlRequest(args);
1321
+ case "hitl-submit":
1322
+ return await cmdHitlSubmit(args);
1323
+ case "hitl-pending":
1324
+ return await cmdHitlPending(args);
1325
+ case "retry-run":
1326
+ return await cmdRetryRun(args);
1327
+ case "retry-status":
1328
+ return await cmdRetryStatus(args);
790
1329
  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> [--옵션]');
1330
+ console.error(
1331
+ "사용법: 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|retry-run|retry-status> [--옵션]",
1332
+ );
792
1333
  process.exit(1);
793
1334
  }
794
1335
  }
795
1336
 
796
- const selfRun = process.argv[1]?.replace(/\\/g, '/').endsWith('hub/bridge.mjs');
1337
+ const selfRun = process.argv[1]?.replace(/\\/g, "/").endsWith("hub/bridge.mjs");
797
1338
  if (selfRun) {
798
- process.exitCode = await main() ? 0 : 1;
1339
+ process.exitCode = (await main()) ? 0 : 1;
799
1340
  }