@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.
Files changed (62) 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/claude-transcript.mjs +19 -0
  13. package/hub/team/cli/commands/debug.mjs +1 -2
  14. package/hub/team/cli/commands/start/index.mjs +2 -1
  15. package/hub/team/cli/commands/start/start-headless.mjs +2 -0
  16. package/hub/team/cli/commands/start/start-mux.mjs +2 -4
  17. package/hub/team/cli/services/state-store.mjs +5 -20
  18. package/hub/team/headless.mjs +132 -551
  19. package/hub/team/psmux.mjs +3 -33
  20. package/hub/team/results-index.mjs +92 -0
  21. package/hub/team/session-context.mjs +1 -1
  22. package/hub/team/tui-viewer.mjs +9 -56
  23. package/hub/team/tui.mjs +0 -37
  24. package/hub/team/uds-orchestrator.mjs +20 -599
  25. package/hub/workers/factory.mjs +6 -51
  26. package/hub/workers/interface.mjs +1 -7
  27. package/hub/workers/lib/jsonrpc-core.mjs +11 -0
  28. package/hub/workers/lib/jsonrpc-ws-uds.mjs +5 -6
  29. package/package.json +3 -8
  30. package/scripts/lib/agent-route-policy.mjs +0 -67
  31. package/scripts/lib/cli-claude.mjs +0 -12
  32. package/scripts/lib/env-probe.mjs +0 -70
  33. package/scripts/lib/legacy-mcp-cleanup.mjs +283 -0
  34. package/scripts/lib/mcp-guard-engine.mjs +42 -101
  35. package/hub/hub-lifecycle.mjs +0 -248
  36. package/hub/pipe.mjs +0 -825
  37. package/hub/promote-penalties.mjs +0 -138
  38. package/hub/public/dashboard.html +0 -416
  39. package/hub/schema.sql +0 -209
  40. package/hub/server.mjs +0 -3048
  41. package/hub/store-adapter.mjs +0 -182
  42. package/hub/store.mjs +0 -1736
  43. package/hub/team/cli/services/hub-client.mjs +0 -178
  44. package/hub/team/conductor-registry.mjs +0 -121
  45. package/hub/team/cto-auto-collect.mjs +0 -115
  46. package/hub/team/lead-control.mjs +0 -191
  47. package/hub/team/session-sync.mjs +0 -316
  48. package/hub/team/swarm-intent.mjs +0 -18
  49. package/hub/team/synapse-cli.mjs +0 -458
  50. package/hub/team/synapse-http.mjs +0 -311
  51. package/hub/team/synapse-registry.mjs +0 -618
  52. package/hub/team/tui-synapse.mjs +0 -223
  53. package/hub/team/worker-sandbox.mjs +0 -125
  54. package/hub/tools.mjs +0 -900
  55. package/hub/workers/codex-app-server-worker.mjs +0 -1067
  56. package/hub/workers/delegator-mcp.mjs +0 -1098
  57. package/hub/workers/lib/jsonrpc-stdio.mjs +0 -204
  58. package/scripts/lib/claudemd-scanner.mjs +0 -26
  59. package/scripts/lib/context.mjs +0 -51
  60. package/scripts/lib/handoff.mjs +0 -187
  61. package/scripts/lib/logger.mjs +0 -122
  62. package/scripts/lib/stealth-fetch.mjs +0 -176
@@ -931,22 +931,8 @@ function killOrphanPipeHelpers(sessionName) {
931
931
  */
932
932
  function killOrphanMcpProcesses(sessionName) {
933
933
  if (!IS_WINDOWS) {
934
- // macOS/Linux: 세션별 스코핑 + Hub PID 보호
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 === hubPidUnix || numPid <= 0) continue;
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 && p !== hubPid);
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"),
@@ -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
- if (SOURCE === "files" && WORKER_COUNT === 0) {
43
- process.stderr.write("ERROR: --source files requires --workers <count>\n");
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 (SOURCE !== "files" && (resultSize > 10 || shellReturned)) {
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
- // daemon output files grow while the job is still running; file size is
463
- // activity evidence, not a completion signal in read-only file mode.
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
  },