triflux 3.2.0-dev.9 → 3.3.0-dev.3
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/bin/triflux.mjs +1516 -1386
- package/hooks/hooks.json +22 -0
- package/hooks/keyword-rules.json +4 -4
- package/hooks/pipeline-stop.mjs +54 -0
- package/hub/bridge.mjs +120 -13
- package/hub/pipe.mjs +23 -0
- package/hub/pipeline/index.mjs +121 -0
- package/hub/pipeline/state.mjs +164 -0
- package/hub/pipeline/transitions.mjs +114 -0
- package/hub/router.mjs +322 -1
- package/hub/schema.sql +49 -2
- package/hub/server.mjs +173 -8
- package/hub/store.mjs +259 -1
- package/hub/team/cli-team-control.mjs +381 -381
- package/hub/team/cli-team-start.mjs +474 -470
- package/hub/team/cli-team-status.mjs +238 -238
- package/hub/team/cli.mjs +86 -86
- package/hub/team/native.mjs +144 -6
- package/hub/team/nativeProxy.mjs +51 -38
- package/hub/team/orchestrator.mjs +15 -20
- package/hub/team/pane.mjs +101 -90
- package/hub/team/psmux.mjs +721 -72
- package/hub/team/session.mjs +450 -450
- package/hub/tools.mjs +223 -63
- package/hub/workers/delegator-mcp.mjs +900 -0
- package/hub/workers/factory.mjs +3 -0
- package/hub/workers/interface.mjs +2 -2
- package/hud/hud-qos-status.mjs +89 -144
- package/package.json +1 -1
- package/scripts/__tests__/keyword-detector.test.mjs +11 -11
- package/scripts/__tests__/smoke.test.mjs +34 -0
- package/scripts/hub-ensure.mjs +21 -3
- package/scripts/preflight-cache.mjs +72 -0
- package/scripts/setup.mjs +23 -11
- package/scripts/tfx-route.sh +74 -15
- package/skills/{tfx-team → tfx-multi}/SKILL.md +115 -32
package/hub/tools.mjs
CHANGED
|
@@ -8,16 +8,25 @@ import {
|
|
|
8
8
|
teamTaskUpdate,
|
|
9
9
|
teamSendMessage,
|
|
10
10
|
} from './team/nativeProxy.mjs';
|
|
11
|
+
import {
|
|
12
|
+
ensurePipelineTable,
|
|
13
|
+
createPipeline,
|
|
14
|
+
} from './pipeline/index.mjs';
|
|
15
|
+
import {
|
|
16
|
+
readPipelineState,
|
|
17
|
+
initPipelineState,
|
|
18
|
+
listPipelineStates,
|
|
19
|
+
} from './pipeline/state.mjs';
|
|
11
20
|
|
|
12
21
|
/**
|
|
13
22
|
* MCP 도구 목록 생성
|
|
14
23
|
* @param {object} store — createStore() 반환
|
|
15
24
|
* @param {object} router — createRouter() 반환
|
|
16
|
-
* @param {object} hitl — createHitlManager() 반환
|
|
17
|
-
* @param {object} pipe — createPipeServer() 반환
|
|
18
|
-
* @returns {Array<{name, description, inputSchema, handler}>}
|
|
19
|
-
*/
|
|
20
|
-
export function createTools(store, router, hitl, pipe = null) {
|
|
25
|
+
* @param {object} hitl — createHitlManager() 반환
|
|
26
|
+
* @param {object} pipe — createPipeServer() 반환
|
|
27
|
+
* @returns {Array<{name, description, inputSchema, handler}>}
|
|
28
|
+
*/
|
|
29
|
+
export function createTools(store, router, hitl, pipe = null) {
|
|
21
30
|
/** 도구 핸들러 래퍼 — 에러 처리 + MCP content 형식 변환 */
|
|
22
31
|
function wrap(code, fn) {
|
|
23
32
|
return async (args) => {
|
|
@@ -49,10 +58,10 @@ export function createTools(store, router, hitl, pipe = null) {
|
|
|
49
58
|
heartbeat_ttl_ms: { type: 'integer', minimum: 10000, maximum: 7200000 },
|
|
50
59
|
},
|
|
51
60
|
},
|
|
52
|
-
handler: wrap('REGISTER_FAILED', (args) => {
|
|
53
|
-
const data = router.registerAgent(args);
|
|
54
|
-
return { ok: true, data };
|
|
55
|
-
}),
|
|
61
|
+
handler: wrap('REGISTER_FAILED', (args) => {
|
|
62
|
+
const data = router.registerAgent(args);
|
|
63
|
+
return { ok: true, data };
|
|
64
|
+
}),
|
|
56
65
|
},
|
|
57
66
|
|
|
58
67
|
// ── 2. status ──
|
|
@@ -122,10 +131,10 @@ export function createTools(store, router, hitl, pipe = null) {
|
|
|
122
131
|
}),
|
|
123
132
|
},
|
|
124
133
|
|
|
125
|
-
// ── 5. poll_messages ──
|
|
126
|
-
{
|
|
127
|
-
name: 'poll_messages',
|
|
128
|
-
description: 'Deprecated. poll_messages 대신 Named Pipe subscribe/publish 채널을 사용합니다',
|
|
134
|
+
// ── 5. poll_messages ──
|
|
135
|
+
{
|
|
136
|
+
name: 'poll_messages',
|
|
137
|
+
description: 'Deprecated. poll_messages 대신 Named Pipe subscribe/publish 채널을 사용합니다',
|
|
129
138
|
inputSchema: {
|
|
130
139
|
type: 'object',
|
|
131
140
|
required: ['agent_id'],
|
|
@@ -137,40 +146,40 @@ export function createTools(store, router, hitl, pipe = null) {
|
|
|
137
146
|
ack_ids: { type: 'array', items: { type: 'string' }, maxItems: 100 },
|
|
138
147
|
auto_ack: { type: 'boolean', default: false },
|
|
139
148
|
},
|
|
140
|
-
},
|
|
141
|
-
handler: wrap('POLL_DEPRECATED', async (args) => {
|
|
142
|
-
const replay = router.drainAgent(args.agent_id, {
|
|
143
|
-
max_messages: args.max_messages,
|
|
144
|
-
include_topics: args.include_topics,
|
|
145
|
-
auto_ack: args.auto_ack,
|
|
146
|
-
});
|
|
147
|
-
if (args.ack_ids?.length) {
|
|
148
|
-
router.ackMessages(args.ack_ids, args.agent_id);
|
|
149
|
-
}
|
|
150
|
-
return {
|
|
151
|
-
ok: false,
|
|
152
|
-
error: {
|
|
153
|
-
code: 'POLL_DEPRECATED',
|
|
154
|
-
message: 'poll_messages는 deprecated 되었습니다. pipe subscribe/publish 채널을 사용하세요.',
|
|
155
|
-
},
|
|
156
|
-
data: {
|
|
157
|
-
pipe_path: pipe?.path || null,
|
|
158
|
-
delivery_mode: 'pipe_push',
|
|
159
|
-
protocol: 'ndjson',
|
|
160
|
-
replay: {
|
|
161
|
-
messages: replay,
|
|
162
|
-
count: replay.length,
|
|
163
|
-
},
|
|
164
|
-
server_time_ms: Date.now(),
|
|
165
|
-
},
|
|
166
|
-
};
|
|
167
|
-
}),
|
|
149
|
+
},
|
|
150
|
+
handler: wrap('POLL_DEPRECATED', async (args) => {
|
|
151
|
+
const replay = router.drainAgent(args.agent_id, {
|
|
152
|
+
max_messages: args.max_messages,
|
|
153
|
+
include_topics: args.include_topics,
|
|
154
|
+
auto_ack: args.auto_ack,
|
|
155
|
+
});
|
|
156
|
+
if (args.ack_ids?.length) {
|
|
157
|
+
router.ackMessages(args.ack_ids, args.agent_id);
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
ok: false,
|
|
161
|
+
error: {
|
|
162
|
+
code: 'POLL_DEPRECATED',
|
|
163
|
+
message: 'poll_messages는 deprecated 되었습니다. pipe subscribe/publish 채널을 사용하세요.',
|
|
164
|
+
},
|
|
165
|
+
data: {
|
|
166
|
+
pipe_path: pipe?.path || null,
|
|
167
|
+
delivery_mode: 'pipe_push',
|
|
168
|
+
protocol: 'ndjson',
|
|
169
|
+
replay: {
|
|
170
|
+
messages: replay,
|
|
171
|
+
count: replay.length,
|
|
172
|
+
},
|
|
173
|
+
server_time_ms: Date.now(),
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}),
|
|
168
177
|
},
|
|
169
178
|
|
|
170
179
|
// ── 6. handoff ──
|
|
171
|
-
{
|
|
172
|
-
name: 'handoff',
|
|
173
|
-
description: '다른 에이전트에게 작업을 인계합니다. acceptance_criteria로 완료 기준 지정 가능',
|
|
180
|
+
{
|
|
181
|
+
name: 'handoff',
|
|
182
|
+
description: '다른 에이전트에게 작업을 인계합니다. acceptance_criteria로 완료 기준 지정 가능',
|
|
174
183
|
inputSchema: {
|
|
175
184
|
type: 'object',
|
|
176
185
|
required: ['from', 'to', 'topic', 'task'],
|
|
@@ -187,15 +196,90 @@ export function createTools(store, router, hitl, pipe = null) {
|
|
|
187
196
|
correlation_id: { type: 'string' },
|
|
188
197
|
},
|
|
189
198
|
},
|
|
190
|
-
handler: wrap('HANDOFF_FAILED', (args) => {
|
|
191
|
-
return router.handleHandoff(args);
|
|
192
|
-
}),
|
|
193
|
-
},
|
|
194
|
-
|
|
195
|
-
// ── 7.
|
|
196
|
-
{
|
|
197
|
-
name: '
|
|
198
|
-
description: '
|
|
199
|
+
handler: wrap('HANDOFF_FAILED', (args) => {
|
|
200
|
+
return router.handleHandoff(args);
|
|
201
|
+
}),
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
// ── 7. assign_async ──
|
|
205
|
+
{
|
|
206
|
+
name: 'assign_async',
|
|
207
|
+
description: 'AWS CAO 스타일 비차단 assign job을 생성하고 워커에게 실시간 전달합니다',
|
|
208
|
+
inputSchema: {
|
|
209
|
+
type: 'object',
|
|
210
|
+
required: ['supervisor_agent', 'worker_agent', 'task'],
|
|
211
|
+
properties: {
|
|
212
|
+
supervisor_agent: { type: 'string', pattern: '^[a-zA-Z0-9._:-]{3,64}$' },
|
|
213
|
+
worker_agent: { type: 'string', pattern: '^[a-zA-Z0-9._:-]{3,64}$' },
|
|
214
|
+
topic: { type: 'string', pattern: '^[a-zA-Z0-9._:-]+$', default: 'assign.job' },
|
|
215
|
+
task: { type: 'string', minLength: 1, maxLength: 20000 },
|
|
216
|
+
payload: { type: 'object' },
|
|
217
|
+
priority: { type: 'integer', minimum: 1, maximum: 9, default: 5 },
|
|
218
|
+
ttl_ms: { type: 'integer', minimum: 1000, maximum: 86400000, default: 600000 },
|
|
219
|
+
timeout_ms: { type: 'integer', minimum: 1000, maximum: 86400000, default: 600000 },
|
|
220
|
+
max_retries: { type: 'integer', minimum: 0, maximum: 20, default: 0 },
|
|
221
|
+
trace_id: { type: 'string' },
|
|
222
|
+
correlation_id: { type: 'string' },
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
handler: wrap('ASSIGN_ASYNC_FAILED', (args) => {
|
|
226
|
+
return router.assignAsync(args);
|
|
227
|
+
}),
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
// ── 8. assign_result ──
|
|
231
|
+
{
|
|
232
|
+
name: 'assign_result',
|
|
233
|
+
description: 'assign job의 진행/완료 결과를 보고합니다. completed + metadata.result 관례를 지원합니다',
|
|
234
|
+
inputSchema: {
|
|
235
|
+
type: 'object',
|
|
236
|
+
required: ['job_id', 'status'],
|
|
237
|
+
properties: {
|
|
238
|
+
job_id: { type: 'string', minLength: 1, maxLength: 128 },
|
|
239
|
+
worker_agent: { type: 'string', pattern: '^[a-zA-Z0-9._:-]{3,64}$' },
|
|
240
|
+
status: { type: 'string', enum: ['queued', 'running', 'in_progress', 'completed', 'succeeded', 'success', 'failed', 'error', 'timed_out', 'timeout'] },
|
|
241
|
+
attempt: { type: 'integer', minimum: 1 },
|
|
242
|
+
result: {},
|
|
243
|
+
error: {},
|
|
244
|
+
metadata: { type: 'object' },
|
|
245
|
+
payload: { type: 'object' },
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
handler: wrap('ASSIGN_RESULT_FAILED', (args) => {
|
|
249
|
+
return router.reportAssignResult(args);
|
|
250
|
+
}),
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
// ── 9. assign_status ──
|
|
254
|
+
{
|
|
255
|
+
name: 'assign_status',
|
|
256
|
+
description: 'assign job 단건 상태 또는 supervisor/worker/status 기준 목록을 조회합니다',
|
|
257
|
+
inputSchema: {
|
|
258
|
+
type: 'object',
|
|
259
|
+
properties: {
|
|
260
|
+
job_id: { type: 'string', minLength: 1, maxLength: 128 },
|
|
261
|
+
supervisor_agent: { type: 'string', pattern: '^[a-zA-Z0-9._:-]{3,64}$' },
|
|
262
|
+
worker_agent: { type: 'string', pattern: '^[a-zA-Z0-9._:-]{3,64}$' },
|
|
263
|
+
status: { type: 'string', enum: ['queued', 'running', 'succeeded', 'failed', 'timed_out'] },
|
|
264
|
+
statuses: {
|
|
265
|
+
type: 'array',
|
|
266
|
+
items: { type: 'string', enum: ['queued', 'running', 'succeeded', 'failed', 'timed_out'] },
|
|
267
|
+
maxItems: 8,
|
|
268
|
+
},
|
|
269
|
+
trace_id: { type: 'string' },
|
|
270
|
+
correlation_id: { type: 'string' },
|
|
271
|
+
limit: { type: 'integer', minimum: 1, maximum: 100, default: 50 },
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
handler: wrap('ASSIGN_STATUS_FAILED', (args) => {
|
|
275
|
+
return router.getAssignStatus(args);
|
|
276
|
+
}),
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
// ── 10. request_human_input ──
|
|
280
|
+
{
|
|
281
|
+
name: 'request_human_input',
|
|
282
|
+
description: '사용자에게 입력을 요청합니다 (CAPTCHA, 승인, 자격증명, 선택, 텍스트)',
|
|
199
283
|
inputSchema: {
|
|
200
284
|
type: 'object',
|
|
201
285
|
required: ['requester_agent', 'kind', 'prompt', 'requested_schema', 'deadline_ms', 'default_action'],
|
|
@@ -211,12 +295,12 @@ export function createTools(store, router, hitl, pipe = null) {
|
|
|
211
295
|
correlation_id: { type: 'string' },
|
|
212
296
|
},
|
|
213
297
|
},
|
|
214
|
-
handler: wrap('HITL_REQUEST_FAILED', (args) => {
|
|
215
|
-
return hitl.requestHumanInput(args);
|
|
216
|
-
}),
|
|
217
|
-
},
|
|
218
|
-
|
|
219
|
-
// ──
|
|
298
|
+
handler: wrap('HITL_REQUEST_FAILED', (args) => {
|
|
299
|
+
return hitl.requestHumanInput(args);
|
|
300
|
+
}),
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
// ── 11. submit_human_input ──
|
|
220
304
|
{
|
|
221
305
|
name: 'submit_human_input',
|
|
222
306
|
description: '사용자 입력 요청에 응답합니다 (accept, decline, cancel)',
|
|
@@ -235,7 +319,7 @@ export function createTools(store, router, hitl, pipe = null) {
|
|
|
235
319
|
}),
|
|
236
320
|
},
|
|
237
321
|
|
|
238
|
-
// ──
|
|
322
|
+
// ── 12. team_info ──
|
|
239
323
|
{
|
|
240
324
|
name: 'team_info',
|
|
241
325
|
description: 'Claude Native Teams 메타/멤버/경로 정보를 조회합니다',
|
|
@@ -253,7 +337,7 @@ export function createTools(store, router, hitl, pipe = null) {
|
|
|
253
337
|
}),
|
|
254
338
|
},
|
|
255
339
|
|
|
256
|
-
// ──
|
|
340
|
+
// ── 13. team_task_list ──
|
|
257
341
|
{
|
|
258
342
|
name: 'team_task_list',
|
|
259
343
|
description: 'Claude Native Teams task 목록을 owner/status 조건으로 조회합니다',
|
|
@@ -277,7 +361,7 @@ export function createTools(store, router, hitl, pipe = null) {
|
|
|
277
361
|
}),
|
|
278
362
|
},
|
|
279
363
|
|
|
280
|
-
// ──
|
|
364
|
+
// ── 14. team_task_update ──
|
|
281
365
|
{
|
|
282
366
|
name: 'team_task_update',
|
|
283
367
|
description: 'Claude Native Teams task를 claim/update 합니다',
|
|
@@ -305,7 +389,7 @@ export function createTools(store, router, hitl, pipe = null) {
|
|
|
305
389
|
}),
|
|
306
390
|
},
|
|
307
391
|
|
|
308
|
-
// ──
|
|
392
|
+
// ── 15. team_send_message ──
|
|
309
393
|
{
|
|
310
394
|
name: 'team_send_message',
|
|
311
395
|
description: 'Claude Native Teams inbox에 메시지를 append 합니다',
|
|
@@ -325,5 +409,81 @@ export function createTools(store, router, hitl, pipe = null) {
|
|
|
325
409
|
return teamSendMessage(args);
|
|
326
410
|
}),
|
|
327
411
|
},
|
|
412
|
+
|
|
413
|
+
// ── 16. pipeline_state ──
|
|
414
|
+
{
|
|
415
|
+
name: 'pipeline_state',
|
|
416
|
+
description: '파이프라인 상태를 조회합니다 (--thorough 모드)',
|
|
417
|
+
inputSchema: {
|
|
418
|
+
type: 'object',
|
|
419
|
+
required: ['team_name'],
|
|
420
|
+
properties: {
|
|
421
|
+
team_name: { type: 'string', pattern: '^[a-z0-9][a-z0-9-]*$' },
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
handler: wrap('PIPELINE_STATE_FAILED', (args) => {
|
|
425
|
+
ensurePipelineTable(store.db);
|
|
426
|
+
const state = readPipelineState(store.db, args.team_name);
|
|
427
|
+
return state
|
|
428
|
+
? { ok: true, data: state }
|
|
429
|
+
: { ok: false, error: { code: 'PIPELINE_NOT_FOUND', message: `파이프라인 없음: ${args.team_name}` } };
|
|
430
|
+
}),
|
|
431
|
+
},
|
|
432
|
+
|
|
433
|
+
// ── 17. pipeline_advance ──
|
|
434
|
+
{
|
|
435
|
+
name: 'pipeline_advance',
|
|
436
|
+
description: '파이프라인을 다음 단계로 전이합니다 (전이 규칙 + fix loop 바운딩 적용)',
|
|
437
|
+
inputSchema: {
|
|
438
|
+
type: 'object',
|
|
439
|
+
required: ['team_name', 'phase'],
|
|
440
|
+
properties: {
|
|
441
|
+
team_name: { type: 'string', pattern: '^[a-z0-9][a-z0-9-]*$' },
|
|
442
|
+
phase: { type: 'string', enum: ['plan', 'prd', 'exec', 'verify', 'fix', 'complete', 'failed'] },
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
handler: wrap('PIPELINE_ADVANCE_FAILED', (args) => {
|
|
446
|
+
ensurePipelineTable(store.db);
|
|
447
|
+
const pipeline = createPipeline(store.db, args.team_name);
|
|
448
|
+
return pipeline.advance(args.phase);
|
|
449
|
+
}),
|
|
450
|
+
},
|
|
451
|
+
|
|
452
|
+
// ── 18. pipeline_init ──
|
|
453
|
+
{
|
|
454
|
+
name: 'pipeline_init',
|
|
455
|
+
description: '새 파이프라인을 초기화합니다 (기존 상태 덮어쓰기)',
|
|
456
|
+
inputSchema: {
|
|
457
|
+
type: 'object',
|
|
458
|
+
required: ['team_name'],
|
|
459
|
+
properties: {
|
|
460
|
+
team_name: { type: 'string', pattern: '^[a-z0-9][a-z0-9-]*$' },
|
|
461
|
+
fix_max: { type: 'integer', minimum: 1, maximum: 20, default: 3 },
|
|
462
|
+
ralph_max: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
handler: wrap('PIPELINE_INIT_FAILED', (args) => {
|
|
466
|
+
ensurePipelineTable(store.db);
|
|
467
|
+
const state = initPipelineState(store.db, args.team_name, {
|
|
468
|
+
fix_max: args.fix_max,
|
|
469
|
+
ralph_max: args.ralph_max,
|
|
470
|
+
});
|
|
471
|
+
return { ok: true, data: state };
|
|
472
|
+
}),
|
|
473
|
+
},
|
|
474
|
+
|
|
475
|
+
// ── 19. pipeline_list ──
|
|
476
|
+
{
|
|
477
|
+
name: 'pipeline_list',
|
|
478
|
+
description: '활성 파이프라인 목록을 조회합니다',
|
|
479
|
+
inputSchema: {
|
|
480
|
+
type: 'object',
|
|
481
|
+
properties: {},
|
|
482
|
+
},
|
|
483
|
+
handler: wrap('PIPELINE_LIST_FAILED', () => {
|
|
484
|
+
ensurePipelineTable(store.db);
|
|
485
|
+
return { ok: true, data: listPipelineStates(store.db) };
|
|
486
|
+
}),
|
|
487
|
+
},
|
|
328
488
|
];
|
|
329
489
|
}
|