@triflux/remote 10.51.0 → 10.53.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/brief.mjs +0 -8
- package/cto/collect.mjs +1 -127
- package/cto/current.schema.json +2 -19
- package/cto/hygiene.mjs +2 -61
- package/cto/lake-root.mjs +0 -56
- package/cto/status.mjs +5 -139
- package/hub/index.mjs +0 -9
- package/hub/team/agents-row-attach.py +120 -0
- package/hub/team/agents-row.mjs +200 -0
- package/hub/team/claude-daemon-control.mjs +1 -2
- package/hub/team/claude-session-projection.mjs +5 -73
- package/hub/team/claude-transcript.mjs +19 -0
- package/hub/team/cli/commands/debug.mjs +1 -2
- package/hub/team/cli/commands/start/index.mjs +2 -1
- package/hub/team/cli/commands/start/start-headless.mjs +2 -0
- package/hub/team/cli/commands/start/start-mux.mjs +2 -4
- package/hub/team/cli/services/state-store.mjs +5 -20
- package/hub/team/headless.mjs +132 -551
- package/hub/team/psmux.mjs +3 -33
- package/hub/team/results-index.mjs +92 -0
- package/hub/team/session-context.mjs +1 -1
- package/hub/team/tui-viewer.mjs +9 -56
- package/hub/team/tui.mjs +0 -37
- package/hub/team/uds-orchestrator.mjs +20 -599
- package/hub/workers/factory.mjs +6 -51
- package/hub/workers/interface.mjs +1 -7
- package/hub/workers/lib/jsonrpc-core.mjs +11 -0
- package/hub/workers/lib/jsonrpc-ws-uds.mjs +5 -6
- package/package.json +3 -8
- package/scripts/lib/agent-route-policy.mjs +0 -67
- package/scripts/lib/cli-claude.mjs +0 -12
- package/scripts/lib/env-probe.mjs +0 -70
- package/scripts/lib/legacy-mcp-cleanup.mjs +283 -0
- package/scripts/lib/mcp-guard-engine.mjs +42 -101
- package/hub/hub-lifecycle.mjs +0 -248
- package/hub/pipe.mjs +0 -825
- package/hub/promote-penalties.mjs +0 -138
- package/hub/public/dashboard.html +0 -416
- package/hub/schema.sql +0 -209
- package/hub/server.mjs +0 -3048
- package/hub/store-adapter.mjs +0 -182
- package/hub/store.mjs +0 -1736
- package/hub/team/cli/services/hub-client.mjs +0 -178
- package/hub/team/conductor-registry.mjs +0 -121
- package/hub/team/cto-auto-collect.mjs +0 -115
- package/hub/team/lead-control.mjs +0 -191
- package/hub/team/session-sync.mjs +0 -316
- package/hub/team/swarm-intent.mjs +0 -18
- package/hub/team/synapse-cli.mjs +0 -458
- package/hub/team/synapse-http.mjs +0 -311
- package/hub/team/synapse-registry.mjs +0 -618
- package/hub/team/tui-synapse.mjs +0 -223
- package/hub/team/worker-sandbox.mjs +0 -125
- package/hub/tools.mjs +0 -900
- package/hub/workers/codex-app-server-worker.mjs +0 -1067
- package/hub/workers/delegator-mcp.mjs +0 -1098
- package/hub/workers/lib/jsonrpc-stdio.mjs +0 -204
- package/scripts/lib/claudemd-scanner.mjs +0 -26
- package/scripts/lib/context.mjs +0 -51
- package/scripts/lib/handoff.mjs +0 -187
- package/scripts/lib/logger.mjs +0 -122
- package/scripts/lib/stealth-fetch.mjs +0 -176
package/hub/team/psmux.mjs
CHANGED
|
@@ -931,22 +931,8 @@ function killOrphanPipeHelpers(sessionName) {
|
|
|
931
931
|
*/
|
|
932
932
|
function killOrphanMcpProcesses(sessionName) {
|
|
933
933
|
if (!IS_WINDOWS) {
|
|
934
|
-
// macOS/Linux: 세션별 스코핑
|
|
934
|
+
// macOS/Linux: 세션별 스코핑
|
|
935
935
|
const safeSessionUnix = sanitizePathPart(sessionName);
|
|
936
|
-
let hubPidUnix = 0;
|
|
937
|
-
try {
|
|
938
|
-
const hubPidFile = join(
|
|
939
|
-
homedir(),
|
|
940
|
-
".claude",
|
|
941
|
-
"cache",
|
|
942
|
-
"tfx-hub",
|
|
943
|
-
"hub.pid",
|
|
944
|
-
);
|
|
945
|
-
if (existsSync(hubPidFile)) {
|
|
946
|
-
const info = JSON.parse(readFileSync(hubPidFile, "utf8"));
|
|
947
|
-
hubPidUnix = Number(info.pid) || 0;
|
|
948
|
-
}
|
|
949
|
-
} catch {}
|
|
950
936
|
try {
|
|
951
937
|
// MCP/result files live under `tfx-headless/<session>-<pane>.txt`, so
|
|
952
938
|
// match `tfx-headless/<session>` with a trailing boundary to avoid
|
|
@@ -963,7 +949,7 @@ function killOrphanMcpProcesses(sessionName) {
|
|
|
963
949
|
if (pids) {
|
|
964
950
|
for (const p of pids.split("\n").filter(Boolean)) {
|
|
965
951
|
const numPid = Number(p);
|
|
966
|
-
if (numPid
|
|
952
|
+
if (numPid <= 0) continue;
|
|
967
953
|
try {
|
|
968
954
|
process.kill(numPid, "SIGTERM");
|
|
969
955
|
} catch {}
|
|
@@ -975,22 +961,6 @@ function killOrphanMcpProcesses(sessionName) {
|
|
|
975
961
|
// Windows: escape + trailing boundary (mirror of the macOS branch).
|
|
976
962
|
const safeSession = escapeRegex(sanitizePathPart(sessionName));
|
|
977
963
|
|
|
978
|
-
// Hub PID 보호 — Hub 프로세스를 고아로 잘못 식별하지 않도록
|
|
979
|
-
let hubPid = null;
|
|
980
|
-
try {
|
|
981
|
-
const hubPidPath = join(
|
|
982
|
-
homedir(),
|
|
983
|
-
".claude",
|
|
984
|
-
"cache",
|
|
985
|
-
"tfx-hub",
|
|
986
|
-
"hub.pid",
|
|
987
|
-
);
|
|
988
|
-
if (existsSync(hubPidPath)) {
|
|
989
|
-
const hubInfo = JSON.parse(readFileSync(hubPidPath, "utf8"));
|
|
990
|
-
hubPid = Number(hubInfo?.pid);
|
|
991
|
-
}
|
|
992
|
-
} catch {}
|
|
993
|
-
|
|
994
964
|
try {
|
|
995
965
|
// 세션 결과 디렉토리 패턴으로 MCP 서버 프로세스 식별
|
|
996
966
|
const output = childProcess.execSync(
|
|
@@ -1005,7 +975,7 @@ function killOrphanMcpProcesses(sessionName) {
|
|
|
1005
975
|
const pids = output
|
|
1006
976
|
.split(/\r?\n/)
|
|
1007
977
|
.map((l) => Number.parseInt(l.trim(), 10))
|
|
1008
|
-
.filter((p) => Number.isFinite(p) && p > 0
|
|
978
|
+
.filter((p) => Number.isFinite(p) && p > 0);
|
|
1009
979
|
for (const pid of pids) {
|
|
1010
980
|
killProcessTree(pid);
|
|
1011
981
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// headless 세션 결과 색인. 화면의 HEADLESS_COMPLETE 문자열 대신 읽도록 세션당 JSON 하나를 둔다 (ADR-0034).
|
|
2
|
+
import { renameSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
export const RESULTS_INDEX_VERSION = 1;
|
|
6
|
+
|
|
7
|
+
export function resultsIndexPath(dir, sessionName) {
|
|
8
|
+
return join(dir, `${sessionName}.results.json`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} path
|
|
13
|
+
* @param {string} sessionName
|
|
14
|
+
* @param {Array<{paneName: string, displayName?: string, cli: string, role?: string, resultFile: string}>} workers
|
|
15
|
+
* @param {() => boolean} canWrite
|
|
16
|
+
*/
|
|
17
|
+
export function createResultsIndex(path, sessionName, workers, canWrite) {
|
|
18
|
+
const now = () => new Date().toISOString();
|
|
19
|
+
const index = {
|
|
20
|
+
version: RESULTS_INDEX_VERSION,
|
|
21
|
+
sessionName,
|
|
22
|
+
startedAt: now(),
|
|
23
|
+
finishedAt: null,
|
|
24
|
+
completed: false,
|
|
25
|
+
workers: workers.map((w) => ({
|
|
26
|
+
paneName: w.paneName,
|
|
27
|
+
displayName: w.displayName || w.paneName,
|
|
28
|
+
cli: w.cli,
|
|
29
|
+
role: w.role || "",
|
|
30
|
+
status: "pending",
|
|
31
|
+
matched: null,
|
|
32
|
+
exitCode: null,
|
|
33
|
+
sessionDead: false,
|
|
34
|
+
startedAt: null,
|
|
35
|
+
finishedAt: null,
|
|
36
|
+
resultFile: w.resultFile,
|
|
37
|
+
})),
|
|
38
|
+
};
|
|
39
|
+
const byPane = new Map(index.workers.map((w) => [w.paneName, w]));
|
|
40
|
+
|
|
41
|
+
// 임시 파일에 쓰고 rename 해 읽는 쪽이 반쯤 쓴 JSON 을 보지 않게 한다.
|
|
42
|
+
// 색인은 보조 산출물이라 쓰기 실패가 실행을 멈추면 안 된다.
|
|
43
|
+
const flush = () => {
|
|
44
|
+
if (!canWrite()) return;
|
|
45
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
46
|
+
try {
|
|
47
|
+
writeFileSync(tmp, `${JSON.stringify(index, null, 2)}\n`, "utf8");
|
|
48
|
+
renameSync(tmp, path);
|
|
49
|
+
} catch {
|
|
50
|
+
try {
|
|
51
|
+
rmSync(tmp, { force: true });
|
|
52
|
+
} catch {
|
|
53
|
+
/* best-effort */
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
path,
|
|
60
|
+
// 같은 이름의 이전 실행 색인이 남아 있으면 세션을 잡자마자 덮어 오판을 막는다.
|
|
61
|
+
begin: flush,
|
|
62
|
+
workerStarted(paneName) {
|
|
63
|
+
const w = byPane.get(paneName);
|
|
64
|
+
if (!w || w.status !== "pending") return;
|
|
65
|
+
w.status = "running";
|
|
66
|
+
w.startedAt = now();
|
|
67
|
+
flush();
|
|
68
|
+
},
|
|
69
|
+
workerFinished(paneName, { matched, exitCode, sessionDead } = {}) {
|
|
70
|
+
const w = byPane.get(paneName);
|
|
71
|
+
if (!w) return;
|
|
72
|
+
w.status = matched && exitCode === 0 ? "completed" : "failed";
|
|
73
|
+
w.matched = Boolean(matched);
|
|
74
|
+
w.exitCode = exitCode ?? null;
|
|
75
|
+
w.sessionDead = Boolean(sessionDead);
|
|
76
|
+
w.finishedAt = now();
|
|
77
|
+
flush();
|
|
78
|
+
},
|
|
79
|
+
finish(completed) {
|
|
80
|
+
// 예외로 끝나면 끝나지 못한 워커를 failed 로 확정해 "아직 도는 중"과 구분한다.
|
|
81
|
+
for (const w of index.workers) {
|
|
82
|
+
if (w.status === "pending" || w.status === "running") {
|
|
83
|
+
w.status = "failed";
|
|
84
|
+
w.finishedAt = now();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
index.finishedAt = now();
|
|
88
|
+
index.completed = Boolean(completed);
|
|
89
|
+
flush();
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
@@ -96,7 +96,7 @@ export function contextGuard(
|
|
|
96
96
|
: {};
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
async function findCodexRollout(threadId) {
|
|
99
|
+
export async function findCodexRollout(threadId) {
|
|
100
100
|
if (!/^[a-zA-Z0-9-]+$/u.test(threadId || "")) return null;
|
|
101
101
|
const root = path.join(
|
|
102
102
|
process.env.CODEX_HOME || path.join(homedir(), ".codex"),
|
package/hub/team/tui-viewer.mjs
CHANGED
|
@@ -25,12 +25,7 @@ function argVal(flag) {
|
|
|
25
25
|
const SESSION = argVal("--session");
|
|
26
26
|
const RESULT_DIR = argVal("--result-dir") ?? join(tmpdir(), "tfx-headless");
|
|
27
27
|
const LAYOUT = argVal("--layout") ?? "single";
|
|
28
|
-
const SOURCE = argVal("--source") === "files" ? "files" : "mux";
|
|
29
28
|
const LEAD_PANE = argVal("--lead-pane");
|
|
30
|
-
const parsedWorkerCount = Number.parseInt(argVal("--workers") || "0", 10);
|
|
31
|
-
const WORKER_COUNT = Number.isFinite(parsedWorkerCount)
|
|
32
|
-
? Math.min(64, Math.max(0, parsedWorkerCount))
|
|
33
|
-
: 0;
|
|
34
29
|
const MUX_BIN = getMultiplexerType();
|
|
35
30
|
|
|
36
31
|
if (!SESSION || !/^[a-zA-Z0-9_-]+$/u.test(SESSION)) {
|
|
@@ -39,22 +34,15 @@ if (!SESSION || !/^[a-zA-Z0-9_-]+$/u.test(SESSION)) {
|
|
|
39
34
|
);
|
|
40
35
|
process.exit(1);
|
|
41
36
|
}
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
try {
|
|
38
|
+
execFileSync(MUX_BIN, ["-V"], { encoding: "utf8", timeout: 2000 });
|
|
39
|
+
} catch {
|
|
40
|
+
process.stderr.write(
|
|
41
|
+
`ERROR: primary multiplexer 미설치. 설치 방법:\n${formatPsmuxInstallGuidance(" ")}\n`,
|
|
42
|
+
);
|
|
44
43
|
process.exit(1);
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
if (SOURCE === "mux") {
|
|
48
|
-
try {
|
|
49
|
-
execFileSync(MUX_BIN, ["-V"], { encoding: "utf8", timeout: 2000 });
|
|
50
|
-
} catch {
|
|
51
|
-
process.stderr.write(
|
|
52
|
-
`ERROR: primary multiplexer 미설치. 설치 방법:\n${formatPsmuxInstallGuidance(" ")}\n`,
|
|
53
|
-
);
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
46
|
// ── 메모리 보호 상수 ──
|
|
59
47
|
const MAX_BODY_BYTES = 10240;
|
|
60
48
|
|
|
@@ -182,13 +170,6 @@ function estimateProgress(lines, context = {}) {
|
|
|
182
170
|
|
|
183
171
|
// ── psmux 래퍼 ──
|
|
184
172
|
function listPanes() {
|
|
185
|
-
if (SOURCE === "files") {
|
|
186
|
-
return Array.from({ length: WORKER_COUNT }, (_, index) => ({
|
|
187
|
-
index: index + 1,
|
|
188
|
-
title: `worker-${index + 1} (daemon)`,
|
|
189
|
-
pid: "",
|
|
190
|
-
}));
|
|
191
|
-
}
|
|
192
173
|
try {
|
|
193
174
|
const out = execFileSync(
|
|
194
175
|
MUX_BIN,
|
|
@@ -215,32 +196,6 @@ function listPanes() {
|
|
|
215
196
|
}
|
|
216
197
|
|
|
217
198
|
function capturePane(paneIdx, lines = 20) {
|
|
218
|
-
if (SOURCE === "files") {
|
|
219
|
-
const base = join(RESULT_DIR, `${SESSION}-worker-${paneIdx}.txt`);
|
|
220
|
-
const artifacts = [
|
|
221
|
-
{ path: base, label: "output" },
|
|
222
|
-
{ path: `${base}.partial`, label: "daemon" },
|
|
223
|
-
{ path: `${base}.err`, label: "stderr" },
|
|
224
|
-
];
|
|
225
|
-
const snapshots = [];
|
|
226
|
-
for (const artifact of artifacts) {
|
|
227
|
-
try {
|
|
228
|
-
const content = readFileSync(artifact.path, "utf8").trim();
|
|
229
|
-
if (!content) continue;
|
|
230
|
-
snapshots.push(
|
|
231
|
-
`[${artifact.label}]\n${content.split("\n").slice(-lines).join("\n")}`,
|
|
232
|
-
);
|
|
233
|
-
} catch {
|
|
234
|
-
/* artifact is optional while the daemon job is starting */
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
return (
|
|
238
|
-
snapshots.join("\n") ||
|
|
239
|
-
`worker-${paneIdx} daemon active · waiting for output · ${Math.round(
|
|
240
|
-
(Date.now() - startTime) / 1000,
|
|
241
|
-
)}s`
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
199
|
try {
|
|
245
200
|
return execFileSync(
|
|
246
201
|
MUX_BIN,
|
|
@@ -417,7 +372,7 @@ function ingest() {
|
|
|
417
372
|
const tokens = extractTokenLabel(snapshot);
|
|
418
373
|
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
419
374
|
|
|
420
|
-
if (
|
|
375
|
+
if (resultSize > 10 || shellReturned) {
|
|
421
376
|
const resultContent = existsSync(resultFile)
|
|
422
377
|
? readFileSync(resultFile, "utf8")
|
|
423
378
|
: snapshot;
|
|
@@ -459,10 +414,8 @@ function ingest() {
|
|
|
459
414
|
// 진행 중
|
|
460
415
|
const progress = estimateProgress(lines, {
|
|
461
416
|
tokens,
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
resultSize: SOURCE === "files" ? 0 : resultSize,
|
|
465
|
-
shellReturned: SOURCE === "files" ? false : shellReturned,
|
|
417
|
+
resultSize,
|
|
418
|
+
shellReturned,
|
|
466
419
|
done: false,
|
|
467
420
|
});
|
|
468
421
|
const verdict = lastLine;
|
package/hub/team/tui.mjs
CHANGED
|
@@ -44,11 +44,6 @@ import {
|
|
|
44
44
|
wrapLine,
|
|
45
45
|
wrapText as wrapTextAll,
|
|
46
46
|
} from "./tui-core.mjs";
|
|
47
|
-
import {
|
|
48
|
-
createMetricsCollector,
|
|
49
|
-
createSynapseEventStream,
|
|
50
|
-
renderMetricsTier1,
|
|
51
|
-
} from "./tui-synapse.mjs";
|
|
52
47
|
import {
|
|
53
48
|
createPanelResizer,
|
|
54
49
|
createSearchState,
|
|
@@ -934,19 +929,6 @@ export function createLogDashboard(opts = {}) {
|
|
|
934
929
|
initialRatio: focus === "detail" ? 0.2 : 0.3,
|
|
935
930
|
});
|
|
936
931
|
|
|
937
|
-
// Synapse 실시간 관제 (Phase 3)
|
|
938
|
-
const synapseMetrics = deps.synapseMetrics || createMetricsCollector();
|
|
939
|
-
const synapseStream =
|
|
940
|
-
deps.synapseStream ||
|
|
941
|
-
createSynapseEventStream({
|
|
942
|
-
onEvent(event) {
|
|
943
|
-
synapseMetrics.ingest(event);
|
|
944
|
-
},
|
|
945
|
-
fetchImpl: deps.synapseFetch,
|
|
946
|
-
});
|
|
947
|
-
// Synapse 자동 시작 (옵트인: deps.enableSynapse)
|
|
948
|
-
if (deps.enableSynapse) synapseStream.start();
|
|
949
|
-
|
|
950
932
|
// virtual row buffer (altScreen 전용)
|
|
951
933
|
const rowBuf = new RowBuffer();
|
|
952
934
|
|
|
@@ -1017,7 +999,6 @@ export function createLogDashboard(opts = {}) {
|
|
|
1017
999
|
function doClose() {
|
|
1018
1000
|
if (closed) return;
|
|
1019
1001
|
if (timer) clearInterval(timer);
|
|
1020
|
-
synapseStream.stop();
|
|
1021
1002
|
if (inputAttached && typeof input?.off === "function")
|
|
1022
1003
|
input.off("data", handleInput);
|
|
1023
1004
|
if (rawModeEnabled && typeof input?.setRawMode === "function")
|
|
@@ -1369,13 +1350,6 @@ export function createLogDashboard(opts = {}) {
|
|
|
1369
1350
|
truncate(` ${color("▸", MOCHA.green)} ${flashMessage}`, totalCols),
|
|
1370
1351
|
);
|
|
1371
1352
|
}
|
|
1372
|
-
// Synapse 실시간 메트릭 (활성 시)
|
|
1373
|
-
if (synapseStream.isRunning) {
|
|
1374
|
-
const metricsSnap = synapseMetrics.snapshot();
|
|
1375
|
-
tier1.push(
|
|
1376
|
-
truncate(renderMetricsTier1(metricsSnap, totalCols), totalCols),
|
|
1377
|
-
);
|
|
1378
|
-
}
|
|
1379
1353
|
// 검색 프롬프트 (/ 모드)
|
|
1380
1354
|
const searchPrompt = searchState.renderPrompt(totalCols);
|
|
1381
1355
|
if (searchPrompt) {
|
|
@@ -1659,17 +1633,6 @@ export function createLogDashboard(opts = {}) {
|
|
|
1659
1633
|
return attachToSession(w);
|
|
1660
1634
|
},
|
|
1661
1635
|
|
|
1662
|
-
// Synapse 관제 API
|
|
1663
|
-
startSynapse() {
|
|
1664
|
-
synapseStream.start();
|
|
1665
|
-
},
|
|
1666
|
-
stopSynapse() {
|
|
1667
|
-
synapseStream.stop();
|
|
1668
|
-
},
|
|
1669
|
-
getSynapseMetrics() {
|
|
1670
|
-
return synapseMetrics.snapshot();
|
|
1671
|
-
},
|
|
1672
|
-
|
|
1673
1636
|
close() {
|
|
1674
1637
|
doClose();
|
|
1675
1638
|
},
|