@triflux/remote 10.48.1 → 10.50.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.
@@ -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
- }