@triflux/remote 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/hub/pipe.mjs +10 -15
- package/hub/schema.sql +1 -0
- package/hub/server.mjs +172 -93
- package/hub/store.mjs +127 -61
- 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/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 +17 -1
- 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/synapse-http.mjs +149 -4
- package/hub/team/terminal-opener.mjs +68 -13
- package/hub/team/tui-viewer.mjs +65 -14
- package/hub/tools.mjs +10 -1
- package/hub/workers/codex-mcp.mjs +8 -3
- package/hub/workers/delegator-mcp.mjs +1 -1
- package/package.json +1 -1
- package/scripts/lib/agent-route-policy.mjs +84 -24
- package/scripts/lib/cli-agy.mjs +24 -4
- package/scripts/lib/cli-codex.mjs +2 -0
- 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/hub/pipe.mjs
CHANGED
|
@@ -267,18 +267,11 @@ export function createPipeServer({
|
|
|
267
267
|
const result = router.refreshAgentLease(
|
|
268
268
|
agentId,
|
|
269
269
|
payload.heartbeat_ttl_ms || heartbeatTtlMs,
|
|
270
|
+
payload.presence_generation,
|
|
271
|
+
payload,
|
|
270
272
|
);
|
|
271
273
|
if (client) touchClient(client);
|
|
272
|
-
|
|
273
|
-
return {
|
|
274
|
-
ok: false,
|
|
275
|
-
error: {
|
|
276
|
-
code: "AGENT_NOT_FOUND",
|
|
277
|
-
message: `heartbeat target not registered: ${agentId}`,
|
|
278
|
-
},
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
return { ok: true, data: result };
|
|
274
|
+
return result;
|
|
282
275
|
}
|
|
283
276
|
|
|
284
277
|
case "publish": {
|
|
@@ -357,12 +350,14 @@ export function createPipeServer({
|
|
|
357
350
|
|
|
358
351
|
case "deregister": {
|
|
359
352
|
const agentId = resolveAgentId(client, payload);
|
|
360
|
-
router.
|
|
353
|
+
const result = router.transitionAgentStatus(
|
|
354
|
+
agentId,
|
|
355
|
+
"offline",
|
|
356
|
+
payload.presence_generation,
|
|
357
|
+
payload,
|
|
358
|
+
);
|
|
361
359
|
if (client) touchClient(client);
|
|
362
|
-
return
|
|
363
|
-
ok: true,
|
|
364
|
-
data: { agent_id: agentId, status: "offline" },
|
|
365
|
-
};
|
|
360
|
+
return result;
|
|
366
361
|
}
|
|
367
362
|
|
|
368
363
|
case "send_input": {
|
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,
|
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
|
|
|
@@ -1720,6 +1722,8 @@ export async function startHub({
|
|
|
1720
1722
|
agent_id,
|
|
1721
1723
|
reason = "manual",
|
|
1722
1724
|
requested_by = "http",
|
|
1725
|
+
hub_instance_id,
|
|
1726
|
+
store_fingerprint,
|
|
1723
1727
|
} = body;
|
|
1724
1728
|
if (!role || !agent_id) {
|
|
1725
1729
|
return writeJson(res, 400, {
|
|
@@ -1732,6 +1736,8 @@ export async function startHub({
|
|
|
1732
1736
|
agent_id,
|
|
1733
1737
|
reason,
|
|
1734
1738
|
requested_by,
|
|
1739
|
+
hub_instance_id,
|
|
1740
|
+
store_fingerprint,
|
|
1735
1741
|
});
|
|
1736
1742
|
return writeJson(res, result.ok ? 200 : 409, result);
|
|
1737
1743
|
}
|
|
@@ -1748,6 +1754,8 @@ export async function startHub({
|
|
|
1748
1754
|
session_id,
|
|
1749
1755
|
host_id,
|
|
1750
1756
|
transport_locators_json,
|
|
1757
|
+
hub_instance_id,
|
|
1758
|
+
store_fingerprint,
|
|
1751
1759
|
} = body;
|
|
1752
1760
|
if (!agent_id || !cli) {
|
|
1753
1761
|
return writeJson(res, 400, {
|
|
@@ -1782,6 +1790,8 @@ export async function startHub({
|
|
|
1782
1790
|
...(transport_locators_json === undefined
|
|
1783
1791
|
? {}
|
|
1784
1792
|
: { transport_locators_json }),
|
|
1793
|
+
...(hub_instance_id === undefined ? {} : { hub_instance_id }),
|
|
1794
|
+
...(store_fingerprint === undefined ? {} : { store_fingerprint }),
|
|
1785
1795
|
});
|
|
1786
1796
|
if (result.ok) {
|
|
1787
1797
|
workerSpans.set(agent_id, {
|
|
@@ -1816,7 +1826,13 @@ export async function startHub({
|
|
|
1816
1826
|
}
|
|
1817
1827
|
|
|
1818
1828
|
if (path === "/bridge/heartbeat" && req.method === "POST") {
|
|
1819
|
-
const {
|
|
1829
|
+
const {
|
|
1830
|
+
agent_id,
|
|
1831
|
+
ttl_ms,
|
|
1832
|
+
presence_generation,
|
|
1833
|
+
hub_instance_id,
|
|
1834
|
+
store_fingerprint,
|
|
1835
|
+
} = body;
|
|
1820
1836
|
if (!agent_id)
|
|
1821
1837
|
return writeJson(res, 400, { ok: false, error: "agent_id 필수" });
|
|
1822
1838
|
const result = await pipe.executeCommand("heartbeat", {
|
|
@@ -1825,6 +1841,9 @@ export async function startHub({
|
|
|
1825
1841
|
ttl_ms,
|
|
1826
1842
|
resolveWorkerLeaseTtlMs(),
|
|
1827
1843
|
),
|
|
1844
|
+
presence_generation,
|
|
1845
|
+
hub_instance_id,
|
|
1846
|
+
store_fingerprint,
|
|
1828
1847
|
});
|
|
1829
1848
|
return writeJson(res, result.ok ? 200 : 404, result);
|
|
1830
1849
|
}
|
|
@@ -1905,6 +1924,9 @@ export async function startHub({
|
|
|
1905
1924
|
status,
|
|
1906
1925
|
include_metrics = true,
|
|
1907
1926
|
trace_id,
|
|
1927
|
+
presence_generation,
|
|
1928
|
+
hub_instance_id,
|
|
1929
|
+
store_fingerprint,
|
|
1908
1930
|
} = body;
|
|
1909
1931
|
|
|
1910
1932
|
if (agent_id && status) {
|
|
@@ -1925,7 +1947,15 @@ export async function startHub({
|
|
|
1925
1947
|
]).has(normalizedStatus)
|
|
1926
1948
|
? normalizedStatus
|
|
1927
1949
|
: "online";
|
|
1928
|
-
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
|
+
}
|
|
1929
1959
|
const snapshot = await pipe.executeQuery("status", {
|
|
1930
1960
|
scope: "agent",
|
|
1931
1961
|
agent_id: normalizedAgentId,
|
|
@@ -1939,6 +1969,8 @@ export async function startHub({
|
|
|
1939
1969
|
reported_status: normalizedStatus,
|
|
1940
1970
|
reported_at_ms: Date.now(),
|
|
1941
1971
|
snapshot: snapshot?.data?.agent || null,
|
|
1972
|
+
hub_instance_id: transition.data.hub_instance_id,
|
|
1973
|
+
store_fingerprint: transition.data.store_fingerprint,
|
|
1942
1974
|
},
|
|
1943
1975
|
});
|
|
1944
1976
|
}
|
|
@@ -1948,6 +1980,8 @@ export async function startHub({
|
|
|
1948
1980
|
agent_id,
|
|
1949
1981
|
include_metrics,
|
|
1950
1982
|
trace_id,
|
|
1983
|
+
hub_instance_id,
|
|
1984
|
+
store_fingerprint,
|
|
1951
1985
|
});
|
|
1952
1986
|
return writeJson(res, 200, result);
|
|
1953
1987
|
}
|
|
@@ -2167,12 +2201,20 @@ export async function startHub({
|
|
|
2167
2201
|
}
|
|
2168
2202
|
|
|
2169
2203
|
if (path === "/bridge/deregister" && req.method === "POST") {
|
|
2170
|
-
const {
|
|
2204
|
+
const {
|
|
2205
|
+
agent_id,
|
|
2206
|
+
presence_generation,
|
|
2207
|
+
hub_instance_id,
|
|
2208
|
+
store_fingerprint,
|
|
2209
|
+
} = body;
|
|
2171
2210
|
if (!agent_id) {
|
|
2172
2211
|
return writeJson(res, 400, { ok: false, error: "agent_id 필수" });
|
|
2173
2212
|
}
|
|
2174
2213
|
const result = await pipe.executeCommand("deregister", {
|
|
2175
2214
|
agent_id,
|
|
2215
|
+
presence_generation,
|
|
2216
|
+
hub_instance_id,
|
|
2217
|
+
store_fingerprint,
|
|
2176
2218
|
});
|
|
2177
2219
|
if (result.ok) {
|
|
2178
2220
|
finishWorkerTrace(agent_id, "deregister");
|
|
@@ -2316,102 +2358,107 @@ export async function startHub({
|
|
|
2316
2358
|
httpServer.requestTimeout = 30000;
|
|
2317
2359
|
httpServer.headersTimeout = 10000;
|
|
2318
2360
|
|
|
2319
|
-
|
|
2361
|
+
let hitlTimer = null;
|
|
2362
|
+
let sessionTimer = null;
|
|
2363
|
+
let synapsePruneTimer = null;
|
|
2364
|
+
let orphanCleanupTimer = null;
|
|
2365
|
+
let rateLimitTimer = null;
|
|
2320
2366
|
|
|
2321
|
-
const
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
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();
|
|
2343
2390
|
|
|
2344
|
-
|
|
2345
|
-
try {
|
|
2346
|
-
const { count } = synapseRegistry.pruneExpired();
|
|
2347
|
-
if (count > 0) hubLog.info({ count }, "synapse.prune");
|
|
2348
|
-
} catch (err) {
|
|
2349
|
-
hubLog.warn({ err }, "synapse.prune_failed");
|
|
2350
|
-
}
|
|
2351
|
-
}, 60000);
|
|
2352
|
-
synapsePruneTimer.unref();
|
|
2353
|
-
|
|
2354
|
-
// 고아 node.exe 프로세스 + stale spawn 세션 주기적 정리 (5분마다)
|
|
2355
|
-
// TFX_DISABLE_ORPHAN_CLEANUP=1 로 비활성 (active SSH/swarm 세션 보호 회피용 hotfix gate)
|
|
2356
|
-
const orphanCleanupTimer = setInterval(
|
|
2357
|
-
() => {
|
|
2358
|
-
if (process.env.TFX_DISABLE_ORPHAN_CLEANUP === "1") return;
|
|
2391
|
+
synapsePruneTimer = setInterval(() => {
|
|
2359
2392
|
try {
|
|
2360
|
-
const {
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
);
|
|
2375
|
-
|
|
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
|
+
}
|
|
2376
2423
|
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
}
|
|
2388
|
-
} 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 {}
|
|
2389
2435
|
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
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);
|
|
2395
2457
|
}
|
|
2396
|
-
} catch {}
|
|
2397
|
-
},
|
|
2398
|
-
5 * 60 * 1000,
|
|
2399
|
-
);
|
|
2400
|
-
orphanCleanupTimer.unref();
|
|
2401
|
-
|
|
2402
|
-
// Evict stale rate-limit buckets once per minute to bound memory usage.
|
|
2403
|
-
const rateLimitTimer = setInterval(() => {
|
|
2404
|
-
const cutoff = Date.now() - RATE_LIMIT_WINDOW_MS;
|
|
2405
|
-
for (const [ip, timestamps] of rateLimitMap) {
|
|
2406
|
-
const fresh = timestamps.filter((t) => t >= cutoff);
|
|
2407
|
-
if (fresh.length === 0) {
|
|
2408
|
-
rateLimitMap.delete(ip);
|
|
2409
|
-
} else {
|
|
2410
|
-
rateLimitMap.set(ip, fresh);
|
|
2411
2458
|
}
|
|
2412
|
-
}
|
|
2413
|
-
|
|
2414
|
-
|
|
2459
|
+
}, RATE_LIMIT_WINDOW_MS);
|
|
2460
|
+
rateLimitTimer.unref();
|
|
2461
|
+
};
|
|
2415
2462
|
|
|
2416
2463
|
mkdirSync(PID_DIR, { recursive: true });
|
|
2417
2464
|
|
|
@@ -2419,6 +2466,19 @@ export async function startHub({
|
|
|
2419
2466
|
try {
|
|
2420
2467
|
router.stopSweeper();
|
|
2421
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();
|
|
2422
2482
|
try {
|
|
2423
2483
|
await pipe.stop();
|
|
2424
2484
|
} catch {}
|
|
@@ -2428,6 +2488,9 @@ export async function startHub({
|
|
|
2428
2488
|
try {
|
|
2429
2489
|
await delegatorWorker.stop();
|
|
2430
2490
|
} catch {}
|
|
2491
|
+
try {
|
|
2492
|
+
synapseRegistry.destroy();
|
|
2493
|
+
} catch {}
|
|
2431
2494
|
try {
|
|
2432
2495
|
store.close();
|
|
2433
2496
|
} catch {}
|
|
@@ -2438,6 +2501,22 @@ export async function startHub({
|
|
|
2438
2501
|
};
|
|
2439
2502
|
|
|
2440
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();
|
|
2441
2520
|
await pipe.start();
|
|
2442
2521
|
await assignCallbacks.start();
|
|
2443
2522
|
} catch (error) {
|