triflux 10.44.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.
- package/README.md +1 -1
- package/adapters/codex/skills/tfx-harness/SKILL.md +45 -9
- package/bin/tfx-live.mjs +752 -77
- package/bin/triflux.mjs +9 -6
- package/hooks/codex-session-hook.mjs +67 -1
- package/hooks/cto-north-star-brief.mjs +3 -16
- package/hooks/error-context.mjs +3 -6
- package/hooks/hook-manager.mjs +1 -1
- package/hooks/hook-registry.json +0 -11
- package/hooks/keyword-rules.json +8 -4
- package/hooks/permission-safe-allow.mjs +36 -2
- package/hooks/session-start-fast.mjs +50 -1
- package/hooks/session-start-lake.mjs +3 -16
- package/hub/bridge.mjs +111 -5
- package/hub/gemini-adapter.mjs +29 -11
- package/hub/lib/memory-store.mjs +66 -30
- package/hub/lib/tfx-route-args.mjs +41 -4
- package/hub/pipe.mjs +10 -15
- package/hub/reflexion.mjs +0 -57
- package/hub/router.mjs +202 -24
- package/hub/schema.sql +1 -0
- package/hub/server.mjs +172 -93
- package/hub/store.mjs +106 -33
- package/hub/team/backend.mjs +4 -2
- package/hub/team/claude-daemon-control.mjs +2 -0
- package/hub/team/cli/commands/start/start-headless.mjs +2 -0
- package/hub/team/cli/commands/start/start-mux.mjs +24 -3
- package/hub/team/cli/services/runtime-mode.mjs +23 -9
- package/hub/team/codex-review.mjs +2 -3
- package/hub/team/conductor.mjs +80 -24
- package/hub/team/cto-auto-collect.mjs +2 -6
- package/hub/team/cto-pull.mjs +4 -0
- package/hub/team/dashboard-open.mjs +1 -0
- package/hub/team/execution-mode.mjs +38 -2
- package/hub/team/headless.mjs +493 -82
- package/hub/team/interactive-tui-transport.mjs +15 -1
- package/hub/team/launcher-template.mjs +19 -2
- package/hub/team/orchestrator.mjs +36 -9
- package/hub/team/pane.mjs +176 -14
- package/hub/team/psmux.mjs +79 -70
- package/hub/team/retry-state-machine.mjs +39 -16
- package/hub/team/session.mjs +57 -0
- package/hub/team/terminal-opener.mjs +68 -13
- package/hub/team/tui-viewer.mjs +65 -14
- package/hub/tools.mjs +6 -0
- package/hub/workers/codex-mcp.mjs +34 -5
- package/hud/cli-policy.mjs +69 -0
- package/hud/constants.mjs +20 -17
- package/hud/context-monitor.mjs +31 -21
- package/hud/hud-qos-status.mjs +81 -91
- package/hud/providers/claude.mjs +150 -84
- package/hud/providers/codex.mjs +10 -1
- package/hud/renderers.mjs +21 -59
- package/hud/utils.mjs +1 -15
- package/package.json +1 -1
- package/scripts/__tests__/keyword-detector.test.mjs +89 -1
- package/scripts/__tests__/release-governance.test.mjs +94 -1
- package/scripts/__tests__/skill-surface.test.mjs +6 -4
- package/scripts/__tests__/tfx-route-bash-node-parity.test.mjs +6 -4
- package/scripts/__tests__/tfx-route-node-entry.test.mjs +23 -17
- package/scripts/claude-login-detect.mjs +107 -25
- package/scripts/ensure-agy-hooks.mjs +31 -1
- package/scripts/keyword-detector.mjs +11 -1
- package/scripts/lib/agent-route-policy.mjs +29 -22
- package/scripts/lib/cli-agy.mjs +24 -4
- package/scripts/lib/codex-profile-config.mjs +26 -8
- package/scripts/lib/codex-recovery.sh +19 -0
- package/scripts/lib/gemini-profiles.mjs +113 -29
- package/scripts/lib/keyword-rules.mjs +12 -0
- package/scripts/lib/logger.mjs +60 -43
- package/scripts/lib/machine-profile.mjs +293 -0
- package/scripts/lib/mcp-health.mjs +44 -18
- package/scripts/notion-read.mjs +2 -1
- package/scripts/release/check-packages-mirror.mjs +53 -1
- package/scripts/release/lib.mjs +1 -2
- package/scripts/release/prepare.mjs +24 -9
- package/scripts/remote-spawn.mjs +830 -32
- package/scripts/session-stale-cleanup.mjs +0 -1
- package/scripts/setup.mjs +474 -143
- package/scripts/sync-hub-mcp-settings.mjs +11 -3
- package/scripts/test-lock.mjs +15 -1
- package/scripts/tfx-gate-activate.mjs +4 -4
- package/scripts/tfx-route-post.mjs +90 -13
- package/scripts/tfx-route.sh +502 -126
- package/skills/merge-worktree/SKILL.md +3 -3
- package/skills/star-prompt/skill.json +1 -1
- package/skills/tfx-analysis/SKILL.md +8 -9
- package/skills/tfx-analysis/skill.json +1 -10
- package/skills/tfx-auto/SKILL.md +203 -46
- package/skills/tfx-auto/skill.json +2 -23
- package/skills/tfx-doctor/skill.json +0 -3
- package/skills/tfx-find/skill.json +0 -7
- package/skills/tfx-forge/SKILL.md +2 -2
- package/skills/tfx-forge/skill.json +0 -7
- package/skills/tfx-goal-clarify/skill.json +5 -0
- package/skills/tfx-harness/SKILL.md +26 -3
- package/skills/tfx-harness/skill.json +5 -0
- package/skills/tfx-hooks/SKILL.md +0 -2
- package/skills/tfx-hooks/skill.json +0 -3
- package/skills/tfx-hub/SKILL.md +1 -6
- package/skills/tfx-hub/skill.json +0 -3
- package/skills/tfx-index/skill.json +0 -6
- package/skills/tfx-interview/SKILL.md +0 -2
- package/skills/tfx-live/SKILL.md +22 -1
- package/skills/tfx-live/skill.json +0 -5
- package/skills/tfx-plan/SKILL.md +7 -8
- package/skills/tfx-plan/skill.json +1 -12
- package/skills/tfx-profile/SKILL.md +6 -6
- package/skills/tfx-profile/skill.json +0 -3
- package/skills/tfx-prune/SKILL.md +4 -6
- package/skills/tfx-qa/SKILL.md +7 -7
- package/skills/tfx-qa/skill.json +1 -11
- package/skills/tfx-ralph/SKILL.md +8 -11
- package/skills/tfx-remote/SKILL.md +2 -2
- package/skills/tfx-remote/skill.json +0 -3
- package/skills/tfx-research/SKILL.md +2 -2
- package/skills/tfx-research/skill.json +1 -16
- package/skills/tfx-review/SKILL.md +11 -11
- package/skills/tfx-review/skill.json +1 -11
- package/skills/tfx-setup/SKILL.md +5 -6
- package/skills/tfx-setup/skill.json +0 -3
- package/skills/tfx-ship/skill.json +1 -9
- package/skills/tfx-wt/skill.json +0 -7
- package/tui/codex-profile.mjs +2 -1
- package/tui/gemini-profile.mjs +11 -24
- package/tui/monitor.mjs +1 -0
- package/tui/setup.mjs +3 -3
- package/scripts/headless-guard-fast.sh +0 -29
- package/scripts/headless-guard.mjs +0 -523
- package/skills/tfx-autopilot/SKILL.md +0 -51
- package/skills/tfx-autopilot/skill.json +0 -12
- package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +0 -75
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/eval_metadata.json +0 -42
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +0 -39
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/analysis.md +0 -87
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/classification.md +0 -35
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/commands.sh +0 -275
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/routing.md +0 -56
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/timing.json +0 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +0 -39
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/analysis.md +0 -92
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/classification.md +0 -71
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/commands.sh +0 -264
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/routing.md +0 -113
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/timing.json +0 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/eval_metadata.json +0 -32
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +0 -29
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/analysis.md +0 -96
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/classification.md +0 -38
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/commands.sh +0 -151
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/routing.md +0 -51
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/timing.json +0 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +0 -29
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/analysis.md +0 -127
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/classification.md +0 -57
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/commands.sh +0 -129
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/routing.md +0 -84
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/timing.json +0 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/eval_metadata.json +0 -27
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +0 -24
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/analysis.md +0 -98
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/classification.md +0 -65
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/commands.sh +0 -123
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/routing.md +0 -66
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/timing.json +0 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +0 -20
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/analysis.md +0 -88
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/classification.md +0 -40
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/commands.sh +0 -130
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/routing.md +0 -61
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/timing.json +0 -5
- package/skills/tfx-consensus/SKILL.md +0 -56
- package/skills/tfx-consensus/skill.json +0 -8
- package/skills/tfx-debate/SKILL.md +0 -60
- package/skills/tfx-debate/skill.json +0 -14
- package/skills/tfx-fullcycle/SKILL.md +0 -47
- package/skills/tfx-fullcycle/skill.json +0 -13
- package/skills/tfx-multi/SKILL.md +0 -45
- package/skills/tfx-multi/references/agent-wrapper-rules.md +0 -94
- package/skills/tfx-multi/references/thorough-pipeline.md +0 -105
- package/skills/tfx-multi/skill.json +0 -10
- package/skills/tfx-panel/SKILL.md +0 -60
- package/skills/tfx-panel/skill.json +0 -14
- package/skills/tfx-persist/SKILL.md +0 -63
- package/skills/tfx-persist/skill.json +0 -14
- package/skills/tfx-psmux-rules/SKILL.md +0 -41
- package/skills/tfx-psmux-rules/skill.json +0 -10
- package/skills/tfx-remote-setup/SKILL.md +0 -42
- package/skills/tfx-remote-setup/skill.json +0 -10
- package/skills/tfx-remote-spawn/SKILL.md +0 -52
- package/skills/tfx-remote-spawn/skill.json +0 -10
- package/skills/tfx-swarm/SKILL.md +0 -49
- package/skills/tfx-swarm/skill.json +0 -13
package/hub/router.mjs
CHANGED
|
@@ -376,6 +376,9 @@ export function createRouter(store) {
|
|
|
376
376
|
const queuesByAgent = new Map();
|
|
377
377
|
const liveMessages = new Map();
|
|
378
378
|
const roleStates = new Map();
|
|
379
|
+
const hubInstanceId = uuidv7();
|
|
380
|
+
const storeFingerprint =
|
|
381
|
+
store.fingerprint || `${store.type || "unknown"}:unknown`;
|
|
379
382
|
const updateRegistrationIdentity = store.db?.prepare
|
|
380
383
|
? store.db.prepare(`
|
|
381
384
|
UPDATE agents SET
|
|
@@ -392,6 +395,70 @@ export function createRouter(store) {
|
|
|
392
395
|
let latencyIdx = 0;
|
|
393
396
|
const deliveryLatencies = new Array(MAX_LATENCY_SAMPLES).fill(0);
|
|
394
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
|
+
|
|
395
462
|
function ensureAgentQueue(agentId) {
|
|
396
463
|
let queue = queuesByAgent.get(agentId);
|
|
397
464
|
if (!queue) {
|
|
@@ -1120,6 +1187,18 @@ export function createRouter(store) {
|
|
|
1120
1187
|
deliveryEmitter,
|
|
1121
1188
|
|
|
1122
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
|
+
}
|
|
1123
1202
|
const identity = normalizeRegistrationIdentity(args);
|
|
1124
1203
|
const result = store.registerAgent(args);
|
|
1125
1204
|
if (!registerPresenceIsEffective(result)) {
|
|
@@ -1139,11 +1218,23 @@ export function createRouter(store) {
|
|
|
1139
1218
|
transferBacklog: true,
|
|
1140
1219
|
});
|
|
1141
1220
|
}
|
|
1142
|
-
return {
|
|
1221
|
+
return {
|
|
1222
|
+
ok: true,
|
|
1223
|
+
data: { ...result, hub_pid: process.pid, ...hubIdentity() },
|
|
1224
|
+
};
|
|
1143
1225
|
},
|
|
1144
1226
|
|
|
1145
|
-
refreshAgentLease(
|
|
1146
|
-
|
|
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);
|
|
1147
1238
|
refreshRoleCandidateForAgent(agentId);
|
|
1148
1239
|
for (const roleName of ROLE_TOPICS) {
|
|
1149
1240
|
ensureRoleLeader(roleName, {
|
|
@@ -1151,7 +1242,7 @@ export function createRouter(store) {
|
|
|
1151
1242
|
transferBacklog: true,
|
|
1152
1243
|
});
|
|
1153
1244
|
}
|
|
1154
|
-
return result;
|
|
1245
|
+
return { ok: true, data: result };
|
|
1155
1246
|
},
|
|
1156
1247
|
|
|
1157
1248
|
subscribeAgent(agentId, topics, { replace = false } = {}) {
|
|
@@ -1170,11 +1261,15 @@ export function createRouter(store) {
|
|
|
1170
1261
|
return listRuntimeTopics(agentId);
|
|
1171
1262
|
},
|
|
1172
1263
|
|
|
1173
|
-
updateAgentStatus(agentId, status) {
|
|
1264
|
+
updateAgentStatus(agentId, status, presenceGeneration) {
|
|
1174
1265
|
if (status === "offline") {
|
|
1175
1266
|
runtimeTopics.delete(agentId);
|
|
1176
1267
|
}
|
|
1177
|
-
const updated = store.updateAgentStatus(
|
|
1268
|
+
const updated = store.updateAgentStatus(
|
|
1269
|
+
agentId,
|
|
1270
|
+
status,
|
|
1271
|
+
presenceGeneration,
|
|
1272
|
+
);
|
|
1178
1273
|
refreshRoleCandidateForAgent(agentId);
|
|
1179
1274
|
for (const roleName of ROLE_TOPICS) {
|
|
1180
1275
|
ensureRoleLeader(roleName, {
|
|
@@ -1185,6 +1280,39 @@ export function createRouter(store) {
|
|
|
1185
1280
|
return updated;
|
|
1186
1281
|
},
|
|
1187
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
|
+
|
|
1188
1316
|
route(msg) {
|
|
1189
1317
|
const recipients = uniqueStrings(resolveRecipients(msg));
|
|
1190
1318
|
if (!recipients.length) {
|
|
@@ -1369,7 +1497,14 @@ export function createRouter(store) {
|
|
|
1369
1497
|
agent_id,
|
|
1370
1498
|
reason = "manual",
|
|
1371
1499
|
requested_by = "manual",
|
|
1500
|
+
hub_instance_id,
|
|
1501
|
+
store_fingerprint,
|
|
1372
1502
|
} = {}) {
|
|
1503
|
+
const identityFailure = validateCallerIdentity({
|
|
1504
|
+
hub_instance_id,
|
|
1505
|
+
store_fingerprint,
|
|
1506
|
+
});
|
|
1507
|
+
if (identityFailure) return identityFailure;
|
|
1373
1508
|
const roleName = normalizeRoleName(role);
|
|
1374
1509
|
if (!roleName) {
|
|
1375
1510
|
return {
|
|
@@ -1440,6 +1575,7 @@ export function createRouter(store) {
|
|
|
1440
1575
|
leader_epoch: snapshot.leader_epoch,
|
|
1441
1576
|
...transfer,
|
|
1442
1577
|
status: snapshot,
|
|
1578
|
+
...hubIdentity(),
|
|
1443
1579
|
},
|
|
1444
1580
|
};
|
|
1445
1581
|
},
|
|
@@ -1647,18 +1783,15 @@ export function createRouter(store) {
|
|
|
1647
1783
|
return scheduleAssignRetry(job, reason, job.error, requested_by);
|
|
1648
1784
|
},
|
|
1649
1785
|
|
|
1650
|
-
sweepExpired() {
|
|
1651
|
-
const
|
|
1652
|
-
let expired = 0;
|
|
1653
|
-
const deadLettered = [];
|
|
1786
|
+
sweepExpired({ now_ms = Date.now(), source = "manual" } = {}) {
|
|
1787
|
+
const result = store.sweepExpired({ now_ms });
|
|
1654
1788
|
for (const [messageId, record] of Array.from(liveMessages.entries())) {
|
|
1655
|
-
if (record.message.expires_at_ms >
|
|
1656
|
-
store.moveToDeadLetter(messageId, "ttl_expired", null);
|
|
1657
|
-
deadLettered.push(record.message);
|
|
1789
|
+
if (record.message.expires_at_ms > now_ms) continue;
|
|
1658
1790
|
removeMessage(messageId);
|
|
1659
|
-
expired += 1;
|
|
1660
1791
|
}
|
|
1661
|
-
for (const
|
|
1792
|
+
for (const transition of result.transitions) {
|
|
1793
|
+
if (!transition.dlq_inserted) continue;
|
|
1794
|
+
const { message } = transition;
|
|
1662
1795
|
if (
|
|
1663
1796
|
!["handoff", "request"].includes(message.type) ||
|
|
1664
1797
|
message.payload?.kind === "assign.job"
|
|
@@ -1683,12 +1816,15 @@ export function createRouter(store) {
|
|
|
1683
1816
|
original_to: message.to_agent,
|
|
1684
1817
|
reason: "ttl_expired",
|
|
1685
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,
|
|
1686
1822
|
},
|
|
1687
1823
|
trace_id: message.trace_id,
|
|
1688
1824
|
correlation_id: message.correlation_id,
|
|
1689
1825
|
});
|
|
1690
1826
|
}
|
|
1691
|
-
return
|
|
1827
|
+
return result;
|
|
1692
1828
|
},
|
|
1693
1829
|
|
|
1694
1830
|
sweepTimedOutAssigns() {
|
|
@@ -1709,19 +1845,49 @@ export function createRouter(store) {
|
|
|
1709
1845
|
return { timed_out, retried };
|
|
1710
1846
|
},
|
|
1711
1847
|
|
|
1712
|
-
startSweeper(
|
|
1848
|
+
startSweeper({
|
|
1849
|
+
run_immediately = false,
|
|
1850
|
+
interval_ms = 10000,
|
|
1851
|
+
onSweepError,
|
|
1852
|
+
} = {}) {
|
|
1713
1853
|
if (sweepTimer) return;
|
|
1714
|
-
|
|
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
|
+
}
|
|
1715
1869
|
try {
|
|
1716
|
-
router.sweepExpired();
|
|
1717
1870
|
router.sweepTimedOutAssigns();
|
|
1718
|
-
|
|
1719
|
-
|
|
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;
|
|
1720
1882
|
staleTimer = setInterval(() => {
|
|
1721
1883
|
try {
|
|
1722
1884
|
store.sweepStaleAgents();
|
|
1723
1885
|
router.reelectStaleRoles();
|
|
1724
|
-
|
|
1886
|
+
staleFailures = 0;
|
|
1887
|
+
} catch (error) {
|
|
1888
|
+
staleFailures += 1;
|
|
1889
|
+
reportSweepError("stale_agents", error, staleFailures);
|
|
1890
|
+
}
|
|
1725
1891
|
}, 120000);
|
|
1726
1892
|
sweepTimer.unref();
|
|
1727
1893
|
staleTimer.unref();
|
|
@@ -1777,8 +1943,19 @@ export function createRouter(store) {
|
|
|
1777
1943
|
|
|
1778
1944
|
getStatus(
|
|
1779
1945
|
scope = "hub",
|
|
1780
|
-
{
|
|
1946
|
+
{
|
|
1947
|
+
agent_id,
|
|
1948
|
+
trace_id,
|
|
1949
|
+
include_metrics = true,
|
|
1950
|
+
hub_instance_id,
|
|
1951
|
+
store_fingerprint,
|
|
1952
|
+
} = {},
|
|
1781
1953
|
) {
|
|
1954
|
+
const identityFailure = validateCallerIdentity({
|
|
1955
|
+
hub_instance_id,
|
|
1956
|
+
store_fingerprint,
|
|
1957
|
+
});
|
|
1958
|
+
if (identityFailure) return identityFailure;
|
|
1782
1959
|
const data = {};
|
|
1783
1960
|
|
|
1784
1961
|
if (scope === "hub" || scope === "queue") {
|
|
@@ -1787,6 +1964,7 @@ export function createRouter(store) {
|
|
|
1787
1964
|
uptime_ms: (process.uptime() * 1000) | 0,
|
|
1788
1965
|
realtime_transport: "named-pipe",
|
|
1789
1966
|
audit_store: store.type || "sqlite",
|
|
1967
|
+
...hubIdentity(),
|
|
1790
1968
|
};
|
|
1791
1969
|
data.roles = {
|
|
1792
1970
|
cto: getRoleSnapshot("cto"),
|
|
@@ -1840,7 +2018,7 @@ export function createRouter(store) {
|
|
|
1840
2018
|
data.trace = store.getMessagesByTrace(trace_id);
|
|
1841
2019
|
}
|
|
1842
2020
|
|
|
1843
|
-
return { ok: true, data };
|
|
2021
|
+
return { ok: true, data: { ...data, ...hubIdentity() } };
|
|
1844
2022
|
},
|
|
1845
2023
|
};
|
|
1846
2024
|
|
package/hub/schema.sql
CHANGED
|
@@ -10,6 +10,7 @@ CREATE TABLE IF NOT EXISTS agents (
|
|
|
10
10
|
topics_json TEXT NOT NULL DEFAULT '[]',
|
|
11
11
|
last_seen_ms INTEGER NOT NULL,
|
|
12
12
|
lease_expires_ms INTEGER NOT NULL,
|
|
13
|
+
presence_generation INTEGER NOT NULL DEFAULT 0,
|
|
13
14
|
status TEXT NOT NULL CHECK (status IN ('online','stale','offline')),
|
|
14
15
|
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
15
16
|
project_id TEXT,
|