@triflux/remote 10.50.2 → 10.52.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 (134) hide show
  1. package/cto/brief.mjs +0 -20
  2. package/cto/collect.mjs +1 -200
  3. package/cto/current.schema.json +2 -23
  4. package/cto/hygiene.mjs +2 -61
  5. package/cto/lake-root.mjs +0 -56
  6. package/cto/status.mjs +5 -187
  7. package/hub/index.mjs +0 -17
  8. package/hub/team/agent-map.json +0 -1
  9. package/hub/team/agents-row-attach.py +120 -0
  10. package/hub/team/agents-row.mjs +200 -0
  11. package/hub/team/ansi.mjs +1 -35
  12. package/hub/team/backend.mjs +9 -145
  13. package/hub/team/claude-daemon-control.mjs +1 -2
  14. package/hub/team/claude-session-projection.mjs +5 -73
  15. package/hub/team/cli/commands/attach.mjs +1 -17
  16. package/hub/team/cli/commands/control.mjs +12 -38
  17. package/hub/team/cli/commands/debug.mjs +2 -39
  18. package/hub/team/cli/commands/focus.mjs +5 -31
  19. package/hub/team/cli/commands/interrupt.mjs +6 -25
  20. package/hub/team/cli/commands/kill.mjs +1 -30
  21. package/hub/team/cli/commands/list.mjs +0 -16
  22. package/hub/team/cli/commands/send.mjs +6 -26
  23. package/hub/team/cli/commands/start/index.mjs +42 -133
  24. package/hub/team/cli/commands/start/parse-args.mjs +15 -40
  25. package/hub/team/cli/commands/start/start-headless.mjs +138 -105
  26. package/hub/team/cli/commands/start/start-mux.mjs +2 -7
  27. package/hub/team/cli/commands/status.mjs +2 -57
  28. package/hub/team/cli/commands/stop.mjs +14 -19
  29. package/hub/team/cli/help.mjs +4 -5
  30. package/hub/team/cli/manifest.mjs +5 -1
  31. package/hub/team/cli/render.mjs +0 -12
  32. package/hub/team/cli/services/runtime-mode.mjs +27 -98
  33. package/hub/team/cli/services/state-store.mjs +5 -20
  34. package/hub/team/handoff.mjs +3 -34
  35. package/hub/team/headless.mjs +108 -1296
  36. package/hub/team/orchestrator.mjs +4 -30
  37. package/hub/team/pane.mjs +6 -28
  38. package/hub/team/psmux.mjs +3 -34
  39. package/hub/team/remote-session.mjs +1 -2
  40. package/hub/team/session-context.mjs +1 -1
  41. package/hub/team/session.mjs +7 -91
  42. package/hub/team/tui-core.mjs +1 -1
  43. package/hub/team/tui-viewer.mjs +11 -58
  44. package/hub/team/tui.mjs +8 -306
  45. package/hub/team/uds-orchestrator.mjs +2 -580
  46. package/hub/team/wt-manager.mjs +2 -2
  47. package/hub/workers/factory.mjs +6 -84
  48. package/hub/workers/interface.mjs +1 -7
  49. package/hub/workers/lib/jsonrpc-core.mjs +11 -0
  50. package/hub/workers/lib/jsonrpc-ws-uds.mjs +5 -8
  51. package/package.json +3 -8
  52. package/scripts/lib/agent-route-policy.mjs +143 -62
  53. package/scripts/lib/cli-claude.mjs +0 -14
  54. package/scripts/lib/env-probe.mjs +2 -131
  55. package/scripts/lib/gemini-profiles.mjs +2 -69
  56. package/scripts/lib/legacy-mcp-cleanup.mjs +283 -0
  57. package/scripts/lib/machine-profile.mjs +3 -17
  58. package/scripts/lib/mcp-filter.mjs +109 -53
  59. package/scripts/lib/mcp-guard-engine.mjs +40 -98
  60. package/scripts/lib/mcp-health.mjs +2 -2
  61. package/scripts/lib/mcp-server-catalog.mjs +2 -2
  62. package/scripts/lib/psmux-info.mjs +11 -59
  63. package/scripts/lib/skill-template.mjs +0 -245
  64. package/hub/hub-lifecycle.mjs +0 -248
  65. package/hub/pipe.mjs +0 -825
  66. package/hub/promote-penalties.mjs +0 -138
  67. package/hub/public/dashboard.html +0 -416
  68. package/hub/schema.sql +0 -209
  69. package/hub/server.mjs +0 -3048
  70. package/hub/store-adapter.mjs +0 -182
  71. package/hub/store.mjs +0 -1736
  72. package/hub/team/build-worker-prompt.mjs +0 -92
  73. package/hub/team/check-mcp-hub.mjs +0 -49
  74. package/hub/team/claude-native-bridge.mjs +0 -987
  75. package/hub/team/cli/commands/start/start-in-process.mjs +0 -50
  76. package/hub/team/cli/commands/start/start-wt.mjs +0 -86
  77. package/hub/team/cli/services/hub-client.mjs +0 -264
  78. package/hub/team/cli/services/native-control.mjs +0 -145
  79. package/hub/team/codex-review.mjs +0 -520
  80. package/hub/team/conductor-mesh-bridge.mjs +0 -122
  81. package/hub/team/conductor-registry.mjs +0 -121
  82. package/hub/team/conductor.mjs +0 -1529
  83. package/hub/team/consensus-meta.mjs +0 -176
  84. package/hub/team/cto-auto-collect.mjs +0 -115
  85. package/hub/team/cto-pull.mjs +0 -31
  86. package/hub/team/daemon-pty-tmux-bridge.mjs +0 -146
  87. package/hub/team/dashboard.mjs +0 -322
  88. package/hub/team/event-log.mjs +0 -84
  89. package/hub/team/execution-mode.mjs +0 -324
  90. package/hub/team/extract-completion-payload.mjs +0 -110
  91. package/hub/team/headless-bridge-session.mjs +0 -17
  92. package/hub/team/health-probe.mjs +0 -435
  93. package/hub/team/interactive-native-launcher.mjs +0 -119
  94. package/hub/team/interactive-native-worker.mjs +0 -80
  95. package/hub/team/interactive-tui-transport.mjs +0 -479
  96. package/hub/team/launcher-template.mjs +0 -144
  97. package/hub/team/lead-control.mjs +0 -191
  98. package/hub/team/mcp-selector.mjs +0 -145
  99. package/hub/team/native-supervisor.mjs +0 -460
  100. package/hub/team/native.mjs +0 -727
  101. package/hub/team/recovery-store.mjs +0 -149
  102. package/hub/team/remote-probe.mjs +0 -292
  103. package/hub/team/remote-watcher.mjs +0 -506
  104. package/hub/team/runtime-strategy.mjs +0 -200
  105. package/hub/team/schemas/worker-completion.json +0 -39
  106. package/hub/team/sentinel-capture.mjs +0 -127
  107. package/hub/team/session-sync.mjs +0 -316
  108. package/hub/team/swarm-cli.mjs +0 -390
  109. package/hub/team/swarm-hypervisor.mjs +0 -2206
  110. package/hub/team/swarm-intent.mjs +0 -216
  111. package/hub/team/swarm-locks.mjs +0 -335
  112. package/hub/team/swarm-planner.mjs +0 -371
  113. package/hub/team/swarm-preflight.mjs +0 -366
  114. package/hub/team/swarm-reconciler.mjs +0 -242
  115. package/hub/team/synapse-cli.mjs +0 -458
  116. package/hub/team/synapse-http.mjs +0 -311
  117. package/hub/team/synapse-registry.mjs +0 -618
  118. package/hub/team/tui-remote-adapter.mjs +0 -377
  119. package/hub/team/tui-synapse.mjs +0 -223
  120. package/hub/team/worker-completion-validator.mjs +0 -79
  121. package/hub/team/worker-sandbox.mjs +0 -125
  122. package/hub/team/worktree-lifecycle.mjs +0 -639
  123. package/hub/team/wt-templates.json +0 -43
  124. package/hub/tools.mjs +0 -900
  125. package/hub/workers/antigravity-route-worker.mjs +0 -179
  126. package/hub/workers/codex-app-server-worker.mjs +0 -1067
  127. package/hub/workers/delegator-mcp.mjs +0 -1098
  128. package/hub/workers/lib/jsonrpc-stdio.mjs +0 -204
  129. package/scripts/lib/claudemd-scanner.mjs +0 -223
  130. package/scripts/lib/context.mjs +0 -67
  131. package/scripts/lib/dynamic-route-cli.mjs +0 -107
  132. package/scripts/lib/handoff.mjs +0 -187
  133. package/scripts/lib/logger.mjs +0 -122
  134. package/scripts/lib/stealth-fetch.mjs +0 -176
