triflux 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 (263) hide show
  1. package/CLAUDE.md +57 -41
  2. package/README.ko.md +41 -45
  3. package/README.md +41 -45
  4. package/adapters/codex/skills/tfx-live/SKILL.md +25 -11
  5. package/bin/tfx-live.mjs +341 -104
  6. package/bin/triflux.mjs +147 -2624
  7. package/config/mcp-registry.json +3 -12
  8. package/cto/brief.mjs +0 -20
  9. package/cto/collect.mjs +1 -200
  10. package/cto/current.schema.json +2 -23
  11. package/cto/hygiene.mjs +2 -61
  12. package/cto/lake-root.mjs +0 -56
  13. package/cto/status.mjs +5 -187
  14. package/hooks/agy-session-hook.mjs +11 -56
  15. package/hooks/codex-session-hook.mjs +11 -101
  16. package/hub/bridge.mjs +65 -1250
  17. package/hub/cli-adapter-base.mjs +12 -90
  18. package/hub/codex-adapter.mjs +0 -1
  19. package/hub/codex-preflight.mjs +5 -28
  20. package/hub/lib/process-utils.mjs +1 -156
  21. package/hub/lib/spawn-trace.mjs +3 -2
  22. package/hub/lib/timeout-defaults.mjs +0 -1
  23. package/hub/team/agent-map.json +0 -1
  24. package/hub/team/agents-row-attach.py +120 -0
  25. package/hub/team/agents-row.mjs +200 -0
  26. package/hub/team/ansi.mjs +1 -35
  27. package/hub/team/backend.mjs +9 -145
  28. package/hub/team/claude-daemon-control.mjs +1 -2
  29. package/hub/team/claude-session-projection.mjs +5 -73
  30. package/hub/team/cli/commands/attach.mjs +1 -17
  31. package/hub/team/cli/commands/control.mjs +12 -38
  32. package/hub/team/cli/commands/debug.mjs +2 -39
  33. package/hub/team/cli/commands/focus.mjs +5 -31
  34. package/hub/team/cli/commands/interrupt.mjs +6 -25
  35. package/hub/team/cli/commands/kill.mjs +1 -30
  36. package/hub/team/cli/commands/list.mjs +0 -16
  37. package/hub/team/cli/commands/send.mjs +6 -26
  38. package/hub/team/cli/commands/start/index.mjs +42 -133
  39. package/hub/team/cli/commands/start/parse-args.mjs +15 -40
  40. package/hub/team/cli/commands/start/start-headless.mjs +138 -105
  41. package/hub/team/cli/commands/start/start-mux.mjs +2 -7
  42. package/hub/team/cli/commands/status.mjs +2 -57
  43. package/hub/team/cli/commands/stop.mjs +14 -19
  44. package/hub/team/cli/help.mjs +4 -5
  45. package/hub/team/cli/manifest.mjs +5 -1
  46. package/hub/team/cli/render.mjs +0 -12
  47. package/hub/team/cli/services/runtime-mode.mjs +27 -98
  48. package/hub/team/cli/services/state-store.mjs +5 -20
  49. package/hub/team/codex-queue.mjs +385 -0
  50. package/hub/team/handoff.mjs +3 -34
  51. package/hub/team/headless.mjs +108 -1296
  52. package/hub/team/orchestrator.mjs +4 -30
  53. package/hub/team/pane.mjs +6 -28
  54. package/hub/team/psmux.mjs +3 -34
  55. package/hub/team/remote-session.mjs +1 -2
  56. package/hub/team/session-context.mjs +1 -1
  57. package/hub/team/session.mjs +7 -91
  58. package/hub/team/tui-core.mjs +1 -1
  59. package/hub/team/tui-viewer.mjs +11 -58
  60. package/hub/team/tui.mjs +8 -306
  61. package/hub/team/uds-orchestrator.mjs +2 -580
  62. package/hub/team/wt-manager.mjs +2 -2
  63. package/hub/workers/factory.mjs +6 -84
  64. package/hub/workers/interface.mjs +1 -7
  65. package/hub/workers/lib/jsonrpc-core.mjs +11 -0
  66. package/hub/workers/lib/jsonrpc-ws-uds.mjs +5 -8
  67. package/hud/cli-policy.mjs +0 -22
  68. package/hud/constants.mjs +13 -26
  69. package/hud/hud-qos-status.mjs +42 -54
  70. package/hud/providers/gemini.mjs +125 -20
  71. package/hud/renderers.mjs +101 -122
  72. package/hud/utils.mjs +10 -6
  73. package/package.json +7 -12
  74. package/scripts/__tests__/mcp-guard-engine-http-headers.test.mjs +6 -6
  75. package/scripts/__tests__/mcp-guard-engine-policy-sync.test.mjs +1 -4
  76. package/scripts/__tests__/mcp-guard-engine-proactive-sync.test.mjs +16 -21
  77. package/scripts/__tests__/mcp-guard-engine-stdio-sync.test.mjs +1 -1
  78. package/scripts/__tests__/mcp-guard-engine-sync-http-headers.test.mjs +2 -2
  79. package/scripts/__tests__/mcp-guard-engine.test.mjs +56 -63
  80. package/scripts/__tests__/spawn-trace.test.mjs +16 -0
  81. package/scripts/check-codex-config-stable.mjs +4 -37
  82. package/scripts/claude-login-detect.mjs +0 -1
  83. package/scripts/claudemd-sync.mjs +1 -28
  84. package/scripts/lib/agent-route-policy.mjs +143 -62
  85. package/scripts/lib/cli-claude.mjs +0 -14
  86. package/scripts/lib/env-probe.mjs +2 -131
  87. package/scripts/lib/gemini-profiles.mjs +2 -69
  88. package/scripts/lib/legacy-mcp-cleanup.mjs +283 -0
  89. package/scripts/lib/machine-profile.mjs +3 -17
  90. package/scripts/lib/mcp-filter.mjs +109 -53
  91. package/scripts/lib/mcp-guard-engine.mjs +40 -98
  92. package/scripts/lib/mcp-health.mjs +2 -2
  93. package/scripts/lib/mcp-server-catalog.mjs +2 -2
  94. package/scripts/lib/psmux-info.mjs +11 -59
  95. package/scripts/lib/session-presence.mjs +2 -100
  96. package/scripts/lib/skill-template.mjs +0 -245
  97. package/scripts/pack.mjs +8 -83
  98. package/scripts/preflight-cache.mjs +5 -18
  99. package/scripts/release/check-packages-mirror.mjs +2 -8
  100. package/scripts/release/version-manifest.json +9 -1
  101. package/scripts/setup.mjs +100 -417
  102. package/scripts/test-lock.mjs +0 -1
  103. package/scripts/tfx-route-post.mjs +5 -69
  104. package/scripts/tfx-route-worker.mjs +4 -79
  105. package/scripts/tfx-route.sh +169 -1495
  106. package/scripts/wt-cli.mjs +1 -2
  107. package/skills/tfx-auto/SKILL.md +123 -741
  108. package/skills/tfx-auto/references/consensus-schema.md +161 -0
  109. package/skills/tfx-auto/references/tmux-observation.md +97 -0
  110. package/skills/tfx-doctor/SKILL.md +5 -6
  111. package/skills/tfx-lead/SKILL.md +195 -0
  112. package/skills/tfx-live/SKILL.md +25 -11
  113. package/skills/tfx-remote/SKILL.md +24 -87
  114. package/skills/tfx-research/SKILL.md +13 -20
  115. package/skills/tfx-review/SKILL.md +33 -35
  116. package/skills/tfx-setup/SKILL.md +1 -1
  117. package/skills/tfx-ship/SKILL.md +6 -27
  118. package/skills/tfx-wt/SKILL.md +34 -68
  119. package/bin/tfx-doctor-tui.mjs +0 -7
  120. package/bin/tfx-setup-tui.mjs +0 -7
  121. package/config/routing-policy.json +0 -124
  122. package/docs/assets/consensus-flow.svg +0 -125
  123. package/docs/assets/deep-vs-light.svg +0 -193
  124. package/docs/assets/demo-dark.gif +0 -0
  125. package/docs/assets/demo-light.gif +0 -0
  126. package/docs/assets/demo-psmux.gif +0 -0
  127. package/hub/adaptive-diagnostic.mjs +0 -350
  128. package/hub/adaptive-inject.mjs +0 -224
  129. package/hub/adaptive-memory.mjs +0 -277
  130. package/hub/adaptive.mjs +0 -172
  131. package/hub/assign-callbacks.mjs +0 -170
  132. package/hub/delegator/contracts.mjs +0 -38
  133. package/hub/delegator/index.mjs +0 -14
  134. package/hub/delegator/schema/delegator-tools.schema.json +0 -250
  135. package/hub/delegator/service.mjs +0 -336
  136. package/hub/delegator/tool-definitions.mjs +0 -35
  137. package/hub/dynamic-routing-engine.mjs +0 -511
  138. package/hub/hitl.mjs +0 -202
  139. package/hub/hub-lifecycle.mjs +0 -248
  140. package/hub/lib/known-errors.json +0 -72
  141. package/hub/lib/memory-store.mjs +0 -1001
  142. package/hub/lib/phase-manager.mjs +0 -493
  143. package/hub/lib/risk-tier.mjs +0 -53
  144. package/hub/lib/ssh-command.mjs +0 -231
  145. package/hub/lib/staged-file-detect.mjs +0 -43
  146. package/hub/lib/state-snapshot.mjs +0 -293
  147. package/hub/lib/tfx-route-args.mjs +0 -353
  148. package/hub/lib/trace-recorder.mjs +0 -153
  149. package/hub/lib/uuidv7.mjs +0 -45
  150. package/hub/memory-doctor.mjs +0 -640
  151. package/hub/middleware/quota-middleware.mjs +0 -70
  152. package/hub/middleware/request-logger.mjs +0 -191
  153. package/hub/paths.mjs +0 -24
  154. package/hub/pipe.mjs +0 -825
  155. package/hub/pipeline/index.mjs +0 -163
  156. package/hub/pipeline/state.mjs +0 -191
  157. package/hub/pipeline/transitions.mjs +0 -131
  158. package/hub/promote-penalties.mjs +0 -138
  159. package/hub/public/dashboard.html +0 -416
  160. package/hub/reflexion.mjs +0 -290
  161. package/hub/role-contract.mjs +0 -314
  162. package/hub/router.mjs +0 -2026
  163. package/hub/routing-snapshot.mjs +0 -261
  164. package/hub/schema.sql +0 -209
  165. package/hub/server.mjs +0 -3048
  166. package/hub/session-fingerprint.mjs +0 -418
  167. package/hub/state.mjs +0 -300
  168. package/hub/store-adapter.mjs +0 -182
  169. package/hub/store.mjs +0 -1736
  170. package/hub/team/build-worker-prompt.mjs +0 -92
  171. package/hub/team/check-mcp-hub.mjs +0 -49
  172. package/hub/team/claude-native-bridge.mjs +0 -987
  173. package/hub/team/cli/commands/start/start-in-process.mjs +0 -50
  174. package/hub/team/cli/commands/start/start-wt.mjs +0 -86
  175. package/hub/team/cli/services/hub-client.mjs +0 -264
  176. package/hub/team/cli/services/native-control.mjs +0 -145
  177. package/hub/team/codex-review.mjs +0 -520
  178. package/hub/team/conductor-mesh-bridge.mjs +0 -122
  179. package/hub/team/conductor-registry.mjs +0 -121
  180. package/hub/team/conductor.mjs +0 -1529
  181. package/hub/team/consensus-meta.mjs +0 -176
  182. package/hub/team/cto-auto-collect.mjs +0 -115
  183. package/hub/team/cto-pull.mjs +0 -31
  184. package/hub/team/daemon-pty-tmux-bridge.mjs +0 -146
  185. package/hub/team/dashboard.mjs +0 -322
  186. package/hub/team/event-log.mjs +0 -84
  187. package/hub/team/execution-mode.mjs +0 -324
  188. package/hub/team/extract-completion-payload.mjs +0 -110
  189. package/hub/team/headless-bridge-session.mjs +0 -17
  190. package/hub/team/health-probe.mjs +0 -435
  191. package/hub/team/interactive-native-launcher.mjs +0 -119
  192. package/hub/team/interactive-native-worker.mjs +0 -80
  193. package/hub/team/interactive-tui-transport.mjs +0 -479
  194. package/hub/team/launcher-template.mjs +0 -144
  195. package/hub/team/lead-control.mjs +0 -191
  196. package/hub/team/mcp-selector.mjs +0 -145
  197. package/hub/team/native-supervisor.mjs +0 -460
  198. package/hub/team/native.mjs +0 -727
  199. package/hub/team/recovery-store.mjs +0 -149
  200. package/hub/team/remote-probe.mjs +0 -292
  201. package/hub/team/remote-watcher.mjs +0 -506
  202. package/hub/team/runtime-strategy.mjs +0 -200
  203. package/hub/team/schemas/worker-completion.json +0 -39
  204. package/hub/team/sentinel-capture.mjs +0 -127
  205. package/hub/team/session-sync.mjs +0 -316
  206. package/hub/team/swarm-cli.mjs +0 -390
  207. package/hub/team/swarm-hypervisor.mjs +0 -2206
  208. package/hub/team/swarm-intent.mjs +0 -216
  209. package/hub/team/swarm-locks.mjs +0 -335
  210. package/hub/team/swarm-planner.mjs +0 -371
  211. package/hub/team/swarm-preflight.mjs +0 -366
  212. package/hub/team/swarm-reconciler.mjs +0 -242
  213. package/hub/team/synapse-cli.mjs +0 -458
  214. package/hub/team/synapse-http.mjs +0 -311
  215. package/hub/team/synapse-registry.mjs +0 -618
  216. package/hub/team/tui-remote-adapter.mjs +0 -377
  217. package/hub/team/tui-synapse.mjs +0 -223
  218. package/hub/team/worker-completion-validator.mjs +0 -79
  219. package/hub/team/worker-sandbox.mjs +0 -125
  220. package/hub/team/worktree-lifecycle.mjs +0 -639
  221. package/hub/team/wt-templates.json +0 -43
  222. package/hub/team-bridge.mjs +0 -27
  223. package/hub/tools.mjs +0 -900
  224. package/hub/workers/antigravity-route-worker.mjs +0 -179
  225. package/hub/workers/codex-app-server-worker.mjs +0 -1067
  226. package/hub/workers/delegator-mcp.mjs +0 -1098
  227. package/hub/workers/lib/jsonrpc-stdio.mjs +0 -204
  228. package/mesh/index.mjs +0 -69
  229. package/mesh/mesh-budget.mjs +0 -137
  230. package/mesh/mesh-heartbeat.mjs +0 -100
  231. package/mesh/mesh-protocol.mjs +0 -96
  232. package/mesh/mesh-queue.mjs +0 -163
  233. package/mesh/mesh-registry.mjs +0 -78
  234. package/mesh/mesh-router.mjs +0 -77
  235. package/scripts/__tests__/skill-surface.test.mjs +0 -93
  236. package/scripts/cache-doctor.mjs +0 -156
  237. package/scripts/cache-warmup.mjs +0 -630
  238. package/scripts/completions/tfx.bash +0 -74
  239. package/scripts/completions/tfx.fish +0 -57
  240. package/scripts/completions/tfx.zsh +0 -98
  241. package/scripts/demo.mjs +0 -197
  242. package/scripts/doctor-dynamic-routing.mjs +0 -87
  243. package/scripts/hub-ensure.mjs +0 -842
  244. package/scripts/hub-watchdog.mjs +0 -90
  245. package/scripts/lib/claudemd-scanner.mjs +0 -223
  246. package/scripts/lib/context.mjs +0 -67
  247. package/scripts/lib/dynamic-route-cli.mjs +0 -107
  248. package/scripts/lib/handoff.mjs +0 -187
  249. package/scripts/lib/logger.mjs +0 -122
  250. package/scripts/lib/stealth-fetch.mjs +0 -176
  251. package/scripts/notion-read.mjs +0 -686
  252. package/scripts/snapshot-codex-state.mjs +0 -37
  253. package/scripts/snapshot-gemini-state.mjs +0 -37
  254. package/scripts/sync-codex-auth.mjs +0 -71
  255. package/scripts/sync-hub-mcp-settings.mjs +0 -697
  256. package/skills/_templates/base.md +0 -4
  257. package/skills/_templates/deep.md +0 -6
  258. package/skills/shared/telemetry-segment.md +0 -6
  259. package/tui/core.mjs +0 -266
  260. package/tui/doctor.mjs +0 -375
  261. package/tui/monitor-data.mjs +0 -152
  262. package/tui/monitor.mjs +0 -318
  263. package/tui/setup.mjs +0 -506
