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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.ko.md +145 -145
- package/README.md +145 -145
- package/hub/pipeline/index.mjs +318 -318
- package/hub/schema.sql +146 -146
- package/hub/team/agent-map.json +2 -1
- package/hub/team/backend.mjs +3 -3
- package/hub/team/cli/commands/kill.mjs +37 -37
- package/hub/team/cli/commands/start/parse-args.mjs +4 -2
- package/hub/team/cli/commands/stop.mjs +31 -31
- package/hub/team/cli/commands/task.mjs +30 -30
- package/hub/team/cli/services/hub-client.mjs +208 -208
- package/hub/team/cli/services/native-control.mjs +4 -1
- package/hub/team/cli/services/runtime-mode.mjs +62 -62
- package/hub/team/cli/services/state-store.mjs +48 -48
- package/hub/team/codex-compat.mjs +78 -0
- package/hub/team/dashboard.mjs +274 -274
- package/hub/team/native.mjs +649 -649
- package/hub/team/pane.mjs +154 -150
- package/hub/team/psmux.mjs +1041 -1023
- package/hub/team/tui-viewer.mjs +2 -2
- package/hub/team/tui.mjs +12 -1
- package/hub/tools.mjs +554 -554
- package/hud/constants.mjs +3 -0
- package/package.json +1 -1
- package/scripts/claude-logged.ps1 +54 -0
- package/scripts/headless-guard.mjs +94 -7
- package/scripts/lib/mcp-filter.mjs +720 -720
- package/scripts/preflight-cache.mjs +137 -137
- package/scripts/remote-spawn.mjs +222 -0
- package/scripts/setup.mjs +84 -1
- package/scripts/tfx-gate-activate.mjs +89 -0
- package/scripts/tfx-route-post.mjs +17 -13
- package/scripts/tfx-route.sh +118 -46
- package/scripts/token-snapshot.mjs +575 -575
- package/skills/remote-spawn/SKILL.md +63 -0
- package/skills/tfx-auto/SKILL.md +1 -1
- 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
|
@@ -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:
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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|실행)/,
|