package/hub/team/tui.mjs CHANGED
@@ -32,10 +32,10 @@ import {
32
32
  cliColor,
33
33
  normalizeWorkerState as coreNormalizeWorkerState,
34
34
  countStatuses,
35
- FALLBACK_COLUMNS,
36
- FALLBACK_ROWS,
37
35
  formatTokens,
38
36
  loadVersion,
37
+ resolveViewportColumns,
38
+ resolveViewportRows,
39
39
  runtimeStatus,
40
40
  sanitizeFiles,
41
41
  sanitizeOneLine,
@@ -44,22 +44,15 @@ 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,
55
- createTokenTracker,
56
50
  createVimMotion,
57
51
  } from "./tui-widgets.mjs";
58
52
  import { createWtManager } from "./wt-manager.mjs";
59
53
 
60
54
  const VERSION = await loadVersion();
61
55
 
62
- // FALLBACK_COLUMNS, FALLBACK_ROWS → tui-core.mjs에서 import
63
56
  const MIN_CARD_WIDTH = 28;
64
57
  const ATTACH_SESSION_NAME_PATTERN = /^[a-zA-Z0-9_.-]+$/u;
65
58
  const DEFAULT_ATTACH_TAB_TTL_MS = 30_000;
@@ -284,11 +277,6 @@ function activityWave(tick, count = 4) {
284
277
  return `${MOCHA.executing}${wave}${RESET}`;
285
278
  }
