triflux 10.3.4 → 10.7.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 (329) hide show
  1. package/LICENSE +21 -21
  2. package/bin/tfx-doctor-tui.mjs +1 -1
  3. package/bin/tfx-doctor.mjs +6 -1
  4. package/bin/tfx-profile.mjs +1 -1
  5. package/bin/tfx-setup-tui.mjs +1 -1
  6. package/bin/tfx-setup.mjs +6 -1
  7. package/bin/triflux.mjs +2396 -1140
  8. package/hooks/agent-route-guard.mjs +12 -8
  9. package/hooks/cross-review-tracker.mjs +21 -8
  10. package/hooks/error-context.mjs +19 -7
  11. package/hooks/hook-adaptive-collector.mjs +18 -16
  12. package/hooks/hook-manager.mjs +93 -32
  13. package/hooks/hook-orchestrator.mjs +108 -24
  14. package/hooks/hook-registry.json +11 -0
  15. package/hooks/keyword-rules.json +6 -10
  16. package/hooks/lib/resolve-root.mjs +1 -1
  17. package/hooks/mcp-config-watcher.mjs +6 -2
  18. package/hooks/pipeline-stop.mjs +3 -6
  19. package/hooks/safety-guard.mjs +99 -28
  20. package/hooks/session-start-fast.mjs +143 -0
  21. package/hooks/subagent-verifier.mjs +5 -4
  22. package/hub/account-broker.mjs +256 -60
  23. package/hub/adaptive-diagnostic.mjs +75 -48
  24. package/hub/adaptive-inject.mjs +95 -57
  25. package/hub/adaptive-memory.mjs +156 -42
  26. package/hub/adaptive.mjs +60 -31
  27. package/hub/assign-callbacks.mjs +67 -30
  28. package/hub/bridge.mjs +0 -1
  29. package/hub/cli-adapter-base.mjs +200 -48
  30. package/hub/codex-adapter.mjs +76 -96
  31. package/hub/codex-compat.mjs +3 -3
  32. package/hub/codex-preflight.mjs +63 -37
  33. package/hub/delegator/contracts.mjs +19 -23
  34. package/hub/delegator/index.mjs +3 -3
  35. package/hub/delegator/service.mjs +88 -64
  36. package/hub/delegator/tool-definitions.mjs +5 -5
  37. package/hub/fullcycle.mjs +33 -17
  38. package/hub/gemini-adapter.mjs +69 -94
  39. package/hub/hitl.mjs +89 -30
  40. package/hub/intent.mjs +161 -38
  41. package/hub/lib/cache-guard.mjs +43 -17
  42. package/hub/lib/mcp-response-cache.mjs +66 -32
  43. package/hub/lib/memory-store.mjs +285 -111
  44. package/hub/lib/path-utils.mjs +35 -37
  45. package/hub/lib/process-utils.mjs +106 -37
  46. package/hub/lib/spawn-trace.mjs +527 -0
  47. package/hub/lib/ssh-command.mjs +34 -4
  48. package/hub/lib/ssh-retry.mjs +5 -1
  49. package/hub/lib/uuidv7.mjs +4 -3
  50. package/hub/memory-doctor.mjs +266 -106
  51. package/hub/middleware/request-logger.mjs +61 -34
  52. package/hub/paths.mjs +9 -9
  53. package/hub/pipeline/gates/confidence.mjs +34 -15
  54. package/hub/pipeline/gates/consensus.mjs +27 -15
  55. package/hub/pipeline/gates/index.mjs +7 -3
  56. package/hub/pipeline/gates/selfcheck.mjs +57 -19
  57. package/hub/pipeline/index.mjs +77 -42
  58. package/hub/pipeline/state.mjs +10 -10
  59. package/hub/pipeline/transitions.mjs +40 -23
  60. package/hub/platform.mjs +57 -48
  61. package/hub/promote-penalties.mjs +25 -7
  62. package/hub/quality/deslop.mjs +70 -49
  63. package/hub/research.mjs +32 -25
  64. package/hub/router.mjs +240 -107
  65. package/hub/routing/complexity.mjs +132 -29
  66. package/hub/routing/index.mjs +17 -12
  67. package/hub/routing/q-learning.mjs +76 -28
  68. package/hub/server.mjs +4 -4
  69. package/hub/session-fingerprint.mjs +126 -60
  70. package/hub/state.mjs +84 -43
  71. package/hub/store-adapter.mjs +59 -26
  72. package/hub/store.mjs +356 -153
  73. package/hub/team/agent-map.json +22 -7
  74. package/hub/team/ansi.mjs +186 -122
  75. package/hub/team/backend.mjs +28 -10
  76. package/hub/team/cli/commands/attach.mjs +29 -9
  77. package/hub/team/cli/commands/control.mjs +29 -8
  78. package/hub/team/cli/commands/debug.mjs +32 -11
  79. package/hub/team/cli/commands/focus.mjs +38 -11
  80. package/hub/team/cli/commands/interrupt.mjs +18 -6
  81. package/hub/team/cli/commands/kill.mjs +16 -5
  82. package/hub/team/cli/commands/list.mjs +11 -4
  83. package/hub/team/cli/commands/send.mjs +19 -6
  84. package/hub/team/cli/commands/start/index.mjs +154 -31
  85. package/hub/team/cli/commands/start/parse-args.mjs +38 -11
  86. package/hub/team/cli/commands/start/start-headless.mjs +112 -36
  87. package/hub/team/cli/commands/start/start-in-process.mjs +12 -2
  88. package/hub/team/cli/commands/start/start-mux.mjs +70 -21
  89. package/hub/team/cli/commands/start/start-wt.mjs +29 -12
  90. package/hub/team/cli/commands/status.mjs +43 -14
  91. package/hub/team/cli/commands/stop.mjs +11 -4
  92. package/hub/team/cli/commands/task.mjs +8 -3
  93. package/hub/team/cli/commands/tasks.mjs +1 -1
  94. package/hub/team/cli/index.mjs +2 -2
  95. package/hub/team/cli/manifest.mjs +38 -8
  96. package/hub/team/cli/render.mjs +30 -8
  97. package/hub/team/cli/services/attach-fallback.mjs +31 -11
  98. package/hub/team/cli/services/hub-client.mjs +42 -14
  99. package/hub/team/cli/services/member-selector.mjs +11 -4
  100. package/hub/team/cli/services/native-control.mjs +48 -21
  101. package/hub/team/cli/services/runtime-mode.mjs +2 -1
  102. package/hub/team/cli/services/state-store.mjs +25 -8
  103. package/hub/team/cli/services/task-model.mjs +16 -6
  104. package/hub/team/conductor-mesh-bridge.mjs +24 -23
  105. package/hub/team/conductor.mjs +8 -4
  106. package/hub/team/dashboard-anchor.mjs +4 -5
  107. package/hub/team/dashboard-layout.mjs +3 -1
  108. package/hub/team/dashboard-open.mjs +41 -21
  109. package/hub/team/dashboard.mjs +76 -28
  110. package/hub/team/event-log.mjs +18 -10
  111. package/hub/team/handoff.mjs +31 -15
  112. package/hub/team/headless.mjs +2 -1
  113. package/hub/team/health-probe.mjs +69 -54
  114. package/hub/team/launcher-template.mjs +16 -13
  115. package/hub/team/native-supervisor.mjs +65 -21
  116. package/hub/team/native.mjs +74 -35
  117. package/hub/team/nativeProxy.mjs +184 -113
  118. package/hub/team/notify.mjs +119 -76
  119. package/hub/team/orchestrator.mjs +9 -4
  120. package/hub/team/pane.mjs +12 -7
  121. package/hub/team/process-cleanup.mjs +25 -16
  122. package/hub/team/psmux.mjs +491 -201
  123. package/hub/team/remote-probe.mjs +68 -52
  124. package/hub/team/remote-session.mjs +117 -59
  125. package/hub/team/remote-watcher.mjs +61 -33
  126. package/hub/team/routing.mjs +51 -25
  127. package/hub/team/runtime-strategy.mjs +3 -1
  128. package/hub/team/session.mjs +98 -34
  129. package/hub/team/staleState.mjs +72 -30
  130. package/hub/team/swarm-locks.mjs +15 -13
  131. package/hub/team/swarm-planner.mjs +32 -21
  132. package/hub/team/swarm-reconciler.mjs +48 -23
  133. package/hub/team/tui-lite.mjs +266 -68
  134. package/hub/team/tui-remote-adapter.mjs +14 -10
  135. package/hub/team/tui-viewer.mjs +99 -43
  136. package/hub/team/tui.mjs +708 -271
  137. package/hub/team/worktree-lifecycle.mjs +152 -58
  138. package/hub/team/wt-manager.mjs +24 -14
  139. package/hub/token-mode.mjs +71 -71
  140. package/hub/tray.mjs +66 -23
  141. package/hub/workers/claude-worker.mjs +162 -118
  142. package/hub/workers/codex-mcp.mjs +192 -141
  143. package/hub/workers/delegator-mcp.mjs +507 -333
  144. package/hub/workers/factory.mjs +8 -8
  145. package/hub/workers/gemini-worker.mjs +115 -84
  146. package/hub/workers/interface.mjs +6 -1
  147. package/hub/workers/worker-utils.mjs +21 -14
  148. package/hud/colors.mjs +27 -9
  149. package/hud/constants.mjs +162 -26
  150. package/hud/context-monitor.mjs +82 -41
  151. package/hud/hud-qos-status.mjs +129 -49
  152. package/hud/mission-board.mjs +6 -3
  153. package/hud/providers/claude.mjs +226 -115
  154. package/hud/providers/codex.mjs +62 -22
  155. package/hud/providers/gemini.mjs +168 -56
  156. package/hud/renderers.mjs +384 -119
  157. package/hud/terminal.mjs +101 -31
  158. package/hud/utils.mjs +78 -38
  159. package/mesh/index.mjs +11 -5
  160. package/mesh/mesh-budget.mjs +18 -9
  161. package/mesh/mesh-heartbeat.mjs +1 -1
  162. package/mesh/mesh-queue.mjs +3 -5
  163. package/mesh/mesh-router.mjs +5 -4
  164. package/package.json +2 -1
  165. package/scripts/__tests__/gen-skill-docs.test.mjs +36 -7
  166. package/scripts/__tests__/keyword-detector.test.mjs +77 -28
  167. package/scripts/__tests__/mcp-guard-engine.test.mjs +58 -20
  168. package/scripts/__tests__/remote-spawn-transfer.test.mjs +30 -19
  169. package/scripts/__tests__/remote-spawn.test.mjs +10 -4
  170. package/scripts/__tests__/session-start-fast.test.mjs +36 -0
  171. package/scripts/__tests__/skill-template.test.mjs +98 -50
  172. package/scripts/__tests__/smoke.test.mjs +1 -1
  173. package/scripts/__tests__/spawn-trace.test.mjs +102 -0
  174. package/scripts/__tests__/tfx-doctor-diagnose.test.mjs +48 -0
  175. package/scripts/cache-doctor.mjs +11 -4
  176. package/scripts/cache-warmup.mjs +96 -37
  177. package/scripts/claudemd-sync.mjs +27 -17
  178. package/scripts/codex-gateway-preflight.mjs +52 -37
  179. package/scripts/codex-mcp-gateway-sync.mjs +59 -39
  180. package/scripts/completions/tfx.bash +47 -47
  181. package/scripts/completions/tfx.fish +44 -44
  182. package/scripts/completions/tfx.zsh +83 -83
  183. package/scripts/config-audit.mjs +232 -0
  184. package/scripts/convert-to-tmpl.mjs +54 -0
  185. package/scripts/cross-review-gate.mjs +35 -12
  186. package/scripts/cross-review-tracker.mjs +21 -8
  187. package/scripts/demo.mjs +35 -17
  188. package/scripts/doctor-diagnose.mjs +284 -0
  189. package/scripts/gen-skill-docs.mjs +7 -2
  190. package/scripts/gen-skill-manifest.mjs +2 -1
  191. package/scripts/headless-guard.mjs +86 -48
  192. package/scripts/hub-ensure.mjs +45 -26
  193. package/scripts/keyword-detector.mjs +41 -20
  194. package/scripts/keyword-rules-expander.mjs +47 -30
  195. package/scripts/lib/claudemd-scanner.mjs +6 -1
  196. package/scripts/lib/context.mjs +3 -3
  197. package/scripts/lib/cross-review-utils.mjs +6 -3
  198. package/scripts/lib/env-probe.mjs +47 -28
  199. package/scripts/lib/gemini-profiles.mjs +44 -10
  200. package/scripts/lib/handoff.mjs +33 -17
  201. package/scripts/lib/hook-utils.mjs +8 -6
  202. package/scripts/lib/keyword-rules.mjs +43 -19
  203. package/scripts/lib/logger.mjs +24 -24
  204. package/scripts/lib/mcp-filter.mjs +377 -239
  205. package/scripts/lib/mcp-guard-engine.mjs +194 -79
  206. package/scripts/lib/mcp-manifest.mjs +23 -13
  207. package/scripts/lib/mcp-server-catalog.mjs +300 -63
  208. package/scripts/lib/psmux-info.mjs +11 -6
  209. package/scripts/lib/remote-spawn-transfer.mjs +44 -14
  210. package/scripts/lib/skill-template.mjs +30 -7
  211. package/scripts/mcp-check.mjs +58 -39
  212. package/scripts/mcp-gateway-config.mjs +83 -39
  213. package/scripts/mcp-gateway-ensure.mjs +43 -35
  214. package/scripts/mcp-gateway-integration-test.mjs +70 -58
  215. package/scripts/mcp-gateway-start.mjs +126 -60
  216. package/scripts/mcp-gateway-verify.mjs +24 -22
  217. package/scripts/mcp-safety-guard.mjs +44 -11
  218. package/scripts/notion-read.mjs +199 -84
  219. package/scripts/pack.mjs +94 -89
  220. package/scripts/preflight-cache.mjs +27 -10
  221. package/scripts/preinstall.mjs +42 -13
  222. package/scripts/remote-spawn.mjs +309 -94
  223. package/scripts/run.cjs +8 -5
  224. package/scripts/session-spawn-helper.mjs +130 -39
  225. package/scripts/session-stale-cleanup.mjs +123 -0
  226. package/scripts/setup.mjs +941 -492
  227. package/scripts/test-lock.mjs +20 -7
  228. package/scripts/test-tfx-route-no-claude-native.mjs +16 -12
  229. package/scripts/tfx-batch-stats.mjs +32 -11
  230. package/scripts/tfx-gate-activate.mjs +11 -4
  231. package/scripts/tfx-route-post.mjs +87 -20
  232. package/scripts/tfx-route-worker.mjs +57 -51
  233. package/scripts/tfx-route.sh +41 -124
  234. package/scripts/tmp-cleanup.mjs +21 -7
  235. package/scripts/token-snapshot.mjs +204 -85
  236. package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +1 -0
  237. package/skills/.omc/state/idle-notif-cooldown.json +3 -0
  238. package/skills/.omc/state/last-tool-error.json +7 -0
  239. package/skills/.omc/state/subagent-tracking.json +7 -0
  240. package/skills/_templates/base.md +1 -6
  241. package/skills/merge-worktree/SKILL.md.tmpl +144 -0
  242. package/skills/shared/telemetry-segment.md +6 -0
  243. package/skills/star-prompt/SKILL.md.tmpl +222 -0
  244. package/skills/tfx-analysis/SKILL.md.tmpl +107 -0
  245. package/skills/tfx-analysis/skill.json +1 -6
  246. package/skills/tfx-auto/SKILL.md +1 -0
  247. package/skills/tfx-auto-codex/SKILL.md.tmpl +106 -0
  248. package/skills/tfx-auto-codex/skill.json +1 -3
  249. package/skills/tfx-autopilot/SKILL.md.tmpl +116 -0
  250. package/skills/tfx-autopilot/skill.json +1 -5
  251. package/skills/tfx-autoresearch/SKILL.md.tmpl +136 -0
  252. package/skills/tfx-autoroute/SKILL.md.tmpl +189 -0
  253. package/skills/tfx-autoroute/skill.json +1 -7
  254. package/skills/tfx-codex/SKILL.md +1 -0
  255. package/skills/tfx-codex/skill.json +1 -3
  256. package/skills/tfx-codex-swarm/SKILL.md.tmpl +16 -0
  257. package/skills/tfx-codex-swarm/evals/evals.json +1 -1
  258. package/skills/tfx-codex-swarm/skill.json +1 -4
  259. package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +54 -12
  260. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +35 -7
  261. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +35 -7
  262. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +25 -5
  263. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +25 -5
  264. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +20 -4
  265. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +16 -4
  266. package/skills/tfx-consensus/SKILL.md.tmpl +146 -0
  267. package/skills/tfx-debate/SKILL.md.tmpl +192 -0
  268. package/skills/tfx-debate/skill.json +1 -7
  269. package/skills/tfx-deep-analysis/SKILL.md.tmpl +228 -0
  270. package/skills/tfx-deep-analysis/skill.json +1 -5
  271. package/skills/tfx-deep-interview/SKILL.md.tmpl +203 -0
  272. package/skills/tfx-deep-plan/SKILL.md.tmpl +282 -0
  273. package/skills/tfx-deep-qa/SKILL.md.tmpl +165 -0
  274. package/skills/tfx-deep-qa/skill.json +1 -6
  275. package/skills/tfx-deep-research/SKILL.md.tmpl +217 -0
  276. package/skills/tfx-deep-review/SKILL.md.tmpl +179 -0
  277. package/skills/tfx-doctor/SKILL.md +21 -0
  278. package/skills/tfx-doctor/SKILL.md.tmpl +172 -0
  279. package/skills/tfx-doctor/skill.json +1 -3
  280. package/skills/tfx-find/SKILL.md +1 -0
  281. package/skills/tfx-forge/SKILL.md.tmpl +187 -0
  282. package/skills/tfx-fullcycle/SKILL.md.tmpl +286 -0
  283. package/skills/tfx-fullcycle/skill.json +1 -6
  284. package/skills/tfx-gemini/SKILL.md.tmpl +91 -0
  285. package/skills/tfx-gemini/skill.json +1 -3
  286. package/skills/tfx-hooks/SKILL.md.tmpl +216 -0
  287. package/skills/tfx-hooks/skill.json +1 -3
  288. package/skills/tfx-hub/SKILL.md.tmpl +212 -0
  289. package/skills/tfx-hub/skill.json +1 -3
  290. package/skills/tfx-index/SKILL.md +1 -0
  291. package/skills/tfx-index/skill.json +1 -6
  292. package/skills/tfx-interview/SKILL.md.tmpl +285 -0
  293. package/skills/tfx-multi/SKILL.md.tmpl +183 -0
  294. package/skills/tfx-multi/skill.json +1 -3
  295. package/skills/tfx-panel/SKILL.md.tmpl +189 -0
  296. package/skills/tfx-panel/skill.json +1 -7
  297. package/skills/tfx-persist/SKILL.md.tmpl +270 -0
  298. package/skills/tfx-persist/skill.json +1 -7
  299. package/skills/tfx-plan/SKILL.md +1 -0
  300. package/skills/tfx-plan/skill.json +1 -6
  301. package/skills/tfx-profile/SKILL.md.tmpl +239 -0
  302. package/skills/tfx-profile/skill.json +1 -3
  303. package/skills/tfx-prune/SKILL.md.tmpl +200 -0
  304. package/skills/tfx-prune/skill.json +1 -7
  305. package/skills/tfx-psmux-rules/SKILL.md.tmpl +326 -0
  306. package/skills/tfx-psmux-rules/skill.json +1 -4
  307. package/skills/tfx-qa/SKILL.md +1 -0
  308. package/skills/tfx-qa/skill.json +1 -6
  309. package/skills/tfx-ralph/SKILL.md.tmpl +28 -0
  310. package/skills/tfx-ralph/skill.json +1 -4
  311. package/skills/tfx-remote-setup/SKILL.md.tmpl +576 -0
  312. package/skills/tfx-remote-setup/skill.json +1 -3
  313. package/skills/tfx-remote-spawn/SKILL.md.tmpl +263 -0
  314. package/skills/tfx-remote-spawn/references/hosts.json +16 -0
  315. package/skills/tfx-remote-spawn/skill.json +1 -4
  316. package/skills/tfx-research/SKILL.md +1 -0
  317. package/skills/tfx-review/SKILL.md +1 -0
  318. package/skills/tfx-review/skill.json +1 -6
  319. package/skills/tfx-setup/SKILL.md.tmpl +504 -0
  320. package/skills/tfx-setup/skill.json +1 -3
  321. package/skills/tfx-swarm/SKILL.md +22 -0
  322. package/skills/tfx-swarm/SKILL.md.tmpl +218 -0
  323. package/tui/codex-profile.mjs +88 -33
  324. package/tui/core.mjs +45 -15
  325. package/tui/doctor.mjs +75 -28
  326. package/tui/gemini-profile.mjs +74 -29
  327. package/tui/monitor-data.mjs +8 -4
  328. package/tui/monitor.mjs +71 -27
  329. package/tui/setup.mjs +133 -42
