triflux 7.3.2 → 7.5.0

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 (38) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.ko.md +145 -145
  3. package/README.md +145 -145
  4. package/hub/pipeline/index.mjs +318 -318
  5. package/hub/schema.sql +146 -146
  6. package/hub/team/agent-map.json +2 -1
  7. package/hub/team/backend.mjs +3 -3
  8. package/hub/team/cli/commands/kill.mjs +37 -37
  9. package/hub/team/cli/commands/start/parse-args.mjs +4 -2
  10. package/hub/team/cli/commands/stop.mjs +31 -31
  11. package/hub/team/cli/commands/task.mjs +30 -30
  12. package/hub/team/cli/services/hub-client.mjs +208 -208
  13. package/hub/team/cli/services/native-control.mjs +4 -1
  14. package/hub/team/cli/services/runtime-mode.mjs +62 -62
  15. package/hub/team/cli/services/state-store.mjs +48 -48
  16. package/hub/team/codex-compat.mjs +78 -0
  17. package/hub/team/dashboard.mjs +274 -274
  18. package/hub/team/native.mjs +649 -649
  19. package/hub/team/pane.mjs +154 -150
  20. package/hub/team/psmux.mjs +1041 -1023
  21. package/hub/team/tui-viewer.mjs +2 -2
  22. package/hub/team/tui.mjs +12 -1
  23. package/hub/tools.mjs +554 -554
  24. package/hud/constants.mjs +3 -0
  25. package/package.json +1 -1
  26. package/scripts/claude-logged.ps1 +54 -0
  27. package/scripts/headless-guard.mjs +94 -7
  28. package/scripts/lib/mcp-filter.mjs +720 -720
  29. package/scripts/preflight-cache.mjs +137 -137
  30. package/scripts/remote-spawn.mjs +222 -0
  31. package/scripts/setup.mjs +84 -1
  32. package/scripts/tfx-gate-activate.mjs +89 -0
  33. package/scripts/tfx-route-post.mjs +17 -13
  34. package/scripts/tfx-route.sh +118 -46
  35. package/scripts/token-snapshot.mjs +575 -575
  36. package/skills/remote-spawn/SKILL.md +63 -0
  37. package/skills/tfx-auto/SKILL.md +1 -1
  38. package/skills/tfx-multi/SKILL.md +1 -1
