@triflux/core 10.0.3 → 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.
- package/hooks/agent-route-guard.mjs +14 -10
- package/hooks/cross-review-tracker.mjs +21 -8
- package/hooks/error-context.mjs +47 -7
- package/hooks/hook-adaptive-collector.mjs +18 -16
- package/hooks/hook-manager.mjs +93 -32
- package/hooks/hook-orchestrator.mjs +279 -33
- package/hooks/hook-registry.json +104 -7
- package/hooks/hooks.json +49 -1
- package/hooks/keyword-rules.json +183 -23
- package/hooks/lib/resolve-root.mjs +1 -1
- package/hooks/mcp-config-watcher.mjs +6 -2
- package/hooks/permission-safe-allow.mjs +233 -0
- package/hooks/pipeline-stop.mjs +369 -22
- package/hooks/post-tool-tips.mjs +306 -0
- package/hooks/pre-compact-snapshot.mjs +181 -0
- package/hooks/safety-guard.mjs +308 -18
- package/hooks/session-end-cleanup.mjs +345 -0
- package/hooks/session-start-fast.mjs +214 -0
- package/hooks/subagent-tracker.mjs +267 -0
- package/hooks/subagent-verifier.mjs +9 -4
- package/hub/account-broker.mjs +1036 -66
- package/hub/adaptive-diagnostic.mjs +75 -48
- package/hub/adaptive-inject.mjs +96 -58
- package/hub/adaptive-memory.mjs +156 -42
- package/hub/adaptive.mjs +60 -31
- package/hub/assign-callbacks.mjs +163 -126
- package/hub/bridge.mjs +832 -291
- package/hub/cli-adapter-base.mjs +321 -97
- package/hub/codex-adapter.mjs +179 -93
- package/hub/codex-compat.mjs +4 -3
- package/hub/codex-preflight.mjs +113 -37
- package/hub/delegator/contracts.mjs +19 -23
- package/hub/delegator/index.mjs +14 -14
- package/hub/delegator/service.mjs +88 -64
- package/hub/delegator/tool-definitions.mjs +35 -35
- package/hub/dynamic-routing-engine.mjs +511 -0
- package/hub/fullcycle.mjs +34 -17
- package/hub/gemini-adapter.mjs +77 -95
- package/hub/hitl.mjs +202 -143
- package/hub/intent.mjs +171 -39
- package/hub/lib/bash-path.mjs +73 -0
- package/hub/lib/cache-guard.mjs +43 -17
- package/hub/lib/env-detect.mjs +145 -0
- package/hub/lib/hosts-compat.mjs +319 -0
- package/hub/lib/mcp-response-cache.mjs +239 -0
- package/hub/lib/memory-store.mjs +303 -107
- package/hub/lib/path-utils.mjs +165 -0
- package/hub/lib/phase-manager.mjs +493 -0
- package/hub/lib/process-utils.mjs +797 -104
- package/hub/lib/prompt-tmp.mjs +33 -0
- package/hub/lib/risk-tier.mjs +53 -0
- package/hub/lib/spawn-trace.mjs +561 -0
- package/hub/lib/ssh-command.mjs +44 -24
- package/hub/lib/ssh-retry.mjs +5 -1
- package/hub/lib/staged-file-detect.mjs +43 -0
- package/hub/lib/state-snapshot.mjs +293 -0
- package/hub/lib/tfx-route-args.mjs +309 -0
- package/hub/lib/timeline-adapter.mjs +190 -0
- package/hub/lib/trace-recorder.mjs +153 -0
- package/hub/lib/uuidv7.mjs +4 -3
- package/hub/memory-doctor.mjs +266 -106
- package/hub/middleware/quota-middleware.mjs +70 -0
- package/hub/middleware/request-logger.mjs +61 -34
- package/hub/paths.mjs +9 -9
- package/hub/pipeline/gates/confidence.mjs +34 -15
- package/hub/pipeline/gates/consensus.mjs +27 -15
- package/hub/pipeline/gates/index.mjs +7 -3
- package/hub/pipeline/gates/selfcheck.mjs +57 -19
- package/hub/pipeline/index.mjs +77 -42
- package/hub/pipeline/state.mjs +10 -10
- package/hub/pipeline/transitions.mjs +40 -23
- package/hub/platform.mjs +65 -48
- package/hub/quality/deslop.mjs +70 -49
- package/hub/reflexion.mjs +116 -58
- package/hub/research.mjs +34 -25
- package/hub/router.mjs +941 -791
- package/hub/routing/complexity.mjs +132 -29
- package/hub/routing/index.mjs +17 -12
- package/hub/routing/q-learning.mjs +76 -28
- package/hub/routing-snapshot.mjs +261 -0
- package/hub/session-fingerprint.mjs +129 -63
- package/hub/state.mjs +86 -44
- package/hub/team/retry-state-machine.mjs +267 -0
- package/hub/token-mode.mjs +76 -72
- package/hub/workers/worker-utils.mjs +22 -16
- package/hud/colors.mjs +27 -9
- package/hud/constants.mjs +184 -27
- package/hud/context-monitor.mjs +133 -44
- package/hud/hud-qos-status.mjs +153 -50
- package/hud/mission-board.mjs +56 -0
- package/hud/providers/claude.mjs +398 -114
- package/hud/providers/codex.mjs +96 -21
- package/hud/providers/gemini.mjs +184 -51
- package/hud/renderers.mjs +413 -123
- package/hud/terminal.mjs +101 -31
- package/hud/utils.mjs +81 -39
- package/mesh/index.mjs +69 -0
- package/mesh/mesh-budget.mjs +137 -0
- package/mesh/mesh-heartbeat.mjs +100 -0
- package/mesh/mesh-protocol.mjs +96 -0
- package/mesh/mesh-queue.mjs +163 -0
- package/mesh/mesh-registry.mjs +78 -0
- package/mesh/mesh-router.mjs +77 -0
- package/package.json +5 -1
- package/scripts/lib/agent-json.mjs +27 -0
- package/scripts/lib/async.mjs +174 -0
- package/scripts/lib/claudemd-scanner.mjs +6 -1
- package/scripts/lib/cli-agy.mjs +62 -0
- package/scripts/lib/cli-claude.mjs +78 -0
- package/scripts/lib/cli-codex.mjs +199 -0
- package/scripts/lib/cli-gemini.mjs +67 -0
- package/scripts/lib/codex-recovery.sh +50 -0
- package/scripts/lib/context.mjs +3 -3
- package/scripts/lib/cross-review-utils.mjs +13 -5
- package/scripts/lib/doctor-env-checks.mjs +121 -0
- package/scripts/lib/dynamic-route-cli.mjs +107 -0
- package/scripts/lib/env-probe.mjs +58 -30
- package/scripts/lib/env.mjs +14 -0
- package/scripts/lib/gemini-profiles.mjs +44 -10
- package/scripts/lib/handoff.mjs +33 -17
- package/scripts/lib/hook-utils.mjs +8 -6
- package/scripts/lib/hub.mjs +48 -0
- package/scripts/lib/keyword-rules.mjs +48 -19
- package/scripts/lib/logger.mjs +24 -24
- package/scripts/lib/mcp-filter.mjs +393 -249
- package/scripts/lib/mcp-gateway-health-check.mjs +159 -0
- package/scripts/lib/mcp-gateway-servers.mjs +70 -0
- package/scripts/lib/mcp-gateway-wrapper-check.mjs +35 -0
- package/scripts/lib/mcp-guard-engine.mjs +1282 -116
- package/scripts/lib/mcp-health.mjs +773 -0
- package/scripts/lib/mcp-manifest.mjs +24 -14
- package/scripts/lib/mcp-server-catalog.mjs +355 -118
- package/scripts/lib/pid.mjs +63 -0
- package/scripts/lib/process-utils.mjs +26 -0
- package/scripts/lib/psmux-info.mjs +72 -12
- package/scripts/lib/quota.mjs +47 -0
- package/scripts/lib/remote-spawn-transfer.mjs +44 -14
- package/scripts/lib/skill-state.mjs +220 -0
- package/scripts/lib/skill-template.mjs +36 -11
- package/scripts/lib/team.mjs +75 -0
- package/scripts/lib/timeout.mjs +66 -0
- package/scripts/lib/tmp.mjs +26 -0
- 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
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
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,
|
|
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)
|
|
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,
|
|
45
|
-
|
|
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
|
-
|
|
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,
|
|
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: {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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(
|
|
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
|
|
|
@@ -96,10 +239,10 @@ export async function requestJson(path, { method = 'POST', body, timeoutMs = 500
|
|
|
96
239
|
const headers = {};
|
|
97
240
|
const token = readHubToken();
|
|
98
241
|
if (token) {
|
|
99
|
-
headers
|
|
242
|
+
headers.Authorization = `Bearer ${token}`;
|
|
100
243
|
}
|
|
101
244
|
if (body !== undefined) {
|
|
102
|
-
headers[
|
|
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:
|
|
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 {
|
|
273
|
+
try {
|
|
274
|
+
socket.destroy();
|
|
275
|
+
} catch {}
|
|
131
276
|
resolve(null);
|
|
132
277
|
}, timeoutMs);
|
|
133
278
|
|
|
134
|
-
socket.once(
|
|
279
|
+
socket.once("connect", () => {
|
|
135
280
|
clearTimeout(timer);
|
|
136
|
-
socket.setEncoding(
|
|
281
|
+
socket.setEncoding("utf8");
|
|
137
282
|
resolve(socket);
|
|
138
283
|
});
|
|
139
284
|
|
|
140
|
-
socket.once(
|
|
285
|
+
socket.once("error", () => {
|
|
141
286
|
clearTimeout(timer);
|
|
142
|
-
try {
|
|
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 {
|
|
311
|
+
try {
|
|
312
|
+
socket.end();
|
|
313
|
+
} catch {}
|
|
165
314
|
resolve(result);
|
|
166
315
|
};
|
|
167
316
|
|
|
168
|
-
socket.on(
|
|
317
|
+
socket.on("data", (chunk) => {
|
|
169
318
|
buffer += chunk;
|
|
170
|
-
let newlineIndex = buffer.indexOf(
|
|
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(
|
|
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 !==
|
|
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(
|
|
195
|
-
socket.write(
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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(
|
|
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(
|
|
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:
|
|
216
|
-
cli: { type:
|
|
217
|
-
timeout: { type:
|
|
218
|
-
topics: { type:
|
|
219
|
-
capabilities: { type:
|
|
220
|
-
file: { type:
|
|
221
|
-
payload: { type:
|
|
222
|
-
topic: { type:
|
|
223
|
-
trace: { type:
|
|
224
|
-
correlation: { type:
|
|
225
|
-
|
|
226
|
-
max: { type:
|
|
227
|
-
out: { type:
|
|
228
|
-
team: { type:
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
owner: { type:
|
|
232
|
-
status: { type:
|
|
233
|
-
statuses: { type:
|
|
234
|
-
claim: { type:
|
|
235
|
-
actor: { type:
|
|
236
|
-
command: { type:
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
487
|
+
const info = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
|
|
296
488
|
if (info.pid) {
|
|
297
|
-
try {
|
|
298
|
-
|
|
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(),
|
|
501
|
+
const logDir = join(process.cwd(), ".tfx", "logs");
|
|
305
502
|
if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
|
|
306
|
-
const logFile = join(logDir,
|
|
307
|
-
const logFd = openSync(logFile,
|
|
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: [
|
|
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 ===
|
|
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 =
|
|
335
|
-
|
|
336
|
-
|
|
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:
|
|
536
|
+
return { transport: "pipe", result: viaPipe };
|
|
339
537
|
}
|
|
340
538
|
|
|
341
539
|
const viaHttp = operation.httpPath
|
|
342
540
|
? await requestJson(operation.httpPath, {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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:
|
|
547
|
+
return { transport: "http", result: viaHttp };
|
|
350
548
|
}
|
|
351
549
|
|
|
352
550
|
// Hub 재시작 시도 → Pipe/HTTP 재시도
|
|
353
551
|
if (await tryRestartHub()) {
|
|
354
|
-
const retryPipe =
|
|
355
|
-
|
|
356
|
-
|
|
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:
|
|
557
|
+
return { transport: "pipe", result: retryPipe };
|
|
359
558
|
}
|
|
360
559
|
const retryHttp = operation.httpPath
|
|
361
560
|
? await requestJson(operation.httpPath, {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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:
|
|
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:
|
|
574
|
+
return { transport: "fallback", result: viaFallback };
|
|
376
575
|
}
|
|
377
576
|
|
|
378
577
|
function unavailableResult() {
|
|
379
|
-
return { ok: false, reason:
|
|
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 ||
|
|
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 ||
|
|
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(
|
|
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,
|
|
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[
|
|
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 ||
|
|
436
|
-
payload: args.payload
|
|
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 ||
|
|
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[
|
|
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(
|
|
468
|
-
max_messages: parseInt(args.max ||
|
|
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 =
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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(
|
|
716
|
+
const combined = parts.join("\n\n");
|
|
481
717
|
|
|
482
718
|
if (args.out) {
|
|
483
|
-
writeFileSync(args.out, combined,
|
|
484
|
-
return emitJson({
|
|
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({
|
|
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:
|
|
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[
|
|
516
|
-
worker_agent: args[
|
|
759
|
+
supervisor_agent: args["supervisor-agent"],
|
|
760
|
+
worker_agent: args["worker-agent"],
|
|
517
761
|
task: args.task,
|
|
518
|
-
topic: args.topic ||
|
|
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[
|
|
522
|
-
timeout_ms:
|
|
523
|
-
|
|
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[
|
|
534
|
-
worker_agent: args[
|
|
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[
|
|
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[
|
|
802
|
+
job_id: args["job-id"],
|
|
557
803
|
reason: args.reason,
|
|
558
|
-
requested_by: args[
|
|
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(
|
|
571
|
-
|
|
572
|
-
|
|
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
|
|
583
|
-
|
|
584
|
-
|
|
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(
|
|
587
|
-
|
|
588
|
-
|
|
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[
|
|
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[
|
|
604
|
-
add_blocks: args[
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
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(
|
|
611
|
-
|
|
612
|
-
|
|
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 ||
|
|
903
|
+
to: args.to || "team-lead",
|
|
623
904
|
text: args.text,
|
|
624
905
|
summary: args.summary,
|
|
625
|
-
color: args.color ||
|
|
906
|
+
color: args.color || "blue",
|
|
626
907
|
};
|
|
627
|
-
const outcome = await requestHub(
|
|
628
|
-
|
|
629
|
-
|
|
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(
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
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
|
-
|
|
649
|
-
|
|
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(
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
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
|
-
|
|
677
|
-
|
|
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[
|
|
693
|
-
ralph_max:
|
|
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(
|
|
1052
|
+
const outcome = await requestHub(
|
|
1053
|
+
HUB_OPERATIONS.hubStatus,
|
|
1054
|
+
{ scope: "hub" },
|
|
1055
|
+
2000,
|
|
1056
|
+
);
|
|
707
1057
|
|
|
708
|
-
if (outcome?.transport ===
|
|
1058
|
+
if (outcome?.transport === "pipe" && outcome.result?.ok) {
|
|
709
1059
|
return emitJson({
|
|
710
1060
|
ok: true,
|
|
711
|
-
hub: outcome.result.data?.hub?.state ||
|
|
1061
|
+
hub: outcome.result.data?.hub?.state || "healthy",
|
|
712
1062
|
pipe_path: getHubPipePath(),
|
|
713
|
-
transport:
|
|
1063
|
+
transport: "pipe",
|
|
714
1064
|
});
|
|
715
1065
|
}
|
|
716
1066
|
|
|
717
|
-
if (outcome?.transport ===
|
|
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:
|
|
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 ||
|
|
735
|
-
mode: args.mode ||
|
|
736
|
-
agent_type: args.agent ||
|
|
737
|
-
mcp_profile: args[
|
|
738
|
-
session_key: args[
|
|
739
|
-
timeout_ms:
|
|
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 ===
|
|
742
|
-
const outcome = await requestHub(
|
|
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[
|
|
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[
|
|
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
|
|
770
|
-
|
|
771
|
-
case
|
|
772
|
-
|
|
773
|
-
case
|
|
774
|
-
|
|
775
|
-
case
|
|
776
|
-
|
|
777
|
-
case
|
|
778
|
-
|
|
779
|
-
case
|
|
780
|
-
|
|
781
|
-
case
|
|
782
|
-
|
|
783
|
-
case
|
|
784
|
-
|
|
785
|
-
case
|
|
786
|
-
|
|
787
|
-
case
|
|
788
|
-
|
|
789
|
-
case
|
|
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(
|
|
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,
|
|
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
|
}
|