@@ -1,10 +1,37 @@
1
- import { altScreenOff, altScreenOn, BG, bold, box, clearScreen, clearToEnd, color, cursorHide, cursorHome, cursorShow, dim, eraseBelow, FG, MOCHA, moveTo, padRight, progressBar, statusBadge, stripAnsi, truncate, wcswidth } from "./ansi.mjs";
1
+ import {
2
+ altScreenOff,
3
+ altScreenOn,
4
+ BG,
5
+ bold,
6
+ box,
7
+ clearScreen,
8
+ clearToEnd,
9
+ color,
10
+ cursorHide,
11
+ cursorHome,
12
+ cursorShow,
13
+ dim,
14
+ eraseBelow,
15
+ FG,
16
+ MOCHA,
17
+ moveTo,
18
+ padRight,
19
+ progressBar,
20
+ statusBadge,
21
+ stripAnsi,
22
+ truncate,
23
+ wcswidth,
24
+ } from "./ansi.mjs";
2
25
 
3
- const FALLBACK_COLUMNS = 100, FALLBACK_ROWS = 24;
26
+ const FALLBACK_COLUMNS = 100,
27
+ FALLBACK_ROWS = 24;
4
28
  const VALID_TABS = new Set(["log", "detail", "files"]);
5
29
 
