@triflux/core 10.43.0 → 10.45.0

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 (41) hide show
  1. package/hooks/codex-session-hook.mjs +67 -1
  2. package/hooks/cto-north-star-brief.mjs +3 -16
  3. package/hooks/error-context.mjs +3 -6
  4. package/hooks/hook-manager.mjs +1 -1
  5. package/hooks/hook-registry.json +0 -11
  6. package/hooks/keyword-rules.json +147 -23
  7. package/hooks/permission-safe-allow.mjs +36 -2
  8. package/hooks/session-start-fast.mjs +50 -1
  9. package/hooks/session-start-lake.mjs +3 -16
  10. package/hub/bridge.mjs +111 -5
  11. package/hub/cli-adapter-base.mjs +11 -2
  12. package/hub/codex-adapter.mjs +3 -0
  13. package/hub/gemini-adapter.mjs +29 -11
  14. package/hub/intent.mjs +24 -32
  15. package/hub/lib/memory-store.mjs +66 -30
  16. package/hub/lib/tfx-route-args.mjs +41 -4
  17. package/hub/reflexion.mjs +0 -57
  18. package/hub/router.mjs +474 -24
  19. package/hub/team/claude-daemon-control.mjs +2 -0
  20. package/hub/team/intervention.mjs +1 -1
  21. package/hub/team/retry-state-machine.mjs +41 -13
  22. package/hub/team/synapse-http.mjs +149 -4
  23. package/hud/cli-policy.mjs +69 -0
  24. package/hud/constants.mjs +20 -17
  25. package/hud/context-monitor.mjs +31 -21
  26. package/hud/hud-qos-status.mjs +81 -91
  27. package/hud/providers/claude.mjs +150 -84
  28. package/hud/providers/codex.mjs +10 -1
  29. package/hud/renderers.mjs +21 -59
  30. package/hud/utils.mjs +1 -15
  31. package/package.json +1 -1
  32. package/scripts/lib/agent-route-policy.mjs +367 -0
  33. package/scripts/lib/cli-agy.mjs +24 -4
  34. package/scripts/lib/cli-codex.mjs +22 -160
  35. package/scripts/lib/codex-profile-config.mjs +48 -4
  36. package/scripts/lib/gemini-profiles.mjs +113 -29
  37. package/scripts/lib/keyword-rules.mjs +24 -0
  38. package/scripts/lib/logger.mjs +60 -43
  39. package/scripts/lib/machine-profile.mjs +293 -0
  40. package/scripts/lib/mcp-health.mjs +44 -18
  41. package/hub/log-retention.mjs +0 -726
package/hub/router.mjs CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  resolveHardCeilingMs,
10
10
  } from "./lib/timeout-defaults.mjs";
11
11
  import { uuidv7 } from "./lib/uuidv7.mjs";
12
+ import { normalizeRoleKey } from "./role-contract.mjs";
12
13
 
13
14
  const ASSIGN_PENDING_STATUSES = new Set(["queued", "running"]);
14
15
  const ROLE_TOPICS = new Set(["cto"]);
@@ -16,6 +17,258 @@ const ROLE_SOURCE_RANK = Object.freeze({
16
17
  explicit: 2,
17
18
  "topic-fallback": 1,
18
19
  });
