@triflux/remote 10.0.0-alpha.1

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 (68) hide show
  1. package/hub/pipe.mjs +579 -0
  2. package/hub/public/dashboard.html +355 -0
  3. package/hub/public/tray-icon.ico +0 -0
  4. package/hub/public/tray-icon.png +0 -0
  5. package/hub/server.mjs +1124 -0
  6. package/hub/store-adapter.mjs +851 -0
  7. package/hub/store.mjs +897 -0
  8. package/hub/team/agent-map.json +11 -0
  9. package/hub/team/ansi.mjs +379 -0
  10. package/hub/team/backend.mjs +90 -0
  11. package/hub/team/cli/commands/attach.mjs +37 -0
  12. package/hub/team/cli/commands/control.mjs +43 -0
  13. package/hub/team/cli/commands/debug.mjs +74 -0
  14. package/hub/team/cli/commands/focus.mjs +53 -0
  15. package/hub/team/cli/commands/interrupt.mjs +36 -0
  16. package/hub/team/cli/commands/kill.mjs +37 -0
  17. package/hub/team/cli/commands/list.mjs +24 -0
  18. package/hub/team/cli/commands/send.mjs +37 -0
  19. package/hub/team/cli/commands/start/index.mjs +106 -0
  20. package/hub/team/cli/commands/start/parse-args.mjs +130 -0
  21. package/hub/team/cli/commands/start/start-headless.mjs +109 -0
  22. package/hub/team/cli/commands/start/start-in-process.mjs +40 -0
  23. package/hub/team/cli/commands/start/start-mux.mjs +73 -0
  24. package/hub/team/cli/commands/start/start-wt.mjs +69 -0
  25. package/hub/team/cli/commands/status.mjs +87 -0
  26. package/hub/team/cli/commands/stop.mjs +31 -0
  27. package/hub/team/cli/commands/task.mjs +30 -0
  28. package/hub/team/cli/commands/tasks.mjs +13 -0
  29. package/hub/team/cli/help.mjs +42 -0
  30. package/hub/team/cli/index.mjs +41 -0
  31. package/hub/team/cli/manifest.mjs +29 -0
  32. package/hub/team/cli/render.mjs +30 -0
  33. package/hub/team/cli/services/attach-fallback.mjs +54 -0
  34. package/hub/team/cli/services/hub-client.mjs +208 -0
  35. package/hub/team/cli/services/member-selector.mjs +30 -0
  36. package/hub/team/cli/services/native-control.mjs +117 -0
  37. package/hub/team/cli/services/runtime-mode.mjs +62 -0
  38. package/hub/team/cli/services/state-store.mjs +48 -0
  39. package/hub/team/cli/services/task-model.mjs +30 -0
  40. package/hub/team/dashboard-anchor.mjs +14 -0
  41. package/hub/team/dashboard-layout.mjs +33 -0
  42. package/hub/team/dashboard-open.mjs +153 -0
  43. package/hub/team/dashboard.mjs +274 -0
  44. package/hub/team/handoff.mjs +303 -0
  45. package/hub/team/headless.mjs +1149 -0
  46. package/hub/team/native-supervisor.mjs +392 -0
  47. package/hub/team/native.mjs +649 -0
  48. package/hub/team/nativeProxy.mjs +681 -0
  49. package/hub/team/orchestrator.mjs +161 -0
  50. package/hub/team/pane.mjs +153 -0
  51. package/hub/team/psmux.mjs +1354 -0
  52. package/hub/team/routing.mjs +223 -0
  53. package/hub/team/session.mjs +611 -0
  54. package/hub/team/shared.mjs +13 -0
  55. package/hub/team/staleState.mjs +361 -0
  56. package/hub/team/tui-lite.mjs +380 -0
  57. package/hub/team/tui-viewer.mjs +463 -0
  58. package/hub/team/tui.mjs +1245 -0
  59. package/hub/tools.mjs +554 -0
  60. package/hub/tray.mjs +376 -0
  61. package/hub/workers/claude-worker.mjs +475 -0
  62. package/hub/workers/codex-mcp.mjs +504 -0
  63. package/hub/workers/delegator-mcp.mjs +1076 -0
  64. package/hub/workers/factory.mjs +21 -0
  65. package/hub/workers/gemini-worker.mjs +373 -0
  66. package/hub/workers/interface.mjs +52 -0
  67. package/hub/workers/worker-utils.mjs +104 -0
  68. package/package.json +31 -0