6
30
  let VERSION = "lite";
7
- try { const { createRequire } = await import("node:module"); VERSION = createRequire(import.meta.url)("../../package.json").version; } catch {}
31
+ try {
32
+ const { createRequire } = await import("node:module");
33
+ VERSION = createRequire(import.meta.url)("../../package.json").version;
34
+ } catch {}
8
35
 
9
36
  const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
10
37
 
@@ -76,39 +103,87 @@ function wrap(text, width) {
76
103
  return lines;
77
104
  }
78
105
 
79
- const runtimeStatus = (worker) => worker?.handoff?.status || worker?.status || "pending";
106
+ const runtimeStatus = (worker) =>
107
+ worker?.handoff?.status || worker?.status || "pending";
80
108
 
81
109
  function normalizeWorkerState(existing = {}, state = {}) {
82
- const handoff = state.handoff === undefined
83
- ? existing.handoff
84
- : {
85
- ...(existing.handoff || {}),
86
- ...(state.handoff || {}),
87
- verdict: state.handoff?.verdict !== undefined ? sanitizeOneLine(state.handoff.verdict) : existing.handoff?.verdict,
88
- confidence: state.handoff?.confidence !== undefined ? sanitizeOneLine(state.handoff.confidence) : existing.handoff?.confidence,
89
- status: state.handoff?.status !== undefined ? sanitizeOneLine(state.handoff.status) : existing.handoff?.status,
90
- files_changed: state.handoff?.files_changed !== undefined ? sanitizeFiles(state.handoff.files_changed) : existing.handoff?.files_changed,
91
- };
110
+ const handoff =
111
+ state.handoff === undefined
112
+ ? existing.handoff
113
+ : {
114
+ ...(existing.handoff || {}),
115
+ ...(state.handoff || {}),
116
+ verdict:
117
+ state.handoff?.verdict !== undefined
118
+ ? sanitizeOneLine(state.handoff.verdict)
119
+ : existing.handoff?.verdict,
120
+ confidence:
121
+ state.handoff?.confidence !== undefined
122
+ ? sanitizeOneLine(state.handoff.confidence)
123
+ : existing.handoff?.confidence,
124
+ status:
125
+ state.handoff?.status !== undefined
126
+ ? sanitizeOneLine(state.handoff.status)
127
+ : existing.handoff?.status,
128
+ files_changed:
129
+ state.handoff?.files_changed !== undefined
130
+ ? sanitizeFiles(state.handoff.files_changed)
131
+ : existing.handoff?.files_changed,
132
+ };
92
133
  return {
93
134
  ...existing,
94
135
  ...state,
95
- cli: state.cli !== undefined ? sanitizeOneLine(state.cli, existing.cli || "codex") : (existing.cli || "codex"),
96
- status: state.status !== undefined ? sanitizeOneLine(state.status, existing.status || "pending") : (existing.status || "pending"),
97
- snapshot: state.snapshot !== undefined ? sanitizeBlock(state.snapshot) : existing.snapshot,
98
- summary: state.summary !== undefined ? sanitizeBlock(state.summary) : existing.summary,
99
- detail: state.detail !== undefined ? sanitizeBlock(state.detail) : existing.detail,
100
- findings: state.findings !== undefined ? sanitizeFiles(state.findings) : existing.findings,
101
- files_changed: state.files_changed !== undefined ? sanitizeFiles(state.files_changed) : existing.files_changed,
102
- confidence: state.confidence !== undefined ? sanitizeOneLine(state.confidence) : existing.confidence,
103
- tokens: state.tokens !== undefined ? normalizeTokens(state.tokens) : existing.tokens,
104
- progress: state.progress !== undefined ? clamp(Number(state.progress) || 0, 0, 1) : existing.progress,
136
+ cli:
137
+ state.cli !== undefined
138
+ ? sanitizeOneLine(state.cli, existing.cli || "codex")
139
+ : existing.cli || "codex",
140
+ status:
141
+ state.status !== undefined
142
+ ? sanitizeOneLine(state.status, existing.status || "pending")
143
+ : existing.status || "pending",
144
+ snapshot:
145
+ state.snapshot !== undefined
146
+ ? sanitizeBlock(state.snapshot)
147
+ : existing.snapshot,
148
+ summary:
149
+ state.summary !== undefined
150
+ ? sanitizeBlock(state.summary)
151
+ : existing.summary,
152
+ detail:
153
+ state.detail !== undefined
154
+ ? sanitizeBlock(state.detail)
155
+ : existing.detail,
156
+ findings:
157
+ state.findings !== undefined
158
+ ? sanitizeFiles(state.findings)
159
+ : existing.findings,
160
+ files_changed:
161
+ state.files_changed !== undefined
162
+ ? sanitizeFiles(state.files_changed)
163
+ : existing.files_changed,
164
+ confidence:
165
+ state.confidence !== undefined
166
+ ? sanitizeOneLine(state.confidence)
167
+ : existing.confidence,
168
+ tokens:
169
+ state.tokens !== undefined
170
+ ? normalizeTokens(state.tokens)
171
+ : existing.tokens,
172
+ progress:
173
+ state.progress !== undefined
174
+ ? clamp(Number(state.progress) || 0, 0, 1)
175
+ : existing.progress,
105
176
  handoff,
106
177
  };
107
178
  }