package/hud/constants.mjs CHANGED
@@ -20,6 +20,9 @@ export const OMC_PLUGIN_USAGE_CACHE_PATH = join(homedir(), ".claude", "plugins",
20
20
  export const CLAUDE_USAGE_STALE_MS_SOLO = 5 * 60 * 1000; // OMC 없을 때: 5분 캐시
21
21
  export const CLAUDE_USAGE_STALE_MS_WITH_OMC = 15 * 60 * 1000; // OMC 있을 때: 15분 (OMC가 30초마다 갱신)
22
22
  export const CLAUDE_USAGE_429_BACKOFF_MS = 10 * 60 * 1000; // 429 에러 시 10분 backoff
23
+ export const GEMINI_429_BASE_DELAY_MS = 2000;
24
+ export const GEMINI_429_MAX_RETRIES = 3;
25
+ export const GEMINI_429_COOLDOWN_MS = 30000;
23
26
  export const CLAUDE_USAGE_ERROR_BACKOFF_MS = 3 * 60 * 1000; // 기타 에러 시 3분 backoff
24
27
  export const CLAUDE_API_TIMEOUT_MS = 10_000;
25
28
  export const FIVE_HOUR_MS = 5 * 60 * 60 * 1000;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "triflux",
3
- "version": "7.3.2",
3
+ "version": "7.5.0",
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": {
@@ -0,0 +1,54 @@
1
+ param(
2
+ [Parameter(ValueFromRemainingArguments=$true)]
3
+ [string[]]$ClaudeArgs
4
+ )
5
+
6
+ $LogDir = "$HOME\Desktop\Projects\triflux\logs"
7
+ $LogFile = "$LogDir\claude-sessions.log"
8
+
9
+ if (-not (Test-Path $LogDir)) {
10
+ New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
11
+ }
12
+
13
+ function Write-Log {
14
+ param([string]$Level, [string]$Message)
15
+ $ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
16
+ $line = "[$ts] [$Level] $Message"
17
+ Write-Host $line
18
+ Add-Content -Path $LogFile -Value $line -Encoding UTF8
19
+ }
20
+
21
+ $SessionId = "{0}-{1}" -f (Get-Date -Format "yyyyMMddHHmmss"), $PID
22
+
23
+ Write-Log "INFO" "=== Claude Code 세션 시작 [ID: $SessionId] | 인자: $($ClaudeArgs -join " ") ==="
24
+
25
+ $StartTime = Get-Date
26
+
27
+ try {
28
+ if ($ClaudeArgs -and $ClaudeArgs.Count -gt 0) {
29
+ & claude @ClaudeArgs
30
+ } else {
31
+ & claude
32
+ }
33
+ $ExitCode = $LASTEXITCODE
34
+ } catch {
35
+ $ExitCode = -1
36
+ Write-Log "ERROR" "Claude 실행 실패: $_"
37
+ }
38
+
39
+ $EndTime = Get-Date
40
+ $Elapsed = [math]::Round(($EndTime - $StartTime).TotalSeconds, 1)
41
+
42
+ $Reason = switch ($ExitCode) {
43
+ 0 { "정상 종료" }
44
+ 1 { "일반 오류" }
45
+ -1 { "실행 실패 (명령 없음)" }
46
+ default { "비정상 종료" }
47
+ }
48
+
49
+ if ($ExitCode -eq -1073741510) { $Reason = "사용자 인터럽트 (Ctrl+C)" }
50
+
51
+ Write-Log "INFO" "세션 종료 | exit=$ExitCode | 실행시간=${Elapsed}s | 원인=$Reason"
52
+ Write-Log "INFO" "=== Claude Code 세션 종료 [ID: $SessionId] ==="
53
+
54
+ exit $ExitCode
@@ -8,16 +8,23 @@
8
8
  * v2: 마커 파일 의존 제거. psmux 설치 여부만으로 판단.
9
9
  * Opus가 SKILL.md를 무시해도 auto-route가 작동한다.
10
10
  *
11
+ * v3: A(gate) + B(nudge) — OMC 패턴 도입
12
+ * A: tfx-multi 활성 시 headless dispatch 전까지 Agent 작업 위임 차단
13
+ * B: dispatch 후 네이티브 드리프트 감지 시 nudge
14
+ * 상태: $TMPDIR/tfx-multi-state.json (tfx-multi-activate.mjs가 생성)
15
+ *
11
16
  * 동작:
12
17
  * - psmux 설치 + Bash(tfx-route.sh) → updatedInput: tfx multi --headless --assign
13
18
  * - psmux 설치 + Bash(codex exec / gemini -p) → deny
14
19
  * - psmux 설치 + Agent(codex/gemini CLI 래핑) → deny
15
20
  * - psmux 미설치 → 전부 통과
21
+ * - tfx-multi 활성 + Agent(work) before dispatch → deny (A: gate)
22
+ * - tfx-multi 활성 + Agent(work) after dispatch → nudge (B: nudge)
16
23
  *
17
24
  * 성능: psmux 감지 결과를 5분간 캐시 ($TMPDIR/tfx-psmux-check.json)
18
25
  */
19
26
 
20
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
27
+ import { existsSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
21
28
  import { execFileSync } from "node:child_process";
22
29
  import { tmpdir } from "node:os";
23
30
  import { join } from "node:path";
@@ -25,6 +32,42 @@ import { join } from "node:path";
25
32
  const CACHE_FILE = join(tmpdir(), "tfx-psmux-check.json");
26
33
  const CACHE_TTL_MS = 5 * 60 * 1000; // 5분
27
34
 
35
+ // ── tfx-multi 상태 관리 (A+B) ──
36
+ const MULTI_STATE_FILE = join(tmpdir(), "tfx-multi-state.json");
37
+ const MULTI_EXPIRE_MS = 30 * 60 * 1000; // 30분 자동 만료
38
+ const GATE_THRESHOLD = 2; // A: dispatch 전 허용할 Agent 호출 수
39
+ const NUDGE_THRESHOLD = 4; // B: dispatch 후 nudge 트리거 횟수
40
+
41
+ function readMultiState() {
42
+ try {
43
+ if (!existsSync(MULTI_STATE_FILE)) return null;
44
+ const state = JSON.parse(readFileSync(MULTI_STATE_FILE, "utf8"));
45
+ if (!state.active) return null;
46
+ // 자동 만료
47
+ if (Date.now() - state.activatedAt > MULTI_EXPIRE_MS) {
48
+ try { unlinkSync(MULTI_STATE_FILE); } catch { /* ignore */ }
49
+ return null;
50
+ }
51
+ return state;
52
+ } catch { return null; }
53
+ }
54
+
55
+ function writeMultiState(state) {
56
+ try {
57
+ writeFileSync(MULTI_STATE_FILE, JSON.stringify(state));
58
+ } catch { /* ignore */ }
59
+ }
60
+
61
+ function nudge(message) {
62
+ process.stdout.write(JSON.stringify({
63
+ hookSpecificOutput: {
64
+ hookEventName: "PreToolUse",
65
+ additionalContext: message,
66
+ },
67
+ }));
68
+ process.exit(0);
69
+ }
70
+
28
71
  function isPsmuxInstalled() {
29
72
  // 캐시 확인
30
73
  try {
@@ -135,8 +178,14 @@ async function main() {
135
178
  if (toolName === "Bash") {
136
179
  const cmd = toolInput.command || "";
137
180
 
138
- // headless 명령은 통과
181
+ // headless 명령은 통과 + dispatch 감지 (A: gate 해제)
139
182
  if (cmd.includes("tfx multi") || cmd.includes("triflux.mjs multi")) {
183
+ const multiState = readMultiState();
184
+ if (multiState && cmd.includes("--assign")) {
185
+ multiState.dispatched = true;
186
+ multiState.nativeWorkCallsSinceDispatch = 0;
187
+ writeMultiState(multiState);
188
+ }
140
189
  process.exit(0);
141
190
  }
142
191
 
@@ -197,15 +246,53 @@ async function main() {
197
246
  }
198
247
  }
199
248
 
200
- // ── Agent: CLI 워커 래핑 deny (Claude native는 통과) ──
249
+ // ── Agent: A(gate) + B(nudge) + CLI 래핑 deny ──
201
250
  if (toolName === "Agent") {
202
251
  const subType = (toolInput.subagent_type || "").toLowerCase();
203
- // Claude native subagent types → 무조건 통과
204
252
  const NATIVE_TYPES = new Set(["explore", "plan", "general-purpose", ""]);
205
- if (NATIVE_TYPES.has(subType)) process.exit(0);
206
- // oh-my-claudecode 계열도 통과
207
- if (subType.startsWith("oh-my-claudecode:")) process.exit(0);
253
+ const isNative = NATIVE_TYPES.has(subType) || subType.startsWith("oh-my-claudecode:");
254
+
255
+ // ── A+B: tfx-multi 상태 기반 처리 ──
256
+ const multiState = readMultiState();
257
+ if (multiState && multiState.active && isNative) {
258
+ if (!multiState.dispatched) {
259
+ // ── A: gate — dispatch 전, Agent 작업 위임 제한 ──
260
+ multiState.nativeWorkCalls = (multiState.nativeWorkCalls || 0) + 1;
261
+ writeMultiState(multiState);
262
+
263
+ if (multiState.nativeWorkCalls > GATE_THRESHOLD) {
264
+ deny(
265
+ `[headless-guard] tfx-multi gate: Agent(${subType || "default"}) 호출 ${multiState.nativeWorkCalls}회 — headless에 먼저 dispatch하세요.\n` +
266
+ 'Bash("tfx multi --teammate-mode headless --auto-attach --dashboard --assign \'codex:프롬프트:역할\' --timeout 600")',
267
+ );
268
+ }
269
+ // 허용 범위 내 → 경고 + 통과
270
+ nudge(
271
+ `[headless-guard] tfx-multi 활성 (${multiState.nativeWorkCalls}/${GATE_THRESHOLD}). ` +
272
+ "headless dispatch 후 작업을 시작하세요.",
273
+ );
274
+ } else {
275
+ // ── B: nudge — dispatch 후, 네이티브 드리프트 감지 ──
276
+ multiState.nativeWorkCallsSinceDispatch = (multiState.nativeWorkCallsSinceDispatch || 0) + 1;
277
+ writeMultiState(multiState);
278
+
279
+ if (multiState.nativeWorkCallsSinceDispatch >= NUDGE_THRESHOLD) {
280
+ multiState.nativeWorkCallsSinceDispatch = 0;
281
+ writeMultiState(multiState);
282
+ nudge(
283
+ "[headless-guard] nudge: headless 워커가 실행 중입니다. " +
284
+ "결과를 기다리거나 추가 --assign으로 위임하세요.",
285
+ );
286
+ }
287
+ }
288
+ // native → 통과 (gate deny 안 걸린 경우)
289
+ process.exit(0);
290
+ }
291
+
292
+ // native → 통과 (tfx-multi 비활성)
293
+ if (isNative) process.exit(0);
208
294
 
295
+ // ── CLI 래핑 체크 (기존) ──
209
296
  const combined = `${(toolInput.prompt || "").toLowerCase()} ${(toolInput.description || "").toLowerCase()}`;
210
297
  const cliPatterns = [
211
298
  /codex\s+(exec|run|실행)/,