20
+ const TRANSPORT_LOCATOR_SCHEMA_VERSION = "tfx.transport-locator.v1";
21
+ const TRANSPORT_KINDS = new Set(["claude-uds", "tmux"]);
22
+ const TRANSPORT_CAPABILITIES = new Set([
23
+ "probe",
24
+ "wake",
25
+ "activate",
26
+ "interrupt",
27
+ ]);
28
+ const HOST_ID_RE = /^hst_[A-Za-z0-9_-]+$/u;
29
+
30
+ function registrationError(code, message = code) {
31
+ const error = new Error(message);
32
+ error.code = code;
33
+ return error;
34
+ }
35
+
36
+ function registrationString(value, code, { maxLength = 256 } = {}) {
37
+ if (typeof value !== "string" || !value.trim() || value.length > maxLength) {
38
+ throw registrationError(code);
39
+ }
40
+ return value.trim();
41
+ }
42
+
43
+ function registrationLogicalRef(value, code, { maxLength = 128 } = {}) {
44
+ const normalized = registrationString(value, code, { maxLength });
45
+ if (!/^[A-Za-z0-9._:-]+$/u.test(normalized)) {
46
+ throw registrationError(code);
47
+ }
48
+ return normalized;
49
+ }
50
+
51
+ function assertExactFields(value, allowed, code) {
52
+ if (
53
+ value === null ||
54
+ typeof value !== "object" ||
55
+ Array.isArray(value) ||
56
+ Object.keys(value).some((field) => !allowed.has(field))
57
+ ) {
58
+ throw registrationError(code);
59
+ }
60
+ }
61
+
62
+ function normalizeTransportLocator(
63
+ value,
64
+ { cli, hostId, now, leaseExpiresMs },
65
+ ) {
66
+ assertExactFields(
67
+ value,
68
+ new Set([
69
+ "schema_version",
70
+ "transport_id",
71
+ "kind",
72
+ "host_id",
73
+ "capabilities",
74
+ "priority",
75
+ "expires_at_ms",
76
+ "locator",
77
+ ]),
78
+ "TRANSPORT_LOCATOR_INVALID",
79
+ );
80
+ if (value.schema_version !== TRANSPORT_LOCATOR_SCHEMA_VERSION) {
81
+ throw registrationError("TRANSPORT_LOCATOR_SCHEMA_UNSUPPORTED");
82
+ }
83
+ const transportId = registrationLogicalRef(
84
+ value.transport_id,
85
+ "TRANSPORT_ID_INVALID",
86
+ { maxLength: 128 },
87
+ );
88
+ if (!TRANSPORT_KINDS.has(value.kind)) {
89
+ throw registrationError("TRANSPORT_KIND_UNSUPPORTED");
90
+ }
91
+ if (cli === "codex" && value.kind !== "tmux") {
92
+ throw registrationError("CODEX_TRANSPORT_UNSUPPORTED");
93
+ }
94
+ const locatorHostId = registrationString(value.host_id, "HOST_ID_INVALID", {
95
+ maxLength: 128,
96
+ });
97
+ if (!HOST_ID_RE.test(locatorHostId) || (hostId && locatorHostId !== hostId)) {
98
+ throw registrationError("HOST_ID_INVALID");
99
+ }
100
+ if (!Array.isArray(value.capabilities)) {
101
+ throw registrationError("TRANSPORT_CAPABILITIES_INVALID");
102
+ }
103
+ const capabilities = uniqueStrings(value.capabilities);
104
+ if (
105
+ capabilities.length !== value.capabilities.length ||
106
+ capabilities.some((capability) => !TRANSPORT_CAPABILITIES.has(capability))
107
+ ) {
108
+ throw registrationError("TRANSPORT_CAPABILITIES_INVALID");
109
+ }
110
+ if (!Number.isSafeInteger(value.priority)) {
111
+ throw registrationError("TRANSPORT_PRIORITY_INVALID");
112
+ }
113
+ if (
114
+ !Number.isSafeInteger(value.expires_at_ms) ||
115
+ value.expires_at_ms <= now ||
116
+ value.expires_at_ms > leaseExpiresMs
117
+ ) {
118
+ throw registrationError("TRANSPORT_EXPIRY_INVALID");
119
+ }
120
+
121
+ let locator;
122
+ if (value.kind === "claude-uds") {
123
+ assertExactFields(
124
+ value.locator,
125
+ new Set(["session_id", "short", "bridge_id"]),
126
+ "TRANSPORT_LOCATOR_INVALID",
127
+ );
128
+ const sessionId = value.locator.session_id;
129
+ const short = value.locator.short;
130
+ if (!sessionId && !short) {
131
+ throw registrationError("CLAUDE_UDS_SESSION_REQUIRED");
132
+ }
133
+ locator = {
134
+ ...(sessionId
135
+ ? {
136
+ session_id: registrationLogicalRef(
137
+ sessionId,
138
+ "CLAUDE_UDS_SESSION_INVALID",
139
+ ),
140
+ }
141
+ : {}),
142
+ ...(short
143
+ ? {
144
+ short: registrationLogicalRef(short, "CLAUDE_UDS_SHORT_INVALID", {
145
+ maxLength: 128,
146
+ }),
147
+ }
148
+ : {}),
149
+ bridge_id: registrationLogicalRef(
150
+ value.locator.bridge_id,
151
+ "CLAUDE_UDS_BRIDGE_INVALID",
152
+ { maxLength: 128 },
153
+ ),
154
+ };
155
+ } else {
156
+ assertExactFields(
157
+ value.locator,
158
+ new Set(["session_name", "mux_server_id"]),
159
+ "TRANSPORT_LOCATOR_INVALID",
160
+ );
161
+ locator = {
162
+ session_name: registrationLogicalRef(
163
+ value.locator.session_name,
164
+ "TMUX_SESSION_INVALID",
165
+ { maxLength: 128 },
166
+ ),
167
+ mux_server_id: registrationLogicalRef(
168
+ value.locator.mux_server_id,
169
+ "TMUX_SERVER_INVALID",
170
+ { maxLength: 128 },
171
+ ),
172
+ };
173
+ }
174
+
175
+ return {
176
+ schema_version: TRANSPORT_LOCATOR_SCHEMA_VERSION,
177
+ transport_id: transportId,
178
+ kind: value.kind,
179
+ host_id: locatorHostId,
180
+ capabilities,
181
+ priority: value.priority,
182
+ expires_at_ms: value.expires_at_ms,
183
+ locator,
184
+ };
185
+ }
186
+
187
+ export function normalizeRegistrationIdentity(args = {}, now = Date.now()) {
188
+ const hasProjectId = Object.hasOwn(args, "project_id");
189
+ const hasSessionId = Object.hasOwn(args, "session_id");
190
+ const hasHostId = Object.hasOwn(args, "host_id");
191
+ const hasLocatorJson = Object.hasOwn(args, "transport_locators_json");
192
+ const hasLocators = Object.hasOwn(args, "transport_locators");
193
+ if (
194
+ !hasProjectId &&
195
+ !hasSessionId &&
196
+ !hasHostId &&
197
+ !hasLocatorJson &&
198
+ !hasLocators
199
+ ) {
200
+ return null;
201
+ }
202
+
203
+ const projectId = hasProjectId
204
+ ? normalizeRoleKey({ project_id: args.project_id, role_kind: "cto" })
205
+ .project_id
206
+ : null;
207
+ const sessionId = hasSessionId
208
+ ? registrationString(args.session_id, "SESSION_ID_INVALID")
209
+ : null;
210
+ const hostId = hasHostId
211
+ ? registrationString(args.host_id, "HOST_ID_INVALID", { maxLength: 128 })
212
+ : null;
213
+ if (hostId && !HOST_ID_RE.test(hostId)) {
214
+ throw registrationError("HOST_ID_INVALID");
215
+ }
216
+
217
+ let rawLocators = [];
218
+ if (hasLocatorJson) {
219
+ if (Array.isArray(args.transport_locators_json)) {
220
+ rawLocators = args.transport_locators_json;
221
+ } else if (typeof args.transport_locators_json === "string") {
222
+ try {
223
+ rawLocators = JSON.parse(args.transport_locators_json);
224
+ } catch {
225
+ throw registrationError("TRANSPORT_LOCATORS_JSON_INVALID");
226
+ }
227
+ } else {
228
+ throw registrationError("TRANSPORT_LOCATORS_JSON_INVALID");
229
+ }
230
+ } else if (hasLocators) {
231
+ rawLocators = args.transport_locators;
232
+ }
233
+ if (!Array.isArray(rawLocators)) {
234
+ throw registrationError("TRANSPORT_LOCATORS_INVALID");
235
+ }
236
+
237
+ const ttlMs = Number(args.heartbeat_ttl_ms ?? 30000);
238
+ if (rawLocators.length > 0 && (!Number.isFinite(ttlMs) || ttlMs <= 0)) {
239
+ throw registrationError("HEARTBEAT_TTL_INVALID");
240
+ }
241
+ const leaseExpiresMs = now + ttlMs;
242
+ const normalizedLocators = rawLocators
243
+ .map((locator) =>
244
+ normalizeTransportLocator(locator, {
245
+ cli: args.cli,
246
+ hostId,
247
+ now,
248
+ leaseExpiresMs,
249
+ }),
250
+ )
251
+ .sort(
252
+ (left, right) =>
253
+ right.priority - left.priority ||
254
+ left.transport_id.localeCompare(right.transport_id),
255
+ );
256
+ const resolvedHostId = hostId || normalizedLocators[0]?.host_id || null;
257
+ if (
258
+ resolvedHostId &&
259
+ normalizedLocators.some((locator) => locator.host_id !== resolvedHostId)
260
+ ) {
261
+ throw registrationError("HOST_ID_INVALID");
262
+ }
263
+
264
+ return {
265
+ project_id: projectId,
266
+ session_id: sessionId,
267
+ host_id: resolvedHostId,
268
+ transport_locators_json:
269
+ hasLocatorJson || hasLocators ? JSON.stringify(normalizedLocators) : null,
270
+ };
271
+ }
19
272
 
