@triflux/remote 10.51.0 → 10.52.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 (59) hide show
  1. package/cto/brief.mjs +0 -8
  2. package/cto/collect.mjs +1 -127
  3. package/cto/current.schema.json +2 -19
  4. package/cto/hygiene.mjs +2 -61
  5. package/cto/lake-root.mjs +0 -56
  6. package/cto/status.mjs +5 -139
  7. package/hub/index.mjs +0 -9
  8. package/hub/team/agents-row-attach.py +120 -0
  9. package/hub/team/agents-row.mjs +200 -0
  10. package/hub/team/claude-daemon-control.mjs +1 -2
  11. package/hub/team/claude-session-projection.mjs +5 -73
  12. package/hub/team/cli/commands/debug.mjs +1 -2
  13. package/hub/team/cli/commands/start/index.mjs +2 -1
  14. package/hub/team/cli/commands/start/start-mux.mjs +2 -4
  15. package/hub/team/cli/services/state-store.mjs +5 -20
  16. package/hub/team/headless.mjs +96 -543
  17. package/hub/team/psmux.mjs +3 -33
  18. package/hub/team/session-context.mjs +1 -1
  19. package/hub/team/tui-viewer.mjs +9 -56
  20. package/hub/team/tui.mjs +0 -37
  21. package/hub/team/uds-orchestrator.mjs +2 -580
  22. package/hub/workers/factory.mjs +6 -51
  23. package/hub/workers/interface.mjs +1 -7
  24. package/hub/workers/lib/jsonrpc-core.mjs +11 -0
  25. package/hub/workers/lib/jsonrpc-ws-uds.mjs +5 -6
  26. package/package.json +3 -8
  27. package/scripts/lib/agent-route-policy.mjs +0 -67
  28. package/scripts/lib/cli-claude.mjs +0 -12
  29. package/scripts/lib/env-probe.mjs +0 -70
  30. package/scripts/lib/legacy-mcp-cleanup.mjs +283 -0
  31. package/scripts/lib/mcp-guard-engine.mjs +40 -98
  32. package/hub/hub-lifecycle.mjs +0 -248
  33. package/hub/pipe.mjs +0 -825
  34. package/hub/promote-penalties.mjs +0 -138
  35. package/hub/public/dashboard.html +0 -416
  36. package/hub/schema.sql +0 -209
  37. package/hub/server.mjs +0 -3048
  38. package/hub/store-adapter.mjs +0 -182
  39. package/hub/store.mjs +0 -1736
  40. package/hub/team/cli/services/hub-client.mjs +0 -178
  41. package/hub/team/conductor-registry.mjs +0 -121
  42. package/hub/team/cto-auto-collect.mjs +0 -115
  43. package/hub/team/lead-control.mjs +0 -191
  44. package/hub/team/session-sync.mjs +0 -316
  45. package/hub/team/swarm-intent.mjs +0 -18
  46. package/hub/team/synapse-cli.mjs +0 -458
  47. package/hub/team/synapse-http.mjs +0 -311
  48. package/hub/team/synapse-registry.mjs +0 -618
  49. package/hub/team/tui-synapse.mjs +0 -223
  50. package/hub/team/worker-sandbox.mjs +0 -125
  51. package/hub/tools.mjs +0 -900
  52. package/hub/workers/codex-app-server-worker.mjs +0 -1067
  53. package/hub/workers/delegator-mcp.mjs +0 -1098
  54. package/hub/workers/lib/jsonrpc-stdio.mjs +0 -204
  55. package/scripts/lib/claudemd-scanner.mjs +0 -26
  56. package/scripts/lib/context.mjs +0 -51
  57. package/scripts/lib/handoff.mjs +0 -187
  58. package/scripts/lib/logger.mjs +0 -122
  59. package/scripts/lib/stealth-fetch.mjs +0 -176
