triflux 3.2.0-dev.1 → 3.2.0-dev.10
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/README.ko.md +26 -18
- package/README.md +26 -18
- package/bin/triflux.mjs +1614 -1084
- package/hooks/hooks.json +12 -0
- package/hooks/keyword-rules.json +354 -0
- package/hub/bridge.mjs +371 -193
- package/hub/hitl.mjs +45 -31
- package/hub/pipe.mjs +457 -0
- package/hub/router.mjs +422 -161
- package/hub/server.mjs +429 -344
- package/hub/store.mjs +388 -314
- package/hub/team/cli-team-common.mjs +348 -0
- package/hub/team/cli-team-control.mjs +393 -0
- package/hub/team/cli-team-start.mjs +516 -0
- package/hub/team/cli-team-status.mjs +269 -0
- package/hub/team/cli.mjs +99 -368
- package/hub/team/dashboard.mjs +165 -64
- package/hub/team/native-supervisor.mjs +300 -0
- package/hub/team/native.mjs +62 -0
- package/hub/team/nativeProxy.mjs +534 -0
- package/hub/team/orchestrator.mjs +99 -35
- package/hub/team/pane.mjs +138 -101
- package/hub/team/psmux.mjs +297 -0
- package/hub/team/session.mjs +608 -186
- package/hub/team/shared.mjs +13 -0
- package/hub/team/staleState.mjs +299 -0
- package/hub/tools.mjs +140 -53
- package/hub/workers/claude-worker.mjs +446 -0
- package/hub/workers/codex-mcp.mjs +414 -0
- package/hub/workers/factory.mjs +18 -0
- package/hub/workers/gemini-worker.mjs +349 -0
- package/hub/workers/interface.mjs +41 -0
- package/hud/hud-qos-status.mjs +1789 -1732
- package/package.json +6 -2
- package/scripts/__tests__/keyword-detector.test.mjs +234 -0
- package/scripts/hub-ensure.mjs +83 -0
- package/scripts/keyword-detector.mjs +272 -0
- package/scripts/keyword-rules-expander.mjs +521 -0
- package/scripts/lib/keyword-rules.mjs +168 -0
- package/scripts/psmux-steering-prototype.sh +368 -0
- package/scripts/run.cjs +62 -0
- package/scripts/setup.mjs +189 -7
- package/scripts/test-tfx-route-no-claude-native.mjs +49 -0
- package/scripts/tfx-route-worker.mjs +161 -0
- package/scripts/tfx-route.sh +943 -508
- package/skills/tfx-auto/SKILL.md +90 -564
- package/skills/tfx-auto-codex/SKILL.md +77 -0
- package/skills/tfx-codex/SKILL.md +1 -4
- package/skills/tfx-doctor/SKILL.md +1 -0
- package/skills/tfx-gemini/SKILL.md +1 -4
- package/skills/tfx-multi/SKILL.md +296 -0
- package/skills/tfx-setup/SKILL.md +1 -4
- package/skills/tfx-team/SKILL.md +0 -172
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function out(result) {
|
|
19
|
+
return `${result.stdout || ""}\n${result.stderr || ""}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test("gemini 모드에서는 no-claude-native 강제 치환이 적용되지 않는다", () => {
|
|
23
|
+
const result = runBash(
|
|
24
|
+
"TFX_CLI_MODE=gemini TFX_NO_CLAUDE_NATIVE=1 bash scripts/tfx-route.sh explore 'test-case'"
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
assert.equal(result.status, 0, out(result));
|
|
28
|
+
assert.match(out(result), /ROUTE_TYPE=claude-native/, out(result));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("auto 모드 + no-claude-native=1이면 explore가 codex로 치환된다", () => {
|
|
32
|
+
const result = runBash(
|
|
33
|
+
"TFX_CLI_MODE=auto TFX_NO_CLAUDE_NATIVE=1 CODEX_BIN=true bash scripts/tfx-route.sh explore 'test-case' minimal 5"
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
assert.equal(result.status, 0, out(result));
|
|
37
|
+
assert.match(out(result), /TFX_NO_CLAUDE_NATIVE=1: explore -> codex/, out(result));
|
|
38
|
+
assert.match(out(result), /type=codex|cli:\\s*codex/i, out(result));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("TFX_NO_CLAUDE_NATIVE는 0/1 값만 허용한다", () => {
|
|
42
|
+
const result = runBash(
|
|
43
|
+
"TFX_NO_CLAUDE_NATIVE=2 bash scripts/tfx-route.sh explore 'test-case'"
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
assert.notEqual(result.status, 0, out(result));
|
|
47
|
+
assert.match(out(result), /0 또는 1/, out(result));
|
|
48
|
+
});
|
|
49
|
+
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// tfx-route-worker.mjs — tfx-route.sh용 subprocess worker 러너
|
|
3
|
+
|
|
4
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
5
|
+
import { dirname, resolve } from 'node:path';
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const FACTORY_CANDIDATES = [
|
|
10
|
+
resolve(SCRIPT_DIR, '../hub/workers/factory.mjs'),
|
|
11
|
+
resolve(SCRIPT_DIR, './hub/workers/factory.mjs'),
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
// MCP transport 실패 시 tfx-route.sh가 exec fallback을 수행할 수 있도록
|
|
15
|
+
// CODEX_MCP_TRANSPORT_EXIT_CODE(70)으로 종료한다.
|
|
16
|
+
const MCP_TRANSPORT_EXIT_CODE = 70;
|
|
17
|
+
|
|
18
|
+
let createWorker = null;
|
|
19
|
+
|
|
20
|
+
for (const candidate of FACTORY_CANDIDATES) {
|
|
21
|
+
if (!existsSync(candidate)) continue;
|
|
22
|
+
try {
|
|
23
|
+
({ createWorker } = await import(pathToFileURL(candidate).href));
|
|
24
|
+
} catch (err) {
|
|
25
|
+
// 의존성 누락 (예: @modelcontextprotocol/sdk) → fallback 가능하도록 exit 70
|
|
26
|
+
if (err.code === 'ERR_MODULE_NOT_FOUND') {
|
|
27
|
+
process.stderr.write(`[tfx-route-worker] 모듈 로드 실패: ${err.message}\n`);
|
|
28
|
+
process.exit(MCP_TRANSPORT_EXIT_CODE);
|
|
29
|
+
}
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!createWorker) {
|
|
36
|
+
process.stderr.write('[tfx-route-worker] worker factory를 찾지 못했습니다.\n');
|
|
37
|
+
process.exit(MCP_TRANSPORT_EXIT_CODE);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseArgs(argv) {
|
|
41
|
+
const args = {
|
|
42
|
+
allowedMcpServerNames: [],
|
|
43
|
+
mcpConfig: [],
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
47
|
+
const token = argv[index];
|
|
48
|
+
const next = argv[index + 1];
|
|
49
|
+
|
|
50
|
+
switch (token) {
|
|
51
|
+
case '--type':
|
|
52
|
+
args.type = next;
|
|
53
|
+
index += 1;
|
|
54
|
+
break;
|
|
55
|
+
case '--command':
|
|
56
|
+
args.command = next;
|
|
57
|
+
index += 1;
|
|
58
|
+
break;
|
|
59
|
+
case '--command-args-json':
|
|
60
|
+
args.commandArgsJson = next;
|
|
61
|
+
index += 1;
|
|
62
|
+
break;
|
|
63
|
+
case '--model':
|
|
64
|
+
args.model = next;
|
|
65
|
+
index += 1;
|
|
66
|
+
break;
|
|
67
|
+
case '--timeout-ms':
|
|
68
|
+
args.timeoutMs = Number(next);
|
|
69
|
+
index += 1;
|
|
70
|
+
break;
|
|
71
|
+
case '--approval-mode':
|
|
72
|
+
args.approvalMode = next;
|
|
73
|
+
index += 1;
|
|
74
|
+
break;
|
|
75
|
+
case '--permission-mode':
|
|
76
|
+
args.permissionMode = next;
|
|
77
|
+
index += 1;
|
|
78
|
+
break;
|
|
79
|
+
case '--allow-dangerously-skip-permissions':
|
|
80
|
+
args.allowDangerouslySkipPermissions = true;
|
|
81
|
+
break;
|
|
82
|
+
case '--allowed-mcp-server-name':
|
|
83
|
+
args.allowedMcpServerNames.push(next);
|
|
84
|
+
index += 1;
|
|
85
|
+
break;
|
|
86
|
+
case '--mcp-config':
|
|
87
|
+
args.mcpConfig.push(next);
|
|
88
|
+
index += 1;
|
|
89
|
+
break;
|
|
90
|
+
case '--cwd':
|
|
91
|
+
args.cwd = next;
|
|
92
|
+
index += 1;
|
|
93
|
+
break;
|
|
94
|
+
default:
|
|
95
|
+
throw new Error(`Unknown argument: ${token}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!args.type) {
|
|
100
|
+
throw new Error('--type is required');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return args;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function parseJsonArray(raw, label) {
|
|
107
|
+
if (!raw) return [];
|
|
108
|
+
try {
|
|
109
|
+
const parsed = JSON.parse(raw);
|
|
110
|
+
if (!Array.isArray(parsed)) {
|
|
111
|
+
throw new Error(`${label} must be a JSON array`);
|
|
112
|
+
}
|
|
113
|
+
return parsed.map((item) => String(item));
|
|
114
|
+
} catch (error) {
|
|
115
|
+
throw new Error(`${label} parse failed: ${error.message}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function readPromptFromStdin() {
|
|
120
|
+
return readFileSync(0, 'utf8');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function resolveDefaultMcpConfig(cwd) {
|
|
124
|
+
const candidate = resolve(cwd, '.mcp.json');
|
|
125
|
+
return existsSync(candidate) ? [candidate] : [];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const args = parseArgs(process.argv.slice(2));
|
|
129
|
+
const prompt = readPromptFromStdin();
|
|
130
|
+
|
|
131
|
+
const worker = createWorker(args.type, {
|
|
132
|
+
command: args.command,
|
|
133
|
+
commandArgs: parseJsonArray(args.commandArgsJson, '--command-args-json'),
|
|
134
|
+
model: args.model,
|
|
135
|
+
timeoutMs: args.timeoutMs,
|
|
136
|
+
approvalMode: args.approvalMode,
|
|
137
|
+
permissionMode: args.permissionMode,
|
|
138
|
+
allowDangerouslySkipPermissions: args.allowDangerouslySkipPermissions,
|
|
139
|
+
allowedMcpServerNames: args.allowedMcpServerNames,
|
|
140
|
+
mcpConfig: args.type === 'claude' && args.mcpConfig.length === 0
|
|
141
|
+
? resolveDefaultMcpConfig(args.cwd || process.cwd())
|
|
142
|
+
: args.mcpConfig,
|
|
143
|
+
cwd: args.cwd || process.cwd(),
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const result = await worker.run(prompt);
|
|
148
|
+
if (result.response) {
|
|
149
|
+
process.stdout.write(result.response);
|
|
150
|
+
if (!result.response.endsWith('\n')) process.stdout.write('\n');
|
|
151
|
+
}
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (error.stderr) {
|
|
154
|
+
process.stderr.write(String(error.stderr));
|
|
155
|
+
if (!String(error.stderr).endsWith('\n')) process.stderr.write('\n');
|
|
156
|
+
}
|
|
157
|
+
process.stderr.write(`${error.message}\n`);
|
|
158
|
+
process.exitCode = error.code === 'ETIMEDOUT' ? 124 : 1;
|
|
159
|
+
} finally {
|
|
160
|
+
try { await worker.stop(); } catch {}
|
|
161
|
+
}
|