triflux 10.3.2 → 10.3.3
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/plugin.json +22 -22
- package/LICENSE +21 -21
- package/README.ko.md +16 -0
- package/README.md +8 -0
- package/hooks/hook-registry.json +256 -256
- package/hub/adaptive-inject.mjs +1 -1
- package/hub/assign-callbacks.mjs +120 -120
- package/hub/delegator/index.mjs +14 -14
- package/hub/delegator/tool-definitions.mjs +35 -35
- package/hub/hitl.mjs +143 -143
- package/hub/router.mjs +791 -791
- package/hub/session-fingerprint.mjs +1 -1
- package/hub/team/cli/commands/attach.mjs +37 -37
- package/hub/team/cli/commands/debug.mjs +74 -74
- package/hub/team/cli/commands/focus.mjs +53 -53
- package/hub/team/cli/commands/list.mjs +24 -24
- package/hub/team/cli/commands/start/start-in-process.mjs +40 -40
- package/hub/team/cli/commands/start/start-mux.mjs +73 -73
- package/hub/team/cli/commands/start/start-wt.mjs +69 -69
- package/hub/team/cli/commands/tasks.mjs +13 -13
- package/hub/team/cli/render.mjs +30 -30
- package/hub/team/cli/services/attach-fallback.mjs +54 -54
- package/hub/team/cli/services/member-selector.mjs +30 -30
- package/hub/team/cli/services/native-control.mjs +116 -116
- package/hub/team/cli/services/task-model.mjs +30 -30
- package/hub/team/notify.mjs +1 -1
- package/hub/team/orchestrator.mjs +161 -161
- package/hub/team/session.mjs +611 -611
- package/hub/team/shared.mjs +13 -13
- package/hub/tray.mjs +368 -368
- package/hub/workers/codex-mcp.mjs +507 -507
- package/hub/workers/factory.mjs +21 -21
- package/mesh/index.mjs +63 -0
- package/mesh/mesh-budget.mjs +128 -0
- package/mesh/mesh-heartbeat.mjs +100 -0
- package/mesh/mesh-protocol.mjs +96 -0
- package/mesh/mesh-queue.mjs +165 -0
- package/mesh/mesh-registry.mjs +78 -0
- package/mesh/mesh-router.mjs +76 -0
- package/package.json +2 -1
- package/scripts/completions/tfx.bash +47 -47
- package/scripts/completions/tfx.fish +44 -44
- package/scripts/completions/tfx.zsh +83 -83
- package/scripts/hub-ensure.mjs +120 -120
- package/scripts/keyword-detector.mjs +272 -272
- package/scripts/keyword-rules-expander.mjs +521 -521
- package/scripts/lib/mcp-server-catalog.mjs +118 -118
- package/scripts/notion-read.mjs +553 -553
- package/scripts/test-tfx-route-no-claude-native.mjs +57 -57
- package/scripts/tfx-batch-stats.mjs +96 -96
- package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +0 -1
- package/skills/.omc/state/idle-notif-cooldown.json +0 -3
- package/skills/.omc/state/last-tool-error.json +0 -7
- package/skills/.omc/state/subagent-tracking.json +0 -7
- package/skills/tfx-remote-spawn/references/hosts.json +0 -16
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import assert from "node:assert/strict";
|
|
4
|
-
import { spawnSync } from "node:child_process";
|
|
5
|
-
import { dirname, resolve } from "node:path";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
-
|
|
8
|
-
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
const PROJECT_ROOT = resolve(SCRIPT_DIR, "..");
|
|
10
|
-
|
|
11
|
-
function runBash(command) {
|
|
12
|
-
return spawnSync("bash", ["-lc", command], {
|
|
13
|
-
cwd: PROJECT_ROOT,
|
|
14
|
-
encoding: "utf8",
|
|
15
|
-
env: {
|
|
16
|
-
...process.env,
|
|
17
|
-
TFX_TEAM_NAME: '',
|
|
18
|
-
TFX_TEAM_TASK_ID: '',
|
|
19
|
-
TFX_TEAM_AGENT_NAME: '',
|
|
20
|
-
TFX_TEAM_LEAD_NAME: '',
|
|
21
|
-
TFX_HUB_URL: '',
|
|
22
|
-
TMUX: '',
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function out(result) {
|
|
28
|
-
return `${result.stdout || ""}\n${result.stderr || ""}`;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
test("gemini 모드에서는 no-claude-native 강제 치환이 적용되지 않는다", () => {
|
|
32
|
-
const result = runBash(
|
|
33
|
-
"TFX_CLI_MODE=gemini TFX_NO_CLAUDE_NATIVE=1 bash scripts/tfx-route.sh explore 'test-case'"
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
assert.equal(result.status, 0, out(result));
|
|
37
|
-
assert.match(out(result), /ROUTE_TYPE=claude-native/, out(result));
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test("auto 모드 + no-claude-native=1이면 explore가 codex로 치환된다", () => {
|
|
41
|
-
const result = runBash(
|
|
42
|
-
"TFX_CLI_MODE=auto TFX_NO_CLAUDE_NATIVE=1 CODEX_BIN=true bash scripts/tfx-route.sh explore 'test-case' minimal 5"
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
assert.equal(result.status, 0, out(result));
|
|
46
|
-
assert.match(out(result), /TFX_NO_CLAUDE_NATIVE=1: explore -> codex/, out(result));
|
|
47
|
-
assert.match(out(result), /type=codex|cli:\\s*codex/i, out(result));
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("TFX_NO_CLAUDE_NATIVE는 0/1 값만 허용한다", () => {
|
|
51
|
-
const result = runBash(
|
|
52
|
-
"TFX_NO_CLAUDE_NATIVE=2 bash scripts/tfx-route.sh explore 'test-case'"
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
assert.notEqual(result.status, 0, out(result));
|
|
56
|
-
assert.match(out(result), /0 또는 1/, out(result));
|
|
57
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import assert from "node:assert/strict";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { dirname, resolve } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const PROJECT_ROOT = resolve(SCRIPT_DIR, "..");
|
|
10
|
+
|
|
11
|
+
function runBash(command) {
|
|
12
|
+
return spawnSync("bash", ["-lc", command], {
|
|
13
|
+
cwd: PROJECT_ROOT,
|
|
14
|
+
encoding: "utf8",
|
|
15
|
+
env: {
|
|
16
|
+
...process.env,
|
|
17
|
+
TFX_TEAM_NAME: '',
|
|
18
|
+
TFX_TEAM_TASK_ID: '',
|
|
19
|
+
TFX_TEAM_AGENT_NAME: '',
|
|
20
|
+
TFX_TEAM_LEAD_NAME: '',
|
|
21
|
+
TFX_HUB_URL: '',
|
|
22
|
+
TMUX: '',
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function out(result) {
|
|
28
|
+
return `${result.stdout || ""}\n${result.stderr || ""}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
test("gemini 모드에서는 no-claude-native 강제 치환이 적용되지 않는다", () => {
|
|
32
|
+
const result = runBash(
|
|
33
|
+
"TFX_CLI_MODE=gemini TFX_NO_CLAUDE_NATIVE=1 bash scripts/tfx-route.sh explore 'test-case'"
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
assert.equal(result.status, 0, out(result));
|
|
37
|
+
assert.match(out(result), /ROUTE_TYPE=claude-native/, out(result));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("auto 모드 + no-claude-native=1이면 explore가 codex로 치환된다", () => {
|
|
41
|
+
const result = runBash(
|
|
42
|
+
"TFX_CLI_MODE=auto TFX_NO_CLAUDE_NATIVE=1 CODEX_BIN=true bash scripts/tfx-route.sh explore 'test-case' minimal 5"
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
assert.equal(result.status, 0, out(result));
|
|
46
|
+
assert.match(out(result), /TFX_NO_CLAUDE_NATIVE=1: explore -> codex/, out(result));
|
|
47
|
+
assert.match(out(result), /type=codex|cli:\\s*codex/i, out(result));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("TFX_NO_CLAUDE_NATIVE는 0/1 값만 허용한다", () => {
|
|
51
|
+
const result = runBash(
|
|
52
|
+
"TFX_NO_CLAUDE_NATIVE=2 bash scripts/tfx-route.sh explore 'test-case'"
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
assert.notEqual(result.status, 0, out(result));
|
|
56
|
+
assert.match(out(result), /0 또는 1/, out(result));
|
|
57
|
+
});
|
|
@@ -1,96 +1,96 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// tfx-batch-stats.mjs v1.0 — batch-events.jsonl 소비자
|
|
3
|
-
//
|
|
4
|
-
// tfx-route-post.mjs가 기록한 AIMD 이벤트를 읽어서:
|
|
5
|
-
// 1. 에이전트별 성공/실패/타임아웃 통계 집계
|
|
6
|
-
// 2. AIMD(Additive Increase / Multiplicative Decrease) batch_size 계산
|
|
7
|
-
//
|
|
8
|
-
// 사용법:
|
|
9
|
-
// node tfx-batch-stats.mjs stats [--recent] 에이전트별 통계 (--recent: 30분)
|
|
10
|
-
// node tfx-batch-stats.mjs batch 현재 권장 batch_size
|
|
11
|
-
// node tfx-batch-stats.mjs agent <name> 특정 에이전트 통계
|
|
12
|
-
|
|
13
|
-
import { readFileSync } from "node:fs";
|
|
14
|
-
import { join } from "node:path";
|
|
15
|
-
import { homedir } from "node:os";
|
|
16
|
-
|
|
17
|
-
const CACHE_DIR = join(homedir(), ".claude", "cache");
|
|
18
|
-
const EVENTS_FILE = join(CACHE_DIR, "batch-events.jsonl");
|
|
19
|
-
|
|
20
|
-
// AIMD 파라미터
|
|
21
|
-
const AIMD_INITIAL = 3;
|
|
22
|
-
const AIMD_MIN = 1;
|
|
23
|
-
const AIMD_MAX = 10;
|
|
24
|
-
const AIMD_INC = 1; // 성공 시 +1
|
|
25
|
-
const AIMD_DEC = 0.5; // 실패 시 ×0.5
|
|
26
|
-
const WINDOW_MS = 30 * 60 * 1000; // 30분 윈도우
|
|
27
|
-
|
|
28
|
-
// ── 이벤트 읽기 ──
|
|
29
|
-
export function readBatchEvents(opts = {}) {
|
|
30
|
-
const { sinceMs = 0, agent = null } = opts;
|
|
31
|
-
try {
|
|
32
|
-
const lines = readFileSync(EVENTS_FILE, "utf-8").trim().split("\n").filter(Boolean);
|
|
33
|
-
return lines
|
|
34
|
-
.map((l) => { try { return JSON.parse(l); } catch { return null; } })
|
|
35
|
-
.filter((e) => e && (!sinceMs || e.ts >= sinceMs) && (!agent || e.agent === agent));
|
|
36
|
-
} catch {
|
|
37
|
-
return [];
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// ── 에이전트별 통계 ──
|
|
42
|
-
export function getAgentStats(opts = {}) {
|
|
43
|
-
const events = readBatchEvents(opts);
|
|
44
|
-
const stats = {};
|
|
45
|
-
|
|
46
|
-
for (const ev of events) {
|
|
47
|
-
if (!stats[ev.agent]) stats[ev.agent] = { success: 0, fail: 0, timeout: 0, total: 0 };
|
|
48
|
-
const s = stats[ev.agent];
|
|
49
|
-
if (ev.result === "success" || ev.result === "success_with_warnings") s.success++;
|
|
50
|
-
else if (ev.result === "timeout") s.timeout++;
|
|
51
|
-
else s.fail++;
|
|
52
|
-
s.total++;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
for (const s of Object.values(stats)) {
|
|
56
|
-
s.successRate = s.total > 0 ? +(s.success / s.total).toFixed(2) : 0;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return { total: events.length, agents: stats };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// ── AIMD batch_size 계산 ──
|
|
63
|
-
// 최근 윈도우 이벤트를 순회하며 성공 시 +1, 실패 시 ×0.5
|
|
64
|
-
export function getAimdBatchSize() {
|
|
65
|
-
const since = Date.now() - WINDOW_MS;
|
|
66
|
-
const events = readBatchEvents({ sinceMs: since });
|
|
67
|
-
if (events.length === 0) return AIMD_INITIAL;
|
|
68
|
-
|
|
69
|
-
let batch = AIMD_INITIAL;
|
|
70
|
-
for (const ev of events) {
|
|
71
|
-
if (ev.result === "success" || ev.result === "success_with_warnings") {
|
|
72
|
-
batch = Math.min(AIMD_MAX, batch + AIMD_INC);
|
|
73
|
-
} else {
|
|
74
|
-
batch = Math.max(AIMD_MIN, Math.floor(batch * AIMD_DEC));
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return batch;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ── CLI 진입점 ──
|
|
81
|
-
const scriptName = process.argv[1] || "";
|
|
82
|
-
if (scriptName.endsWith("tfx-batch-stats.mjs")) {
|
|
83
|
-
const cmd = process.argv[2] || "stats";
|
|
84
|
-
const recent = process.argv.includes("--recent");
|
|
85
|
-
const sinceMs = recent ? Date.now() - WINDOW_MS : 0;
|
|
86
|
-
|
|
87
|
-
if (cmd === "batch") {
|
|
88
|
-
console.log(JSON.stringify({ batchSize: getAimdBatchSize(), window: "30m" }));
|
|
89
|
-
} else if (cmd === "agent") {
|
|
90
|
-
const name = process.argv[3];
|
|
91
|
-
if (!name) { console.error("에이전트명 필수: node tfx-batch-stats.mjs agent executor"); process.exit(1); }
|
|
92
|
-
console.log(JSON.stringify(getAgentStats({ sinceMs, agent: name }), null, 2));
|
|
93
|
-
} else {
|
|
94
|
-
console.log(JSON.stringify(getAgentStats({ sinceMs }), null, 2));
|
|
95
|
-
}
|
|
96
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// tfx-batch-stats.mjs v1.0 — batch-events.jsonl 소비자
|
|
3
|
+
//
|
|
4
|
+
// tfx-route-post.mjs가 기록한 AIMD 이벤트를 읽어서:
|
|
5
|
+
// 1. 에이전트별 성공/실패/타임아웃 통계 집계
|
|
6
|
+
// 2. AIMD(Additive Increase / Multiplicative Decrease) batch_size 계산
|
|
7
|
+
//
|
|
8
|
+
// 사용법:
|
|
9
|
+
// node tfx-batch-stats.mjs stats [--recent] 에이전트별 통계 (--recent: 30분)
|
|
10
|
+
// node tfx-batch-stats.mjs batch 현재 권장 batch_size
|
|
11
|
+
// node tfx-batch-stats.mjs agent <name> 특정 에이전트 통계
|
|
12
|
+
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { homedir } from "node:os";
|
|
16
|
+
|
|
17
|
+
const CACHE_DIR = join(homedir(), ".claude", "cache");
|
|
18
|
+
const EVENTS_FILE = join(CACHE_DIR, "batch-events.jsonl");
|
|
19
|
+
|
|
20
|
+
// AIMD 파라미터
|
|
21
|
+
const AIMD_INITIAL = 3;
|
|
22
|
+
const AIMD_MIN = 1;
|
|
23
|
+
const AIMD_MAX = 10;
|
|
24
|
+
const AIMD_INC = 1; // 성공 시 +1
|
|
25
|
+
const AIMD_DEC = 0.5; // 실패 시 ×0.5
|
|
26
|
+
const WINDOW_MS = 30 * 60 * 1000; // 30분 윈도우
|
|
27
|
+
|
|
28
|
+
// ── 이벤트 읽기 ──
|
|
29
|
+
export function readBatchEvents(opts = {}) {
|
|
30
|
+
const { sinceMs = 0, agent = null } = opts;
|
|
31
|
+
try {
|
|
32
|
+
const lines = readFileSync(EVENTS_FILE, "utf-8").trim().split("\n").filter(Boolean);
|
|
33
|
+
return lines
|
|
34
|
+
.map((l) => { try { return JSON.parse(l); } catch { return null; } })
|
|
35
|
+
.filter((e) => e && (!sinceMs || e.ts >= sinceMs) && (!agent || e.agent === agent));
|
|
36
|
+
} catch {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── 에이전트별 통계 ──
|
|
42
|
+
export function getAgentStats(opts = {}) {
|
|
43
|
+
const events = readBatchEvents(opts);
|
|
44
|
+
const stats = {};
|
|
45
|
+
|
|
46
|
+
for (const ev of events) {
|
|
47
|
+
if (!stats[ev.agent]) stats[ev.agent] = { success: 0, fail: 0, timeout: 0, total: 0 };
|
|
48
|
+
const s = stats[ev.agent];
|
|
49
|
+
if (ev.result === "success" || ev.result === "success_with_warnings") s.success++;
|
|
50
|
+
else if (ev.result === "timeout") s.timeout++;
|
|
51
|
+
else s.fail++;
|
|
52
|
+
s.total++;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const s of Object.values(stats)) {
|
|
56
|
+
s.successRate = s.total > 0 ? +(s.success / s.total).toFixed(2) : 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { total: events.length, agents: stats };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── AIMD batch_size 계산 ──
|
|
63
|
+
// 최근 윈도우 이벤트를 순회하며 성공 시 +1, 실패 시 ×0.5
|
|
64
|
+
export function getAimdBatchSize() {
|
|
65
|
+
const since = Date.now() - WINDOW_MS;
|
|
66
|
+
const events = readBatchEvents({ sinceMs: since });
|
|
67
|
+
if (events.length === 0) return AIMD_INITIAL;
|
|
68
|
+
|
|
69
|
+
let batch = AIMD_INITIAL;
|
|
70
|
+
for (const ev of events) {
|
|
71
|
+
if (ev.result === "success" || ev.result === "success_with_warnings") {
|
|
72
|
+
batch = Math.min(AIMD_MAX, batch + AIMD_INC);
|
|
73
|
+
} else {
|
|
74
|
+
batch = Math.max(AIMD_MIN, Math.floor(batch * AIMD_DEC));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return batch;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── CLI 진입점 ──
|
|
81
|
+
const scriptName = process.argv[1] || "";
|
|
82
|
+
if (scriptName.endsWith("tfx-batch-stats.mjs")) {
|
|
83
|
+
const cmd = process.argv[2] || "stats";
|
|
84
|
+
const recent = process.argv.includes("--recent");
|
|
85
|
+
const sinceMs = recent ? Date.now() - WINDOW_MS : 0;
|
|
86
|
+
|
|
87
|
+
if (cmd === "batch") {
|
|
88
|
+
console.log(JSON.stringify({ batchSize: getAimdBatchSize(), window: "30m" }));
|
|
89
|
+
} else if (cmd === "agent") {
|
|
90
|
+
const name = process.argv[3];
|
|
91
|
+
if (!name) { console.error("에이전트명 필수: node tfx-batch-stats.mjs agent executor"); process.exit(1); }
|
|
92
|
+
console.log(JSON.stringify(getAgentStats({ sinceMs, agent: name }), null, 2));
|
|
93
|
+
} else {
|
|
94
|
+
console.log(JSON.stringify(getAgentStats({ sinceMs }), null, 2));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"t":0,"agent":"adb34b0","agent_type":"general-purpose","event":"agent_stop","success":true}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"tool_name": "Bash",
|
|
3
|
-
"tool_input_preview": "{\"command\":\"ls -la \\\"C:/Users/tellang/Desktop/Projects/triflux/skills/tfx-deslop/\\\" 2>/dev/null; echo \\\"===\\\"; ls -la \\\"C:/Users/tellang/Desktop/Projects/triflux/skills/tfx-codebase-search/\\\" 2>/dev/n...",
|
|
4
|
-
"error": "Exit code 2\n===",
|
|
5
|
-
"timestamp": "2026-03-29T03:40:35.913Z",
|
|
6
|
-
"retry_count": 1
|
|
7
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"hosts": {
|
|
3
|
-
"ultra4": {
|
|
4
|
-
"description": "Windows 데스크탑 (SSAFY)",
|
|
5
|
-
"aliases": ["울트라", "데스크탑"],
|
|
6
|
-
"default_dir": "~/Desktop/Projects"
|
|
7
|
-
},
|
|
8
|
-
"m2": {
|
|
9
|
-
"description": "MacBook Pro",
|
|
10
|
-
"aliases": ["맥북", "맥"],
|
|
11
|
-
"default_dir": "~/projects"
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
"default_host": "ultra4",
|
|
15
|
-
"triggers": ["원격에서", "다른 머신에서", "다른 컴퓨터에서"]
|
|
16
|
-
}
|