@triflux/remote 10.43.0 → 10.44.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/cto/hygiene.mjs CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  } from "node:fs";
12
12
  import { homedir } from "node:os";
13
13
  import { basename, dirname, join } from "node:path";
14
- import { getCtoHygieneApplyMode } from "@triflux/core/hub/lib/cto-env.mjs";
14
+ import { getCtoHygieneApplyMode } from "../hub/lib/cto-env.mjs";
15
15
  import { appendCtoEvent } from "./events.mjs";
16
16
  import { applyHygieneArchiveActions } from "./hygiene-actions.mjs";
17
17
  import { resolveLakeRootDir } from "./lake-root.mjs";
@@ -0,0 +1 @@
1
+ export * from "@triflux/core/hub/lib/cto-env.mjs";
package/hub/pipe.mjs CHANGED
@@ -270,7 +270,13 @@ export function createPipeServer({
270
270
  );
271
271
  if (client) touchClient(client);
272
272
  if (result?.effective?.updated === false) {
273
- return { ok: false, error: { code: "AGENT_NOT_FOUND", message: `heartbeat target not registered: ${agentId}` } };
273
+ return {
274
+ ok: false,
275
+ error: {
276
+ code: "AGENT_NOT_FOUND",
277
+ message: `heartbeat target not registered: ${agentId}`,
278
+ },
279
+ };
274
280
  }
275
281
  return { ok: true, data: result };
276
282
  }
package/hub/schema.sql CHANGED
@@ -11,7 +11,11 @@ CREATE TABLE IF NOT EXISTS agents (
11
11
  last_seen_ms INTEGER NOT NULL,
12
12
  lease_expires_ms INTEGER NOT NULL,
13
13
  status TEXT NOT NULL CHECK (status IN ('online','stale','offline')),
14
- metadata_json TEXT NOT NULL DEFAULT '{}'
14
+ metadata_json TEXT NOT NULL DEFAULT '{}',
15
+ project_id TEXT,
16
+ session_id TEXT,
17
+ host_id TEXT,
18
+ transport_locators_json TEXT NOT NULL DEFAULT '[]'
15
19
  );
16
20
 
17
21
  -- 메시지 테이블
@@ -28,7 +32,61 @@ CREATE TABLE IF NOT EXISTS messages (
28
32
  correlation_id TEXT NOT NULL,
29
33
  trace_id TEXT NOT NULL,
30
34
  payload_json TEXT NOT NULL DEFAULT '{}',
31
- status TEXT NOT NULL CHECK (status IN ('queued','delivered','acked','expired','dead_letter'))
35
+ status TEXT NOT NULL CHECK (status IN ('queued','delivered','acked','expired','dead_letter')),
36
+ role_key_wire TEXT
37
+ );
38
+
39
+ -- 동적 역할 레지스트리 (schema v6)
40
+ CREATE TABLE IF NOT EXISTS role_registry (
41
+ role_key_wire TEXT PRIMARY KEY,
42
+ project_id TEXT NOT NULL,
43
+ role_kind TEXT NOT NULL CHECK (role_kind IN ('cto','lead')),
44
+ scope_id TEXT NOT NULL DEFAULT '',
45
+ state TEXT NOT NULL CHECK (
46
+ state IN (
47
+ 'electable',
48
+ 'elected-probing',
49
+ 'active',
50
+ 'unreachable',
51
+ 'quarantined'
52
+ )
53
+ ),
54
+ holder_agent_id TEXT,
55
+ previous_holder_agent_id TEXT,
56
+ epoch INTEGER NOT NULL DEFAULT 0 CHECK (epoch >= 0),
57
+ activation_seq INTEGER NOT NULL DEFAULT 0 CHECK (activation_seq >= 0),
58
+ activation_id TEXT,
59
+ activation_deadline_ms INTEGER,
60
+ holder_lease_expires_ms INTEGER,
61
+ charter_version TEXT,
62
+ charter_acked_epoch INTEGER,
63
+ retry_count INTEGER NOT NULL DEFAULT 0,
64
+ next_probe_ms INTEGER,
65
+ blocked_reason TEXT,
66
+ last_transition_ms INTEGER NOT NULL,
67
+ last_reason TEXT NOT NULL,
68
+ version INTEGER NOT NULL DEFAULT 0,
69
+ CHECK (
70
+ (role_kind = 'cto' AND scope_id = '') OR
71
+ (role_kind = 'lead' AND scope_id <> '')
72
+ ),
73
+ UNIQUE(project_id, role_kind, scope_id)
74
+ );
75
+
76
+ CREATE TABLE IF NOT EXISTS role_candidates (
77
+ role_key_wire TEXT NOT NULL,
78
+ agent_id TEXT NOT NULL,
79
+ source TEXT NOT NULL CHECK (source IN ('grant','standup','operator')),
80
+ priority INTEGER NOT NULL DEFAULT 0,
81
+ transport_locators_json TEXT NOT NULL DEFAULT '[]',
82
+ consecutive_probe_failures INTEGER NOT NULL DEFAULT 0,
83
+ excluded_until_ms INTEGER,
84
+ last_probe_ms INTEGER,
85
+ last_probe_code TEXT,
86
+ updated_at_ms INTEGER NOT NULL,
87
+ PRIMARY KEY(role_key_wire, agent_id),
88
+ FOREIGN KEY(role_key_wire)
89
+ REFERENCES role_registry(role_key_wire) ON DELETE CASCADE
32
90
  );