108
179
 
109
180
  function frame(lines, width, border = MOCHA.border) {
110
181
  const body = lines.length ? lines : [dim("내용 없음")];
111
- const rendered = box(body.map((line) => padRight(truncate(line, width - 4), width - 4)), width, border);
182
+ const rendered = box(
183
+ body.map((line) => padRight(truncate(line, width - 4), width - 4)),
184
+ width,
185
+ border,
186
+ );
112
187
  return [rendered.top, ...rendered.body, rendered.bot];
113
188
  }
114
189
 
@@ -128,9 +203,10 @@ function buildHeader(width, names, workers, pipeline, startedAt) {
128
203
  else if (status === "running" || status === "in_progress") counts.running++;
129
204
  }
130
205
  const elapsed = Math.max(0, Math.round((Date.now() - startedAt) / 1000));
131
- const line1 = color(` triflux ${VERSION} `, FG.white, BG.header)
132
- + ` ${bold(`phase ${pipeline.phase || "exec"}`)}`
133
- + ` ${dim(`+${elapsed}s`)} ${names.length} workers`;
206
+ const line1 =
207
+ color(` triflux ${VERSION} `, FG.white, BG.header) +
208
+ ` ${bold(`phase ${pipeline.phase || "exec"}`)}` +
209
+ ` ${dim(`+${elapsed}s`)} ${names.length} workers`;
134
210
  const line2 = `${color(`ok ${counts.ok}`, MOCHA.ok)} ${color(`partial ${counts.partial}`, MOCHA.partial)} ${color(`failed ${counts.failed}`, MOCHA.fail)} ${color(`running ${counts.running}`, MOCHA.executing)}`;
