triflux 3.2.0-dev.1 → 3.2.0-dev.10

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.
Files changed (53) hide show
  1. package/README.ko.md +26 -18
  2. package/README.md +26 -18
  3. package/bin/triflux.mjs +1614 -1084
  4. package/hooks/hooks.json +12 -0
  5. package/hooks/keyword-rules.json +354 -0
  6. package/hub/bridge.mjs +371 -193
  7. package/hub/hitl.mjs +45 -31
  8. package/hub/pipe.mjs +457 -0
  9. package/hub/router.mjs +422 -161
  10. package/hub/server.mjs +429 -344
  11. package/hub/store.mjs +388 -314
  12. package/hub/team/cli-team-common.mjs +348 -0
  13. package/hub/team/cli-team-control.mjs +393 -0
  14. package/hub/team/cli-team-start.mjs +516 -0
  15. package/hub/team/cli-team-status.mjs +269 -0
  16. package/hub/team/cli.mjs +99 -368
  17. package/hub/team/dashboard.mjs +165 -64
  18. package/hub/team/native-supervisor.mjs +300 -0
  19. package/hub/team/native.mjs +62 -0
  20. package/hub/team/nativeProxy.mjs +534 -0
  21. package/hub/team/orchestrator.mjs +99 -35
  22. package/hub/team/pane.mjs +138 -101
  23. package/hub/team/psmux.mjs +297 -0
  24. package/hub/team/session.mjs +608 -186
  25. package/hub/team/shared.mjs +13 -0
  26. package/hub/team/staleState.mjs +299 -0
  27. package/hub/tools.mjs +140 -53
  28. package/hub/workers/claude-worker.mjs +446 -0
  29. package/hub/workers/codex-mcp.mjs +414 -0
  30. package/hub/workers/factory.mjs +18 -0
  31. package/hub/workers/gemini-worker.mjs +349 -0
  32. package/hub/workers/interface.mjs +41 -0
  33. package/hud/hud-qos-status.mjs +1789 -1732
  34. package/package.json +6 -2
  35. package/scripts/__tests__/keyword-detector.test.mjs +234 -0
  36. package/scripts/hub-ensure.mjs +83 -0
  37. package/scripts/keyword-detector.mjs +272 -0
  38. package/scripts/keyword-rules-expander.mjs +521 -0
  39. package/scripts/lib/keyword-rules.mjs +168 -0
  40. package/scripts/psmux-steering-prototype.sh +368 -0
  41. package/scripts/run.cjs +62 -0
  42. package/scripts/setup.mjs +189 -7
  43. package/scripts/test-tfx-route-no-claude-native.mjs +49 -0
  44. package/scripts/tfx-route-worker.mjs +161 -0
  45. package/scripts/tfx-route.sh +943 -508
  46. package/skills/tfx-auto/SKILL.md +90 -564
  47. package/skills/tfx-auto-codex/SKILL.md +77 -0
  48. package/skills/tfx-codex/SKILL.md +1 -4
  49. package/skills/tfx-doctor/SKILL.md +1 -0
  50. package/skills/tfx-gemini/SKILL.md +1 -4
  51. package/skills/tfx-multi/SKILL.md +296 -0
  52. package/skills/tfx-setup/SKILL.md +1 -4
  53. package/skills/tfx-team/SKILL.md +0 -172
package/hub/router.mjs CHANGED
@@ -1,73 +1,306 @@
1
- // hub/router.mjs — Actor mailbox 라우터 + QoS 스케줄러
2
- // 메시지 라우팅, ask/publish/handoff 처리, TTL 정리
1
+ // hub/router.mjs — 실시간 라우팅/수신함 상태 관리자
2
+ // SQLite는 감사 로그만 담당하고, 실제 배달 상태는 메모리에서 관리한다.
3
3
  import { EventEmitter, once } from 'node:events';
4
4
  import { uuidv7 } from './store.mjs';