20
273
  function uniqueStrings(values = []) {
21
274
  return Array.from(
@@ -123,10 +376,89 @@ export function createRouter(store) {
123
376
  const queuesByAgent = new Map();
124
377
  const liveMessages = new Map();
125
378
  const roleStates = new Map();
379
+ const hubInstanceId = uuidv7();
380
+ const storeFingerprint =
381
+ store.fingerprint || `${store.type || "unknown"}:unknown`;
382
+ const updateRegistrationIdentity = store.db?.prepare
383
+ ? store.db.prepare(`
384
+ UPDATE agents SET
385
+ project_id=COALESCE(@project_id, project_id),
386
+ session_id=COALESCE(@session_id, session_id),
387
+ host_id=COALESCE(@host_id, host_id),
388
+ transport_locators_json=COALESCE(
389
+ @transport_locators_json,
390
+ transport_locators_json
391
+ )
392
+ WHERE agent_id=@agent_id`)
393
+ : null;
126
394
  const MAX_LATENCY_SAMPLES = 100;
127
395
  let latencyIdx = 0;
128
396
  const deliveryLatencies = new Array(MAX_LATENCY_SAMPLES).fill(0);
129
397
 
398
+ function hubIdentity() {
399
+ return {
400
+ hub_instance_id: hubInstanceId,
401
+ store_fingerprint: storeFingerprint,
402
+ };
403
+ }
404
+
405
+ function validateCallerIdentity(input = {}) {
406
+ if (
407
+ input.hub_instance_id != null &&
408
+ input.hub_instance_id !== hubInstanceId
409
+ ) {
410
+ return {
411
+ ok: false,
412
+ error: {
413
+ code: "STALE_HUB_INSTANCE",
414
+ message: "caller is bound to a previous hub instance",
415
+ },
416
+ data: hubIdentity(),
417
+ };
418
+ }
419
+ if (
420
+ input.store_fingerprint != null &&
421
+ input.store_fingerprint !== storeFingerprint
422
+ ) {
423
+ return {
424
+ ok: false,
425
+ error: {
426
+ code: "STORE_FINGERPRINT_MISMATCH",
427
+ message: "caller store fingerprint does not match this hub",
428
+ },
429
+ data: hubIdentity(),
430
+ };
431
+ }
432
+ return null;
433
+ }
434
+
435
+ function isControlPlaneRegistration(args = {}) {
436
+ const metadata = args.metadata || {};
437
+ const values = [
438
+ ...(Array.isArray(args.capabilities) ? args.capabilities : []),
439
+ ...(Array.isArray(args.topics) ? args.topics : []),
440
+ metadata.role,
441
+ ...(Array.isArray(metadata.roles) ? metadata.roles : []),
442
+ ]
443
+ .flat()
444
+ .map((value) => String(value || "").toLowerCase());
445
+ return (
446
+ metadata.control_plane === true ||
447
+ values.some((value) => value === "cto" || value === "role:cto")
448
+ );
449
+ }
450
+
451
+ function staleOwnerFailure(agentId, result) {
452
+ return {
453
+ ok: false,
454
+ error: {
455
+ code: "STALE_PRESENCE_OWNER",
456
+ message: `stale presence owner: ${agentId}`,
457
+ },
458
+ data: { ...result, ...hubIdentity() },
459
+ };
460
+ }
461
+
130
462
  function ensureAgentQueue(agentId) {
131
463
  let queue = queuesByAgent.get(agentId);
132
464
  if (!queue) {
@@ -855,10 +1187,29 @@ export function createRouter(store) {
855
1187
  deliveryEmitter,
856
1188
 
857
1189
  registerAgent(args) {
1190
+ const identityFailure = validateCallerIdentity(args);
1191
+ if (identityFailure) return identityFailure;
1192
+ if (isControlPlaneRegistration(args) && store.type !== "sqlite") {
1193
+ return {
1194
+ ok: false,
1195
+ error: {
1196
+ code: "CONTROL_PLANE_STORE_DEGRADED",
1197
+ message: "control-plane presence requires durable SQLite storage",
1198
+ },
1199
+ data: { ...hubIdentity(), store_type: store.type || "unknown" },
1200
+ };
1201
+ }
1202
+ const identity = normalizeRegistrationIdentity(args);
858
1203
  const result = store.registerAgent(args);
859
1204
  if (!registerPresenceIsEffective(result)) {
860
1205
  return registerPresenceFailure(args.agent_id, result);
861
1206
  }
1207
+ if (identity && updateRegistrationIdentity) {
1208
+ updateRegistrationIdentity.run({
1209
+ agent_id: args.agent_id,
1210
+ ...identity,
1211
+ });
1212
+ }
862
1213
  upsertRuntimeTopics(args.agent_id, args.topics || [], { replace: true });
863
1214
  refreshRoleCandidateForAgent(args.agent_id);
864
1215
  for (const roleName of ROLE_TOPICS) {
@@ -867,11 +1218,23 @@ export function createRouter(store) {
867
1218
  transferBacklog: true,
868
1219
  });
869
1220
  }
870
- return { ok: true, data: { ...result, hub_pid: process.pid } };
1221
+ return {
1222
+ ok: true,
1223
+ data: { ...result, hub_pid: process.pid, ...hubIdentity() },
1224
+ };
871
1225
  },
872
1226
 
873
- refreshAgentLease(agentId, ttlMs = 30000) {
874
- const result = store.refreshLease(agentId, ttlMs);
1227
+ refreshAgentLease(
1228
+ agentId,
1229
+ ttlMs = 30000,
1230
+ presenceGeneration,
1231
+ callerIdentity = {},
1232
+ ) {
1233
+ const identityFailure = validateCallerIdentity(callerIdentity);
1234
+ if (identityFailure) return identityFailure;
1235
+ const result = store.refreshLease(agentId, ttlMs, presenceGeneration);
1236
+ if (result?.effective?.updated === false)
1237
+ return staleOwnerFailure(agentId, result);
875
1238
  refreshRoleCandidateForAgent(agentId);
876
1239
  for (const roleName of ROLE_TOPICS) {
877
1240
  ensureRoleLeader(roleName, {
@@ -879,7 +1242,7 @@ export function createRouter(store) {
879
1242
  transferBacklog: true,
880
1243
  });
881
1244
  }
882
- return result;
1245
+ return { ok: true, data: result };
883
1246
  },
884
1247
 
885
1248
  subscribeAgent(agentId, topics, { replace = false } = {}) {
@@ -898,11 +1261,15 @@ export function createRouter(store) {
898
1261
  return listRuntimeTopics(agentId);
899
1262
  },
900
1263
 
901
- updateAgentStatus(agentId, status) {
1264
+ updateAgentStatus(agentId, status, presenceGeneration) {
902
1265
  if (status === "offline") {
903
1266
  runtimeTopics.delete(agentId);
904
1267
  }
905
- const updated = store.updateAgentStatus(agentId, status);
1268
+ const updated = store.updateAgentStatus(
1269
+ agentId,
1270
+ status,
1271
+ presenceGeneration,
1272
+ );
906
1273
  refreshRoleCandidateForAgent(agentId);
907
1274
  for (const roleName of ROLE_TOPICS) {
908
1275
  ensureRoleLeader(roleName, {
@@ -913,6 +1280,39 @@ export function createRouter(store) {
913
1280
  return updated;
914
1281
  },
915
1282
 
1283
+ transitionAgentStatus(
1284
+ agentId,
1285
+ status,
1286
+ presenceGeneration,
1287
+ callerIdentity = {},
1288
+ ) {
1289
+ const identityFailure = validateCallerIdentity(callerIdentity);
1290
+ if (identityFailure) return identityFailure;
1291
+ const updated = store.updateAgentStatus(
1292
+ agentId,
1293
+ status,
1294
+ presenceGeneration,
1295
+ );
1296
+ if (!updated) return staleOwnerFailure(agentId, { status });
1297
+ if (status === "offline") runtimeTopics.delete(agentId);
1298
+ refreshRoleCandidateForAgent(agentId);
1299
+ for (const roleName of ROLE_TOPICS) {
1300
+ ensureRoleLeader(roleName, {
1301
+ reason: `status:${status}`,
1302
+ transferBacklog: true,
1303
+ });
1304
+ }
1305
+ return {
1306
+ ok: true,
1307
+ data: {
1308
+ agent_id: agentId,
1309
+ status,
1310
+ presence_generation: presenceGeneration,
1311
+ ...hubIdentity(),
1312
+ },
1313
+ };
1314
+ },
1315
+
916
1316
  route(msg) {
917
1317
  const recipients = uniqueStrings(resolveRecipients(msg));
918
1318
  if (!recipients.length) {
@@ -1097,7 +1497,14 @@ export function createRouter(store) {
1097
1497
  agent_id,
1098
1498
  reason = "manual",
1099
1499
  requested_by = "manual",
1500
+ hub_instance_id,
1501
+ store_fingerprint,
1100
1502
  } = {}) {
1503
+ const identityFailure = validateCallerIdentity({
1504
+ hub_instance_id,
1505
+ store_fingerprint,
1506
+ });
1507
+ if (identityFailure) return identityFailure;
1101
1508
  const roleName = normalizeRoleName(role);
1102
1509
  if (!roleName) {
1103
1510
  return {
@@ -1168,6 +1575,7 @@ export function createRouter(store) {
1168
1575
  leader_epoch: snapshot.leader_epoch,
1169
1576
  ...transfer,
1170
1577
  status: snapshot,
1578
+ ...hubIdentity(),
1171
1579
  },
1172
1580
  };
1173
1581
  },
@@ -1375,18 +1783,15 @@ export function createRouter(store) {
1375
1783
  return scheduleAssignRetry(job, reason, job.error, requested_by);
1376
1784
  },
1377
1785
 
1378
- sweepExpired() {
1379
- const now = Date.now();
1380
- let expired = 0;
1381
- const deadLettered = [];
1786
+ sweepExpired({ now_ms = Date.now(), source = "manual" } = {}) {
1787
+ const result = store.sweepExpired({ now_ms });
1382
1788
  for (const [messageId, record] of Array.from(liveMessages.entries())) {
1383
- if (record.message.expires_at_ms > now) continue;
1384
- store.moveToDeadLetter(messageId, "ttl_expired", null);
1385
- deadLettered.push(record.message);
1789
+ if (record.message.expires_at_ms > now_ms) continue;
1386
1790
  removeMessage(messageId);
1387
- expired += 1;
1388
1791
  }
1389
- for (const message of deadLettered) {
1792
+ for (const transition of result.transitions) {
1793
+ if (!transition.dlq_inserted) continue;
1794
+ const { message } = transition;
1390
1795
  if (
1391
1796
  !["handoff", "request"].includes(message.type) ||
1392
1797
  message.payload?.kind === "assign.job"
@@ -1411,12 +1816,15 @@ export function createRouter(store) {
1411
1816
  original_to: message.to_agent,
1412
1817
  reason: "ttl_expired",
1413
1818
  task: message.payload?.task ?? null,
1819
+ expired_at_ms: message.expires_at_ms,
1820
+ failed_at_ms: transition.failed_at_ms,
1821
+ recovery_source: source,
1414
1822
  },
1415
1823
  trace_id: message.trace_id,
1416
1824
  correlation_id: message.correlation_id,
1417
1825
  });
1418
1826
  }
1419
- return { messages: expired };
1827
+ return result;
1420
1828
  },
1421
1829
 
1422
1830
  sweepTimedOutAssigns() {
@@ -1437,19 +1845,49 @@ export function createRouter(store) {
1437
1845
  return { timed_out, retried };
1438
1846
  },
1439
1847
 
1440
- startSweeper() {
1848
+ startSweeper({
1849
+ run_immediately = false,
1850
+ interval_ms = 10000,
1851
+ onSweepError,
1852
+ } = {}) {
1441
1853
  if (sweepTimer) return;
1442
- sweepTimer = setInterval(() => {
1854
+ const reportSweepError = (source, error, failures) => {
1855
+ if (typeof onSweepError === "function") {
1856
+ onSweepError({ source, error, consecutive_failures: failures });
1857
+ }
1858
+ };
1859
+ let sweepFailures = 0;
1860
+ let assignFailures = 0;
1861
+ const runSweep = () => {
1862
+ try {
1863
+ router.sweepExpired({ source: "periodic" });
1864
+ sweepFailures = 0;
1865
+ } catch (error) {
1866
+ sweepFailures += 1;
1867
+ reportSweepError("periodic", error, sweepFailures);
1868
+ }
1443
1869
  try {
1444
- router.sweepExpired();
1445
1870
  router.sweepTimedOutAssigns();
1446
- } catch {}
1447
- }, 10000);
1871
+ assignFailures = 0;
1872
+ } catch (error) {
1873
+ assignFailures += 1;
1874
+ reportSweepError("assign_timeout", error, assignFailures);
1875
+ }
1876
+ };
1877
+ if (run_immediately) runSweep();
1878
+ sweepTimer = setInterval(() => {
1879
+ runSweep();
1880
+ }, interval_ms);
1881
+ let staleFailures = 0;
1448
1882
  staleTimer = setInterval(() => {
1449
1883
  try {
1450
1884
  store.sweepStaleAgents();
1451
1885
  router.reelectStaleRoles();
1452
- } catch {}
1886
+ staleFailures = 0;
1887
+ } catch (error) {
1888
+ staleFailures += 1;
1889
+ reportSweepError("stale_agents", error, staleFailures);
1890
+ }
1453
1891
  }, 120000);
1454
1892
  sweepTimer.unref();
1455
1893
  staleTimer.unref();
@@ -1505,8 +1943,19 @@ export function createRouter(store) {
1505
1943
 
1506
1944
  getStatus(
1507
1945
  scope = "hub",
1508
- { agent_id, trace_id, include_metrics = true } = {},
1946
+ {
1947
+ agent_id,
1948
+ trace_id,
1949
+ include_metrics = true,
1950
+ hub_instance_id,
1951
+ store_fingerprint,
1952
+ } = {},
1509
1953
  ) {
1954
+ const identityFailure = validateCallerIdentity({
1955
+ hub_instance_id,
1956
+ store_fingerprint,
1957
+ });
1958
+ if (identityFailure) return identityFailure;
1510
1959
  const data = {};
1511
1960
 
1512
1961
  if (scope === "hub" || scope === "queue") {
@@ -1515,6 +1964,7 @@ export function createRouter(store) {
1515
1964
  uptime_ms: (process.uptime() * 1000) | 0,
1516
1965
  realtime_transport: "named-pipe",
1517
1966
  audit_store: store.type || "sqlite",
1967
+ ...hubIdentity(),
1518
1968
  };
1519
1969
  data.roles = {
1520
1970
  cto: getRoleSnapshot("cto"),
@@ -1568,7 +2018,7 @@ export function createRouter(store) {
1568
2018
  data.trace = store.getMessagesByTrace(trace_id);
1569
2019
  }
1570
2020
 
1571
- return { ok: true, data };
2021
+ return { ok: true, data: { ...data, ...hubIdentity() } };
1572
2022
  },
1573
2023
  };
1574
2024
 
@@ -321,10 +321,12 @@ export async function probeClaudeDaemonCandidates({
321
321
  short,
322
322
  sessionId,
323
323
  timeoutMs = 6000,
324
+ tmpRoot = "/tmp",
324
325
  } = {}) {
325
326
  const candidates = await buildClaudeDaemonDiscoveryCandidates({
326
327
  configDir,
327
328
  env,
329
+ tmpRoot,
328
330
  });
329
331
  const callerProvenance = detectCallerProvenance(env);
330
332
  const targetRequested = Boolean(short || sessionId);
@@ -5,11 +5,11 @@ import { statSync } from "node:fs";
5
5
  import { readdir, readlink } from "node:fs/promises";
6
6
  import { promisify } from "node:util";
7
7
  import { isPidAlive } from "../lib/process-utils.mjs";
8
- import { killProcess } from "../platform.mjs";
9
8
  import {
10
9
  resolveHardCeilingMs,
11
10
  resolveStallInterventionMs as resolveStallThresholdMs,
12
11
  } from "../lib/timeout-defaults.mjs";
12
+ import { killProcess } from "../platform.mjs";
13
13
 
14
14
  export { resolveHardCeilingMs, resolveStallThresholdMs };
15
15