triflux 10.9.19 → 10.9.21

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 (113) hide show
  1. package/CLAUDE.md +212 -0
  2. package/hub/lib/bash-path.mjs +73 -0
  3. package/hub/team/dashboard-open.mjs +1 -68
  4. package/hub/team/native-supervisor.mjs +9 -2
  5. package/hub/team/psmux.mjs +5 -13
  6. package/hub/team/session.mjs +6 -26
  7. package/hub/team/swarm-hypervisor.mjs +205 -27
  8. package/hub/team/synapse-http.mjs +1 -0
  9. package/hub/team/tui-core.mjs +292 -0
  10. package/hub/team/tui-lite.mjs +20 -154
  11. package/hub/team/tui-synapse.mjs +213 -0
  12. package/hub/team/tui-widgets.mjs +262 -0
  13. package/hub/team/tui.mjs +159 -255
  14. package/hub/workers/delegator-mcp.mjs +2 -2
  15. package/package.json +21 -62
  16. package/references/hosts.json +46 -0
  17. package/scripts/__tests__/keyword-detector.test.mjs +4 -4
  18. package/scripts/cross-review-gate.mjs +13 -0
  19. package/scripts/remote-spawn.mjs +11 -46
  20. package/scripts/session-spawn-helper.mjs +8 -21
  21. package/scripts/test-tfx-route-no-claude-native.mjs +4 -2
  22. package/scripts/tfx-route.sh +13 -0
  23. package/skills/tfx-deep-interview/SKILL.md +6 -6
  24. package/skills/tfx-deep-interview/SKILL.md.tmpl +6 -6
  25. package/skills/tfx-index/SKILL.md +1 -1
  26. package/skills/tfx-index/SKILL.md.tmpl +1 -1
  27. package/skills/tfx-interview/SKILL.md +9 -9
  28. package/skills/tfx-interview/SKILL.md.tmpl +9 -9
  29. package/skills/tfx-plan/SKILL.md +1 -1
  30. package/skills/tfx-plan/SKILL.md.tmpl +1 -1
  31. package/skills/tfx-research/SKILL.md +1 -1
  32. package/skills/tfx-research/SKILL.md.tmpl +1 -1
  33. package/skills/tfx-workspace/async-tests/run-tests.sh +203 -0
  34. package/skills/tfx-workspace/evals/evals.json +79 -0
  35. package/skills/tfx-workspace/iteration-1/benchmark.json +524 -0
  36. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/eval_metadata.json +11 -0
  37. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/grading.json +25 -0
  38. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/outputs/analysis.md +154 -0
  39. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/timing.json +5 -0
  40. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/grading.json +25 -0
  41. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/outputs/analysis.md +126 -0
  42. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/timing.json +5 -0
  43. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/eval_metadata.json +11 -0
  44. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/grading.json +25 -0
  45. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/outputs/analysis.md +119 -0
  46. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/timing.json +5 -0
  47. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/grading.json +25 -0
  48. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/outputs/analysis.md +115 -0
  49. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/timing.json +5 -0
  50. package/skills/tfx-workspace/iteration-1/hub-start-sequence/eval_metadata.json +10 -0
  51. package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/grading.json +20 -0
  52. package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/outputs/analysis.md +86 -0
  53. package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/timing.json +5 -0
  54. package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/grading.json +20 -0
  55. package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/outputs/analysis.md +81 -0
  56. package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/timing.json +5 -0
  57. package/skills/tfx-workspace/iteration-1/multi-team-creation/eval_metadata.json +12 -0
  58. package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/grading.json +30 -0
  59. package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/outputs/analysis.md +316 -0
  60. package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/timing.json +5 -0
  61. package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/grading.json +30 -0
  62. package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/outputs/analysis.md +352 -0
  63. package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/timing.json +5 -0
  64. package/skills/tfx-workspace/iteration-1/review.html +1325 -0
  65. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/eval_metadata.json +12 -0
  66. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/grading.json +30 -0
  67. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/outputs/analysis.md +97 -0
  68. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/timing.json +5 -0
  69. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/grading.json +30 -0
  70. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/outputs/analysis.md +94 -0
  71. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/timing.json +5 -0
  72. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/eval_metadata.json +12 -0
  73. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/grading.json +30 -0
  74. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/outputs/analysis.md +209 -0
  75. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/timing.json +5 -0
  76. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/grading.json +30 -0
  77. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/outputs/analysis.md +193 -0
  78. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/timing.json +5 -0
  79. package/skills/tfx-workspace/iteration-2/benchmark.json +144 -0
  80. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/eval_metadata.json +13 -0
  81. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/grading.json +35 -0
  82. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/outputs/analysis.md +382 -0
  83. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/timing.json +5 -0
  84. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/grading.json +35 -0
  85. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/outputs/analysis.md +333 -0
  86. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/timing.json +5 -0
  87. package/skills/tfx-workspace/iteration-2/review.html +1325 -0
  88. package/skills/tfx-workspace/skill-snapshot/tfx-auto/SKILL.md +217 -0
  89. package/skills/tfx-workspace/skill-snapshot/tfx-auto-codex/SKILL.md +77 -0
  90. package/skills/tfx-workspace/skill-snapshot/tfx-codex/SKILL.md +65 -0
  91. package/skills/tfx-workspace/skill-snapshot/tfx-doctor/SKILL.md +94 -0
  92. package/skills/tfx-workspace/skill-snapshot/tfx-gemini/SKILL.md +82 -0
  93. package/skills/tfx-workspace/skill-snapshot/tfx-hub/SKILL.md +133 -0
  94. package/skills/tfx-workspace/skill-snapshot/tfx-multi/SKILL.md +426 -0
  95. package/skills/tfx-workspace/skill-snapshot/tfx-setup/SKILL.md +101 -0
  96. package/.claude-plugin/marketplace.json +0 -34
  97. package/.claude-plugin/plugin.json +0 -22
  98. package/config/mcp-registry.json +0 -29
  99. package/scripts/__tests__/release-governance.test.mjs +0 -148
  100. package/scripts/release/bump-version.mjs +0 -77
  101. package/scripts/release/check-sync.mjs +0 -51
  102. package/scripts/release/lib.mjs +0 -303
  103. package/scripts/release/prepare.mjs +0 -85
  104. package/scripts/release/publish.mjs +0 -87
  105. package/scripts/release/verify.mjs +0 -81
  106. package/scripts/release/version-manifest.json +0 -26
  107. package/tui/codex-profile.mjs +0 -457
  108. package/tui/core.mjs +0 -266
  109. package/tui/doctor.mjs +0 -375
  110. package/tui/gemini-profile.mjs +0 -299
  111. package/tui/monitor-data.mjs +0 -152
  112. package/tui/monitor.mjs +0 -339
  113. package/tui/setup.mjs +0 -598
