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