@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.
- package/hub/server.mjs +0 -122
- package/hub/team/claude-daemon-control.mjs +54 -6
- package/hub/team/claude-transcript.mjs +240 -0
- package/hub/team/cli/commands/start/index.mjs +17 -3
- package/hub/team/cli/commands/start/parse-args.mjs +1 -0
- package/hub/team/session-context.mjs +203 -0
- package/hub/team/uds-orchestrator.mjs +63 -1
- package/hub/team/worktree-lifecycle.mjs +0 -1
- package/package.json +2 -3
- package/scripts/lib/doctor-env-checks.mjs +0 -85
- package/scripts/lib/legacy-hook-cleanup.mjs +230 -0
- package/scripts/lib/legacy-mcp-cleanup.mjs +772 -0
- package/scripts/lib/mcp-guard-engine.mjs +44 -271
- package/scripts/lib/mcp-manifest.mjs +2 -2
- 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/scripts/lib/cross-review-utils.mjs +0 -59
- package/scripts/lib/hook-utils.mjs +0 -16
- package/scripts/lib/keyword-rules.mjs +0 -219
- package/scripts/lib/mcp-gateway-health-check.mjs +0 -224
- package/scripts/lib/mcp-gateway-servers.mjs +0 -70
- package/scripts/lib/mcp-gateway-wrapper-check.mjs +0 -35
package/hub/tray-lifecycle.mjs
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
// hub/tray-lifecycle.mjs — Hub/Tray bidirectional auto-start helpers
|
|
2
|
-
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
4
|
-
import {
|
|
5
|
-
isWorktreeOrEphemeralHubContext,
|
|
6
|
-
resolveHubPortForContext,
|
|
7
|
-
} from "./hub-lifecycle.mjs";
|
|
8
|
-
|
|
9
|
-
const DEFAULT_HUB_PORT = "27888";
|
|
10
|
-
const DEFAULT_HEALTH_TIMEOUT_MS = 1_000;
|
|
11
|
-
const DEFAULT_READY_TIMEOUT_MS = 3_000;
|
|
12
|
-
const DEFAULT_READY_POLL_MS = 200;
|
|
13
|
-
|
|
14
|
-
function formatHost(host = "127.0.0.1") {
|
|
15
|
-
return String(host).includes(":") ? `[${host}]` : host;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function hubHealthUrl({ host = "127.0.0.1", port = DEFAULT_HUB_PORT } = {}) {
|
|
19
|
-
return `http://${formatHost(host)}:${port}/health`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async function isHubHealthy({
|
|
23
|
-
host = "127.0.0.1",
|
|
24
|
-
port = DEFAULT_HUB_PORT,
|
|
25
|
-
fetchFn = globalThis.fetch,
|
|
26
|
-
timeoutMs = DEFAULT_HEALTH_TIMEOUT_MS,
|
|
27
|
-
} = {}) {
|
|
28
|
-
if (typeof fetchFn !== "function") return false;
|
|
29
|
-
try {
|
|
30
|
-
const response = await fetchFn(hubHealthUrl({ host, port }), {
|
|
31
|
-
signal:
|
|
32
|
-
typeof AbortSignal !== "undefined" &&
|
|
33
|
-
typeof AbortSignal.timeout === "function"
|
|
34
|
-
? AbortSignal.timeout(timeoutMs)
|
|
35
|
-
: undefined,
|
|
36
|
-
});
|
|
37
|
-
if (!response?.ok) return false;
|
|
38
|
-
const body =
|
|
39
|
-
typeof response.json === "function" ? await response.json() : {};
|
|
40
|
-
return body?.ok !== false;
|
|
41
|
-
} catch {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function waitForHubHealth(options = {}) {
|
|
47
|
-
const deadline =
|
|
48
|
-
Date.now() + (options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS);
|
|
49
|
-
do {
|
|
50
|
-
if (await isHubHealthy(options)) return true;
|
|
51
|
-
await new Promise((resolve) =>
|
|
52
|
-
setTimeout(resolve, options.readyPollMs ?? DEFAULT_READY_POLL_MS),
|
|
53
|
-
);
|
|
54
|
-
} while (Date.now() < deadline);
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export async function ensureHubForTray({
|
|
59
|
-
host = "127.0.0.1",
|
|
60
|
-
port,
|
|
61
|
-
serverPath,
|
|
62
|
-
nodePath = process.execPath,
|
|
63
|
-
env = process.env,
|
|
64
|
-
cwd = process.cwd(),
|
|
65
|
-
fetchFn = globalThis.fetch,
|
|
66
|
-
spawnFn = spawn,
|
|
67
|
-
waitForHealth = true,
|
|
68
|
-
readyTimeoutMs = DEFAULT_READY_TIMEOUT_MS,
|
|
69
|
-
readyPollMs = DEFAULT_READY_POLL_MS,
|
|
70
|
-
} = {}) {
|
|
71
|
-
const targetPort = String(
|
|
72
|
-
resolveHubPortForContext({
|
|
73
|
-
port,
|
|
74
|
-
env,
|
|
75
|
-
cwd,
|
|
76
|
-
defaultPort: Number(DEFAULT_HUB_PORT),
|
|
77
|
-
}),
|
|
78
|
-
);
|
|
79
|
-
if (await isHubHealthy({ host, port: targetPort, fetchFn })) {
|
|
80
|
-
return { status: "running" };
|
|
81
|
-
}
|
|
82
|
-
if (!serverPath) return { status: "missing-server-path" };
|
|
83
|
-
|
|
84
|
-
const child = spawnFn(nodePath, [serverPath], {
|
|
85
|
-
env: {
|
|
86
|
-
...env,
|
|
87
|
-
TFX_HUB_PORT: targetPort,
|
|
88
|
-
// The tray explicitly started this hub, so the hub must not spawn a
|
|
89
|
-
// second tray back at us.
|
|
90
|
-
TFX_HUB_AUTO_TRAY: "0",
|
|
91
|
-
},
|
|
92
|
-
detached: true,
|
|
93
|
-
stdio: "ignore",
|
|
94
|
-
windowsHide: true,
|
|
95
|
-
});
|
|
96
|
-
if (typeof child?.unref === "function") child.unref();
|
|
97
|
-
|
|
98
|
-
if (waitForHealth) {
|
|
99
|
-
const healthy = await waitForHubHealth({
|
|
100
|
-
host,
|
|
101
|
-
port: targetPort,
|
|
102
|
-
fetchFn,
|
|
103
|
-
readyTimeoutMs,
|
|
104
|
-
readyPollMs,
|
|
105
|
-
});
|
|
106
|
-
return {
|
|
107
|
-
status: healthy ? "started" : "starting",
|
|
108
|
-
pid: child?.pid ?? null,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return { status: "started", pid: child?.pid ?? null };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export function spawnTrayForHub({
|
|
116
|
-
platform = process.platform,
|
|
117
|
-
trayPath,
|
|
118
|
-
nodePath = process.execPath,
|
|
119
|
-
env = process.env,
|
|
120
|
-
cwd = process.cwd(),
|
|
121
|
-
spawnFn = spawn,
|
|
122
|
-
} = {}) {
|
|
123
|
-
if (env?.TFX_HUB_AUTO_TRAY !== "1") return { status: "disabled" };
|
|
124
|
-
if (isWorktreeOrEphemeralHubContext({ cwd, env })) {
|
|
125
|
-
return { status: "disabled", reason: "ephemeral-or-worktree-context" };
|
|
126
|
-
}
|
|
127
|
-
if (platform !== "darwin") return { status: "unsupported-platform" };
|
|
128
|
-
if (!trayPath) return { status: "missing-tray-path" };
|
|
129
|
-
|
|
130
|
-
const child = spawnFn(nodePath, [trayPath], {
|
|
131
|
-
env,
|
|
132
|
-
detached: true,
|
|
133
|
-
stdio: "ignore",
|
|
134
|
-
windowsHide: true,
|
|
135
|
-
});
|
|
136
|
-
if (typeof child?.unref === "function") child.unref();
|
|
137
|
-
return { status: "started", pid: child?.pid ?? null };
|
|
138
|
-
}
|
package/hub/tray-runtime.mjs
DELETED
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
// hub/tray-runtime.mjs — lightweight local CLI runtime detection for tray CTO fallback
|
|
2
|
-
|
|
3
|
-
import { execFileSync } from "node:child_process";
|
|
4
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
5
|
-
import { homedir } from "node:os";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
|
|
8
|
-
const RUNTIME_CLIENTS = Object.freeze([
|
|
9
|
-
{ id: "claude", label: "Claude", icon: "C", color: "#d19a66" },
|
|
10
|
-
{ id: "codex", label: "Codex", icon: "X", color: "#ffffff" },
|
|
11
|
-
{ id: "antigravity", label: "Antigravity", icon: "A", color: "#61afef" },
|
|
12
|
-
]);
|
|
13
|
-
const AGY_CONVERSATION_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
14
|
-
|
|
15
|
-
function basename(token = "") {
|
|
16
|
-
return String(token).split(/[\\/]/).pop() || "";
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function classifyCommand(command = "") {
|
|
20
|
-
const text = String(command).trim();
|
|
21
|
-
if (!text) return null;
|
|
22
|
-
const executable = basename(text.split(/\s+/)[0]);
|
|
23
|
-
|
|
24
|
-
if (executable === "claude") {
|
|
25
|
-
if (/\bdaemon\s+run\b/.test(text)) return null;
|
|
26
|
-
return "claude";
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (executable === "codex") {
|
|
30
|
-
if (/\bapp-server\b/.test(text)) return null;
|
|
31
|
-
return "codex";
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (/^antigravity(?:-cli)?$/i.test(executable)) return "antigravity";
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function cwdFromMap(cwdByPid, pid) {
|
|
39
|
-
if (!cwdByPid || !Number.isFinite(pid)) return "";
|
|
40
|
-
if (cwdByPid instanceof Map) return String(cwdByPid.get(pid) || "");
|
|
41
|
-
return String(cwdByPid[pid] || cwdByPid[String(pid)] || "");
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function isGenericProjectsCwd(cwd = "") {
|
|
45
|
-
return /^\/Users\/[^/]+\/Projects\/?$/u.test(String(cwd || "").trim());
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function inferProjectRootFromCommand(command = "") {
|
|
49
|
-
const text = String(command || "");
|
|
50
|
-
const match = text.match(
|
|
51
|
-
/model_instructions_file=(?:"([^"]+)"|'([^']+)'|(\S+))/u,
|
|
52
|
-
);
|
|
53
|
-
const filePath = match?.[1] || match?.[2] || match?.[3] || "";
|
|
54
|
-
if (!filePath) return "";
|
|
55
|
-
const marker = filePath.match(/^(.*?)\/\.(?:omx|omc)\/state\/sessions\//u);
|
|
56
|
-
return marker?.[1] || "";
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function resolveProjectCwd({ command = "", cwd = "", parentCwd = "" } = {}) {
|
|
60
|
-
const inferred = inferProjectRootFromCommand(command);
|
|
61
|
-
if (inferred) return inferred;
|
|
62
|
-
if (cwd && (!isGenericProjectsCwd(cwd) || !parentCwd)) return cwd;
|
|
63
|
-
if (parentCwd) return parentCwd;
|
|
64
|
-
return cwd || "";
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function parseLsofCwdOutput(output = "") {
|
|
68
|
-
const cwdByPid = new Map();
|
|
69
|
-
let currentPid = null;
|
|
70
|
-
for (const line of String(output || "").split(/\r?\n/u)) {
|
|
71
|
-
if (line.startsWith("p")) {
|
|
72
|
-
const pid = Number.parseInt(line.slice(1), 10);
|
|
73
|
-
currentPid = Number.isFinite(pid) ? pid : null;
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (line.startsWith("n") && currentPid) {
|
|
77
|
-
cwdByPid.set(currentPid, line.slice(1));
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return cwdByPid;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function collectRuntimeProcesses(psOutput = "", options = {}) {
|
|
84
|
-
const cwdByPid = options?.cwdByPid;
|
|
85
|
-
const seen = new Set();
|
|
86
|
-
const processes = [];
|
|
87
|
-
for (const line of String(psOutput).split(/\r?\n/)) {
|
|
88
|
-
const match = line.match(/^\s*(\d+)\s+(\d+)\s+(\S+)\s+(.+)$/);
|
|
89
|
-
if (!match) continue;
|
|
90
|
-
const [, pidText, ppidText, elapsed, command] = match;
|
|
91
|
-
const client = classifyCommand(command);
|
|
92
|
-
const pid = Number.parseInt(pidText, 10);
|
|
93
|
-
if (!client || !Number.isFinite(pid) || seen.has(pid)) continue;
|
|
94
|
-
seen.add(pid);
|
|
95
|
-
const ppid = Number.parseInt(ppidText, 10) || null;
|
|
96
|
-
const cwd = resolveProjectCwd({
|
|
97
|
-
command,
|
|
98
|
-
cwd: cwdFromMap(cwdByPid, pid),
|
|
99
|
-
parentCwd: cwdFromMap(cwdByPid, ppid),
|
|
100
|
-
});
|
|
101
|
-
processes.push({
|
|
102
|
-
client,
|
|
103
|
-
pid,
|
|
104
|
-
ppid,
|
|
105
|
-
elapsed,
|
|
106
|
-
command: command.trim(),
|
|
107
|
-
...(cwd ? { cwd } : {}),
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
return processes;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function summarizeRuntimeProcesses(processes = []) {
|
|
114
|
-
const safeProcesses = Array.isArray(processes) ? processes : [];
|
|
115
|
-
return {
|
|
116
|
-
total: safeProcesses.length,
|
|
117
|
-
clients: RUNTIME_CLIENTS.map((client) => {
|
|
118
|
-
const count = safeProcesses.filter(
|
|
119
|
-
(process) => process.client === client.id,
|
|
120
|
-
).length;
|
|
121
|
-
return { ...client, count, status: count > 0 ? "on" : "off" };
|
|
122
|
-
}),
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function normalizeProjectRoots(projectRoots = []) {
|
|
127
|
-
return (Array.isArray(projectRoots) ? projectRoots : [projectRoots])
|
|
128
|
-
.map((root) => String(root || "").replace(/\/+$/u, ""))
|
|
129
|
-
.filter(Boolean);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function isUnderProjectRoots(cwd = "", roots = []) {
|
|
133
|
-
if (roots.length === 0) return true;
|
|
134
|
-
const path = String(cwd || "").replace(/\/+$/u, "");
|
|
135
|
-
return roots.some((root) => path === root || path.startsWith(`${root}/`));
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function conversationFile(baseDir, conversationId) {
|
|
139
|
-
for (const ext of [".db", ".db-wal", ".db-shm", ".pb"]) {
|
|
140
|
-
const filePath = join(baseDir, "conversations", `${conversationId}${ext}`);
|
|
141
|
-
if (existsSync(filePath)) return filePath;
|
|
142
|
-
}
|
|
143
|
-
return "";
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export function collectAntigravityConversations(options = {}) {
|
|
147
|
-
const baseDir =
|
|
148
|
-
options.baseDir || join(homedir(), ".gemini", "antigravity-cli");
|
|
149
|
-
const projectRoots = normalizeProjectRoots(options.projectRoots || []);
|
|
150
|
-
const maxAgeMs = Number.isFinite(Number(options.maxAgeMs))
|
|
151
|
-
? Number(options.maxAgeMs)
|
|
152
|
-
: AGY_CONVERSATION_MAX_AGE_MS;
|
|
153
|
-
const now = Number.isFinite(Number(options.now))
|
|
154
|
-
? Number(options.now)
|
|
155
|
-
: Date.now();
|
|
156
|
-
const limit = Number.isFinite(Number(options.limit))
|
|
157
|
-
? Number(options.limit)
|
|
158
|
-
: 12;
|
|
159
|
-
const cachePath = join(baseDir, "cache", "last_conversations.json");
|
|
160
|
-
if (!existsSync(cachePath)) return [];
|
|
161
|
-
|
|
162
|
-
try {
|
|
163
|
-
const cache = JSON.parse(readFileSync(cachePath, "utf8"));
|
|
164
|
-
const rows = Object.entries(cache || {}).flatMap(([cwd, rawId]) => {
|
|
165
|
-
const conversationId = String(rawId || "").trim();
|
|
166
|
-
if (!conversationId || !isUnderProjectRoots(cwd, projectRoots)) return [];
|
|
167
|
-
const filePath = conversationFile(baseDir, conversationId);
|
|
168
|
-
if (!filePath) return [];
|
|
169
|
-
let mtimeMs = 0;
|
|
170
|
-
try {
|
|
171
|
-
mtimeMs = statSync(filePath).mtimeMs;
|
|
172
|
-
} catch {
|
|
173
|
-
return [];
|
|
174
|
-
}
|
|
175
|
-
if (maxAgeMs > 0 && now - mtimeMs > maxAgeMs) return [];
|
|
176
|
-
return [
|
|
177
|
-
{
|
|
178
|
-
client: "antigravity",
|
|
179
|
-
sessionId: `antigravity-${conversationId.slice(0, 8)}`,
|
|
180
|
-
conversationId,
|
|
181
|
-
source: "conversation",
|
|
182
|
-
focusable: false,
|
|
183
|
-
pid: null,
|
|
184
|
-
ppid: null,
|
|
185
|
-
elapsed: "",
|
|
186
|
-
command: "agy conversation",
|
|
187
|
-
cwd: String(cwd),
|
|
188
|
-
taskSummary: "AGY conversation",
|
|
189
|
-
mtimeMs,
|
|
190
|
-
phase: "idle",
|
|
191
|
-
},
|
|
192
|
-
];
|
|
193
|
-
});
|
|
194
|
-
return rows
|
|
195
|
-
.sort((a, b) => b.mtimeMs - a.mtimeMs || a.cwd.localeCompare(b.cwd))
|
|
196
|
-
.slice(0, Math.max(0, limit));
|
|
197
|
-
} catch (_error) {
|
|
198
|
-
return [];
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export function buildRuntimeStatus(psOutput = "", options = {}) {
|
|
203
|
-
const processes = collectRuntimeProcesses(psOutput);
|
|
204
|
-
const conversations = collectAntigravityConversations(options);
|
|
205
|
-
const allProcesses = [...processes, ...conversations];
|
|
206
|
-
return {
|
|
207
|
-
processes: allProcesses,
|
|
208
|
-
summary: summarizeRuntimeProcesses(allProcesses),
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
export function collectCwdByPid(pids = []) {
|
|
213
|
-
const ids = [
|
|
214
|
-
...new Set(
|
|
215
|
-
pids
|
|
216
|
-
.map((pid) => Number.parseInt(pid, 10))
|
|
217
|
-
.filter((pid) => Number.isFinite(pid) && pid > 0),
|
|
218
|
-
),
|
|
219
|
-
];
|
|
220
|
-
if (ids.length === 0) return new Map();
|
|
221
|
-
try {
|
|
222
|
-
const output = execFileSync(
|
|
223
|
-
"lsof",
|
|
224
|
-
["-a", "-p", ids.join(","), "-d", "cwd", "-Fn"],
|
|
225
|
-
{
|
|
226
|
-
encoding: "utf8",
|
|
227
|
-
timeout: 1000,
|
|
228
|
-
maxBuffer: 1024 * 1024,
|
|
229
|
-
},
|
|
230
|
-
);
|
|
231
|
-
return parseLsofCwdOutput(output);
|
|
232
|
-
} catch (_error) {
|
|
233
|
-
return new Map();
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
export function getRuntimeStatus(options = {}) {
|
|
238
|
-
try {
|
|
239
|
-
const output = execFileSync("ps", ["-axo", "pid,ppid,etime,command"], {
|
|
240
|
-
encoding: "utf8",
|
|
241
|
-
timeout: 1000,
|
|
242
|
-
maxBuffer: 1024 * 1024,
|
|
243
|
-
});
|
|
244
|
-
const initialProcesses = collectRuntimeProcesses(output);
|
|
245
|
-
const cwdByPid = collectCwdByPid(
|
|
246
|
-
initialProcesses.flatMap((process) => [process.pid, process.ppid]),
|
|
247
|
-
);
|
|
248
|
-
const processes =
|
|
249
|
-
cwdByPid.size > 0
|
|
250
|
-
? collectRuntimeProcesses(output, { cwdByPid })
|
|
251
|
-
: initialProcesses;
|
|
252
|
-
const conversations = collectAntigravityConversations(options);
|
|
253
|
-
const allProcesses = [...processes, ...conversations];
|
|
254
|
-
return {
|
|
255
|
-
processes: allProcesses,
|
|
256
|
-
summary: summarizeRuntimeProcesses(allProcesses),
|
|
257
|
-
};
|
|
258
|
-
} catch (error) {
|
|
259
|
-
return {
|
|
260
|
-
processes: [],
|
|
261
|
-
summary: summarizeRuntimeProcesses([]),
|
|
262
|
-
error: error?.message || String(error),
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
}
|