@@ -1,299 +0,0 @@
1
- #!/usr/bin/env node
2
- // tui/gemini-profile.mjs — Interactive Gemini Profile Manager
3
- // Codex config.toml 대칭 구조 — JSON 기반 프로필 CRUD
4
- import {
5
- copyFileSync,
6
- existsSync,
7
- mkdirSync,
8
- readFileSync,
9
- writeFileSync,
10
- } from "node:fs";
11
- import { homedir } from "node:os";
12
- import { join } from "node:path";
13
- import {
14
- BOLD,
15
- box,
16
- CYAN,
17
- clear,
18
- confirm,
19
- DIM,
20
- divider,
21
- fail,
22
- GREEN,
23
- info,
24
- input,
25
- label,
26
- ok,
27
- onExit,
28
- RED,
29
- RESET,
30
- select,
31
- showCursor,
32
- table,
33
- WHITE,
34
- warn,
35
- YELLOW,
36
- } from "./core.mjs";
37
-
38
- const GEMINI_DIR = join(homedir(), ".gemini");
39
- const CONFIG_PATH = join(GEMINI_DIR, "triflux-profiles.json");
40
-
41
- const KNOWN_MODELS = [
42
- { label: "gemini-3.1-pro-preview", hint: "3.1 Pro — 플래그십" },
43
- { label: "gemini-3-flash-preview", hint: "3.0 Flash — 빠른 응답" },
44
- { label: "gemini-2.5-pro", hint: "2.5 Pro — 안정" },
45
- { label: "gemini-2.5-flash", hint: "2.5 Flash — 경량" },
46
- { label: "gemini-2.5-flash-lite", hint: "2.5 Flash Lite — 최경량" },
47
- { label: "직접 입력", hint: "" },
48
- ];
49
-
50
- const DEFAULT_CONFIG = {
51
- model: "gemini-3.1-pro-preview",
52
- profiles: {
53
- pro31: {
54
- model: "gemini-3.1-pro-preview",
55
- hint: "3.1 Pro — 플래그십 (1M ctx, 멀티모달)",
56
- },
57
- flash3: {
58
- model: "gemini-3-flash-preview",
59
- hint: "3.0 Flash — 빠른 응답, 비용 효율",
60
- },
61
- pro25: { model: "gemini-2.5-pro", hint: "2.5 Pro — 안정 (추론 강화)" },
62
- flash25: { model: "gemini-2.5-flash", hint: "2.5 Flash — 경량 범용" },
63
- lite25: { model: "gemini-2.5-flash-lite", hint: "2.5 Flash Lite — 최경량" },
64
- },
65
- };
66
-
67
- // ── JSON Config ──
68
-
69
- function readConfig() {
70
- if (!existsSync(CONFIG_PATH)) return structuredClone(DEFAULT_CONFIG);
71
- try {
72
- return JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
73
- } catch {
74
- warn("triflux-profiles.json 파싱 실패, 기본값 사용");
75
- return structuredClone(DEFAULT_CONFIG);
76
- }
77
- }
78
-
79
- function getProfiles(config) {
80
- return Object.entries(config.profiles || {}).map(([name, p]) => ({
81
- name,
82
- model: p.model || config.model,
83
- hint: p.hint || "",
84
- }));
85
- }
86
-
87
- // ── UI Flows ──
88
-
89
- function showStatus(config) {
90
- const profiles = getProfiles(config);
91
-
92
- console.log();
93
- label("기본 모델", `${WHITE}${config.model || "미설정"}${RESET}`);
94
- console.log();
95
-
96
- if (profiles.length === 0) {
97
- warn("등록된 프로필이 없습니다.");
98
- return;
99
- }
100
-
101
- const headers = ["프로필", "모델", "설명"];
102
- const rows = profiles.map((p) => [
103
- `${CYAN}${p.name}${RESET}`,
104
- modelColor(p.model),
105
- p.hint ? `${DIM}${p.hint}${RESET}` : "",
106
- ]);
107
-
108
- table(headers, rows);
109
- }
110
-
111
- function modelColor(model) {
112
- if (!model) return `${DIM}inherit${RESET}`;
113
- if (model.includes("pro")) return `${YELLOW}${model}${RESET}`;
114
- if (model.includes("flash-lite")) return `${GREEN}${model}${RESET}`;
115
- if (model.includes("flash")) return `${CYAN}${model}${RESET}`;
116
- return `${WHITE}${model}${RESET}`;
117
- }
118
-
119
- async function pickModel(current) {
120
- const idx = KNOWN_MODELS.findIndex((m) => m.label === current);
121
- const choice = await select("모델 선택", KNOWN_MODELS, {
122
- initial: Math.max(0, idx),
123
- });
124
- if (!choice) return null;
125
- if (choice.value.label === "직접 입력") {
126
- return await input("모델 ID", current || "");
127
- }
128
- return choice.value.label;
129
- }
130
-
131
- async function editProfile(config) {
132
- const profiles = getProfiles(config);
133
- if (profiles.length === 0) {
134
- warn("편집할 프로필이 없습니다.");
135
- return config;
136
- }
137
-
138
- const options = profiles.map((p) => ({
139
- label: p.name,
140
- hint: `${DIM}${p.model}${RESET}`,
141
- }));
142
-
143
- const picked = await select("편집할 프로필", options);
144
- if (!picked) return config;
145
-
146
- const profile = profiles[picked.index];
147
- console.log();
148
- info(`현재: ${BOLD}${profile.name}${RESET} → ${profile.model}`);
149
-
150
- const newModel = await pickModel(profile.model);
151
- if (newModel === null) return config;
152
-
153
- const hint = await input("설명 (Enter로 유지)", profile.hint);
154
-
155
- console.log();
156
- info(`변경: ${profile.model} → ${BOLD}${newModel}${RESET}`);
157
-
158
- if (!(await confirm("저장하시겠습니까?"))) return config;
159
-
160
- config.profiles[profile.name] = {
161
- model: newModel,
162
- hint: hint || profile.hint,
163
- };
164
- save(config);
165
- ok(`${profile.name} 프로필 저장 완료`);
166
- return readConfig();
167
- }
168
-
169
- async function editDefault(config) {
170
- info(`현재 기본 모델: ${BOLD}${config.model || "미설정"}${RESET}`);
171
-
172
- const newModel = await pickModel(config.model);
173
- if (newModel === null) return config;
174
-
175
- console.log();
176
- info(`변경: ${config.model} → ${BOLD}${newModel}${RESET}`);
177
-
178
- if (!(await confirm("저장하시겠습니까?"))) return config;
179
-
180
- config.model = newModel;
181
- save(config);
182
- ok("기본 모델 저장 완료");
183
- return readConfig();
184
- }
185
-
186
- async function addProfile(config) {
187
- const name = await input("새 프로필 이름");
188
- if (!name) return config;
189
-
190
- if (config.profiles[name]) {
191
- fail(`'${name}' 프로필이 이미 존재합니다.`);
192
- return config;
193
- }
194
-
195
- const model = await pickModel("");
196
- if (!model) return config;
197
-
198
- const hint = await input("설명 (선택)", "");
199
-
200
- console.log();
201
- info(`추가: ${BOLD}${name}${RESET} → ${model}`);
202
- if (!(await confirm("저장하시겠습니까?"))) return config;
203
-
204
- config.profiles[name] = { model, hint };
205
- save(config);
206
- ok(`${name} 프로필 추가 완료`);
207
- return readConfig();
208
- }
209
-
210
- async function removeProfile(config) {
211
- const profiles = getProfiles(config);
212
- if (profiles.length === 0) {
213
- warn("삭제할 프로필이 없습니다.");
214
- return config;
215
- }
216
-
217
- const options = profiles.map((p) => ({ label: p.name, hint: p.model }));
218
- const picked = await select("삭제할 프로필", options);
219
- if (!picked) return config;
220
-
221
- const name = profiles[picked.index].name;
222
- if (
223
- !(await confirm(`${RED}${name}${RESET} 프로필을 삭제하시겠습니까?`, false))
224
- ) {
225
- return config;
226
- }
227
-
228
- delete config.profiles[name];
229
- save(config);
230
- ok(`${name} 프로필 삭제 완료`);
231
- return readConfig();
232
- }
233
-
234
- function save(config) {
235
- if (!existsSync(GEMINI_DIR)) mkdirSync(GEMINI_DIR, { recursive: true });
236
-
237
- // Backup before write
238
- if (existsSync(CONFIG_PATH)) {
239
- copyFileSync(CONFIG_PATH, CONFIG_PATH + ".bak");
240
- }
241
-
242
- writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf8");
243
- }
244
-
245
- // ── Main Loop ──
246
-
247
- const MENU = [
248
- { label: "프로필 모델 변경", hint: "모델 수정" },
249
- { label: "기본 모델 변경", hint: "top-level default" },
250
- { label: "프로필 추가", hint: "새 프로필 생성" },
251
- { label: "프로필 삭제", hint: "기존 프로필 제거" },
252
- { label: "종료", hint: "Ctrl+C" },
253
- ];
254
-
255
- async function main() {
256
- onExit(() => {});
257
- clear();
258
-
259
- let config = readConfig();
260
-
261
- while (true) {
262
- box("Gemini Profile Manager", 46);
263
- showStatus(config);
264
- console.log();
265
-
266
- const choice = await select("작업 선택", MENU);
267
- if (!choice || choice.index === 4) {
268
- console.log();
269
- info("종료합니다.");
270
- showCursor();
271
- break;
272
- }
273
-
274
- console.log();
275
- switch (choice.index) {
276
- case 0:
277
- config = await editProfile(config);
278
- break;
279
- case 1:
280
- config = await editDefault(config);
281
- break;
282
- case 2:
283
- config = await addProfile(config);
284
- break;
285
- case 3:
286
- config = await removeProfile(config);
287
- break;
288
- }
289
-
290
- console.log();
291
- divider(46);
292
- }
293
- }
294
-
295
- main().catch((e) => {
296
- showCursor();
297
- console.error(e);
298
- process.exit(1);
299
- });
@@ -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 };