triflux 3.2.0-dev.8 → 3.2.0-dev.9

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 (42) hide show
  1. package/bin/triflux.mjs +581 -340
  2. package/hooks/keyword-rules.json +16 -0
  3. package/hub/bridge.mjs +410 -318
  4. package/hub/hitl.mjs +45 -31
  5. package/hub/pipe.mjs +457 -0
  6. package/hub/router.mjs +422 -161
  7. package/hub/server.mjs +429 -424
  8. package/hub/store.mjs +388 -314
  9. package/hub/team/cli-team-common.mjs +348 -0
  10. package/hub/team/cli-team-control.mjs +393 -0
  11. package/hub/team/cli-team-start.mjs +512 -0
  12. package/hub/team/cli-team-status.mjs +269 -0
  13. package/hub/team/cli.mjs +59 -1459
  14. package/hub/team/dashboard.mjs +1 -9
  15. package/hub/team/native.mjs +12 -80
  16. package/hub/team/nativeProxy.mjs +121 -47
  17. package/hub/team/pane.mjs +66 -43
  18. package/hub/team/psmux.mjs +297 -0
  19. package/hub/team/session.mjs +354 -291
  20. package/hub/team/shared.mjs +13 -0
  21. package/hub/team/staleState.mjs +299 -0
  22. package/hub/tools.mjs +41 -52
  23. package/hub/workers/claude-worker.mjs +446 -0
  24. package/hub/workers/codex-mcp.mjs +414 -0
  25. package/hub/workers/factory.mjs +18 -0
  26. package/hub/workers/gemini-worker.mjs +349 -0
  27. package/hub/workers/interface.mjs +41 -0
  28. package/hud/hud-qos-status.mjs +4 -2
  29. package/package.json +4 -1
  30. package/scripts/keyword-detector.mjs +15 -0
  31. package/scripts/lib/keyword-rules.mjs +4 -1
  32. package/scripts/psmux-steering-prototype.sh +368 -0
  33. package/scripts/setup.mjs +128 -70
  34. package/scripts/tfx-route-worker.mjs +161 -0
  35. package/scripts/tfx-route.sh +415 -80
  36. package/skills/tfx-auto/SKILL.md +90 -564
  37. package/skills/tfx-auto-codex/SKILL.md +1 -3
  38. package/skills/tfx-codex/SKILL.md +1 -4
  39. package/skills/tfx-doctor/SKILL.md +1 -0
  40. package/skills/tfx-gemini/SKILL.md +1 -4
  41. package/skills/tfx-setup/SKILL.md +1 -4
  42. package/skills/tfx-team/SKILL.md +53 -62
@@ -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
+ }