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.
- package/CLAUDE.md +212 -0
- package/hub/lib/bash-path.mjs +73 -0
- package/hub/team/dashboard-open.mjs +1 -68
- package/hub/team/native-supervisor.mjs +9 -2
- package/hub/team/psmux.mjs +5 -13
- package/hub/team/session.mjs +6 -26
- package/hub/team/swarm-hypervisor.mjs +205 -27
- package/hub/team/synapse-http.mjs +1 -0
- package/hub/team/tui-core.mjs +292 -0
- package/hub/team/tui-lite.mjs +20 -154
- package/hub/team/tui-synapse.mjs +213 -0
- package/hub/team/tui-widgets.mjs +262 -0
- package/hub/team/tui.mjs +159 -255
- package/hub/workers/delegator-mcp.mjs +2 -2
- package/package.json +21 -62
- package/references/hosts.json +46 -0
- package/scripts/__tests__/keyword-detector.test.mjs +4 -4
- package/scripts/cross-review-gate.mjs +13 -0
- package/scripts/remote-spawn.mjs +11 -46
- package/scripts/session-spawn-helper.mjs +8 -21
- package/scripts/test-tfx-route-no-claude-native.mjs +4 -2
- package/scripts/tfx-route.sh +13 -0
- package/skills/tfx-deep-interview/SKILL.md +6 -6
- package/skills/tfx-deep-interview/SKILL.md.tmpl +6 -6
- package/skills/tfx-index/SKILL.md +1 -1
- package/skills/tfx-index/SKILL.md.tmpl +1 -1
- package/skills/tfx-interview/SKILL.md +9 -9
- package/skills/tfx-interview/SKILL.md.tmpl +9 -9
- package/skills/tfx-plan/SKILL.md +1 -1
- package/skills/tfx-plan/SKILL.md.tmpl +1 -1
- package/skills/tfx-research/SKILL.md +1 -1
- package/skills/tfx-research/SKILL.md.tmpl +1 -1
- package/skills/tfx-workspace/async-tests/run-tests.sh +203 -0
- package/skills/tfx-workspace/evals/evals.json +79 -0
- package/skills/tfx-workspace/iteration-1/benchmark.json +524 -0
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/eval_metadata.json +11 -0
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/grading.json +25 -0
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/outputs/analysis.md +154 -0
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/grading.json +25 -0
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/outputs/analysis.md +126 -0
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/eval_metadata.json +11 -0
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/grading.json +25 -0
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/outputs/analysis.md +119 -0
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/grading.json +25 -0
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/outputs/analysis.md +115 -0
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/eval_metadata.json +10 -0
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/grading.json +20 -0
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/outputs/analysis.md +86 -0
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/grading.json +20 -0
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/outputs/analysis.md +81 -0
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-1/multi-team-creation/eval_metadata.json +12 -0
- package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/grading.json +30 -0
- package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/outputs/analysis.md +316 -0
- package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/grading.json +30 -0
- package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/outputs/analysis.md +352 -0
- package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-1/review.html +1325 -0
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/eval_metadata.json +12 -0
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/grading.json +30 -0
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/outputs/analysis.md +97 -0
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/grading.json +30 -0
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/outputs/analysis.md +94 -0
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/eval_metadata.json +12 -0
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/grading.json +30 -0
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/outputs/analysis.md +209 -0
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/grading.json +30 -0
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/outputs/analysis.md +193 -0
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-2/benchmark.json +144 -0
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/eval_metadata.json +13 -0
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/grading.json +35 -0
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/outputs/analysis.md +382 -0
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/grading.json +35 -0
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/outputs/analysis.md +333 -0
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/timing.json +5 -0
- package/skills/tfx-workspace/iteration-2/review.html +1325 -0
- package/skills/tfx-workspace/skill-snapshot/tfx-auto/SKILL.md +217 -0
- package/skills/tfx-workspace/skill-snapshot/tfx-auto-codex/SKILL.md +77 -0
- package/skills/tfx-workspace/skill-snapshot/tfx-codex/SKILL.md +65 -0
- package/skills/tfx-workspace/skill-snapshot/tfx-doctor/SKILL.md +94 -0
- package/skills/tfx-workspace/skill-snapshot/tfx-gemini/SKILL.md +82 -0
- package/skills/tfx-workspace/skill-snapshot/tfx-hub/SKILL.md +133 -0
- package/skills/tfx-workspace/skill-snapshot/tfx-multi/SKILL.md +426 -0
- package/skills/tfx-workspace/skill-snapshot/tfx-setup/SKILL.md +101 -0
- package/.claude-plugin/marketplace.json +0 -34
- package/.claude-plugin/plugin.json +0 -22
- package/config/mcp-registry.json +0 -29
- package/scripts/__tests__/release-governance.test.mjs +0 -148
- package/scripts/release/bump-version.mjs +0 -77
- package/scripts/release/check-sync.mjs +0 -51
- package/scripts/release/lib.mjs +0 -303
- package/scripts/release/prepare.mjs +0 -85
- package/scripts/release/publish.mjs +0 -87
- package/scripts/release/verify.mjs +0 -81
- package/scripts/release/version-manifest.json +0 -26
- package/tui/codex-profile.mjs +0 -457
- package/tui/core.mjs +0 -266
- package/tui/doctor.mjs +0 -375
- package/tui/gemini-profile.mjs +0 -299
- package/tui/monitor-data.mjs +0 -152
- package/tui/monitor.mjs +0 -339
- package/tui/setup.mjs +0 -598
package/hub/team/tui.mjs
CHANGED
|
@@ -29,19 +29,40 @@ import {
|
|
|
29
29
|
wcswidth,
|
|
30
30
|
} from "./ansi.mjs";
|
|
31
31
|
import { resolveAttachCommand } from "./session.mjs";
|
|
32
|
+
import {
|
|
33
|
+
clamp,
|
|
34
|
+
cliColor,
|
|
35
|
+
countStatuses,
|
|
36
|
+
FALLBACK_COLUMNS,
|
|
37
|
+
FALLBACK_ROWS,
|
|
38
|
+
formatTokens,
|
|
39
|
+
loadVersion,
|
|
40
|
+
normalizeWorkerState as coreNormalizeWorkerState,
|
|
41
|
+
runtimeStatus,
|
|
42
|
+
sanitizeFiles,
|
|
43
|
+
sanitizeFindings,
|
|
44
|
+
sanitizeOneLine,
|
|
45
|
+
sanitizeTextBlock,
|
|
46
|
+
statusColor,
|
|
47
|
+
stripCodeBlocks,
|
|
48
|
+
wrapLine,
|
|
49
|
+
wrapText as wrapTextAll,
|
|
50
|
+
} from "./tui-core.mjs";
|
|
51
|
+
import {
|
|
52
|
+
createPanelResizer,
|
|
53
|
+
createSearchState,
|
|
54
|
+
createTokenTracker,
|
|
55
|
+
createVimMotion,
|
|
56
|
+
} from "./tui-widgets.mjs";
|
|
57
|
+
import {
|
|
58
|
+
createMetricsCollector,
|
|
59
|
+
createSynapseEventStream,
|
|
60
|
+
renderMetricsTier1,
|
|
61
|
+
} from "./tui-synapse.mjs";
|
|
32
62
|
|
|
33
|
-
|
|
34
|
-
let VERSION = "7.x";
|
|
35
|
-
try {
|
|
36
|
-
const { createRequire } = await import("node:module");
|
|
37
|
-
const require = createRequire(import.meta.url);
|
|
38
|
-
VERSION = require("../../package.json").version;
|
|
39
|
-
} catch {
|
|
40
|
-
/* fallback */
|
|
41
|
-
}
|
|
63
|
+
const VERSION = await loadVersion();
|
|
42
64
|
|
|
43
|
-
|
|
44
|
-
const FALLBACK_ROWS = 30;
|
|
65
|
+
// FALLBACK_COLUMNS, FALLBACK_ROWS → tui-core.mjs에서 import
|
|
45
66
|
const MIN_CARD_WIDTH = 28;
|
|
46
67
|
const ATTACH_SESSION_NAME_PATTERN = /^[a-zA-Z0-9_.-]+$/u;
|
|
47
68
|
const DEFAULT_ATTACH_TAB_TTL_MS = 30_000;
|
|
@@ -308,96 +329,7 @@ function _autoColumnCount(totalCols, workerCount) {
|
|
|
308
329
|
return 1;
|
|
309
330
|
}
|
|
310
331
|
|
|
311
|
-
//
|
|
312
|
-
function clamp(value, min, max) {
|
|
313
|
-
return Math.min(max, Math.max(min, value));
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
function stripCodeBlocks(text) {
|
|
317
|
-
return (
|
|
318
|
-
String(text || "")
|
|
319
|
-
.replace(/\r/g, "")
|
|
320
|
-
// fenced code blocks
|
|
321
|
-
.replace(/```[\s\S]*?(?:```|$)/g, "\n")
|
|
322
|
-
.replace(/^\s*```.*$/gm, "")
|
|
323
|
-
// indented code blocks (4+ spaces or tab at line start)
|
|
324
|
-
.replace(/^(?: {4}|\t).+$/gm, "")
|
|
325
|
-
// shell prompts: PS C:\...>, >, $
|
|
326
|
-
.replace(/^(?:PS\s+\S[^\n]*?>|>\s+|\$\s+)[^\n]*/gm, "")
|
|
327
|
-
.trim()
|
|
328
|
-
);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function sanitizeTextBlock(text, rawMode = false) {
|
|
332
|
-
const normalized = rawMode
|
|
333
|
-
? String(text || "").replace(/\r/g, "")
|
|
334
|
-
: stripCodeBlocks(text);
|
|
335
|
-
return normalized
|
|
336
|
-
.split("\n")
|
|
337
|
-
.map((line) => line.trim())
|
|
338
|
-
.filter(Boolean)
|
|
339
|
-
.filter((line) => line !== "--- HANDOFF ---")
|
|
340
|
-
.join("\n")
|
|
341
|
-
.trim();
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function sanitizeOneLine(text, fallback = "") {
|
|
345
|
-
const normalized = sanitizeTextBlock(text).replace(/\s+/g, " ").trim();
|
|
346
|
-
return normalized || fallback;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
function sanitizeFiles(files) {
|
|
350
|
-
if (!files) return [];
|
|
351
|
-
const raw = Array.isArray(files) ? files : String(files).split(",");
|
|
352
|
-
return raw.map((e) => sanitizeOneLine(e)).filter(Boolean);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function sanitizeFindings(findings) {
|
|
356
|
-
if (!findings) return [];
|
|
357
|
-
const raw = Array.isArray(findings)
|
|
358
|
-
? findings
|
|
359
|
-
: sanitizeTextBlock(findings).split("\n");
|
|
360
|
-
return raw.map((e) => sanitizeOneLine(e)).filter(Boolean);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function normalizeTokens(tokens) {
|
|
364
|
-
if (tokens === null || tokens === undefined) return "";
|
|
365
|
-
if (typeof tokens === "number" && Number.isFinite(tokens)) return tokens;
|
|
366
|
-
const raw = sanitizeOneLine(tokens);
|
|
367
|
-
if (!raw) return "";
|
|
368
|
-
const match = raw.match(/(\d+(?:[.,]\d+)?\s*[kKmM]?)/);
|
|
369
|
-
return match ? match[1].replace(/\s+/g, "").toLowerCase() : raw;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
function formatTokens(tokens) {
|
|
373
|
-
if (tokens === null || tokens === undefined || tokens === "") return "n/a";
|
|
374
|
-
if (typeof tokens === "number" && Number.isFinite(tokens)) {
|
|
375
|
-
if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}m`;
|
|
376
|
-
if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(1)}k`;
|
|
377
|
-
return `${tokens}`;
|
|
378
|
-
}
|
|
379
|
-
return String(tokens);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// ── 색상 헬퍼 ─────────────────────────────────────────────────────────────
|
|
383
|
-
function cliColor(cli) {
|
|
384
|
-
if (cli === "gemini") return FG.gemini;
|
|
385
|
-
if (cli === "claude") return FG.claude;
|
|
386
|
-
if (cli === "codex") return FG.codex;
|
|
387
|
-
return FG.white;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function runtimeStatus(st) {
|
|
391
|
-
return st?.handoff?.status || st?.status || "pending";
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function statusColor(status) {
|
|
395
|
-
if (status === "ok" || status === "completed") return MOCHA.ok;
|
|
396
|
-
if (status === "partial") return MOCHA.partial;
|
|
397
|
-
if (status === "failed") return MOCHA.fail;
|
|
398
|
-
if (status === "running" || status === "in_progress") return MOCHA.executing;
|
|
399
|
-
return FG.muted;
|
|
400
|
-
}
|
|
332
|
+
// 텍스트/상태/색상 유틸은 tui-core.mjs에서 import (위 참조)
|
|
401
333
|
|
|
402
334
|
// ── MOCHA RGB (gradual fade 보간용) ──
|
|
403
335
|
const MOCHA_RGB = {
|
|
@@ -505,37 +437,7 @@ function dedupeRole(role, name, cli) {
|
|
|
505
437
|
return r;
|
|
506
438
|
}
|
|
507
439
|
|
|
508
|
-
//
|
|
509
|
-
function wrapLine(text, width) {
|
|
510
|
-
const limit = Math.max(8, width);
|
|
511
|
-
const source = String(text || "").trim();
|
|
512
|
-
if (!source) return [""];
|
|
513
|
-
const words = source.split(/\s+/);
|
|
514
|
-
const lines = [];
|
|
515
|
-
let current = "";
|
|
516
|
-
for (const word of words) {
|
|
517
|
-
const candidate = current ? `${current} ${word}` : word;
|
|
518
|
-
if (wcswidth(candidate) <= limit) {
|
|
519
|
-
current = candidate;
|
|
520
|
-
continue;
|
|
521
|
-
}
|
|
522
|
-
if (current) {
|
|
523
|
-
lines.push(current);
|
|
524
|
-
current = "";
|
|
525
|
-
}
|
|
526
|
-
if (wcswidth(word) <= limit) {
|
|
527
|
-
current = word;
|
|
528
|
-
continue;
|
|
529
|
-
}
|
|
530
|
-
let offset = 0;
|
|
531
|
-
while (offset < word.length) {
|
|
532
|
-
lines.push(word.slice(offset, offset + limit));
|
|
533
|
-
offset += limit;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
if (current) lines.push(current);
|
|
537
|
-
return lines.length > 0 ? lines : [source.slice(0, limit)];
|
|
538
|
-
}
|
|
440
|
+
// wrapLine, wrapTextAll → tui-core.mjs에서 import
|
|
539
441
|
|
|
540
442
|
function _wrapText(
|
|
541
443
|
text,
|
|
@@ -557,16 +459,6 @@ function _wrapText(
|
|
|
557
459
|
];
|
|
558
460
|
}
|
|
559
461
|
|
|
560
|
-
// 스크롤 없이 전체 줄 반환 (focus pane용)
|
|
561
|
-
function wrapTextAll(text, width, rawMode = false) {
|
|
562
|
-
const input = sanitizeTextBlock(text, rawMode);
|
|
563
|
-
if (!input) return [];
|
|
564
|
-
return input
|
|
565
|
-
.split("\n")
|
|
566
|
-
.flatMap((line) => wrapLine(line, width))
|
|
567
|
-
.filter(Boolean);
|
|
568
|
-
}
|
|
569
|
-
|
|
570
462
|
// ── virtual row buffer ────────────────────────────────────────────────────
|
|
571
463
|
class RowBuffer {
|
|
572
464
|
constructor() {
|
|
@@ -600,22 +492,7 @@ class RowBuffer {
|
|
|
600
492
|
}
|
|
601
493
|
}
|
|
602
494
|
|
|
603
|
-
//
|
|
604
|
-
function countStatuses(names, workers) {
|
|
605
|
-
let ok = 0,
|
|
606
|
-
partial = 0,
|
|
607
|
-
failed = 0,
|
|
608
|
-
running = 0;
|
|
609
|
-
for (const name of names) {
|
|
610
|
-
const st = workers.get(name);
|
|
611
|
-
const s = runtimeStatus(st);
|
|
612
|
-
if (s === "ok" || s === "completed") ok++;
|
|
613
|
-
else if (s === "partial") partial++;
|
|
614
|
-
else if (s === "failed") failed++;
|
|
615
|
-
else if (s === "running" || s === "in_progress") running++;
|
|
616
|
-
}
|
|
617
|
-
return { ok, partial, failed, running };
|
|
618
|
-
}
|
|
495
|
+
// countStatuses → tui-core.mjs에서 import
|
|
619
496
|
|
|
620
497
|
// ── Tier1: 상단 고정 1행 ─────────────────────────────────────────────────
|
|
621
498
|
function phaseColor(phase, time = Date.now()) {
|
|
@@ -646,7 +523,7 @@ function buildTier1(
|
|
|
646
523
|
width,
|
|
647
524
|
);
|
|
648
525
|
const keysHint = color(
|
|
649
|
-
"Tab:focus • j/k
|
|
526
|
+
"Tab:focus • j/k:nav • gg/G:jump • /:search • n/N:next • H/L:resize • f:follow • l:tab",
|
|
650
527
|
MOCHA.subtext,
|
|
651
528
|
);
|
|
652
529
|
const hintWidth = wcswidth(stripAnsi(keysHint));
|
|
@@ -1000,7 +877,7 @@ function buildSummaryBar(
|
|
|
1000
877
|
);
|
|
1001
878
|
const keysLine = truncate(
|
|
1002
879
|
color(
|
|
1003
|
-
"Tab:focus • j/k
|
|
880
|
+
"Tab:focus • j/k:nav • gg/G:jump • /:search • n/N:next • H/L:resize • f:follow • l:tab",
|
|
1004
881
|
MOCHA.subtext,
|
|
1005
882
|
),
|
|
1006
883
|
width - 4,
|
|
@@ -1067,87 +944,9 @@ function _joinColumns(blocks, gap = GRID_GAP) {
|
|
|
1067
944
|
|
|
1068
945
|
// ── normalizeWorkerState ──────────────────────────────────────────────────
|
|
1069
946
|
function normalizeWorkerState(existing, state) {
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
: {
|
|
1074
|
-
...(existing.handoff || {}),
|
|
1075
|
-
...(state.handoff || {}),
|
|
1076
|
-
verdict:
|
|
1077
|
-
state.handoff?.verdict !== undefined
|
|
1078
|
-
? sanitizeOneLine(state.handoff.verdict)
|
|
1079
|
-
: existing.handoff?.verdict,
|
|
1080
|
-
files_changed:
|
|
1081
|
-
state.handoff?.files_changed !== undefined
|
|
1082
|
-
? sanitizeFiles(state.handoff.files_changed)
|
|
1083
|
-
: existing.handoff?.files_changed,
|
|
1084
|
-
confidence:
|
|
1085
|
-
state.handoff?.confidence !== undefined
|
|
1086
|
-
? sanitizeOneLine(state.handoff.confidence)
|
|
1087
|
-
: existing.handoff?.confidence,
|
|
1088
|
-
status:
|
|
1089
|
-
state.handoff?.status !== undefined
|
|
1090
|
-
? sanitizeOneLine(state.handoff.status)
|
|
1091
|
-
: existing.handoff?.status,
|
|
1092
|
-
};
|
|
1093
|
-
|
|
1094
|
-
return {
|
|
1095
|
-
...existing,
|
|
1096
|
-
...state,
|
|
1097
|
-
cli:
|
|
1098
|
-
state.cli !== undefined
|
|
1099
|
-
? sanitizeOneLine(state.cli, existing.cli || "codex")
|
|
1100
|
-
: existing.cli || "codex",
|
|
1101
|
-
role:
|
|
1102
|
-
state.role !== undefined ? sanitizeOneLine(state.role) : existing.role,
|
|
1103
|
-
status:
|
|
1104
|
-
state.status !== undefined
|
|
1105
|
-
? sanitizeOneLine(state.status, existing.status || "pending")
|
|
1106
|
-
: existing.status || "pending",
|
|
1107
|
-
snapshot:
|
|
1108
|
-
state.snapshot !== undefined
|
|
1109
|
-
? sanitizeTextBlock(state.snapshot)
|
|
1110
|
-
: existing.snapshot,
|
|
1111
|
-
summary:
|
|
1112
|
-
state.summary !== undefined
|
|
1113
|
-
? sanitizeTextBlock(state.summary)
|
|
1114
|
-
: existing.summary,
|
|
1115
|
-
detail:
|
|
1116
|
-
state.detail !== undefined
|
|
1117
|
-
? sanitizeTextBlock(state.detail)
|
|
1118
|
-
: existing.detail,
|
|
1119
|
-
findings:
|
|
1120
|
-
state.findings !== undefined
|
|
1121
|
-
? sanitizeFindings(state.findings)
|
|
1122
|
-
: existing.findings,
|
|
1123
|
-
files_changed:
|
|
1124
|
-
state.files_changed !== undefined
|
|
1125
|
-
? sanitizeFiles(state.files_changed)
|
|
1126
|
-
: existing.files_changed,
|
|
1127
|
-
confidence:
|
|
1128
|
-
state.confidence !== undefined
|
|
1129
|
-
? sanitizeOneLine(state.confidence)
|
|
1130
|
-
: existing.confidence,
|
|
1131
|
-
tokens:
|
|
1132
|
-
state.tokens !== undefined
|
|
1133
|
-
? normalizeTokens(state.tokens)
|
|
1134
|
-
: existing.tokens,
|
|
1135
|
-
progress:
|
|
1136
|
-
state.progress !== undefined
|
|
1137
|
-
? clamp(Number(state.progress) || 0, 0, 1)
|
|
1138
|
-
: existing.progress,
|
|
1139
|
-
handoff: nextHandoff,
|
|
1140
|
-
_prevStatus:
|
|
1141
|
-
state.status !== undefined &&
|
|
1142
|
-
sanitizeOneLine(state.status) !== existing.status
|
|
1143
|
-
? existing.status
|
|
1144
|
-
: existing._prevStatus,
|
|
1145
|
-
_statusChangedAt:
|
|
1146
|
-
state.status !== undefined &&
|
|
1147
|
-
sanitizeOneLine(state.status) !== existing.status
|
|
1148
|
-
? Date.now()
|
|
1149
|
-
: existing._statusChangedAt || 0,
|
|
1150
|
-
};
|
|
947
|
+
return coreNormalizeWorkerState(existing || { cli: "codex", status: "pending" }, state, {
|
|
948
|
+
trackChanges: true,
|
|
949
|
+
});
|
|
1151
950
|
}
|
|
1152
951
|
|
|
1153
952
|
// ── createLogDashboard ────────────────────────────────────────────────────
|
|
@@ -1208,6 +1007,25 @@ export function createLogDashboard(opts = {}) {
|
|
|
1208
1007
|
let inputAttached = false;
|
|
1209
1008
|
let rawModeEnabled = false;
|
|
1210
1009
|
|
|
1010
|
+
// UX 위젯 (ISSUE-14)
|
|
1011
|
+
const tokenTracker = createTokenTracker();
|
|
1012
|
+
const searchState = createSearchState();
|
|
1013
|
+
const vimMotion = createVimMotion();
|
|
1014
|
+
const panelResizer = createPanelResizer({
|
|
1015
|
+
initialRatio: focus === "detail" ? 0.2 : 0.3,
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
// Synapse 실시간 관제 (Phase 3)
|
|
1019
|
+
const synapseMetrics = deps.synapseMetrics || createMetricsCollector();
|
|
1020
|
+
const synapseStream = deps.synapseStream || createSynapseEventStream({
|
|
1021
|
+
onEvent(event) {
|
|
1022
|
+
synapseMetrics.ingest(event);
|
|
1023
|
+
},
|
|
1024
|
+
fetchImpl: deps.synapseFetch,
|
|
1025
|
+
});
|
|
1026
|
+
// Synapse 자동 시작 (옵트인: deps.enableSynapse)
|
|
1027
|
+
if (deps.enableSynapse) synapseStream.start();
|
|
1028
|
+
|
|
1211
1029
|
// virtual row buffer (altScreen 전용)
|
|
1212
1030
|
const rowBuf = new RowBuffer();
|
|
1213
1031
|
|
|
@@ -1298,6 +1116,7 @@ export function createLogDashboard(opts = {}) {
|
|
|
1298
1116
|
function doClose() {
|
|
1299
1117
|
if (closed) return;
|
|
1300
1118
|
if (timer) clearInterval(timer);
|
|
1119
|
+
synapseStream.stop();
|
|
1301
1120
|
if (inputAttached && typeof input?.off === "function")
|
|
1302
1121
|
input.off("data", handleInput);
|
|
1303
1122
|
if (rawModeEnabled && typeof input?.setRawMode === "function")
|
|
@@ -1312,6 +1131,14 @@ export function createLogDashboard(opts = {}) {
|
|
|
1312
1131
|
const key = String(chunk);
|
|
1313
1132
|
if (key === "\u0003") { doClose(); return; }
|
|
1314
1133
|
|
|
1134
|
+
// 검색 모드 활성 중: 키를 검색 상태에 위임
|
|
1135
|
+
if (searchState.active) {
|
|
1136
|
+
if (searchState.handleKey(key)) {
|
|
1137
|
+
render();
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1315
1142
|
// Help overlay: 아무 키나 누르면 닫기
|
|
1316
1143
|
if (helpOverlay) {
|
|
1317
1144
|
helpOverlay = false;
|
|
@@ -1379,20 +1206,33 @@ export function createLogDashboard(opts = {}) {
|
|
|
1379
1206
|
}
|
|
1380
1207
|
}
|
|
1381
1208
|
|
|
1382
|
-
//
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1209
|
+
// vim 모션: gg(첫 워커/상단), G(마지막 워커/하단)
|
|
1210
|
+
const motion = vimMotion.handleKey(key);
|
|
1211
|
+
if (motion === "gg") {
|
|
1212
|
+
if (focus === "detail") {
|
|
1213
|
+
followTail = false;
|
|
1214
|
+
detailScrollOffset = 0;
|
|
1215
|
+
} else {
|
|
1216
|
+
const names = visibleWorkerNames();
|
|
1217
|
+
if (names.length > 0) setSelectedWorker(names[0]);
|
|
1218
|
+
}
|
|
1386
1219
|
render();
|
|
1387
1220
|
return;
|
|
1388
1221
|
}
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1222
|
+
if (motion === "G") {
|
|
1223
|
+
if (focus === "detail") {
|
|
1224
|
+
followTail = true;
|
|
1225
|
+
detailScrollOffset = 0;
|
|
1226
|
+
} else {
|
|
1227
|
+
const names = visibleWorkerNames();
|
|
1228
|
+
if (names.length > 0) setSelectedWorker(names[names.length - 1]);
|
|
1229
|
+
}
|
|
1393
1230
|
render();
|
|
1394
1231
|
return;
|
|
1395
1232
|
}
|
|
1233
|
+
// 단일 g는 vimMotion 대기 → return하지 않음 (이전 동작 유지)
|
|
1234
|
+
if (key === "g") return;
|
|
1235
|
+
|
|
1396
1236
|
// PgUp/PgDn: 페이지 단위 스크롤
|
|
1397
1237
|
const pageSize = Math.max(1, Math.floor(getViewportRows() / 2));
|
|
1398
1238
|
if (key === "\x1b[5~") {
|
|
@@ -1424,9 +1264,52 @@ export function createLogDashboard(opts = {}) {
|
|
|
1424
1264
|
render();
|
|
1425
1265
|
return;
|
|
1426
1266
|
}
|
|
1427
|
-
//
|
|
1267
|
+
// /: 검색 모드 활성화 (vim 패턴)
|
|
1268
|
+
if (key === "/") {
|
|
1269
|
+
searchState.activate();
|
|
1270
|
+
render();
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
// n: 검색 결과 다음 / 최근 변경 워커
|
|
1428
1274
|
if (key === "n") {
|
|
1429
|
-
|
|
1275
|
+
if (searchState.query) {
|
|
1276
|
+
const names = visibleWorkerNames();
|
|
1277
|
+
const idx = names.indexOf(selectedWorker);
|
|
1278
|
+
const match = searchState.findMatch(names, idx, 1);
|
|
1279
|
+
if (match >= 0) {
|
|
1280
|
+
setSelectedWorker(names[match]);
|
|
1281
|
+
render();
|
|
1282
|
+
} else {
|
|
1283
|
+
showFlash(`검색 결과 없음: ${searchState.query}`);
|
|
1284
|
+
}
|
|
1285
|
+
} else {
|
|
1286
|
+
selectMostRecentChangedWorker();
|
|
1287
|
+
}
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
// N: 검색 결과 이전
|
|
1291
|
+
if (key === "N") {
|
|
1292
|
+
if (searchState.query) {
|
|
1293
|
+
const names = visibleWorkerNames();
|
|
1294
|
+
const idx = names.indexOf(selectedWorker);
|
|
1295
|
+
const match = searchState.findMatch(names, idx, -1);
|
|
1296
|
+
if (match >= 0) {
|
|
1297
|
+
setSelectedWorker(names[match]);
|
|
1298
|
+
render();
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
// H: rail 축소 (패널 리사이즈)
|
|
1304
|
+
if (key === "H") {
|
|
1305
|
+
panelResizer.shrinkRail();
|
|
1306
|
+
render();
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
// L: rail 확대 (패널 리사이즈)
|
|
1310
|
+
if (key === "L") {
|
|
1311
|
+
panelResizer.expandRail();
|
|
1312
|
+
render();
|
|
1430
1313
|
return;
|
|
1431
1314
|
}
|
|
1432
1315
|
// h/?: 도움말 오버레이 토글
|
|
@@ -1576,6 +1459,16 @@ export function createLogDashboard(opts = {}) {
|
|
|
1576
1459
|
truncate(` ${color("▸", MOCHA.green)} ${flashMessage}`, totalCols),
|
|
1577
1460
|
);
|
|
1578
1461
|
}
|
|
1462
|
+
// Synapse 실시간 메트릭 (활성 시)
|
|
1463
|
+
if (synapseStream.isRunning) {
|
|
1464
|
+
const metricsSnap = synapseMetrics.snapshot();
|
|
1465
|
+
tier1.push(truncate(renderMetricsTier1(metricsSnap, totalCols), totalCols));
|
|
1466
|
+
}
|
|
1467
|
+
// 검색 프롬프트 (/ 모드)
|
|
1468
|
+
const searchPrompt = searchState.renderPrompt(totalCols);
|
|
1469
|
+
if (searchPrompt) {
|
|
1470
|
+
tier1.push(truncate(searchPrompt, totalCols));
|
|
1471
|
+
}
|
|
1579
1472
|
|
|
1580
1473
|
// 레이아웃 결정
|
|
1581
1474
|
let effectiveLayout = layoutHint;
|
|
@@ -1614,13 +1507,11 @@ export function createLogDashboard(opts = {}) {
|
|
|
1614
1507
|
return [...tier1, ...summaryBar, ...focusPane];
|
|
1615
1508
|
}
|
|
1616
1509
|
|
|
1617
|
-
// 좌우 분할: Left Rail
|
|
1618
|
-
// 목업: Tier2 Left Rail + Tier3 Focus 나란히 렌더링
|
|
1510
|
+
// 좌우 분할: Left Rail | Right Focus (H/L로 비율 조정 가능)
|
|
1619
1511
|
const GAP = 1; // rail과 focus 사이 구분선
|
|
1620
|
-
const railRatio = focus === "detail" ? 0.2 : 0.3;
|
|
1621
1512
|
const railWidth = Math.max(
|
|
1622
1513
|
MIN_CARD_WIDTH,
|
|
1623
|
-
Math.floor(totalCols *
|
|
1514
|
+
Math.floor(totalCols * panelResizer.ratio),
|
|
1624
1515
|
);
|
|
1625
1516
|
const focusWidth = totalCols - railWidth - GAP;
|
|
1626
1517
|
const bodyHeight = Math.max(6, totalRows - tier1.length - 1); // -1 for status bar
|
|
@@ -1777,6 +1668,8 @@ export function createLogDashboard(opts = {}) {
|
|
|
1777
1668
|
? existing._logSec
|
|
1778
1669
|
: (explicitElapsed ?? nowElapsedSec());
|
|
1779
1670
|
workers.set(paneName, merged);
|
|
1671
|
+
// 토큰 히스토리 추적 (스파크라인용)
|
|
1672
|
+
if (merged.tokens !== undefined) tokenTracker.record(paneName, merged.tokens);
|
|
1780
1673
|
ensureSelectedWorker(visibleWorkerNames());
|
|
1781
1674
|
// follow-tail: 새 데이터 → 자동 scroll 재계산
|
|
1782
1675
|
if (followTail) detailScrollOffset = 0;
|
|
@@ -1855,6 +1748,17 @@ export function createLogDashboard(opts = {}) {
|
|
|
1855
1748
|
return attachToSession(w);
|
|
1856
1749
|
},
|
|
1857
1750
|
|
|
1751
|
+
// Synapse 관제 API
|
|
1752
|
+
startSynapse() {
|
|
1753
|
+
synapseStream.start();
|
|
1754
|
+
},
|
|
1755
|
+
stopSynapse() {
|
|
1756
|
+
synapseStream.stop();
|
|
1757
|
+
},
|
|
1758
|
+
getSynapseMetrics() {
|
|
1759
|
+
return synapseMetrics.snapshot();
|
|
1760
|
+
},
|
|
1761
|
+
|
|
1858
1762
|
close() {
|
|
1859
1763
|
doClose();
|
|
1860
1764
|
},
|
|
@@ -11,7 +11,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
11
11
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
12
12
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
13
13
|
import * as z from "zod";
|
|
14
|
-
|
|
14
|
+
import { resolveBashExecutable } from "../lib/bash-path.mjs";
|
|
15
15
|
import { CodexMcpWorker } from "./codex-mcp.mjs";
|
|
16
16
|
import { GeminiWorker } from "./gemini-worker.mjs";
|
|
17
17
|
|
|
@@ -440,7 +440,7 @@ export class DelegatorMcpWorker {
|
|
|
440
440
|
options.bashCommand ||
|
|
441
441
|
this.env.TFX_DELEGATOR_BASH_COMMAND ||
|
|
442
442
|
this.env.BASH_BIN ||
|
|
443
|
-
|
|
443
|
+
resolveBashExecutable();
|
|
444
444
|
|
|
445
445
|
this.codexWorker = new CodexMcpWorker({
|
|
446
446
|
command:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "triflux",
|
|
3
|
-
"version": "10.9.
|
|
3
|
+
"version": "10.9.21",
|
|
4
4
|
"description": "CLI-first multi-model orchestrator for Claude Code — route tasks to Codex, Gemini, and Claude",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,75 +13,26 @@
|
|
|
13
13
|
"tfx-doctor-tui": "bin/tfx-doctor-tui.mjs",
|
|
14
14
|
"tfx-setup-tui": "bin/tfx-setup-tui.mjs"
|
|
15
15
|
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18.0.0"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@triflux/core": "10.0.1",
|
|
21
|
+
"@triflux/remote": "^10.0.0-alpha.1"
|
|
22
|
+
},
|
|
16
23
|
"files": [
|
|
17
24
|
"bin",
|
|
18
|
-
"tui",
|
|
19
|
-
"hub",
|
|
20
|
-
"config",
|
|
21
25
|
"skills",
|
|
22
|
-
"!skills/tfx-workspace",
|
|
23
|
-
"!**/failure-reports",
|
|
24
|
-
"scripts",
|
|
25
26
|
"hooks",
|
|
26
27
|
"hud",
|
|
28
|
+
"scripts",
|
|
29
|
+
"hub",
|
|
27
30
|
"mesh",
|
|
28
|
-
"
|
|
31
|
+
"references",
|
|
32
|
+
"CLAUDE.md",
|
|
29
33
|
"README.md",
|
|
30
|
-
"README.ko.md",
|
|
31
34
|
"LICENSE"
|
|
32
35
|
],
|
|
33
|
-
"workspaces": [
|
|
34
|
-
"packages/core",
|
|
35
|
-
"packages/remote",
|
|
36
|
-
"packages/triflux"
|
|
37
|
-
],
|
|
38
|
-
"scripts": {
|
|
39
|
-
"pack": "node scripts/pack.mjs all",
|
|
40
|
-
"pack:core": "node scripts/pack.mjs core",
|
|
41
|
-
"pack:remote": "node scripts/pack.mjs remote",
|
|
42
|
-
"setup": "node scripts/setup.mjs",
|
|
43
|
-
"preinstall": "node scripts/preinstall.mjs",
|
|
44
|
-
"postinstall": "node scripts/setup.mjs",
|
|
45
|
-
"lint": "biome check bin config hooks hub hud mesh scripts tests .claude-plugin .github package.json package-lock.json biome.json",
|
|
46
|
-
"lint:fix": "biome check --write bin config hooks hub hud mesh scripts tests .claude-plugin .github package.json package-lock.json biome.json",
|
|
47
|
-
"health": "npm test && npm run lint",
|
|
48
|
-
"test": "node scripts/test-lock.mjs --test --test-force-exit --test-concurrency=8 \"tests/**/*.test.mjs\" \"scripts/__tests__/**/*.test.mjs\"",
|
|
49
|
-
"test:unit": "node scripts/test-lock.mjs --test --test-force-exit --test-concurrency=8 tests/unit/**/*.test.mjs",
|
|
50
|
-
"test:integration": "node scripts/test-lock.mjs --test --test-force-exit --test-concurrency=8 tests/integration/**/*.test.mjs",
|
|
51
|
-
"test:route-smoke": "node scripts/test-lock.mjs --test scripts/test-tfx-route-no-claude-native.mjs",
|
|
52
|
-
"test:contract": "node scripts/test-lock.mjs --test --test-force-exit --test-concurrency=8 tests/contract/**/*.test.mjs",
|
|
53
|
-
"test:coverage": "node --experimental-test-coverage --test-coverage-lines=60 --test-coverage-functions=60 --test --test-force-exit --test-concurrency=8 \"tests/**/*.test.mjs\"",
|
|
54
|
-
"gen:skill-docs": "node scripts/gen-skill-docs.mjs",
|
|
55
|
-
"gen:skill-manifest": "node scripts/gen-skill-manifest.mjs",
|
|
56
|
-
"release:check-sync": "node scripts/release/check-sync.mjs",
|
|
57
|
-
"release:check-sync:fix": "node scripts/release/check-sync.mjs --fix",
|
|
58
|
-
"release:bump": "node scripts/release/bump-version.mjs",
|
|
59
|
-
"release:prepare": "node scripts/release/prepare.mjs",
|
|
60
|
-
"release:publish": "node scripts/release/publish.mjs",
|
|
61
|
-
"release:verify": "node scripts/release/verify.mjs"
|
|
62
|
-
},
|
|
63
|
-
"engines": {
|
|
64
|
-
"node": ">=18.0.0"
|
|
65
|
-
},
|
|
66
|
-
"repository": {
|
|
67
|
-
"type": "git",
|
|
68
|
-
"url": "git+https://github.com/tellang/triflux.git"
|
|
69
|
-
},
|
|
70
|
-
"homepage": "https://github.com/tellang/triflux#readme",
|
|
71
|
-
"author": "tellang",
|
|
72
|
-
"license": "MIT",
|
|
73
|
-
"dependencies": {
|
|
74
|
-
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
75
|
-
"better-sqlite3": "^12.6.2",
|
|
76
|
-
"pino": "^10.3.1",
|
|
77
|
-
"pino-pretty": "^13.1.3",
|
|
78
|
-
"systray2": "^2.1.4",
|
|
79
|
-
"zod": "^4.0.0"
|
|
80
|
-
},
|
|
81
|
-
"devDependencies": {
|
|
82
|
-
"@biomejs/biome": "^2.0.0",
|
|
83
|
-
"knip": "^6.3.0"
|
|
84
|
-
},
|
|
85
36
|
"keywords": [
|
|
86
37
|
"claude-code",
|
|
87
38
|
"plugin",
|
|
@@ -92,5 +43,13 @@
|
|
|
92
43
|
"multi-model",
|
|
93
44
|
"triflux",
|
|
94
45
|
"tfx"
|
|
95
|
-
]
|
|
46
|
+
],
|
|
47
|
+
"author": "tellang",
|
|
48
|
+
"license": "MIT",
|
|
49
|
+
"homepage": "https://github.com/tellang/triflux#readme",
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/tellang/triflux.git",
|
|
53
|
+
"directory": "packages/triflux"
|
|
54
|
+
}
|
|
96
55
|
}
|