triflux 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.
- package/README.md +3 -1
- package/adapters/codex/skills/tfx-harness/SKILL.md +66 -0
- package/adapters/codex/skills/tfx-harness/skill.json +5 -0
- package/bin/tfx-live.mjs +752 -77
- package/bin/triflux.mjs +20 -3
- 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 +147 -23
- 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/cli-adapter-base.mjs +11 -2
- package/hub/codex-adapter.mjs +3 -0
- package/hub/gemini-adapter.mjs +29 -11
- package/hub/intent.mjs +24 -32
- 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 +474 -24
- package/hub/schema.sql +62 -2
- package/hub/server.mjs +214 -93
- package/hub/store.mjs +682 -37
- 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 +101 -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 +50 -4
- package/hub/team/headless.mjs +505 -78
- 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 +41 -13
- package/hub/team/session.mjs +57 -0
- package/hub/team/swarm-hypervisor.mjs +1 -0
- package/hub/team/swarm-preflight.mjs +47 -0
- package/hub/team/synapse-http.mjs +149 -4
- package/hub/team/terminal-opener.mjs +68 -13
- package/hub/team/tui-viewer.mjs +65 -14
- package/hub/team/worktree-lifecycle.mjs +56 -59
- package/hub/tools.mjs +17 -0
- package/hub/workers/codex-mcp.mjs +52 -7
- package/hub/workers/delegator-mcp.mjs +3 -49
- 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 +2 -1
- package/scripts/__tests__/keyword-detector.test.mjs +89 -1
- package/scripts/__tests__/lint-skills.test.mjs +68 -0
- 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 +53 -11
- package/scripts/claude-login-detect.mjs +107 -25
- package/scripts/ensure-agy-hooks.mjs +31 -1
- package/scripts/keyword-detector.mjs +66 -9
- package/scripts/lib/agent-route-policy.mjs +367 -0
- package/scripts/lib/cli-agy.mjs +24 -4
- package/scripts/lib/cli-codex.mjs +22 -160
- package/scripts/lib/codex-profile-config.mjs +48 -4
- package/scripts/lib/codex-recovery.sh +19 -0
- package/scripts/lib/gemini-profiles.mjs +113 -29
- package/scripts/lib/keyword-rules.mjs +24 -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/lint-skills.mjs +65 -0
- package/scripts/notion-read.mjs +2 -1
- package/scripts/pack.mjs +13 -0
- package/scripts/release/check-packages-mirror.mjs +58 -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 +548 -125
- 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.mjs +14 -1
- package/scripts/tfx-route.sh +716 -177
- 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 +36 -70
- 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 +2 -7
- package/skills/tfx-hub/skill.json +0 -3
- package/skills/tfx-index/skill.json +0 -6
- package/skills/tfx-interview/SKILL.md +3 -3
- package/skills/tfx-interview/skill.json +1 -13
- 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 +25 -15
- package/skills/tfx-profile/skill.json +0 -3
- package/skills/tfx-prune/SKILL.md +7 -7
- package/skills/tfx-prune/skill.json +1 -8
- 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 +13 -3
- package/tui/gemini-profile.mjs +11 -24
- package/tui/monitor.mjs +1 -0
- package/tui/setup.mjs +3 -1
- 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/schema.sql
CHANGED
|
@@ -10,8 +10,13 @@ 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
|
-
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
15
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
16
|
+
project_id TEXT,
|
|
17
|
+
session_id TEXT,
|
|
18
|
+
host_id TEXT,
|
|
19
|
+
transport_locators_json TEXT NOT NULL DEFAULT '[]'
|
|
15
20
|
);
|
|
16
21
|
|
|
17
22
|
-- 메시지 테이블
|
|
@@ -28,7 +33,61 @@ CREATE TABLE IF NOT EXISTS messages (
|
|
|
28
33
|
correlation_id TEXT NOT NULL,
|
|
29
34
|
trace_id TEXT NOT NULL,
|
|
30
35
|
payload_json TEXT NOT NULL DEFAULT '{}',
|
|
31
|
-
status TEXT NOT NULL CHECK (status IN ('queued','delivered','acked','expired','dead_letter'))
|
|
36
|
+
status TEXT NOT NULL CHECK (status IN ('queued','delivered','acked','expired','dead_letter')),
|
|
37
|
+
role_key_wire TEXT
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
-- 동적 역할 레지스트리 (schema v6)
|
|
41
|
+
CREATE TABLE IF NOT EXISTS role_registry (
|
|
42
|
+
role_key_wire TEXT PRIMARY KEY,
|
|
43
|
+
project_id TEXT NOT NULL,
|
|
44
|
+
role_kind TEXT NOT NULL CHECK (role_kind IN ('cto','lead')),
|
|
45
|
+
scope_id TEXT NOT NULL DEFAULT '',
|
|
46
|
+
state TEXT NOT NULL CHECK (
|
|
47
|
+
state IN (
|
|
48
|
+
'electable',
|
|
49
|
+
'elected-probing',
|
|
50
|
+
'active',
|
|
51
|
+
'unreachable',
|
|
52
|
+
'quarantined'
|
|
53
|
+
)
|
|
54
|
+
),
|
|
55
|
+
holder_agent_id TEXT,
|
|
56
|
+
previous_holder_agent_id TEXT,
|
|
57
|
+
epoch INTEGER NOT NULL DEFAULT 0 CHECK (epoch >= 0),
|
|
58
|
+
activation_seq INTEGER NOT NULL DEFAULT 0 CHECK (activation_seq >= 0),
|
|
59
|
+
activation_id TEXT,
|
|
60
|
+
activation_deadline_ms INTEGER,
|
|
61
|
+
holder_lease_expires_ms INTEGER,
|
|
62
|
+
charter_version TEXT,
|
|
63
|
+
charter_acked_epoch INTEGER,
|
|
64
|
+
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
65
|
+
next_probe_ms INTEGER,
|
|
66
|
+
blocked_reason TEXT,
|
|
67
|
+
last_transition_ms INTEGER NOT NULL,
|
|
68
|
+
last_reason TEXT NOT NULL,
|
|
69
|
+
version INTEGER NOT NULL DEFAULT 0,
|
|
70
|
+
CHECK (
|
|
71
|
+
(role_kind = 'cto' AND scope_id = '') OR
|
|
72
|
+
(role_kind = 'lead' AND scope_id <> '')
|
|
73
|
+
),
|
|
74
|
+
UNIQUE(project_id, role_kind, scope_id)
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
CREATE TABLE IF NOT EXISTS role_candidates (
|
|
78
|
+
role_key_wire TEXT NOT NULL,
|
|
79
|
+
agent_id TEXT NOT NULL,
|
|
80
|
+
source TEXT NOT NULL CHECK (source IN ('grant','standup','operator')),
|
|
81
|
+
priority INTEGER NOT NULL DEFAULT 0,
|
|
82
|
+
transport_locators_json TEXT NOT NULL DEFAULT '[]',
|
|
83
|
+
consecutive_probe_failures INTEGER NOT NULL DEFAULT 0,
|
|
84
|
+
excluded_until_ms INTEGER,
|
|
85
|
+
last_probe_ms INTEGER,
|
|
86
|
+
last_probe_code TEXT,
|
|
87
|
+
updated_at_ms INTEGER NOT NULL,
|
|
88
|
+
PRIMARY KEY(role_key_wire, agent_id),
|
|
89
|
+
FOREIGN KEY(role_key_wire)
|
|
90
|
+
REFERENCES role_registry(role_key_wire) ON DELETE CASCADE
|
|
32
91
|
);
|
|
33
92
|
|
|
34
93
|
-- 메시지 수신함 (배달 추적)
|
|
@@ -73,6 +132,7 @@ CREATE INDEX IF NOT EXISTS idx_messages_correlation ON messages(correlation_id);
|
|
|
73
132
|
CREATE INDEX IF NOT EXISTS idx_messages_trace ON messages(trace_id);
|
|
74
133
|
CREATE INDEX IF NOT EXISTS idx_messages_expires ON messages(expires_at_ms);
|
|
75
134
|
CREATE INDEX IF NOT EXISTS idx_messages_priority ON messages(priority DESC, created_at_ms ASC);
|
|
135
|
+
CREATE INDEX IF NOT EXISTS idx_messages_role_key ON messages(role_key_wire, status, expires_at_ms);
|
|
76
136
|
CREATE INDEX IF NOT EXISTS idx_inbox_agent ON message_inbox(agent_id, delivered_at_ms);
|
|
77
137
|
CREATE INDEX IF NOT EXISTS idx_inbox_message ON message_inbox(message_id);
|
|
78
138
|
CREATE INDEX IF NOT EXISTS idx_human_requests_state ON human_requests(state);
|
package/hub/server.mjs
CHANGED
|
@@ -961,6 +961,7 @@ function servePublicFile(res, path) {
|
|
|
961
961
|
* @param {string} [opts.host]
|
|
962
962
|
* @param {string|number} [opts.sessionId]
|
|
963
963
|
* @param {(options: { cwd: string }) => object} [opts.createDelegatorWorker]
|
|
964
|
+
* @param {(dbPath: string) => object|Promise<object>} [opts.createStoreAdapterFn] test seam
|
|
964
965
|
*/
|
|
965
966
|
export async function startHub({
|
|
966
967
|
port: portOpt,
|
|
@@ -968,6 +969,7 @@ export async function startHub({
|
|
|
968
969
|
host = "127.0.0.1",
|
|
969
970
|
sessionId = process.pid,
|
|
970
971
|
createDelegatorWorker = createDelegatorMcpWorker,
|
|
972
|
+
createStoreAdapterFn = createStoreAdapter,
|
|
971
973
|
} = {}) {
|
|
972
974
|
const resolvedPort = parseHubPort(portOpt);
|
|
973
975
|
const portSpecified = Number.isFinite(resolvedPort);
|
|
@@ -1062,7 +1064,7 @@ export async function startHub({
|
|
|
1062
1064
|
cleanupOwnedTokenFile({ token: HUB_TOKEN, port });
|
|
1063
1065
|
}
|
|
1064
1066
|
|
|
1065
|
-
const store = await
|
|
1067
|
+
const store = await createStoreAdapterFn(dbPath);
|
|
1066
1068
|
const router = createRouter(store);
|
|
1067
1069
|
const fingerprintService = createAdaptiveFingerprintService({ store });
|
|
1068
1070
|
|
|
@@ -1558,6 +1560,38 @@ export async function startHub({
|
|
|
1558
1560
|
try {
|
|
1559
1561
|
const body = await parseBody(req);
|
|
1560
1562
|
const { sessionId } = body || {};
|
|
1563
|
+
if (body?.agent_id && body?.cli) {
|
|
1564
|
+
const registered = router.registerAgent({
|
|
1565
|
+
agent_id: body.agent_id,
|
|
1566
|
+
cli: body.cli,
|
|
1567
|
+
capabilities: Array.isArray(body.capabilities)
|
|
1568
|
+
? body.capabilities
|
|
1569
|
+
: [],
|
|
1570
|
+
topics: Array.isArray(body.topics) ? body.topics : [],
|
|
1571
|
+
heartbeat_ttl_ms: body.heartbeat_ttl_ms ?? 300000,
|
|
1572
|
+
metadata:
|
|
1573
|
+
body.metadata && typeof body.metadata === "object"
|
|
1574
|
+
? body.metadata
|
|
1575
|
+
: { source: "session_start" },
|
|
1576
|
+
...(Object.hasOwn(body, "project_id")
|
|
1577
|
+
? { project_id: body.project_id }
|
|
1578
|
+
: {}),
|
|
1579
|
+
...(Object.hasOwn(body, "session_id")
|
|
1580
|
+
? { session_id: body.session_id }
|
|
1581
|
+
: {}),
|
|
1582
|
+
...(Object.hasOwn(body, "host_id")
|
|
1583
|
+
? { host_id: body.host_id }
|
|
1584
|
+
: {}),
|
|
1585
|
+
...(Object.hasOwn(body, "transport_locators_json")
|
|
1586
|
+
? { transport_locators_json: body.transport_locators_json }
|
|
1587
|
+
: {}),
|
|
1588
|
+
});
|
|
1589
|
+
if (!registered?.ok) {
|
|
1590
|
+
throw new Error(
|
|
1591
|
+
registered?.error?.message || "hub agent register failed",
|
|
1592
|
+
);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1561
1595
|
const result = synapseRegistry.register(sessionId, body);
|
|
1562
1596
|
if (!result?.ok) {
|
|
1563
1597
|
throw new Error(result?.reason || "register failed");
|
|
@@ -1688,6 +1722,8 @@ export async function startHub({
|
|
|
1688
1722
|
agent_id,
|
|
1689
1723
|
reason = "manual",
|
|
1690
1724
|
requested_by = "http",
|
|
1725
|
+
hub_instance_id,
|
|
1726
|
+
store_fingerprint,
|
|
1691
1727
|
} = body;
|
|
1692
1728
|
if (!role || !agent_id) {
|
|
1693
1729
|
return writeJson(res, 400, {
|
|
@@ -1700,6 +1736,8 @@ export async function startHub({
|
|
|
1700
1736
|
agent_id,
|
|
1701
1737
|
reason,
|
|
1702
1738
|
requested_by,
|
|
1739
|
+
hub_instance_id,
|
|
1740
|
+
store_fingerprint,
|
|
1703
1741
|
});
|
|
1704
1742
|
return writeJson(res, result.ok ? 200 : 409, result);
|
|
1705
1743
|
}
|
|
@@ -1712,6 +1750,12 @@ export async function startHub({
|
|
|
1712
1750
|
topics = [],
|
|
1713
1751
|
capabilities = [],
|
|
1714
1752
|
metadata = {},
|
|
1753
|
+
project_id,
|
|
1754
|
+
session_id,
|
|
1755
|
+
host_id,
|
|
1756
|
+
transport_locators_json,
|
|
1757
|
+
hub_instance_id,
|
|
1758
|
+
store_fingerprint,
|
|
1715
1759
|
} = body;
|
|
1716
1760
|
if (!agent_id || !cli) {
|
|
1717
1761
|
return writeJson(res, 400, {
|
|
@@ -1740,6 +1784,14 @@ export async function startHub({
|
|
|
1740
1784
|
topics,
|
|
1741
1785
|
heartbeat_ttl_ms,
|
|
1742
1786
|
metadata,
|
|
1787
|
+
...(project_id === undefined ? {} : { project_id }),
|
|
1788
|
+
...(session_id === undefined ? {} : { session_id }),
|
|
1789
|
+
...(host_id === undefined ? {} : { host_id }),
|
|
1790
|
+
...(transport_locators_json === undefined
|
|
1791
|
+
? {}
|
|
1792
|
+
: { transport_locators_json }),
|
|
1793
|
+
...(hub_instance_id === undefined ? {} : { hub_instance_id }),
|
|
1794
|
+
...(store_fingerprint === undefined ? {} : { store_fingerprint }),
|
|
1743
1795
|
});
|
|
1744
1796
|
if (result.ok) {
|
|
1745
1797
|
workerSpans.set(agent_id, {
|
|
@@ -1774,7 +1826,13 @@ export async function startHub({
|
|
|
1774
1826
|
}
|
|
1775
1827
|
|
|
1776
1828
|
if (path === "/bridge/heartbeat" && req.method === "POST") {
|
|
1777
|
-
const {
|
|
1829
|
+
const {
|
|
1830
|
+
agent_id,
|
|
1831
|
+
ttl_ms,
|
|
1832
|
+
presence_generation,
|
|
1833
|
+
hub_instance_id,
|
|
1834
|
+
store_fingerprint,
|
|
1835
|
+
} = body;
|
|
1778
1836
|
if (!agent_id)
|
|
1779
1837
|
return writeJson(res, 400, { ok: false, error: "agent_id 필수" });
|
|
1780
1838
|
const result = await pipe.executeCommand("heartbeat", {
|
|
@@ -1783,6 +1841,9 @@ export async function startHub({
|
|
|
1783
1841
|
ttl_ms,
|
|
1784
1842
|
resolveWorkerLeaseTtlMs(),
|
|
1785
1843
|
),
|
|
1844
|
+
presence_generation,
|
|
1845
|
+
hub_instance_id,
|
|
1846
|
+
store_fingerprint,
|
|
1786
1847
|
});
|
|
1787
1848
|
return writeJson(res, result.ok ? 200 : 404, result);
|
|
1788
1849
|
}
|
|
@@ -1863,6 +1924,9 @@ export async function startHub({
|
|
|
1863
1924
|
status,
|
|
1864
1925
|
include_metrics = true,
|
|
1865
1926
|
trace_id,
|
|
1927
|
+
presence_generation,
|
|
1928
|
+
hub_instance_id,
|
|
1929
|
+
store_fingerprint,
|
|
1866
1930
|
} = body;
|
|
1867
1931
|
|
|
1868
1932
|
if (agent_id && status) {
|
|
@@ -1883,7 +1947,15 @@ export async function startHub({
|
|
|
1883
1947
|
]).has(normalizedStatus)
|
|
1884
1948
|
? normalizedStatus
|
|
1885
1949
|
: "online";
|
|
1886
|
-
router.
|
|
1950
|
+
const transition = router.transitionAgentStatus(
|
|
1951
|
+
normalizedAgentId,
|
|
1952
|
+
statusForStore,
|
|
1953
|
+
presence_generation,
|
|
1954
|
+
{ hub_instance_id, store_fingerprint },
|
|
1955
|
+
);
|
|
1956
|
+
if (!transition.ok) {
|
|
1957
|
+
return writeJson(res, 409, transition);
|
|
1958
|
+
}
|
|
1887
1959
|
const snapshot = await pipe.executeQuery("status", {
|
|
1888
1960
|
scope: "agent",
|
|
1889
1961
|
agent_id: normalizedAgentId,
|
|
@@ -1897,6 +1969,8 @@ export async function startHub({
|
|
|
1897
1969
|
reported_status: normalizedStatus,
|
|
1898
1970
|
reported_at_ms: Date.now(),
|
|
1899
1971
|
snapshot: snapshot?.data?.agent || null,
|
|
1972
|
+
hub_instance_id: transition.data.hub_instance_id,
|
|
1973
|
+
store_fingerprint: transition.data.store_fingerprint,
|
|
1900
1974
|
},
|
|
1901
1975
|
});
|
|
1902
1976
|
}
|
|
@@ -1906,6 +1980,8 @@ export async function startHub({
|
|
|
1906
1980
|
agent_id,
|
|
1907
1981
|
include_metrics,
|
|
1908
1982
|
trace_id,
|
|
1983
|
+
hub_instance_id,
|
|
1984
|
+
store_fingerprint,
|
|
1909
1985
|
});
|
|
1910
1986
|
return writeJson(res, 200, result);
|
|
1911
1987
|
}
|
|
@@ -2125,12 +2201,20 @@ export async function startHub({
|
|
|
2125
2201
|
}
|
|
2126
2202
|
|
|
2127
2203
|
if (path === "/bridge/deregister" && req.method === "POST") {
|
|
2128
|
-
const {
|
|
2204
|
+
const {
|
|
2205
|
+
agent_id,
|
|
2206
|
+
presence_generation,
|
|
2207
|
+
hub_instance_id,
|
|
2208
|
+
store_fingerprint,
|
|
2209
|
+
} = body;
|
|
2129
2210
|
if (!agent_id) {
|
|
2130
2211
|
return writeJson(res, 400, { ok: false, error: "agent_id 필수" });
|
|
2131
2212
|
}
|
|
2132
2213
|
const result = await pipe.executeCommand("deregister", {
|
|
2133
2214
|
agent_id,
|
|
2215
|
+
presence_generation,
|
|
2216
|
+
hub_instance_id,
|
|
2217
|
+
store_fingerprint,
|
|
2134
2218
|
});
|
|
2135
2219
|
if (result.ok) {
|
|
2136
2220
|
finishWorkerTrace(agent_id, "deregister");
|
|
@@ -2274,102 +2358,107 @@ export async function startHub({
|
|
|
2274
2358
|
httpServer.requestTimeout = 30000;
|
|
2275
2359
|
httpServer.headersTimeout = 10000;
|
|
2276
2360
|
|
|
2277
|
-
|
|
2361
|
+
let hitlTimer = null;
|
|
2362
|
+
let sessionTimer = null;
|
|
2363
|
+
let synapsePruneTimer = null;
|
|
2364
|
+
let orphanCleanupTimer = null;
|
|
2365
|
+
let rateLimitTimer = null;
|
|
2278
2366
|
|
|
2279
|
-
const
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2367
|
+
const startBackgroundTimers = () => {
|
|
2368
|
+
hitlTimer = setInterval(() => {
|
|
2369
|
+
try {
|
|
2370
|
+
hitl.checkTimeouts();
|
|
2371
|
+
} catch (err) {
|
|
2372
|
+
hubLog.warn({ err }, "hitl.timeout_check_failed");
|
|
2373
|
+
}
|
|
2374
|
+
}, 10000);
|
|
2375
|
+
hitlTimer.unref();
|
|
2376
|
+
|
|
2377
|
+
// MCP session TTL: sessions idle for SESSION_TTL_MS are closed automatically.
|
|
2378
|
+
// Configurable via SESSION_TTL_MS (default 30 minutes). The sweep runs every 60 s.
|
|
2379
|
+
const SESSION_TTL_MS =
|
|
2380
|
+
parseInt(process.env.TFX_SESSION_TTL_MS || "", 10) || 30 * 60 * 1000;
|
|
2381
|
+
sessionTimer = setInterval(() => {
|
|
2382
|
+
const now = Date.now();
|
|
2383
|
+
for (const [sid, session] of transports) {
|
|
2384
|
+
if (now - (session.transport._lastActivity || 0) <= SESSION_TTL_MS)
|
|
2385
|
+
continue;
|
|
2386
|
+
void closeMcpTransportSession(sid, session, "session_ttl");
|
|
2387
|
+
}
|
|
2388
|
+
}, 60000);
|
|
2389
|
+
sessionTimer.unref();
|
|
2301
2390
|
|
|
2302
|
-
|
|
2303
|
-
try {
|
|
2304
|
-
const { count } = synapseRegistry.pruneExpired();
|
|
2305
|
-
if (count > 0) hubLog.info({ count }, "synapse.prune");
|
|
2306
|
-
} catch (err) {
|
|
2307
|
-
hubLog.warn({ err }, "synapse.prune_failed");
|
|
2308
|
-
}
|
|
2309
|
-
}, 60000);
|
|
2310
|
-
synapsePruneTimer.unref();
|
|
2311
|
-
|
|
2312
|
-
// 고아 node.exe 프로세스 + stale spawn 세션 주기적 정리 (5분마다)
|
|
2313
|
-
// TFX_DISABLE_ORPHAN_CLEANUP=1 로 비활성 (active SSH/swarm 세션 보호 회피용 hotfix gate)
|
|
2314
|
-
const orphanCleanupTimer = setInterval(
|
|
2315
|
-
() => {
|
|
2316
|
-
if (process.env.TFX_DISABLE_ORPHAN_CLEANUP === "1") return;
|
|
2391
|
+
synapsePruneTimer = setInterval(() => {
|
|
2317
2392
|
try {
|
|
2318
|
-
const {
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
);
|
|
2333
|
-
|
|
2393
|
+
const { count } = synapseRegistry.pruneExpired();
|
|
2394
|
+
if (count > 0) hubLog.info({ count }, "synapse.prune");
|
|
2395
|
+
} catch (err) {
|
|
2396
|
+
hubLog.warn({ err }, "synapse.prune_failed");
|
|
2397
|
+
}
|
|
2398
|
+
}, 60000);
|
|
2399
|
+
synapsePruneTimer.unref();
|
|
2400
|
+
|
|
2401
|
+
// 고아 node.exe 프로세스 + stale spawn 세션 주기적 정리 (5분마다)
|
|
2402
|
+
// TFX_DISABLE_ORPHAN_CLEANUP=1 로 비활성 (active SSH/swarm 세션 보호 회피용 hotfix gate)
|
|
2403
|
+
orphanCleanupTimer = setInterval(
|
|
2404
|
+
() => {
|
|
2405
|
+
if (process.env.TFX_DISABLE_ORPHAN_CLEANUP === "1") return;
|
|
2406
|
+
try {
|
|
2407
|
+
const { killed, killedProcesses = [] } = cleanupOrphanNodeProcesses();
|
|
2408
|
+
const {
|
|
2409
|
+
killed: runtimeKilled,
|
|
2410
|
+
killedProcesses: runtimeKilledProcesses = [],
|
|
2411
|
+
} = cleanupOrphanRuntimeProcesses();
|
|
2412
|
+
const totalKilled = killed + runtimeKilled;
|
|
2413
|
+
if (totalKilled > 0) {
|
|
2414
|
+
hubLog.info(
|
|
2415
|
+
{
|
|
2416
|
+
killed: totalKilled,
|
|
2417
|
+
processes: [...killedProcesses, ...runtimeKilledProcesses],
|
|
2418
|
+
caller: "timer",
|
|
2419
|
+
},
|
|
2420
|
+
"hub.orphan_cleanup",
|
|
2421
|
+
);
|
|
2422
|
+
}
|
|
2334
2423
|
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
}
|
|
2346
|
-
} catch {}
|
|
2424
|
+
const { killed: fsmonitorKilled, stale } =
|
|
2425
|
+
cleanupStaleFsmonitorDaemons({
|
|
2426
|
+
minAgeMs: 24 * 60 * 60 * 1000,
|
|
2427
|
+
});
|
|
2428
|
+
if (fsmonitorKilled > 0) {
|
|
2429
|
+
hubLog.info(
|
|
2430
|
+
{ killed: fsmonitorKilled, stale: stale.length },
|
|
2431
|
+
"hub.fsmonitor_cleanup",
|
|
2432
|
+
);
|
|
2433
|
+
}
|
|
2434
|
+
} catch {}
|
|
2347
2435
|
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2436
|
+
// stale tfx-spawn-* psmux 세션 정리 (30분 이상 idle)
|
|
2437
|
+
try {
|
|
2438
|
+
const staleKilled = cleanupStaleSpawnSessions(hubLog);
|
|
2439
|
+
if (staleKilled > 0) {
|
|
2440
|
+
hubLog.info({ killed: staleKilled }, "hub.stale_spawn_cleanup");
|
|
2441
|
+
}
|
|
2442
|
+
} catch {}
|
|
2443
|
+
},
|
|
2444
|
+
5 * 60 * 1000,
|
|
2445
|
+
);
|
|
2446
|
+
orphanCleanupTimer.unref();
|
|
2447
|
+
|
|
2448
|
+
// Evict stale rate-limit buckets once per minute to bound memory usage.
|
|
2449
|
+
rateLimitTimer = setInterval(() => {
|
|
2450
|
+
const cutoff = Date.now() - RATE_LIMIT_WINDOW_MS;
|
|
2451
|
+
for (const [ip, timestamps] of rateLimitMap) {
|
|
2452
|
+
const fresh = timestamps.filter((t) => t >= cutoff);
|
|
2453
|
+
if (fresh.length === 0) {
|
|
2454
|
+
rateLimitMap.delete(ip);
|
|
2455
|
+
} else {
|
|
2456
|
+
rateLimitMap.set(ip, fresh);
|
|
2353
2457
|
}
|
|
2354
|
-
} catch {}
|
|
2355
|
-
},
|
|
2356
|
-
5 * 60 * 1000,
|
|
2357
|
-
);
|
|
2358
|
-
orphanCleanupTimer.unref();
|
|
2359
|
-
|
|
2360
|
-
// Evict stale rate-limit buckets once per minute to bound memory usage.
|
|
2361
|
-
const rateLimitTimer = setInterval(() => {
|
|
2362
|
-
const cutoff = Date.now() - RATE_LIMIT_WINDOW_MS;
|
|
2363
|
-
for (const [ip, timestamps] of rateLimitMap) {
|
|
2364
|
-
const fresh = timestamps.filter((t) => t >= cutoff);
|
|
2365
|
-
if (fresh.length === 0) {
|
|
2366
|
-
rateLimitMap.delete(ip);
|
|
2367
|
-
} else {
|
|
2368
|
-
rateLimitMap.set(ip, fresh);
|
|
2369
2458
|
}
|
|
2370
|
-
}
|
|
2371
|
-
|
|
2372
|
-
|
|
2459
|
+
}, RATE_LIMIT_WINDOW_MS);
|
|
2460
|
+
rateLimitTimer.unref();
|
|
2461
|
+
};
|
|
2373
2462
|
|
|
2374
2463
|
mkdirSync(PID_DIR, { recursive: true });
|
|
2375
2464
|
|
|
@@ -2377,6 +2466,19 @@ export async function startHub({
|
|
|
2377
2466
|
try {
|
|
2378
2467
|
router.stopSweeper();
|
|
2379
2468
|
} catch {}
|
|
2469
|
+
for (const timer of [
|
|
2470
|
+
hitlTimer,
|
|
2471
|
+
sessionTimer,
|
|
2472
|
+
synapsePruneTimer,
|
|
2473
|
+
orphanCleanupTimer,
|
|
2474
|
+
rateLimitTimer,
|
|
2475
|
+
]) {
|
|
2476
|
+
if (timer) clearInterval(timer);
|
|
2477
|
+
}
|
|
2478
|
+
for (const [sid, session] of Array.from(transports)) {
|
|
2479
|
+
await closeMcpTransportSession(sid, session, "hub.startup_failure");
|
|
2480
|
+
}
|
|
2481
|
+
transports.clear();
|
|
2380
2482
|
try {
|
|
2381
2483
|
await pipe.stop();
|
|
2382
2484
|
} catch {}
|
|
@@ -2386,6 +2488,9 @@ export async function startHub({
|
|
|
2386
2488
|
try {
|
|
2387
2489
|
await delegatorWorker.stop();
|
|
2388
2490
|
} catch {}
|
|
2491
|
+
try {
|
|
2492
|
+
synapseRegistry.destroy();
|
|
2493
|
+
} catch {}
|
|
2389
2494
|
try {
|
|
2390
2495
|
store.close();
|
|
2391
2496
|
} catch {}
|
|
@@ -2396,6 +2501,22 @@ export async function startHub({
|
|
|
2396
2501
|
};
|
|
2397
2502
|
|
|
2398
2503
|
try {
|
|
2504
|
+
router.sweepExpired({ now_ms: Date.now(), source: "startup" });
|
|
2505
|
+
router.startSweeper({
|
|
2506
|
+
run_immediately: false,
|
|
2507
|
+
interval_ms: 10000,
|
|
2508
|
+
onSweepError: ({ source, error, consecutive_failures }) => {
|
|
2509
|
+
hubLog.warn(
|
|
2510
|
+
{
|
|
2511
|
+
source,
|
|
2512
|
+
code: error?.code || "UNKNOWN",
|
|
2513
|
+
consecutive_failures,
|
|
2514
|
+
},
|
|
2515
|
+
"hub.sweep_failed",
|
|
2516
|
+
);
|
|
2517
|
+
},
|
|
2518
|
+
});
|
|
2519
|
+
startBackgroundTimers();
|
|
2399
2520
|
await pipe.start();
|
|
2400
2521
|
await assignCallbacks.start();
|
|
2401
2522
|
} catch (error) {
|