5
-
6
- /**
7
- * 라우터 생성
8
- * @param {object} store — createStore() 반환 객체
9
- */
5
+
6
+ function uniqueStrings(values = []) {
7
+ return Array.from(new Set((values || []).map((value) => String(value || '').trim()).filter(Boolean)));
8
+ }
9
+
10
+ function normalizeAgentTopics(store, agentId, runtimeTopics) {
11
+ const topics = new Set(runtimeTopics || []);
12
+ const persisted = store.getAgent(agentId)?.topics || [];
13
+ for (const topic of persisted) topics.add(topic);
14
+ return Array.from(topics);
15
+ }
16
+
17
+ /**
18
+ * 라우터 생성
19
+ * @param {object} store
20
+ */
10
21
  export function createRouter(store) {
11
22
  let sweepTimer = null;
12
23
  let staleTimer = null;
13
24
  const responseEmitter = new EventEmitter();
25
+ const deliveryEmitter = new EventEmitter();
14
26
  responseEmitter.setMaxListeners(200);
15
-
16
- const router = {
17
- /**
18
- * 메시지를 대상에게 라우팅
19
- * "topic:XXX" 토픽 구독자 전체 fanout
20
- * 직접 agent_id → 1:1 배달
21
- * @returns {number} 배달된 에이전트 수
22
- */
23
- route(msg) {
24
- const to = msg.to_agent ?? msg.to;
25
- if (to.startsWith('topic:')) {
26
- return store.deliverToTopic(msg.id, to.slice(6));
27
- }
28
- store.deliverToAgent(msg.id, to);
29
- return 1;
30
- },
31
-
32
- /**
33
- * ask — 질문 요청 (request/reply 패턴)
34
- * await_response_ms > 0 이면 짧은 폴링으로 응답 대기
35
- * 0 이면 티켓(correlation_id) 즉시 반환
36
- */
37
- async handleAsk({
38
- from, to, topic, question, context_refs,
39
- payload = {}, priority = 5, ttl_ms = 300000,
40
- await_response_ms = 0, trace_id, correlation_id,
41
- }) {
42
- const cid = correlation_id || uuidv7();
43
- const tid = trace_id || uuidv7();
44
-
45
- const msg = store.enqueueMessage({
46
- type: 'request', from, to, topic, priority, ttl_ms,
47
- payload: { question, context_refs, ...payload },
48
- correlation_id: cid, trace_id: tid,
49
- });
50
- router.route(msg);
51
-
52
- // 티켓 모드: 즉시 반환
53
- if (await_response_ms <= 0) {
54
- return {
55
- ok: true,
56
- data: { request_message_id: msg.id, correlation_id: cid, trace_id: tid, state: 'queued' },
57
- };
58
- }
59
-
60
- // 이벤트 기반 대기 (최대 30초 제한)
27
+ deliveryEmitter.setMaxListeners(200);
28
+
29
+ const runtimeTopics = new Map();
30
+ const queuesByAgent = new Map();
31
+ const liveMessages = new Map();
32
+ const deliveryLatencies = [];
33
+
34
+ function ensureAgentQueue(agentId) {
35
+ let queue = queuesByAgent.get(agentId);
36
+ if (!queue) {
37
+ queue = new Map();
38
+ queuesByAgent.set(agentId, queue);
39
+ }
40
+ return queue;
41
+ }
42
+
43
+ function pruneDeliveryStats(now = Date.now()) {
44
+ while (deliveryLatencies.length && deliveryLatencies[0].at < now - 300000) {
45
+ deliveryLatencies.shift();
46
+ }
47
+ }
48
+
49
+ function upsertRuntimeTopics(agentId, topics, { replace = true } = {}) {
50
+ const normalized = uniqueStrings(topics);
51
+ const current = replace ? new Set() : new Set(runtimeTopics.get(agentId) || []);
52
+ for (const topic of normalized) current.add(topic);
53
+ runtimeTopics.set(agentId, current);
54
+ store.updateAgentTopics(agentId, Array.from(current));
55
+ return Array.from(current);
56
+ }
57
+
58
+ function listRuntimeTopics(agentId) {
59
+ return normalizeAgentTopics(store, agentId, runtimeTopics.get(agentId));
60
+ }
61
+
62
+ function trackMessage(message, recipients) {
63
+ liveMessages.set(message.id, {
64
+ message,
65
+ recipients: new Set(recipients),
66
+ ackedBy: new Set(),
67
+ });
68
+ }
69
+
70
+ function getMessageRecord(messageId) {
71
+ return liveMessages.get(messageId) || null;
72
+ }
73
+
74
+ function removeMessage(messageId) {
75
+ const record = liveMessages.get(messageId);
76
+ if (!record) return;
77
+ for (const agentId of record.recipients) {
78
+ queuesByAgent.get(agentId)?.delete(messageId);
79
+ }
80
+ liveMessages.delete(messageId);
81
+ }
82
+
83
+ function queueMessage(agentId, message) {
84
+ const queue = ensureAgentQueue(agentId);
85
+ queue.set(message.id, {
86
+ message,
87
+ attempts: 0,
88
+ delivered_at_ms: null,
89
+ acked_at_ms: null,
90
+ });
91
+ deliveryEmitter.emit('message', agentId, message);
92
+ }
93
+
94
+ function resolveRecipients(msg) {
95
+ const to = msg.to_agent ?? msg.to;
96
+ if (!to?.startsWith('topic:')) {
97
+ return [to];
98
+ }
99
+
100
+ const topic = to.slice(6);
101
+ const recipients = new Set();
102
+ for (const [agentId, topics] of runtimeTopics) {
103
+ if (topics.has(topic)) recipients.add(agentId);
104
+ }
105
+ for (const agent of store.getAgentsByTopic(topic)) {
106
+ recipients.add(agent.agent_id);
107
+ }
108
+ return Array.from(recipients);
109
+ }
110
+
111
+ function sortedPending(agentId, { max_messages = 20, include_topics = null } = {}) {
112
+ const queue = ensureAgentQueue(agentId);
113
+ const topicFilter = include_topics?.length ? new Set(include_topics) : null;
114
+ const now = Date.now();
115
+ const pending = [];
116
+
117
+ for (const delivery of queue.values()) {
118
+ const { message } = delivery;
119
+ if (delivery.acked_at_ms) continue;
120
+ if (message.expires_at_ms <= now) continue;
121
+ if (topicFilter && !topicFilter.has(message.topic)) continue;
122
+ pending.push(message);
123
+ }
124
+
125
+ pending.sort((a, b) => {
126
+ if (b.priority !== a.priority) return b.priority - a.priority;
127
+ return a.created_at_ms - b.created_at_ms;
128
+ });
129
+ return pending.slice(0, max_messages);
130
+ }
131
+
132
+ function markDelivered(agentId, messageId) {
133
+ const delivery = queuesByAgent.get(agentId)?.get(messageId);
134
+ const record = getMessageRecord(messageId);
135
+ if (!delivery || !record) return false;
136
+
137
+ delivery.attempts += 1;
138
+ if (!delivery.delivered_at_ms) {
139
+ delivery.delivered_at_ms = Date.now();
140
+ record.message.status = 'delivered';
141
+ store.updateMessageStatus(messageId, 'delivered');
142
+ deliveryLatencies.push({
143
+ at: delivery.delivered_at_ms,
144
+ ms: delivery.delivered_at_ms - record.message.created_at_ms,
145
+ });
146
+ pruneDeliveryStats(delivery.delivered_at_ms);
147
+ return true;
148
+ }
149
+ return false;
150
+ }
151
+
152
+ function ackMessages(ids, agentId) {
153
+ const now = Date.now();
154
+ let count = 0;
155
+
156
+ for (const id of ids || []) {
157
+ const delivery = queuesByAgent.get(agentId)?.get(id);
158
+ const record = getMessageRecord(id);
159
+ if (!delivery || !record || delivery.acked_at_ms) continue;
160
+
161
+ delivery.acked_at_ms = now;
162
+ record.ackedBy.add(agentId);
163
+ count += 1;
164
+
165
+ if (record.ackedBy.size >= record.recipients.size) {
166
+ record.message.status = 'acked';
167
+ store.updateMessageStatus(id, 'acked');
168
+ removeMessage(id);
169
+ }
170
+ }
171
+
172
+ return count;
173
+ }
174
+
175
+ function dispatchMessage({ type, from, to, topic, priority = 5, ttl_ms = 300000, payload = {}, trace_id, correlation_id }) {
176
+ const msg = store.auditLog({
177
+ type,
178
+ from,
179
+ to,
180
+ topic,
181
+ priority,
182
+ ttl_ms,
183
+ payload,
184
+ trace_id,
185
+ correlation_id,
186
+ });
187
+ const recipients = uniqueStrings(resolveRecipients(msg));
188
+ if (recipients.length) {
189
+ trackMessage(msg, recipients);
190
+ for (const agentId of recipients) {
191
+ queueMessage(agentId, msg);
192
+ }
193
+ msg.status = 'delivered';
194
+ store.updateMessageStatus(msg.id, 'delivered');
195
+ }
196
+ if (msg.type === 'response') {
197
+ responseEmitter.emit(msg.correlation_id, msg.payload);
198
+ }
199
+ return { msg, recipients };
200
+ }
201
+
202
+ const router = {
203
+ responseEmitter,
204
+ deliveryEmitter,
205
+
206
+ registerAgent(args) {
207
+ const result = store.registerAgent(args);
208
+ upsertRuntimeTopics(args.agent_id, args.topics || [], { replace: true });
209
+ return result;
210
+ },
211
+
212
+ refreshAgentLease(agentId, ttlMs = 30000) {
213
+ return store.refreshLease(agentId, ttlMs);
214
+ },
215
+
216
+ subscribeAgent(agentId, topics, { replace = false } = {}) {
217
+ const nextTopics = upsertRuntimeTopics(agentId, topics, { replace });
218
+ return { agent_id: agentId, topics: nextTopics };
219
+ },
220
+
221
+ getSubscribedTopics(agentId) {
222
+ return listRuntimeTopics(agentId);
223
+ },
224
+
225
+ updateAgentStatus(agentId, status) {
226
+ if (status === 'offline') {
227
+ runtimeTopics.delete(agentId);
228
+ }
229
+ return store.updateAgentStatus(agentId, status);
230
+ },
231
+
232
+ route(msg) {
233
+ const recipients = uniqueStrings(resolveRecipients(msg));
234
+ if (!recipients.length) return 0;
235
+ if (!getMessageRecord(msg.id)) {
236
+ trackMessage(msg, recipients);
237
+ }
238
+ for (const agentId of recipients) {
239
+ queueMessage(agentId, msg);
240
+ }
241
+ store.updateMessageStatus(msg.id, 'delivered');
242
+ return recipients.length;
243
+ },
244
+
245
+ getPendingMessages(agentId, options = {}) {
246
+ return sortedPending(agentId, options);
247
+ },
248
+
249
+ markMessagePushed(agentId, messageId) {
250
+ return markDelivered(agentId, messageId);
251
+ },
252
+
253
+ drainAgent(agentId, { max_messages = 20, include_topics = null, auto_ack = false } = {}) {
254
+ const messages = sortedPending(agentId, { max_messages, include_topics });
255
+ for (const message of messages) {
256
+ markDelivered(agentId, message.id);
257
+ }
258
+ if (auto_ack && messages.length) {
259
+ ackMessages(messages.map((message) => message.id), agentId);
260
+ }
261
+ return messages;
262
+ },
263
+
264
+ ackMessages(ids, agentId) {
265
+ return ackMessages(ids, agentId);
266
+ },
267
+
268
+ async handleAsk({
269
+ from, to, topic, question, context_refs,
270
+ payload = {}, priority = 5, ttl_ms = 300000,
271
+ await_response_ms = 0, trace_id, correlation_id,
272
+ }) {
273
+ const cid = correlation_id || uuidv7();
274
+ const tid = trace_id || uuidv7();
275
+
276
+ const { msg } = dispatchMessage({
277
+ type: 'request',
278
+ from,
279
+ to,
280
+ topic,
281
+ priority,
282
+ ttl_ms,
283
+ payload: { question, context_refs, ...payload },
284
+ correlation_id: cid,
285
+ trace_id: tid,
286
+ });
287
+
288
+ if (await_response_ms <= 0) {
289
+ return {
290
+ ok: true,
291
+ data: { request_message_id: msg.id, correlation_id: cid, trace_id: tid, state: 'queued' },
292
+ };
293
+ }
294
+
61
295
  try {
62
- const [payload] = await once(responseEmitter, cid, {
296
+ const [response] = await once(responseEmitter, cid, {
63
297
  signal: AbortSignal.timeout(Math.min(await_response_ms, 30000)),
64
298
  });
65
299
  return {
66
300
  ok: true,
67
- data: { request_message_id: msg.id, correlation_id: cid, trace_id: tid, state: 'answered', response: payload },
301
+ data: { request_message_id: msg.id, correlation_id: cid, trace_id: tid, state: 'answered', response },
68
302
  };
69
303
  } catch {
70
- // 타임아웃 — DB에서 최종 확인
71
304
  const resp = store.getResponseByCorrelation(cid);
72
305
  if (resp) {
73
306
  return {
@@ -80,121 +313,149 @@ export function createRouter(store) {
80
313
  data: { request_message_id: msg.id, correlation_id: cid, trace_id: tid, state: 'delivered' },
81
314
  };
82
315
  }
83
- },
84
-
85
- /**
86
- * publish 이벤트/응답 발행
87
- * correlation_id 존재 response 타입, 없으면 event 타입
88
- */
89
- handlePublish({
90
- from, to, topic, priority = 5, ttl_ms = 300000,
91
- payload = {}, trace_id, correlation_id,
92
- }) {
93
- const type = correlation_id ? 'response' : 'event';
94
- const msg = store.enqueueMessage({
95
- type, from, to, topic, priority, ttl_ms, payload,
316
+ },
317
+
318
+ handlePublish({
319
+ from, to, topic, priority = 5, ttl_ms = 300000,
320
+ payload = {}, trace_id, correlation_id, message_type,
321
+ }) {
322
+ const type = message_type || (correlation_id ? 'response' : 'event');
323
+ const { msg, recipients } = dispatchMessage({
324
+ type,
325
+ from,
326
+ to,
327
+ topic,
328
+ priority,
329
+ ttl_ms,
330
+ payload,
331
+ trace_id: trace_id || uuidv7(),
96
332
  correlation_id: correlation_id || uuidv7(),
333
+ });
334
+ return {
335
+ ok: true,
336
+ data: {
337
+ message_id: msg.id,
338
+ fanout_count: recipients.length,
339
+ expires_at_ms: msg.expires_at_ms,
340
+ },
341
+ };
342
+ },
343
+
344
+ handleHandoff({
345
+ from, to, topic, task, acceptance_criteria, context_refs,
346
+ priority = 5, ttl_ms = 600000, trace_id, correlation_id,
347
+ }) {
348
+ const { msg } = dispatchMessage({
349
+ type: 'handoff',
350
+ from,
351
+ to,
352
+ topic,
353
+ priority,
354
+ ttl_ms,
355
+ payload: { task, acceptance_criteria, context_refs },
97
356
  trace_id: trace_id || uuidv7(),
357
+ correlation_id: correlation_id || uuidv7(),
98
358
  });
99
- const fanout = router.route(msg);
100
- if (correlation_id) {
101
- responseEmitter.emit(correlation_id, msg.payload);
102
- }
103
359
  return {
104
360
  ok: true,
105
- data: { message_id: msg.id, fanout_count: fanout, expires_at_ms: msg.expires_at_ms },
361
+ data: { handoff_message_id: msg.id, state: 'queued', assigned_to: to },
106
362
  };
107
- },
108
-
109
- /**
110
- * handoff 작업 인계
111
- * acceptance_criteria, context_refs 포함 가능
112
- */
113
- handleHandoff({
114
- from, to, topic, task, acceptance_criteria, context_refs,
115
- priority = 5, ttl_ms = 600000, trace_id, correlation_id,
116
- }) {
117
- const msg = store.enqueueMessage({
118
- type: 'handoff', from, to, topic, priority, ttl_ms,
119
- payload: { task, acceptance_criteria, context_refs },
120
- correlation_id: correlation_id || uuidv7(),
121
- trace_id: trace_id || uuidv7(),
122
- });
123
- router.route(msg);
124
- return {
125
- ok: true,
126
- data: { handoff_message_id: msg.id, state: 'queued', assigned_to: to },
127
- };
128
- },
129
-
130
- // ── 스위퍼 ──
131
-
132
- /** 주기적 만료 정리 시작 (10초: 메시지, 60초: 비활성 에이전트) */
363
+ },
364
+
365
+ sweepExpired() {
366
+ const now = Date.now();
367
+ let expired = 0;
368
+ for (const [messageId, record] of Array.from(liveMessages.entries())) {
369
+ if (record.message.expires_at_ms > now) continue;
370
+ store.moveToDeadLetter(messageId, 'ttl_expired', null);
371
+ removeMessage(messageId);
372
+ expired += 1;
373
+ }
374
+ return { messages: expired };
375
+ },
376
+
133
377
  startSweeper() {
134
378
  if (sweepTimer) return;
135
379
  sweepTimer = setInterval(() => {
136
- try { store.sweepExpired(); } catch { /* 무시 */ }
380
+ try { router.sweepExpired(); } catch {}
137
381
  }, 10000);
138
382
  staleTimer = setInterval(() => {
139
- try { store.sweepStaleAgents(); } catch { /* 무시 */ }
383
+ try { store.sweepStaleAgents(); } catch {}
140
384
  }, 120000);
141
- sweepTimer.unref();
142
- staleTimer.unref();
143
- },
144
-
145
- /** 정리 중지 */
146
- stopSweeper() {
147
- if (sweepTimer) { clearInterval(sweepTimer); sweepTimer = null; }
148
- if (staleTimer) { clearInterval(staleTimer); staleTimer = null; }
149
- },
150
-
151
- // ── 상태 조회 ──
152
-
153
- /**
154
- * 허브/에이전트/큐/트레이스 상태 조회
155
- * @param {'hub'|'agent'|'queue'|'trace'} scope
156
- */
157
- getStatus(scope = 'hub', { agent_id, trace_id, include_metrics = true } = {}) {
158
- const data = {};
159
-
160
- if (scope === 'hub' || scope === 'queue') {
161
- data.hub = {
162
- state: 'healthy',
163
- uptime_ms: process.uptime() * 1000 | 0,
164
- db_wal_mode: true,
165
- };
166
- if (include_metrics) {
167
- const depths = store.getQueueDepths();
168
- const stats = store.getDeliveryStats();
169
- data.queues = {
170
- urgent_depth: depths.urgent,
171
- normal_depth: depths.normal,
172
- dlq_depth: depths.dlq,
173
- p95_delivery_ms: stats.avg_delivery_ms,
174
- timeout_rate: 0,
175
- };
176
- }
177
- }
178
-
179
- if (scope === 'agent' && agent_id) {
180
- const agent = store.getAgent(agent_id);
181
- if (agent) {
182
- data.agent = {
183
- agent_id: agent.agent_id,
184
- status: agent.status,
185
- pending: 0,
186
- last_seen_ms: agent.last_seen_ms,
187
- };
188
- }
189
- }
190
-
191
- if (scope === 'trace' && trace_id) {
192
- data.trace = store.getMessagesByTrace(trace_id);
193
- }
194
-
195
- return { ok: true, data };
196
- },
197
- };
198
-
199
- return { ...router, responseEmitter };
385
+ sweepTimer.unref();
386
+ staleTimer.unref();
387
+ },
388
+
389
+ stopSweeper() {
390
+ if (sweepTimer) { clearInterval(sweepTimer); sweepTimer = null; }
391
+ if (staleTimer) { clearInterval(staleTimer); staleTimer = null; }
392
+ },
393
+
394
+ getQueueDepths() {
395
+ const counts = { urgent: 0, normal: 0, dlq: store.getAuditStats().dlq };
396
+ for (const record of liveMessages.values()) {
397
+ const pending = record.recipients.size > record.ackedBy.size;
398
+ if (!pending) continue;
399
+ if (record.message.priority >= 7) counts.urgent += 1;
400
+ else counts.normal += 1;
401
+ }
402
+ return counts;
403
+ },
404
+
405
+ getDeliveryStats() {
406
+ pruneDeliveryStats();
407
+ if (!deliveryLatencies.length) {
408
+ return { total_deliveries: 0, avg_delivery_ms: 0 };
409
+ }
410
+ const total = deliveryLatencies.reduce((sum, item) => sum + item.ms, 0);
411
+ return {
412
+ total_deliveries: deliveryLatencies.length,
413
+ avg_delivery_ms: Math.round(total / deliveryLatencies.length),
414
+ };
415
+ },
416
+
417
+ getStatus(scope = 'hub', { agent_id, trace_id, include_metrics = true } = {}) {
418
+ const data = {};
419
+
420
+ if (scope === 'hub' || scope === 'queue') {
421
+ data.hub = {
422
+ state: 'healthy',
423
+ uptime_ms: process.uptime() * 1000 | 0,
424
+ realtime_transport: 'named-pipe',
425
+ audit_store: 'sqlite',
426
+ };
427
+ if (include_metrics) {
428
+ const depths = router.getQueueDepths();
429
+ const stats = router.getDeliveryStats();
430
+ data.queues = {
431
+ urgent_depth: depths.urgent,
432
+ normal_depth: depths.normal,
433
+ dlq_depth: depths.dlq,
434
+ avg_delivery_ms: stats.avg_delivery_ms,
435
+ };
436
+ }
437
+ }
438
+
439
+ if (scope === 'agent' && agent_id) {
440
+ const agent = store.getAgent(agent_id);
441
+ if (agent) {
442
+ data.agent = {
443
+ agent_id: agent.agent_id,
444
+ status: agent.status,
445
+ pending: sortedPending(agent_id, { max_messages: 1000 }).length,
446
+ last_seen_ms: agent.last_seen_ms,
447
+ topics: listRuntimeTopics(agent_id),
448
+ };
449
+ }
450
+ }
451
+
452
+ if (scope === 'trace' && trace_id) {
453
+ data.trace = store.getMessagesByTrace(trace_id);
454
+ }
455
+
456
+ return { ok: true, data };
457
+ },
458
+ };
459
+
460
+ return router;
200
461
  }