@@ -1,152 +0,0 @@
1
- import { readdirSync, readFileSync, unlinkSync } from "node:fs";
2
- import { get as httpGet } from "node:http";
3
- import { join } from "node:path";
4
-
5
- const HUB_URL = "http://127.0.0.1:27888";
6
- const AGENT_FILE_PREFIX = "tfx-agent-";
7
- const AGENT_FILE_SUFFIX = ".json";
8
-
9
- // 임시 디렉터리 경로를 플랫폼별 우선순위로 고른다.
10
- function getTmpDir(env = process.env) {
11
- return env.TMPDIR || env.TEMP || "/tmp";
12
- }
13
-
14
- // 모니터가 읽을 에이전트 메타 파일만 고른다.
15
- function isAgentFile(name) {
16
- return name.startsWith(AGENT_FILE_PREFIX) && name.endsWith(AGENT_FILE_SUFFIX);
17
- }
18
-
19
- // JSON 파싱 실패는 삼키고 null로 처리한다.
20
- function readAgentRecord(filePath, deps) {
21
- try {
22
- return JSON.parse(deps.readFileSync(filePath, "utf8"));
23
- } catch {
24
- return null;
25
- }
26
- }
27
-
28
- // PID 생존 여부를 확인한다.
29
- // Windows에서 process.kill(pid, 0)이 불안정할 수 있으므로
30
- // 24시간 이상 경과한 에이전트는 좀비로 간주한다.
31
- const MAX_AGENT_AGE_S = 86400;
32
-
33
- function isAlive(pid, deps, startedAt = 0) {
34
- const age = Math.floor(deps.now() / 1000) - (Number(startedAt) || 0);
35
- if (age > MAX_AGENT_AGE_S) return false;
36
- try {
37
- deps.kill(pid, 0);
38
- return true;
39
- } catch {
40
- return false;
41
- }
42
- }
43
-
44
- // 좀비 파일 삭제 실패는 무시한다.
45
- function removeZombie(filePath, deps) {
46
- try {
47
- deps.unlinkSync(filePath);
48
- } catch {}
49
- }
50
-
51
- // 표시용 경과 시간을 계산한다.
52
- function toAgentView(record, now) {
53
- const started = Number(record.started) || 0;
54
- return {
55
- pid: Number(record.pid),
56
- cli: String(record.cli || ""),
57
- agent: String(record.agent || ""),
58
- started,
59
- elapsed: Math.max(0, now - started),
60
- alive: true,
61
- };
62
- }
63
-
64
- // /tmp/tfx-agent-*.json 파일에서 살아있는 에이전트 목록을 읽는다.
65
- function pollAgents(deps = {}) {
66
- const resolved = {
67
- readdirSync,
68
- readFileSync,
69
- unlinkSync,
70
- kill: process.kill.bind(process),
71
- env: process.env,
72
- now: Date.now,
73
- ...deps,
74
- };
75
- const dir = getTmpDir(resolved.env);
76
- let names = [];
77
-
78
- try {
79
- names = resolved.readdirSync(dir).filter(isAgentFile);
80
- } catch {
81
- return [];
82
- }
83
-
84
- const now = Math.floor(resolved.now() / 1000);
85
- const agents = [];
86
- for (const name of names) {
87
- const filePath = join(dir, name);
88
- const record = readAgentRecord(filePath, resolved);
89
- if (!record) continue;
90
- if (!isAlive(Number(record.pid), resolved, record.started)) {
91
- removeZombie(filePath, resolved);
92
- continue;
93
- }
94
- agents.push(toAgentView(record, now));
95
- }
96
- return agents;
97
- }
98
-
99
- // hub /status 경로 URL을 안전하게 만든다.
100
- function buildStatusUrl(hubUrl = HUB_URL) {
101
- return new URL("/status", hubUrl).toString();
102
- }
103
-
104
- // 응답 바디를 JSON으로 해석하고 핵심 필드만 추린다.
105
- function parseStatus(body) {
106
- const parsed = JSON.parse(body);
107
- return {
108
- online: true,
109
- uptime: parsed.uptime,
110
- queueDepth: parsed.queueDepth,
111
- agents: parsed.agents,
112
- };
113
- }
114
-
115
- // node:http.get으로 허브 상태를 2초 안에 조회한다.
116
- function fetchHubStatus(hubUrl = HUB_URL, deps = {}) {
117
- const get = deps.get || httpGet;
118
- return new Promise((resolve) => {
119
- let settled = false;
120
- const finish = (value) => {
121
- if (settled) return;
122
- settled = true;
123
- resolve(value);
124
- };
125
- try {
126
- const req = get(buildStatusUrl(hubUrl), (res) => {
127
- let body = "";
128
- res.setEncoding?.("utf8");
129
- res.on("data", (chunk) => {
130
- body += chunk;
131
- });
132
- res.on("end", () => {
133
- try {
134
- finish(
135
- res.statusCode === 200 ? parseStatus(body) : { online: false },
136
- );
137
- } catch {
138
- finish({ online: false });
139
- }
140
- });
141
- res.on("error", () => finish({ online: false }));
142
- });
143
-
144
- req.setTimeout?.(2000, () => req.destroy(new Error("timeout")));
145
- req.on?.("error", () => finish({ online: false }));
146
- } catch {
147
- finish({ online: false });
148
- }
149
- });
150
- }
151
-
152
- export { fetchHubStatus, pollAgents };
package/tui/monitor.mjs DELETED
@@ -1,318 +0,0 @@
1
- import readline from "node:readline";
2
-
3
- import { fetchHubStatus, pollAgents } from "./monitor-data.mjs";
4
-
5
- const RESET = "\x1b[0m";
6
- const DIM = "\x1b[2m";
7
- const BOLD = "\x1b[1m";
8
- const GREEN = "\x1b[32m";
9
- const RED = "\x1b[31m";
10
- const ORANGE = "\x1b[38;5;208m";
11
- const BLUE = "\x1b[38;5;39m";
12
- const WHITE = "\x1b[37m";
13
- const GRAY = "\x1b[38;5;245m";
14
- const FALLBACK_COLUMNS = 100;
15
-
16
- function colorCli(cli) {
17
- if (cli === "claude") return ORANGE;
18
- if (cli === "gemini") return BLUE;
19
- return WHITE;
20
- }
21
-
22
- function clamp(value, min, max) {
23
- return Math.min(max, Math.max(min, value));
24
- }
25
-
26
- function pad(text, width) {
27
- const value = String(text ?? "");
28
- return value.length >= width
29
- ? value
30
- : value + " ".repeat(width - value.length);
31
- }
32
-
33
- function formatElapsed(ms) {
34
- const total = Math.max(0, Math.floor((Number(ms) || 0) / 1000));
35
- const hours = Math.floor(total / 3600);
36
- const minutes = Math.floor((total % 3600) / 60);
37
- const seconds = total % 60;
38
- if (hours > 0)
39
- return `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
40
- return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
41
- }
42
-
43
- function progressBar(value, width = 16) {
44
- const safe = clamp(Number(value) || 0, 0, 1);
45
- const filled = Math.round(safe * width);
46
- return `[${"█".repeat(filled)}${"░".repeat(Math.max(0, width - filled))}]`;
47
- }
48
-
49
- function escapePs(value) {
50
- return String(value || "").replace(/'/g, "''");
51
- }
52
-
53
- function sanitizeTitle(value, fallback = "agent") {
54
- const safe = String(value || "")
55
- .replace(/[\r\n<>:"/\\|?*\x00-\x1f]/g, " ")
56
- .trim();
57
- return safe || fallback;
58
- }
59
-
60
- function stripUnsafeText(value) {
61
- return String(value || "")
62
- .replace(/[\r\n\t]+/g, " ")
63
- .trim();
64
- }
65
-
66
- function buildOpenCommand(agent) {
67
- const sessionName = escapePs(agent.agent || "");
68
- const pid = Number(agent.pid) || 0;
69
- const processInfo =
70
- pid > 0
71
- ? `Get-Process -Id ${pid} -ErrorAction SilentlyContinue | Format-List Id,ProcessName,StartTime`
72
- : "Write-Host 'PID 정보가 없습니다.'";
73
- return [
74
- "$ErrorActionPreference = 'Continue'",
75
- `if (Get-Command psmux -ErrorAction SilentlyContinue) { try { psmux attach-session -t '${sessionName}' } catch { Write-Host 'psmux attach 실패:' $_.Exception.Message } }`,
76
- "else { Write-Host 'psmux 미설치 환경입니다.' }",
77
- processInfo,
78
- ].join("; ");
79
- }
80
-
81
- function resolveRatio(agent, maxElapsed) {
82
- if (maxElapsed <= 0) return 1;
83
- return clamp(agent.elapsed / maxElapsed, 0, 1);
84
- }
85
-
86
- export function createMonitor(opts = {}) {
87
- const stream = opts.stream || process.stdout;
88
- const input = opts.input || process.stdin;
89
- const refreshMs = Number.isFinite(opts.refreshMs)
90
- ? Math.max(0, opts.refreshMs)
91
- : 1000;
92
- const deps = {
93
- pollAgents,
94
- fetchHubStatus,
95
- emitKeypressEvents: readline.emitKeypressEvents,
96
- importModule: (specifier) => import(specifier),
97
- setIntervalFn: setInterval,
98
- clearIntervalFn: clearInterval,
99
- ...opts._deps,
100
- };
101
-
102
- let timer = null;
103
- let started = false;
104
- let agents = [];
105
- let cursor = 0;
106
- let helpVisible = false;
107
- let hubStatus = { online: false };
108
- let statusMessage = "";
109
-
110
- const write = (chunk) => stream.write(String(chunk));
111
-
112
- function viewportWidth() {
113
- return Math.max(60, Number(stream?.columns) || FALLBACK_COLUMNS);
114
- }
115
-
116
- function syncCursor() {
117
- cursor = agents.length === 0 ? 0 : clamp(cursor, 0, agents.length - 1);
118
- }
119
-
120
- async function openSelectedAgent() {
121
- const agent = agents[cursor];
122
- if (!agent) {
123
- statusMessage = `${RED}선택된 에이전트가 없습니다.${RESET}`;
124
- return false;
125
- }
126
-
127
- const title = sanitizeTitle(
128
- `tfx ${agent.agent || agent.cli || agent.pid}`,
129
- "tfx-agent",
130
- );
131
- const command = buildOpenCommand(agent);
132
-
133
- try {
134
- const { createTerminalOpener } = await deps.importModule(
135
- "../hub/team/terminal-opener.mjs",
136
- );
137
- const opener = createTerminalOpener(deps);
138
- const opened = await opener.openCommand({
139
- title,
140
- command,
141
- cwd: process.cwd(),
142
- profile: "triflux",
143
- targetPane: opts.targetPane,
144
- });
145
- if (!opened) {
146
- statusMessage = `${RED}터미널 열기 실패${RESET}`;
147
- return false;
148
- }
149
- statusMessage = `${GREEN}${stripUnsafeText(agent.agent || "agent")} 열기 시도 완료${RESET}`;
150
- return true;
151
- } catch (error) {
152
- statusMessage = `${RED}열기 실패: ${stripUnsafeText(error?.message || "unknown error")}${RESET}`;
153
- return false;
154
- }
155
- }
156
-
157
- async function renderFrame() {
158
- const [nextAgents, nextHubStatus] = await Promise.all([
159
- Promise.resolve(deps.pollAgents()),
160
- Promise.resolve(deps.fetchHubStatus(opts.hubUrl)),
161
- ]);
162
-
163
- agents = Array.isArray(nextAgents) ? nextAgents : [];
164
- hubStatus =
165
- nextHubStatus && typeof nextHubStatus === "object"
166
- ? nextHubStatus
167
- : { online: false };
168
- syncCursor();
169
-
170
- const width = viewportWidth();
171
- const maxElapsed = agents.reduce(
172
- (max, agent) => Math.max(max, Number(agent.elapsed) || 0),
173
- 0,
174
- );
175
- const hubLabel = hubStatus.online
176
- ? `${GREEN}online${RESET}`
177
- : `${RED}offline${RESET}`;
178
- const header = `${BOLD}triflux monitor${RESET} hub ${hubLabel}`;
179
- const summary = hubStatus.online
180
- ? `${DIM}queue ${hubStatus.queueDepth ?? "-"} · agents ${hubStatus.agents ?? agents.length}${RESET}`
181
- : `${DIM}허브 연결 없음${RESET}`;
182
-
183
- const lines = [pad(header, width), pad(summary, width), ""];
184
-
185
- if (agents.length === 0) {
186
- lines.push(`${DIM}에이전트 없음${RESET}`);
187
- } else {
188
- lines.push(`${BOLD}Agents${RESET}`);
189
- for (const [index, agent] of agents.entries()) {
190
- const selected = index === cursor;
191
- const marker = selected ? `${GREEN}▶${RESET}` : " ";
192
- const cli = stripUnsafeText(agent.cli || "unknown");
193
- const name = stripUnsafeText(agent.agent || `pid:${agent.pid || "?"}`);
194
- const elapsed = formatElapsed(agent.elapsed);
195
- const alive = agent.alive
196
- ? `${GREEN}alive${RESET}`
197
- : `${RED}dead${RESET}`;
198
- const left = `${marker} ${colorCli(cli)}${cli}${RESET} ${BOLD}${name}${RESET} ${GRAY}${elapsed}${RESET}`;
199
- if (hubStatus.online) {
200
- lines.push(
201
- `${left} ${BLUE}${progressBar(resolveRatio(agent, maxElapsed))}${RESET}`,
202
- );
203
- } else {
204
- lines.push(`${left} ${alive}`);
205
- }
206
- }
207
- }
208
-
209
- if (helpVisible) {
210
- lines.push("", `${BOLD}Help${RESET}`);
211
- lines.push(" j / ↓ : 아래 이동");
212
- lines.push(" k / ↑ : 위로 이동");
213
- lines.push(" Enter : 선택 에이전트 열기");
214
- lines.push(" r : 즉시 새로고침");
215
- lines.push(" h : 도움말 토글");
216
- lines.push(" q / Ctrl+C : 종료");
217
- }
218
-
219
- if (statusMessage) lines.push("", statusMessage);
220
- lines.push(
221
- "",
222
- `${DIM}[Enter] open [j/k] select [r] refresh [h] help [q] quit${RESET}`,
223
- );
224
-
225
- write("\x1b[H");
226
- write(lines.join("\n"));
227
- write("\x1b[J");
228
- return { agents, hubStatus };
229
- }
230
-
231
- async function handleKey(str, key = {}) {
232
- const name = key?.name || "";
233
- if (str === "j" || name === "down") {
234
- syncCursor();
235
- cursor =
236
- agents.length === 0 ? 0 : Math.min(cursor + 1, agents.length - 1);
237
- await renderFrame();
238
- return;
239
- }
240
- if (str === "k" || name === "up") {
241
- syncCursor();
242
- cursor = agents.length === 0 ? 0 : Math.max(cursor - 1, 0);
243
- await renderFrame();
244
- return;
245
- }
246
- if (name === "return" || name === "enter") {
247
- await openSelectedAgent();
248
- await renderFrame();
249
- return;
250
- }
251
- if (str === "r") {
252
- await renderFrame();
253
- return;
254
- }
255
- if (str === "h") {
256
- helpVisible = !helpVisible;
257
- await renderFrame();
258
- return;
259
- }
260
- if (str === "q" || (key?.ctrl && name === "c")) {
261
- stop();
262
- }
263
- }
264
-
265
- async function start() {
266
- if (started) return;
267
- started = true;
268
- write("\x1b[?1049h");
269
- write("\x1b[?25l");
270
- if (typeof input?.setRawMode === "function") input.setRawMode(true);
271
- deps.emitKeypressEvents(input);
272
- if (typeof input?.resume === "function") input.resume();
273
- input?.on?.("keypress", handleKey);
274
- if (refreshMs > 0) {
275
- timer = deps.setIntervalFn(() => {
276
- void renderFrame();
277
- }, refreshMs);
278
- timer?.unref?.();
279
- }
280
- await renderFrame();
281
- }
282
-
283
- function stop() {
284
- if (!started) return;
285
- started = false;
286
- if (timer) {
287
- deps.clearIntervalFn(timer);
288
- timer = null;
289
- }
290
- input?.removeListener?.("keypress", handleKey);
291
- if (typeof input?.setRawMode === "function") input.setRawMode(false);
292
- write("\x1b[?25h");
293
- write("\x1b[?1049l");
294
- }
295
-
296
- function destroy() {
297
- stop();
298
- }
299
-
300
- return {
301
- start,
302
- stop,
303
- destroy,
304
- renderFrame,
305
- handleKey,
306
- openSelectedAgent,
307
- getState() {
308
- return {
309
- cursor,
310
- helpVisible,
311
- hubStatus: { ...hubStatus },
312
- agents: agents.map((agent) => ({ ...agent })),
313
- started,
314
- statusMessage,
315
- };
316
- },
317
- };
318
- }