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/router.mjs
CHANGED
|
@@ -3,10 +3,45 @@
|
|
|
3
3
|
import { EventEmitter, once } from 'node:events';
|
|
4
4
|
import { uuidv7 } from './store.mjs';
|
|
5
5
|
|
|
6
|
+
const ASSIGN_PENDING_STATUSES = new Set(['queued', 'running']);
|
|
7
|
+
|
|
6
8
|
function uniqueStrings(values = []) {
|
|
7
9
|
return Array.from(new Set((values || []).map((value) => String(value || '').trim()).filter(Boolean)));
|
|
8
10
|
}
|
|
9
11
|
|
|
12
|
+
function clampAssignDuration(value, fallback = 600000, min = 1000, max = 86400000) {
|
|
13
|
+
const num = Number(value);
|
|
14
|
+
if (!Number.isFinite(num)) return fallback;
|
|
15
|
+
return Math.max(min, Math.min(Math.trunc(num), max));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalizeAssignTerminalStatus(input, metadata = {}) {
|
|
19
|
+
const status = String(input || '').trim().toLowerCase();
|
|
20
|
+
const resultTag = String(
|
|
21
|
+
metadata?.result
|
|
22
|
+
?? metadata?.status
|
|
23
|
+
?? metadata?.outcome
|
|
24
|
+
?? '',
|
|
25
|
+
).trim().toLowerCase();
|
|
26
|
+
|
|
27
|
+
if (status === 'queued') return 'queued';
|
|
28
|
+
if (status === 'running' || status === 'in_progress') return 'running';
|
|
29
|
+
if (status === 'timed_out' || status === 'timeout') return 'timed_out';
|
|
30
|
+
if (status === 'failed' || status === 'error') return 'failed';
|
|
31
|
+
if (status === 'succeeded' || status === 'success') return 'succeeded';
|
|
32
|
+
|
|
33
|
+
if (status === 'completed') {
|
|
34
|
+
if (resultTag === 'failed' || resultTag === 'error') return 'failed';
|
|
35
|
+
if (resultTag === 'timed_out' || resultTag === 'timeout') return 'timed_out';
|
|
36
|
+
return 'succeeded';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (resultTag === 'failed' || resultTag === 'error') return 'failed';
|
|
40
|
+
if (resultTag === 'timed_out' || resultTag === 'timeout') return 'timed_out';
|
|
41
|
+
if (resultTag === 'succeeded' || resultTag === 'success') return 'succeeded';
|
|
42
|
+
return 'succeeded';
|
|
43
|
+
}
|
|
44
|
+
|
|
10
45
|
function normalizeAgentTopics(store, agentId, runtimeTopics) {
|
|
11
46
|
const topics = new Set(runtimeTopics || []);
|
|
12
47
|
const persisted = store.getAgent(agentId)?.topics || [];
|
|
@@ -199,6 +234,133 @@ export function createRouter(store) {
|
|
|
199
234
|
return { msg, recipients };
|
|
200
235
|
}
|
|
201
236
|
|
|
237
|
+
function buildAssignSnapshot(job, extra = {}) {
|
|
238
|
+
if (!job) return null;
|
|
239
|
+
return {
|
|
240
|
+
job_id: job.job_id,
|
|
241
|
+
supervisor_agent: job.supervisor_agent,
|
|
242
|
+
worker_agent: job.worker_agent,
|
|
243
|
+
topic: job.topic,
|
|
244
|
+
task: job.task,
|
|
245
|
+
status: job.status,
|
|
246
|
+
attempt: job.attempt,
|
|
247
|
+
retry_count: job.retry_count,
|
|
248
|
+
max_retries: job.max_retries,
|
|
249
|
+
timeout_ms: job.timeout_ms,
|
|
250
|
+
deadline_ms: job.deadline_ms,
|
|
251
|
+
trace_id: job.trace_id,
|
|
252
|
+
correlation_id: job.correlation_id,
|
|
253
|
+
last_message_id: job.last_message_id,
|
|
254
|
+
result: job.result,
|
|
255
|
+
error: job.error,
|
|
256
|
+
updated_at_ms: job.updated_at_ms,
|
|
257
|
+
completed_at_ms: job.completed_at_ms,
|
|
258
|
+
...extra,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function notifyAssignSupervisor(job, event, extra = {}) {
|
|
263
|
+
if (!job?.supervisor_agent) return null;
|
|
264
|
+
const { msg } = dispatchMessage({
|
|
265
|
+
type: 'event',
|
|
266
|
+
from: job.worker_agent || 'assign-router',
|
|
267
|
+
to: job.supervisor_agent,
|
|
268
|
+
topic: 'assign.result',
|
|
269
|
+
priority: Math.max(5, job.priority || 5),
|
|
270
|
+
ttl_ms: job.ttl_ms || job.timeout_ms || 600000,
|
|
271
|
+
payload: {
|
|
272
|
+
event,
|
|
273
|
+
...buildAssignSnapshot(job),
|
|
274
|
+
...extra,
|
|
275
|
+
},
|
|
276
|
+
trace_id: job.trace_id,
|
|
277
|
+
correlation_id: job.correlation_id,
|
|
278
|
+
});
|
|
279
|
+
return msg;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function dispatchAssignJob(job, reason = 'dispatch') {
|
|
283
|
+
const { msg, recipients } = dispatchMessage({
|
|
284
|
+
type: 'handoff',
|
|
285
|
+
from: job.supervisor_agent,
|
|
286
|
+
to: job.worker_agent,
|
|
287
|
+
topic: job.topic || 'assign.job',
|
|
288
|
+
priority: job.priority || 5,
|
|
289
|
+
ttl_ms: job.ttl_ms || job.timeout_ms || 600000,
|
|
290
|
+
payload: {
|
|
291
|
+
kind: 'assign.job',
|
|
292
|
+
reason,
|
|
293
|
+
assign_job_id: job.job_id,
|
|
294
|
+
attempt: job.attempt,
|
|
295
|
+
retry_count: job.retry_count,
|
|
296
|
+
max_retries: job.max_retries,
|
|
297
|
+
timeout_ms: job.timeout_ms,
|
|
298
|
+
supervisor_agent: job.supervisor_agent,
|
|
299
|
+
worker_agent: job.worker_agent,
|
|
300
|
+
task: job.task,
|
|
301
|
+
payload: job.payload || {},
|
|
302
|
+
},
|
|
303
|
+
trace_id: job.trace_id,
|
|
304
|
+
correlation_id: job.correlation_id,
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const updated = store.updateAssignStatus(job.job_id, job.status, {
|
|
308
|
+
last_message_id: msg.id,
|
|
309
|
+
});
|
|
310
|
+
return { job: updated || job, recipients, message_id: msg.id };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function scheduleAssignRetry(job, reason, error = null, requested_by = 'system') {
|
|
314
|
+
if (!job) {
|
|
315
|
+
return { ok: false, error: { code: 'ASSIGN_NOT_FOUND', message: 'assign job not found' } };
|
|
316
|
+
}
|
|
317
|
+
if (job.retry_count >= job.max_retries) {
|
|
318
|
+
return {
|
|
319
|
+
ok: false,
|
|
320
|
+
error: {
|
|
321
|
+
code: 'ASSIGN_RETRY_EXHAUSTED',
|
|
322
|
+
message: `retry exhausted for ${job.job_id}`,
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const queued = store.retryAssign(job.job_id, {
|
|
328
|
+
error,
|
|
329
|
+
timeout_ms: job.timeout_ms,
|
|
330
|
+
ttl_ms: job.ttl_ms,
|
|
331
|
+
});
|
|
332
|
+
const dispatched = dispatchAssignJob(queued, 'retry');
|
|
333
|
+
notifyAssignSupervisor(dispatched.job, 'retry_scheduled', {
|
|
334
|
+
retry_reason: reason,
|
|
335
|
+
requested_by,
|
|
336
|
+
});
|
|
337
|
+
return {
|
|
338
|
+
ok: true,
|
|
339
|
+
data: {
|
|
340
|
+
retried: true,
|
|
341
|
+
...buildAssignSnapshot(dispatched.job, {
|
|
342
|
+
retry_reason: reason,
|
|
343
|
+
requested_by,
|
|
344
|
+
}),
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function handleAssignTimeout(job) {
|
|
350
|
+
const timedOut = store.updateAssignStatus(job.job_id, 'timed_out', {
|
|
351
|
+
error: job.error ?? { message: 'assign job timed out' },
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
if (timedOut.retry_count < timedOut.max_retries) {
|
|
355
|
+
return scheduleAssignRetry(timedOut, 'timed_out', timedOut.error, 'sweeper');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
notifyAssignSupervisor(timedOut, 'completed', {
|
|
359
|
+
completion_reason: 'timed_out',
|
|
360
|
+
});
|
|
361
|
+
return { ok: true, data: buildAssignSnapshot(timedOut, { completion_reason: 'timed_out' }) };
|
|
362
|
+
}
|
|
363
|
+
|
|
202
364
|
const router = {
|
|
203
365
|
responseEmitter,
|
|
204
366
|
deliveryEmitter,
|
|
@@ -362,6 +524,137 @@ export function createRouter(store) {
|
|
|
362
524
|
};
|
|
363
525
|
},
|
|
364
526
|
|
|
527
|
+
assignAsync({
|
|
528
|
+
supervisor_agent,
|
|
529
|
+
worker_agent,
|
|
530
|
+
topic = 'assign.job',
|
|
531
|
+
task = '',
|
|
532
|
+
payload = {},
|
|
533
|
+
priority = 5,
|
|
534
|
+
ttl_ms = 600000,
|
|
535
|
+
timeout_ms = 600000,
|
|
536
|
+
max_retries = 0,
|
|
537
|
+
trace_id,
|
|
538
|
+
correlation_id,
|
|
539
|
+
}) {
|
|
540
|
+
const job = store.createAssign({
|
|
541
|
+
supervisor_agent,
|
|
542
|
+
worker_agent,
|
|
543
|
+
topic,
|
|
544
|
+
task,
|
|
545
|
+
payload,
|
|
546
|
+
priority,
|
|
547
|
+
ttl_ms,
|
|
548
|
+
timeout_ms,
|
|
549
|
+
max_retries,
|
|
550
|
+
trace_id,
|
|
551
|
+
correlation_id,
|
|
552
|
+
});
|
|
553
|
+
const dispatched = dispatchAssignJob(job, 'create');
|
|
554
|
+
return {
|
|
555
|
+
ok: true,
|
|
556
|
+
data: {
|
|
557
|
+
assigned_to: worker_agent,
|
|
558
|
+
...buildAssignSnapshot(dispatched.job),
|
|
559
|
+
},
|
|
560
|
+
};
|
|
561
|
+
},
|
|
562
|
+
|
|
563
|
+
reportAssignResult({
|
|
564
|
+
job_id,
|
|
565
|
+
worker_agent,
|
|
566
|
+
status,
|
|
567
|
+
attempt,
|
|
568
|
+
result,
|
|
569
|
+
error,
|
|
570
|
+
payload = {},
|
|
571
|
+
metadata = {},
|
|
572
|
+
}) {
|
|
573
|
+
const job = store.getAssign(job_id);
|
|
574
|
+
if (!job) {
|
|
575
|
+
return {
|
|
576
|
+
ok: false,
|
|
577
|
+
error: { code: 'ASSIGN_NOT_FOUND', message: `assign job not found: ${job_id}` },
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
if (worker_agent && worker_agent !== job.worker_agent) {
|
|
581
|
+
return {
|
|
582
|
+
ok: false,
|
|
583
|
+
error: { code: 'ASSIGN_WORKER_MISMATCH', message: `worker mismatch: ${worker_agent}` },
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
if (Number.isFinite(Number(attempt)) && Number(attempt) !== job.attempt) {
|
|
587
|
+
return {
|
|
588
|
+
ok: false,
|
|
589
|
+
error: {
|
|
590
|
+
code: 'ASSIGN_ATTEMPT_MISMATCH',
|
|
591
|
+
message: `stale assign result for attempt ${attempt} (current ${job.attempt})`,
|
|
592
|
+
},
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const mergedMetadata = {
|
|
597
|
+
...(payload?.metadata || {}),
|
|
598
|
+
...(metadata || {}),
|
|
599
|
+
};
|
|
600
|
+
const normalizedStatus = normalizeAssignTerminalStatus(
|
|
601
|
+
status || payload?.status,
|
|
602
|
+
mergedMetadata,
|
|
603
|
+
);
|
|
604
|
+
const nextResult = result ?? (Object.prototype.hasOwnProperty.call(payload || {}, 'result') ? payload.result : payload);
|
|
605
|
+
const nextError = error ?? payload?.error ?? null;
|
|
606
|
+
|
|
607
|
+
if (normalizedStatus === 'running') {
|
|
608
|
+
const running = store.updateAssignStatus(job.job_id, 'running', {
|
|
609
|
+
started_at_ms: job.started_at_ms || Date.now(),
|
|
610
|
+
deadline_ms: Date.now() + clampAssignDuration(job.timeout_ms, job.timeout_ms),
|
|
611
|
+
result: nextResult,
|
|
612
|
+
error: nextError,
|
|
613
|
+
});
|
|
614
|
+
notifyAssignSupervisor(running, 'progress');
|
|
615
|
+
return { ok: true, data: buildAssignSnapshot(running) };
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const finalized = store.updateAssignStatus(job.job_id, normalizedStatus, {
|
|
619
|
+
result: nextResult,
|
|
620
|
+
error: nextError,
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
if ((normalizedStatus === 'failed' || normalizedStatus === 'timed_out')
|
|
624
|
+
&& finalized.retry_count < finalized.max_retries) {
|
|
625
|
+
return scheduleAssignRetry(finalized, normalizedStatus, nextError, worker_agent || finalized.worker_agent);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
notifyAssignSupervisor(finalized, 'completed');
|
|
629
|
+
return { ok: true, data: buildAssignSnapshot(finalized) };
|
|
630
|
+
},
|
|
631
|
+
|
|
632
|
+
getAssignStatus({ job_id, ...filters } = {}) {
|
|
633
|
+
if (job_id) {
|
|
634
|
+
const job = store.getAssign(job_id);
|
|
635
|
+
return job
|
|
636
|
+
? { ok: true, data: buildAssignSnapshot(job) }
|
|
637
|
+
: { ok: false, error: { code: 'ASSIGN_NOT_FOUND', message: `assign job not found: ${job_id}` } };
|
|
638
|
+
}
|
|
639
|
+
return {
|
|
640
|
+
ok: true,
|
|
641
|
+
data: {
|
|
642
|
+
assigns: store.listAssigns(filters).map((job) => buildAssignSnapshot(job)),
|
|
643
|
+
},
|
|
644
|
+
};
|
|
645
|
+
},
|
|
646
|
+
|
|
647
|
+
retryAssign(job_id, { reason = 'manual', requested_by = 'manual' } = {}) {
|
|
648
|
+
const job = store.getAssign(job_id);
|
|
649
|
+
if (!job) {
|
|
650
|
+
return {
|
|
651
|
+
ok: false,
|
|
652
|
+
error: { code: 'ASSIGN_NOT_FOUND', message: `assign job not found: ${job_id}` },
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
return scheduleAssignRetry(job, reason, job.error, requested_by);
|
|
656
|
+
},
|
|
657
|
+
|
|
365
658
|
sweepExpired() {
|
|
366
659
|
const now = Date.now();
|
|
367
660
|
let expired = 0;
|
|
@@ -374,10 +667,31 @@ export function createRouter(store) {
|
|
|
374
667
|
return { messages: expired };
|
|
375
668
|
},
|
|
376
669
|
|
|
670
|
+
sweepTimedOutAssigns() {
|
|
671
|
+
const expiredAssigns = store.listAssigns({
|
|
672
|
+
statuses: Array.from(ASSIGN_PENDING_STATUSES),
|
|
673
|
+
active_before_ms: Date.now(),
|
|
674
|
+
limit: 100,
|
|
675
|
+
});
|
|
676
|
+
let timed_out = 0;
|
|
677
|
+
let retried = 0;
|
|
678
|
+
|
|
679
|
+
for (const job of expiredAssigns) {
|
|
680
|
+
const result = handleAssignTimeout(job);
|
|
681
|
+
timed_out += 1;
|
|
682
|
+
if (result?.data?.retried) retried += 1;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
return { timed_out, retried };
|
|
686
|
+
},
|
|
687
|
+
|
|
377
688
|
startSweeper() {
|
|
378
689
|
if (sweepTimer) return;
|
|
379
690
|
sweepTimer = setInterval(() => {
|
|
380
|
-
try {
|
|
691
|
+
try {
|
|
692
|
+
router.sweepExpired();
|
|
693
|
+
router.sweepTimedOutAssigns();
|
|
694
|
+
} catch {}
|
|
381
695
|
}, 10000);
|
|
382
696
|
staleTimer = setInterval(() => {
|
|
383
697
|
try { store.sweepStaleAgents(); } catch {}
|
|
@@ -427,12 +741,19 @@ export function createRouter(store) {
|
|
|
427
741
|
if (include_metrics) {
|
|
428
742
|
const depths = router.getQueueDepths();
|
|
429
743
|
const stats = router.getDeliveryStats();
|
|
744
|
+
const auditStats = store.getAuditStats();
|
|
430
745
|
data.queues = {
|
|
431
746
|
urgent_depth: depths.urgent,
|
|
432
747
|
normal_depth: depths.normal,
|
|
433
748
|
dlq_depth: depths.dlq,
|
|
434
749
|
avg_delivery_ms: stats.avg_delivery_ms,
|
|
435
750
|
};
|
|
751
|
+
data.assigns = {
|
|
752
|
+
queued: auditStats.assign_queued,
|
|
753
|
+
running: auditStats.assign_running,
|
|
754
|
+
failed: auditStats.assign_failed,
|
|
755
|
+
timed_out: auditStats.assign_timed_out,
|
|
756
|
+
};
|
|
436
757
|
}
|
|
437
758
|
}
|
|
438
759
|
|
package/hub/schema.sql
CHANGED
|
@@ -76,13 +76,46 @@ CREATE INDEX IF NOT EXISTS idx_messages_priority ON messages(priority DESC, crea
|
|
|
76
76
|
CREATE INDEX IF NOT EXISTS idx_inbox_agent ON message_inbox(agent_id, delivered_at_ms);
|
|
77
77
|
CREATE INDEX IF NOT EXISTS idx_inbox_message ON message_inbox(message_id);
|
|
78
78
|
CREATE INDEX IF NOT EXISTS idx_human_requests_state ON human_requests(state);
|
|
79
|
-
CREATE INDEX IF NOT EXISTS idx_agents_status ON agents(status);
|
|
80
|
-
CREATE INDEX IF NOT EXISTS idx_agents_lease ON agents(lease_expires_ms);
|
|
81
|
-
|
|
82
|
-
--
|
|
83
|
-
CREATE TABLE IF NOT EXISTS
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
CREATE INDEX IF NOT EXISTS idx_agents_status ON agents(status);
|
|
80
|
+
CREATE INDEX IF NOT EXISTS idx_agents_lease ON agents(lease_expires_ms);
|
|
81
|
+
|
|
82
|
+
-- Assign Job 테이블
|
|
83
|
+
CREATE TABLE IF NOT EXISTS assign_jobs (
|
|
84
|
+
job_id TEXT PRIMARY KEY,
|
|
85
|
+
supervisor_agent TEXT NOT NULL,
|
|
86
|
+
worker_agent TEXT NOT NULL,
|
|
87
|
+
topic TEXT NOT NULL DEFAULT 'assign.job',
|
|
88
|
+
task TEXT NOT NULL DEFAULT '',
|
|
89
|
+
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
90
|
+
status TEXT NOT NULL CHECK (status IN ('queued','running','succeeded','failed','timed_out')),
|
|
91
|
+
attempt INTEGER NOT NULL DEFAULT 1,
|
|
92
|
+
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
93
|
+
max_retries INTEGER NOT NULL DEFAULT 0,
|
|
94
|
+
priority INTEGER NOT NULL DEFAULT 5 CHECK (priority BETWEEN 1 AND 9),
|
|
95
|
+
ttl_ms INTEGER NOT NULL DEFAULT 600000,
|
|
96
|
+
timeout_ms INTEGER NOT NULL DEFAULT 600000,
|
|
97
|
+
deadline_ms INTEGER,
|
|
98
|
+
trace_id TEXT NOT NULL,
|
|
99
|
+
correlation_id TEXT NOT NULL,
|
|
100
|
+
last_message_id TEXT,
|
|
101
|
+
result_json TEXT,
|
|
102
|
+
error_json TEXT,
|
|
103
|
+
created_at_ms INTEGER NOT NULL,
|
|
104
|
+
updated_at_ms INTEGER NOT NULL,
|
|
105
|
+
started_at_ms INTEGER,
|
|
106
|
+
completed_at_ms INTEGER,
|
|
107
|
+
last_retry_at_ms INTEGER
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
CREATE INDEX IF NOT EXISTS idx_assign_jobs_status ON assign_jobs(status, updated_at_ms DESC);
|
|
111
|
+
CREATE INDEX IF NOT EXISTS idx_assign_jobs_supervisor ON assign_jobs(supervisor_agent, updated_at_ms DESC);
|
|
112
|
+
CREATE INDEX IF NOT EXISTS idx_assign_jobs_worker ON assign_jobs(worker_agent, updated_at_ms DESC);
|
|
113
|
+
CREATE INDEX IF NOT EXISTS idx_assign_jobs_deadline ON assign_jobs(deadline_ms, status);
|
|
114
|
+
|
|
115
|
+
-- 파이프라인 상태 테이블 (Phase 2)
|
|
116
|
+
CREATE TABLE IF NOT EXISTS pipeline_state (
|
|
117
|
+
team_name TEXT PRIMARY KEY,
|
|
118
|
+
phase TEXT NOT NULL DEFAULT 'plan',
|
|
86
119
|
fix_attempt INTEGER DEFAULT 0,
|
|
87
120
|
fix_max INTEGER DEFAULT 3,
|
|
88
121
|
ralph_iteration INTEGER DEFAULT 0,
|
package/hub/server.mjs
CHANGED
|
@@ -245,6 +245,101 @@ export async function startHub({ port = 27888, dbPath, host = '127.0.0.1', sessi
|
|
|
245
245
|
return res.end(JSON.stringify(result));
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
+
if (path === '/bridge/assign/async' && req.method === 'POST') {
|
|
249
|
+
const {
|
|
250
|
+
supervisor_agent,
|
|
251
|
+
worker_agent,
|
|
252
|
+
task,
|
|
253
|
+
topic = 'assign.job',
|
|
254
|
+
payload = {},
|
|
255
|
+
priority = 5,
|
|
256
|
+
ttl_ms = 600000,
|
|
257
|
+
timeout_ms = 600000,
|
|
258
|
+
max_retries = 0,
|
|
259
|
+
trace_id,
|
|
260
|
+
correlation_id,
|
|
261
|
+
} = body;
|
|
262
|
+
|
|
263
|
+
if (!supervisor_agent || !worker_agent || !task) {
|
|
264
|
+
res.writeHead(400);
|
|
265
|
+
return res.end(JSON.stringify({ ok: false, error: 'supervisor_agent, worker_agent, task 필수' }));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const result = await pipe.executeCommand('assign', {
|
|
269
|
+
supervisor_agent,
|
|
270
|
+
worker_agent,
|
|
271
|
+
task,
|
|
272
|
+
topic,
|
|
273
|
+
payload,
|
|
274
|
+
priority,
|
|
275
|
+
ttl_ms,
|
|
276
|
+
timeout_ms,
|
|
277
|
+
max_retries,
|
|
278
|
+
trace_id,
|
|
279
|
+
correlation_id,
|
|
280
|
+
});
|
|
281
|
+
res.writeHead(result.ok ? 200 : 400);
|
|
282
|
+
return res.end(JSON.stringify(result));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (path === '/bridge/assign/result' && req.method === 'POST') {
|
|
286
|
+
const {
|
|
287
|
+
job_id,
|
|
288
|
+
worker_agent,
|
|
289
|
+
status,
|
|
290
|
+
attempt,
|
|
291
|
+
result: assignResult,
|
|
292
|
+
error: assignError,
|
|
293
|
+
payload = {},
|
|
294
|
+
metadata = {},
|
|
295
|
+
} = body;
|
|
296
|
+
|
|
297
|
+
if (!job_id || !status) {
|
|
298
|
+
res.writeHead(400);
|
|
299
|
+
return res.end(JSON.stringify({ ok: false, error: 'job_id, status 필수' }));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const result = await pipe.executeCommand('assign_result', {
|
|
303
|
+
job_id,
|
|
304
|
+
worker_agent,
|
|
305
|
+
status,
|
|
306
|
+
attempt,
|
|
307
|
+
result: assignResult,
|
|
308
|
+
error: assignError,
|
|
309
|
+
payload,
|
|
310
|
+
metadata,
|
|
311
|
+
});
|
|
312
|
+
res.writeHead(result.ok ? 200 : 409);
|
|
313
|
+
return res.end(JSON.stringify(result));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (path === '/bridge/assign/status' && req.method === 'POST') {
|
|
317
|
+
const result = await pipe.executeQuery('assign_status', body);
|
|
318
|
+
const statusCode = result.ok ? 200 : (result.error?.code === 'ASSIGN_NOT_FOUND' ? 404 : 400);
|
|
319
|
+
res.writeHead(statusCode);
|
|
320
|
+
return res.end(JSON.stringify(result));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (path === '/bridge/assign/retry' && req.method === 'POST') {
|
|
324
|
+
const { job_id, reason, requested_by } = body;
|
|
325
|
+
if (!job_id) {
|
|
326
|
+
res.writeHead(400);
|
|
327
|
+
return res.end(JSON.stringify({ ok: false, error: 'job_id 필수' }));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const result = await pipe.executeCommand('assign_retry', {
|
|
331
|
+
job_id,
|
|
332
|
+
reason,
|
|
333
|
+
requested_by,
|
|
334
|
+
});
|
|
335
|
+
const statusCode = result.ok ? 200
|
|
336
|
+
: result.error?.code === 'ASSIGN_NOT_FOUND' ? 404
|
|
337
|
+
: result.error?.code === 'ASSIGN_RETRY_EXHAUSTED' ? 409
|
|
338
|
+
: 400;
|
|
339
|
+
res.writeHead(statusCode);
|
|
340
|
+
return res.end(JSON.stringify(result));
|
|
341
|
+
}
|
|
342
|
+
|
|
248
343
|
if (req.method === 'POST') {
|
|
249
344
|
let teamResult = null;
|
|
250
345
|
if (path === '/bridge/team/info' || path === '/bridge/team-info') {
|