@triflux/core 10.1.0 → 10.26.0
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 +20 -8
- package/hooks/hook-adaptive-collector.mjs +18 -16
- package/hooks/hook-manager.mjs +93 -32
- package/hooks/hook-orchestrator.mjs +267 -35
- package/hooks/hook-registry.json +343 -256
- package/hooks/hooks.json +49 -1
- package/hooks/keyword-rules.json +191 -31
- 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 +262 -34
- 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 +1051 -290
- package/hub/cli-adapter-base.mjs +321 -97
- package/hub/codex-adapter.mjs +179 -93
- package/hub/codex-compat.mjs +4 -4
- package/hub/codex-preflight.mjs +113 -37
- package/hub/delegator/contracts.mjs +22 -21
- package/hub/delegator/index.mjs +14 -14
- package/hub/delegator/schema/delegator-tools.schema.json +1 -1
- package/hub/delegator/service.mjs +93 -64
- package/hub/delegator/tool-definitions.mjs +35 -35
- package/hub/dynamic-routing-engine.mjs +511 -0
- package/hub/fullcycle.mjs +34 -18
- package/hub/gemini-adapter.mjs +82 -108
- package/hub/hitl.mjs +202 -143
- package/hub/intent.mjs +167 -38
- 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 +285 -111
- 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 +87 -13
- package/hub/research.mjs +33 -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 +78 -30
- package/hub/routing-snapshot.mjs +261 -0
- package/hub/session-fingerprint.mjs +127 -61
- package/hub/state.mjs +86 -44
- package/hub/team/claude-daemon-control.mjs +1130 -0
- package/hub/team/retry-state-machine.mjs +336 -0
- package/hub/token-mode.mjs +72 -71
- package/hub/workers/worker-utils.mjs +23 -17
- package/hud/colors.mjs +27 -9
- package/hud/constants.mjs +190 -27
- package/hud/context-monitor.mjs +133 -44
- package/hud/hud-qos-status.mjs +157 -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 +210 -52
- package/hud/renderers.mjs +440 -127
- 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 +37 -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 +49 -20
- package/scripts/lib/logger.mjs +24 -24
- package/scripts/lib/mcp-filter.mjs +391 -247
- 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 +1281 -115
- 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 +117 -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 +32 -7
- 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
|
|
|
@@ -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[
|
|
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,103 @@ 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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
+
"payload-file": { type: "string" },
|
|
375
|
+
topic: { type: "string" },
|
|
376
|
+
trace: { type: "string" },
|
|
377
|
+
correlation: { type: "string" },
|
|
378
|
+
"exit-code": { type: "string" },
|
|
379
|
+
max: { type: "string" },
|
|
380
|
+
out: { type: "string" },
|
|
381
|
+
team: { type: "string" },
|
|
382
|
+
"task-id": { type: "string" },
|
|
383
|
+
"job-id": { type: "string" },
|
|
384
|
+
owner: { type: "string" },
|
|
385
|
+
status: { type: "string" },
|
|
386
|
+
statuses: { type: "string" },
|
|
387
|
+
claim: { type: "boolean" },
|
|
388
|
+
actor: { type: "string" },
|
|
389
|
+
command: { type: "string" },
|
|
390
|
+
"session-id": { type: "string" },
|
|
391
|
+
reason: { type: "string" },
|
|
392
|
+
type: { type: "string" },
|
|
393
|
+
from: { type: "string" },
|
|
394
|
+
to: { type: "string" },
|
|
395
|
+
text: { type: "string" },
|
|
396
|
+
task: { type: "string" },
|
|
397
|
+
"supervisor-agent": { type: "string" },
|
|
398
|
+
"worker-agent": { type: "string" },
|
|
399
|
+
priority: { type: "string" },
|
|
400
|
+
"ttl-ms": { type: "string" },
|
|
401
|
+
"timeout-ms": { type: "string" },
|
|
402
|
+
"max-retries": { type: "string" },
|
|
403
|
+
attempt: { type: "string" },
|
|
404
|
+
result: { type: "string" },
|
|
405
|
+
error: { type: "string" },
|
|
406
|
+
metadata: { type: "string" },
|
|
407
|
+
"requested-by": { type: "string" },
|
|
408
|
+
summary: { type: "string" },
|
|
409
|
+
color: { type: "string" },
|
|
410
|
+
limit: { type: "string" },
|
|
411
|
+
"include-internal": { type: "boolean" },
|
|
412
|
+
subject: { type: "string" },
|
|
413
|
+
description: { type: "string" },
|
|
414
|
+
"fix-max": { type: "string" },
|
|
415
|
+
"ralph-max": { type: "string" },
|
|
416
|
+
"active-form": { type: "string" },
|
|
417
|
+
"add-blocks": { type: "string" },
|
|
418
|
+
"add-blocked-by": { type: "string" },
|
|
419
|
+
"metadata-patch": { type: "string" },
|
|
420
|
+
"if-match-mtime-ms": { type: "string" },
|
|
421
|
+
provider: { type: "string" },
|
|
422
|
+
mode: { type: "string" },
|
|
423
|
+
prompt: { type: "string" },
|
|
424
|
+
reply: { type: "string" },
|
|
425
|
+
done: { type: "boolean" },
|
|
426
|
+
"mcp-profile": { type: "string" },
|
|
427
|
+
"session-key": { type: "string" },
|
|
428
|
+
snapshot: { type: "string" },
|
|
429
|
+
"snapshot-file": { type: "string" },
|
|
430
|
+
event: { type: "string" },
|
|
431
|
+
"max-iterations": { type: "string" },
|
|
273
432
|
},
|
|
433
|
+
allowPositionals: true,
|
|
274
434
|
strict: false,
|
|
275
435
|
});
|
|
276
|
-
|
|
436
|
+
const parsed = { ...values, _: positionals };
|
|
437
|
+
positionals.forEach((value, index) => {
|
|
438
|
+
parsed[index + 1] = value;
|
|
439
|
+
});
|
|
440
|
+
return parsed;
|
|
277
441
|
}
|
|
278
442
|
|
|
279
443
|
export function parseJsonSafe(raw, fallback = null) {
|
|
@@ -285,29 +449,78 @@ export function parseJsonSafe(raw, fallback = null) {
|
|
|
285
449
|
}
|
|
286
450
|
}
|
|
287
451
|
|
|
452
|
+
export function readBridgePayload(args = {}, stdin) {
|
|
453
|
+
if (args["payload-file"]) {
|
|
454
|
+
const payloadFile = String(args["payload-file"]);
|
|
455
|
+
const raw =
|
|
456
|
+
payloadFile === "-"
|
|
457
|
+
? stdin === undefined
|
|
458
|
+
? readFileSync(0, "utf8")
|
|
459
|
+
: String(stdin)
|
|
460
|
+
: readFileSync(payloadFile, "utf8");
|
|
461
|
+
return JSON.parse(raw);
|
|
462
|
+
}
|
|
463
|
+
if (args.payload) return JSON.parse(args.payload);
|
|
464
|
+
return {};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function normalizeBridgePayload(payload) {
|
|
468
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
469
|
+
return {};
|
|
470
|
+
}
|
|
471
|
+
return payload;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function buildHandoffBody(from, to, payload) {
|
|
475
|
+
const normalizedPayload = normalizeBridgePayload(payload);
|
|
476
|
+
return {
|
|
477
|
+
...normalizedPayload,
|
|
478
|
+
from,
|
|
479
|
+
to,
|
|
480
|
+
payload: normalizedPayload,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function buildPublishBody(from, to, type, payload) {
|
|
485
|
+
const normalizedPayload = normalizeBridgePayload(payload);
|
|
486
|
+
return {
|
|
487
|
+
...normalizedPayload,
|
|
488
|
+
from,
|
|
489
|
+
to,
|
|
490
|
+
type,
|
|
491
|
+
message_type: type,
|
|
492
|
+
payload: normalizedPayload,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
288
496
|
// Hub 자동 재시작 (Pipe+HTTP 모두 실패 시 1회 시도, 최대 4초 대기)
|
|
289
497
|
async function tryRestartHub() {
|
|
290
|
-
const serverPath = join(PROJECT_ROOT,
|
|
498
|
+
const serverPath = join(PROJECT_ROOT, "hub", "server.mjs");
|
|
291
499
|
if (!existsSync(serverPath)) return false;
|
|
292
500
|
|
|
293
501
|
if (existsSync(HUB_PID_FILE)) {
|
|
294
502
|
try {
|
|
295
|
-
const info = JSON.parse(readFileSync(HUB_PID_FILE,
|
|
503
|
+
const info = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
|
|
296
504
|
if (info.pid) {
|
|
297
|
-
try {
|
|
298
|
-
|
|
505
|
+
try {
|
|
506
|
+
process.kill(info.pid, 0);
|
|
507
|
+
return false;
|
|
508
|
+
} catch (e) {
|
|
509
|
+
// still alive
|
|
510
|
+
if (e.code === "EPERM") return false;
|
|
511
|
+
} // alive, no permission
|
|
299
512
|
}
|
|
300
513
|
} catch {} // corrupt PID file, proceed with restart
|
|
301
514
|
}
|
|
302
515
|
|
|
303
516
|
try {
|
|
304
|
-
const logDir = join(process.cwd(),
|
|
517
|
+
const logDir = join(process.cwd(), ".tfx", "logs");
|
|
305
518
|
if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
|
|
306
|
-
const logFile = join(logDir,
|
|
307
|
-
const logFd = openSync(logFile,
|
|
519
|
+
const logFile = join(logDir, "hub-restart.log");
|
|
520
|
+
const logFd = openSync(logFile, "a");
|
|
308
521
|
const child = spawn(process.execPath, [serverPath], {
|
|
309
522
|
detached: true,
|
|
310
|
-
stdio: [
|
|
523
|
+
stdio: ["ignore", "ignore", logFd],
|
|
311
524
|
windowsHide: true,
|
|
312
525
|
});
|
|
313
526
|
child.unref();
|
|
@@ -323,7 +536,7 @@ async function tryRestartHub() {
|
|
|
323
536
|
});
|
|
324
537
|
if (res.ok) {
|
|
325
538
|
const data = await res.json();
|
|
326
|
-
if (data?.hub?.state ===
|
|
539
|
+
if (data?.hub?.state === "healthy") return true;
|
|
327
540
|
}
|
|
328
541
|
} catch {}
|
|
329
542
|
}
|
|
@@ -331,52 +544,54 @@ async function tryRestartHub() {
|
|
|
331
544
|
}
|
|
332
545
|
|
|
333
546
|
async function requestHub(operation, body, timeoutMs = 3000, fallback = null) {
|
|
334
|
-
const viaPipe =
|
|
335
|
-
|
|
336
|
-
|
|
547
|
+
const viaPipe =
|
|
548
|
+
operation.transport === "command"
|
|
549
|
+
? await pipeCommand(operation.action, body, timeoutMs)
|
|
550
|
+
: await pipeQuery(operation.action, body, timeoutMs);
|
|
337
551
|
if (viaPipe) {
|
|
338
|
-
return { transport:
|
|
552
|
+
return { transport: "pipe", result: viaPipe };
|
|
339
553
|
}
|
|
340
554
|
|
|
341
555
|
const viaHttp = operation.httpPath
|
|
342
556
|
? await requestJson(operation.httpPath, {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
557
|
+
method: operation.httpMethod || "POST",
|
|
558
|
+
body: operation.httpMethod === "GET" ? undefined : body,
|
|
559
|
+
timeoutMs: Math.max(timeoutMs, 5000),
|
|
560
|
+
})
|
|
347
561
|
: null;
|
|
348
562
|
if (viaHttp) {
|
|
349
|
-
return { transport:
|
|
563
|
+
return { transport: "http", result: viaHttp };
|
|
350
564
|
}
|
|
351
565
|
|
|
352
566
|
// Hub 재시작 시도 → Pipe/HTTP 재시도
|
|
353
567
|
if (await tryRestartHub()) {
|
|
354
|
-
const retryPipe =
|
|
355
|
-
|
|
356
|
-
|
|
568
|
+
const retryPipe =
|
|
569
|
+
operation.transport === "command"
|
|
570
|
+
? await pipeCommand(operation.action, body, timeoutMs)
|
|
571
|
+
: await pipeQuery(operation.action, body, timeoutMs);
|
|
357
572
|
if (retryPipe) {
|
|
358
|
-
return { transport:
|
|
573
|
+
return { transport: "pipe", result: retryPipe };
|
|
359
574
|
}
|
|
360
575
|
const retryHttp = operation.httpPath
|
|
361
576
|
? await requestJson(operation.httpPath, {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
577
|
+
method: operation.httpMethod || "POST",
|
|
578
|
+
body: operation.httpMethod === "GET" ? undefined : body,
|
|
579
|
+
timeoutMs: Math.max(timeoutMs, 5000),
|
|
580
|
+
})
|
|
366
581
|
: null;
|
|
367
582
|
if (retryHttp) {
|
|
368
|
-
return { transport:
|
|
583
|
+
return { transport: "http", result: retryHttp };
|
|
369
584
|
}
|
|
370
585
|
}
|
|
371
586
|
|
|
372
587
|
if (!fallback) return null;
|
|
373
588
|
const viaFallback = await fallback();
|
|
374
589
|
if (!viaFallback) return null;
|
|
375
|
-
return { transport:
|
|
590
|
+
return { transport: "fallback", result: viaFallback };
|
|
376
591
|
}
|
|
377
592
|
|
|
378
593
|
function unavailableResult() {
|
|
379
|
-
return { ok: false, reason:
|
|
594
|
+
return { ok: false, reason: "hub_unavailable" };
|
|
380
595
|
}
|
|
381
596
|
|
|
382
597
|
function emitJson(payload) {
|
|
@@ -388,14 +603,14 @@ function emitJson(payload) {
|
|
|
388
603
|
|
|
389
604
|
async function cmdRegister(args) {
|
|
390
605
|
const agentId = args.agent;
|
|
391
|
-
const timeoutSec = parseInt(args.timeout ||
|
|
606
|
+
const timeoutSec = parseInt(args.timeout || "600", 10);
|
|
392
607
|
const outcome = await requestHub(HUB_OPERATIONS.register, {
|
|
393
608
|
agent_id: agentId,
|
|
394
|
-
cli: args.cli ||
|
|
609
|
+
cli: args.cli || "other",
|
|
395
610
|
timeout_sec: timeoutSec,
|
|
396
611
|
heartbeat_ttl_ms: (timeoutSec + 120) * 1000,
|
|
397
|
-
topics: args.topics ? args.topics.split(
|
|
398
|
-
capabilities: args.capabilities ? args.capabilities.split(
|
|
612
|
+
topics: args.topics ? args.topics.split(",") : [],
|
|
613
|
+
capabilities: args.capabilities ? args.capabilities.split(",") : ["code"],
|
|
399
614
|
metadata: {
|
|
400
615
|
pid: process.ppid,
|
|
401
616
|
registered_at: Date.now(),
|
|
@@ -416,14 +631,14 @@ async function cmdRegister(args) {
|
|
|
416
631
|
}
|
|
417
632
|
|
|
418
633
|
async function cmdResult(args) {
|
|
419
|
-
let output =
|
|
634
|
+
let output = "";
|
|
420
635
|
if (args.file && existsSync(args.file)) {
|
|
421
|
-
output = readFileSync(args.file,
|
|
636
|
+
output = readFileSync(args.file, "utf8").slice(0, 49152);
|
|
422
637
|
}
|
|
423
638
|
|
|
424
639
|
const defaultPayload = {
|
|
425
640
|
agent_id: args.agent,
|
|
426
|
-
exit_code: parseInt(args[
|
|
641
|
+
exit_code: parseInt(args["exit-code"] || "0", 10),
|
|
427
642
|
output_length: output.length,
|
|
428
643
|
output_preview: output.slice(0, 4096),
|
|
429
644
|
output_file: args.file || null,
|
|
@@ -432,8 +647,10 @@ async function cmdResult(args) {
|
|
|
432
647
|
|
|
433
648
|
const outcome = await requestHub(HUB_OPERATIONS.result, {
|
|
434
649
|
agent_id: args.agent,
|
|
435
|
-
topic: args.topic ||
|
|
436
|
-
payload: args.payload
|
|
650
|
+
topic: args.topic || "task.result",
|
|
651
|
+
payload: args.payload
|
|
652
|
+
? parseJsonSafe(args.payload, defaultPayload)
|
|
653
|
+
: defaultPayload,
|
|
437
654
|
trace_id: args.trace || undefined,
|
|
438
655
|
correlation_id: args.correlation || undefined,
|
|
439
656
|
});
|
|
@@ -448,14 +665,48 @@ async function cmdResult(args) {
|
|
|
448
665
|
|
|
449
666
|
async function cmdControl(args) {
|
|
450
667
|
const outcome = await requestHub(HUB_OPERATIONS.control, {
|
|
451
|
-
from_agent: args.from ||
|
|
668
|
+
from_agent: args.from || "lead",
|
|
452
669
|
to_agent: args.to,
|
|
453
670
|
command: args.command,
|
|
454
|
-
reason: args.reason ||
|
|
671
|
+
reason: args.reason || "",
|
|
455
672
|
payload: args.payload ? parseJsonSafe(args.payload, {}) : {},
|
|
456
673
|
trace_id: args.trace || undefined,
|
|
457
674
|
correlation_id: args.correlation || undefined,
|
|
458
|
-
ttl_ms: args[
|
|
675
|
+
ttl_ms: args["ttl-ms"] != null ? Number(args["ttl-ms"]) : undefined,
|
|
676
|
+
});
|
|
677
|
+
const result = outcome?.result;
|
|
678
|
+
return emitJson(result || unavailableResult());
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
async function cmdHandoff(args) {
|
|
682
|
+
const from = args.from || args[1];
|
|
683
|
+
const to = args.to || args[2];
|
|
684
|
+
const payload = parseJsonSafe(args.payload || args[3] || "{}", {});
|
|
685
|
+
const outcome = await requestHub(
|
|
686
|
+
HUB_OPERATIONS.handoff,
|
|
687
|
+
buildHandoffBody(from, to, payload),
|
|
688
|
+
);
|
|
689
|
+
const result = outcome?.result;
|
|
690
|
+
return emitJson(result || unavailableResult());
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
async function cmdPublish(args) {
|
|
694
|
+
const from = args.from || args[1];
|
|
695
|
+
const to = args.to || args[2];
|
|
696
|
+
const type = args.type || args[3] || "event";
|
|
697
|
+
const payload = parseJsonSafe(args.payload || args[4] || "{}", {});
|
|
698
|
+
const outcome = await requestHub(
|
|
699
|
+
HUB_OPERATIONS.publish,
|
|
700
|
+
buildPublishBody(from, to, type, payload),
|
|
701
|
+
);
|
|
702
|
+
const result = outcome?.result;
|
|
703
|
+
return emitJson(result || unavailableResult());
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
async function cmdSendInput(args) {
|
|
707
|
+
const outcome = await requestHub(HUB_OPERATIONS.sendInput, {
|
|
708
|
+
session_id: args["session-id"],
|
|
709
|
+
text: args.text,
|
|
459
710
|
});
|
|
460
711
|
const result = outcome?.result;
|
|
461
712
|
return emitJson(result || unavailableResult());
|
|
@@ -464,26 +715,35 @@ async function cmdControl(args) {
|
|
|
464
715
|
async function cmdContext(args) {
|
|
465
716
|
const outcome = await requestHub(HUB_OPERATIONS.context, {
|
|
466
717
|
agent_id: args.agent,
|
|
467
|
-
topics: args.topics ? args.topics.split(
|
|
468
|
-
max_messages: parseInt(args.max ||
|
|
718
|
+
topics: args.topics ? args.topics.split(",") : undefined,
|
|
719
|
+
max_messages: parseInt(args.max || "10", 10),
|
|
469
720
|
auto_ack: true,
|
|
470
721
|
});
|
|
471
722
|
const result = outcome?.result;
|
|
472
723
|
|
|
473
724
|
if (result?.ok && result.data?.messages?.length) {
|
|
474
725
|
const parts = result.data.messages.map((message, index) => {
|
|
475
|
-
const payload =
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
726
|
+
const payload =
|
|
727
|
+
typeof message.payload === "string"
|
|
728
|
+
? message.payload
|
|
729
|
+
: JSON.stringify(message.payload, null, 2);
|
|
730
|
+
return `=== Context ${index + 1}: ${message.from_agent || "unknown"} (${message.topic || "unknown"}) ===\n${payload}`;
|
|
479
731
|
});
|
|
480
|
-
const combined = parts.join(
|
|
732
|
+
const combined = parts.join("\n\n");
|
|
481
733
|
|
|
482
734
|
if (args.out) {
|
|
483
|
-
writeFileSync(args.out, combined,
|
|
484
|
-
return emitJson({
|
|
735
|
+
writeFileSync(args.out, combined, "utf8");
|
|
736
|
+
return emitJson({
|
|
737
|
+
ok: true,
|
|
738
|
+
count: result.data.messages.length,
|
|
739
|
+
file: args.out,
|
|
740
|
+
});
|
|
485
741
|
} else {
|
|
486
|
-
return emitJson({
|
|
742
|
+
return emitJson({
|
|
743
|
+
ok: true,
|
|
744
|
+
count: result.data.messages.length,
|
|
745
|
+
context: combined,
|
|
746
|
+
});
|
|
487
747
|
}
|
|
488
748
|
}
|
|
489
749
|
|
|
@@ -491,7 +751,7 @@ async function cmdContext(args) {
|
|
|
491
751
|
if (args.out) {
|
|
492
752
|
return emitJson({ ok: true, count: 0 });
|
|
493
753
|
}
|
|
494
|
-
return emitJson({ ok: true, count: 0, context:
|
|
754
|
+
return emitJson({ ok: true, count: 0, context: "" });
|
|
495
755
|
}
|
|
496
756
|
|
|
497
757
|
return emitJson(result || unavailableResult());
|
|
@@ -504,7 +764,7 @@ async function cmdDeregister(args) {
|
|
|
504
764
|
const result = outcome?.result;
|
|
505
765
|
|
|
506
766
|
if (result?.ok) {
|
|
507
|
-
return emitJson({ ok: true, agent_id: args.agent, status:
|
|
767
|
+
return emitJson({ ok: true, agent_id: args.agent, status: "offline" });
|
|
508
768
|
}
|
|
509
769
|
|
|
510
770
|
return emitJson(result || unavailableResult());
|
|
@@ -512,15 +772,17 @@ async function cmdDeregister(args) {
|
|
|
512
772
|
|
|
513
773
|
async function cmdAssignAsync(args) {
|
|
514
774
|
const outcome = await requestHub(HUB_OPERATIONS.assignAsync, {
|
|
515
|
-
supervisor_agent: args[
|
|
516
|
-
worker_agent: args[
|
|
775
|
+
supervisor_agent: args["supervisor-agent"],
|
|
776
|
+
worker_agent: args["worker-agent"],
|
|
517
777
|
task: args.task,
|
|
518
|
-
topic: args.topic ||
|
|
778
|
+
topic: args.topic || "assign.job",
|
|
519
779
|
payload: args.payload ? parseJsonSafe(args.payload, {}) : {},
|
|
520
780
|
priority: args.priority != null ? Number(args.priority) : undefined,
|
|
521
|
-
ttl_ms: args[
|
|
522
|
-
timeout_ms:
|
|
523
|
-
|
|
781
|
+
ttl_ms: args["ttl-ms"] != null ? Number(args["ttl-ms"]) : undefined,
|
|
782
|
+
timeout_ms:
|
|
783
|
+
args["timeout-ms"] != null ? Number(args["timeout-ms"]) : undefined,
|
|
784
|
+
max_retries:
|
|
785
|
+
args["max-retries"] != null ? Number(args["max-retries"]) : undefined,
|
|
524
786
|
trace_id: args.trace || undefined,
|
|
525
787
|
correlation_id: args.correlation || undefined,
|
|
526
788
|
});
|
|
@@ -530,8 +792,8 @@ async function cmdAssignAsync(args) {
|
|
|
530
792
|
|
|
531
793
|
async function cmdAssignResult(args) {
|
|
532
794
|
const outcome = await requestHub(HUB_OPERATIONS.assignResult, {
|
|
533
|
-
job_id: args[
|
|
534
|
-
worker_agent: args[
|
|
795
|
+
job_id: args["job-id"],
|
|
796
|
+
worker_agent: args["worker-agent"],
|
|
535
797
|
status: args.status,
|
|
536
798
|
attempt: args.attempt != null ? Number(args.attempt) : undefined,
|
|
537
799
|
result: args.result ? parseJsonSafe(args.result, null) : undefined,
|
|
@@ -545,7 +807,7 @@ async function cmdAssignResult(args) {
|
|
|
545
807
|
|
|
546
808
|
async function cmdAssignStatus(args) {
|
|
547
809
|
const outcome = await requestHub(HUB_OPERATIONS.assignStatus, {
|
|
548
|
-
job_id: args[
|
|
810
|
+
job_id: args["job-id"],
|
|
549
811
|
});
|
|
550
812
|
const result = outcome?.result;
|
|
551
813
|
return emitJson(result || unavailableResult());
|
|
@@ -553,9 +815,9 @@ async function cmdAssignStatus(args) {
|
|
|
553
815
|
|
|
554
816
|
async function cmdAssignRetry(args) {
|
|
555
817
|
const outcome = await requestHub(HUB_OPERATIONS.assignRetry, {
|
|
556
|
-
job_id: args[
|
|
818
|
+
job_id: args["job-id"],
|
|
557
819
|
reason: args.reason,
|
|
558
|
-
requested_by: args[
|
|
820
|
+
requested_by: args["requested-by"],
|
|
559
821
|
});
|
|
560
822
|
const result = outcome?.result;
|
|
561
823
|
return emitJson(result || unavailableResult());
|
|
@@ -567,10 +829,15 @@ async function cmdTeamInfo(args) {
|
|
|
567
829
|
include_members: true,
|
|
568
830
|
include_paths: true,
|
|
569
831
|
};
|
|
570
|
-
const outcome = await requestHub(
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
832
|
+
const outcome = await requestHub(
|
|
833
|
+
HUB_OPERATIONS.teamInfo,
|
|
834
|
+
body,
|
|
835
|
+
3000,
|
|
836
|
+
async () => {
|
|
837
|
+
const { teamInfo } = await import("./team/nativeProxy.mjs");
|
|
838
|
+
return await teamInfo(body);
|
|
839
|
+
},
|
|
840
|
+
);
|
|
574
841
|
const result = outcome?.result;
|
|
575
842
|
return emitJson(result || unavailableResult());
|
|
576
843
|
}
|
|
@@ -579,14 +846,24 @@ async function cmdTeamTaskList(args) {
|
|
|
579
846
|
const body = {
|
|
580
847
|
team_name: args.team,
|
|
581
848
|
owner: args.owner,
|
|
582
|
-
statuses: args.statuses
|
|
583
|
-
|
|
584
|
-
|
|
849
|
+
statuses: args.statuses
|
|
850
|
+
? args.statuses
|
|
851
|
+
.split(",")
|
|
852
|
+
.map((status) => status.trim())
|
|
853
|
+
.filter(Boolean)
|
|
854
|
+
: [],
|
|
855
|
+
include_internal: !!args["include-internal"],
|
|
856
|
+
limit: parseInt(args.limit || "200", 10),
|
|
585
857
|
};
|
|
586
|
-
const outcome = await requestHub(
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
858
|
+
const outcome = await requestHub(
|
|
859
|
+
HUB_OPERATIONS.teamTaskList,
|
|
860
|
+
body,
|
|
861
|
+
3000,
|
|
862
|
+
async () => {
|
|
863
|
+
const { teamTaskList } = await import("./team/nativeProxy.mjs");
|
|
864
|
+
return await teamTaskList(body);
|
|
865
|
+
},
|
|
866
|
+
);
|
|
590
867
|
const result = outcome?.result;
|
|
591
868
|
return emitJson(result || unavailableResult());
|
|
592
869
|
}
|
|
@@ -594,23 +871,43 @@ async function cmdTeamTaskList(args) {
|
|
|
594
871
|
async function cmdTeamTaskUpdate(args) {
|
|
595
872
|
const body = {
|
|
596
873
|
team_name: args.team,
|
|
597
|
-
task_id: args[
|
|
874
|
+
task_id: args["task-id"],
|
|
598
875
|
claim: !!args.claim,
|
|
599
876
|
owner: args.owner,
|
|
600
877
|
status: args.status,
|
|
601
878
|
subject: args.subject,
|
|
602
879
|
description: args.description,
|
|
603
|
-
activeForm: args[
|
|
604
|
-
add_blocks: args[
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
880
|
+
activeForm: args["active-form"],
|
|
881
|
+
add_blocks: args["add-blocks"]
|
|
882
|
+
? args["add-blocks"]
|
|
883
|
+
.split(",")
|
|
884
|
+
.map((value) => value.trim())
|
|
885
|
+
.filter(Boolean)
|
|
886
|
+
: undefined,
|
|
887
|
+
add_blocked_by: args["add-blocked-by"]
|
|
888
|
+
? args["add-blocked-by"]
|
|
889
|
+
.split(",")
|
|
890
|
+
.map((value) => value.trim())
|
|
891
|
+
.filter(Boolean)
|
|
892
|
+
: undefined,
|
|
893
|
+
metadata_patch: args["metadata-patch"]
|
|
894
|
+
? parseJsonSafe(args["metadata-patch"], null)
|
|
895
|
+
: undefined,
|
|
896
|
+
if_match_mtime_ms:
|
|
897
|
+
args["if-match-mtime-ms"] != null
|
|
898
|
+
? Number(args["if-match-mtime-ms"])
|
|
899
|
+
: undefined,
|
|
608
900
|
actor: args.actor,
|
|
609
901
|
};
|
|
610
|
-
const outcome = await requestHub(
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
902
|
+
const outcome = await requestHub(
|
|
903
|
+
HUB_OPERATIONS.teamTaskUpdate,
|
|
904
|
+
body,
|
|
905
|
+
3000,
|
|
906
|
+
async () => {
|
|
907
|
+
const { teamTaskUpdate } = await import("./team/nativeProxy.mjs");
|
|
908
|
+
return await teamTaskUpdate(body);
|
|
909
|
+
},
|
|
910
|
+
);
|
|
614
911
|
const result = outcome?.result;
|
|
615
912
|
return emitJson(result || unavailableResult());
|
|
616
913
|
}
|
|
@@ -619,15 +916,20 @@ async function cmdTeamSendMessage(args) {
|
|
|
619
916
|
const body = {
|
|
620
917
|
team_name: args.team,
|
|
621
918
|
from: args.from,
|
|
622
|
-
to: args.to ||
|
|
919
|
+
to: args.to || "team-lead",
|
|
623
920
|
text: args.text,
|
|
624
921
|
summary: args.summary,
|
|
625
|
-
color: args.color ||
|
|
922
|
+
color: args.color || "blue",
|
|
626
923
|
};
|
|
627
|
-
const outcome = await requestHub(
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
924
|
+
const outcome = await requestHub(
|
|
925
|
+
HUB_OPERATIONS.teamSendMessage,
|
|
926
|
+
body,
|
|
927
|
+
3000,
|
|
928
|
+
async () => {
|
|
929
|
+
const { teamSendMessage } = await import("./team/nativeProxy.mjs");
|
|
930
|
+
return await teamSendMessage(body);
|
|
931
|
+
},
|
|
932
|
+
);
|
|
631
933
|
const result = outcome?.result;
|
|
632
934
|
return emitJson(result || unavailableResult());
|
|
633
935
|
}
|
|
@@ -637,62 +939,122 @@ function getHubDbPath() {
|
|
|
637
939
|
}
|
|
638
940
|
|
|
639
941
|
async function cmdPipelineState(args) {
|
|
640
|
-
const outcome = await requestHub(
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
942
|
+
const outcome = await requestHub(
|
|
943
|
+
HUB_OPERATIONS.pipelineState,
|
|
944
|
+
{ team_name: args.team },
|
|
945
|
+
3000,
|
|
946
|
+
async () => {
|
|
947
|
+
try {
|
|
948
|
+
const { default: Database } = await import("better-sqlite3");
|
|
949
|
+
const { ensurePipelineTable, readPipelineState } = await import(
|
|
950
|
+
"./pipeline/state.mjs"
|
|
951
|
+
);
|
|
952
|
+
const dbPath = getHubDbPath();
|
|
953
|
+
if (!existsSync(dbPath)) {
|
|
954
|
+
return { ok: false, error: "hub_db_not_found" };
|
|
955
|
+
}
|
|
956
|
+
const db = new Database(dbPath, { readonly: true });
|
|
957
|
+
ensurePipelineTable(db);
|
|
958
|
+
const state = readPipelineState(db, args.team);
|
|
959
|
+
db.close();
|
|
960
|
+
return state
|
|
961
|
+
? { ok: true, data: state }
|
|
962
|
+
: { ok: false, error: "pipeline_not_found" };
|
|
963
|
+
} catch (e) {
|
|
964
|
+
return { ok: false, error: e.message };
|
|
647
965
|
}
|
|
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
|
-
});
|
|
966
|
+
},
|
|
967
|
+
);
|
|
659
968
|
const result = outcome?.result;
|
|
660
969
|
return emitJson(result || unavailableResult());
|
|
661
970
|
}
|
|
662
971
|
|
|
972
|
+
// Best-effort phase-manager bridge (silent on failure — pipeline 동작 영향 X)
|
|
973
|
+
async function writePhaseSafely(runId, phase, status) {
|
|
974
|
+
if (!runId) return;
|
|
975
|
+
try {
|
|
976
|
+
const mod = await import("./lib/phase-manager.mjs");
|
|
977
|
+
if (typeof mod?.writePhase === "function") {
|
|
978
|
+
await mod.writePhase(runId, phase, status);
|
|
979
|
+
}
|
|
980
|
+
if (typeof mod?.syncToGstack === "function") {
|
|
981
|
+
const slug = process.cwd().split(/[\\/]/).pop();
|
|
982
|
+
await mod.syncToGstack(runId, slug);
|
|
983
|
+
}
|
|
984
|
+
} catch {
|
|
985
|
+
/* silent — phase tracking is best-effort */
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
async function writePhaseSafelyFromLegacy(runId, legacyStatus) {
|
|
990
|
+
if (!runId || !legacyStatus) return;
|
|
991
|
+
try {
|
|
992
|
+
const mod = await import("./lib/phase-manager.mjs");
|
|
993
|
+
if (typeof mod?.coerceLegacyPhase !== "function") return;
|
|
994
|
+
const coerced = mod.coerceLegacyPhase(legacyStatus);
|
|
995
|
+
if (!coerced) return;
|
|
996
|
+
if (coerced === "complete") {
|
|
997
|
+
const current =
|
|
998
|
+
typeof mod.readPhase === "function" ? await mod.readPhase(runId) : null;
|
|
999
|
+
const lastPhase = current?.phase || current?.lastPhase;
|
|
1000
|
+
if (lastPhase) await mod.writePhase(runId, lastPhase, "complete");
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
await mod.writePhase(runId, coerced, "active");
|
|
1004
|
+
if (typeof mod?.syncToGstack === "function") {
|
|
1005
|
+
const slug = process.cwd().split(/[\\/]/).pop();
|
|
1006
|
+
await mod.syncToGstack(runId, slug);
|
|
1007
|
+
}
|
|
1008
|
+
} catch {
|
|
1009
|
+
/* silent */
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
663
1013
|
async function cmdPipelineAdvance(args) {
|
|
664
1014
|
const body = {
|
|
665
1015
|
team_name: args.team,
|
|
666
1016
|
phase: args.status, // --status를 phase로 재활용
|
|
667
1017
|
};
|
|
668
|
-
const outcome = await requestHub(
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
1018
|
+
const outcome = await requestHub(
|
|
1019
|
+
HUB_OPERATIONS.pipelineAdvance,
|
|
1020
|
+
body,
|
|
1021
|
+
3000,
|
|
1022
|
+
async () => {
|
|
1023
|
+
try {
|
|
1024
|
+
const { default: Database } = await import("better-sqlite3");
|
|
1025
|
+
const { createPipeline } = await import("./pipeline/index.mjs");
|
|
1026
|
+
const dbPath = getHubDbPath();
|
|
1027
|
+
if (!existsSync(dbPath)) {
|
|
1028
|
+
return { ok: false, error: "hub_db_not_found" };
|
|
1029
|
+
}
|
|
1030
|
+
const db = new Database(dbPath);
|
|
1031
|
+
const pipeline = createPipeline(db, args.team);
|
|
1032
|
+
const advanceResult = pipeline.advance(args.status);
|
|
1033
|
+
db.close();
|
|
1034
|
+
return advanceResult;
|
|
1035
|
+
} catch (e) {
|
|
1036
|
+
return { ok: false, error: e.message };
|
|
675
1037
|
}
|
|
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
|
-
});
|
|
1038
|
+
},
|
|
1039
|
+
);
|
|
685
1040
|
const result = outcome?.result;
|
|
1041
|
+
if (args.team && args.status && result?.ok !== false) {
|
|
1042
|
+
void writePhaseSafelyFromLegacy(args.team, args.status);
|
|
1043
|
+
}
|
|
686
1044
|
return emitJson(result || unavailableResult());
|
|
687
1045
|
}
|
|
688
1046
|
|
|
689
1047
|
async function cmdPipelineInit(args) {
|
|
690
1048
|
const outcome = await requestHub(HUB_OPERATIONS.pipelineInit, {
|
|
691
1049
|
team_name: args.team,
|
|
692
|
-
fix_max: args[
|
|
693
|
-
ralph_max:
|
|
1050
|
+
fix_max: args["fix-max"] != null ? Number(args["fix-max"]) : undefined,
|
|
1051
|
+
ralph_max:
|
|
1052
|
+
args["ralph-max"] != null ? Number(args["ralph-max"]) : undefined,
|
|
694
1053
|
});
|
|
695
1054
|
const result = outcome?.result;
|
|
1055
|
+
if (args.team && result?.ok !== false) {
|
|
1056
|
+
void writePhaseSafely(args.team, "Strategy", "active");
|
|
1057
|
+
}
|
|
696
1058
|
return emitJson(result || unavailableResult());
|
|
697
1059
|
}
|
|
698
1060
|
|
|
@@ -703,25 +1065,29 @@ async function cmdPipelineList() {
|
|
|
703
1065
|
}
|
|
704
1066
|
|
|
705
1067
|
async function cmdPing() {
|
|
706
|
-
const outcome = await requestHub(
|
|
1068
|
+
const outcome = await requestHub(
|
|
1069
|
+
HUB_OPERATIONS.hubStatus,
|
|
1070
|
+
{ scope: "hub" },
|
|
1071
|
+
2000,
|
|
1072
|
+
);
|
|
707
1073
|
|
|
708
|
-
if (outcome?.transport ===
|
|
1074
|
+
if (outcome?.transport === "pipe" && outcome.result?.ok) {
|
|
709
1075
|
return emitJson({
|
|
710
1076
|
ok: true,
|
|
711
|
-
hub: outcome.result.data?.hub?.state ||
|
|
1077
|
+
hub: outcome.result.data?.hub?.state || "healthy",
|
|
712
1078
|
pipe_path: getHubPipePath(),
|
|
713
|
-
transport:
|
|
1079
|
+
transport: "pipe",
|
|
714
1080
|
});
|
|
715
1081
|
}
|
|
716
1082
|
|
|
717
|
-
if (outcome?.transport ===
|
|
1083
|
+
if (outcome?.transport === "http" && outcome.result) {
|
|
718
1084
|
const data = outcome.result;
|
|
719
1085
|
return emitJson({
|
|
720
1086
|
ok: true,
|
|
721
1087
|
hub: data.hub?.state,
|
|
722
1088
|
sessions: data.sessions,
|
|
723
1089
|
pipe_path: data.pipe?.path || data.pipe_path || null,
|
|
724
|
-
transport:
|
|
1090
|
+
transport: "http",
|
|
725
1091
|
});
|
|
726
1092
|
}
|
|
727
1093
|
|
|
@@ -731,21 +1097,26 @@ async function cmdPing() {
|
|
|
731
1097
|
async function cmdDelegatorDelegate(args) {
|
|
732
1098
|
const body = {
|
|
733
1099
|
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:
|
|
1100
|
+
provider: args.provider || "auto",
|
|
1101
|
+
mode: args.mode || "sync",
|
|
1102
|
+
agent_type: args.agent || "executor",
|
|
1103
|
+
mcp_profile: args["mcp-profile"] || "auto",
|
|
1104
|
+
session_key: args["session-key"] || undefined,
|
|
1105
|
+
timeout_ms:
|
|
1106
|
+
args["timeout-ms"] != null ? Number(args["timeout-ms"]) : undefined,
|
|
740
1107
|
};
|
|
741
|
-
const timeoutMs = body.mode ===
|
|
742
|
-
const outcome = await requestHub(
|
|
1108
|
+
const timeoutMs = body.mode === "async" ? 10000 : 120000;
|
|
1109
|
+
const outcome = await requestHub(
|
|
1110
|
+
HUB_OPERATIONS.delegatorDelegate,
|
|
1111
|
+
body,
|
|
1112
|
+
timeoutMs,
|
|
1113
|
+
);
|
|
743
1114
|
return emitJson(outcome?.result || unavailableResult());
|
|
744
1115
|
}
|
|
745
1116
|
|
|
746
1117
|
async function cmdDelegatorReply(args) {
|
|
747
1118
|
const body = {
|
|
748
|
-
job_id: args[
|
|
1119
|
+
job_id: args["job-id"],
|
|
749
1120
|
reply: args.text || args.reply,
|
|
750
1121
|
done: !!args.done,
|
|
751
1122
|
};
|
|
@@ -755,45 +1126,435 @@ async function cmdDelegatorReply(args) {
|
|
|
755
1126
|
|
|
756
1127
|
async function cmdDelegatorStatus(args) {
|
|
757
1128
|
const body = {
|
|
758
|
-
job_id: args[
|
|
1129
|
+
job_id: args["job-id"],
|
|
759
1130
|
};
|
|
760
1131
|
const outcome = await requestHub(HUB_OPERATIONS.delegatorStatus, body, 5000);
|
|
761
1132
|
return emitJson(outcome?.result || unavailableResult());
|
|
762
1133
|
}
|
|
763
1134
|
|
|
1135
|
+
async function cmdHitlRequest(args) {
|
|
1136
|
+
const body = {
|
|
1137
|
+
kind: args[1],
|
|
1138
|
+
prompt: args[2],
|
|
1139
|
+
requester_agent: args[3] || "cli",
|
|
1140
|
+
};
|
|
1141
|
+
const outcome = await requestHub(
|
|
1142
|
+
HUB_OPERATIONS["hitl-request"],
|
|
1143
|
+
body,
|
|
1144
|
+
120000,
|
|
1145
|
+
);
|
|
1146
|
+
return emitJson(outcome?.result || unavailableResult());
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
async function cmdHitlSubmit(args) {
|
|
1150
|
+
const body = {
|
|
1151
|
+
request_id: args[1],
|
|
1152
|
+
action: args[2] || "accept",
|
|
1153
|
+
content: args[3],
|
|
1154
|
+
};
|
|
1155
|
+
const outcome = await requestHub(HUB_OPERATIONS["hitl-submit"], body, 120000);
|
|
1156
|
+
return emitJson(outcome?.result || unavailableResult());
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
async function cmdHitlPending() {
|
|
1160
|
+
const outcome = await requestHub(HUB_OPERATIONS["hitl-pending"], {}, 5000);
|
|
1161
|
+
return emitJson(outcome?.result || unavailableResult());
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
async function loadDaemonControl() {
|
|
1165
|
+
return await import("./team/claude-daemon-control.mjs");
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function daemonErrorResult(error) {
|
|
1169
|
+
return {
|
|
1170
|
+
ok: false,
|
|
1171
|
+
error: error?.message || String(error),
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
function daemonAttachErrorResult(error) {
|
|
1176
|
+
return {
|
|
1177
|
+
ok: false,
|
|
1178
|
+
text: "",
|
|
1179
|
+
raw: "",
|
|
1180
|
+
responseRaw: "",
|
|
1181
|
+
matchedCompletion: false,
|
|
1182
|
+
timedOut: false,
|
|
1183
|
+
closed: false,
|
|
1184
|
+
inputSent: error?.inputSent === true,
|
|
1185
|
+
error: error?.message || String(error),
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function daemonInterruptErrorResult(error) {
|
|
1190
|
+
return {
|
|
1191
|
+
ok: false,
|
|
1192
|
+
done: false,
|
|
1193
|
+
aborted: false,
|
|
1194
|
+
reason: "interrupt_failed",
|
|
1195
|
+
inputSent: error?.inputSent === true,
|
|
1196
|
+
error: error?.message || String(error),
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
function numericOption(value, fallback) {
|
|
1201
|
+
const parsed = Number(value);
|
|
1202
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
async function cmdDaemonProbe(args) {
|
|
1206
|
+
try {
|
|
1207
|
+
const payload = readBridgePayload(args);
|
|
1208
|
+
const {
|
|
1209
|
+
deriveClaudeDaemonPaths,
|
|
1210
|
+
findDaemonJobBySessionId,
|
|
1211
|
+
findDaemonJobByShort,
|
|
1212
|
+
sendClaudeControlRequest,
|
|
1213
|
+
} = await loadDaemonControl();
|
|
1214
|
+
const daemonPaths = deriveClaudeDaemonPaths({
|
|
1215
|
+
configDir: payload.configDir,
|
|
1216
|
+
});
|
|
1217
|
+
const list = await sendClaudeControlRequest(
|
|
1218
|
+
daemonPaths.controlSock,
|
|
1219
|
+
{ proto: 1, op: "list" },
|
|
1220
|
+
{ timeoutMs: numericOption(payload.timeoutMs, 6000) },
|
|
1221
|
+
);
|
|
1222
|
+
const target = payload.sessionId
|
|
1223
|
+
? findDaemonJobBySessionId(list, payload.sessionId)
|
|
1224
|
+
: payload.short
|
|
1225
|
+
? findDaemonJobByShort(list, payload.short)
|
|
1226
|
+
: null;
|
|
1227
|
+
|
|
1228
|
+
return emitJson({
|
|
1229
|
+
ok: list?.ok !== false,
|
|
1230
|
+
controlSock: daemonPaths.controlSock,
|
|
1231
|
+
sessions: list?.jobs || list?.sessions || [],
|
|
1232
|
+
target: target || undefined,
|
|
1233
|
+
error: list?.ok === false ? list?.error : undefined,
|
|
1234
|
+
});
|
|
1235
|
+
} catch (error) {
|
|
1236
|
+
return emitJson(daemonErrorResult(error));
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
async function cmdDaemonAttach(args) {
|
|
1241
|
+
try {
|
|
1242
|
+
const payload = readBridgePayload(args);
|
|
1243
|
+
if (!payload.prompt) throw new Error("prompt is required");
|
|
1244
|
+
|
|
1245
|
+
const {
|
|
1246
|
+
attachClaudeDaemonSession,
|
|
1247
|
+
deriveClaudeDaemonPaths,
|
|
1248
|
+
findDaemonJobBySessionId,
|
|
1249
|
+
sendClaudeControlRequest,
|
|
1250
|
+
} = await loadDaemonControl();
|
|
1251
|
+
const daemonPaths = deriveClaudeDaemonPaths({
|
|
1252
|
+
configDir: payload.configDir,
|
|
1253
|
+
});
|
|
1254
|
+
let short = payload.short;
|
|
1255
|
+
if (!short && payload.sessionId) {
|
|
1256
|
+
const list = await sendClaudeControlRequest(
|
|
1257
|
+
daemonPaths.controlSock,
|
|
1258
|
+
{ proto: 1, op: "list" },
|
|
1259
|
+
{ timeoutMs: numericOption(payload.timeoutMs, 6000) },
|
|
1260
|
+
);
|
|
1261
|
+
const target = findDaemonJobBySessionId(list, payload.sessionId);
|
|
1262
|
+
short = target?.short;
|
|
1263
|
+
}
|
|
1264
|
+
if (!short) throw new Error("short or resolvable sessionId is required");
|
|
1265
|
+
|
|
1266
|
+
const result = await attachClaudeDaemonSession({
|
|
1267
|
+
controlSock: daemonPaths.controlSock,
|
|
1268
|
+
short,
|
|
1269
|
+
input: payload.prompt,
|
|
1270
|
+
cols: numericOption(payload.cols, undefined),
|
|
1271
|
+
rows: numericOption(payload.rows, undefined),
|
|
1272
|
+
timeoutMs: numericOption(payload.timeoutMs, 30_000),
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
return emitJson({
|
|
1276
|
+
ok: result.matchedCompletion === true,
|
|
1277
|
+
text: result.text,
|
|
1278
|
+
raw: result.streamText,
|
|
1279
|
+
responseRaw: result.responseStreamText,
|
|
1280
|
+
matchedCompletion: result.matchedCompletion === true,
|
|
1281
|
+
timedOut: result.timedOut === true,
|
|
1282
|
+
closed: result.closed === true,
|
|
1283
|
+
inputSent: result.inputSent === true,
|
|
1284
|
+
error:
|
|
1285
|
+
result.handshake?.ok === false ? result.handshake?.error : undefined,
|
|
1286
|
+
});
|
|
1287
|
+
} catch (error) {
|
|
1288
|
+
return emitJson(daemonAttachErrorResult(error));
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
async function cmdDaemonInterrupt(args) {
|
|
1293
|
+
try {
|
|
1294
|
+
const payload = readBridgePayload(args);
|
|
1295
|
+
|
|
1296
|
+
const {
|
|
1297
|
+
deriveClaudeDaemonPaths,
|
|
1298
|
+
findDaemonJobBySessionId,
|
|
1299
|
+
interruptClaudeDaemonSession,
|
|
1300
|
+
sendClaudeControlRequest,
|
|
1301
|
+
} = await loadDaemonControl();
|
|
1302
|
+
const daemonPaths = deriveClaudeDaemonPaths({
|
|
1303
|
+
configDir: payload.configDir,
|
|
1304
|
+
});
|
|
1305
|
+
let short = payload.short;
|
|
1306
|
+
if (!short && payload.sessionId) {
|
|
1307
|
+
const list = await sendClaudeControlRequest(
|
|
1308
|
+
daemonPaths.controlSock,
|
|
1309
|
+
{ proto: 1, op: "list" },
|
|
1310
|
+
{ timeoutMs: numericOption(payload.timeoutMs, 6000) },
|
|
1311
|
+
);
|
|
1312
|
+
const target = findDaemonJobBySessionId(list, payload.sessionId);
|
|
1313
|
+
short = target?.short;
|
|
1314
|
+
}
|
|
1315
|
+
if (!short) throw new Error("short or resolvable sessionId is required");
|
|
1316
|
+
|
|
1317
|
+
const result = await interruptClaudeDaemonSession({
|
|
1318
|
+
controlSock: daemonPaths.controlSock,
|
|
1319
|
+
short,
|
|
1320
|
+
cols: numericOption(payload.cols, undefined),
|
|
1321
|
+
rows: numericOption(payload.rows, undefined),
|
|
1322
|
+
timeoutMs: numericOption(payload.timeoutMs, 5000),
|
|
1323
|
+
});
|
|
1324
|
+
const aborted = result.inputSent === true;
|
|
1325
|
+
|
|
1326
|
+
return emitJson({
|
|
1327
|
+
ok: aborted,
|
|
1328
|
+
done: false,
|
|
1329
|
+
aborted,
|
|
1330
|
+
reason: aborted ? "user_interrupt" : "interrupt_failed",
|
|
1331
|
+
raw: result.streamText,
|
|
1332
|
+
timedOut: result.timedOut === true,
|
|
1333
|
+
closed: result.closed === true,
|
|
1334
|
+
inputSent: result.inputSent === true,
|
|
1335
|
+
error:
|
|
1336
|
+
result.handshake?.ok === false ? result.handshake?.error : undefined,
|
|
1337
|
+
});
|
|
1338
|
+
} catch (error) {
|
|
1339
|
+
return emitJson(daemonInterruptErrorResult(error));
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
// ---------------------------------------------------------------------------
|
|
1344
|
+
// retry-run / retry-status — Phase 3 Step C2 bridge 서브커맨드.
|
|
1345
|
+
// retry-state-machine.mjs 를 multi-process safe 하게 외부 호출용 wrap.
|
|
1346
|
+
// 사용자 워크플로우:
|
|
1347
|
+
// 1) 첫 호출: retry-run --snapshot X --mode ralph --event start
|
|
1348
|
+
// → 새 SM 생성, PLANNING → EXECUTING transition, snapshot 저장
|
|
1349
|
+
// 2) verify 성공 시: retry-run --snapshot X --event verify-success
|
|
1350
|
+
// → DONE, 종료 판단 반환
|
|
1351
|
+
// 3) verify 실패 시: retry-run --snapshot X --event verify-fail --reason R
|
|
1352
|
+
// → DIAGNOSING 또는 STUCK/BUDGET_EXCEEDED, 종료 판단 반환
|
|
1353
|
+
// 4) 다음 iter 시작: retry-run --snapshot X --event start
|
|
1354
|
+
// 출력: {ok, current, iterations, done, shouldStop, reason?, cli?} JSON.
|
|
1355
|
+
// ---------------------------------------------------------------------------
|
|
1356
|
+
|
|
1357
|
+
function buildRetrySmFromArgs(args, snapshot) {
|
|
1358
|
+
const mode = args.mode || snapshot?.mode || "bounded";
|
|
1359
|
+
const maxIterations =
|
|
1360
|
+
args["max-iterations"] !== undefined
|
|
1361
|
+
? Number(args["max-iterations"])
|
|
1362
|
+
: snapshot?.maxIterations;
|
|
1363
|
+
const sessionId = args["session-id"] || snapshot?.sessionId || null;
|
|
1364
|
+
const cliChain = snapshot?.cliChain;
|
|
1365
|
+
|
|
1366
|
+
const sm = createRetryStateMachine({
|
|
1367
|
+
mode,
|
|
1368
|
+
maxIterations,
|
|
1369
|
+
sessionId,
|
|
1370
|
+
cliChain,
|
|
1371
|
+
});
|
|
1372
|
+
if (snapshot) sm.applySnapshot(snapshot);
|
|
1373
|
+
return sm;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
function buildRetryCliInvocation(cli) {
|
|
1377
|
+
if (!cli) return null;
|
|
1378
|
+
|
|
1379
|
+
const invocation = {
|
|
1380
|
+
cli: cli.cli,
|
|
1381
|
+
model: cli.model,
|
|
1382
|
+
argv: [],
|
|
1383
|
+
};
|
|
1384
|
+
if (cli.profile) invocation.profile = cli.profile;
|
|
1385
|
+
if (cli.cli === "codex" && cli.profile) {
|
|
1386
|
+
invocation.argv.push("--profile", cli.profile);
|
|
1387
|
+
}
|
|
1388
|
+
return invocation;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
async function cmdRetryRun(args) {
|
|
1392
|
+
const snapshotFile = args.snapshot || args["snapshot-file"];
|
|
1393
|
+
const event = args.event;
|
|
1394
|
+
const reason = args.reason || "";
|
|
1395
|
+
|
|
1396
|
+
if (!snapshotFile) {
|
|
1397
|
+
console.error("--snapshot <path> required");
|
|
1398
|
+
return false;
|
|
1399
|
+
}
|
|
1400
|
+
if (!event) {
|
|
1401
|
+
console.error("--event <start|verify-success|verify-fail> required");
|
|
1402
|
+
return false;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
const existing = loadSnapshot(snapshotFile);
|
|
1406
|
+
const sm = buildRetrySmFromArgs(args, existing);
|
|
1407
|
+
|
|
1408
|
+
let result;
|
|
1409
|
+
switch (event) {
|
|
1410
|
+
case "start":
|
|
1411
|
+
result = sm.startIteration();
|
|
1412
|
+
break;
|
|
1413
|
+
case "verify-success":
|
|
1414
|
+
result = sm.reportVerifySuccess();
|
|
1415
|
+
break;
|
|
1416
|
+
case "verify-fail":
|
|
1417
|
+
result = sm.reportVerifyFail(reason || "unspecified");
|
|
1418
|
+
break;
|
|
1419
|
+
default:
|
|
1420
|
+
console.error(`unknown --event: ${event}`);
|
|
1421
|
+
return false;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
const snap = sm.serialize();
|
|
1425
|
+
saveSnapshot(snapshotFile, snap);
|
|
1426
|
+
|
|
1427
|
+
const terminal = ["DONE", "STUCK", "BUDGET_EXCEEDED"].includes(snap.current);
|
|
1428
|
+
const cli = snap.cliChain?.[snap.cliIndex] || null;
|
|
1429
|
+
const cliInvocation = buildRetryCliInvocation(cli);
|
|
1430
|
+
const out = {
|
|
1431
|
+
ok: true,
|
|
1432
|
+
current: snap.current,
|
|
1433
|
+
iterations: snap.iterations,
|
|
1434
|
+
cliIndex: snap.cliIndex,
|
|
1435
|
+
cli,
|
|
1436
|
+
cliInvocation,
|
|
1437
|
+
done: snap.current === "DONE",
|
|
1438
|
+
shouldStop: terminal,
|
|
1439
|
+
stuckCounter: snap.stuckCounter,
|
|
1440
|
+
lastFailureReason: snap.lastFailureReason,
|
|
1441
|
+
transition: result,
|
|
1442
|
+
};
|
|
1443
|
+
console.log(JSON.stringify(out));
|
|
1444
|
+
return true;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
async function cmdRetryStatus(args) {
|
|
1448
|
+
const snapshotFile = args.snapshot || args["snapshot-file"];
|
|
1449
|
+
if (!snapshotFile) {
|
|
1450
|
+
console.error("--snapshot <path> required");
|
|
1451
|
+
return false;
|
|
1452
|
+
}
|
|
1453
|
+
const snap = loadSnapshot(snapshotFile);
|
|
1454
|
+
if (!snap) {
|
|
1455
|
+
console.log(JSON.stringify({ ok: true, exists: false }));
|
|
1456
|
+
return true;
|
|
1457
|
+
}
|
|
1458
|
+
const terminal = ["DONE", "STUCK", "BUDGET_EXCEEDED"].includes(snap.current);
|
|
1459
|
+
const cli = snap.cliChain?.[snap.cliIndex] || null;
|
|
1460
|
+
const cliInvocation = buildRetryCliInvocation(cli);
|
|
1461
|
+
console.log(
|
|
1462
|
+
JSON.stringify({
|
|
1463
|
+
ok: true,
|
|
1464
|
+
exists: true,
|
|
1465
|
+
current: snap.current,
|
|
1466
|
+
iterations: snap.iterations,
|
|
1467
|
+
maxIterations: snap.maxIterations,
|
|
1468
|
+
cliIndex: snap.cliIndex,
|
|
1469
|
+
cli,
|
|
1470
|
+
cliInvocation,
|
|
1471
|
+
mode: snap.mode,
|
|
1472
|
+
shouldStop: terminal,
|
|
1473
|
+
stuckCounter: snap.stuckCounter,
|
|
1474
|
+
lastFailureReason: snap.lastFailureReason,
|
|
1475
|
+
}),
|
|
1476
|
+
);
|
|
1477
|
+
return true;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
764
1480
|
export async function main(argv = process.argv.slice(2)) {
|
|
765
1481
|
const cmd = argv[0];
|
|
766
1482
|
const args = parseArgs(argv.slice(1));
|
|
767
1483
|
|
|
768
1484
|
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
|
|
1485
|
+
case "register":
|
|
1486
|
+
return await cmdRegister(args);
|
|
1487
|
+
case "result":
|
|
1488
|
+
return await cmdResult(args);
|
|
1489
|
+
case "control":
|
|
1490
|
+
return await cmdControl(args);
|
|
1491
|
+
case "handoff":
|
|
1492
|
+
return await cmdHandoff(args);
|
|
1493
|
+
case "publish":
|
|
1494
|
+
return await cmdPublish(args);
|
|
1495
|
+
case "send-input":
|
|
1496
|
+
return await cmdSendInput(args);
|
|
1497
|
+
case "context":
|
|
1498
|
+
return await cmdContext(args);
|
|
1499
|
+
case "deregister":
|
|
1500
|
+
return await cmdDeregister(args);
|
|
1501
|
+
case "assign-async":
|
|
1502
|
+
return await cmdAssignAsync(args);
|
|
1503
|
+
case "assign-result":
|
|
1504
|
+
return await cmdAssignResult(args);
|
|
1505
|
+
case "assign-status":
|
|
1506
|
+
return await cmdAssignStatus(args);
|
|
1507
|
+
case "assign-retry":
|
|
1508
|
+
return await cmdAssignRetry(args);
|
|
1509
|
+
case "team-info":
|
|
1510
|
+
return await cmdTeamInfo(args);
|
|
1511
|
+
case "team-task-list":
|
|
1512
|
+
return await cmdTeamTaskList(args);
|
|
1513
|
+
case "team-task-update":
|
|
1514
|
+
return await cmdTeamTaskUpdate(args);
|
|
1515
|
+
case "team-send-message":
|
|
1516
|
+
return await cmdTeamSendMessage(args);
|
|
1517
|
+
case "pipeline-state":
|
|
1518
|
+
return await cmdPipelineState(args);
|
|
1519
|
+
case "pipeline-advance":
|
|
1520
|
+
return await cmdPipelineAdvance(args);
|
|
1521
|
+
case "pipeline-init":
|
|
1522
|
+
return await cmdPipelineInit(args);
|
|
1523
|
+
case "pipeline-list":
|
|
1524
|
+
return await cmdPipelineList(args);
|
|
1525
|
+
case "ping":
|
|
1526
|
+
return await cmdPing(args);
|
|
1527
|
+
case "delegator-delegate":
|
|
1528
|
+
return await cmdDelegatorDelegate(args);
|
|
1529
|
+
case "delegator-reply":
|
|
1530
|
+
return await cmdDelegatorReply(args);
|
|
1531
|
+
case "delegator-status":
|
|
1532
|
+
return await cmdDelegatorStatus(args);
|
|
1533
|
+
case "hitl-request":
|
|
1534
|
+
return await cmdHitlRequest(args);
|
|
1535
|
+
case "hitl-submit":
|
|
1536
|
+
return await cmdHitlSubmit(args);
|
|
1537
|
+
case "hitl-pending":
|
|
1538
|
+
return await cmdHitlPending(args);
|
|
1539
|
+
case "daemon-probe":
|
|
1540
|
+
return await cmdDaemonProbe(args);
|
|
1541
|
+
case "daemon-attach":
|
|
1542
|
+
return await cmdDaemonAttach(args);
|
|
1543
|
+
case "daemon-interrupt":
|
|
1544
|
+
return await cmdDaemonInterrupt(args);
|
|
1545
|
+
case "retry-run":
|
|
1546
|
+
return await cmdRetryRun(args);
|
|
1547
|
+
case "retry-status":
|
|
1548
|
+
return await cmdRetryStatus(args);
|
|
790
1549
|
default:
|
|
791
|
-
console.error(
|
|
1550
|
+
console.error(
|
|
1551
|
+
"사용법: 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|daemon-probe|daemon-attach|daemon-interrupt|retry-run|retry-status> [--옵션]",
|
|
1552
|
+
);
|
|
792
1553
|
process.exit(1);
|
|
793
1554
|
}
|
|
794
1555
|
}
|
|
795
1556
|
|
|
796
|
-
const selfRun = process.argv[1]?.replace(/\\/g,
|
|
1557
|
+
const selfRun = process.argv[1]?.replace(/\\/g, "/").endsWith("hub/bridge.mjs");
|
|
797
1558
|
if (selfRun) {
|
|
798
|
-
process.exitCode = await main() ? 0 : 1;
|
|
1559
|
+
process.exitCode = (await main()) ? 0 : 1;
|
|
799
1560
|
}
|