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/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 = '1';
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
  };