135
211
  return [padRight(line1, width), padRight(line2, width)];
136
212
  }
@@ -140,9 +216,14 @@ function buildWorkerRail(names, workers, selectedWorker, width) {
140
216
  ? names.map((name, index) => {
141
217
  const worker = workers.get(name);
142
218
  const status = runtimeStatus(worker);
143
- const pct = Math.round(((worker?.progress ?? (status === "completed" ? 1 : 0)) || 0) * 100);
144
- const token = worker?.tokens ? ` tok ${formatTokens(worker.tokens)}` : "";
145
- const prefix = name === selectedWorker ? color("▶", MOCHA.blue) : dim("·");
219
+ const pct = Math.round(
220
+ ((worker?.progress ?? (status === "completed" ? 1 : 0)) || 0) * 100,
221
+ );
222
+ const token = worker?.tokens
223
+ ? ` tok ${formatTokens(worker.tokens)}`
224
+ : "";
225
+ const prefix =
226
+ name === selectedWorker ? color("▶", MOCHA.blue) : dim("·");
146
227
  return `${prefix} ${index + 1}.${name} ${stripAnsi(statusBadge(status))} ${pct}%${token}`;
147
228
  })
148
229
  : [dim("workers 없음")];
@@ -175,12 +256,27 @@ function buildDetail(workerName, worker, width, tab, helpVisible) {
175
256
  `verdict ${worker.handoff?.verdict || worker.summary || worker.snapshot || "-"}`,
176
257
  ];
177
258
  if (tab === "files") {
178
- const files = [...sanitizeFiles(worker.handoff?.files_changed), ...sanitizeFiles(worker.files_changed)];
179
- detailLines.push(...(files.length ? files.map((file) => `files ${file}`) : ["files 없음"]));
259
+ const files = [
260
+ ...sanitizeFiles(worker.handoff?.files_changed),
261
+ ...sanitizeFiles(worker.files_changed),
262
+ ];
263
+ detailLines.push(
264
+ ...(files.length ? files.map((file) => `files ${file}`) : ["files 없음"]),
265
+ );
180
266
  } else if (tab === "detail") {
181
- detailLines.push(...wrap(worker.detail || worker.summary || worker.snapshot || "", width - 4));
267
+ detailLines.push(
268
+ ...wrap(
269
+ worker.detail || worker.summary || worker.snapshot || "",
270
+ width - 4,
271
+ ),
272
+ );
182
273
  } else {
183
- detailLines.push(...wrap(worker.summary || worker.snapshot || worker.detail || "", width - 4));
274
+ detailLines.push(
275
+ ...wrap(
276
+ worker.summary || worker.snapshot || worker.detail || "",
277
+ width - 4,
278
+ ),
279
+ );
184
280
  }
185
281
  return frame(detailLines, width, MOCHA.thinking);
186
282
  }