@@ -0,0 +1,380 @@
1
+ import { altScreenOff, altScreenOn, BG, bold, box, clearScreen, clearToEnd, color, cursorHide, cursorHome, cursorShow, dim, eraseBelow, FG, MOCHA, padRight, progressBar, statusBadge, stripAnsi, truncate, wcswidth } from "./ansi.mjs";
2
+
3
+ const FALLBACK_COLUMNS = 100, FALLBACK_ROWS = 24;
4
+ const VALID_TABS = new Set(["log", "detail", "files"]);
5
+
6
+ let VERSION = "lite";
7
+ try { const { createRequire } = await import("node:module"); VERSION = createRequire(import.meta.url)("../../package.json").version; } catch {}
8
+
9
+ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
10
+
11
+ function sanitizeBlock(text, rawMode = false) {
12
+ const value = String(text || "").replace(/\r/g, "");
13
+ const cleaned = rawMode
14
+ ? value
15
+ : value
16
+ .replace(/```[\s\S]*?(?:```|$)/g, "\n")
17
+ .replace(/^\s*```.*$/gm, "")
18
+ .replace(/^(?:PS\s+\S[^\n]*?>|>\s+|\$\s+)[^\n]*/gm, "");
19
+ return cleaned
20
+ .split("\n")
21
+ .map((line) => line.trim())
22
+ .filter(Boolean)
23
+ .filter((line) => line !== "--- HANDOFF ---")
24
+ .join("\n")
25
+ .trim();
26
+ }
27
+
28
+ function sanitizeOneLine(text, fallback = "") {
29
+ return sanitizeBlock(text).replace(/\s+/g, " ").trim() || fallback;
30
+ }
31
+
32
+ function sanitizeFiles(files) {
33
+ const list = Array.isArray(files) ? files : String(files || "").split(",");
34
+ return list.map((entry) => sanitizeOneLine(entry)).filter(Boolean);
35
+ }
36
+
37
+ function normalizeTokens(tokens) {
38
+ if (tokens === null || tokens === undefined || tokens === "") return "";
39
+ if (typeof tokens === "number" && Number.isFinite(tokens)) return tokens;
40
+ const raw = sanitizeOneLine(tokens);
41
+ const match = raw.match(/(\d+(?:[.,]\d+)?\s*[kKmM]?)/);
42
+ return match ? match[1].replace(/\s+/g, "").toLowerCase() : raw;
43
+ }
44
+
45
+ function formatTokens(tokens) {
46
+ if (!tokens && tokens !== 0) return "n/a";
47
+ if (typeof tokens === "number") {
48
+ if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}m`;
49
+ if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(1)}k`;
50
+ }
51
+ return String(tokens);
52
+ }
53
+
54
+ function wrap(text, width) {
55
+ const limit = Math.max(8, width);
56
+ const lines = [];
57
+ for (const rawLine of sanitizeBlock(text).split("\n")) {
58
+ const words = rawLine.split(/\s+/).filter(Boolean);
59
+ if (words.length === 0) continue;
60
+ let current = "";
61
+ for (const word of words) {
62
+ const next = current ? `${current} ${word}` : word;
63
+ if (wcswidth(next) <= limit) {
64
+ current = next;
65
+ continue;
66
+ }
67
+ if (current) lines.push(current);
68
+ current = word;
69
+ while (wcswidth(current) > limit) {
70
+ lines.push(current.slice(0, limit));
71
+ current = current.slice(limit);
72
+ }
73
+ }
74
+ if (current) lines.push(current);
75
+ }
76
+ return lines;
77
+ }
78
+
79
+ const runtimeStatus = (worker) => worker?.handoff?.status || worker?.status || "pending";
80
+
81
+ function normalizeWorkerState(existing = {}, state = {}) {
82
+ const handoff = state.handoff === undefined
83
+ ? existing.handoff
84
+ : {
85
+ ...(existing.handoff || {}),
86
+ ...(state.handoff || {}),
87
+ verdict: state.handoff?.verdict !== undefined ? sanitizeOneLine(state.handoff.verdict) : existing.handoff?.verdict,
88
+ confidence: state.handoff?.confidence !== undefined ? sanitizeOneLine(state.handoff.confidence) : existing.handoff?.confidence,
89
+ status: state.handoff?.status !== undefined ? sanitizeOneLine(state.handoff.status) : existing.handoff?.status,
90
+ files_changed: state.handoff?.files_changed !== undefined ? sanitizeFiles(state.handoff.files_changed) : existing.handoff?.files_changed,
91
+ };
92
+ return {
93
+ ...existing,
94
+ ...state,
95
+ cli: state.cli !== undefined ? sanitizeOneLine(state.cli, existing.cli || "codex") : (existing.cli || "codex"),
96
+ status: state.status !== undefined ? sanitizeOneLine(state.status, existing.status || "pending") : (existing.status || "pending"),
97
+ snapshot: state.snapshot !== undefined ? sanitizeBlock(state.snapshot) : existing.snapshot,
98
+ summary: state.summary !== undefined ? sanitizeBlock(state.summary) : existing.summary,
99
+ detail: state.detail !== undefined ? sanitizeBlock(state.detail) : existing.detail,
100
+ findings: state.findings !== undefined ? sanitizeFiles(state.findings) : existing.findings,
101
+ files_changed: state.files_changed !== undefined ? sanitizeFiles(state.files_changed) : existing.files_changed,
102
+ confidence: state.confidence !== undefined ? sanitizeOneLine(state.confidence) : existing.confidence,
103
+ tokens: state.tokens !== undefined ? normalizeTokens(state.tokens) : existing.tokens,
104
+ progress: state.progress !== undefined ? clamp(Number(state.progress) || 0, 0, 1) : existing.progress,
105
+ handoff,
106
+ };
107
+ }
108
+
109
+ function frame(lines, width, border = MOCHA.border) {
110
+ const body = lines.length ? lines : [dim("내용 없음")];
111
+ const rendered = box(body.map((line) => padRight(truncate(line, width - 4), width - 4)), width, border);
112
+ return [rendered.top, ...rendered.body, rendered.bot];
113
+ }
114
+
115
+ function fitHeight(lines, width, height) {
116
+ const out = lines.slice(0, Math.max(3, height));
117
+ while (out.length < Math.max(3, height)) out.push(" ".repeat(width));
118
+ return out;
119
+ }
120
+
121
+ function buildHeader(width, names, workers, pipeline, startedAt) {
122
+ const counts = { ok: 0, partial: 0, failed: 0, running: 0 };
123
+ for (const name of names) {
124
+ const status = runtimeStatus(workers.get(name));
125
+ if (status === "ok" || status === "completed") counts.ok++;
126
+ else if (status === "partial") counts.partial++;
127
+ else if (status === "failed") counts.failed++;
128
+ else if (status === "running" || status === "in_progress") counts.running++;
129
+ }
130
+ const elapsed = Math.max(0, Math.round((Date.now() - startedAt) / 1000));
131
+ const line1 = color(` triflux ${VERSION} `, FG.black, BG.header)
132
+ + ` ${bold(`phase ${pipeline.phase || "exec"}`)}`
133
+ + ` ${dim(`+${elapsed}s`)} ${names.length} workers`;
134
+ const line2 = `${color(`ok ${counts.ok}`, MOCHA.ok)} ${color(`partial ${counts.partial}`, MOCHA.partial)} ${color(`failed ${counts.failed}`, MOCHA.fail)} ${color(`running ${counts.running}`, MOCHA.executing)}`;
135
+ return [padRight(line1, width), padRight(line2, width)];
136
+ }
137
+
138
+ function buildWorkerRail(names, workers, selectedWorker, width) {
139
+ const lines = names.length
140
+ ? names.map((name, index) => {
141
+ const worker = workers.get(name);
142
+ const status = runtimeStatus(worker);
143
+ const pct = Math.round(((worker?.progress ?? (status === "completed" ? 1 : 0)) || 0) * 100);
144
+ const token = worker?.tokens ? ` tok ${formatTokens(worker.tokens)}` : "";
145
+ const prefix = name === selectedWorker ? color("▶", MOCHA.blue) : dim("·");
146
+ return `${prefix} ${index + 1}.${name} ${stripAnsi(statusBadge(status))} ${pct}%${token}`;
147
+ })
148
+ : [dim("workers 없음")];
149
+ return frame(lines, width);
150
+ }
151
+
152
+ function buildDetail(workerName, worker, width, tab, helpVisible) {
153
+ if (helpVisible) {
154
+ return frame(
155
+ [
156
+ bold("tui-lite"),
157
+ "j/k or arrows: worker selection",
158
+ "Enter: open selected worker",
159
+ "Shift+Enter: open all workers",
160
+ "l: tabs log/detail/files",
161
+ "1-9: direct select, q: quit",
162
+ "toggleDetail(false) 로 상세 패널 숨김",
163
+ ],
164
+ width,
165
+ MOCHA.blue,
166
+ );
167
+ }
168
+ if (!workerName || !worker) return frame([dim("선택된 워커 없음")], width);
169
+ const detailLines = [
170
+ bold(workerName),
171
+ `status ${runtimeStatus(worker)}`,
172
+ `progress ${Math.round((worker.progress || 0) * 100)}% ${progressBar(Math.round((worker.progress || 0) * 100), 12)}`,
173
+ `tokens ${formatTokens(worker.tokens)}`,
174
+ `confidence ${worker.handoff?.confidence || worker.confidence || "n/a"}`,
175
+ `verdict ${worker.handoff?.verdict || worker.summary || worker.snapshot || "-"}`,
176
+ ];
177
+ if (tab === "files") {
178
+ const files = [...sanitizeFiles(worker.handoff?.files_changed), ...sanitizeFiles(worker.files_changed)];
179
+ detailLines.push(...(files.length ? files.map((file) => `files ${file}`) : ["files 없음"]));
180
+ } else if (tab === "detail") {
181
+ detailLines.push(...wrap(worker.detail || worker.summary || worker.snapshot || "", width - 4));
182
+ } else {
183
+ detailLines.push(...wrap(worker.summary || worker.snapshot || worker.detail || "", width - 4));
184
+ }
185
+ return frame(detailLines, width, MOCHA.thinking);
186
+ }
187
+
188
+ export function createLiteDashboard(opts = {}) {
189
+ const {
190
+ stream = process.stdout,
191
+ input = process.stdin,
192
+ refreshMs = 1000,
193
+ columns,
194
+ rows,
195
+ layout = "auto",
196
+ forceTTY = false,
197
+ onOpenSelectedWorker,
198
+ onOpenAllWorkers,
199
+ } = opts;
200
+
201
+ const isTTY = forceTTY || !!stream?.isTTY;
202
+ const workers = new Map();
203
+ let pipeline = { phase: "exec", fix_attempt: 0 };
204
+ let startedAt = Date.now();
205
+ let timer = null;
206
+ let closed = false;
207
+ let frameCount = 0;
208
+ let selectedWorker = null;
209
+ let detailExpanded = true;
210
+ let focusTab = "log";
211
+ let helpVisible = false;
212
+ let inputAttached = false;
213
+ let rawModeEnabled = false;
214
+
215
+ const write = (text) => { if (!closed) stream.write(text); };
216
+ const workerNames = () => [...workers.keys()].sort();
217
+ const viewportColumns = () => Math.max(48, columns || stream?.columns || process.stdout?.columns || FALLBACK_COLUMNS);
218
+ const viewportRows = () => Math.max(10, rows || stream?.rows || process.stdout?.rows || FALLBACK_ROWS);
219
+ const ensureSelection = (names) => { if (names.length && (!selectedWorker || !workers.has(selectedWorker))) selectedWorker = names[0]; };
220
+
221
+ function selectRelative(offset) {
222
+ const names = workerNames();
223
+ if (names.length === 0) return;
224
+ ensureSelection(names);
225
+ const idx = Math.max(0, names.indexOf(selectedWorker));
226
+ selectedWorker = names[(idx + offset + names.length) % names.length];
227
+ }
228
+
229
+ function triggerOpenSelected() {
230
+ if (typeof onOpenSelectedWorker !== "function" || !selectedWorker || !workers.has(selectedWorker)) return;
231
+ try {
232
+ const result = onOpenSelectedWorker(selectedWorker, workers.get(selectedWorker), new Map(workers));
233
+ if (result && typeof result.catch === "function") result.catch(() => {});
234
+ } catch {}
235
+ }
236
+
237
+ function triggerOpenAll() {
238
+ if (typeof onOpenAllWorkers !== "function") return;
239
+ try {
240
+ const result = onOpenAllWorkers(selectedWorker, workers.get(selectedWorker), new Map(workers));
241
+ if (result && typeof result.catch === "function") result.catch(() => {});
242
+ } catch {}
243
+ }
244
+
245
+ function handleInput(chunk) {
246
+ const key = String(chunk);
247
+ if (key === "\u0003") return;
248
+
249
+ if (helpVisible) {
250
+ helpVisible = false;
251
+ render();
252
+ return;
253
+ }
254
+
255
+ if (key === "j" || key === "\u001b[B") {
256
+ selectRelative(1);
257
+ render();
258
+ return;
259
+ }
260
+ if (key === "k" || key === "\u001b[A") {
261
+ selectRelative(-1);
262
+ render();
263
+ return;
264
+ }
265
+ if (key === "\r" || key === "\n") {
266
+ triggerOpenSelected();
267
+ return;
268
+ }
269
+ if (key === "\x1b[13;2u" || key === "\x1b[27;13;2~" || key === "\x1b\r" || key === "\x1b\n") {
270
+ triggerOpenAll();
271
+ return;
272
+ }
273
+ if (key === "l") {
274
+ const tabs = ["log", "detail", "files"];
275
+ focusTab = tabs[(tabs.indexOf(focusTab) + 1) % tabs.length];
276
+ render();
277
+ return;
278
+ }
279
+ if (key === "h" || key === "?") {
280
+ helpVisible = true;
281
+ render();
282
+ return;
283
+ }
284
+ if (key === "q") {
285
+ close();
286
+ return;
287
+ }
288
+ if (/^[1-9]$/.test(key)) {
289
+ const names = workerNames();
290
+ const target = names[Number.parseInt(key, 10) - 1];
291
+ if (target) {
292
+ selectedWorker = target;
293
+ render();
294
+ }
295
+ }
296
+ }
297
+
298
+ function attachInput() {
299
+ if (inputAttached) return;
300
+ if (!isTTY || !input?.isTTY || typeof input?.on !== "function") return;
301
+ inputAttached = true;
302
+ if (typeof input.setRawMode === "function") {
303
+ input.setRawMode(true);
304
+ rawModeEnabled = true;
305
+ }
306
+ if (typeof input.resume === "function") input.resume();
307
+ input.on("data", handleInput);
308
+ }
309
+
310
+ function buildRows() {
311
+ const names = workerNames();
312
+ ensureSelection(names);
313
+ const width = viewportColumns();
314
+ const height = viewportRows();
315
+ const header = buildHeader(width, names, workers, pipeline, startedAt);
316
+ const railOnly = !detailExpanded || names.length <= 1 || width < 100 || layout === "single";
317
+ if (railOnly) {
318
+ const sections = [header, ...buildWorkerRail(names, workers, selectedWorker, width)];
319
+ if (detailExpanded) sections.push(...buildDetail(selectedWorker, workers.get(selectedWorker), width, focusTab, helpVisible));
320
+ return fitHeight(sections, width, height);
321
+ }
322
+ const railWidth = Math.max(28, Math.floor(width * 0.32));
323
+ const detailWidth = width - railWidth - 1;
324
+ const bodyHeight = Math.max(6, height - header.length);
325
+ const rail = fitHeight(buildWorkerRail(names, workers, selectedWorker, railWidth), railWidth, bodyHeight);
326
+ const detail = fitHeight(buildDetail(selectedWorker, workers.get(selectedWorker), detailWidth, focusTab, helpVisible), detailWidth, bodyHeight);
327
+ return [
328
+ ...header,
329
+ ...Array.from({ length: bodyHeight }, (_, index) => `${rail[index]}${dim("│")}${detail[index]}`),
330
+ ];
331
+ }
332
+
333
+ function render() {
334
+ if (closed) return;
335
+ attachInput();
336
+ frameCount++;
337
+ const rowsOut = buildRows();
338
+ if (isTTY) {
339
+ const width = viewportColumns();
340
+ const padded = rowsOut.map((line) => padRight(String(line ?? ""), width) + clearToEnd);
341
+ write(cursorHome + padded.join("\n") + eraseBelow);
342
+ } else write(`${rowsOut.join("\n")}\n`);
343
+ }
344
+
345
+ function close() {
346
+ if (closed) return;
347
+ if (timer) clearInterval(timer);
348
+ if (inputAttached && typeof input?.off === "function") input.off("data", handleInput);
349
+ if (rawModeEnabled && typeof input?.setRawMode === "function") input.setRawMode(false);
350
+ if (inputAttached && typeof input?.pause === "function") input.pause();
351
+ if (isTTY) write(cursorShow + altScreenOff);
352
+ closed = true;
353
+ }
354
+
355
+ if (isTTY) write(altScreenOn + cursorHide + clearScreen + cursorHome);
356
+ if (refreshMs > 0) {
357
+ timer = setInterval(render, refreshMs);
358
+ if (timer.unref) timer.unref();
359
+ }
360
+
361
+ return {
362
+ updateWorker(name, state) { workers.set(name, normalizeWorkerState(workers.get(name), state)); ensureSelection(workerNames()); },
363
+ updatePipeline(state) { pipeline = { ...pipeline, ...state }; },
364
+ setStartTime(ms) { startedAt = ms; },
365
+ selectWorker(name) { if (workers.has(name)) selectedWorker = name; },
366
+ toggleDetail(force) { detailExpanded = typeof force === "boolean" ? force : !detailExpanded; },
367
+ render,
368
+ getWorkers() { return new Map(workers); },
369
+ getFrameCount() { return frameCount; },
370
+ getPipelineState() { return { ...pipeline }; },
371
+ getSelectedWorker() { return selectedWorker; },
372
+ isDetailExpanded() { return detailExpanded; },
373
+ getFocusTab() { return focusTab; },
374
+ setFocusTab(tab) { if (VALID_TABS.has(tab)) focusTab = tab; },
375
+ getLayout() { return layout; },
376
+ toggleHelp(force) { helpVisible = typeof force === "boolean" ? force : !helpVisible; },
377
+ isHelpVisible() { return helpVisible; },
378
+ close,
379
+ };
380
+ }