@triflux/remote 10.48.1 → 10.49.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/server.mjs +0 -122
- package/package.json +2 -3
- package/scripts/lib/keyword-rules.mjs +83 -6
- package/hub/mac-focus.mjs +0 -94
- package/hub/mac-tray.swift +0 -144
- package/hub/public/tray-icon.ico +0 -0
- package/hub/public/tray-icon.png +0 -0
- package/hub/public/tray.html +0 -756
- package/hub/tray-lifecycle.mjs +0 -138
- package/hub/tray-runtime.mjs +0 -265
- package/hub/tray-state.mjs +0 -524
- package/hub/tray.mjs +0 -527
package/hub/tray-state.mjs
DELETED
|
@@ -1,524 +0,0 @@
|
|
|
1
|
-
// hub/tray-state.mjs — normalize Hub/CTO/MCP state for the macOS tray UI
|
|
2
|
-
|
|
3
|
-
function toIsoTime(value) {
|
|
4
|
-
if (typeof value === "string" && value.trim()) return value;
|
|
5
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
6
|
-
return new Date(value).toISOString();
|
|
7
|
-
}
|
|
8
|
-
return null;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function statusRank(status) {
|
|
12
|
-
switch (status) {
|
|
13
|
-
case "active":
|
|
14
|
-
return 0;
|
|
15
|
-
case "idle":
|
|
16
|
-
return 1;
|
|
17
|
-
case "starting":
|
|
18
|
-
return 2;
|
|
19
|
-
case "stale":
|
|
20
|
-
return 4;
|
|
21
|
-
default:
|
|
22
|
-
return 3;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function readSynapseSessions(snapshot) {
|
|
27
|
-
if (Array.isArray(snapshot)) return snapshot;
|
|
28
|
-
if (Array.isArray(snapshot?.sessions)) return snapshot.sessions;
|
|
29
|
-
if (snapshot && typeof snapshot === "object") {
|
|
30
|
-
return Object.values(snapshot).filter(
|
|
31
|
-
(entry) => entry && typeof entry === "object" && entry.sessionId,
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
return [];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const MCP_CLIENTS = Object.freeze([
|
|
38
|
-
{ id: "claude", label: "Claude", icon: "C", color: "#d19a66" },
|
|
39
|
-
{ id: "codex", label: "Codex", icon: "X", color: "#ffffff" },
|
|
40
|
-
{ id: "antigravity", label: "Antigravity", icon: "A", color: "#61afef" },
|
|
41
|
-
]);
|
|
42
|
-
|
|
43
|
-
function normalizeAgentId(value) {
|
|
44
|
-
const id = String(value || "")
|
|
45
|
-
.trim()
|
|
46
|
-
.toLowerCase();
|
|
47
|
-
if (id === "agy" || id === "anti") return "antigravity";
|
|
48
|
-
if (id === "claude" || id === "codex" || id === "antigravity") return id;
|
|
49
|
-
return "";
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function rawAgentId(value) {
|
|
53
|
-
return String(value ?? "").trim();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function inferAgentId(value = {}) {
|
|
57
|
-
const direct = normalizeAgentId(
|
|
58
|
-
value.agent_id ??
|
|
59
|
-
value.agentId ??
|
|
60
|
-
value.client ??
|
|
61
|
-
value.provider ??
|
|
62
|
-
value.runtimeClient,
|
|
63
|
-
);
|
|
64
|
-
if (direct) return direct;
|
|
65
|
-
const haystack = [
|
|
66
|
-
value.sessionId,
|
|
67
|
-
value.sessionKind,
|
|
68
|
-
value.role,
|
|
69
|
-
value.taskSummary,
|
|
70
|
-
value.command,
|
|
71
|
-
]
|
|
72
|
-
.filter(Boolean)
|
|
73
|
-
.join(" ")
|
|
74
|
-
.toLowerCase();
|
|
75
|
-
for (const id of ["claude", "codex", "antigravity"]) {
|
|
76
|
-
if (haystack.includes(id)) return id;
|
|
77
|
-
}
|
|
78
|
-
if (haystack.includes("agy")) return "antigravity";
|
|
79
|
-
return "";
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function agentMeta(agentId) {
|
|
83
|
-
const id = normalizeAgentId(agentId);
|
|
84
|
-
const meta = MCP_CLIENTS.find((client) => client.id === id);
|
|
85
|
-
return meta ? { ...meta } : null;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function normalizeTraySessions(snapshot) {
|
|
89
|
-
return readSynapseSessions(snapshot)
|
|
90
|
-
.map((session) => {
|
|
91
|
-
const directAgentId = session?.agent_id ?? session?.agentId;
|
|
92
|
-
const agentId =
|
|
93
|
-
normalizeAgentId(directAgentId) ||
|
|
94
|
-
rawAgentId(directAgentId) ||
|
|
95
|
-
inferAgentId(session);
|
|
96
|
-
return {
|
|
97
|
-
sessionId: String(session?.sessionId || ""),
|
|
98
|
-
host: typeof session?.host === "string" ? session.host : "local",
|
|
99
|
-
status:
|
|
100
|
-
typeof session?.status === "string"
|
|
101
|
-
? session.status
|
|
102
|
-
: typeof session?.phase === "string"
|
|
103
|
-
? session.phase
|
|
104
|
-
: "active",
|
|
105
|
-
sessionKind:
|
|
106
|
-
typeof session?.sessionKind === "string"
|
|
107
|
-
? session.sessionKind
|
|
108
|
-
: "interactive",
|
|
109
|
-
cwd: typeof session?.cwd === "string" ? session.cwd : "",
|
|
110
|
-
worktreePath:
|
|
111
|
-
typeof session?.worktreePath === "string" ? session.worktreePath : "",
|
|
112
|
-
branch: typeof session?.branch === "string" ? session.branch : "",
|
|
113
|
-
taskSummary:
|
|
114
|
-
typeof session?.taskSummary === "string" ? session.taskSummary : "",
|
|
115
|
-
agent_id: agentId || null,
|
|
116
|
-
agent: agentMeta(agentId),
|
|
117
|
-
lastHeartbeat: Number.isFinite(Number(session?.lastHeartbeat))
|
|
118
|
-
? Number(session.lastHeartbeat)
|
|
119
|
-
: null,
|
|
120
|
-
started_at: toIsoTime(
|
|
121
|
-
session?.started_at ?? session?.startedAt ?? session?.lastHeartbeat,
|
|
122
|
-
),
|
|
123
|
-
isRemote: session?.isRemote === true,
|
|
124
|
-
};
|
|
125
|
-
})
|
|
126
|
-
.filter((session) => session.sessionId)
|
|
127
|
-
.sort((a, b) => {
|
|
128
|
-
const rankDelta = statusRank(a.status) - statusRank(b.status);
|
|
129
|
-
if (rankDelta !== 0) return rankDelta;
|
|
130
|
-
return (b.lastHeartbeat || 0) - (a.lastHeartbeat || 0);
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function countBy(items, pickKey) {
|
|
135
|
-
const counts = {};
|
|
136
|
-
for (const item of Array.isArray(items) ? items : []) {
|
|
137
|
-
const key = pickKey(item) || "unknown";
|
|
138
|
-
counts[key] = (counts[key] || 0) + 1;
|
|
139
|
-
}
|
|
140
|
-
return counts;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function sanitizeMcpRow(row) {
|
|
144
|
-
return {
|
|
145
|
-
type: String(row?.type || ""),
|
|
146
|
-
name: String(row?.name || ""),
|
|
147
|
-
client: String(row?.client || ""),
|
|
148
|
-
label: String(row?.label || ""),
|
|
149
|
-
filePath: String(row?.filePath || ""),
|
|
150
|
-
expectedUrl: String(row?.expectedUrl || ""),
|
|
151
|
-
expectedCommand: String(row?.expectedCommand || ""),
|
|
152
|
-
actualUrl: String(row?.actualUrl || ""),
|
|
153
|
-
actualCommand: String(row?.actualCommand || ""),
|
|
154
|
-
status: String(row?.status || ""),
|
|
155
|
-
headerStatus: String(row?.headerStatus || ""),
|
|
156
|
-
expectedHeaderNames: Array.isArray(row?.expectedHeaderNames)
|
|
157
|
-
? row.expectedHeaderNames.map(String)
|
|
158
|
-
: [],
|
|
159
|
-
actualHeaderNames: Array.isArray(row?.actualHeaderNames)
|
|
160
|
-
? row.actualHeaderNames.map(String)
|
|
161
|
-
: [],
|
|
162
|
-
command: row?.command ? String(row.command) : "",
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function summarizeMcpClients(rows) {
|
|
167
|
-
return MCP_CLIENTS.map((client) => {
|
|
168
|
-
const clientRows = rows.filter(
|
|
169
|
-
(row) => row.type === "registry" && row.client === client.id,
|
|
170
|
-
);
|
|
171
|
-
const present = clientRows.filter(
|
|
172
|
-
(row) => row.status === "present" || row.status === "ok",
|
|
173
|
-
).length;
|
|
174
|
-
const total = clientRows.length;
|
|
175
|
-
let status = "off";
|
|
176
|
-
if (total > 0 && present === total) status = "on";
|
|
177
|
-
else if (present > 0) status = "partial";
|
|
178
|
-
return {
|
|
179
|
-
...client,
|
|
180
|
-
status,
|
|
181
|
-
present,
|
|
182
|
-
total,
|
|
183
|
-
};
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function summarizeMcpServers(rows) {
|
|
188
|
-
const grouped = new Map();
|
|
189
|
-
for (const row of rows) {
|
|
190
|
-
if (row.type !== "registry" || !row.name) continue;
|
|
191
|
-
if (!grouped.has(row.name)) {
|
|
192
|
-
grouped.set(row.name, {
|
|
193
|
-
name: row.name,
|
|
194
|
-
target:
|
|
195
|
-
row.expectedUrl ||
|
|
196
|
-
row.actualUrl ||
|
|
197
|
-
row.expectedCommand ||
|
|
198
|
-
row.actualCommand ||
|
|
199
|
-
"",
|
|
200
|
-
rows: [],
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
const group = grouped.get(row.name);
|
|
204
|
-
if (!group.target) {
|
|
205
|
-
group.target =
|
|
206
|
-
row.expectedUrl ||
|
|
207
|
-
row.actualUrl ||
|
|
208
|
-
row.expectedCommand ||
|
|
209
|
-
row.actualCommand ||
|
|
210
|
-
"";
|
|
211
|
-
}
|
|
212
|
-
group.rows.push(row);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return [...grouped.values()]
|
|
216
|
-
.map((server) => {
|
|
217
|
-
const clients = MCP_CLIENTS.map((client) => {
|
|
218
|
-
const clientRows = server.rows.filter(
|
|
219
|
-
(row) => row.client === client.id,
|
|
220
|
-
);
|
|
221
|
-
if (clientRows.length === 0) return null;
|
|
222
|
-
const present = clientRows.filter(
|
|
223
|
-
(row) => row.status === "present" || row.status === "ok",
|
|
224
|
-
).length;
|
|
225
|
-
let status = "off";
|
|
226
|
-
if (present === clientRows.length) status = "on";
|
|
227
|
-
else if (present > 0) status = "partial";
|
|
228
|
-
return {
|
|
229
|
-
...client,
|
|
230
|
-
status,
|
|
231
|
-
present,
|
|
232
|
-
total: clientRows.length,
|
|
233
|
-
labels: [
|
|
234
|
-
...new Set(clientRows.map((row) => row.label).filter(Boolean)),
|
|
235
|
-
],
|
|
236
|
-
};
|
|
237
|
-
}).filter(Boolean);
|
|
238
|
-
const onCount = clients.filter((client) => client.status === "on").length;
|
|
239
|
-
const partialCount = clients.filter(
|
|
240
|
-
(client) => client.status === "partial",
|
|
241
|
-
).length;
|
|
242
|
-
let status = "off";
|
|
243
|
-
if (clients.length > 0 && onCount === clients.length) status = "on";
|
|
244
|
-
else if (onCount > 0 || partialCount > 0) status = "partial";
|
|
245
|
-
return {
|
|
246
|
-
name: server.name,
|
|
247
|
-
target: server.target,
|
|
248
|
-
status,
|
|
249
|
-
clients,
|
|
250
|
-
client_count: clients.length,
|
|
251
|
-
present_client_count: onCount,
|
|
252
|
-
};
|
|
253
|
-
})
|
|
254
|
-
.sort((a, b) => {
|
|
255
|
-
if (a.name === "tfx-hub") return -1;
|
|
256
|
-
if (b.name === "tfx-hub") return 1;
|
|
257
|
-
return a.name.localeCompare(b.name);
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function normalizeMcpStatus(mcpStatus = {}) {
|
|
262
|
-
const rows = Array.isArray(mcpStatus?.rows)
|
|
263
|
-
? mcpStatus.rows.map(sanitizeMcpRow)
|
|
264
|
-
: [];
|
|
265
|
-
return {
|
|
266
|
-
registry_path: String(
|
|
267
|
-
mcpStatus?.registry_path || mcpStatus?.registryPath || "",
|
|
268
|
-
),
|
|
269
|
-
server_count: Number.isFinite(Number(mcpStatus?.server_count))
|
|
270
|
-
? Number(mcpStatus.server_count)
|
|
271
|
-
: Number.isFinite(Number(mcpStatus?.serverCount))
|
|
272
|
-
? Number(mcpStatus.serverCount)
|
|
273
|
-
: 0,
|
|
274
|
-
rows,
|
|
275
|
-
clients: summarizeMcpClients(rows),
|
|
276
|
-
servers: summarizeMcpServers(rows),
|
|
277
|
-
summary: {
|
|
278
|
-
total: rows.length,
|
|
279
|
-
by_status: countBy(rows, (row) => row.status),
|
|
280
|
-
by_client: countBy(rows, (row) => row.client || row.label),
|
|
281
|
-
},
|
|
282
|
-
...(mcpStatus?.error ? { error: String(mcpStatus.error) } : {}),
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function normalizeHub(hub = {}) {
|
|
287
|
-
const id = String(hub?.id ?? hub?.sessionId ?? hub?.session_id ?? "");
|
|
288
|
-
return {
|
|
289
|
-
id,
|
|
290
|
-
pid: Number.isFinite(Number(hub?.pid)) ? Number(hub.pid) : null,
|
|
291
|
-
port: Number.isFinite(Number(hub?.port)) ? Number(hub.port) : null,
|
|
292
|
-
url: String(hub?.url || ""),
|
|
293
|
-
projectRoot: String(hub?.projectRoot || hub?.project_root || ""),
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function numericPid(value) {
|
|
298
|
-
const text = String(value ?? "").trim();
|
|
299
|
-
if (!text) return null;
|
|
300
|
-
const pid = Number(text);
|
|
301
|
-
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function normalizeRuntimeStatus(runtimeStatus = {}) {
|
|
305
|
-
const processes = Array.isArray(runtimeStatus?.processes)
|
|
306
|
-
? runtimeStatus.processes.map((process) => ({
|
|
307
|
-
client: String(process?.client || ""),
|
|
308
|
-
sessionId: String(process?.sessionId || ""),
|
|
309
|
-
source: String(process?.source || ""),
|
|
310
|
-
conversationId: String(process?.conversationId || ""),
|
|
311
|
-
focusable: process?.focusable !== false,
|
|
312
|
-
pid: numericPid(process?.pid),
|
|
313
|
-
ppid: Number.isFinite(Number(process?.ppid))
|
|
314
|
-
? Number(process.ppid)
|
|
315
|
-
: null,
|
|
316
|
-
phase:
|
|
317
|
-
typeof process?.phase === "string"
|
|
318
|
-
? process.phase
|
|
319
|
-
: process?.source === "conversation"
|
|
320
|
-
? "idle"
|
|
321
|
-
: "active",
|
|
322
|
-
elapsed: String(process?.elapsed || ""),
|
|
323
|
-
command: String(process?.command || ""),
|
|
324
|
-
cwd: String(process?.cwd || ""),
|
|
325
|
-
taskSummary: String(process?.taskSummary || process?.command || ""),
|
|
326
|
-
}))
|
|
327
|
-
: [];
|
|
328
|
-
return {
|
|
329
|
-
processes: processes.filter(
|
|
330
|
-
(process) =>
|
|
331
|
-
process.client &&
|
|
332
|
-
(process.pid || process.sessionId || process.conversationId),
|
|
333
|
-
),
|
|
334
|
-
summary: runtimeStatus?.summary || {
|
|
335
|
-
total: processes.length,
|
|
336
|
-
clients: [],
|
|
337
|
-
},
|
|
338
|
-
...(runtimeStatus?.error ? { error: String(runtimeStatus.error) } : {}),
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
function sessionsToLiveOverlay(sessions = []) {
|
|
343
|
-
return sessions
|
|
344
|
-
.filter((session) => session.status !== "stale")
|
|
345
|
-
.map((session) => ({
|
|
346
|
-
sessionId: session.sessionId,
|
|
347
|
-
host: session.host,
|
|
348
|
-
agent_id: session.agent_id,
|
|
349
|
-
agent: session.agent,
|
|
350
|
-
phase: session.status,
|
|
351
|
-
started_at: session.started_at,
|
|
352
|
-
}));
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function runtimeToLiveOverlay(runtime) {
|
|
356
|
-
return runtime.processes.map((process) => ({
|
|
357
|
-
sessionId:
|
|
358
|
-
process.sessionId ||
|
|
359
|
-
(process.pid
|
|
360
|
-
? `${process.client}-${process.pid}`
|
|
361
|
-
: `${process.client}-${process.conversationId || "session"}`),
|
|
362
|
-
host: "local",
|
|
363
|
-
agent_id: process.client,
|
|
364
|
-
agent: agentMeta(process.client),
|
|
365
|
-
phase: process.phase || "active",
|
|
366
|
-
pid: process.pid,
|
|
367
|
-
command: process.command,
|
|
368
|
-
elapsed: process.elapsed,
|
|
369
|
-
cwd: process.cwd,
|
|
370
|
-
taskSummary: process.taskSummary || process.command || `PID ${process.pid}`,
|
|
371
|
-
source: process.source,
|
|
372
|
-
conversationId: process.conversationId,
|
|
373
|
-
focusable: process.focusable,
|
|
374
|
-
started_at: null,
|
|
375
|
-
}));
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
function normalizeLiveSession(session = {}) {
|
|
379
|
-
const directAgentId = session.agent_id ?? session.agentId;
|
|
380
|
-
const agentId =
|
|
381
|
-
normalizeAgentId(directAgentId) ||
|
|
382
|
-
rawAgentId(directAgentId) ||
|
|
383
|
-
inferAgentId(session);
|
|
384
|
-
return {
|
|
385
|
-
...session,
|
|
386
|
-
sessionId: String(session.sessionId || ""),
|
|
387
|
-
host: typeof session.host === "string" ? session.host : "local",
|
|
388
|
-
agent_id: agentId || null,
|
|
389
|
-
agent: session.agent || agentMeta(agentId),
|
|
390
|
-
phase:
|
|
391
|
-
typeof session.phase === "string"
|
|
392
|
-
? session.phase
|
|
393
|
-
: typeof session.status === "string"
|
|
394
|
-
? session.status
|
|
395
|
-
: "active",
|
|
396
|
-
started_at: toIsoTime(
|
|
397
|
-
session.started_at ?? session.startedAt ?? session.lastHeartbeat,
|
|
398
|
-
),
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
const CTO_HYGIENE_COUNT_KEYS = Object.freeze([
|
|
403
|
-
"active_tasks",
|
|
404
|
-
"completed_tasks",
|
|
405
|
-
"stale_sessions",
|
|
406
|
-
"orphan_worktrees",
|
|
407
|
-
"superseded_checkpoints",
|
|
408
|
-
"unknown_owner",
|
|
409
|
-
]);
|
|
410
|
-
|
|
411
|
-
function compactNumber(value) {
|
|
412
|
-
const number = Number(value);
|
|
413
|
-
return Number.isFinite(number) && number > 0 ? number : 0;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
function normalizeHygieneAction(action = {}) {
|
|
417
|
-
return {
|
|
418
|
-
kind: String(action.kind || ""),
|
|
419
|
-
id: String(action.id || ""),
|
|
420
|
-
status: String(action.status || ""),
|
|
421
|
-
action: String(action.action || ""),
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
function normalizeCtoHygiene(ctoStatus = {}) {
|
|
426
|
-
const hygiene =
|
|
427
|
-
ctoStatus?.hygiene && typeof ctoStatus.hygiene === "object"
|
|
428
|
-
? ctoStatus.hygiene
|
|
429
|
-
: null;
|
|
430
|
-
if (!hygiene) return null;
|
|
431
|
-
|
|
432
|
-
const actionsSource = Array.isArray(hygiene.actions)
|
|
433
|
-
? hygiene.actions
|
|
434
|
-
: Array.isArray(hygiene.rows)
|
|
435
|
-
? hygiene.rows
|
|
436
|
-
: Array.isArray(ctoStatus?.hygiene_actions)
|
|
437
|
-
? ctoStatus.hygiene_actions
|
|
438
|
-
: [];
|
|
439
|
-
const actions = actionsSource
|
|
440
|
-
.map(normalizeHygieneAction)
|
|
441
|
-
.filter(
|
|
442
|
-
(action) => action.kind || action.id || action.status || action.action,
|
|
443
|
-
);
|
|
444
|
-
|
|
445
|
-
return {
|
|
446
|
-
...Object.fromEntries(
|
|
447
|
-
CTO_HYGIENE_COUNT_KEYS.map((key) => [key, compactNumber(hygiene[key])]),
|
|
448
|
-
),
|
|
449
|
-
action_count: compactNumber(hygiene.action_count ?? actions.length),
|
|
450
|
-
actions: actions.slice(0, 2),
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
function normalizeCtoStatus(ctoStatus = {}, sessions = [], runtime = {}) {
|
|
455
|
-
const explicitLive = Array.isArray(ctoStatus?.live_sessions)
|
|
456
|
-
? ctoStatus.live_sessions
|
|
457
|
-
: null;
|
|
458
|
-
const sessionLive = sessionsToLiveOverlay(sessions);
|
|
459
|
-
const runtimeLive = runtimeToLiveOverlay(runtime);
|
|
460
|
-
const overlayLive =
|
|
461
|
-
explicitLive && explicitLive.length > 0
|
|
462
|
-
? explicitLive
|
|
463
|
-
: sessionLive.length > 0
|
|
464
|
-
? sessionLive
|
|
465
|
-
: runtimeLive;
|
|
466
|
-
const liveSessions =
|
|
467
|
-
runtimeLive.length > overlayLive.length ? runtimeLive : overlayLive;
|
|
468
|
-
const normalizedLiveSessions = liveSessions
|
|
469
|
-
.map(normalizeLiveSession)
|
|
470
|
-
.filter((session) => session.sessionId);
|
|
471
|
-
const activeShards = Array.isArray(ctoStatus?.active_shards)
|
|
472
|
-
? ctoStatus.active_shards
|
|
473
|
-
: [];
|
|
474
|
-
const hygiene = normalizeCtoHygiene(ctoStatus);
|
|
475
|
-
const roles =
|
|
476
|
-
ctoStatus?.roles && typeof ctoStatus.roles === "object"
|
|
477
|
-
? ctoStatus.roles
|
|
478
|
-
: null;
|
|
479
|
-
const succession =
|
|
480
|
-
ctoStatus?.succession && typeof ctoStatus.succession === "object"
|
|
481
|
-
? ctoStatus.succession
|
|
482
|
-
: null;
|
|
483
|
-
return {
|
|
484
|
-
schema_version: String(ctoStatus?.schema_version || "cto-lake.v1"),
|
|
485
|
-
generated_at: ctoStatus?.generated_at || null,
|
|
486
|
-
live_sessions: normalizedLiveSessions,
|
|
487
|
-
active_shards: activeShards,
|
|
488
|
-
...(roles ? { roles } : {}),
|
|
489
|
-
...(succession ? { succession } : {}),
|
|
490
|
-
...(hygiene ? { hygiene } : {}),
|
|
491
|
-
summary: {
|
|
492
|
-
live_session_count: normalizedLiveSessions.length,
|
|
493
|
-
active_shard_count: activeShards.length,
|
|
494
|
-
tray_session_count: sessions.length,
|
|
495
|
-
tray_sessions_by_status: countBy(sessions, (session) => session.status),
|
|
496
|
-
},
|
|
497
|
-
...(ctoStatus?.error ? { error: String(ctoStatus.error) } : {}),
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
export function buildTrayStatePayload({
|
|
502
|
-
hub = {},
|
|
503
|
-
qos = {},
|
|
504
|
-
synapseSnapshot = {},
|
|
505
|
-
broker = [],
|
|
506
|
-
ctoStatus = {},
|
|
507
|
-
mcpStatus = {},
|
|
508
|
-
runtimeStatus = {},
|
|
509
|
-
now = Date.now(),
|
|
510
|
-
} = {}) {
|
|
511
|
-
const sessions = normalizeTraySessions(synapseSnapshot);
|
|
512
|
-
const runtime = normalizeRuntimeStatus(runtimeStatus);
|
|
513
|
-
return {
|
|
514
|
-
ok: true,
|
|
515
|
-
hub: normalizeHub(hub),
|
|
516
|
-
qos,
|
|
517
|
-
sessions,
|
|
518
|
-
cto: normalizeCtoStatus(ctoStatus, sessions, runtime),
|
|
519
|
-
mcp: normalizeMcpStatus(mcpStatus),
|
|
520
|
-
runtime,
|
|
521
|
-
broker: Array.isArray(broker) ? broker : [],
|
|
522
|
-
ts: now,
|
|
523
|
-
};
|
|
524
|
-
}
|