@@ -214,11 +310,21 @@ export function createLiteDashboard(opts = {}) {
214
310
  let inputAttached = false;
215
311
  let rawModeEnabled = false;
216
312
 
217
- const write = (text) => { if (!closed) stream.write(text); };
313
+ const write = (text) => {
314
+ if (!closed) stream.write(text);
315
+ };
218
316
  const workerNames = () => [...workers.keys()].sort();
219
- const viewportColumns = () => Math.max(48, columns || stream?.columns || process.stdout?.columns || FALLBACK_COLUMNS);
220
- const viewportRows = () => Math.max(10, rows || stream?.rows || process.stdout?.rows || FALLBACK_ROWS);
221
- const ensureSelection = (names) => { if (names.length && (!selectedWorker || !workers.has(selectedWorker))) selectedWorker = names[0]; };
317
+ const viewportColumns = () =>
318
+ Math.max(
319
+ 48,
320
+ columns || stream?.columns || process.stdout?.columns || FALLBACK_COLUMNS,
321
+ );
322
+ const viewportRows = () =>
323
+ Math.max(10, rows || stream?.rows || process.stdout?.rows || FALLBACK_ROWS);
324
+ const ensureSelection = (names) => {
325
+ if (names.length && (!selectedWorker || !workers.has(selectedWorker)))
326
+ selectedWorker = names[0];
327
+ };
222
328
 
223
329
  function selectRelative(offset) {
224
330
  const names = workerNames();
@@ -229,9 +335,18 @@ export function createLiteDashboard(opts = {}) {
229
335
  }
230
336
 
231
337
  function triggerOpenSelected() {
232
- if (typeof onOpenSelectedWorker !== "function" || !selectedWorker || !workers.has(selectedWorker)) return;
338
+ if (
339
+ typeof onOpenSelectedWorker !== "function" ||
340
+ !selectedWorker ||
341
+ !workers.has(selectedWorker)
342
+ )
343
+ return;
233
344
  try {
234
- const result = onOpenSelectedWorker(selectedWorker, workers.get(selectedWorker), new Map(workers));
345
+ const result = onOpenSelectedWorker(
346
+ selectedWorker,
347
+ workers.get(selectedWorker),
348
+ new Map(workers),
349
+ );
235
350
  if (result && typeof result.catch === "function") result.catch(() => {});
236
351
  } catch {}
237
352
  }
@@ -239,7 +354,11 @@ export function createLiteDashboard(opts = {}) {
239
354
  function triggerOpenAll() {
240
355
  if (typeof onOpenAllWorkers !== "function") return;
241
356
  try {
242
- const result = onOpenAllWorkers(selectedWorker, workers.get(selectedWorker), new Map(workers));
357
+ const result = onOpenAllWorkers(
358
+ selectedWorker,
359
+ workers.get(selectedWorker),
360
+ new Map(workers),
361
+ );
243
362
  if (result && typeof result.catch === "function") result.catch(() => {});
244
363
  } catch {}
245
364
  }
@@ -265,7 +384,11 @@ export function createLiteDashboard(opts = {}) {
265
384
  return;
266
385
  }
267
386
  if (key === "\r" || key === "\n") {
268
- if (typeof onOpenSelectedWorker === "function" && selectedWorker && workers.has(selectedWorker)) {
387
+ if (
388
+ typeof onOpenSelectedWorker === "function" &&
389
+ selectedWorker &&
390
+ workers.has(selectedWorker)
391
+ ) {
269
392
  triggerOpenSelected();
270
393
  } else {
271
394
  // 콜백 없거나 선택 워커 없으면 탭 순환
@@ -275,7 +398,12 @@ export function createLiteDashboard(opts = {}) {
275
398
  render();
276
399
  return;
277
400
  }
278
- if (key === "\x1b[13;2u" || key === "\x1b[27;13;2~" || key === "\x1b\r" || key === "\x1b\n") {
401
+ if (
402
+ key === "\x1b[13;2u" ||
403
+ key === "\x1b[27;13;2~" ||
404
+ key === "\x1b\r" ||
405
+ key === "\x1b\n"
406
+ ) {
279
407
  triggerOpenAll();
280
408
  return;
281
409
  }
@@ -322,20 +450,53 @@ export function createLiteDashboard(opts = {}) {
322
450
  const width = viewportColumns();
323
451
  const height = viewportRows();
324
452
  const header = buildHeader(width, names, workers, pipeline, startedAt);
325
- const railOnly = !detailExpanded || names.length <= 1 || width < 100 || layout === "single";
453
+ const railOnly =
454
+ !detailExpanded ||
455
+ names.length <= 1 ||
456
+ width < 100 ||
457
+ layout === "single";
326
458
  if (railOnly) {
327
- const sections = [header, ...buildWorkerRail(names, workers, selectedWorker, width)];
328
- if (detailExpanded) sections.push(...buildDetail(selectedWorker, workers.get(selectedWorker), width, focusTab, helpVisible));
459
+ const sections = [
460
+ header,
461
+ ...buildWorkerRail(names, workers, selectedWorker, width),
462
+ ];
463
+ if (detailExpanded)
464
+ sections.push(
465
+ ...buildDetail(
466
+ selectedWorker,
467
+ workers.get(selectedWorker),
468
+ width,
469
+ focusTab,
470
+ helpVisible,
471
+ ),
472
+ );
329
473
  return fitHeight(sections, width, height);
330
474
  }
331
475
  const railWidth = Math.max(28, Math.floor(width * 0.32));
332
476
  const detailWidth = width - railWidth - 1;
333
477
  const bodyHeight = Math.max(6, height - header.length);
334
- const rail = fitHeight(buildWorkerRail(names, workers, selectedWorker, railWidth), railWidth, bodyHeight);
335
- const detail = fitHeight(buildDetail(selectedWorker, workers.get(selectedWorker), detailWidth, focusTab, helpVisible), detailWidth, bodyHeight);
478
+ const rail = fitHeight(
479
+ buildWorkerRail(names, workers, selectedWorker, railWidth),
480
+ railWidth,
481
+ bodyHeight,
482
+ );
483
+ const detail = fitHeight(
484
+ buildDetail(
485
+ selectedWorker,
486
+ workers.get(selectedWorker),
487
+ detailWidth,
488
+ focusTab,
489
+ helpVisible,
490
+ ),
491
+ detailWidth,
492
+ bodyHeight,
493
+ );
336
494
  return [
337
495
  ...header,
338
- ...Array.from({ length: bodyHeight }, (_, index) => `${rail[index]}${dim("│")}${detail[index]}`),
496
+ ...Array.from(
497
+ { length: bodyHeight },
498
+ (_, index) => `${rail[index]}${dim("│")}${detail[index]}`,
499
+ ),
339
500
  ];
340
501
  }
341
502
 
@@ -350,7 +511,11 @@ export function createLiteDashboard(opts = {}) {
350
511
  // Full redraw on first frame or terminal resize to avoid artifacts
351
512
  if (prevFrame.length === 0 || width !== prevWidth) {
352
513
  prevWidth = width;
353
- write(cursorHome + padded.map((l) => l + clearToEnd).join("\n") + eraseBelow);
514
+ write(
515
+ cursorHome +
516
+ padded.map((l) => l + clearToEnd).join("\n") +
517
+ eraseBelow,
518
+ );
354
519
  prevFrame = padded;
355
520
  return;
356
521
  }
@@ -372,8 +537,10 @@ export function createLiteDashboard(opts = {}) {
372
537
  function close() {
373
538
  if (closed) return;
374
539
  if (timer) clearInterval(timer);
375
- if (inputAttached && typeof input?.off === "function") input.off("data", handleInput);
376
- if (rawModeEnabled && typeof input?.setRawMode === "function") input.setRawMode(false);
540
+ if (inputAttached && typeof input?.off === "function")
541
+ input.off("data", handleInput);
542
+ if (rawModeEnabled && typeof input?.setRawMode === "function")
543
+ input.setRawMode(false);
377
544
  if (inputAttached && typeof input?.pause === "function") input.pause();
378
545
  if (isTTY) write(cursorShow + altScreenOff);
379
546
  prevFrame = [];
@@ -387,22 +554,53 @@ export function createLiteDashboard(opts = {}) {
387
554
  }
388
555
 
389
556
  return {
390
- updateWorker(name, state) { workers.set(name, normalizeWorkerState(workers.get(name), state)); ensureSelection(workerNames()); },
391
- updatePipeline(state) { pipeline = { ...pipeline, ...state }; },
392
- setStartTime(ms) { startedAt = ms; },
393
- selectWorker(name) { if (workers.has(name)) selectedWorker = name; },
394
- toggleDetail(force) { detailExpanded = typeof force === "boolean" ? force : !detailExpanded; },
557
+ updateWorker(name, state) {
558
+ workers.set(name, normalizeWorkerState(workers.get(name), state));
559
+ ensureSelection(workerNames());
560
+ },
561
+ updatePipeline(state) {
562
+ pipeline = { ...pipeline, ...state };
563
+ },
564
+ setStartTime(ms) {
565
+ startedAt = ms;
566
+ },
567
+ selectWorker(name) {
568
+ if (workers.has(name)) selectedWorker = name;
569
+ },
570
+ toggleDetail(force) {
571
+ detailExpanded = typeof force === "boolean" ? force : !detailExpanded;
572
+ },
395
573
  render,
396
- getWorkers() { return new Map(workers); },
397
- getFrameCount() { return frameCount; },
398
- getPipelineState() { return { ...pipeline }; },
399
- getSelectedWorker() { return selectedWorker; },
400
- isDetailExpanded() { return detailExpanded; },
401
- getFocusTab() { return focusTab; },
402
- setFocusTab(tab) { if (VALID_TABS.has(tab)) focusTab = tab; },
403
- getLayout() { return layout; },
404
- toggleHelp(force) { helpVisible = typeof force === "boolean" ? force : !helpVisible; },
405
- isHelpVisible() { return helpVisible; },
574
+ getWorkers() {
575
+ return new Map(workers);
576
+ },
577
+ getFrameCount() {
578
+ return frameCount;
579
+ },
580
+ getPipelineState() {
581
+ return { ...pipeline };
582
+ },
583
+ getSelectedWorker() {
584
+ return selectedWorker;
585
+ },
586
+ isDetailExpanded() {
587
+ return detailExpanded;
588
+ },
589
+ getFocusTab() {
590
+ return focusTab;
591
+ },
592
+ setFocusTab(tab) {
593
+ if (VALID_TABS.has(tab)) focusTab = tab;
594
+ },
595
+ getLayout() {
596
+ return layout;
597
+ },
598
+ toggleHelp(force) {
599
+ helpVisible = typeof force === "boolean" ? force : !helpVisible;
600
+ },
601
+ isHelpVisible() {
602
+ return helpVisible;
603
+ },
406
604
  close,
407
605
  };
408
606
  }
@@ -4,10 +4,10 @@
4
4
  // tui.mjs updateWorker() 호환 형식으로 변환한다.
5
5
  // 완료/실패 시 notify.mjs 자동 호출.
6
6
 
7
+ import { EventEmitter } from "node:events";
7
8
  import { readFileSync } from "node:fs";
8
9
  import { join } from "node:path";
9
10
  import { fileURLToPath } from "node:url";
10
- import { EventEmitter } from "node:events";
11
11
 
12
12
  import { STATES } from "./conductor.mjs";
13
13
 
@@ -90,9 +90,8 @@ function mapConductorStateToStatus(conductorState) {
90
90
  function buildWorkerData(entry, watcherSession, hostsData) {
91
91
  const host = entry.host || resolveHostFromSessionName(entry.id);
92
92
  const snapshot = watcherSession?.lastOutput || "";
93
- const probeLevel = entry.health?.level
94
- || watcherSession?.lastProbeLevel
95
- || null;
93
+ const probeLevel =
94
+ entry.health?.level || watcherSession?.lastProbeLevel || null;
96
95
 
97
96
  return Object.freeze({
98
97
  cli: entry.agent || "claude",
@@ -115,15 +114,18 @@ function buildWorkerData(entry, watcherSession, hostsData) {
115
114
  * remote-watcher 전용 엔트리 → TUI 워커 데이터 (conductor 미등록 세션).
116
115
  */
117
116
  function buildWatcherOnlyWorkerData(watcherRecord, hostsData) {
118
- const host = watcherRecord.host
119
- || resolveHostFromSessionName(watcherRecord.sessionName);
117
+ const host =
118
+ watcherRecord.host || resolveHostFromSessionName(watcherRecord.sessionName);
120
119
 
121
120
  return Object.freeze({
122
121
  cli: "claude",
123
122
  role: resolveRole(null, watcherRecord.sessionName),
124
- status: watcherRecord.state === "completed" ? "completed"
125
- : watcherRecord.state === "failed" ? "failed"
126
- : "running",
123
+ status:
124
+ watcherRecord.state === "completed"
125
+ ? "completed"
126
+ : watcherRecord.state === "failed"
127
+ ? "failed"
128
+ : "running",
127
129
  host: host || "unknown",
128
130
  remote: true,
129
131
  sshUser: resolveSshUser(hostsData, host),
@@ -321,7 +323,9 @@ export function createRemoteAdapter(opts = {}) {
321
323
  // watcher-only 세션 (conductor 미등록)
322
324
  if (watcher) {
323
325
  const watcherStatus = watcher.getStatus();
324
- for (const [sessionName, record] of Object.entries(watcherStatus.sessions || {})) {
326
+ for (const [sessionName, record] of Object.entries(
327
+ watcherStatus.sessions || {},
328
+ )) {
325
329
  if (conductorSessionIds.has(sessionName)) continue;
326
330
  const paneName = buildPaneName(sessionName);
327
331
  const workerData = buildWatcherOnlyWorkerData(record, hostsData);