triflux 3.3.0-dev.1 → 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 +169 -39
- package/hooks/hooks.json +5 -0
- package/hub/pipe.mjs +23 -0
- package/hub/router.mjs +322 -1
- package/hub/schema.sql +40 -7
- package/hub/server.mjs +95 -0
- package/hub/store.mjs +259 -1
- package/hub/team/native.mjs +200 -190
- package/hub/team/psmux.mjs +555 -115
- package/hub/tools.mjs +101 -26
- 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 +1735 -1790
- package/package.json +1 -1
- package/scripts/__tests__/keyword-detector.test.mjs +3 -3
- package/scripts/__tests__/smoke.test.mjs +34 -0
- package/scripts/hub-ensure.mjs +21 -3
- package/scripts/setup.mjs +15 -10
package/hub/store.mjs
CHANGED
|
@@ -80,6 +80,17 @@ function parseHumanRequestRow(row) {
|
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
function parseAssignRow(row) {
|
|
84
|
+
if (!row) return null;
|
|
85
|
+
const { payload_json, result_json, error_json, ...rest } = row;
|
|
86
|
+
return {
|
|
87
|
+
...rest,
|
|
88
|
+
payload: parseJson(payload_json, {}),
|
|
89
|
+
result: parseJson(result_json, null),
|
|
90
|
+
error: parseJson(error_json, null),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
83
94
|
/**
|
|
84
95
|
* 저장소 생성
|
|
85
96
|
* @param {string} dbPath
|
|
@@ -96,7 +107,7 @@ export function createStore(dbPath) {
|
|
|
96
107
|
|
|
97
108
|
const schemaSQL = readFileSync(join(__dirname, 'schema.sql'), 'utf8');
|
|
98
109
|
db.exec("CREATE TABLE IF NOT EXISTS _meta (key TEXT PRIMARY KEY, value TEXT)");
|
|
99
|
-
const SCHEMA_VERSION = '
|
|
110
|
+
const SCHEMA_VERSION = '2';
|
|
100
111
|
const curVer = (() => {
|
|
101
112
|
try { return db.prepare("SELECT value FROM _meta WHERE key='schema_version'").pluck().get(); }
|
|
102
113
|
catch { return null; }
|
|
@@ -162,6 +173,45 @@ export function createStore(dbPath) {
|
|
|
162
173
|
insertDL: db.prepare('INSERT OR REPLACE INTO dead_letters (message_id, reason, failed_at_ms, last_error) VALUES (?,?,?,?)'),
|
|
163
174
|
getDL: db.prepare('SELECT * FROM dead_letters ORDER BY failed_at_ms DESC LIMIT ?'),
|
|
164
175
|
|
|
176
|
+
insertAssign: db.prepare(`
|
|
177
|
+
INSERT INTO assign_jobs (
|
|
178
|
+
job_id, supervisor_agent, worker_agent, topic, task, payload_json,
|
|
179
|
+
status, attempt, retry_count, max_retries, priority, ttl_ms, timeout_ms, deadline_ms,
|
|
180
|
+
trace_id, correlation_id, last_message_id, result_json, error_json,
|
|
181
|
+
created_at_ms, updated_at_ms, started_at_ms, completed_at_ms, last_retry_at_ms
|
|
182
|
+
) VALUES (
|
|
183
|
+
@job_id, @supervisor_agent, @worker_agent, @topic, @task, @payload_json,
|
|
184
|
+
@status, @attempt, @retry_count, @max_retries, @priority, @ttl_ms, @timeout_ms, @deadline_ms,
|
|
185
|
+
@trace_id, @correlation_id, @last_message_id, @result_json, @error_json,
|
|
186
|
+
@created_at_ms, @updated_at_ms, @started_at_ms, @completed_at_ms, @last_retry_at_ms
|
|
187
|
+
)`),
|
|
188
|
+
getAssign: db.prepare('SELECT * FROM assign_jobs WHERE job_id = ?'),
|
|
189
|
+
updateAssign: db.prepare(`
|
|
190
|
+
UPDATE assign_jobs SET
|
|
191
|
+
supervisor_agent=@supervisor_agent,
|
|
192
|
+
worker_agent=@worker_agent,
|
|
193
|
+
topic=@topic,
|
|
194
|
+
task=@task,
|
|
195
|
+
payload_json=@payload_json,
|
|
196
|
+
status=@status,
|
|
197
|
+
attempt=@attempt,
|
|
198
|
+
retry_count=@retry_count,
|
|
199
|
+
max_retries=@max_retries,
|
|
200
|
+
priority=@priority,
|
|
201
|
+
ttl_ms=@ttl_ms,
|
|
202
|
+
timeout_ms=@timeout_ms,
|
|
203
|
+
deadline_ms=@deadline_ms,
|
|
204
|
+
trace_id=@trace_id,
|
|
205
|
+
correlation_id=@correlation_id,
|
|
206
|
+
last_message_id=@last_message_id,
|
|
207
|
+
result_json=@result_json,
|
|
208
|
+
error_json=@error_json,
|
|
209
|
+
updated_at_ms=@updated_at_ms,
|
|
210
|
+
started_at_ms=@started_at_ms,
|
|
211
|
+
completed_at_ms=@completed_at_ms,
|
|
212
|
+
last_retry_at_ms=@last_retry_at_ms
|
|
213
|
+
WHERE job_id=@job_id`),
|
|
214
|
+
|
|
165
215
|
findExpired: db.prepare("SELECT id FROM messages WHERE status='queued' AND expires_at_ms < ?"),
|
|
166
216
|
urgentDepth: db.prepare("SELECT COUNT(*) as cnt FROM messages WHERE status='queued' AND priority >= 7"),
|
|
167
217
|
normalDepth: db.prepare("SELECT COUNT(*) as cnt FROM messages WHERE status='queued' AND priority < 7"),
|
|
@@ -169,6 +219,8 @@ export function createStore(dbPath) {
|
|
|
169
219
|
msgCount: db.prepare('SELECT COUNT(*) as cnt FROM messages'),
|
|
170
220
|
dlqDepth: db.prepare('SELECT COUNT(*) as cnt FROM dead_letters'),
|
|
171
221
|
ackedRecent: db.prepare("SELECT COUNT(*) as cnt FROM messages WHERE status='acked' AND created_at_ms > ? - 300000"),
|
|
222
|
+
assignCountByStatus: db.prepare('SELECT COUNT(*) as cnt FROM assign_jobs WHERE status = ?'),
|
|
223
|
+
activeAssignCount: db.prepare("SELECT COUNT(*) as cnt FROM assign_jobs WHERE status IN ('queued','running')"),
|
|
172
224
|
};
|
|
173
225
|
|
|
174
226
|
function clampMaxMessages(value, fallback = 20) {
|
|
@@ -177,6 +229,18 @@ export function createStore(dbPath) {
|
|
|
177
229
|
return Math.max(1, Math.min(Math.trunc(num), 100));
|
|
178
230
|
}
|
|
179
231
|
|
|
232
|
+
function clampPriority(value, fallback = 5) {
|
|
233
|
+
const num = Number(value);
|
|
234
|
+
if (!Number.isFinite(num)) return fallback;
|
|
235
|
+
return Math.max(1, Math.min(Math.trunc(num), 9));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function clampDuration(value, fallback = 600000, min = 1000, max = 86400000) {
|
|
239
|
+
const num = Number(value);
|
|
240
|
+
if (!Number.isFinite(num)) return fallback;
|
|
241
|
+
return Math.max(min, Math.min(Math.trunc(num), max));
|
|
242
|
+
}
|
|
243
|
+
|
|
180
244
|
const store = {
|
|
181
245
|
db,
|
|
182
246
|
uuidv7,
|
|
@@ -365,6 +429,195 @@ export function createStore(dbPath) {
|
|
|
365
429
|
return S.getDL.all(limit);
|
|
366
430
|
},
|
|
367
431
|
|
|
432
|
+
createAssign({
|
|
433
|
+
job_id,
|
|
434
|
+
supervisor_agent,
|
|
435
|
+
worker_agent,
|
|
436
|
+
topic = 'assign.job',
|
|
437
|
+
task = '',
|
|
438
|
+
payload = {},
|
|
439
|
+
status = 'queued',
|
|
440
|
+
attempt = 1,
|
|
441
|
+
retry_count = 0,
|
|
442
|
+
max_retries = 0,
|
|
443
|
+
priority = 5,
|
|
444
|
+
ttl_ms = 600000,
|
|
445
|
+
timeout_ms = 600000,
|
|
446
|
+
deadline_ms,
|
|
447
|
+
trace_id,
|
|
448
|
+
correlation_id,
|
|
449
|
+
last_message_id = null,
|
|
450
|
+
result = null,
|
|
451
|
+
error = null,
|
|
452
|
+
}) {
|
|
453
|
+
const now = Date.now();
|
|
454
|
+
const normalizedTimeout = clampDuration(timeout_ms, 600000);
|
|
455
|
+
const row = {
|
|
456
|
+
job_id: job_id || uuidv7(),
|
|
457
|
+
supervisor_agent,
|
|
458
|
+
worker_agent,
|
|
459
|
+
topic: String(topic || 'assign.job'),
|
|
460
|
+
task: String(task || ''),
|
|
461
|
+
payload_json: JSON.stringify(payload || {}),
|
|
462
|
+
status,
|
|
463
|
+
attempt: Math.max(1, Number(attempt) || 1),
|
|
464
|
+
retry_count: Math.max(0, Number(retry_count) || 0),
|
|
465
|
+
max_retries: Math.max(0, Number(max_retries) || 0),
|
|
466
|
+
priority: clampPriority(priority, 5),
|
|
467
|
+
ttl_ms: clampDuration(ttl_ms, normalizedTimeout),
|
|
468
|
+
timeout_ms: normalizedTimeout,
|
|
469
|
+
deadline_ms: Number.isFinite(Number(deadline_ms))
|
|
470
|
+
? Math.trunc(Number(deadline_ms))
|
|
471
|
+
: now + normalizedTimeout,
|
|
472
|
+
trace_id: trace_id || uuidv7(),
|
|
473
|
+
correlation_id: correlation_id || uuidv7(),
|
|
474
|
+
last_message_id,
|
|
475
|
+
result_json: result == null ? null : JSON.stringify(result),
|
|
476
|
+
error_json: error == null ? null : JSON.stringify(error),
|
|
477
|
+
created_at_ms: now,
|
|
478
|
+
updated_at_ms: now,
|
|
479
|
+
started_at_ms: status === 'running' ? now : null,
|
|
480
|
+
completed_at_ms: ['succeeded', 'failed', 'timed_out'].includes(status) ? now : null,
|
|
481
|
+
last_retry_at_ms: retry_count > 0 ? now : null,
|
|
482
|
+
};
|
|
483
|
+
S.insertAssign.run(row);
|
|
484
|
+
return store.getAssign(row.job_id);
|
|
485
|
+
},
|
|
486
|
+
|
|
487
|
+
getAssign(jobId) {
|
|
488
|
+
return parseAssignRow(S.getAssign.get(jobId));
|
|
489
|
+
},
|
|
490
|
+
|
|
491
|
+
updateAssignStatus(jobId, status, patch = {}) {
|
|
492
|
+
const current = store.getAssign(jobId);
|
|
493
|
+
if (!current) return null;
|
|
494
|
+
|
|
495
|
+
const now = Date.now();
|
|
496
|
+
const nextStatus = status || current.status;
|
|
497
|
+
const isTerminal = ['succeeded', 'failed', 'timed_out'].includes(nextStatus);
|
|
498
|
+
const nextTimeout = clampDuration(patch.timeout_ms ?? current.timeout_ms, current.timeout_ms);
|
|
499
|
+
const nextRow = {
|
|
500
|
+
job_id: current.job_id,
|
|
501
|
+
supervisor_agent: patch.supervisor_agent ?? current.supervisor_agent,
|
|
502
|
+
worker_agent: patch.worker_agent ?? current.worker_agent,
|
|
503
|
+
topic: patch.topic ?? current.topic,
|
|
504
|
+
task: patch.task ?? current.task,
|
|
505
|
+
payload_json: JSON.stringify(patch.payload ?? current.payload ?? {}),
|
|
506
|
+
status: nextStatus,
|
|
507
|
+
attempt: Math.max(1, Number(patch.attempt ?? current.attempt) || current.attempt || 1),
|
|
508
|
+
retry_count: Math.max(0, Number(patch.retry_count ?? current.retry_count) || 0),
|
|
509
|
+
max_retries: Math.max(0, Number(patch.max_retries ?? current.max_retries) || 0),
|
|
510
|
+
priority: clampPriority(patch.priority ?? current.priority, current.priority || 5),
|
|
511
|
+
ttl_ms: clampDuration(patch.ttl_ms ?? current.ttl_ms, current.ttl_ms || nextTimeout),
|
|
512
|
+
timeout_ms: nextTimeout,
|
|
513
|
+
deadline_ms: (() => {
|
|
514
|
+
if (Object.prototype.hasOwnProperty.call(patch, 'deadline_ms')) {
|
|
515
|
+
return patch.deadline_ms == null ? null : Math.trunc(Number(patch.deadline_ms));
|
|
516
|
+
}
|
|
517
|
+
if (isTerminal) return null;
|
|
518
|
+
if (nextStatus === 'running' && !current.deadline_ms) return now + nextTimeout;
|
|
519
|
+
return current.deadline_ms;
|
|
520
|
+
})(),
|
|
521
|
+
trace_id: patch.trace_id ?? current.trace_id,
|
|
522
|
+
correlation_id: patch.correlation_id ?? current.correlation_id,
|
|
523
|
+
last_message_id: Object.prototype.hasOwnProperty.call(patch, 'last_message_id')
|
|
524
|
+
? patch.last_message_id
|
|
525
|
+
: current.last_message_id,
|
|
526
|
+
result_json: Object.prototype.hasOwnProperty.call(patch, 'result')
|
|
527
|
+
? (patch.result == null ? null : JSON.stringify(patch.result))
|
|
528
|
+
: (current.result == null ? null : JSON.stringify(current.result)),
|
|
529
|
+
error_json: Object.prototype.hasOwnProperty.call(patch, 'error')
|
|
530
|
+
? (patch.error == null ? null : JSON.stringify(patch.error))
|
|
531
|
+
: (current.error == null ? null : JSON.stringify(current.error)),
|
|
532
|
+
updated_at_ms: now,
|
|
533
|
+
started_at_ms: Object.prototype.hasOwnProperty.call(patch, 'started_at_ms')
|
|
534
|
+
? patch.started_at_ms
|
|
535
|
+
: (nextStatus === 'running' ? (current.started_at_ms || now) : current.started_at_ms),
|
|
536
|
+
completed_at_ms: Object.prototype.hasOwnProperty.call(patch, 'completed_at_ms')
|
|
537
|
+
? patch.completed_at_ms
|
|
538
|
+
: (isTerminal ? (current.completed_at_ms || now) : current.completed_at_ms),
|
|
539
|
+
last_retry_at_ms: Object.prototype.hasOwnProperty.call(patch, 'last_retry_at_ms')
|
|
540
|
+
? patch.last_retry_at_ms
|
|
541
|
+
: current.last_retry_at_ms,
|
|
542
|
+
};
|
|
543
|
+
S.updateAssign.run(nextRow);
|
|
544
|
+
return store.getAssign(jobId);
|
|
545
|
+
},
|
|
546
|
+
|
|
547
|
+
listAssigns({
|
|
548
|
+
supervisor_agent,
|
|
549
|
+
worker_agent,
|
|
550
|
+
status,
|
|
551
|
+
statuses,
|
|
552
|
+
trace_id,
|
|
553
|
+
correlation_id,
|
|
554
|
+
active_before_ms,
|
|
555
|
+
limit = 50,
|
|
556
|
+
} = {}) {
|
|
557
|
+
const clauses = [];
|
|
558
|
+
const values = [];
|
|
559
|
+
|
|
560
|
+
if (supervisor_agent) {
|
|
561
|
+
clauses.push('supervisor_agent = ?');
|
|
562
|
+
values.push(supervisor_agent);
|
|
563
|
+
}
|
|
564
|
+
if (worker_agent) {
|
|
565
|
+
clauses.push('worker_agent = ?');
|
|
566
|
+
values.push(worker_agent);
|
|
567
|
+
}
|
|
568
|
+
if (trace_id) {
|
|
569
|
+
clauses.push('trace_id = ?');
|
|
570
|
+
values.push(trace_id);
|
|
571
|
+
}
|
|
572
|
+
if (correlation_id) {
|
|
573
|
+
clauses.push('correlation_id = ?');
|
|
574
|
+
values.push(correlation_id);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const statusList = Array.isArray(statuses) && statuses.length
|
|
578
|
+
? statuses
|
|
579
|
+
: (status ? [status] : []);
|
|
580
|
+
if (statusList.length) {
|
|
581
|
+
clauses.push(`status IN (${statusList.map(() => '?').join(',')})`);
|
|
582
|
+
values.push(...statusList);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (Number.isFinite(Number(active_before_ms))) {
|
|
586
|
+
clauses.push('deadline_ms IS NOT NULL AND deadline_ms <= ?');
|
|
587
|
+
values.push(Math.trunc(Number(active_before_ms)));
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const sql = `
|
|
591
|
+
SELECT * FROM assign_jobs
|
|
592
|
+
${clauses.length ? `WHERE ${clauses.join(' AND ')}` : ''}
|
|
593
|
+
ORDER BY updated_at_ms DESC
|
|
594
|
+
LIMIT ?`;
|
|
595
|
+
values.push(clampMaxMessages(limit, 50));
|
|
596
|
+
return db.prepare(sql).all(...values).map(parseAssignRow);
|
|
597
|
+
},
|
|
598
|
+
|
|
599
|
+
retryAssign(jobId, patch = {}) {
|
|
600
|
+
const current = store.getAssign(jobId);
|
|
601
|
+
if (!current) return null;
|
|
602
|
+
|
|
603
|
+
const nextRetryCount = Math.max(0, Number(patch.retry_count ?? current.retry_count + 1) || 0);
|
|
604
|
+
const nextAttempt = Math.max(current.attempt + 1, Number(patch.attempt ?? current.attempt + 1) || 1);
|
|
605
|
+
const nextTimeout = clampDuration(patch.timeout_ms ?? current.timeout_ms, current.timeout_ms);
|
|
606
|
+
return store.updateAssignStatus(jobId, 'queued', {
|
|
607
|
+
retry_count: nextRetryCount,
|
|
608
|
+
attempt: nextAttempt,
|
|
609
|
+
timeout_ms: nextTimeout,
|
|
610
|
+
ttl_ms: patch.ttl_ms ?? current.ttl_ms,
|
|
611
|
+
deadline_ms: Date.now() + nextTimeout,
|
|
612
|
+
completed_at_ms: null,
|
|
613
|
+
started_at_ms: null,
|
|
614
|
+
last_retry_at_ms: Date.now(),
|
|
615
|
+
result: patch.result ?? null,
|
|
616
|
+
error: Object.prototype.hasOwnProperty.call(patch, 'error') ? patch.error : current.error,
|
|
617
|
+
last_message_id: null,
|
|
618
|
+
});
|
|
619
|
+
},
|
|
620
|
+
|
|
368
621
|
sweepExpired() {
|
|
369
622
|
const now = Date.now();
|
|
370
623
|
return db.transaction(() => {
|
|
@@ -397,6 +650,7 @@ export function createStore(dbPath) {
|
|
|
397
650
|
return {
|
|
398
651
|
online_agents: S.onlineCount.get().cnt,
|
|
399
652
|
total_messages: S.msgCount.get().cnt,
|
|
653
|
+
active_assign_jobs: S.activeAssignCount.get().cnt,
|
|
400
654
|
...store.getQueueDepths(),
|
|
401
655
|
};
|
|
402
656
|
},
|
|
@@ -406,6 +660,10 @@ export function createStore(dbPath) {
|
|
|
406
660
|
online_agents: S.onlineCount.get().cnt,
|
|
407
661
|
total_messages: S.msgCount.get().cnt,
|
|
408
662
|
dlq: S.dlqDepth.get().cnt,
|
|
663
|
+
assign_queued: S.assignCountByStatus.get('queued').cnt,
|
|
664
|
+
assign_running: S.assignCountByStatus.get('running').cnt,
|
|
665
|
+
assign_failed: S.assignCountByStatus.get('failed').cnt,
|
|
666
|
+
assign_timed_out: S.assignCountByStatus.get('timed_out').cnt,
|
|
409
667
|
};
|
|
410
668
|
},
|
|
411
669
|
};
|