triflux 10.2.1 → 10.3.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/README.md +236 -156
- package/hub/bridge.mjs +638 -290
- package/hub/codex-compat.mjs +1 -1
- package/hub/fullcycle.mjs +1 -1
- package/hub/intent.mjs +1 -0
- package/hub/lib/mcp-response-cache.mjs +205 -0
- package/hub/pipe.mjs +228 -119
- package/hub/reflexion.mjs +87 -13
- package/hub/research.mjs +1 -0
- package/hub/server.mjs +997 -611
- package/hub/team/conductor-registry.mjs +121 -0
- package/hub/team/conductor.mjs +256 -125
- package/hub/team/execution-mode.mjs +105 -0
- package/hub/team/headless.mjs +686 -252
- package/hub/team/lead-control.mjs +91 -4
- package/hub/team/mcp-selector.mjs +145 -0
- package/hub/team/session-sync.mjs +153 -6
- package/hub/team/swarm-hypervisor.mjs +208 -86
- package/hub/token-mode.mjs +1 -0
- package/hub/tools.mjs +474 -252
- package/package.json +5 -5
- package/scripts/codex-gateway-preflight.mjs +133 -0
- package/scripts/codex-mcp-gateway-sync.mjs +199 -0
- package/skills/star-prompt/SKILL.md +169 -69
- package/skills/tfx-setup/SKILL.md +124 -0
- package/skills/tfx-swarm/SKILL.md +124 -72
package/hub/bridge.mjs
CHANGED
|
@@ -1,23 +1,30 @@
|
|
|
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
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
+
|
|
25
|
+
const HUB_PID_FILE = join(homedir(), ".claude", "cache", "tfx-hub", "hub.pid");
|
|
26
|
+
const HUB_TOKEN_FILE = join(homedir(), ".claude", ".tfx-hub-token");
|
|
27
|
+
const PROJECT_ROOT = fileURLToPath(new URL("..", import.meta.url));
|
|
21
28
|
|
|
22
29
|
function normalizeToken(raw) {
|
|
23
30
|
if (raw == null) return null;
|
|
@@ -30,25 +37,26 @@ function readHubToken() {
|
|
|
30
37
|
const envToken = normalizeToken(process.env.TFX_HUB_TOKEN);
|
|
31
38
|
if (envToken) return envToken;
|
|
32
39
|
try {
|
|
33
|
-
return normalizeToken(readFileSync(HUB_TOKEN_FILE,
|
|
40
|
+
return normalizeToken(readFileSync(HUB_TOKEN_FILE, "utf8"));
|
|
34
41
|
} catch {
|
|
35
42
|
return null;
|
|
36
43
|
}
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
export function getHubUrl() {
|
|
40
|
-
if (process.env.TFX_HUB_URL)
|
|
47
|
+
if (process.env.TFX_HUB_URL)
|
|
48
|
+
return process.env.TFX_HUB_URL.replace(/\/mcp$/, "");
|
|
41
49
|
|
|
42
50
|
if (existsSync(HUB_PID_FILE)) {
|
|
43
51
|
try {
|
|
44
|
-
const info = JSON.parse(readFileSync(HUB_PID_FILE,
|
|
45
|
-
return `http://${info.host ||
|
|
52
|
+
const info = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
|
|
53
|
+
return `http://${info.host || "127.0.0.1"}:${info.port || 27888}`;
|
|
46
54
|
} catch {
|
|
47
55
|
// 무시
|
|
48
56
|
}
|
|
49
57
|
}
|
|
50
58
|
|
|
51
|
-
const port = process.env.TFX_HUB_PORT ||
|
|
59
|
+
const port = process.env.TFX_HUB_PORT || "27888";
|
|
52
60
|
return `http://127.0.0.1:${port}`;
|
|
53
61
|
}
|
|
54
62
|
|
|
@@ -57,7 +65,7 @@ export function getHubPipePath() {
|
|
|
57
65
|
|
|
58
66
|
if (!existsSync(HUB_PID_FILE)) return null;
|
|
59
67
|
try {
|
|
60
|
-
const info = JSON.parse(readFileSync(HUB_PID_FILE,
|
|
68
|
+
const info = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
|
|
61
69
|
return info.pipe_path || info.pipePath || null;
|
|
62
70
|
} catch {
|
|
63
71
|
return null;
|
|
@@ -65,30 +73,144 @@ export function getHubPipePath() {
|
|
|
65
73
|
}
|
|
66
74
|
|
|
67
75
|
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
|
-
|
|
76
|
+
register: {
|
|
77
|
+
transport: "command",
|
|
78
|
+
action: "register",
|
|
79
|
+
httpPath: "/bridge/register",
|
|
80
|
+
},
|
|
81
|
+
result: {
|
|
82
|
+
transport: "command",
|
|
83
|
+
action: "result",
|
|
84
|
+
httpPath: "/bridge/result",
|
|
85
|
+
},
|
|
86
|
+
control: {
|
|
87
|
+
transport: "command",
|
|
88
|
+
action: "control",
|
|
89
|
+
httpPath: "/bridge/control",
|
|
90
|
+
},
|
|
91
|
+
handoff: {
|
|
92
|
+
transport: "command",
|
|
93
|
+
action: "handoff",
|
|
94
|
+
httpPath: "/bridge/handoff",
|
|
95
|
+
},
|
|
96
|
+
publish: {
|
|
97
|
+
transport: "command",
|
|
98
|
+
action: "publish",
|
|
99
|
+
httpPath: "/bridge/publish",
|
|
100
|
+
},
|
|
101
|
+
sendInput: {
|
|
102
|
+
transport: "command",
|
|
103
|
+
action: "send_input",
|
|
104
|
+
httpPath: "/bridge/send-input",
|
|
105
|
+
},
|
|
106
|
+
context: { transport: "query", action: "drain", httpPath: "/bridge/context" },
|
|
107
|
+
deregister: {
|
|
108
|
+
transport: "command",
|
|
109
|
+
action: "deregister",
|
|
110
|
+
httpPath: "/bridge/deregister",
|
|
111
|
+
},
|
|
112
|
+
assignAsync: {
|
|
113
|
+
transport: "command",
|
|
114
|
+
action: "assign",
|
|
115
|
+
httpPath: "/bridge/assign/async",
|
|
116
|
+
},
|
|
117
|
+
assignResult: {
|
|
118
|
+
transport: "command",
|
|
119
|
+
action: "assign_result",
|
|
120
|
+
httpPath: "/bridge/assign/result",
|
|
121
|
+
},
|
|
122
|
+
assignStatus: {
|
|
123
|
+
transport: "query",
|
|
124
|
+
action: "assign_status",
|
|
125
|
+
httpPath: "/bridge/assign/status",
|
|
126
|
+
},
|
|
127
|
+
assignRetry: {
|
|
128
|
+
transport: "command",
|
|
129
|
+
action: "assign_retry",
|
|
130
|
+
httpPath: "/bridge/assign/retry",
|
|
131
|
+
},
|
|
132
|
+
teamInfo: {
|
|
133
|
+
transport: "query",
|
|
134
|
+
action: "team_info",
|
|
135
|
+
httpPath: "/bridge/team/info",
|
|
136
|
+
},
|
|
137
|
+
teamTaskList: {
|
|
138
|
+
transport: "query",
|
|
139
|
+
action: "team_task_list",
|
|
140
|
+
httpPath: "/bridge/team/task-list",
|
|
141
|
+
},
|
|
142
|
+
teamTaskUpdate: {
|
|
143
|
+
transport: "command",
|
|
144
|
+
action: "team_task_update",
|
|
145
|
+
httpPath: "/bridge/team/task-update",
|
|
146
|
+
},
|
|
147
|
+
teamSendMessage: {
|
|
148
|
+
transport: "command",
|
|
149
|
+
action: "team_send_message",
|
|
150
|
+
httpPath: "/bridge/team/send-message",
|
|
151
|
+
},
|
|
152
|
+
pipelineState: {
|
|
153
|
+
transport: "query",
|
|
154
|
+
action: "pipeline_state",
|
|
155
|
+
httpPath: "/bridge/pipeline/state",
|
|
156
|
+
},
|
|
157
|
+
pipelineAdvance: {
|
|
158
|
+
transport: "command",
|
|
159
|
+
action: "pipeline_advance",
|
|
160
|
+
httpPath: "/bridge/pipeline/advance",
|
|
161
|
+
},
|
|
162
|
+
pipelineInit: {
|
|
163
|
+
transport: "command",
|
|
164
|
+
action: "pipeline_init",
|
|
165
|
+
httpPath: "/bridge/pipeline/init",
|
|
166
|
+
},
|
|
167
|
+
pipelineList: {
|
|
168
|
+
transport: "query",
|
|
169
|
+
action: "pipeline_list",
|
|
170
|
+
httpPath: "/bridge/pipeline/list",
|
|
171
|
+
},
|
|
172
|
+
hubStatus: {
|
|
173
|
+
transport: "query",
|
|
174
|
+
action: "status",
|
|
175
|
+
httpPath: "/status",
|
|
176
|
+
httpMethod: "GET",
|
|
177
|
+
},
|
|
178
|
+
delegatorDelegate: {
|
|
179
|
+
transport: "command",
|
|
180
|
+
action: "delegator_delegate",
|
|
181
|
+
httpPath: "/bridge/delegator/delegate",
|
|
182
|
+
},
|
|
183
|
+
delegatorReply: {
|
|
184
|
+
transport: "command",
|
|
185
|
+
action: "delegator_reply",
|
|
186
|
+
httpPath: "/bridge/delegator/reply",
|
|
187
|
+
},
|
|
188
|
+
delegatorStatus: {
|
|
189
|
+
transport: "query",
|
|
190
|
+
action: "delegator_status",
|
|
191
|
+
httpPath: "/bridge/delegator/status",
|
|
192
|
+
},
|
|
193
|
+
"hitl-request": {
|
|
194
|
+
transport: "command",
|
|
195
|
+
action: "hitl_request",
|
|
196
|
+
httpPath: "/bridge/hitl/request",
|
|
197
|
+
},
|
|
198
|
+
"hitl-submit": {
|
|
199
|
+
transport: "command",
|
|
200
|
+
action: "hitl_submit",
|
|
201
|
+
httpPath: "/bridge/hitl/submit",
|
|
202
|
+
},
|
|
203
|
+
"hitl-pending": {
|
|
204
|
+
transport: "query",
|
|
205
|
+
action: "hitl_pending",
|
|
206
|
+
httpPath: "/bridge/hitl/pending",
|
|
207
|
+
},
|
|
89
208
|
});
|
|
90
209
|
|
|
91
|
-
export async function requestJson(
|
|
210
|
+
export async function requestJson(
|
|
211
|
+
path,
|
|
212
|
+
{ method = "POST", body, timeoutMs = 5000 } = {},
|
|
213
|
+
) {
|
|
92
214
|
const controller = new AbortController();
|
|
93
215
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
94
216
|
|
|
@@ -99,7 +221,7 @@ export async function requestJson(path, { method = 'POST', body, timeoutMs = 500
|
|
|
99
221
|
headers.Authorization = `Bearer ${token}`;
|
|
100
222
|
}
|
|
101
223
|
if (body !== undefined) {
|
|
102
|
-
headers[
|
|
224
|
+
headers["Content-Type"] = "application/json";
|
|
103
225
|
}
|
|
104
226
|
|
|
105
227
|
const res = await fetch(`${getHubUrl()}${path}`, {
|
|
@@ -117,7 +239,7 @@ export async function requestJson(path, { method = 'POST', body, timeoutMs = 500
|
|
|
117
239
|
}
|
|
118
240
|
|
|
119
241
|
export async function post(path, body, timeoutMs = 5000) {
|
|
120
|
-
return await requestJson(path, { method:
|
|
242
|
+
return await requestJson(path, { method: "POST", body, timeoutMs });
|
|
121
243
|
}
|
|
122
244
|
|
|
123
245
|
export async function connectPipe(timeoutMs = 1200) {
|
|
@@ -127,19 +249,23 @@ export async function connectPipe(timeoutMs = 1200) {
|
|
|
127
249
|
return await new Promise((resolve) => {
|
|
128
250
|
const socket = net.createConnection(pipePath);
|
|
129
251
|
const timer = setTimeout(() => {
|
|
130
|
-
try {
|
|
252
|
+
try {
|
|
253
|
+
socket.destroy();
|
|
254
|
+
} catch {}
|
|
131
255
|
resolve(null);
|
|
132
256
|
}, timeoutMs);
|
|
133
257
|
|
|
134
|
-
socket.once(
|
|
258
|
+
socket.once("connect", () => {
|
|
135
259
|
clearTimeout(timer);
|
|
136
|
-
socket.setEncoding(
|
|
260
|
+
socket.setEncoding("utf8");
|
|
137
261
|
resolve(socket);
|
|
138
262
|
});
|
|
139
263
|
|
|
140
|
-
socket.once(
|
|
264
|
+
socket.once("error", () => {
|
|
141
265
|
clearTimeout(timer);
|
|
142
|
-
try {
|
|
266
|
+
try {
|
|
267
|
+
socket.destroy();
|
|
268
|
+
} catch {}
|
|
143
269
|
resolve(null);
|
|
144
270
|
});
|
|
145
271
|
});
|
|
@@ -151,7 +277,7 @@ async function pipeRequest(type, action, payload, timeoutMs = 3000) {
|
|
|
151
277
|
|
|
152
278
|
return await new Promise((resolve) => {
|
|
153
279
|
const requestId = randomUUID();
|
|
154
|
-
let buffer =
|
|
280
|
+
let buffer = "";
|
|
155
281
|
let settled = false;
|
|
156
282
|
const timer = setTimeout(() => {
|
|
157
283
|
finish(null);
|
|
@@ -161,17 +287,19 @@ async function pipeRequest(type, action, payload, timeoutMs = 3000) {
|
|
|
161
287
|
if (settled) return;
|
|
162
288
|
settled = true;
|
|
163
289
|
clearTimeout(timer);
|
|
164
|
-
try {
|
|
290
|
+
try {
|
|
291
|
+
socket.end();
|
|
292
|
+
} catch {}
|
|
165
293
|
resolve(result);
|
|
166
294
|
};
|
|
167
295
|
|
|
168
|
-
socket.on(
|
|
296
|
+
socket.on("data", (chunk) => {
|
|
169
297
|
buffer += chunk;
|
|
170
|
-
let newlineIndex = buffer.indexOf(
|
|
298
|
+
let newlineIndex = buffer.indexOf("\n");
|
|
171
299
|
while (newlineIndex >= 0) {
|
|
172
300
|
const line = buffer.slice(0, newlineIndex).trim();
|
|
173
301
|
buffer = buffer.slice(newlineIndex + 1);
|
|
174
|
-
newlineIndex = buffer.indexOf(
|
|
302
|
+
newlineIndex = buffer.indexOf("\n");
|
|
175
303
|
if (!line) continue;
|
|
176
304
|
|
|
177
305
|
let frame;
|
|
@@ -181,7 +309,8 @@ async function pipeRequest(type, action, payload, timeoutMs = 3000) {
|
|
|
181
309
|
continue;
|
|
182
310
|
}
|
|
183
311
|
|
|
184
|
-
if (frame?.type !==
|
|
312
|
+
if (frame?.type !== "response" || frame.request_id !== requestId)
|
|
313
|
+
continue;
|
|
185
314
|
finish({
|
|
186
315
|
ok: frame.ok,
|
|
187
316
|
error: frame.error,
|
|
@@ -191,89 +320,99 @@ async function pipeRequest(type, action, payload, timeoutMs = 3000) {
|
|
|
191
320
|
}
|
|
192
321
|
});
|
|
193
322
|
|
|
194
|
-
socket.on(
|
|
195
|
-
socket.write(
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
323
|
+
socket.on("error", () => finish(null));
|
|
324
|
+
socket.write(
|
|
325
|
+
JSON.stringify({
|
|
326
|
+
type,
|
|
327
|
+
request_id: requestId,
|
|
328
|
+
payload: { action, ...payload },
|
|
329
|
+
}) + "\n",
|
|
330
|
+
);
|
|
200
331
|
});
|
|
201
332
|
}
|
|
202
333
|
|
|
203
334
|
async function pipeCommand(action, payload, timeoutMs = 3000) {
|
|
204
|
-
return await pipeRequest(
|
|
335
|
+
return await pipeRequest("command", action, payload, timeoutMs);
|
|
205
336
|
}
|
|
206
337
|
|
|
207
338
|
async function pipeQuery(action, payload, timeoutMs = 3000) {
|
|
208
|
-
return await pipeRequest(
|
|
339
|
+
return await pipeRequest("query", action, payload, timeoutMs);
|
|
209
340
|
}
|
|
210
341
|
|
|
211
342
|
export function parseArgs(argv) {
|
|
212
|
-
const { values } = nodeParseArgs({
|
|
343
|
+
const { values, positionals } = nodeParseArgs({
|
|
213
344
|
args: argv,
|
|
345
|
+
allowPositionals: true,
|
|
214
346
|
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
|
-
|
|
347
|
+
agent: { type: "string" },
|
|
348
|
+
cli: { type: "string" },
|
|
349
|
+
timeout: { type: "string" },
|
|
350
|
+
topics: { type: "string" },
|
|
351
|
+
capabilities: { type: "string" },
|
|
352
|
+
file: { type: "string" },
|
|
353
|
+
payload: { type: "string" },
|
|
354
|
+
topic: { type: "string" },
|
|
355
|
+
trace: { type: "string" },
|
|
356
|
+
correlation: { type: "string" },
|
|
357
|
+
"exit-code": { type: "string" },
|
|
358
|
+
max: { type: "string" },
|
|
359
|
+
out: { type: "string" },
|
|
360
|
+
team: { type: "string" },
|
|
361
|
+
"task-id": { type: "string" },
|
|
362
|
+
"job-id": { type: "string" },
|
|
363
|
+
owner: { type: "string" },
|
|
364
|
+
status: { type: "string" },
|
|
365
|
+
statuses: { type: "string" },
|
|
366
|
+
claim: { type: "boolean" },
|
|
367
|
+
actor: { type: "string" },
|
|
368
|
+
command: { type: "string" },
|
|
369
|
+
"session-id": { type: "string" },
|
|
370
|
+
reason: { type: "string" },
|
|
371
|
+
type: { type: "string" },
|
|
372
|
+
from: { type: "string" },
|
|
373
|
+
to: { type: "string" },
|
|
374
|
+
text: { type: "string" },
|
|
375
|
+
task: { type: "string" },
|
|
376
|
+
"supervisor-agent": { type: "string" },
|
|
377
|
+
"worker-agent": { type: "string" },
|
|
378
|
+
priority: { type: "string" },
|
|
379
|
+
"ttl-ms": { type: "string" },
|
|
380
|
+
"timeout-ms": { type: "string" },
|
|
381
|
+
"max-retries": { type: "string" },
|
|
382
|
+
attempt: { type: "string" },
|
|
383
|
+
result: { type: "string" },
|
|
384
|
+
error: { type: "string" },
|
|
385
|
+
metadata: { type: "string" },
|
|
386
|
+
"requested-by": { type: "string" },
|
|
387
|
+
summary: { type: "string" },
|
|
388
|
+
color: { type: "string" },
|
|
389
|
+
limit: { type: "string" },
|
|
390
|
+
"include-internal": { type: "boolean" },
|
|
391
|
+
subject: { type: "string" },
|
|
392
|
+
description: { type: "string" },
|
|
393
|
+
"fix-max": { type: "string" },
|
|
394
|
+
"ralph-max": { type: "string" },
|
|
395
|
+
"active-form": { type: "string" },
|
|
396
|
+
"add-blocks": { type: "string" },
|
|
397
|
+
"add-blocked-by": { type: "string" },
|
|
398
|
+
"metadata-patch": { type: "string" },
|
|
399
|
+
"if-match-mtime-ms": { type: "string" },
|
|
400
|
+
provider: { type: "string" },
|
|
401
|
+
mode: { type: "string" },
|
|
402
|
+
prompt: { type: "string" },
|
|
403
|
+
reply: { type: "string" },
|
|
404
|
+
done: { type: "boolean" },
|
|
405
|
+
"mcp-profile": { type: "string" },
|
|
406
|
+
"session-key": { type: "string" },
|
|
273
407
|
},
|
|
408
|
+
allowPositionals: true,
|
|
274
409
|
strict: false,
|
|
275
410
|
});
|
|
276
|
-
|
|
411
|
+
const parsed = { ...values, _: positionals };
|
|
412
|
+
positionals.forEach((value, index) => {
|
|
413
|
+
parsed[index + 1] = value;
|
|
414
|
+
});
|
|
415
|
+
return parsed;
|
|
277
416
|
}
|
|
278
417
|
|
|
279
418
|
export function parseJsonSafe(raw, fallback = null) {
|
|
@@ -285,29 +424,63 @@ export function parseJsonSafe(raw, fallback = null) {
|
|
|
285
424
|
}
|
|
286
425
|
}
|
|
287
426
|
|
|
427
|
+
function normalizeBridgePayload(payload) {
|
|
428
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
429
|
+
return {};
|
|
430
|
+
}
|
|
431
|
+
return payload;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function buildHandoffBody(from, to, payload) {
|
|
435
|
+
const normalizedPayload = normalizeBridgePayload(payload);
|
|
436
|
+
return {
|
|
437
|
+
...normalizedPayload,
|
|
438
|
+
from,
|
|
439
|
+
to,
|
|
440
|
+
payload: normalizedPayload,
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function buildPublishBody(from, to, type, payload) {
|
|
445
|
+
const normalizedPayload = normalizeBridgePayload(payload);
|
|
446
|
+
return {
|
|
447
|
+
...normalizedPayload,
|
|
448
|
+
from,
|
|
449
|
+
to,
|
|
450
|
+
type,
|
|
451
|
+
message_type: type,
|
|
452
|
+
payload: normalizedPayload,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
288
456
|
// Hub 자동 재시작 (Pipe+HTTP 모두 실패 시 1회 시도, 최대 4초 대기)
|
|
289
457
|
async function tryRestartHub() {
|
|
290
|
-
const serverPath = join(PROJECT_ROOT,
|
|
458
|
+
const serverPath = join(PROJECT_ROOT, "hub", "server.mjs");
|
|
291
459
|
if (!existsSync(serverPath)) return false;
|
|
292
460
|
|
|
293
461
|
if (existsSync(HUB_PID_FILE)) {
|
|
294
462
|
try {
|
|
295
|
-
const info = JSON.parse(readFileSync(HUB_PID_FILE,
|
|
463
|
+
const info = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
|
|
296
464
|
if (info.pid) {
|
|
297
|
-
try {
|
|
298
|
-
|
|
465
|
+
try {
|
|
466
|
+
process.kill(info.pid, 0);
|
|
467
|
+
return false;
|
|
468
|
+
} catch (e) {
|
|
469
|
+
// still alive
|
|
470
|
+
if (e.code === "EPERM") return false;
|
|
471
|
+
} // alive, no permission
|
|
299
472
|
}
|
|
300
473
|
} catch {} // corrupt PID file, proceed with restart
|
|
301
474
|
}
|
|
302
475
|
|
|
303
476
|
try {
|
|
304
|
-
const logDir = join(process.cwd(),
|
|
477
|
+
const logDir = join(process.cwd(), ".tfx", "logs");
|
|
305
478
|
if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
|
|
306
|
-
const logFile = join(logDir,
|
|
307
|
-
const logFd = openSync(logFile,
|
|
479
|
+
const logFile = join(logDir, "hub-restart.log");
|
|
480
|
+
const logFd = openSync(logFile, "a");
|
|
308
481
|
const child = spawn(process.execPath, [serverPath], {
|
|
309
482
|
detached: true,
|
|
310
|
-
stdio: [
|
|
483
|
+
stdio: ["ignore", "ignore", logFd],
|
|
311
484
|
windowsHide: true,
|
|
312
485
|
});
|
|
313
486
|
child.unref();
|
|
@@ -323,7 +496,7 @@ async function tryRestartHub() {
|
|
|
323
496
|
});
|
|
324
497
|
if (res.ok) {
|
|
325
498
|
const data = await res.json();
|
|
326
|
-
if (data?.hub?.state ===
|
|
499
|
+
if (data?.hub?.state === "healthy") return true;
|
|
327
500
|
}
|
|
328
501
|
} catch {}
|
|
329
502
|
}
|
|
@@ -331,52 +504,54 @@ async function tryRestartHub() {
|
|
|
331
504
|
}
|
|
332
505
|
|
|
333
506
|
async function requestHub(operation, body, timeoutMs = 3000, fallback = null) {
|
|
334
|
-
const viaPipe =
|
|
335
|
-
|
|
336
|
-
|
|
507
|
+
const viaPipe =
|
|
508
|
+
operation.transport === "command"
|
|
509
|
+
? await pipeCommand(operation.action, body, timeoutMs)
|
|
510
|
+
: await pipeQuery(operation.action, body, timeoutMs);
|
|
337
511
|
if (viaPipe) {
|
|
338
|
-
return { transport:
|
|
512
|
+
return { transport: "pipe", result: viaPipe };
|
|
339
513
|
}
|
|
340
514
|
|
|
341
515
|
const viaHttp = operation.httpPath
|
|
342
516
|
? await requestJson(operation.httpPath, {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
517
|
+
method: operation.httpMethod || "POST",
|
|
518
|
+
body: operation.httpMethod === "GET" ? undefined : body,
|
|
519
|
+
timeoutMs: Math.max(timeoutMs, 5000),
|
|
520
|
+
})
|
|
347
521
|
: null;
|
|
348
522
|
if (viaHttp) {
|
|
349
|
-
return { transport:
|
|
523
|
+
return { transport: "http", result: viaHttp };
|
|
350
524
|
}
|
|
351
525
|
|
|
352
526
|
// Hub 재시작 시도 → Pipe/HTTP 재시도
|
|
353
527
|
if (await tryRestartHub()) {
|
|
354
|
-
const retryPipe =
|
|
355
|
-
|
|
356
|
-
|
|
528
|
+
const retryPipe =
|
|
529
|
+
operation.transport === "command"
|
|
530
|
+
? await pipeCommand(operation.action, body, timeoutMs)
|
|
531
|
+
: await pipeQuery(operation.action, body, timeoutMs);
|
|
357
532
|
if (retryPipe) {
|
|
358
|
-
return { transport:
|
|
533
|
+
return { transport: "pipe", result: retryPipe };
|
|
359
534
|
}
|
|
360
535
|
const retryHttp = operation.httpPath
|
|
361
536
|
? await requestJson(operation.httpPath, {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
537
|
+
method: operation.httpMethod || "POST",
|
|
538
|
+
body: operation.httpMethod === "GET" ? undefined : body,
|
|
539
|
+
timeoutMs: Math.max(timeoutMs, 5000),
|
|
540
|
+
})
|
|
366
541
|
: null;
|
|
367
542
|
if (retryHttp) {
|
|
368
|
-
return { transport:
|
|
543
|
+
return { transport: "http", result: retryHttp };
|
|
369
544
|
}
|
|
370
545
|
}
|
|
371
546
|
|
|
372
547
|
if (!fallback) return null;
|
|
373
548
|
const viaFallback = await fallback();
|
|
374
549
|
if (!viaFallback) return null;
|
|
375
|
-
return { transport:
|
|
550
|
+
return { transport: "fallback", result: viaFallback };
|
|
376
551
|
}
|
|
377
552
|
|
|
378
553
|
function unavailableResult() {
|
|
379
|
-
return { ok: false, reason:
|
|
554
|
+
return { ok: false, reason: "hub_unavailable" };
|
|
380
555
|
}
|
|
381
556
|
|
|
382
557
|
function emitJson(payload) {
|
|
@@ -388,14 +563,14 @@ function emitJson(payload) {
|
|
|
388
563
|
|
|
389
564
|
async function cmdRegister(args) {
|
|
390
565
|
const agentId = args.agent;
|
|
391
|
-
const timeoutSec = parseInt(args.timeout ||
|
|
566
|
+
const timeoutSec = parseInt(args.timeout || "600", 10);
|
|
392
567
|
const outcome = await requestHub(HUB_OPERATIONS.register, {
|
|
393
568
|
agent_id: agentId,
|
|
394
|
-
cli: args.cli ||
|
|
569
|
+
cli: args.cli || "other",
|
|
395
570
|
timeout_sec: timeoutSec,
|
|
396
571
|
heartbeat_ttl_ms: (timeoutSec + 120) * 1000,
|
|
397
|
-
topics: args.topics ? args.topics.split(
|
|
398
|
-
capabilities: args.capabilities ? args.capabilities.split(
|
|
572
|
+
topics: args.topics ? args.topics.split(",") : [],
|
|
573
|
+
capabilities: args.capabilities ? args.capabilities.split(",") : ["code"],
|
|
399
574
|
metadata: {
|
|
400
575
|
pid: process.ppid,
|
|
401
576
|
registered_at: Date.now(),
|
|
@@ -416,14 +591,14 @@ async function cmdRegister(args) {
|
|
|
416
591
|
}
|
|
417
592
|
|
|
418
593
|
async function cmdResult(args) {
|
|
419
|
-
let output =
|
|
594
|
+
let output = "";
|
|
420
595
|
if (args.file && existsSync(args.file)) {
|
|
421
|
-
output = readFileSync(args.file,
|
|
596
|
+
output = readFileSync(args.file, "utf8").slice(0, 49152);
|
|
422
597
|
}
|
|
423
598
|
|
|
424
599
|
const defaultPayload = {
|
|
425
600
|
agent_id: args.agent,
|
|
426
|
-
exit_code: parseInt(args[
|
|
601
|
+
exit_code: parseInt(args["exit-code"] || "0", 10),
|
|
427
602
|
output_length: output.length,
|
|
428
603
|
output_preview: output.slice(0, 4096),
|
|
429
604
|
output_file: args.file || null,
|
|
@@ -432,8 +607,10 @@ async function cmdResult(args) {
|
|
|
432
607
|
|
|
433
608
|
const outcome = await requestHub(HUB_OPERATIONS.result, {
|
|
434
609
|
agent_id: args.agent,
|
|
435
|
-
topic: args.topic ||
|
|
436
|
-
payload: args.payload
|
|
610
|
+
topic: args.topic || "task.result",
|
|
611
|
+
payload: args.payload
|
|
612
|
+
? parseJsonSafe(args.payload, defaultPayload)
|
|
613
|
+
: defaultPayload,
|
|
437
614
|
trace_id: args.trace || undefined,
|
|
438
615
|
correlation_id: args.correlation || undefined,
|
|
439
616
|
});
|
|
@@ -448,14 +625,48 @@ async function cmdResult(args) {
|
|
|
448
625
|
|
|
449
626
|
async function cmdControl(args) {
|
|
450
627
|
const outcome = await requestHub(HUB_OPERATIONS.control, {
|
|
451
|
-
from_agent: args.from ||
|
|
628
|
+
from_agent: args.from || "lead",
|
|
452
629
|
to_agent: args.to,
|
|
453
630
|
command: args.command,
|
|
454
|
-
reason: args.reason ||
|
|
631
|
+
reason: args.reason || "",
|
|
455
632
|
payload: args.payload ? parseJsonSafe(args.payload, {}) : {},
|
|
456
633
|
trace_id: args.trace || undefined,
|
|
457
634
|
correlation_id: args.correlation || undefined,
|
|
458
|
-
ttl_ms: args[
|
|
635
|
+
ttl_ms: args["ttl-ms"] != null ? Number(args["ttl-ms"]) : undefined,
|
|
636
|
+
});
|
|
637
|
+
const result = outcome?.result;
|
|
638
|
+
return emitJson(result || unavailableResult());
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
async function cmdHandoff(args) {
|
|
642
|
+
const from = args.from || args[1];
|
|
643
|
+
const to = args.to || args[2];
|
|
644
|
+
const payload = parseJsonSafe(args.payload || args[3] || "{}", {});
|
|
645
|
+
const outcome = await requestHub(
|
|
646
|
+
HUB_OPERATIONS.handoff,
|
|
647
|
+
buildHandoffBody(from, to, payload),
|
|
648
|
+
);
|
|
649
|
+
const result = outcome?.result;
|
|
650
|
+
return emitJson(result || unavailableResult());
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
async function cmdPublish(args) {
|
|
654
|
+
const from = args.from || args[1];
|
|
655
|
+
const to = args.to || args[2];
|
|
656
|
+
const type = args.type || args[3] || "event";
|
|
657
|
+
const payload = parseJsonSafe(args.payload || args[4] || "{}", {});
|
|
658
|
+
const outcome = await requestHub(
|
|
659
|
+
HUB_OPERATIONS.publish,
|
|
660
|
+
buildPublishBody(from, to, type, payload),
|
|
661
|
+
);
|
|
662
|
+
const result = outcome?.result;
|
|
663
|
+
return emitJson(result || unavailableResult());
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
async function cmdSendInput(args) {
|
|
667
|
+
const outcome = await requestHub(HUB_OPERATIONS.sendInput, {
|
|
668
|
+
session_id: args["session-id"],
|
|
669
|
+
text: args.text,
|
|
459
670
|
});
|
|
460
671
|
const result = outcome?.result;
|
|
461
672
|
return emitJson(result || unavailableResult());
|
|
@@ -464,26 +675,35 @@ async function cmdControl(args) {
|
|
|
464
675
|
async function cmdContext(args) {
|
|
465
676
|
const outcome = await requestHub(HUB_OPERATIONS.context, {
|
|
466
677
|
agent_id: args.agent,
|
|
467
|
-
topics: args.topics ? args.topics.split(
|
|
468
|
-
max_messages: parseInt(args.max ||
|
|
678
|
+
topics: args.topics ? args.topics.split(",") : undefined,
|
|
679
|
+
max_messages: parseInt(args.max || "10", 10),
|
|
469
680
|
auto_ack: true,
|
|
470
681
|
});
|
|
471
682
|
const result = outcome?.result;
|
|
472
683
|
|
|
473
684
|
if (result?.ok && result.data?.messages?.length) {
|
|
474
685
|
const parts = result.data.messages.map((message, index) => {
|
|
475
|
-
const payload =
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
686
|
+
const payload =
|
|
687
|
+
typeof message.payload === "string"
|
|
688
|
+
? message.payload
|
|
689
|
+
: JSON.stringify(message.payload, null, 2);
|
|
690
|
+
return `=== Context ${index + 1}: ${message.from_agent || "unknown"} (${message.topic || "unknown"}) ===\n${payload}`;
|
|
479
691
|
});
|
|
480
|
-
const combined = parts.join(
|
|
692
|
+
const combined = parts.join("\n\n");
|
|
481
693
|
|
|
482
694
|
if (args.out) {
|
|
483
|
-
writeFileSync(args.out, combined,
|
|
484
|
-
return emitJson({
|
|
695
|
+
writeFileSync(args.out, combined, "utf8");
|
|
696
|
+
return emitJson({
|
|
697
|
+
ok: true,
|
|
698
|
+
count: result.data.messages.length,
|
|
699
|
+
file: args.out,
|
|
700
|
+
});
|
|
485
701
|
} else {
|
|
486
|
-
return emitJson({
|
|
702
|
+
return emitJson({
|
|
703
|
+
ok: true,
|
|
704
|
+
count: result.data.messages.length,
|
|
705
|
+
context: combined,
|
|
706
|
+
});
|
|
487
707
|
}
|
|
488
708
|
}
|
|
489
709
|
|
|
@@ -491,7 +711,7 @@ async function cmdContext(args) {
|
|
|
491
711
|
if (args.out) {
|
|
492
712
|
return emitJson({ ok: true, count: 0 });
|
|
493
713
|
}
|
|
494
|
-
return emitJson({ ok: true, count: 0, context:
|
|
714
|
+
return emitJson({ ok: true, count: 0, context: "" });
|
|
495
715
|
}
|
|
496
716
|
|
|
497
717
|
return emitJson(result || unavailableResult());
|
|
@@ -504,7 +724,7 @@ async function cmdDeregister(args) {
|
|
|
504
724
|
const result = outcome?.result;
|
|
505
725
|
|
|
506
726
|
if (result?.ok) {
|
|
507
|
-
return emitJson({ ok: true, agent_id: args.agent, status:
|
|
727
|
+
return emitJson({ ok: true, agent_id: args.agent, status: "offline" });
|
|
508
728
|
}
|
|
509
729
|
|
|
510
730
|
return emitJson(result || unavailableResult());
|
|
@@ -512,15 +732,17 @@ async function cmdDeregister(args) {
|
|
|
512
732
|
|
|
513
733
|
async function cmdAssignAsync(args) {
|
|
514
734
|
const outcome = await requestHub(HUB_OPERATIONS.assignAsync, {
|
|
515
|
-
supervisor_agent: args[
|
|
516
|
-
worker_agent: args[
|
|
735
|
+
supervisor_agent: args["supervisor-agent"],
|
|
736
|
+
worker_agent: args["worker-agent"],
|
|
517
737
|
task: args.task,
|
|
518
|
-
topic: args.topic ||
|
|
738
|
+
topic: args.topic || "assign.job",
|
|
519
739
|
payload: args.payload ? parseJsonSafe(args.payload, {}) : {},
|
|
520
740
|
priority: args.priority != null ? Number(args.priority) : undefined,
|
|
521
|
-
ttl_ms: args[
|
|
522
|
-
timeout_ms:
|
|
523
|
-
|
|
741
|
+
ttl_ms: args["ttl-ms"] != null ? Number(args["ttl-ms"]) : undefined,
|
|
742
|
+
timeout_ms:
|
|
743
|
+
args["timeout-ms"] != null ? Number(args["timeout-ms"]) : undefined,
|
|
744
|
+
max_retries:
|
|
745
|
+
args["max-retries"] != null ? Number(args["max-retries"]) : undefined,
|
|
524
746
|
trace_id: args.trace || undefined,
|
|
525
747
|
correlation_id: args.correlation || undefined,
|
|
526
748
|
});
|
|
@@ -530,8 +752,8 @@ async function cmdAssignAsync(args) {
|
|
|
530
752
|
|
|
531
753
|
async function cmdAssignResult(args) {
|
|
532
754
|
const outcome = await requestHub(HUB_OPERATIONS.assignResult, {
|
|
533
|
-
job_id: args[
|
|
534
|
-
worker_agent: args[
|
|
755
|
+
job_id: args["job-id"],
|
|
756
|
+
worker_agent: args["worker-agent"],
|
|
535
757
|
status: args.status,
|
|
536
758
|
attempt: args.attempt != null ? Number(args.attempt) : undefined,
|
|
537
759
|
result: args.result ? parseJsonSafe(args.result, null) : undefined,
|
|
@@ -545,7 +767,7 @@ async function cmdAssignResult(args) {
|
|
|
545
767
|
|
|
546
768
|
async function cmdAssignStatus(args) {
|
|
547
769
|
const outcome = await requestHub(HUB_OPERATIONS.assignStatus, {
|
|
548
|
-
job_id: args[
|
|
770
|
+
job_id: args["job-id"],
|
|
549
771
|
});
|
|
550
772
|
const result = outcome?.result;
|
|
551
773
|
return emitJson(result || unavailableResult());
|
|
@@ -553,9 +775,9 @@ async function cmdAssignStatus(args) {
|
|
|
553
775
|
|
|
554
776
|
async function cmdAssignRetry(args) {
|
|
555
777
|
const outcome = await requestHub(HUB_OPERATIONS.assignRetry, {
|
|
556
|
-
job_id: args[
|
|
778
|
+
job_id: args["job-id"],
|
|
557
779
|
reason: args.reason,
|
|
558
|
-
requested_by: args[
|
|
780
|
+
requested_by: args["requested-by"],
|
|
559
781
|
});
|
|
560
782
|
const result = outcome?.result;
|
|
561
783
|
return emitJson(result || unavailableResult());
|
|
@@ -567,10 +789,15 @@ async function cmdTeamInfo(args) {
|
|
|
567
789
|
include_members: true,
|
|
568
790
|
include_paths: true,
|
|
569
791
|
};
|
|
570
|
-
const outcome = await requestHub(
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
792
|
+
const outcome = await requestHub(
|
|
793
|
+
HUB_OPERATIONS.teamInfo,
|
|
794
|
+
body,
|
|
795
|
+
3000,
|
|
796
|
+
async () => {
|
|
797
|
+
const { teamInfo } = await import("./team/nativeProxy.mjs");
|
|
798
|
+
return await teamInfo(body);
|
|
799
|
+
},
|
|
800
|
+
);
|
|
574
801
|
const result = outcome?.result;
|
|
575
802
|
return emitJson(result || unavailableResult());
|
|
576
803
|
}
|
|
@@ -579,14 +806,24 @@ async function cmdTeamTaskList(args) {
|
|
|
579
806
|
const body = {
|
|
580
807
|
team_name: args.team,
|
|
581
808
|
owner: args.owner,
|
|
582
|
-
statuses: args.statuses
|
|
583
|
-
|
|
584
|
-
|
|
809
|
+
statuses: args.statuses
|
|
810
|
+
? args.statuses
|
|
811
|
+
.split(",")
|
|
812
|
+
.map((status) => status.trim())
|
|
813
|
+
.filter(Boolean)
|
|
814
|
+
: [],
|
|
815
|
+
include_internal: !!args["include-internal"],
|
|
816
|
+
limit: parseInt(args.limit || "200", 10),
|
|
585
817
|
};
|
|
586
|
-
const outcome = await requestHub(
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
818
|
+
const outcome = await requestHub(
|
|
819
|
+
HUB_OPERATIONS.teamTaskList,
|
|
820
|
+
body,
|
|
821
|
+
3000,
|
|
822
|
+
async () => {
|
|
823
|
+
const { teamTaskList } = await import("./team/nativeProxy.mjs");
|
|
824
|
+
return await teamTaskList(body);
|
|
825
|
+
},
|
|
826
|
+
);
|
|
590
827
|
const result = outcome?.result;
|
|
591
828
|
return emitJson(result || unavailableResult());
|
|
592
829
|
}
|
|
@@ -594,23 +831,43 @@ async function cmdTeamTaskList(args) {
|
|
|
594
831
|
async function cmdTeamTaskUpdate(args) {
|
|
595
832
|
const body = {
|
|
596
833
|
team_name: args.team,
|
|
597
|
-
task_id: args[
|
|
834
|
+
task_id: args["task-id"],
|
|
598
835
|
claim: !!args.claim,
|
|
599
836
|
owner: args.owner,
|
|
600
837
|
status: args.status,
|
|
601
838
|
subject: args.subject,
|
|
602
839
|
description: args.description,
|
|
603
|
-
activeForm: args[
|
|
604
|
-
add_blocks: args[
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
840
|
+
activeForm: args["active-form"],
|
|
841
|
+
add_blocks: args["add-blocks"]
|
|
842
|
+
? args["add-blocks"]
|
|
843
|
+
.split(",")
|
|
844
|
+
.map((value) => value.trim())
|
|
845
|
+
.filter(Boolean)
|
|
846
|
+
: undefined,
|
|
847
|
+
add_blocked_by: args["add-blocked-by"]
|
|
848
|
+
? args["add-blocked-by"]
|
|
849
|
+
.split(",")
|
|
850
|
+
.map((value) => value.trim())
|
|
851
|
+
.filter(Boolean)
|
|
852
|
+
: undefined,
|
|
853
|
+
metadata_patch: args["metadata-patch"]
|
|
854
|
+
? parseJsonSafe(args["metadata-patch"], null)
|
|
855
|
+
: undefined,
|
|
856
|
+
if_match_mtime_ms:
|
|
857
|
+
args["if-match-mtime-ms"] != null
|
|
858
|
+
? Number(args["if-match-mtime-ms"])
|
|
859
|
+
: undefined,
|
|
608
860
|
actor: args.actor,
|
|
609
861
|
};
|
|
610
|
-
const outcome = await requestHub(
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
862
|
+
const outcome = await requestHub(
|
|
863
|
+
HUB_OPERATIONS.teamTaskUpdate,
|
|
864
|
+
body,
|
|
865
|
+
3000,
|
|
866
|
+
async () => {
|
|
867
|
+
const { teamTaskUpdate } = await import("./team/nativeProxy.mjs");
|
|
868
|
+
return await teamTaskUpdate(body);
|
|
869
|
+
},
|
|
870
|
+
);
|
|
614
871
|
const result = outcome?.result;
|
|
615
872
|
return emitJson(result || unavailableResult());
|
|
616
873
|
}
|
|
@@ -619,15 +876,20 @@ async function cmdTeamSendMessage(args) {
|
|
|
619
876
|
const body = {
|
|
620
877
|
team_name: args.team,
|
|
621
878
|
from: args.from,
|
|
622
|
-
to: args.to ||
|
|
879
|
+
to: args.to || "team-lead",
|
|
623
880
|
text: args.text,
|
|
624
881
|
summary: args.summary,
|
|
625
|
-
color: args.color ||
|
|
882
|
+
color: args.color || "blue",
|
|
626
883
|
};
|
|
627
|
-
const outcome = await requestHub(
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
884
|
+
const outcome = await requestHub(
|
|
885
|
+
HUB_OPERATIONS.teamSendMessage,
|
|
886
|
+
body,
|
|
887
|
+
3000,
|
|
888
|
+
async () => {
|
|
889
|
+
const { teamSendMessage } = await import("./team/nativeProxy.mjs");
|
|
890
|
+
return await teamSendMessage(body);
|
|
891
|
+
},
|
|
892
|
+
);
|
|
631
893
|
const result = outcome?.result;
|
|
632
894
|
return emitJson(result || unavailableResult());
|
|
633
895
|
}
|
|
@@ -637,25 +899,32 @@ function getHubDbPath() {
|
|
|
637
899
|
}
|
|
638
900
|
|
|
639
901
|
async function cmdPipelineState(args) {
|
|
640
|
-
const outcome = await requestHub(
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
902
|
+
const outcome = await requestHub(
|
|
903
|
+
HUB_OPERATIONS.pipelineState,
|
|
904
|
+
{ team_name: args.team },
|
|
905
|
+
3000,
|
|
906
|
+
async () => {
|
|
907
|
+
try {
|
|
908
|
+
const { default: Database } = await import("better-sqlite3");
|
|
909
|
+
const { ensurePipelineTable, readPipelineState } = await import(
|
|
910
|
+
"./pipeline/state.mjs"
|
|
911
|
+
);
|
|
912
|
+
const dbPath = getHubDbPath();
|
|
913
|
+
if (!existsSync(dbPath)) {
|
|
914
|
+
return { ok: false, error: "hub_db_not_found" };
|
|
915
|
+
}
|
|
916
|
+
const db = new Database(dbPath, { readonly: true });
|
|
917
|
+
ensurePipelineTable(db);
|
|
918
|
+
const state = readPipelineState(db, args.team);
|
|
919
|
+
db.close();
|
|
920
|
+
return state
|
|
921
|
+
? { ok: true, data: state }
|
|
922
|
+
: { ok: false, error: "pipeline_not_found" };
|
|
923
|
+
} catch (e) {
|
|
924
|
+
return { ok: false, error: e.message };
|
|
647
925
|
}
|
|
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
|
-
});
|
|
926
|
+
},
|
|
927
|
+
);
|
|
659
928
|
const result = outcome?.result;
|
|
660
929
|
return emitJson(result || unavailableResult());
|
|
661
930
|
}
|
|
@@ -665,23 +934,28 @@ async function cmdPipelineAdvance(args) {
|
|
|
665
934
|
team_name: args.team,
|
|
666
935
|
phase: args.status, // --status를 phase로 재활용
|
|
667
936
|
};
|
|
668
|
-
const outcome = await requestHub(
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
937
|
+
const outcome = await requestHub(
|
|
938
|
+
HUB_OPERATIONS.pipelineAdvance,
|
|
939
|
+
body,
|
|
940
|
+
3000,
|
|
941
|
+
async () => {
|
|
942
|
+
try {
|
|
943
|
+
const { default: Database } = await import("better-sqlite3");
|
|
944
|
+
const { createPipeline } = await import("./pipeline/index.mjs");
|
|
945
|
+
const dbPath = getHubDbPath();
|
|
946
|
+
if (!existsSync(dbPath)) {
|
|
947
|
+
return { ok: false, error: "hub_db_not_found" };
|
|
948
|
+
}
|
|
949
|
+
const db = new Database(dbPath);
|
|
950
|
+
const pipeline = createPipeline(db, args.team);
|
|
951
|
+
const advanceResult = pipeline.advance(args.status);
|
|
952
|
+
db.close();
|
|
953
|
+
return advanceResult;
|
|
954
|
+
} catch (e) {
|
|
955
|
+
return { ok: false, error: e.message };
|
|
675
956
|
}
|
|
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
|
-
});
|
|
957
|
+
},
|
|
958
|
+
);
|
|
685
959
|
const result = outcome?.result;
|
|
686
960
|
return emitJson(result || unavailableResult());
|
|
687
961
|
}
|
|
@@ -689,8 +963,9 @@ async function cmdPipelineAdvance(args) {
|
|
|
689
963
|
async function cmdPipelineInit(args) {
|
|
690
964
|
const outcome = await requestHub(HUB_OPERATIONS.pipelineInit, {
|
|
691
965
|
team_name: args.team,
|
|
692
|
-
fix_max: args[
|
|
693
|
-
ralph_max:
|
|
966
|
+
fix_max: args["fix-max"] != null ? Number(args["fix-max"]) : undefined,
|
|
967
|
+
ralph_max:
|
|
968
|
+
args["ralph-max"] != null ? Number(args["ralph-max"]) : undefined,
|
|
694
969
|
});
|
|
695
970
|
const result = outcome?.result;
|
|
696
971
|
return emitJson(result || unavailableResult());
|
|
@@ -703,25 +978,29 @@ async function cmdPipelineList() {
|
|
|
703
978
|
}
|
|
704
979
|
|
|
705
980
|
async function cmdPing() {
|
|
706
|
-
const outcome = await requestHub(
|
|
981
|
+
const outcome = await requestHub(
|
|
982
|
+
HUB_OPERATIONS.hubStatus,
|
|
983
|
+
{ scope: "hub" },
|
|
984
|
+
2000,
|
|
985
|
+
);
|
|
707
986
|
|
|
708
|
-
if (outcome?.transport ===
|
|
987
|
+
if (outcome?.transport === "pipe" && outcome.result?.ok) {
|
|
709
988
|
return emitJson({
|
|
710
989
|
ok: true,
|
|
711
|
-
hub: outcome.result.data?.hub?.state ||
|
|
990
|
+
hub: outcome.result.data?.hub?.state || "healthy",
|
|
712
991
|
pipe_path: getHubPipePath(),
|
|
713
|
-
transport:
|
|
992
|
+
transport: "pipe",
|
|
714
993
|
});
|
|
715
994
|
}
|
|
716
995
|
|
|
717
|
-
if (outcome?.transport ===
|
|
996
|
+
if (outcome?.transport === "http" && outcome.result) {
|
|
718
997
|
const data = outcome.result;
|
|
719
998
|
return emitJson({
|
|
720
999
|
ok: true,
|
|
721
1000
|
hub: data.hub?.state,
|
|
722
1001
|
sessions: data.sessions,
|
|
723
1002
|
pipe_path: data.pipe?.path || data.pipe_path || null,
|
|
724
|
-
transport:
|
|
1003
|
+
transport: "http",
|
|
725
1004
|
});
|
|
726
1005
|
}
|
|
727
1006
|
|
|
@@ -731,21 +1010,26 @@ async function cmdPing() {
|
|
|
731
1010
|
async function cmdDelegatorDelegate(args) {
|
|
732
1011
|
const body = {
|
|
733
1012
|
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:
|
|
1013
|
+
provider: args.provider || "auto",
|
|
1014
|
+
mode: args.mode || "sync",
|
|
1015
|
+
agent_type: args.agent || "executor",
|
|
1016
|
+
mcp_profile: args["mcp-profile"] || "auto",
|
|
1017
|
+
session_key: args["session-key"] || undefined,
|
|
1018
|
+
timeout_ms:
|
|
1019
|
+
args["timeout-ms"] != null ? Number(args["timeout-ms"]) : undefined,
|
|
740
1020
|
};
|
|
741
|
-
const timeoutMs = body.mode ===
|
|
742
|
-
const outcome = await requestHub(
|
|
1021
|
+
const timeoutMs = body.mode === "async" ? 10000 : 120000;
|
|
1022
|
+
const outcome = await requestHub(
|
|
1023
|
+
HUB_OPERATIONS.delegatorDelegate,
|
|
1024
|
+
body,
|
|
1025
|
+
timeoutMs,
|
|
1026
|
+
);
|
|
743
1027
|
return emitJson(outcome?.result || unavailableResult());
|
|
744
1028
|
}
|
|
745
1029
|
|
|
746
1030
|
async function cmdDelegatorReply(args) {
|
|
747
1031
|
const body = {
|
|
748
|
-
job_id: args[
|
|
1032
|
+
job_id: args["job-id"],
|
|
749
1033
|
reply: args.text || args.reply,
|
|
750
1034
|
done: !!args.done,
|
|
751
1035
|
};
|
|
@@ -755,45 +1039,109 @@ async function cmdDelegatorReply(args) {
|
|
|
755
1039
|
|
|
756
1040
|
async function cmdDelegatorStatus(args) {
|
|
757
1041
|
const body = {
|
|
758
|
-
job_id: args[
|
|
1042
|
+
job_id: args["job-id"],
|
|
759
1043
|
};
|
|
760
1044
|
const outcome = await requestHub(HUB_OPERATIONS.delegatorStatus, body, 5000);
|
|
761
1045
|
return emitJson(outcome?.result || unavailableResult());
|
|
762
1046
|
}
|
|
763
1047
|
|
|
1048
|
+
async function cmdHitlRequest(args) {
|
|
1049
|
+
const body = {
|
|
1050
|
+
kind: args[1],
|
|
1051
|
+
prompt: args[2],
|
|
1052
|
+
requester_agent: args[3] || "cli",
|
|
1053
|
+
};
|
|
1054
|
+
const outcome = await requestHub(
|
|
1055
|
+
HUB_OPERATIONS["hitl-request"],
|
|
1056
|
+
body,
|
|
1057
|
+
120000,
|
|
1058
|
+
);
|
|
1059
|
+
return emitJson(outcome?.result || unavailableResult());
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
async function cmdHitlSubmit(args) {
|
|
1063
|
+
const body = {
|
|
1064
|
+
request_id: args[1],
|
|
1065
|
+
action: args[2] || "accept",
|
|
1066
|
+
content: args[3],
|
|
1067
|
+
};
|
|
1068
|
+
const outcome = await requestHub(HUB_OPERATIONS["hitl-submit"], body, 120000);
|
|
1069
|
+
return emitJson(outcome?.result || unavailableResult());
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
async function cmdHitlPending() {
|
|
1073
|
+
const outcome = await requestHub(HUB_OPERATIONS["hitl-pending"], {}, 5000);
|
|
1074
|
+
return emitJson(outcome?.result || unavailableResult());
|
|
1075
|
+
}
|
|
1076
|
+
|
|
764
1077
|
export async function main(argv = process.argv.slice(2)) {
|
|
765
1078
|
const cmd = argv[0];
|
|
766
1079
|
const args = parseArgs(argv.slice(1));
|
|
767
1080
|
|
|
768
1081
|
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
|
|
1082
|
+
case "register":
|
|
1083
|
+
return await cmdRegister(args);
|
|
1084
|
+
case "result":
|
|
1085
|
+
return await cmdResult(args);
|
|
1086
|
+
case "control":
|
|
1087
|
+
return await cmdControl(args);
|
|
1088
|
+
case "handoff":
|
|
1089
|
+
return await cmdHandoff(args);
|
|
1090
|
+
case "publish":
|
|
1091
|
+
return await cmdPublish(args);
|
|
1092
|
+
case "send-input":
|
|
1093
|
+
return await cmdSendInput(args);
|
|
1094
|
+
case "context":
|
|
1095
|
+
return await cmdContext(args);
|
|
1096
|
+
case "deregister":
|
|
1097
|
+
return await cmdDeregister(args);
|
|
1098
|
+
case "assign-async":
|
|
1099
|
+
return await cmdAssignAsync(args);
|
|
1100
|
+
case "assign-result":
|
|
1101
|
+
return await cmdAssignResult(args);
|
|
1102
|
+
case "assign-status":
|
|
1103
|
+
return await cmdAssignStatus(args);
|
|
1104
|
+
case "assign-retry":
|
|
1105
|
+
return await cmdAssignRetry(args);
|
|
1106
|
+
case "team-info":
|
|
1107
|
+
return await cmdTeamInfo(args);
|
|
1108
|
+
case "team-task-list":
|
|
1109
|
+
return await cmdTeamTaskList(args);
|
|
1110
|
+
case "team-task-update":
|
|
1111
|
+
return await cmdTeamTaskUpdate(args);
|
|
1112
|
+
case "team-send-message":
|
|
1113
|
+
return await cmdTeamSendMessage(args);
|
|
1114
|
+
case "pipeline-state":
|
|
1115
|
+
return await cmdPipelineState(args);
|
|
1116
|
+
case "pipeline-advance":
|
|
1117
|
+
return await cmdPipelineAdvance(args);
|
|
1118
|
+
case "pipeline-init":
|
|
1119
|
+
return await cmdPipelineInit(args);
|
|
1120
|
+
case "pipeline-list":
|
|
1121
|
+
return await cmdPipelineList(args);
|
|
1122
|
+
case "ping":
|
|
1123
|
+
return await cmdPing(args);
|
|
1124
|
+
case "delegator-delegate":
|
|
1125
|
+
return await cmdDelegatorDelegate(args);
|
|
1126
|
+
case "delegator-reply":
|
|
1127
|
+
return await cmdDelegatorReply(args);
|
|
1128
|
+
case "delegator-status":
|
|
1129
|
+
return await cmdDelegatorStatus(args);
|
|
1130
|
+
case "hitl-request":
|
|
1131
|
+
return await cmdHitlRequest(args);
|
|
1132
|
+
case "hitl-submit":
|
|
1133
|
+
return await cmdHitlSubmit(args);
|
|
1134
|
+
case "hitl-pending":
|
|
1135
|
+
return await cmdHitlPending(args);
|
|
790
1136
|
default:
|
|
791
|
-
console.error(
|
|
1137
|
+
console.error(
|
|
1138
|
+
"사용법: 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> [--옵션]",
|
|
1139
|
+
);
|
|
792
1140
|
process.exit(1);
|
|
793
1141
|
}
|
|
794
1142
|
}
|
|
795
1143
|
|
|
796
|
-
const selfRun = process.argv[1]?.replace(/\\/g,
|
|
1144
|
+
const selfRun = process.argv[1]?.replace(/\\/g, "/").endsWith("hub/bridge.mjs");
|
|
797
1145
|
if (selfRun) {
|
|
798
|
-
process.exitCode = await main() ? 0 : 1;
|
|
1146
|
+
process.exitCode = (await main()) ? 0 : 1;
|
|
799
1147
|
}
|