@triflux/remote 10.0.1 → 10.25.1
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/hub/pipe.mjs +239 -121
- package/hub/public/dashboard.html +68 -7
- package/hub/server.mjs +2181 -701
- package/hub/store-adapter.mjs +101 -37
- package/hub/store.mjs +356 -153
- package/hub/team/agent-map.json +26 -7
- package/hub/team/ansi.mjs +245 -146
- package/hub/team/backend.mjs +84 -17
- package/hub/team/build-worker-prompt.mjs +42 -0
- package/hub/team/check-mcp-hub.mjs +49 -0
- package/hub/team/claude-daemon-control.mjs +219 -0
- package/hub/team/claude-native-bridge.mjs +784 -0
- package/hub/team/claude-session-projection.mjs +65 -0
- package/hub/team/cli/commands/attach.mjs +57 -37
- package/hub/team/cli/commands/control.mjs +29 -8
- package/hub/team/cli/commands/debug.mjs +95 -74
- package/hub/team/cli/commands/focus.mjs +80 -53
- package/hub/team/cli/commands/interrupt.mjs +18 -6
- package/hub/team/cli/commands/kill.mjs +16 -5
- package/hub/team/cli/commands/list.mjs +31 -24
- package/hub/team/cli/commands/send.mjs +19 -6
- package/hub/team/cli/commands/start/index.mjs +154 -33
- package/hub/team/cli/commands/start/parse-args.mjs +89 -12
- package/hub/team/cli/commands/start/start-headless.mjs +127 -37
- package/hub/team/cli/commands/start/start-in-process.mjs +50 -40
- package/hub/team/cli/commands/start/start-mux.mjs +122 -73
- package/hub/team/cli/commands/start/start-wt.mjs +86 -69
- package/hub/team/cli/commands/status.mjs +43 -14
- package/hub/team/cli/commands/stop.mjs +11 -4
- package/hub/team/cli/commands/task.mjs +8 -3
- package/hub/team/cli/commands/tasks.mjs +13 -13
- package/hub/team/cli/index.mjs +2 -2
- package/hub/team/cli/manifest.mjs +38 -8
- package/hub/team/cli/render.mjs +52 -30
- package/hub/team/cli/services/attach-fallback.mjs +74 -54
- package/hub/team/cli/services/hub-client.mjs +45 -17
- package/hub/team/cli/services/member-selector.mjs +37 -30
- package/hub/team/cli/services/native-control.mjs +144 -116
- package/hub/team/cli/services/runtime-mode.mjs +55 -1
- package/hub/team/cli/services/state-store.mjs +66 -9
- package/hub/team/cli/services/task-model.mjs +40 -30
- package/hub/team/codex-review.mjs +521 -0
- package/hub/team/conductor-mesh-bridge.mjs +25 -24
- package/hub/team/conductor-registry.mjs +121 -0
- package/hub/team/conductor.mjs +1379 -671
- package/hub/team/consensus-meta.mjs +176 -0
- package/hub/team/dashboard-anchor.mjs +4 -5
- package/hub/team/dashboard-layout.mjs +3 -1
- package/hub/team/dashboard-open.mjs +21 -119
- package/hub/team/dashboard.mjs +76 -28
- package/hub/team/event-log.mjs +18 -10
- package/hub/team/execution-mode.mjs +241 -0
- package/hub/team/extract-completion-payload.mjs +110 -0
- package/hub/team/git-preflight.mjs +286 -0
- package/hub/team/handoff.mjs +49 -18
- package/hub/team/headless-bridge-session.mjs +17 -0
- package/hub/team/headless.mjs +2065 -1156
- package/hub/team/health-probe.mjs +232 -69
- package/hub/team/launcher-template.mjs +23 -13
- package/hub/team/lead-control.mjs +91 -4
- package/hub/team/mcp-selector.mjs +145 -0
- package/hub/team/native-supervisor.mjs +90 -26
- package/hub/team/native.mjs +74 -35
- package/hub/team/nativeProxy.mjs +184 -113
- package/hub/team/notify.mjs +147 -78
- package/hub/team/orchestrator.mjs +173 -161
- package/hub/team/pane.mjs +43 -15
- package/hub/team/process-cleanup.mjs +69 -25
- package/hub/team/psmux.mjs +1067 -264
- package/hub/team/recovery-store.mjs +149 -0
- package/hub/team/remote-probe.mjs +69 -53
- package/hub/team/remote-session.mjs +131 -59
- package/hub/team/remote-watcher.mjs +62 -34
- package/hub/team/retry-state-machine.mjs +267 -0
- package/hub/team/routing.mjs +51 -25
- package/hub/team/runtime-strategy.mjs +200 -0
- package/hub/team/schemas/worker-completion.json +39 -0
- package/hub/team/sentinel-capture.mjs +127 -0
- package/hub/team/session-sync.mjs +153 -6
- package/hub/team/session.mjs +605 -611
- package/hub/team/shared.mjs +13 -13
- package/hub/team/staleState.mjs +72 -30
- package/hub/team/swarm-cli.mjs +390 -0
- package/hub/team/swarm-hypervisor.mjs +1526 -198
- package/hub/team/swarm-intent.mjs +216 -0
- package/hub/team/swarm-locks.mjs +140 -25
- package/hub/team/swarm-planner.mjs +121 -21
- package/hub/team/swarm-preflight.mjs +317 -0
- package/hub/team/swarm-reconciler.mjs +128 -23
- package/hub/team/synapse-cli.mjs +458 -0
- package/hub/team/synapse-http.mjs +60 -0
- package/hub/team/synapse-registry.mjs +329 -0
- package/hub/team/terminal-opener.mjs +216 -0
- package/hub/team/tui-core.mjs +288 -0
- package/hub/team/tui-lite.mjs +234 -146
- package/hub/team/tui-remote-adapter.mjs +21 -37
- package/hub/team/tui-synapse.mjs +223 -0
- package/hub/team/tui-viewer.mjs +104 -45
- package/hub/team/tui-widgets.mjs +262 -0
- package/hub/team/tui.mjs +936 -449
- package/hub/team/worker-completion-validator.mjs +84 -0
- package/hub/team/worker-sandbox.mjs +93 -0
- package/hub/team/worker-signal.mjs +263 -0
- package/hub/team/worker-signal.types.d.ts +52 -0
- package/hub/team/worktree-lifecycle.mjs +518 -64
- package/hub/team/wt-manager.mjs +297 -26
- package/hub/tools.mjs +474 -252
- package/hub/tray.mjs +411 -368
- package/hub/workers/claude-worker.mjs +162 -118
- package/hub/workers/codex-app-server-worker.mjs +1015 -0
- package/hub/workers/codex-mcp.mjs +724 -507
- package/hub/workers/delegator-mcp.mjs +663 -337
- package/hub/workers/factory.mjs +100 -21
- package/hub/workers/gemini-worker.mjs +179 -86
- package/hub/workers/interface.mjs +23 -2
- package/hub/workers/lib/jsonrpc-stdio.mjs +463 -0
- package/hub/workers/worker-utils.mjs +22 -16
- package/package.json +4 -3
- package/scripts/lib/agent-json.mjs +27 -0
- package/scripts/lib/async.mjs +174 -0
- package/scripts/lib/claudemd-scanner.mjs +223 -0
- package/scripts/lib/cli-agy.mjs +62 -0
- package/scripts/lib/cli-claude.mjs +78 -0
- package/scripts/lib/cli-codex.mjs +199 -0
- package/scripts/lib/cli-gemini.mjs +67 -0
- package/scripts/lib/codex-recovery.sh +50 -0
- package/scripts/lib/context.mjs +67 -0
- package/scripts/lib/cross-review-utils.mjs +59 -0
- package/scripts/lib/doctor-env-checks.mjs +121 -0
- package/scripts/lib/dynamic-route-cli.mjs +107 -0
- package/scripts/lib/env-probe.mjs +269 -0
- package/scripts/lib/env.mjs +14 -0
- package/scripts/lib/gemini-profiles.mjs +119 -0
- package/scripts/lib/handoff.mjs +187 -0
- package/scripts/lib/hook-utils.mjs +16 -0
- package/scripts/lib/hub.mjs +48 -0
- package/scripts/lib/keyword-rules.mjs +195 -0
- package/scripts/lib/logger.mjs +105 -0
- package/scripts/lib/mcp-filter.mjs +883 -0
- package/scripts/lib/mcp-gateway-servers.mjs +70 -0
- package/scripts/lib/mcp-guard-engine.mjs +2120 -0
- package/scripts/lib/mcp-health.mjs +773 -0
- package/scripts/lib/mcp-manifest.mjs +89 -0
- package/scripts/lib/mcp-server-catalog.mjs +355 -0
- package/scripts/lib/pid.mjs +63 -0
- package/scripts/lib/process-utils.mjs +26 -0
- package/scripts/lib/psmux-info.mjs +179 -0
- package/scripts/lib/quota.mjs +47 -0
- package/scripts/lib/remote-spawn-transfer.mjs +226 -0
- package/scripts/lib/skill-state.mjs +220 -0
- package/scripts/lib/skill-template.mjs +375 -0
- package/scripts/lib/team.mjs +75 -0
- package/scripts/lib/timeout.mjs +66 -0
- package/scripts/lib/tmp.mjs +26 -0
- package/scripts/lib/toml.mjs +113 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// cli-codex.mjs — Codex CLI adapter (Phase 0 PoC).
|
|
2
|
+
//
|
|
3
|
+
// 책임: agent 이름과 입력 파라미터를 받아 Codex 실행 계획(profile/effort/timeout/runMode)을
|
|
4
|
+
// 산출한다. 실제 spawn 은 Phase 1 에서 이관된다 (현재는 tfx-route.sh 가 수행).
|
|
5
|
+
//
|
|
6
|
+
// 진입점: tfx-route.mjs → selectAdapter("codex") → plan({...}).
|
|
7
|
+
// 출처: scripts/tfx-route.sh route_agent() L1001-L1079 case 문 미러.
|
|
8
|
+
|
|
9
|
+
export const id = "codex";
|
|
10
|
+
export const cliType = "codex";
|
|
11
|
+
export const command = "codex";
|
|
12
|
+
|
|
13
|
+
const AGENT_PROFILES = {
|
|
14
|
+
executor: {
|
|
15
|
+
profile: "gpt55_high",
|
|
16
|
+
timeoutSec: 1080,
|
|
17
|
+
runMode: "fg",
|
|
18
|
+
opusOversight: false,
|
|
19
|
+
mcpHint: "implement",
|
|
20
|
+
},
|
|
21
|
+
codex: {
|
|
22
|
+
profile: "gpt55_high",
|
|
23
|
+
timeoutSec: 1080,
|
|
24
|
+
runMode: "fg",
|
|
25
|
+
opusOversight: false,
|
|
26
|
+
mcpHint: "implement",
|
|
27
|
+
},
|
|
28
|
+
"build-fixer": {
|
|
29
|
+
profile: "gpt55_low",
|
|
30
|
+
timeoutSec: 540,
|
|
31
|
+
runMode: "fg",
|
|
32
|
+
opusOversight: false,
|
|
33
|
+
mcpHint: "implement",
|
|
34
|
+
},
|
|
35
|
+
cleanup: {
|
|
36
|
+
profile: "mini54_med",
|
|
37
|
+
timeoutSec: 540,
|
|
38
|
+
runMode: "fg",
|
|
39
|
+
opusOversight: false,
|
|
40
|
+
mcpHint: "implement",
|
|
41
|
+
},
|
|
42
|
+
deslop: {
|
|
43
|
+
profile: "mini54_med",
|
|
44
|
+
timeoutSec: 540,
|
|
45
|
+
runMode: "fg",
|
|
46
|
+
opusOversight: false,
|
|
47
|
+
mcpHint: "implement",
|
|
48
|
+
},
|
|
49
|
+
debugger: {
|
|
50
|
+
profile: "gpt55_xhigh",
|
|
51
|
+
timeoutSec: 900,
|
|
52
|
+
runMode: "bg",
|
|
53
|
+
opusOversight: false,
|
|
54
|
+
mcpHint: "implement",
|
|
55
|
+
},
|
|
56
|
+
"deep-executor": {
|
|
57
|
+
profile: "gpt55_xhigh",
|
|
58
|
+
timeoutSec: 3600,
|
|
59
|
+
runMode: "bg",
|
|
60
|
+
opusOversight: true,
|
|
61
|
+
mcpHint: "implement",
|
|
62
|
+
},
|
|
63
|
+
architect: {
|
|
64
|
+
profile: "gpt55_xhigh",
|
|
65
|
+
timeoutSec: 3600,
|
|
66
|
+
runMode: "bg",
|
|
67
|
+
opusOversight: true,
|
|
68
|
+
mcpHint: "analyze",
|
|
69
|
+
},
|
|
70
|
+
critic: {
|
|
71
|
+
profile: "gpt55_xhigh",
|
|
72
|
+
timeoutSec: 3600,
|
|
73
|
+
runMode: "bg",
|
|
74
|
+
opusOversight: true,
|
|
75
|
+
mcpHint: "analyze",
|
|
76
|
+
},
|
|
77
|
+
planner: {
|
|
78
|
+
profile: "gpt55_xhigh",
|
|
79
|
+
timeoutSec: 3600,
|
|
80
|
+
runMode: "fg",
|
|
81
|
+
opusOversight: true,
|
|
82
|
+
mcpHint: "analyze",
|
|
83
|
+
},
|
|
84
|
+
analyst: {
|
|
85
|
+
profile: "gpt55_xhigh",
|
|
86
|
+
timeoutSec: 3600,
|
|
87
|
+
runMode: "fg",
|
|
88
|
+
opusOversight: true,
|
|
89
|
+
mcpHint: "analyze",
|
|
90
|
+
},
|
|
91
|
+
"code-reviewer": {
|
|
92
|
+
profile: "gpt55_high",
|
|
93
|
+
timeoutSec: 1800,
|
|
94
|
+
runMode: "bg",
|
|
95
|
+
opusOversight: false,
|
|
96
|
+
mcpHint: "review",
|
|
97
|
+
subcommand: "review",
|
|
98
|
+
},
|
|
99
|
+
"quality-reviewer": {
|
|
100
|
+
profile: "gpt55_high",
|
|
101
|
+
timeoutSec: 1800,
|
|
102
|
+
runMode: "bg",
|
|
103
|
+
opusOversight: false,
|
|
104
|
+
mcpHint: "review",
|
|
105
|
+
subcommand: "review",
|
|
106
|
+
},
|
|
107
|
+
"security-reviewer": {
|
|
108
|
+
profile: "gpt55_xhigh",
|
|
109
|
+
timeoutSec: 1800,
|
|
110
|
+
runMode: "bg",
|
|
111
|
+
opusOversight: true,
|
|
112
|
+
mcpHint: "review",
|
|
113
|
+
subcommand: "review",
|
|
114
|
+
},
|
|
115
|
+
scientist: {
|
|
116
|
+
profile: "gpt55_high",
|
|
117
|
+
timeoutSec: 1440,
|
|
118
|
+
runMode: "bg",
|
|
119
|
+
opusOversight: false,
|
|
120
|
+
mcpHint: "analyze",
|
|
121
|
+
},
|
|
122
|
+
"document-specialist": {
|
|
123
|
+
profile: "gpt55_high",
|
|
124
|
+
timeoutSec: 1440,
|
|
125
|
+
runMode: "bg",
|
|
126
|
+
opusOversight: false,
|
|
127
|
+
mcpHint: "analyze",
|
|
128
|
+
},
|
|
129
|
+
"scientist-deep": {
|
|
130
|
+
profile: "gpt55_xhigh",
|
|
131
|
+
timeoutSec: 3600,
|
|
132
|
+
runMode: "bg",
|
|
133
|
+
opusOversight: false,
|
|
134
|
+
mcpHint: "analyze",
|
|
135
|
+
},
|
|
136
|
+
verifier: {
|
|
137
|
+
profile: "gpt55_high",
|
|
138
|
+
timeoutSec: 1200,
|
|
139
|
+
runMode: "fg",
|
|
140
|
+
opusOversight: false,
|
|
141
|
+
mcpHint: "review",
|
|
142
|
+
subcommand: "review",
|
|
143
|
+
},
|
|
144
|
+
"test-engineer": {
|
|
145
|
+
profile: "gpt55_high",
|
|
146
|
+
timeoutSec: 1200,
|
|
147
|
+
runMode: "bg",
|
|
148
|
+
opusOversight: false,
|
|
149
|
+
mcpHint: "implement",
|
|
150
|
+
},
|
|
151
|
+
"qa-tester": {
|
|
152
|
+
profile: "gpt55_high",
|
|
153
|
+
timeoutSec: 1200,
|
|
154
|
+
runMode: "bg",
|
|
155
|
+
opusOversight: false,
|
|
156
|
+
mcpHint: "review",
|
|
157
|
+
subcommand: "review",
|
|
158
|
+
},
|
|
159
|
+
spark: {
|
|
160
|
+
profile: "spark53_low",
|
|
161
|
+
timeoutSec: 180,
|
|
162
|
+
runMode: "fg",
|
|
163
|
+
opusOversight: false,
|
|
164
|
+
mcpHint: "implement",
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const DEFAULT_PROFILE = AGENT_PROFILES.executor;
|
|
169
|
+
|
|
170
|
+
export function plan({
|
|
171
|
+
agent,
|
|
172
|
+
prompt = "",
|
|
173
|
+
mcpProfile = "auto",
|
|
174
|
+
timeoutSec,
|
|
175
|
+
contextFile,
|
|
176
|
+
} = {}) {
|
|
177
|
+
if (!agent) {
|
|
178
|
+
throw new Error("[cli-codex] agent required");
|
|
179
|
+
}
|
|
180
|
+
const cfg = AGENT_PROFILES[agent] ?? DEFAULT_PROFILE;
|
|
181
|
+
const effectiveTimeoutSec =
|
|
182
|
+
Number.isFinite(timeoutSec) && timeoutSec > 0 ? timeoutSec : cfg.timeoutSec;
|
|
183
|
+
return {
|
|
184
|
+
command,
|
|
185
|
+
subcommand: cfg.subcommand ?? "exec",
|
|
186
|
+
profile: cfg.profile,
|
|
187
|
+
effort: cfg.profile,
|
|
188
|
+
timeoutMs: effectiveTimeoutSec * 1000,
|
|
189
|
+
runMode: cfg.runMode,
|
|
190
|
+
opusOversight: cfg.opusOversight,
|
|
191
|
+
mcpProfile: mcpProfile === "auto" ? cfg.mcpHint : mcpProfile,
|
|
192
|
+
promptLength: prompt.length,
|
|
193
|
+
contextFile: contextFile || null,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function describe() {
|
|
198
|
+
return { id, cliType, command, agents: Object.keys(AGENT_PROFILES) };
|
|
199
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// cli-gemini.mjs — Gemini CLI adapter (Phase 0 PoC).
|
|
2
|
+
//
|
|
3
|
+
// 책임: Gemini 프로파일/모델/timeout 매핑. resolve_gemini_profile() 의 결과는 Phase 1 에서
|
|
4
|
+
// 통합한다 (Phase 0 은 라벨만 전달).
|
|
5
|
+
//
|
|
6
|
+
// 출처: scripts/tfx-route.sh route_agent() L1045-L1050.
|
|
7
|
+
|
|
8
|
+
export const id = "gemini";
|
|
9
|
+
export const cliType = "gemini";
|
|
10
|
+
export const command = "gemini";
|
|
11
|
+
|
|
12
|
+
const AGENT_PROFILES = {
|
|
13
|
+
designer: {
|
|
14
|
+
profile: "pro31",
|
|
15
|
+
timeoutSec: 900,
|
|
16
|
+
runMode: "bg",
|
|
17
|
+
opusOversight: false,
|
|
18
|
+
mcpHint: "docs",
|
|
19
|
+
},
|
|
20
|
+
gemini: {
|
|
21
|
+
profile: "pro31",
|
|
22
|
+
timeoutSec: 900,
|
|
23
|
+
runMode: "bg",
|
|
24
|
+
opusOversight: false,
|
|
25
|
+
mcpHint: "docs",
|
|
26
|
+
},
|
|
27
|
+
writer: {
|
|
28
|
+
profile: "flash3",
|
|
29
|
+
timeoutSec: 900,
|
|
30
|
+
runMode: "bg",
|
|
31
|
+
opusOversight: false,
|
|
32
|
+
mcpHint: "docs",
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const DEFAULT_PROFILE = AGENT_PROFILES.gemini;
|
|
37
|
+
|
|
38
|
+
export function plan({
|
|
39
|
+
agent,
|
|
40
|
+
prompt = "",
|
|
41
|
+
mcpProfile = "auto",
|
|
42
|
+
timeoutSec,
|
|
43
|
+
contextFile,
|
|
44
|
+
} = {}) {
|
|
45
|
+
if (!agent) {
|
|
46
|
+
throw new Error("[cli-gemini] agent required");
|
|
47
|
+
}
|
|
48
|
+
const cfg = AGENT_PROFILES[agent] ?? DEFAULT_PROFILE;
|
|
49
|
+
const effectiveTimeoutSec =
|
|
50
|
+
Number.isFinite(timeoutSec) && timeoutSec > 0 ? timeoutSec : cfg.timeoutSec;
|
|
51
|
+
return {
|
|
52
|
+
command,
|
|
53
|
+
profile: cfg.profile,
|
|
54
|
+
effort: cfg.profile,
|
|
55
|
+
args: ["-m", cfg.profile, "-y", "--prompt"],
|
|
56
|
+
timeoutMs: effectiveTimeoutSec * 1000,
|
|
57
|
+
runMode: cfg.runMode,
|
|
58
|
+
opusOversight: cfg.opusOversight,
|
|
59
|
+
mcpProfile: mcpProfile === "auto" ? cfg.mcpHint : mcpProfile,
|
|
60
|
+
promptLength: prompt.length,
|
|
61
|
+
contextFile: contextFile || null,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function describe() {
|
|
66
|
+
return { id, cliType, command, agents: Object.keys(AGENT_PROFILES) };
|
|
67
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# codex-recovery.sh — codex exec stdout 4계단 recovery helper.
|
|
3
|
+
# 사용: STDOUT_LOG / STDERR_LOG env 설정 후 `source` + `recover_codex_stdout`.
|
|
4
|
+
#
|
|
5
|
+
# 1차 codex marker → stderr 의 마지막 "codex" 라인 이후 본문 회수
|
|
6
|
+
# 2차 node parser → MCP/header/sandbox 로그 제외, 응답 부분만 추출
|
|
7
|
+
# 3차 stderr tail → "tokens used" 직전까지 tail buffer 회수
|
|
8
|
+
# 4차 FALLBACK_FAILED → 모두 실패 시 marker + stderr_log 경로 출력
|
|
9
|
+
|
|
10
|
+
recover_codex_stdout() {
|
|
11
|
+
if [[ -s "$STDOUT_LOG" || ! -s "$STDERR_LOG" ]]; then
|
|
12
|
+
return 0
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
# 1차: codex marker
|
|
16
|
+
sed 's/\r$//' "$STDERR_LOG" \
|
|
17
|
+
| awk '/^codex$/{found=NR;content=""} found && NR>found{content=content RS $0} END{if(content) print substr(content,2)}' \
|
|
18
|
+
> "$STDOUT_LOG"
|
|
19
|
+
|
|
20
|
+
# 2차: node parser (MCP/header/sandbox 로그 제외)
|
|
21
|
+
if [[ ! -s "$STDOUT_LOG" ]]; then
|
|
22
|
+
node -e '
|
|
23
|
+
const fs=require("fs"),lines=fs.readFileSync(process.argv[1],"utf-8").split(/\r?\n/);
|
|
24
|
+
const skip=/^(mcp[: ]|OpenAI Codex|--------|workdir:|model:|provider:|approval:|sandbox:|reasoning|session id:|user$|tokens used|EXIT:|exec$|"[A-Z]:|succeeded in |\s*$)/;
|
|
25
|
+
const out=lines.filter(l=>!skip.test(l));
|
|
26
|
+
if(out.length) fs.writeFileSync(process.argv[2],out.join("\n"));
|
|
27
|
+
' -- "$STDERR_LOG" "$STDOUT_LOG" 2>/dev/null || true
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# 3차: stderr tail before "tokens used"
|
|
31
|
+
if [[ ! -s "$STDOUT_LOG" ]]; then
|
|
32
|
+
sed 's/\r$//' "$STDERR_LOG" \
|
|
33
|
+
| awk '
|
|
34
|
+
/^tokens used/ { exit }
|
|
35
|
+
{ buf[NR]=$0 }
|
|
36
|
+
END {
|
|
37
|
+
start=NR-200; if (start<1) start=1
|
|
38
|
+
for (i=start; i<=NR; i++) if (i in buf) print buf[i]
|
|
39
|
+
}' \
|
|
40
|
+
> "$STDOUT_LOG"
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
if [[ -s "$STDOUT_LOG" ]]; then
|
|
44
|
+
echo "[tfx-route] 경고: codex stdout 비어있음, stderr에서 응답 복구 ($(wc -c < "$STDOUT_LOG" | tr -d ' ') bytes)" >&2
|
|
45
|
+
else
|
|
46
|
+
# 4차: FALLBACK_FAILED
|
|
47
|
+
echo "[tfx-route] FALLBACK_FAILED stderr_log=$STDERR_LOG" >&2
|
|
48
|
+
echo "[tfx-route] 경고: codex stdout 비어있음, stderr 복구도 실패. 위 stderr_log 경로에서 raw codex 출력 확인 가능." >&2
|
|
49
|
+
fi
|
|
50
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 요청별 로그 컨텍스트 전파 (AsyncLocalStorage 기반).
|
|
3
|
+
*
|
|
4
|
+
* Hub HTTP 서버의 요청마다 correlationId를 자동 할당하여,
|
|
5
|
+
* 하나의 요청에서 발생한 모든 로그를 추적할 수 있다.
|
|
6
|
+
*
|
|
7
|
+
* 사용법:
|
|
8
|
+
* import { getLogger, getCorrelationId, withRequestContext } from './lib/context.mjs';
|
|
9
|
+
*
|
|
10
|
+
* // 미들웨어에서 컨텍스트 생성
|
|
11
|
+
* withRequestContext({ method: 'POST', path: '/bridge/result' }, () => {
|
|
12
|
+
* const log = getLogger();
|
|
13
|
+
* log.info({ agentId }, 'bridge.result_received');
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* // 내부 함수에서 자동 상관 ID
|
|
17
|
+
* function processResult() {
|
|
18
|
+
* const log = getLogger();
|
|
19
|
+
* log.info('result.processed'); // correlationId 자동 포함
|
|
20
|
+
* }
|
|
21
|
+
*/
|
|
22
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
23
|
+
import { randomUUID } from "node:crypto";
|
|
24
|
+
|
|
25
|
+
import { logger } from "./logger.mjs";
|
|
26
|
+
|
|
27
|
+
/** @type {AsyncLocalStorage<{logger: import('pino').Logger, correlationId: string}>} */
|
|
28
|
+
export const asyncLocalStorage = new AsyncLocalStorage();
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 현재 요청 컨텍스트의 로거를 반환한다.
|
|
32
|
+
* 요청 컨텍스트 밖에서 호출하면 기본 로거를 반환한다.
|
|
33
|
+
*
|
|
34
|
+
* @returns {import('pino').Logger}
|
|
35
|
+
*/
|
|
36
|
+
export function getLogger() {
|
|
37
|
+
return asyncLocalStorage.getStore()?.logger || logger;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 현재 요청의 상관 ID를 반환한다.
|
|
42
|
+
*
|
|
43
|
+
* @returns {string|undefined}
|
|
44
|
+
*/
|
|
45
|
+
export function getCorrelationId() {
|
|
46
|
+
return asyncLocalStorage.getStore()?.correlationId;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 요청 컨텍스트를 생성하고 콜백을 실행한다.
|
|
51
|
+
*
|
|
52
|
+
* @param {object} context — 컨텍스트 필드 (method, path 등)
|
|
53
|
+
* @param {string} [context.correlationId] — 외부에서 전달된 상관 ID (없으면 자동 생성)
|
|
54
|
+
* @param {function} callback — 컨텍스트 내에서 실행할 함수
|
|
55
|
+
* @returns {*}
|
|
56
|
+
*/
|
|
57
|
+
export function withRequestContext(context, callback) {
|
|
58
|
+
const correlationId = context.correlationId || randomUUID();
|
|
59
|
+
const { correlationId: _, ...rest } = context;
|
|
60
|
+
|
|
61
|
+
const store = {
|
|
62
|
+
correlationId,
|
|
63
|
+
logger: logger.child({ correlationId, ...rest }),
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return asyncLocalStorage.run(store, callback);
|
|
67
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
|
|
3
|
+
export const SESSION_TTL_SEC = 30 * 60;
|
|
4
|
+
export const STATE_REL_PATH = join(".triflux", "state", "cross-review.json");
|
|
5
|
+
|
|
6
|
+
export function readStdin() {
|
|
7
|
+
return new Promise((resolve) => {
|
|
8
|
+
let raw = "";
|
|
9
|
+
process.stdin.setEncoding("utf8");
|
|
10
|
+
process.stdin.on("data", (chunk) => {
|
|
11
|
+
raw += chunk;
|
|
12
|
+
});
|
|
13
|
+
process.stdin.on("end", () => resolve(raw));
|
|
14
|
+
process.stdin.on("error", () => resolve(""));
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function parseJson(raw) {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(raw);
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function nowSec() {
|
|
27
|
+
return Math.floor(Date.now() / 1000);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function resolveBaseDir(payload) {
|
|
31
|
+
if (typeof payload?.cwd === "string" && payload.cwd.trim())
|
|
32
|
+
return payload.cwd;
|
|
33
|
+
if (typeof payload?.directory === "string" && payload.directory.trim())
|
|
34
|
+
return payload.directory;
|
|
35
|
+
return process.cwd();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function shouldTrackPath(filePath) {
|
|
39
|
+
if (typeof filePath !== "string" || !filePath.trim()) return false;
|
|
40
|
+
|
|
41
|
+
const lower = filePath.toLowerCase();
|
|
42
|
+
if (
|
|
43
|
+
lower.startsWith(".triflux/") ||
|
|
44
|
+
lower.startsWith(".omc/") ||
|
|
45
|
+
lower.startsWith(".claude/")
|
|
46
|
+
)
|
|
47
|
+
return false;
|
|
48
|
+
if (lower === "package-lock.json" || lower.endsWith("/package-lock.json"))
|
|
49
|
+
return false;
|
|
50
|
+
if (/\.(md|lock|yml|yaml)$/i.test(lower)) return false;
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function expectedReviewer(author) {
|
|
55
|
+
if (author === "claude") return "codex";
|
|
56
|
+
if (author === "codex") return "claude";
|
|
57
|
+
if (author === "gemini") return "claude";
|
|
58
|
+
return "";
|
|
59
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
export function commandExists(name) {
|
|
4
|
+
try {
|
|
5
|
+
execFileSync("sh", ["-c", 'command -v "$1" >/dev/null 2>&1', "sh", name], {
|
|
6
|
+
stdio: "ignore",
|
|
7
|
+
timeout: 2000,
|
|
8
|
+
});
|
|
9
|
+
return true;
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function inspectMacTimeoutDependency({
|
|
16
|
+
platform = process.platform,
|
|
17
|
+
commandExists: exists = commandExists,
|
|
18
|
+
} = {}) {
|
|
19
|
+
if (platform !== "darwin") {
|
|
20
|
+
return { ok: true, status: "skipped", platform };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (exists("gtimeout")) {
|
|
24
|
+
return { ok: true, status: "ok", platform, provider: "gtimeout" };
|
|
25
|
+
}
|
|
26
|
+
if (exists("timeout")) {
|
|
27
|
+
return { ok: true, status: "ok", platform, provider: "timeout" };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
status: "missing",
|
|
33
|
+
platform,
|
|
34
|
+
fix: "brew install coreutils",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function addPluginRootFallbackToCommand(command, pluginRoot) {
|
|
39
|
+
if (typeof command !== "string") return command;
|
|
40
|
+
const fallbackRoot = String(pluginRoot || "").replace(/\\/g, "/");
|
|
41
|
+
if (!fallbackRoot) return command;
|
|
42
|
+
return command
|
|
43
|
+
.replaceAll("${PLUGIN_ROOT}", `\${PLUGIN_ROOT:-${fallbackRoot}}`)
|
|
44
|
+
.replaceAll(
|
|
45
|
+
"${CLAUDE_PLUGIN_ROOT}",
|
|
46
|
+
`\${CLAUDE_PLUGIN_ROOT:-${fallbackRoot}}`,
|
|
47
|
+
)
|
|
48
|
+
.replace(
|
|
49
|
+
/(^|[\s"'])\/(hooks|scripts)\//g,
|
|
50
|
+
`$1\${PLUGIN_ROOT:-${fallbackRoot}}/$2/`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function hasBarePluginRootReference(command) {
|
|
55
|
+
if (typeof command !== "string") return false;
|
|
56
|
+
return (
|
|
57
|
+
command.includes("${PLUGIN_ROOT}/") ||
|
|
58
|
+
command.includes("${CLAUDE_PLUGIN_ROOT}/")
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function hasCollapsedRootHookPath(command) {
|
|
63
|
+
if (typeof command !== "string") return false;
|
|
64
|
+
return /(^|[\s"'])\/(?:hooks|scripts)\//.test(command);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function findPluginRootHookIssues(settings) {
|
|
68
|
+
const hooksByEvent =
|
|
69
|
+
settings?.hooks && typeof settings.hooks === "object" ? settings.hooks : {};
|
|
70
|
+
const issues = [];
|
|
71
|
+
|
|
72
|
+
for (const [event, entries] of Object.entries(hooksByEvent)) {
|
|
73
|
+
if (!Array.isArray(entries)) continue;
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
const hooks = Array.isArray(entry?.hooks) ? entry.hooks : [];
|
|
76
|
+
for (const hook of hooks) {
|
|
77
|
+
const command = hook?.command;
|
|
78
|
+
if (
|
|
79
|
+
!hasBarePluginRootReference(command) &&
|
|
80
|
+
!hasCollapsedRootHookPath(command)
|
|
81
|
+
) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
issues.push({
|
|
85
|
+
event,
|
|
86
|
+
matcher: entry?.matcher || "*",
|
|
87
|
+
command,
|
|
88
|
+
reason: hasCollapsedRootHookPath(command)
|
|
89
|
+
? "collapsed-root-path"
|
|
90
|
+
: "missing-plugin-root-fallback",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return issues;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function applyPluginRootHookFallbacks(settings, { pluginRoot } = {}) {
|
|
100
|
+
if (!settings?.hooks || typeof settings.hooks !== "object") {
|
|
101
|
+
return { changed: false, count: 0, settings };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let count = 0;
|
|
105
|
+
for (const entries of Object.values(settings.hooks)) {
|
|
106
|
+
if (!Array.isArray(entries)) continue;
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
const hooks = Array.isArray(entry?.hooks) ? entry.hooks : [];
|
|
109
|
+
for (const hook of hooks) {
|
|
110
|
+
if (typeof hook?.command !== "string") continue;
|
|
111
|
+
const next = addPluginRootFallbackToCommand(hook.command, pluginRoot);
|
|
112
|
+
if (next !== hook.command) {
|
|
113
|
+
hook.command = next;
|
|
114
|
+
count++;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { changed: count > 0, count, settings };
|
|
121
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/lib/dynamic-route-cli.mjs
|
|
3
|
+
//
|
|
4
|
+
// Phase 1 Wire-up 3 helper — tfx-route.sh 등 sh 경로에서 dynamic routing
|
|
5
|
+
// decision 을 받아 단일 CLI 문자열 (또는 JSON) 으로 출력한다.
|
|
6
|
+
//
|
|
7
|
+
// 사용:
|
|
8
|
+
// node scripts/lib/dynamic-route-cli.mjs --agent-hint codex
|
|
9
|
+
// → "codex"
|
|
10
|
+
// node scripts/lib/dynamic-route-cli.mjs --agent-hint codex --json
|
|
11
|
+
// → {"scenario":"fallback","mode":"codex-default","shards":[{"cli":"codex",...}]}
|
|
12
|
+
// node scripts/lib/dynamic-route-cli.mjs --agent-hint codex --field scenario
|
|
13
|
+
// → "fallback"
|
|
14
|
+
//
|
|
15
|
+
// 정책:
|
|
16
|
+
// - opt-in env TRIFLUX_DYNAMIC_ROUTING=1|true 미설정 시 즉시 silent exit 0
|
|
17
|
+
// (override 없음 = stdout empty, sh caller 가 graceful no-op).
|
|
18
|
+
// - error 발생 시 stderr 에 한 줄 + exit 0 (sh caller 가 fallback path 진행).
|
|
19
|
+
|
|
20
|
+
import { createDynamicRouter } from "@triflux/core/hub/dynamic-routing-engine.mjs";
|
|
21
|
+
|
|
22
|
+
function parseArgs(argv) {
|
|
23
|
+
const args = {
|
|
24
|
+
taskId: `tfx-route-${process.pid}-${Date.now()}`,
|
|
25
|
+
agentHint: "codex",
|
|
26
|
+
teamSize: 1,
|
|
27
|
+
json: false,
|
|
28
|
+
field: null,
|
|
29
|
+
};
|
|
30
|
+
for (let i = 2; i < argv.length; i++) {
|
|
31
|
+
const v = argv[i];
|
|
32
|
+
switch (v) {
|
|
33
|
+
case "--task-id":
|
|
34
|
+
args.taskId = argv[++i] || args.taskId;
|
|
35
|
+
break;
|
|
36
|
+
case "--agent-hint":
|
|
37
|
+
args.agentHint = argv[++i] || args.agentHint;
|
|
38
|
+
break;
|
|
39
|
+
case "--team-size":
|
|
40
|
+
args.teamSize = Number(argv[++i]) || args.teamSize;
|
|
41
|
+
break;
|
|
42
|
+
case "--json":
|
|
43
|
+
args.json = true;
|
|
44
|
+
break;
|
|
45
|
+
case "--field":
|
|
46
|
+
args.field = argv[++i] || null;
|
|
47
|
+
break;
|
|
48
|
+
case "--help":
|
|
49
|
+
case "-h":
|
|
50
|
+
process.stdout.write(
|
|
51
|
+
"Usage: dynamic-route-cli.mjs [--task-id ID] [--agent-hint CLI] [--team-size N] [--json|--field FIELD]\n",
|
|
52
|
+
);
|
|
53
|
+
process.exit(0);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return args;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function main() {
|
|
61
|
+
const args = parseArgs(process.argv);
|
|
62
|
+
|
|
63
|
+
// opt-in env 미설정 시 silent no-op (sh caller graceful path)
|
|
64
|
+
const flag = process.env.TRIFLUX_DYNAMIC_ROUTING;
|
|
65
|
+
if (flag !== "1" && flag !== "true") return;
|
|
66
|
+
|
|
67
|
+
let decision;
|
|
68
|
+
try {
|
|
69
|
+
const router = createDynamicRouter();
|
|
70
|
+
decision = await router.routeRequest({
|
|
71
|
+
task_id: args.taskId,
|
|
72
|
+
team_size: args.teamSize,
|
|
73
|
+
agent_hint: args.agentHint,
|
|
74
|
+
});
|
|
75
|
+
} catch (err) {
|
|
76
|
+
process.stderr.write(
|
|
77
|
+
`[dynamic-route-cli] error: ${err?.message || String(err)}\n`,
|
|
78
|
+
);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (args.json) {
|
|
83
|
+
process.stdout.write(JSON.stringify(decision));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (args.field) {
|
|
88
|
+
const value = decision?.[args.field];
|
|
89
|
+
if (value !== undefined && value !== null) {
|
|
90
|
+
process.stdout.write(String(value));
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 기본 — shards[0].cli 만 stdout 으로 (sh caller 가 즉시 비교)
|
|
96
|
+
const cli = decision?.shards?.[0]?.cli;
|
|
97
|
+
if (cli) {
|
|
98
|
+
process.stdout.write(String(cli));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
main().catch((err) => {
|
|
103
|
+
process.stderr.write(
|
|
104
|
+
`[dynamic-route-cli] fatal: ${err?.message || String(err)}\n`,
|
|
105
|
+
);
|
|
106
|
+
process.exit(0); // sh caller 가 정상 fallback 하도록 always 0
|
|
107
|
+
});
|