triflux 10.9.21 → 10.9.23
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.
- package/.claude-plugin/marketplace.json +34 -0
- package/.claude-plugin/plugin.json +22 -0
- package/config/mcp-registry.json +29 -0
- package/hub/account-broker.mjs +6 -4
- package/hub/cli-adapter-base.mjs +14 -14
- package/hub/lib/env-detect.mjs +47 -20
- package/hub/server.mjs +17 -15
- package/hub/team/headless.mjs +10 -0
- package/hub/team/swarm-hypervisor.mjs +2 -2
- package/hub/workers/delegator-mcp.mjs +129 -1
- package/hud/constants.mjs +24 -13
- package/hud/renderers.mjs +2 -1
- package/package.json +62 -21
- package/scripts/__tests__/keyword-detector.test.mjs +4 -4
- package/scripts/__tests__/release-governance.test.mjs +148 -0
- package/scripts/doctor-diagnose.mjs +6 -7
- package/scripts/lib/cross-review-utils.mjs +2 -2
- package/scripts/lib/mcp-filter.mjs +12 -24
- package/scripts/release/bump-version.mjs +77 -0
- package/scripts/release/check-sync.mjs +51 -0
- package/scripts/release/lib.mjs +303 -0
- package/scripts/release/prepare.mjs +85 -0
- package/scripts/release/publish.mjs +87 -0
- package/scripts/release/verify.mjs +81 -0
- package/scripts/release/version-manifest.json +26 -0
- package/scripts/remote-spawn.mjs +3 -3
- package/scripts/setup.mjs +18 -15
- package/scripts/tfx-route.sh +64 -8
- package/tui/codex-profile.mjs +457 -0
- package/tui/core.mjs +266 -0
- package/tui/doctor.mjs +375 -0
- package/tui/gemini-profile.mjs +299 -0
- package/tui/monitor-data.mjs +152 -0
- package/tui/monitor.mjs +339 -0
- package/tui/setup.mjs +598 -0
- package/CLAUDE.md +0 -212
- package/references/hosts.json +0 -46
- package/skills/tfx-workspace/async-tests/run-tests.sh +0 -203
- package/skills/tfx-workspace/evals/evals.json +0 -79
- package/skills/tfx-workspace/iteration-1/benchmark.json +0 -524
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/eval_metadata.json +0 -11
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/grading.json +0 -25
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/outputs/analysis.md +0 -154
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/grading.json +0 -25
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/outputs/analysis.md +0 -126
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/eval_metadata.json +0 -11
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/grading.json +0 -25
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/outputs/analysis.md +0 -119
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/grading.json +0 -25
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/outputs/analysis.md +0 -115
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/eval_metadata.json +0 -10
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/grading.json +0 -20
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/outputs/analysis.md +0 -86
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/grading.json +0 -20
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/outputs/analysis.md +0 -81
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/multi-team-creation/eval_metadata.json +0 -12
- package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/outputs/analysis.md +0 -316
- package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/outputs/analysis.md +0 -352
- package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/review.html +0 -1325
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/eval_metadata.json +0 -12
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/outputs/analysis.md +0 -97
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/outputs/analysis.md +0 -94
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/eval_metadata.json +0 -12
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/outputs/analysis.md +0 -209
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/outputs/analysis.md +0 -193
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-2/benchmark.json +0 -144
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/eval_metadata.json +0 -13
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/grading.json +0 -35
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/outputs/analysis.md +0 -382
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/grading.json +0 -35
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/outputs/analysis.md +0 -333
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-2/review.html +0 -1325
- package/skills/tfx-workspace/skill-snapshot/tfx-auto/SKILL.md +0 -217
- package/skills/tfx-workspace/skill-snapshot/tfx-auto-codex/SKILL.md +0 -77
- package/skills/tfx-workspace/skill-snapshot/tfx-codex/SKILL.md +0 -65
- package/skills/tfx-workspace/skill-snapshot/tfx-doctor/SKILL.md +0 -94
- package/skills/tfx-workspace/skill-snapshot/tfx-gemini/SKILL.md +0 -82
- package/skills/tfx-workspace/skill-snapshot/tfx-hub/SKILL.md +0 -133
- package/skills/tfx-workspace/skill-snapshot/tfx-multi/SKILL.md +0 -426
- package/skills/tfx-workspace/skill-snapshot/tfx-setup/SKILL.md +0 -101
package/tui/monitor.mjs
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import readline from "node:readline";
|
|
3
|
+
|
|
4
|
+
import { fetchHubStatus, pollAgents } from "./monitor-data.mjs";
|
|
5
|
+
|
|
6
|
+
const RESET = "\x1b[0m";
|
|
7
|
+
const DIM = "\x1b[2m";
|
|
8
|
+
const BOLD = "\x1b[1m";
|
|
9
|
+
const GREEN = "\x1b[32m";
|
|
10
|
+
const RED = "\x1b[31m";
|
|
11
|
+
const ORANGE = "\x1b[38;5;208m";
|
|
12
|
+
const BLUE = "\x1b[38;5;39m";
|
|
13
|
+
const WHITE = "\x1b[37m";
|
|
14
|
+
const GRAY = "\x1b[38;5;245m";
|
|
15
|
+
const FALLBACK_COLUMNS = 100;
|
|
16
|
+
|
|
17
|
+
function colorCli(cli) {
|
|
18
|
+
if (cli === "claude") return ORANGE;
|
|
19
|
+
if (cli === "gemini") return BLUE;
|
|
20
|
+
return WHITE;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function clamp(value, min, max) {
|
|
24
|
+
return Math.min(max, Math.max(min, value));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function pad(text, width) {
|
|
28
|
+
const value = String(text ?? "");
|
|
29
|
+
return value.length >= width
|
|
30
|
+
? value
|
|
31
|
+
: value + " ".repeat(width - value.length);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function formatElapsed(ms) {
|
|
35
|
+
const total = Math.max(0, Math.floor((Number(ms) || 0) / 1000));
|
|
36
|
+
const hours = Math.floor(total / 3600);
|
|
37
|
+
const minutes = Math.floor((total % 3600) / 60);
|
|
38
|
+
const seconds = total % 60;
|
|
39
|
+
if (hours > 0)
|
|
40
|
+
return `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
41
|
+
return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function progressBar(value, width = 16) {
|
|
45
|
+
const safe = clamp(Number(value) || 0, 0, 1);
|
|
46
|
+
const filled = Math.round(safe * width);
|
|
47
|
+
return `[${"█".repeat(filled)}${"░".repeat(Math.max(0, width - filled))}]`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function escapePs(value) {
|
|
51
|
+
return String(value || "").replace(/'/g, "''");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function sanitizeTitle(value, fallback = "agent") {
|
|
55
|
+
const safe = String(value || "")
|
|
56
|
+
.replace(/[\r\n<>:"/\\|?*\x00-\x1f]/g, " ")
|
|
57
|
+
.trim();
|
|
58
|
+
return safe || fallback;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function stripUnsafeText(value) {
|
|
62
|
+
return String(value || "")
|
|
63
|
+
.replace(/[\r\n\t]+/g, " ")
|
|
64
|
+
.trim();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function buildOpenCommand(agent) {
|
|
68
|
+
const sessionName = escapePs(agent.agent || "");
|
|
69
|
+
const pid = Number(agent.pid) || 0;
|
|
70
|
+
const processInfo =
|
|
71
|
+
pid > 0
|
|
72
|
+
? `Get-Process -Id ${pid} -ErrorAction SilentlyContinue | Format-List Id,ProcessName,StartTime`
|
|
73
|
+
: "Write-Host 'PID 정보가 없습니다.'";
|
|
74
|
+
return [
|
|
75
|
+
"$ErrorActionPreference = 'Continue'",
|
|
76
|
+
`if (Get-Command psmux -ErrorAction SilentlyContinue) { try { psmux attach-session -t '${sessionName}' } catch { Write-Host 'psmux attach 실패:' $_.Exception.Message } }`,
|
|
77
|
+
"else { Write-Host 'psmux 미설치 환경입니다.' }",
|
|
78
|
+
processInfo,
|
|
79
|
+
].join("; ");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function resolveRatio(agent, maxElapsed) {
|
|
83
|
+
if (maxElapsed <= 0) return 1;
|
|
84
|
+
return clamp(agent.elapsed / maxElapsed, 0, 1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function createMonitor(opts = {}) {
|
|
88
|
+
const stream = opts.stream || process.stdout;
|
|
89
|
+
const input = opts.input || process.stdin;
|
|
90
|
+
const refreshMs = Number.isFinite(opts.refreshMs)
|
|
91
|
+
? Math.max(0, opts.refreshMs)
|
|
92
|
+
: 1000;
|
|
93
|
+
const deps = {
|
|
94
|
+
pollAgents,
|
|
95
|
+
fetchHubStatus,
|
|
96
|
+
emitKeypressEvents: readline.emitKeypressEvents,
|
|
97
|
+
importModule: (specifier) => import(specifier),
|
|
98
|
+
setIntervalFn: setInterval,
|
|
99
|
+
clearIntervalFn: clearInterval,
|
|
100
|
+
spawn,
|
|
101
|
+
...opts._deps,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
let timer = null;
|
|
105
|
+
let started = false;
|
|
106
|
+
let agents = [];
|
|
107
|
+
let cursor = 0;
|
|
108
|
+
let helpVisible = false;
|
|
109
|
+
let hubStatus = { online: false };
|
|
110
|
+
let statusMessage = "";
|
|
111
|
+
|
|
112
|
+
const write = (chunk) => stream.write(String(chunk));
|
|
113
|
+
|
|
114
|
+
function viewportWidth() {
|
|
115
|
+
return Math.max(60, Number(stream?.columns) || FALLBACK_COLUMNS);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function syncCursor() {
|
|
119
|
+
cursor = agents.length === 0 ? 0 : clamp(cursor, 0, agents.length - 1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function openSelectedAgent() {
|
|
123
|
+
const agent = agents[cursor];
|
|
124
|
+
if (!agent) {
|
|
125
|
+
statusMessage = `${RED}선택된 에이전트가 없습니다.${RESET}`;
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const title = sanitizeTitle(
|
|
130
|
+
`tfx ${agent.agent || agent.cli || agent.pid}`,
|
|
131
|
+
"tfx-agent",
|
|
132
|
+
);
|
|
133
|
+
const command = buildOpenCommand(agent);
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
try {
|
|
137
|
+
const { createWtManager } = await deps.importModule(
|
|
138
|
+
"../hub/team/wt-manager.mjs",
|
|
139
|
+
);
|
|
140
|
+
const manager = createWtManager();
|
|
141
|
+
await manager.createTab({
|
|
142
|
+
title,
|
|
143
|
+
command,
|
|
144
|
+
cwd: process.cwd(),
|
|
145
|
+
profile: "triflux",
|
|
146
|
+
});
|
|
147
|
+
} catch {
|
|
148
|
+
const child = deps.spawn(
|
|
149
|
+
"wt.exe",
|
|
150
|
+
[
|
|
151
|
+
"-w",
|
|
152
|
+
"new",
|
|
153
|
+
"nt",
|
|
154
|
+
"--title",
|
|
155
|
+
title,
|
|
156
|
+
"--",
|
|
157
|
+
"powershell.exe",
|
|
158
|
+
"-NoExit",
|
|
159
|
+
"-Command",
|
|
160
|
+
command,
|
|
161
|
+
],
|
|
162
|
+
{
|
|
163
|
+
detached: true,
|
|
164
|
+
stdio: "ignore",
|
|
165
|
+
windowsHide: false,
|
|
166
|
+
},
|
|
167
|
+
);
|
|
168
|
+
child?.unref?.();
|
|
169
|
+
}
|
|
170
|
+
statusMessage = `${GREEN}${stripUnsafeText(agent.agent || "agent")} 열기 시도 완료${RESET}`;
|
|
171
|
+
return true;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
statusMessage = `${RED}열기 실패: ${stripUnsafeText(error?.message || "unknown error")}${RESET}`;
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function renderFrame() {
|
|
179
|
+
const [nextAgents, nextHubStatus] = await Promise.all([
|
|
180
|
+
Promise.resolve(deps.pollAgents()),
|
|
181
|
+
Promise.resolve(deps.fetchHubStatus(opts.hubUrl)),
|
|
182
|
+
]);
|
|
183
|
+
|
|
184
|
+
agents = Array.isArray(nextAgents) ? nextAgents : [];
|
|
185
|
+
hubStatus =
|
|
186
|
+
nextHubStatus && typeof nextHubStatus === "object"
|
|
187
|
+
? nextHubStatus
|
|
188
|
+
: { online: false };
|
|
189
|
+
syncCursor();
|
|
190
|
+
|
|
191
|
+
const width = viewportWidth();
|
|
192
|
+
const maxElapsed = agents.reduce(
|
|
193
|
+
(max, agent) => Math.max(max, Number(agent.elapsed) || 0),
|
|
194
|
+
0,
|
|
195
|
+
);
|
|
196
|
+
const hubLabel = hubStatus.online
|
|
197
|
+
? `${GREEN}online${RESET}`
|
|
198
|
+
: `${RED}offline${RESET}`;
|
|
199
|
+
const header = `${BOLD}triflux monitor${RESET} hub ${hubLabel}`;
|
|
200
|
+
const summary = hubStatus.online
|
|
201
|
+
? `${DIM}queue ${hubStatus.queueDepth ?? "-"} · agents ${hubStatus.agents ?? agents.length}${RESET}`
|
|
202
|
+
: `${DIM}허브 연결 없음${RESET}`;
|
|
203
|
+
|
|
204
|
+
const lines = [pad(header, width), pad(summary, width), ""];
|
|
205
|
+
|
|
206
|
+
if (agents.length === 0) {
|
|
207
|
+
lines.push(`${DIM}에이전트 없음${RESET}`);
|
|
208
|
+
} else {
|
|
209
|
+
lines.push(`${BOLD}Agents${RESET}`);
|
|
210
|
+
for (const [index, agent] of agents.entries()) {
|
|
211
|
+
const selected = index === cursor;
|
|
212
|
+
const marker = selected ? `${GREEN}▶${RESET}` : " ";
|
|
213
|
+
const cli = stripUnsafeText(agent.cli || "unknown");
|
|
214
|
+
const name = stripUnsafeText(agent.agent || `pid:${agent.pid || "?"}`);
|
|
215
|
+
const elapsed = formatElapsed(agent.elapsed);
|
|
216
|
+
const alive = agent.alive
|
|
217
|
+
? `${GREEN}alive${RESET}`
|
|
218
|
+
: `${RED}dead${RESET}`;
|
|
219
|
+
const left = `${marker} ${colorCli(cli)}${cli}${RESET} ${BOLD}${name}${RESET} ${GRAY}${elapsed}${RESET}`;
|
|
220
|
+
if (hubStatus.online) {
|
|
221
|
+
lines.push(
|
|
222
|
+
`${left} ${BLUE}${progressBar(resolveRatio(agent, maxElapsed))}${RESET}`,
|
|
223
|
+
);
|
|
224
|
+
} else {
|
|
225
|
+
lines.push(`${left} ${alive}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (helpVisible) {
|
|
231
|
+
lines.push("", `${BOLD}Help${RESET}`);
|
|
232
|
+
lines.push(" j / ↓ : 아래 이동");
|
|
233
|
+
lines.push(" k / ↑ : 위로 이동");
|
|
234
|
+
lines.push(" Enter : 선택 에이전트 열기");
|
|
235
|
+
lines.push(" r : 즉시 새로고침");
|
|
236
|
+
lines.push(" h : 도움말 토글");
|
|
237
|
+
lines.push(" q / Ctrl+C : 종료");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (statusMessage) lines.push("", statusMessage);
|
|
241
|
+
lines.push(
|
|
242
|
+
"",
|
|
243
|
+
`${DIM}[Enter] open [j/k] select [r] refresh [h] help [q] quit${RESET}`,
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
write("\x1b[H");
|
|
247
|
+
write(lines.join("\n"));
|
|
248
|
+
write("\x1b[J");
|
|
249
|
+
return { agents, hubStatus };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function handleKey(str, key = {}) {
|
|
253
|
+
const name = key?.name || "";
|
|
254
|
+
if (str === "j" || name === "down") {
|
|
255
|
+
syncCursor();
|
|
256
|
+
cursor =
|
|
257
|
+
agents.length === 0 ? 0 : Math.min(cursor + 1, agents.length - 1);
|
|
258
|
+
await renderFrame();
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (str === "k" || name === "up") {
|
|
262
|
+
syncCursor();
|
|
263
|
+
cursor = agents.length === 0 ? 0 : Math.max(cursor - 1, 0);
|
|
264
|
+
await renderFrame();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (name === "return" || name === "enter") {
|
|
268
|
+
await openSelectedAgent();
|
|
269
|
+
await renderFrame();
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (str === "r") {
|
|
273
|
+
await renderFrame();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (str === "h") {
|
|
277
|
+
helpVisible = !helpVisible;
|
|
278
|
+
await renderFrame();
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (str === "q" || (key?.ctrl && name === "c")) {
|
|
282
|
+
stop();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async function start() {
|
|
287
|
+
if (started) return;
|
|
288
|
+
started = true;
|
|
289
|
+
write("\x1b[?1049h");
|
|
290
|
+
write("\x1b[?25l");
|
|
291
|
+
if (typeof input?.setRawMode === "function") input.setRawMode(true);
|
|
292
|
+
deps.emitKeypressEvents(input);
|
|
293
|
+
if (typeof input?.resume === "function") input.resume();
|
|
294
|
+
input?.on?.("keypress", handleKey);
|
|
295
|
+
if (refreshMs > 0) {
|
|
296
|
+
timer = deps.setIntervalFn(() => {
|
|
297
|
+
void renderFrame();
|
|
298
|
+
}, refreshMs);
|
|
299
|
+
timer?.unref?.();
|
|
300
|
+
}
|
|
301
|
+
await renderFrame();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function stop() {
|
|
305
|
+
if (!started) return;
|
|
306
|
+
started = false;
|
|
307
|
+
if (timer) {
|
|
308
|
+
deps.clearIntervalFn(timer);
|
|
309
|
+
timer = null;
|
|
310
|
+
}
|
|
311
|
+
input?.removeListener?.("keypress", handleKey);
|
|
312
|
+
if (typeof input?.setRawMode === "function") input.setRawMode(false);
|
|
313
|
+
write("\x1b[?25h");
|
|
314
|
+
write("\x1b[?1049l");
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function destroy() {
|
|
318
|
+
stop();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
start,
|
|
323
|
+
stop,
|
|
324
|
+
destroy,
|
|
325
|
+
renderFrame,
|
|
326
|
+
handleKey,
|
|
327
|
+
openSelectedAgent,
|
|
328
|
+
getState() {
|
|
329
|
+
return {
|
|
330
|
+
cursor,
|
|
331
|
+
helpVisible,
|
|
332
|
+
hubStatus: { ...hubStatus },
|
|
333
|
+
agents: agents.map((agent) => ({ ...agent })),
|
|
334
|
+
started,
|
|
335
|
+
statusMessage,
|
|
336
|
+
};
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
}
|