@@ -1,178 +0,0 @@
1
- import {
2
- existsSync,
3
- mkdirSync,
4
- readFileSync,
5
- unlinkSync,
6
- writeFileSync,
7
- } from "node:fs";
8
- import { join } from "node:path";
9
- import { publishLeadControl as publishLeadControlBridge } from "../../lead-control.mjs";
10
- import {
11
- getTeamStatus as fetchTeamStatus,
12
- subscribeToLeadCommands as pullLeadCommands,
13
- } from "../../session-sync.mjs";
14
- import { HUB_PID_DIR } from "./state-store.mjs";
15
-
16
- const HUB_PID_FILE = join(HUB_PID_DIR, "hub.pid");
17
- const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1"]);
18
-
19
- export function formatHostForUrl(host) {
20
- return host.includes(":") ? `[${host}]` : host;
21
- }
22
-
23
- export function buildHubBaseUrl(host, port) {
24
- return `http://${formatHostForUrl(host)}:${port}`;
25
- }
26
-
27
- export function getDefaultHubPort() {
28
- const envPortRaw = Number(process.env.TFX_HUB_PORT || "27888");
29
- return Number.isFinite(envPortRaw) && envPortRaw > 0 ? envPortRaw : 27888;
30
- }
31
-
32
- export function getDefaultHubUrl() {
33
- return `${buildHubBaseUrl("127.0.0.1", getDefaultHubPort())}/mcp`;
34
- }
35
-
36
- function normalizeLoopbackHost(host) {
37
- if (typeof host !== "string") return "127.0.0.1";
38
- const candidate = host.trim();
39
- return LOOPBACK_HOSTS.has(candidate) ? candidate : "127.0.0.1";
40
- }
41
-
42
- async function probeHubStatus(host, port, timeoutMs = 1500) {
43
- try {
44
- const res = await fetch(`${buildHubBaseUrl(host, port)}/status`, {
45
- signal: AbortSignal.timeout(timeoutMs),
46
- });
47
- if (!res.ok) return null;
48
- const data = await res.json();
49
- return data?.hub ? data : null;
50
- } catch {
51
- return null;
52
- }
53
- }
54
-
55
- export async function getHubInfo() {
56
- const probePort = getDefaultHubPort();
57
-
58
- if (existsSync(HUB_PID_FILE)) {
59
- try {
60
- const raw = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
61
- const pid = Number(raw?.pid);
62
- if (!Number.isFinite(pid) || pid <= 0) throw new Error("invalid pid");
63
- process.kill(pid, 0);
64
- const host = normalizeLoopbackHost(raw?.host);
65
- const port = Number(raw?.port) || probePort;
66
- const status = await probeHubStatus(host, port, 1200);
67
- if (!status) throw new Error("pid file process is not a healthy hub");
68
- return {
69
- ...raw,
70
- pid,
71
- host,
72
- port,
73
- url: `${buildHubBaseUrl(host, port)}/mcp`,
74
- };
75
- } catch {
76
- try {
77
- unlinkSync(HUB_PID_FILE);
78
- } catch {}
79
- }
80
- }
81
-
82
- for (const portCandidate of Array.from(new Set([probePort, 27888]))) {
83
- const data = await probeHubStatus("127.0.0.1", portCandidate, 1200);
84
- if (!data) continue;
85
- const port = Number(data.port) || portCandidate;
86
- const pid = Number(data.pid);
87
- const recovered = {
88
- pid: Number.isFinite(pid) ? pid : null,
89
- host: "127.0.0.1",
90
- port,
91
- url: `${buildHubBaseUrl("127.0.0.1", port)}/mcp`,
92
- discovered: true,
93
- };
94
- if (Number.isFinite(recovered.pid) && recovered.pid > 0) {
95
- try {
96
- mkdirSync(HUB_PID_DIR, { recursive: true });
97
- writeFileSync(
98
- HUB_PID_FILE,
99
- JSON.stringify({ ...recovered, started: Date.now() }),
100
- );
101
- } catch {}
102
- }
103
- return recovered;
104
- }
105
- return null;
106
- }
107
-
108
- export async function fetchHubTaskList(state) {
109
- const hubBase = (state?.hubUrl || getDefaultHubUrl()).replace(/\/mcp$/, "");
110
- const teamName = state?.sessionName || null;
111
- if (!teamName) return [];
112
-
113
- try {
114
- const res = await fetch(`${hubBase}/bridge/team/task-list`, {
115
- method: "POST",
116
- headers: { "Content-Type": "application/json" },
117
- body: JSON.stringify({ team_name: teamName }),
118
- signal: AbortSignal.timeout(2000),
119
- });
120
- const data = await res.json();
121
- return data?.ok ? data.data?.tasks || [] : [];
122
- } catch {
123
- return [];
124
- }
125
- }
126
-
127
- export async function publishLeadControl(
128
- state,
129
- targetMember,
130
- command,
131
- reason = "",
132
- ) {
133
- const hubBase = (state?.hubUrl || getDefaultHubUrl()).replace(/\/mcp$/, "");
134
- const leadAgent =
135
- (state?.members || []).find((member) => member.role === "lead")?.agentId ||
136
- "lead";
137
- const targetAgent =
138
- typeof targetMember === "string" ? targetMember : targetMember?.agentId;
139
-
140
- const result = await publishLeadControlBridge({
141
- hubUrl: hubBase,
142
- fromAgent: leadAgent,
143
- toAgent: targetAgent,
144
- command,
145
- reason,
146
- payload: {
147
- issued_by: leadAgent,
148
- issued_at: Date.now(),
149
- },
150
- });
151
-
152
- return !!result?.ok;
153
- }
154
-
155
- export async function subscribeToLeadCommands(state, member, options = {}) {
156
- const hubBase = (state?.hubUrl || getDefaultHubUrl()).replace(/\/mcp$/, "");
157
- const fallbackAgentId =
158
- (state?.members || []).find((candidate) => candidate.role === "lead")
159
- ?.agentId || null;
160
- const agentId =
161
- typeof member === "string"
162
- ? member
163
- : member?.agentId || options?.agentId || fallbackAgentId;
164
-
165
- return await pullLeadCommands({
166
- hubUrl: hubBase,
167
- agentId,
168
- ...options,
169
- });
170
- }
171
-
172
- export async function getTeamStatus(state, options = {}) {
173
- const hubBase = (state?.hubUrl || getDefaultHubUrl()).replace(/\/mcp$/, "");
174
- return await fetchTeamStatus({
175
- hubUrl: hubBase,
176
- ...options,
177
- });
178
- }
@@ -1,121 +0,0 @@
1
- function normalizeSessionId(sessionId) {
2
- if (sessionId == null) return "";
3
- return String(sessionId).trim();
4
- }
5
-
6
- export function createConductorRegistry() {
7
- const sessions = new Map();
8
-
9
- return Object.freeze({
10
- register(sessionId, conductor) {
11
- const normalizedSessionId = normalizeSessionId(sessionId);
12
- if (
13
- !normalizedSessionId ||
14
- !conductor ||
15
- typeof conductor.sendInput !== "function"
16
- ) {
17
- return false;
18
- }
19
- sessions.set(normalizedSessionId, conductor);
20
- return true;
21
- },
22
-
23
- unregister(sessionId, conductor = null) {
24
- const normalizedSessionId = normalizeSessionId(sessionId);
25
- if (!normalizedSessionId || !sessions.has(normalizedSessionId))
26
- return false;
27
- if (conductor && sessions.get(normalizedSessionId) !== conductor)
28
- return false;
29
- return sessions.delete(normalizedSessionId);
30
- },
31
-
32
- get(sessionId) {
33
- const normalizedSessionId = normalizeSessionId(sessionId);
34
- if (!normalizedSessionId) return null;
35
- return sessions.get(normalizedSessionId) || null;
36
- },
37
-
38
- clear() {
39
- sessions.clear();
40
- },
41
-
42
- list() {
43
- return [...sessions.keys()];
44
- },
45
- });
46
- }
47
-
48
- let conductorRegistry = null;
49
-
50
- export function getConductorRegistry() {
51
- return conductorRegistry;
52
- }
53
-
54
- export function setConductorRegistry(nextRegistry) {
55
- const previousRegistry = conductorRegistry;
56
- conductorRegistry = nextRegistry ?? null;
57
- return previousRegistry;
58
- }
59
-
60
- export function ensureConductorRegistry() {
61
- if (!conductorRegistry) {
62
- conductorRegistry = createConductorRegistry();
63
- }
64
- return conductorRegistry;
65
- }
66
-
67
- export function sendInputToConductorSession(sessionId, text) {
68
- const normalizedSessionId = normalizeSessionId(sessionId);
69
- const normalizedText = typeof text === "string" ? text : "";
70
-
71
- if (!normalizedSessionId || !normalizedText) {
72
- return {
73
- ok: false,
74
- error: {
75
- code: "INVALID_SEND_INPUT",
76
- message: "session_id, text 필수",
77
- },
78
- };
79
- }
80
-
81
- const registry = getConductorRegistry();
82
- if (!registry) {
83
- return {
84
- ok: false,
85
- error: {
86
- code: "CONDUCTOR_REGISTRY_NOT_AVAILABLE",
87
- message: "Conductor registry가 초기화되지 않았습니다",
88
- },
89
- };
90
- }
91
-
92
- const conductor = registry.get(normalizedSessionId);
93
- if (!conductor) {
94
- return {
95
- ok: false,
96
- error: {
97
- code: "CONDUCTOR_SESSION_NOT_FOUND",
98
- message: `Conductor session not found: ${normalizedSessionId}`,
99
- },
100
- };
101
- }
102
-
103
- const sent = conductor.sendInput(normalizedSessionId, normalizedText);
104
- if (!sent) {
105
- return {
106
- ok: false,
107
- error: {
108
- code: "SEND_INPUT_FAILED",
109
- message: `입력 전송 실패: ${normalizedSessionId}`,
110
- },
111
- };
112
- }
113
-
114
- return {
115
- ok: true,
116
- data: {
117
- session_id: normalizedSessionId,
118
- sent: true,
119
- },
120
- };
121
- }
@@ -1,115 +0,0 @@
1
- import { existsSync, statSync } from "node:fs";
2
- import { join } from "node:path";
3
-
4
- import { resolveLakeRootDir } from "../../cto/lake-root.mjs";
5
- import { resolveRoleControlSnapshot } from "../lib/cto-env.mjs";
6
-
7
- const DEFAULT_DEBOUNCE_MS = 120_000;
8
-
9
- export function isCtoAutoCollectDisabled(env = process.env) {
10
- return !resolveRoleControlSnapshot(env).auto_collect_enabled;
11
- }
12
-
13
- function freshCurrentMd(projectRoot, now, debounceMs) {
14
- try {
15
- const currentMd = join(projectRoot, ".triflux", "lake", "current.md");
16
- if (!existsSync(currentMd)) return false;
17
- return now - statSync(currentMd).mtimeMs < debounceMs;
18
- } catch {
19
- return false;
20
- }
21
- }
22
-
23
- export function createCtoAutoCollector(opts = {}) {
24
- const registry = opts.registry;
25
- const runCollect = opts.runCollect;
26
- const logger = opts.logger || null;
27
- const debounceMs = Number(opts.debounceMs || DEFAULT_DEBOUNCE_MS);
28
- const now = opts.now || (() => Date.now());
29
- const env = opts.env || process.env;
30
- const lastCollectMs = opts.lastCollectMs || new Map();
31
- const inFlight = new Set();
32
-
33
- function handleSessionStarted({ sessionId, session } = {}) {
34
- if (isCtoAutoCollectDisabled(env))
35
- return { triggered: false, reason: "disabled" };
36
- if (!registry || typeof registry.querySessions !== "function") {
37
- return { triggered: false, reason: "missing-registry" };
38
- }
39
- if (typeof runCollect !== "function") {
40
- return { triggered: false, reason: "missing-runCollect" };
41
- }
42
-
43
- const cwd = typeof session?.cwd === "string" ? session.cwd : "";
44
- const worktree =
45
- typeof session?.worktreePath === "string" ? session.worktreePath : "";
46
- const projectRoot = resolveLakeRootDir(worktree || cwd);
47
- if (!projectRoot)
48
- return { triggered: false, reason: "missing-project-root" };
49
-
50
- let peers = [];
51
- try {
52
- peers = registry.querySessions({
53
- cwd,
54
- worktree,
55
- excludeSessionId: sessionId,
56
- });
57
- } catch (error) {
58
- logger?.warn?.(
59
- { sessionId, err: String(error?.message || error) },
60
- "cto.auto_collect.query_failed",
61
- );
62
- // peer 조회 실패는 collect 를 막지 않는다 — peer 정보는 이제 라벨 용도뿐이라
63
- // registry 일시 장애에도 solo 로 진행해 collect 회복력을 유지한다.
64
- peers = [];
65
- }
66
- // 단일 세션(peer 0개)도 허용한다 — 사용자 선택. peer 유무와 무관하게
67
- // 아래 debounce + fresh-lake 게이트가 collect 빈도를 제어한다.
68
- //
69
- // 주의: peerCount 는 best-effort 라벨이다. querySessions 는 raw
70
- // cwd/worktreePath 로 매칭하지만 projectRoot 는 resolved git toplevel 이라,
71
- // 같은 repo 의 다른 하위 폴더 세션은 peer 로 안 잡혀 triggered-solo 로 보고될
72
- // 수 있다. 즉 라벨은 repo-scoped 가 아니라 exact-cwd-scoped 다 — 향후 peer
73
- // 기반 로직을 이 라벨 위에 다시 올릴 때 주의한다.
74
- const peerCount = Array.isArray(peers) ? peers.length : 0;
75
-
76
- const currentTime = now();
77
- const last = lastCollectMs.get(projectRoot) || 0;
78
- if (currentTime - last < debounceMs) {
79
- return { triggered: false, reason: "debounced" };
80
- }
81
- if (freshCurrentMd(projectRoot, currentTime, debounceMs)) {
82
- lastCollectMs.set(projectRoot, currentTime);
83
- return { triggered: false, reason: "fresh-lake" };
84
- }
85
-
86
- lastCollectMs.set(projectRoot, currentTime);
87
- const promise = Promise.resolve()
88
- .then(() => runCollect([], { rootDir: projectRoot }))
89
- .catch((error) => {
90
- logger?.warn?.(
91
- { projectRoot, err: String(error?.message || error) },
92
- "cto.auto_collect.failed",
93
- );
94
- })
95
- .finally(() => {
96
- inFlight.delete(promise);
97
- });
98
- inFlight.add(promise);
99
- return {
100
- triggered: true,
101
- reason: peerCount > 0 ? "triggered" : "triggered-solo",
102
- projectRoot,
103
- peerCount,
104
- };
105
- }
106
-
107
- async function drain() {
108
- await Promise.allSettled([...inFlight]);
109
- }
110
-
111
- return Object.freeze({
112
- handleSessionStarted,
113
- drain,
114
- });
115
- }
@@ -1,191 +0,0 @@
1
- import { readState } from "@triflux/core/hub/state.mjs";
2
-
3
- const DEFAULT_TIMEOUT_MS = 2500;
4
- const CONTROL_COMMAND_ALIASES = Object.freeze({
5
- stop: "abort",
6
- interrupt: "abort",
7
- });
8
-
9
- export const LEAD_CONTROL_COMMANDS = Object.freeze([
10
- "pause",
11
- "resume",
12
- "abort",
13
- "reassign",
14
- ]);
15
-
16
- function resolveFetch(fetchImpl) {
17
- if (typeof fetchImpl === "function") return fetchImpl;
18
- if (typeof globalThis.fetch === "function")
19
- return globalThis.fetch.bind(globalThis);
20
- return null;
21
- }
22
-
23
- function normalizeHubBaseUrl(hubUrl) {
24
- return String(hubUrl || "")
25
- .replace(/\/+$/, "")
26
- .replace(/\/mcp$/, "");
27
- }
28
-
29
- function normalizeCommand(command) {
30
- const raw = String(command || "")
31
- .trim()
32
- .toLowerCase();
33
- if (!raw) return "";
34
- return CONTROL_COMMAND_ALIASES[raw] || raw;
35
- }
36
-
37
- function safeAbortSignal(timeoutMs) {
38
- if (typeof AbortSignal?.timeout !== "function") return undefined;
39
- return AbortSignal.timeout(timeoutMs);
40
- }
41
-
42
- function isPidAlive(pid) {
43
- const resolvedPid = Number(pid);
44
- if (!Number.isFinite(resolvedPid) || resolvedPid <= 0) return false;
45
- try {
46
- process.kill(resolvedPid, 0);
47
- return true;
48
- } catch {
49
- return false;
50
- }
51
- }
52
-
53
- function resolveHeadlessHubUrl() {
54
- const stateUrl = normalizeHubBaseUrl(readState()?.url);
55
- if (stateUrl) return stateUrl;
56
-
57
- const envHubUrl = normalizeHubBaseUrl(process.env.TFX_HUB_URL);
58
- if (envHubUrl) return envHubUrl;
59
-
60
- const envPort = Number(process.env.TFX_HUB_PORT || "27888");
61
- const port = Number.isFinite(envPort) && envPort > 0 ? envPort : 27888;
62
- return `http://127.0.0.1:${port}`;
63
- }
64
-
65
- function hasLiveHubState() {
66
- const state = readState();
67
- return !!normalizeHubBaseUrl(state?.url) && isPidAlive(state?.pid);
68
- }
69
-
70
- function toHeadlessSessionAgentId(sessionName) {
71
- const normalizedSessionName = String(sessionName || "").trim();
72
- return normalizedSessionName ? `session:${normalizedSessionName}` : "";
73
- }
74
-
75
- function safeJson(res) {
76
- return res.json().catch(() => ({}));
77
- }
78
-
79
- export async function publishLeadControl({
80
- hubUrl,
81
- fromAgent = "lead",
82
- toAgent,
83
- command,
84
- reason = "",
85
- payload = {},
86
- traceId,
87
- correlationId,
88
- ttlMs = 3600000,
89
- timeoutMs = DEFAULT_TIMEOUT_MS,
90
- fetchImpl,
91
- } = {}) {
92
- const requestFetch = resolveFetch(fetchImpl);
93
- if (!requestFetch) {
94
- return { ok: false, error: "FETCH_UNAVAILABLE" };
95
- }
96
-
97
- const hubBase = normalizeHubBaseUrl(hubUrl);
98
- if (!hubBase) {
99
- return { ok: false, error: "HUB_URL_REQUIRED" };
100
- }
101
-
102
- const targetAgent = String(toAgent || "").trim();
103
- if (!targetAgent) {
104
- return { ok: false, error: "TARGET_AGENT_REQUIRED" };
105
- }
106
-
107
- const normalizedCommand = normalizeCommand(command);
108
- if (!LEAD_CONTROL_COMMANDS.includes(normalizedCommand)) {
109
- return {
110
- ok: false,
111
- error: "INVALID_COMMAND",
112
- allowed: LEAD_CONTROL_COMMANDS,
113
- command: normalizedCommand,
114
- };
115
- }
116
-
117
- try {
118
- const res = await requestFetch(`${hubBase}/bridge/control`, {
119
- method: "POST",
120
- headers: { "Content-Type": "application/json" },
121
- body: JSON.stringify({
122
- from_agent: String(fromAgent || "lead"),
123
- to_agent: targetAgent,
124
- command: normalizedCommand,
125
- reason: String(reason || ""),
126
- payload: payload && typeof payload === "object" ? payload : {},
127
- ttl_ms: ttlMs,
128
- trace_id: traceId,
129
- correlation_id: correlationId,
130
- }),
131
- signal: safeAbortSignal(timeoutMs),
132
- });
133
-
134
- const body = await safeJson(res);
135
- return {
136
- ok: res.ok && body?.ok !== false,
137
- status: res.status,
138
- body,
139
- command: normalizedCommand,
140
- };
141
- } catch (error) {
142
- return {
143
- ok: false,
144
- error: "CONTROL_PUBLISH_FAILED",
145
- message: error?.message || "control publish failed",
146
- command: normalizedCommand,
147
- };
148
- }
149
- }
150
-
151
- export async function publishHeadlessControl(
152
- sessionName,
153
- command,
154
- targetWorker = "*",
155
- ) {
156
- const sessionAgentId = toHeadlessSessionAgentId(sessionName);
157
- if (!sessionAgentId) {
158
- return {
159
- ok: false,
160
- noop: true,
161
- error: "SESSION_NAME_REQUIRED",
162
- command: String(command || "")
163
- .trim()
164
- .toLowerCase(),
165
- };
166
- }
167
-
168
- if (!normalizeHubBaseUrl(process.env.TFX_HUB_URL) && !hasLiveHubState()) {
169
- return {
170
- ok: false,
171
- noop: true,
172
- error: "HUB_UNAVAILABLE",
173
- command: String(command || "")
174
- .trim()
175
- .toLowerCase(),
176
- };
177
- }
178
-
179
- return await publishLeadControl({
180
- hubUrl: resolveHeadlessHubUrl(),
181
- fromAgent: "lead",
182
- toAgent: sessionAgentId,
183
- command,
184
- payload: {
185
- session_name: String(sessionName || "").trim(),
186
- target_worker: String(targetWorker || "*").trim() || "*",
187
- issued_by: "lead",
188
- issued_at: Date.now(),
189
- },
190
- });
191
- }