286
279
 
287
- const GRID_GAP = 2;
288
- const DEFAULT_DETAIL_LINES = 10;
289
- // Tier1 상단 고정 행 수
290
- const _TIER1_ROWS = 2;
291
-
292
280
  const SUMMARY_KEYS = [
293
281
  "status",
294
282
  "lead_action",
@@ -302,30 +290,6 @@ const SUMMARY_KEYS = [
302
290
  "partial_output",
303
291
  ];
304
292
 
305
- // ── 레이아웃 브레이크포인트 ──────────────────────────────────────────────
306
- // 80-119: 28col rail, 120-159: 36col rail, 160+: 균등
307
- function _resolveRailWidth(totalCols, columnCount) {
308
- if (columnCount <= 1) return totalCols;
309
- if (totalCols >= 160)
310
- return Math.floor((totalCols - GRID_GAP * (columnCount - 1)) / columnCount);
311
- if (totalCols >= 120)
312
- return Math.min(
313
- 36,
314
- Math.floor((totalCols - GRID_GAP * (columnCount - 1)) / columnCount),
315
- );
316
- return Math.min(
317
- 28,
318
- Math.floor((totalCols - GRID_GAP * (columnCount - 1)) / columnCount),
319
- );
320
- }
321
-
322
- function _autoColumnCount(totalCols, workerCount) {
323
- if (workerCount <= 1) return 1;
324
- if (totalCols >= 160) return Math.min(workerCount, 3);
325
- if (totalCols >= 120) return Math.min(workerCount, 2);
326
- return 1;
327
- }
328
-
329
293
  // 텍스트/상태/색상 유틸은 tui-core.mjs에서 import (위 참조)
330
294
 
331
295
  // ── MOCHA RGB (gradual fade 보간용) ──
@@ -354,8 +318,6 @@ function statusToRgb(status) {
354
318
  return MOCHA_RGB.muted;
355
319
  }
356
320
 
357
- const FADE_DURATION_MS = 1500;
358
- const FLASH_PHASE_MS = 250;
359
321
  const CARD_GLOW_MS = 3000;
360
322
 
361
323
  // Effect 1: Pulse border — running 워커 보더가 heartbeat 동기 breathing
@@ -374,22 +336,6 @@ function gradientBorderFn(topRgb, bottomRgb) {
374
336
  };
375
337
  }
376
338
 
