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
@@ -8,7 +8,7 @@
8
8
  // 3. Auto-restart (maxRestarts=3)
9
9
  // 4. JSONL event log (블랙박스 리코더)
10
10
 
11
- import { execFile, spawn } from "node:child_process";
11
+ import { execFile, spawn } from "../lib/spawn-trace.mjs";
12
12
  import { EventEmitter } from "node:events";
13
13
  import {
14
14
  copyFileSync,
@@ -22,7 +22,10 @@ import { createRegistry } from "../../mesh/mesh-registry.mjs";
22
22
  import { broker } from "../account-broker.mjs";
23
23
  import { killProcess } from "../platform.mjs";
24
24
  import { createConductorMeshBridge } from "./conductor-mesh-bridge.mjs";
25
- import { ensureConductorRegistry, getConductorRegistry } from "./conductor-registry.mjs";
25
+ import {
26
+ ensureConductorRegistry,
27
+ getConductorRegistry,
28
+ } from "./conductor-registry.mjs";
26
29
  import { createEventLog } from "./event-log.mjs";
27
30
  import { createHealthProbe } from "./health-probe.mjs";
28
31
  import { buildLauncher } from "./launcher-template.mjs";
@@ -86,7 +89,7 @@ export function createConductor(opts = {}) {
86
89
  const emitter = new EventEmitter();
87
90
  const sessions = new Map();
88
91
  let shuttingDown = false;
89
- let publicApi = null;
92
+ const publicApi = null;
90
93
 
91
94
  // 공유 event log (모든 세션 이벤트를 하나의 JSONL에)
92
95
  const eventLog = createEventLog(join(logsDir, "conductor-events.jsonl"));
@@ -180,7 +183,7 @@ export function createConductor(opts = {}) {
180
183
  execFn(
181
184
  "ssh",
182
185
  [`${sshUser}@${sshIp}`, "psmux", "kill-session", "-t", session.id],
183
- { timeout: 10_000 },
186
+ { timeout: 10_000, reason: "conductor:killRemoteSession" },
184
187
  () => {},
185
188
  );
186
189
  eventLog.append("remote_kill", {
@@ -376,6 +379,7 @@ export function createConductor(opts = {}) {
376
379
  child = spawn(launcher.command, {
377
380
  shell: true,
378
381
  env: { ...process.env, ...launcher.env, ...(session.config.env || {}) },
382
+ reason: `conductor:respawnSession:${session.id}`,
379
383
  stdio: ["pipe", "pipe", "pipe"],
380
384
  windowsHide: true,
381
385
  });
@@ -1,10 +1,9 @@
1
- const DASHBOARD_ANCHORS = new Set([
2
- "window",
3
- "tab",
4
- ]);
1
+ const DASHBOARD_ANCHORS = new Set(["window", "tab"]);
5
2
 
6
3
  export function normalizeDashboardAnchor(value) {
7
- const normalized = String(value ?? "").trim().toLowerCase();
4
+ const normalized = String(value ?? "")
5
+ .trim()
6
+ .toLowerCase();
8
7
  if (!normalized) return "window";
9
8
  return DASHBOARD_ANCHORS.has(normalized) ? normalized : "window";
10
9
  }
@@ -12,7 +12,9 @@ const DASHBOARD_LAYOUTS = new Set([
12
12
  ]);
13
13
 
14
14
  export function normalizeDashboardLayout(value, { allowAuto = true } = {}) {
15
- const normalized = String(value ?? "").trim().toLowerCase();
15
+ const normalized = String(value ?? "")
16
+ .trim()
17
+ .toLowerCase();
16
18
  if (!normalized) return "single";
17
19
  if (normalized === "auto" && !allowAuto) return "single";
18
20
  return DASHBOARD_LAYOUTS.has(normalized) ? normalized : "single";
@@ -1,4 +1,4 @@
1
- import { spawn } from "node:child_process";
1
+ import { spawn } from "../lib/spawn-trace.mjs";
2
2
 
3
3
  import { psmuxExec } from "./psmux.mjs";
4
4
  import {
@@ -10,7 +10,9 @@ import {
10
10
  } from "./session.mjs";
11
11
 
12
12
  function sanitizeWindowTitle(value, fallback = "triflux") {
13
- const text = String(value || "").replace(/[\r\n]+/g, " ").trim();
13
+ const text = String(value || "")
14
+ .replace(/[\r\n]+/g, " ")
15
+ .trim();
14
16
  return text || fallback;
15
17
  }
16
18
 
@@ -19,7 +21,9 @@ function sanitizeSessionName(value) {
19
21
  }
20
22
 
21
23
  function sanitizeWorkingDirectory(value) {
22
- const text = String(value || "").replace(/[\r\n\x00-\x1f]/g, "").trim();
24
+ const text = String(value || "")
25
+ .replace(/[\r\n\x00-\x1f]/g, "")
26
+ .trim();
23
27
  return text || process.cwd();
24
28
  }
25
29
 
@@ -32,7 +36,10 @@ export function parseWorkerNumber(value) {
32
36
  return null;
33
37
  }
34
38
 
35
- export function decideDashboardOpenMode({ openAll = false, hasWtSession = !!process.env.WT_SESSION } = {}) {
39
+ export function decideDashboardOpenMode({
40
+ openAll = false,
41
+ hasWtSession = !!process.env.WT_SESSION,
42
+ } = {}) {
36
43
  if (openAll) return hasWtSession ? "tab" : "window";
37
44
  return hasWtSession ? "split" : "window";
38
45
  }
@@ -44,19 +51,32 @@ function spawnWindowsTerminal(spec, opts = {}) {
44
51
  mode = "window",
45
52
  title = "triflux",
46
53
  cwd = process.cwd(),
47
- split = { orientation: "H", size: 0.50 },
54
+ split = { orientation: "H", size: 0.5 },
48
55
  } = opts;
49
56
 
50
57
  const safeTitle = sanitizeWindowTitle(title);
51
58
  const safeCwd = sanitizeWorkingDirectory(cwd);
52
59
  const orientation = split?.orientation === "V" ? "V" : "H";
53
- const size = Number.isFinite(split?.size) ? Math.min(0.8, Math.max(0.2, split.size)) : 0.50;
54
- const baseArgs = ["--profile", "triflux", "--title", safeTitle, "-d", safeCwd, "--", spec.command, ...spec.args];
55
- const args = mode === "split"
56
- ? ["-w", "0", "sp", `-${orientation}`, "-s", String(size), ...baseArgs]
57
- : mode === "tab"
58
- ? ["-w", "0", "nt", ...baseArgs]
59
- : ["-w", "new", ...baseArgs];
60
+ const size = Number.isFinite(split?.size)
61
+ ? Math.min(0.8, Math.max(0.2, split.size))
62
+ : 0.5;
63
+ const baseArgs = [
64
+ "--profile",
65
+ "triflux",
66
+ "--title",
67
+ safeTitle,
68
+ "-d",
69
+ safeCwd,
70
+ "--",
71
+ spec.command,
72
+ ...spec.args,
73
+ ];
74
+ const args =
75
+ mode === "split"
76
+ ? ["-w", "0", "sp", `-${orientation}`, "-s", String(size), ...baseArgs]
77
+ : mode === "tab"
78
+ ? ["-w", "0", "nt", ...baseArgs]
79
+ : ["-w", "new", ...baseArgs];
60
80
 
61
81
  const child = spawn("wt.exe", args, {
62
82
  detached: true,
@@ -78,7 +98,8 @@ export function focusManagedPane(target, opts = {}) {
78
98
 
79
99
  if (!paneRef) return false;
80
100
  try {
81
- if (detectMultiplexer() === "psmux") psmuxExec(["select-pane", "-t", paneRef]);
101
+ if (detectMultiplexer() === "psmux")
102
+ psmuxExec(["select-pane", "-t", paneRef]);
82
103
  else tmuxExec(`select-pane -t ${paneRef}`);
83
104
  return true;
84
105
  } catch {
@@ -87,12 +108,7 @@ export function focusManagedPane(target, opts = {}) {
87
108
  }
88
109
 
89
110
  export function openHeadlessDashboardTarget(sessionName, opts = {}) {
90
- const {
91
- worker = null,
92
- openAll = false,
93
- cwd = process.cwd(),
94
- title,
95
- } = opts;
111
+ const { worker = null, openAll = false, cwd = process.cwd(), title } = opts;
96
112
 
97
113
  const safeSession = sanitizeSessionName(sessionName);
98
114
  const workerNumber = worker == null ? null : parseWorkerNumber(worker);
@@ -136,12 +152,16 @@ export function openDashboardRuntimeTarget(runtime, opts = {}) {
136
152
  });
137
153
  }
138
154
 
139
- if ((teammateMode === "wt" || String(targetPane).startsWith("wt:")) && !openAll) {
155
+ if (
156
+ (teammateMode === "wt" || String(targetPane).startsWith("wt:")) &&
157
+ !openAll
158
+ ) {
140
159
  return focusManagedPane(targetPane, { teammateMode: "wt", layout });
141
160
  }
142
161
 
143
162
  try {
144
- if (!openAll && targetPane) focusManagedPane(targetPane, { teammateMode, layout });
163
+ if (!openAll && targetPane)
164
+ focusManagedPane(targetPane, { teammateMode, layout });
145
165
  return spawnWindowsTerminal(resolveAttachCommand(sessionName), {
146
166
  mode: decideDashboardOpenMode({ openAll }),
147
167
  title: title || `▲ ${sanitizeSessionName(sessionName)}`,
@@ -6,7 +6,7 @@
6
6
  // node hub/team/dashboard.mjs --session <세션이름> [--interval 2]
7
7
  // node hub/team/dashboard.mjs --team <팀이름> [--interval 2]
8
8
  import { get } from "node:http";
9
- import { AMBER, GREEN, RED, GRAY, DIM, BOLD, RESET } from "./shared.mjs";
9
+ import { AMBER, BOLD, DIM, GRAY, GREEN, RED, RESET } from "./shared.mjs";
10
10
 
11
11
  /**
12
12
  * HTTP GET JSON
@@ -19,11 +19,18 @@ function fetchJson(url) {
19
19
  let data = "";
20
20
  res.on("data", (chunk) => (data += chunk));
21
21
  res.on("end", () => {
22
- try { resolve(JSON.parse(data)); } catch { resolve(null); }
22
+ try {
23
+ resolve(JSON.parse(data));
24
+ } catch {
25
+ resolve(null);
26
+ }
23
27
  });
24
28
  });
25
29
  req.on("error", () => resolve(null));
26
- req.on("timeout", () => { req.destroy(); resolve(null); });
30
+ req.on("timeout", () => {
31
+ req.destroy();
32
+ resolve(null);
33
+ });
27
34
  });
28
35
  }
29
36
 
@@ -77,10 +84,14 @@ function formatUptime(ms) {
77
84
  */
78
85
  function statusIcon(status) {
79
86
  switch (status) {
80
- case "completed": return `${GREEN}✓${RESET}`;
81
- case "in_progress": return `${AMBER}●${RESET}`;
82
- case "failed": return `${RED}✗${RESET}`;
83
- default: return `${GRAY}○${RESET}`;
87
+ case "completed":
88
+ return `${GREEN}✓${RESET}`;
89
+ case "in_progress":
90
+ return `${AMBER}●${RESET}`;
91
+ case "failed":
92
+ return `${RED}✗${RESET}`;
93
+ default:
94
+ return `${GRAY}○${RESET}`;
84
95
  }
85
96
  }
86
97
 
@@ -118,7 +129,12 @@ function buildMemberList(hubTasks, supervisorMembers, teamState) {
118
129
  members.push({
119
130
  name: m.name,
120
131
  cli: m.cli || "",
121
- status: m.status === "running" ? "in_progress" : m.status === "exited" ? "completed" : m.status,
132
+ status:
133
+ m.status === "running"
134
+ ? "in_progress"
135
+ : m.status === "exited"
136
+ ? "completed"
137
+ : m.status,
122
138
  subject: "",
123
139
  preview: m.lastPreview || "",
124
140
  });
@@ -128,7 +144,9 @@ function buildMemberList(hubTasks, supervisorMembers, teamState) {
128
144
 
129
145
  // teamState 폴백 (하위 호환)
130
146
  const panes = teamState?.panes || {};
131
- for (const [, paneInfo] of Object.entries(panes).filter(([, v]) => v.role !== "dashboard" && v.role !== "lead")) {
147
+ for (const [, paneInfo] of Object.entries(panes).filter(
148
+ ([, v]) => v.role !== "dashboard" && v.role !== "lead",
149
+ )) {
132
150
  members.push({
133
151
  name: paneInfo.agentId || paneInfo.name || "?",
134
152
  cli: paneInfo.cli || "",
@@ -162,27 +180,41 @@ export async function renderDashboard(sessionName, opts = {}) {
162
180
  // 데이터 수집 (병렬)
163
181
  const [hubStatus, taskListRes, supervisorRes] = await Promise.all([
164
182
  fetchJson(`${hubUrl}/status`),
165
- teamName ? fetchPost(`${hubUrl}/bridge/team/task-list`, { team_name: teamName }) : null,
183
+ teamName
184
+ ? fetchPost(`${hubUrl}/bridge/team/task-list`, { team_name: teamName })
185
+ : null,
166
186
  supervisorUrl ? fetchJson(`${supervisorUrl}/status`) : null,
167
187
  ]);
168
188
 
169
189
  const hubOnline = !!hubStatus;
170
- const hubState = hubOnline ? `${GREEN}● online${RESET}` : `${RED}● offline${RESET}`;
171
- const uptime = hubStatus?.hub?.uptime ? formatUptime(hubStatus.hub.uptime) : "-";
190
+ const hubState = hubOnline
191
+ ? `${GREEN}● online${RESET}`
192
+ : `${RED}● offline${RESET}`;
193
+ const uptime = hubStatus?.hub?.uptime
194
+ ? formatUptime(hubStatus.hub.uptime)
195
+ : "-";
172
196
  const queueSize = hubStatus?.hub?.queue_depth ?? 0;
173
197
 
174
198
  // Hub task 데이터
175
- const hubTasks = taskListRes?.ok ? (taskListRes.data?.tasks || []) : [];
176
- const completedCount = hubTasks.filter((t) => t.status === "completed").length;
199
+ const hubTasks = taskListRes?.ok ? taskListRes.data?.tasks || [] : [];
200
+ const completedCount = hubTasks.filter(
201
+ (t) => t.status === "completed",
202
+ ).length;
177
203
  const totalCount = hubTasks.length;
178
204
 
179
205
  // Supervisor 멤버 데이터
180
- const supervisorMembers = supervisorRes?.ok ? (supervisorRes.data?.members || []) : [];
206
+ const supervisorMembers = supervisorRes?.ok
207
+ ? supervisorRes.data?.members || []
208
+ : [];
181
209
 
182
210
  // 헤더
183
211
  const progress = totalCount > 0 ? ` ${completedCount}/${totalCount}` : "";
184
- console.log(`${AMBER}┌─ ${sessionName}${progress} ${GRAY}${"─".repeat(Math.max(0, W - sessionName.length - progress.length - 3))}${AMBER}┐${RESET}`);
185
- console.log(`${AMBER}│${RESET} Hub: ${hubState} Uptime: ${DIM}${uptime}${RESET} Queue: ${DIM}${queueSize}${RESET}`);
212
+ console.log(
213
+ `${AMBER}┌─ ${sessionName}${progress} ${GRAY}${"─".repeat(Math.max(0, W - sessionName.length - progress.length - 3))}${AMBER}┐${RESET}`,
214
+ );
215
+ console.log(
216
+ `${AMBER}│${RESET} Hub: ${hubState} Uptime: ${DIM}${uptime}${RESET} Queue: ${DIM}${queueSize}${RESET}`,
217
+ );
186
218
  console.log(`${AMBER}│${RESET}`);
187
219
 
188
220
  // 멤버/워커 렌더링
@@ -194,20 +226,29 @@ export async function renderDashboard(sessionName, opts = {}) {
194
226
  for (const m of members) {
195
227
  const icon = statusIcon(m.status);
196
228
  const label = `[${m.name}]`;
197
- const cliTag = m.cli ? m.cli.charAt(0).toUpperCase() + m.cli.slice(1) : "";
229
+ const cliTag = m.cli
230
+ ? m.cli.charAt(0).toUpperCase() + m.cli.slice(1)
231
+ : "";
198
232
 
199
233
  // 진행률 추정
200
- const pct = m.status === "completed" ? 100
201
- : m.status === "in_progress" ? 50
202
- : m.status === "failed" ? 100
203
- : 0;
234
+ const pct =
235
+ m.status === "completed"
236
+ ? 100
237
+ : m.status === "in_progress"
238
+ ? 50
239
+ : m.status === "failed"
240
+ ? 100
241
+ : 0;
204
242
 
205
- console.log(`${AMBER}│${RESET} ${BOLD}${label}${RESET} ${cliTag} ${icon} ${m.status || "pending"} ${progressBar(pct)}`);
243
+ console.log(
244
+ `${AMBER}│${RESET} ${BOLD}${label}${RESET} ${cliTag} ${icon} ${m.status || "pending"} ${progressBar(pct)}`,
245
+ );
206
246
 
207
247
  // 미리보기: supervisor lastPreview > task subject
208
248
  const preview = m.preview || m.subject || "";
209
249
  if (preview) {
210
- const truncated = preview.length > W - 8 ? preview.slice(0, W - 11) + "..." : preview;
250
+ const truncated =
251
+ preview.length > W - 8 ? preview.slice(0, W - 11) + "..." : preview;
211
252
  console.log(`${AMBER}│${RESET} ${DIM}> ${truncated}${RESET}`);
212
253
  }
213
254
  console.log(`${AMBER}│${RESET}`);
@@ -228,10 +269,12 @@ async function loadTeamState() {
228
269
  const sessionId = process.env.CLAUDE_SESSION_ID;
229
270
  if (sessionId) {
230
271
  const sessionPath = join(hubDir, `team-state-${sessionId}.json`);
231
- if (existsSync(sessionPath)) return JSON.parse(readFileSync(sessionPath, "utf8"));
272
+ if (existsSync(sessionPath))
273
+ return JSON.parse(readFileSync(sessionPath, "utf8"));
232
274
  }
233
275
  const legacyPath = join(hubDir, "team-state.json");
234
- if (existsSync(legacyPath)) return JSON.parse(readFileSync(legacyPath, "utf8"));
276
+ if (existsSync(legacyPath))
277
+ return JSON.parse(readFileSync(legacyPath, "utf8"));
235
278
  return {};
236
279
  } catch {
237
280
  return {};
@@ -244,11 +287,16 @@ if (process.argv[1]?.includes("dashboard.mjs")) {
244
287
  const teamIdx = process.argv.indexOf("--team");
245
288
  const sessionName = sessionIdx !== -1 ? process.argv[sessionIdx + 1] : null;
246
289
  const teamName = teamIdx !== -1 ? process.argv[teamIdx + 1] : null;
247
- const intervalSec = parseInt(process.argv[process.argv.indexOf("--interval") + 1] || "2", 10);
290
+ const intervalSec = parseInt(
291
+ process.argv[process.argv.indexOf("--interval") + 1] || "2",
292
+ 10,
293
+ );
248
294
 
249
295
  const displayName = sessionName || teamName;
250
296
  if (!displayName) {
251
- console.error("사용법: node dashboard.mjs --session <세션이름> [--team <팀이름>] [--interval 2]");
297
+ console.error(
298
+ "사용법: node dashboard.mjs --session <세션이름> [--team <팀이름>] [--interval 2]",
299
+ );
252
300
  process.exit(1);
253
301
  }
254
302
 
@@ -2,8 +2,8 @@
2
2
  // Conductor 세션 lifecycle 이벤트를 JSONL 파일에 기록한다.
3
3
  // 기존 hub/server.mjs의 batch-events.jsonl(MCP 이벤트)과 독립. 공존.
4
4
 
5
- import { createWriteStream, mkdirSync } from 'node:fs';
6
- import { dirname } from 'node:path';
5
+ import { createWriteStream, mkdirSync } from "node:fs";
6
+ import { dirname } from "node:path";
7
7
 
8
8
  /**
9
9
  * JSONL event log 팩토리.
@@ -16,7 +16,7 @@ export function createEventLog(filePath, opts = {}) {
16
16
  const { sessionId } = opts;
17
17
 
18
18
  mkdirSync(dirname(filePath), { recursive: true });
19
- const stream = createWriteStream(filePath, { flags: 'a' });
19
+ const stream = createWriteStream(filePath, { flags: "a" });
20
20
 
21
21
  let closed = false;
22
22
  let pending = 0;
@@ -35,7 +35,9 @@ export function createEventLog(filePath, opts = {}) {
35
35
  ...data,
36
36
  };
37
37
  pending += 1;
38
- stream.write(JSON.stringify(entry) + '\n', () => { pending -= 1; });
38
+ stream.write(JSON.stringify(entry) + "\n", () => {
39
+ pending -= 1;
40
+ });
39
41
  }
40
42
 
41
43
  /**
@@ -45,9 +47,9 @@ export function createEventLog(filePath, opts = {}) {
45
47
  function flush() {
46
48
  if (closed) return Promise.resolve();
47
49
  return new Promise((resolve, reject) => {
48
- stream.once('error', reject);
49
- stream.write('', () => {
50
- stream.removeListener('error', reject);
50
+ stream.once("error", reject);
51
+ stream.write("", () => {
52
+ stream.removeListener("error", reject);
51
53
  resolve();
52
54
  });
53
55
  });
@@ -69,8 +71,14 @@ export function createEventLog(filePath, opts = {}) {
69
71
  append,
70
72
  flush,
71
73
  close,
72
- get filePath() { return filePath; },
73
- get pending() { return pending; },
74
- get closed() { return closed; },
74
+ get filePath() {
75
+ return filePath;
76
+ },
77
+ get pending() {
78
+ return pending;
79
+ },
80
+ get closed() {
81
+ return closed;
82
+ },
75
83
  });
76
84
  }
@@ -44,8 +44,7 @@ Rules:
44
44
  /**
45
45
  * CLI 프롬프트 길이 제한을 고려한 축약 HANDOFF 지시
46
46
  */
47
- export const HANDOFF_INSTRUCTION_SHORT =
48
- `After completing, output this block at the end:
47
+ export const HANDOFF_INSTRUCTION_SHORT = `After completing, output this block at the end:
49
48
  --- HANDOFF ---
50
49
  status: ok | partial | failed
51
50
  lead_action: accept | needs_read | retry | reassign
@@ -71,13 +70,20 @@ export function parseHandoff(rawText) {
71
70
  const endIdx = rest.indexOf("\n---");
72
71
  const block = endIdx === -1 ? rest : rest.slice(0, endIdx);
73
72
 
74
- const lines = block.split("\n").map((l) => l.trim()).filter(Boolean);
73
+ const lines = block
74
+ .split("\n")
75
+ .map((l) => l.trim())
76
+ .filter(Boolean);
75
77
  const parsed = {};
76
78
 
77
79
  for (const line of lines) {
78
80
  const colonIdx = line.indexOf(":");
79
81
  if (colonIdx === -1) continue;
80
- const key = line.slice(0, colonIdx).trim().toLowerCase().replace(/\s+/g, "_");
82
+ const key = line
83
+ .slice(0, colonIdx)
84
+ .trim()
85
+ .toLowerCase()
86
+ .replace(/\s+/g, "_");
81
87
  const value = line.slice(colonIdx + 1).trim();
82
88
  if (key && value) parsed[key] = value;
83
89
  }
@@ -169,14 +175,18 @@ export function validateHandoff(parsed, context = {}) {
169
175
  if (h.status === "failed" || h.status === "partial") {
170
176
  if (h.error_stage) {
171
177
  const v = validateEnum(h.error_stage, ERROR_STAGE_VALUES);
172
- if (!v) { warnings.push(`error_stage: "${h.error_stage}" invalid`); delete h.error_stage; }
173
- else h.error_stage = v;
178
+ if (!v) {
179
+ warnings.push(`error_stage: "${h.error_stage}" invalid`);
180
+ delete h.error_stage;
181
+ } else h.error_stage = v;
174
182
  }
175
183
  for (const f of ["retryable", "partial_output"]) {
176
184
  if (h[f]) {
177
185
  const v = validateEnum(h[f], YES_NO);
178
- if (!v) { warnings.push(`${f}: "${h[f]}" invalid`); delete h[f]; }
179
- else h[f] = v;
186
+ if (!v) {
187
+ warnings.push(`${f}: "${h[f]}" invalid`);
188
+ delete h[f];
189
+ } else h[f] = v;
180
190
  }
181
191
  }
182
192
  }
@@ -204,7 +214,9 @@ export function validateHandoff(parsed, context = {}) {
204
214
  const rest = h.files_changed.length - 3;
205
215
  h.files_changed = [...h.files_changed.slice(0, 3), `+${rest} more`];
206
216
  }
207
- warnings.push(`token cap exceeded (${tokens} > ${TOKEN_HARD_CAP}), trimmed`);
217
+ warnings.push(
218
+ `token cap exceeded (${tokens} > ${TOKEN_HARD_CAP}), trimmed`,
219
+ );
208
220
  }
209
221
 
210
222
  const missingCore = coreRequired.filter((f) => !h[f]);
@@ -232,10 +244,12 @@ export function buildFallbackHandoff(exitCode, resultFile, cli) {
232
244
  confidence: "low",
233
245
  risk: "low",
234
246
  detail: resultFile || "none",
235
- ...(ok ? {} : {
236
- error_stage: exitCode === 124 ? "timeout" : "execution",
237
- retryable: exitCode === 124 ? "no" : "yes",
238
- }),
247
+ ...(ok
248
+ ? {}
249
+ : {
250
+ error_stage: exitCode === 124 ? "timeout" : "execution",
251
+ retryable: exitCode === 124 ? "no" : "yes",
252
+ }),
239
253
  _fallback: true,
240
254
  };
241
255
  }
@@ -248,8 +262,10 @@ export function buildFallbackHandoff(exitCode, resultFile, cli) {
248
262
  export function formatHandoffForLead(handoff) {
249
263
  const h = handoff;
250
264
  const files = Array.isArray(h.files_changed)
251
- ? (h.files_changed.length > 0 ? h.files_changed.join(", ") : "none")
252
- : (h.files_changed || "none");
265
+ ? h.files_changed.length > 0
266
+ ? h.files_changed.join(", ")
267
+ : "none"
268
+ : h.files_changed || "none";
253
269
 
254
270
  const lines = [
255
271
  `[HANDOFF] status=${h.status || "?"} action=${h.lead_action || "?"} confidence=${h.confidence || "?"}`,
@@ -4,7 +4,8 @@
4
4
  // v6.0.0: Lead-direct 모드 (runHeadlessInteractive, autoAttachTerminal)
5
5
  // 의존성: psmux.mjs (Node.js 내장 모듈만 사용)
6
6
 
7
- import { execSync, spawn } from "node:child_process";
7
+ import { execSync } from "node:child_process";
8
+ import { spawn } from "../lib/spawn-trace.mjs";
8
9
  import { randomUUID } from "node:crypto";
9
10
  import {
10
11
  existsSync,