33
91
 
34
92
  -- 메시지 수신함 (배달 추적)
@@ -73,6 +131,7 @@ CREATE INDEX IF NOT EXISTS idx_messages_correlation ON messages(correlation_id);
73
131
  CREATE INDEX IF NOT EXISTS idx_messages_trace ON messages(trace_id);
74
132
  CREATE INDEX IF NOT EXISTS idx_messages_expires ON messages(expires_at_ms);
75
133
  CREATE INDEX IF NOT EXISTS idx_messages_priority ON messages(priority DESC, created_at_ms ASC);
134
+ CREATE INDEX IF NOT EXISTS idx_messages_role_key ON messages(role_key_wire, status, expires_at_ms);
76
135
  CREATE INDEX IF NOT EXISTS idx_inbox_agent ON message_inbox(agent_id, delivered_at_ms);
77
136
  CREATE INDEX IF NOT EXISTS idx_inbox_message ON message_inbox(message_id);
78
137
  CREATE INDEX IF NOT EXISTS idx_human_requests_state ON human_requests(state);
package/hub/server.mjs CHANGED
@@ -32,13 +32,6 @@ import {
32
32
  import { broker as brokerInstance, reloadBroker } from "@triflux/core/hub/account-broker.mjs";
33
33
  import { createAdaptiveEngine } from "@triflux/core/hub/adaptive.mjs";
34
34
  import { createAssignCallbackServer } from "@triflux/core/hub/assign-callbacks.mjs";
35
- import {
36
- clampDurationMs,
37
- DEFAULT_ASSIGN_TIMEOUT_MS,
38
- DEFAULT_ASSIGN_TTL_MS,
39
- DEFAULT_REGISTER_TIMEOUT_SEC,
40
- resolveWorkerLeaseTtlMs,
41
- } from "@triflux/core/hub/lib/timeout-defaults.mjs";
42
35
  import { DelegatorService } from "@triflux/core/hub/delegator/index.mjs";
43
36
  import { createHitlManager } from "@triflux/core/hub/hitl.mjs";
44
37
  import {
@@ -51,6 +44,13 @@ import {
51
44
  cleanupStaleFsmonitorDaemons,
52
45
  } from "@triflux/core/hub/lib/process-utils.mjs";
53
46
  import * as spawnTrace from "@triflux/core/hub/lib/spawn-trace.mjs";
47
+ import {
48
+ clampDurationMs,
49
+ DEFAULT_ASSIGN_TIMEOUT_MS,
50
+ DEFAULT_ASSIGN_TTL_MS,
51
+ DEFAULT_REGISTER_TIMEOUT_SEC,
52
+ resolveWorkerLeaseTtlMs,
53
+ } from "@triflux/core/hub/lib/timeout-defaults.mjs";
54
54
  import {
55
55
  recordRequest,
56
56
  recordWorker,
@@ -980,7 +980,13 @@ export async function startHub({
980
980
  detectPeer: () => livePeer,
981
981
  });
982
982
 
983
- if (portSpecified && !ephemeralPort && livePeer.alive && livePidPort && port !== livePidPort) {
983
+ if (
984
+ portSpecified &&
985
+ !ephemeralPort &&
986
+ livePeer.alive &&
987
+ livePidPort &&
988
+ port !== livePidPort
989
+ ) {
984
990
  hubLog.warn(
985
991
  {
986
992
  requestedPort: port,
@@ -991,9 +997,14 @@ export async function startHub({
991
997
  );
992
998
  }
993
999
 
994
- const existingHub = ephemeralPort ? null : await tryReuseExistingHub({
995
- port, portSpecified, host, detectPeer: () => livePeer,
996
- });
1000
+ const existingHub = ephemeralPort
1001
+ ? null
1002
+ : await tryReuseExistingHub({
1003
+ port,
1004
+ portSpecified,
1005
+ host,
1006
+ detectPeer: () => livePeer,
1007
+ });
997
1008
  if (existingHub) return existingHub;
998
1009
 
999
1010
  attachBrokerDiagnostics(brokerInstance);
@@ -1031,12 +1042,14 @@ export async function startHub({
1031
1042
  lockHeld = false;
1032
1043
  };
1033
1044
 
1034
- const lockedExistingHub = ephemeralPort ? null : await tryReuseExistingHub({
1035
- port,
1036
- portSpecified,
1037
- host,
1038
- detectPeer: () => detectLivePeer(),
1039
- });
1045
+ const lockedExistingHub = ephemeralPort
1046
+ ? null
1047
+ : await tryReuseExistingHub({
1048
+ port,
1049
+ portSpecified,
1050
+ host,
1051
+ detectPeer: () => detectLivePeer(),
1052
+ });
1040
1053
  if (lockedExistingHub) {
1041
1054
  releaseStartupLock();
1042
1055
  return lockedExistingHub;
@@ -1545,6 +1558,38 @@ export async function startHub({
1545
1558
  try {
1546
1559
  const body = await parseBody(req);
1547
1560
  const { sessionId } = body || {};
1561
+ if (body?.agent_id && body?.cli) {
1562
+ const registered = router.registerAgent({
1563
+ agent_id: body.agent_id,
1564
+ cli: body.cli,
1565
+ capabilities: Array.isArray(body.capabilities)
1566
+ ? body.capabilities
1567
+ : [],
1568
+ topics: Array.isArray(body.topics) ? body.topics : [],
1569
+ heartbeat_ttl_ms: body.heartbeat_ttl_ms ?? 300000,
1570
+ metadata:
1571
+ body.metadata && typeof body.metadata === "object"
1572
+ ? body.metadata
1573
+ : { source: "session_start" },
1574
+ ...(Object.hasOwn(body, "project_id")
1575
+ ? { project_id: body.project_id }
1576
+ : {}),
1577
+ ...(Object.hasOwn(body, "session_id")
1578
+ ? { session_id: body.session_id }
1579
+ : {}),
1580
+ ...(Object.hasOwn(body, "host_id")
1581
+ ? { host_id: body.host_id }
1582
+ : {}),
1583
+ ...(Object.hasOwn(body, "transport_locators_json")
1584
+ ? { transport_locators_json: body.transport_locators_json }
1585
+ : {}),
1586
+ });
1587
+ if (!registered?.ok) {
1588
+ throw new Error(
1589
+ registered?.error?.message || "hub agent register failed",
1590
+ );
1591
+ }
1592
+ }
1548
1593
  const result = synapseRegistry.register(sessionId, body);
1549
1594
  if (!result?.ok) {
1550
1595
  throw new Error(result?.reason || "register failed");
@@ -1699,6 +1744,10 @@ export async function startHub({
1699
1744
  topics = [],
1700
1745
  capabilities = [],
1701
1746
  metadata = {},
1747
+ project_id,
1748
+ session_id,
1749
+ host_id,
1750
+ transport_locators_json,
1702
1751
  } = body;
1703
1752
  if (!agent_id || !cli) {
1704
1753
  return writeJson(res, 400, {
@@ -1714,7 +1763,9 @@ export async function startHub({
1714
1763
  metadata?.task ||
1715
1764
  cli;
1716
1765
  const workerStartedAt = performance.now();
1717
- const hasExplicitTimeout = Number.isFinite(Number(body.timeout_sec));
1766
+ const hasExplicitTimeout = Number.isFinite(
1767
+ Number(body.timeout_sec),
1768
+ );
1718
1769
  const heartbeat_ttl_ms = hasExplicitTimeout
1719
1770
  ? (timeout_sec + 120) * 1000
1720
1771
  : Math.max((timeout_sec + 120) * 1000, resolveWorkerLeaseTtlMs());
@@ -1725,6 +1776,12 @@ export async function startHub({
1725
1776
  topics,
1726
1777
  heartbeat_ttl_ms,
1727
1778
  metadata,
1779
+ ...(project_id === undefined ? {} : { project_id }),
1780
+ ...(session_id === undefined ? {} : { session_id }),
1781
+ ...(host_id === undefined ? {} : { host_id }),
1782
+ ...(transport_locators_json === undefined
1783
+ ? {}
1784
+ : { transport_locators_json }),
1728
1785
  });
1729
1786
  if (result.ok) {
1730
1787
  workerSpans.set(agent_id, {
@@ -1760,10 +1817,14 @@ export async function startHub({
1760
1817
 
1761
1818
  if (path === "/bridge/heartbeat" && req.method === "POST") {
1762
1819
  const { agent_id, ttl_ms } = body;
1763
- if (!agent_id) return writeJson(res, 400, { ok: false, error: "agent_id 필수" });
1820
+ if (!agent_id)
1821
+ return writeJson(res, 400, { ok: false, error: "agent_id 필수" });
1764
1822
  const result = await pipe.executeCommand("heartbeat", {
1765
1823
  agent_id,
1766
- heartbeat_ttl_ms: clampDurationMs(ttl_ms, resolveWorkerLeaseTtlMs()),
1824
+ heartbeat_ttl_ms: clampDurationMs(
1825
+ ttl_ms,
1826
+ resolveWorkerLeaseTtlMs(),
1827
+ ),
1767
1828
  });
1768
1829
  return writeJson(res, result.ok ? 200 : 404, result);
1769
1830
  }
@@ -2445,7 +2506,8 @@ export async function startHub({
2445
2506
  httpServer.off("error", onError);
2446
2507
  try {
2447
2508
  const boundAddress = httpServer.address();
2448
- if (boundAddress && typeof boundAddress === "object") port = boundAddress.port;
2509
+ if (boundAddress && typeof boundAddress === "object")
2510
+ port = boundAddress.port;
2449
2511
  let idleTimer = null;
2450
2512
  let stopPromise = null;
2451
2513
 
@@ -2466,25 +2528,28 @@ export async function startHub({
2466
2528
  idleTimeoutMs: hubIdleTimeoutMs,
2467
2529
  };
2468
2530
 
2469
- if (!ephemeralPort) writeState({
2470
- pid: process.pid,
2471
- port,
2472
- host,
2473
- auth_mode: HUB_TOKEN ? "token-required" : "localhost-only",
2474
- url: info.url,
2475
- pipe_path: pipe.path,
2476
- pipePath: pipe.path,
2477
- assign_callback_pipe_path: assignCallbacks.path,
2478
- assignCallbackPipePath: assignCallbacks.path,
2479
- authMode: HUB_TOKEN ? "token-required" : "localhost-only",
2480
- startedAt,
2481
- started: startedAtMs,
2482
- version,
2483
- sessionId,
2484
- session_id: sessionId,
2485
- });
2531
+ if (!ephemeralPort) {
2532
+ writeState({
2533
+ pid: process.pid,
2534
+ port,
2535
+ host,
2536
+ auth_mode: HUB_TOKEN ? "token-required" : "localhost-only",
2537
+ url: info.url,
2538
+ pipe_path: pipe.path,
2539
+ pipePath: pipe.path,
2540
+ assign_callback_pipe_path: assignCallbacks.path,
2541
+ assignCallbackPipePath: assignCallbacks.path,
2542
+ authMode: HUB_TOKEN ? "token-required" : "localhost-only",
2543
+ startedAt,
2544
+ started: startedAtMs,
2545
+ version,
2546
+ sessionId,
2547
+ session_id: sessionId,
2548
+ });
2549
+ }
2486
2550
  releaseStartupLock();
2487
- if (!ephemeralPort) void syncHubMcpSettingsIfAvailable({ hubUrl: info.url });
2551
+ if (!ephemeralPort)
2552
+ void syncHubMcpSettingsIfAvailable({ hubUrl: info.url });
2488
2553
 
2489
2554
  hubLog.info(
2490
2555
  {