377
- // Effect 3: Flash-fade border — 상태 변경 시 백색 플래시 → 페이드아웃
378
- function _flashFadeBorderColor(currentStatus, prevStatus, changedAt) {
379
- const elapsed = Date.now() - (changedAt || 0);
380
- if (elapsed >= FADE_DURATION_MS || !prevStatus) return null;
381
- const statusRgb = statusToRgb(currentStatus);
382
- if (elapsed < FLASH_PHASE_MS) {
383
- const t = elapsed / FLASH_PHASE_MS;
384
- const bright = { r: 255, g: 255, b: 255 };
385
- const c = lerpRgb(bright, statusRgb, t);
386
- return `\x1b[38;2;${c.r};${c.g};${c.b}m`;
387
- }
388
- const t = (elapsed - FLASH_PHASE_MS) / (FADE_DURATION_MS - FLASH_PHASE_MS);
389
- const c = lerpRgb(statusRgb, MOCHA_RGB.border, t);
390
- return `\x1b[38;2;${c.r};${c.g};${c.b}m`;
391
- }
392
-
393
339
  function easeOutCubic(t) {
394
340
  return 1 - (1 - t) ** 3;
395
341
  }
@@ -436,26 +382,6 @@ function dedupeRole(role, name, cli) {
436
382
 
437
383
  // wrapLine, wrapTextAll → tui-core.mjs에서 import
438
384
 
439
- function _wrapText(
440
- text,
441
- width,
442
- maxLines = DEFAULT_DETAIL_LINES,
443
- rawMode = false,
444
- ) {
445
- if (maxLines <= 0) return [];
446
- const input = sanitizeTextBlock(text, rawMode);
447
- if (!input) return [];
448
- const wrapped = input
449
- .split("\n")
450
- .flatMap((line) => wrapLine(line, width))
451
- .filter(Boolean);
452
- if (wrapped.length <= maxLines) return wrapped;
453
- return [
454
- ...wrapped.slice(0, maxLines - 1),
455
- truncate(wrapped[wrapped.length - 1], width),
456
- ];
457
- }
458
-
459
385
  // ── virtual row buffer ────────────────────────────────────────────────────
460
386
  class RowBuffer {
461
387
  constructor() {
@@ -927,19 +853,6 @@ function buildHelpOverlay(width, height) {
927
853
  return result;
928
854
  }
929
855
 
930
- // ── joinColumns ───────────────────────────────────────────────────────────
931
- function _joinColumns(blocks, gap = GRID_GAP) {
932
- const maxHeight = Math.max(...blocks.map((b) => b.length));
933
- return Array.from({ length: maxHeight }, (_, rowIdx) =>
934
- blocks
935
- .map(
936
- (block) =>
937
- block[rowIdx] || " ".repeat(wcswidth(stripAnsi(block[0] || ""))),
938
- )
939
- .join(" ".repeat(gap)),
940
- );
941
- }
942
-
943
856
  // ── normalizeWorkerState ──────────────────────────────────────────────────
944
857
  function normalizeWorkerState(existing, state) {
945
858
  return coreNormalizeWorkerState(
@@ -1010,26 +923,12 @@ export function createLogDashboard(opts = {}) {
1010
923
  let rawModeEnabled = false;
1011
924
 
1012
925
  // UX 위젯 (ISSUE-14)
1013
- const tokenTracker = createTokenTracker();
1014
926
  const searchState = createSearchState();
1015
927
  const vimMotion = createVimMotion();
1016
928
  const panelResizer = createPanelResizer({
1017
929
  initialRatio: focus === "detail" ? 0.2 : 0.3,
1018
930
  });
1019
931
 
1020
- // Synapse 실시간 관제 (Phase 3)
1021
- const synapseMetrics = deps.synapseMetrics || createMetricsCollector();
1022
- const synapseStream =
1023
- deps.synapseStream ||
1024
- createSynapseEventStream({
1025
- onEvent(event) {
1026
- synapseMetrics.ingest(event);
1027
- },
1028
- fetchImpl: deps.synapseFetch,
1029
- });
1030
- // Synapse 자동 시작 (옵트인: deps.enableSynapse)
1031
- if (deps.enableSynapse) synapseStream.start();
1032
-
1033
932
  // virtual row buffer (altScreen 전용)
1034
933
  const rowBuf = new RowBuffer();
1035
934
 
@@ -1046,26 +945,6 @@ export function createLogDashboard(opts = {}) {
1046
945
  return Math.max(0, Math.round((now() - startedAt) / 1000));
1047
946
  }
1048
947
 
1049
- function getViewportColumns() {
1050
- const v = Number.isFinite(columns)
1051
- ? columns
1052
- : Number.isFinite(stream?.columns)
1053
- ? stream.columns
1054
- : Number.isFinite(process.stdout?.columns)
1055
- ? process.stdout.columns
1056
- : FALLBACK_COLUMNS;
1057
- return Math.max(48, v || FALLBACK_COLUMNS);
1058
- }
1059
-
1060
- function getViewportRows() {
1061
- const v = Number.isFinite(stream?.rows)
1062
- ? stream.rows
1063
- : Number.isFinite(process.stdout?.rows)
1064
- ? process.stdout.rows
1065
- : FALLBACK_ROWS;
1066
- return Math.max(10, v || FALLBACK_ROWS);
1067
- }
1068
-
1069
948
  function visibleWorkerNames() {
1070
949
  return [...workers.keys()].sort();
1071
950
  }
@@ -1120,7 +999,6 @@ export function createLogDashboard(opts = {}) {
1120
999
  function doClose() {
1121
1000
  if (closed) return;
1122
1001
  if (timer) clearInterval(timer);
1123
- synapseStream.stop();
1124
1002
  if (inputAttached && typeof input?.off === "function")
1125
1003
  input.off("data", handleInput);
1126
1004
  if (rawModeEnabled && typeof input?.setRawMode === "function")
@@ -1244,7 +1122,10 @@ export function createLogDashboard(opts = {}) {
1244
1122
  if (key === "g") return;
1245
1123
 
1246
1124
  // PgUp/PgDn: 페이지 단위 스크롤
1247
- const pageSize = Math.max(1, Math.floor(getViewportRows() / 2));
1125
+ const pageSize = Math.max(
1126
+ 1,
1127
+ Math.floor(resolveViewportRows({ stream }) / 2),
1128
+ );
1248
1129
  if (key === "\x1b[5~") {
1249
1130
  scrollDetail(-pageSize);
1250
1131
  return;
@@ -1442,8 +1323,8 @@ export function createLogDashboard(opts = {}) {
1442
1323
  ensureSelectedWorker(names);
1443
1324
  attachInput();
1444
1325
 
1445
- const totalCols = getViewportColumns();
1446
- const totalRows = getViewportRows();
1326
+ const totalCols = resolveViewportColumns({ columns, stream });
1327
+ const totalRows = resolveViewportRows({ stream });
1447
1328
 
1448
1329
  // Help overlay: 전체 화면 오버레이
1449
1330
  if (helpOverlay) {
@@ -1469,13 +1350,6 @@ export function createLogDashboard(opts = {}) {
1469
1350
  truncate(` ${color("▸", MOCHA.green)} ${flashMessage}`, totalCols),
1470
1351
  );
1471
1352
  }
1472
- // Synapse 실시간 메트릭 (활성 시)
1473
- if (synapseStream.isRunning) {
1474
- const metricsSnap = synapseMetrics.snapshot();
1475
- tier1.push(
1476
- truncate(renderMetricsTier1(metricsSnap, totalCols), totalCols),
1477
- );
1478
- }
1479
1353
  // 검색 프롬프트 (/ 모드)
1480
1354
  const searchPrompt = searchState.renderPrompt(totalCols);
1481
1355
  if (searchPrompt) {
@@ -1681,9 +1555,6 @@ export function createLogDashboard(opts = {}) {
1681
1555
  ? existing._logSec
1682
1556
  : (explicitElapsed ?? nowElapsedSec());
1683
1557
  workers.set(paneName, merged);
1684
- // 토큰 히스토리 추적 (스파크라인용)
1685
- if (merged.tokens !== undefined)
1686
- tokenTracker.record(paneName, merged.tokens);
1687
1558
  ensureSelectedWorker(visibleWorkerNames());
1688
1559
  // follow-tail: 새 데이터 → 자동 scroll 재계산
1689
1560
  if (followTail) detailScrollOffset = 0;
@@ -1762,177 +1633,8 @@ export function createLogDashboard(opts = {}) {
1762
1633
  return attachToSession(w);
1763
1634
  },
1764
1635
 
1765
- // Synapse 관제 API
1766
- startSynapse() {
1767
- synapseStream.start();
1768
- },
1769
- stopSynapse() {
1770
- synapseStream.stop();
1771
- },
1772
- getSynapseMetrics() {
1773
- return synapseMetrics.snapshot();
1774
- },
1775
-
1776
1636
  close() {
1777
1637
  doClose();
1778
1638
  },
1779
1639
  };
1780
1640
  }
1781
-
1782
- // ── Conductor Tier: 세션 테이블 렌더러 ─────────────────────────────────
1783
- //
1784
- // renderConductorTier(snapshot, cols)
1785
- // snapshot: conductor.getSnapshot() 반환 배열
1786
- // cols: 터미널 폭 (기본 100)
1787
- //
1788
- // 레이아웃:
1789
- // ┌─ CONDUCTOR ──────────────────────────────────────────┐
1790
- // │ ID Agent Host Health Last Out Restarts Why │
1791
- // │ abc123 codex local ■ OK 2s ago 0 │
1792
- // └──────────────────────────────────────────────────────┘
1793
- //
1794
- // Health 색상: healthy=green, stalled=yellow, input_wait=cyan,
1795
- // failed=red, dead/init/starting=dim
1796
-
1797
- const CONDUCTOR_STATE_LABEL = {
1798
- init: { label: "INIT", seq: MOCHA.subtext },
1799
- starting: { label: "START", seq: MOCHA.executing },
1800
- healthy: { label: "OK", seq: MOCHA.ok },
1801
- stalled: { label: "STALL", seq: MOCHA.yellow },
1802
- input_wait: { label: "INPUT_WAIT", seq: FG.cyan },
1803
- failed: { label: "FAIL", seq: MOCHA.fail },
1804
- restarting: { label: "RESTART", seq: MOCHA.partial },
1805
- dead: { label: "DEAD", seq: FG.gray },
1806
- completed: { label: "DONE", seq: MOCHA.ok },
1807
- };
1808
-
1809
- function conductorHealthCell(state) {
1810
- const entry = CONDUCTOR_STATE_LABEL[state] || {
1811
- label: state.toUpperCase(),
1812
- seq: FG.gray,
1813
- };
1814
- return `${entry.seq}■ ${entry.label}${RESET}`;
1815
- }
1816
-
1817
- function conductorRelTime(ms) {
1818
- if (!ms) return "—";
1819
- const sec = Math.round((Date.now() - ms) / 1000);
1820
- if (sec < 0) return "—";
1821
- if (sec < 60) return `${sec}s ago`;
1822
- if (sec < 3600) return `${Math.floor(sec / 60)}m ago`;
1823
- return `${Math.floor(sec / 3600)}h ago`;
1824
- }
1825
-
1826
- /**
1827
- * Conductor 세션 테이블을 문자열 배열(행 목록)로 렌더링.
1828
- *
1829
- * @param {object[]} snapshot — conductor.getSnapshot() 결과
1830
- * @param {number} [cols=100] — 터미널 폭
1831
- * @returns {string[]} 렌더링된 행 목록 (altScreen rowBuf.set()에 바로 삽입 가능)
1832
- */
1833
- export function renderConductorTier(snapshot, cols = 100) {
1834
- const width = Math.max(48, cols);
1835
- const inner = width - 4; // border: '│ ' + content + ' │'
1836
-
1837
- // ── 열 너비 계산 ────────────────────────────────────────
1838
- // ID(8) Agent(7) Host(6) Health(dyn) LastOut(dyn) Restarts(8) Why(rest)
1839
- const COL_ID = 8;
1840
- const COL_AGENT = 7;
1841
- const COL_HOST = 6;
1842
- const COL_RESTARTS = 4;
1843
- const COL_HEALTH = 12; // '■ INPUT_WAIT' = 12 chars
1844
- const COL_LASTOUT = 9; // '999m ago' = 8 + space
1845
- // Why gets the remainder
1846
- const fixedCols =
1847
- COL_ID + COL_AGENT + COL_HOST + COL_HEALTH + COL_LASTOUT + COL_RESTARTS + 6; // 6 spaces between cols
1848
- const COL_WHY = Math.max(4, inner - fixedCols);
1849
-
1850
- function cell(text, width_) {
1851
- return clip(String(text ?? ""), width_);
1852
- }
1853
-
1854
- function buildRow(id, agent, host, healthCell, lastOut, restarts, why) {
1855
- const idC = cell(id, COL_ID);
1856
- const agentC = cell(agent, COL_AGENT);
1857
- const hostC = cell(host, COL_HOST);
1858
- const restartsC = cell(String(restarts ?? 0), COL_RESTARTS);
1859
- const lastOutC = clip(lastOut, COL_LASTOUT);
1860
- const whyC = cell(why, COL_WHY);
1861
- // healthCell already has ANSI codes; pad its visible width manually
1862
- const healthVis = wcswidth(stripAnsi(healthCell));
1863
- const healthPad = Math.max(0, COL_HEALTH - healthVis);
1864
- const healthC = healthCell + " ".repeat(healthPad);
1865
-
1866
- return `${idC} ${agentC} ${hostC} ${healthC} ${lastOutC} ${restartsC} ${whyC}`;
1867
- }
1868
-
1869
- const boxWidth = inner;
1870
-
1871
- // ── 타이틀 행 ───────────────────────────────────────────
1872
- const titleText = ` CONDUCTOR `;
1873
- const titleColored = bold(color(titleText, FG.accent));
1874
- // Border top with title embedded: ┌─ CONDUCTOR ──...─┐
1875
- const dashLen = Math.max(0, boxWidth - titleText.length);
1876
- const dashLeft = 1;
1877
- const dashRight = Math.max(0, dashLen - dashLeft);
1878
- const borderSeq = MOCHA.border;
1879
- const topBorder = `${borderSeq}┌${"─".repeat(dashLeft)}${RESET}${titleColored}${borderSeq}${"─".repeat(dashRight)}┐${RESET}`;
1880
-
1881
- // ── ヘッダー行 ───────────────────────────────────────────
1882
- const headerRow = buildRow(
1883
- "ID",
1884
- "Agent",
1885
- "Host",
1886
- clip("Health", COL_HEALTH),
1887
- "Last Out",
1888
- "Rst",
1889
- "Why",
1890
- );
1891
- const headerLine = `${borderSeq}│${RESET} ${dim(headerRow)} ${borderSeq}│${RESET}`;
1892
-
1893
- // ── データ行 ────────────────────────────────────────────
1894
- const dataLines = [];
1895
- if (!snapshot || snapshot.length === 0) {
1896
- const emptyMsg = color("(no sessions)", FG.muted);
1897
- const _emptyPad = clip(
1898
- stripAnsi(emptyMsg) === "(no sessions)" ? emptyMsg : emptyMsg,
1899
- inner,
1900
- );
1901
- dataLines.push(
1902
- `${borderSeq}│${RESET} ${padRight(emptyMsg, inner - 2)} ${borderSeq}│${RESET}`,
1903
- );
1904
- } else {
1905
- for (const s of snapshot) {
1906
- const id = String(s.id ?? "").slice(0, COL_ID);
1907
- const agent = String(s.agent ?? "unknown").slice(0, COL_AGENT);
1908
- const host = "local";
1909
- const state = s.state ?? "init";
1910
- const healthCell = conductorHealthCell(state);
1911
- const lastOut = conductorRelTime(s.health?.lastProbeAt ?? null);
1912
- const restarts = s.restarts ?? 0;
1913
- // derive "why" from last state transition context
1914
- const why = s.health?.inputWaitPattern
1915
- ? String(s.health.inputWaitPattern).slice(0, COL_WHY)
1916
- : "";
1917
-
1918
- const rowText = buildRow(
1919
- id,
1920
- agent,
1921
- host,
1922
- healthCell,
1923
- lastOut,
1924
- restarts,
1925
- why,
1926
- );
1927
- dataLines.push(`${borderSeq}│${RESET} ${rowText} ${borderSeq}│${RESET}`);
1928
- }
1929
- }
1930
-
1931
- // ── Bottom border ─────────────────────────────────────
1932
- const botBorder = `${borderSeq}└${"─".repeat(boxWidth)}┘${RESET}`;
1933
-
1934
- return [topBorder, headerLine, ...dataLines, botBorder];
1935
- }
1936
-
1937
- // 하위 호환
1938
- export { createLogDashboard as createTui };