triflux 10.3.4 → 10.7.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 (329) hide show
  1. package/LICENSE +21 -21
  2. package/bin/tfx-doctor-tui.mjs +1 -1
  3. package/bin/tfx-doctor.mjs +6 -1
  4. package/bin/tfx-profile.mjs +1 -1
  5. package/bin/tfx-setup-tui.mjs +1 -1
  6. package/bin/tfx-setup.mjs +6 -1
  7. package/bin/triflux.mjs +2396 -1140
  8. package/hooks/agent-route-guard.mjs +12 -8
  9. package/hooks/cross-review-tracker.mjs +21 -8
  10. package/hooks/error-context.mjs +19 -7
  11. package/hooks/hook-adaptive-collector.mjs +18 -16
  12. package/hooks/hook-manager.mjs +93 -32
  13. package/hooks/hook-orchestrator.mjs +108 -24
  14. package/hooks/hook-registry.json +11 -0
  15. package/hooks/keyword-rules.json +6 -10
  16. package/hooks/lib/resolve-root.mjs +1 -1
  17. package/hooks/mcp-config-watcher.mjs +6 -2
  18. package/hooks/pipeline-stop.mjs +3 -6
  19. package/hooks/safety-guard.mjs +99 -28
  20. package/hooks/session-start-fast.mjs +143 -0
  21. package/hooks/subagent-verifier.mjs +5 -4
  22. package/hub/account-broker.mjs +256 -60
  23. package/hub/adaptive-diagnostic.mjs +75 -48
  24. package/hub/adaptive-inject.mjs +95 -57
  25. package/hub/adaptive-memory.mjs +156 -42
  26. package/hub/adaptive.mjs +60 -31
  27. package/hub/assign-callbacks.mjs +67 -30
  28. package/hub/bridge.mjs +0 -1
  29. package/hub/cli-adapter-base.mjs +200 -48
  30. package/hub/codex-adapter.mjs +76 -96
  31. package/hub/codex-compat.mjs +3 -3
  32. package/hub/codex-preflight.mjs +63 -37
  33. package/hub/delegator/contracts.mjs +19 -23
  34. package/hub/delegator/index.mjs +3 -3
  35. package/hub/delegator/service.mjs +88 -64
  36. package/hub/delegator/tool-definitions.mjs +5 -5
  37. package/hub/fullcycle.mjs +33 -17
  38. package/hub/gemini-adapter.mjs +69 -94
  39. package/hub/hitl.mjs +89 -30
  40. package/hub/intent.mjs +161 -38
  41. package/hub/lib/cache-guard.mjs +43 -17
  42. package/hub/lib/mcp-response-cache.mjs +66 -32
  43. package/hub/lib/memory-store.mjs +285 -111
  44. package/hub/lib/path-utils.mjs +35 -37
  45. package/hub/lib/process-utils.mjs +106 -37
  46. package/hub/lib/spawn-trace.mjs +527 -0
  47. package/hub/lib/ssh-command.mjs +34 -4
  48. package/hub/lib/ssh-retry.mjs +5 -1
  49. package/hub/lib/uuidv7.mjs +4 -3
  50. package/hub/memory-doctor.mjs +266 -106
  51. package/hub/middleware/request-logger.mjs +61 -34
  52. package/hub/paths.mjs +9 -9
  53. package/hub/pipeline/gates/confidence.mjs +34 -15
  54. package/hub/pipeline/gates/consensus.mjs +27 -15
  55. package/hub/pipeline/gates/index.mjs +7 -3
  56. package/hub/pipeline/gates/selfcheck.mjs +57 -19
  57. package/hub/pipeline/index.mjs +77 -42
  58. package/hub/pipeline/state.mjs +10 -10
  59. package/hub/pipeline/transitions.mjs +40 -23
  60. package/hub/platform.mjs +57 -48
  61. package/hub/promote-penalties.mjs +25 -7
  62. package/hub/quality/deslop.mjs +70 -49
  63. package/hub/research.mjs +32 -25
  64. package/hub/router.mjs +240 -107
  65. package/hub/routing/complexity.mjs +132 -29
  66. package/hub/routing/index.mjs +17 -12
  67. package/hub/routing/q-learning.mjs +76 -28
  68. package/hub/server.mjs +4 -4
  69. package/hub/session-fingerprint.mjs +126 -60
  70. package/hub/state.mjs +84 -43
  71. package/hub/store-adapter.mjs +59 -26
  72. package/hub/store.mjs +356 -153
  73. package/hub/team/agent-map.json +22 -7
  74. package/hub/team/ansi.mjs +186 -122
  75. package/hub/team/backend.mjs +28 -10
  76. package/hub/team/cli/commands/attach.mjs +29 -9
  77. package/hub/team/cli/commands/control.mjs +29 -8
  78. package/hub/team/cli/commands/debug.mjs +32 -11
  79. package/hub/team/cli/commands/focus.mjs +38 -11
  80. package/hub/team/cli/commands/interrupt.mjs +18 -6
  81. package/hub/team/cli/commands/kill.mjs +16 -5
  82. package/hub/team/cli/commands/list.mjs +11 -4
  83. package/hub/team/cli/commands/send.mjs +19 -6
  84. package/hub/team/cli/commands/start/index.mjs +154 -31
  85. package/hub/team/cli/commands/start/parse-args.mjs +38 -11
  86. package/hub/team/cli/commands/start/start-headless.mjs +112 -36
  87. package/hub/team/cli/commands/start/start-in-process.mjs +12 -2
  88. package/hub/team/cli/commands/start/start-mux.mjs +70 -21
  89. package/hub/team/cli/commands/start/start-wt.mjs +29 -12
  90. package/hub/team/cli/commands/status.mjs +43 -14
  91. package/hub/team/cli/commands/stop.mjs +11 -4
  92. package/hub/team/cli/commands/task.mjs +8 -3
  93. package/hub/team/cli/commands/tasks.mjs +1 -1
  94. package/hub/team/cli/index.mjs +2 -2
  95. package/hub/team/cli/manifest.mjs +38 -8
  96. package/hub/team/cli/render.mjs +30 -8
  97. package/hub/team/cli/services/attach-fallback.mjs +31 -11
  98. package/hub/team/cli/services/hub-client.mjs +42 -14
  99. package/hub/team/cli/services/member-selector.mjs +11 -4
  100. package/hub/team/cli/services/native-control.mjs +48 -21
  101. package/hub/team/cli/services/runtime-mode.mjs +2 -1
  102. package/hub/team/cli/services/state-store.mjs +25 -8
  103. package/hub/team/cli/services/task-model.mjs +16 -6
  104. package/hub/team/conductor-mesh-bridge.mjs +24 -23
  105. package/hub/team/conductor.mjs +8 -4
  106. package/hub/team/dashboard-anchor.mjs +4 -5
  107. package/hub/team/dashboard-layout.mjs +3 -1
  108. package/hub/team/dashboard-open.mjs +41 -21
  109. package/hub/team/dashboard.mjs +76 -28
  110. package/hub/team/event-log.mjs +18 -10
  111. package/hub/team/handoff.mjs +31 -15
  112. package/hub/team/headless.mjs +2 -1
  113. package/hub/team/health-probe.mjs +69 -54
  114. package/hub/team/launcher-template.mjs +16 -13
  115. package/hub/team/native-supervisor.mjs +65 -21
  116. package/hub/team/native.mjs +74 -35
  117. package/hub/team/nativeProxy.mjs +184 -113
  118. package/hub/team/notify.mjs +119 -76
  119. package/hub/team/orchestrator.mjs +9 -4
  120. package/hub/team/pane.mjs +12 -7
  121. package/hub/team/process-cleanup.mjs +25 -16
  122. package/hub/team/psmux.mjs +491 -201
  123. package/hub/team/remote-probe.mjs +68 -52
  124. package/hub/team/remote-session.mjs +117 -59
  125. package/hub/team/remote-watcher.mjs +61 -33
  126. package/hub/team/routing.mjs +51 -25
  127. package/hub/team/runtime-strategy.mjs +3 -1
  128. package/hub/team/session.mjs +98 -34
  129. package/hub/team/staleState.mjs +72 -30
  130. package/hub/team/swarm-locks.mjs +15 -13
  131. package/hub/team/swarm-planner.mjs +32 -21
  132. package/hub/team/swarm-reconciler.mjs +48 -23
  133. package/hub/team/tui-lite.mjs +266 -68
  134. package/hub/team/tui-remote-adapter.mjs +14 -10
  135. package/hub/team/tui-viewer.mjs +99 -43
  136. package/hub/team/tui.mjs +708 -271
  137. package/hub/team/worktree-lifecycle.mjs +152 -58
  138. package/hub/team/wt-manager.mjs +24 -14
  139. package/hub/token-mode.mjs +71 -71
  140. package/hub/tray.mjs +66 -23
  141. package/hub/workers/claude-worker.mjs +162 -118
  142. package/hub/workers/codex-mcp.mjs +192 -141
  143. package/hub/workers/delegator-mcp.mjs +507 -333
  144. package/hub/workers/factory.mjs +8 -8
  145. package/hub/workers/gemini-worker.mjs +115 -84
  146. package/hub/workers/interface.mjs +6 -1
  147. package/hub/workers/worker-utils.mjs +21 -14
  148. package/hud/colors.mjs +27 -9
  149. package/hud/constants.mjs +162 -26
  150. package/hud/context-monitor.mjs +82 -41
  151. package/hud/hud-qos-status.mjs +129 -49
  152. package/hud/mission-board.mjs +6 -3
  153. package/hud/providers/claude.mjs +226 -115
  154. package/hud/providers/codex.mjs +62 -22
  155. package/hud/providers/gemini.mjs +168 -56
  156. package/hud/renderers.mjs +384 -119
  157. package/hud/terminal.mjs +101 -31
  158. package/hud/utils.mjs +78 -38
  159. package/mesh/index.mjs +11 -5
  160. package/mesh/mesh-budget.mjs +18 -9
  161. package/mesh/mesh-heartbeat.mjs +1 -1
  162. package/mesh/mesh-queue.mjs +3 -5
  163. package/mesh/mesh-router.mjs +5 -4
  164. package/package.json +2 -1
  165. package/scripts/__tests__/gen-skill-docs.test.mjs +36 -7
  166. package/scripts/__tests__/keyword-detector.test.mjs +77 -28
  167. package/scripts/__tests__/mcp-guard-engine.test.mjs +58 -20
  168. package/scripts/__tests__/remote-spawn-transfer.test.mjs +30 -19
  169. package/scripts/__tests__/remote-spawn.test.mjs +10 -4
  170. package/scripts/__tests__/session-start-fast.test.mjs +36 -0
  171. package/scripts/__tests__/skill-template.test.mjs +98 -50
  172. package/scripts/__tests__/smoke.test.mjs +1 -1
  173. package/scripts/__tests__/spawn-trace.test.mjs +102 -0
  174. package/scripts/__tests__/tfx-doctor-diagnose.test.mjs +48 -0
  175. package/scripts/cache-doctor.mjs +11 -4
  176. package/scripts/cache-warmup.mjs +96 -37
  177. package/scripts/claudemd-sync.mjs +27 -17
  178. package/scripts/codex-gateway-preflight.mjs +52 -37
  179. package/scripts/codex-mcp-gateway-sync.mjs +59 -39
  180. package/scripts/completions/tfx.bash +47 -47
  181. package/scripts/completions/tfx.fish +44 -44
  182. package/scripts/completions/tfx.zsh +83 -83
  183. package/scripts/config-audit.mjs +232 -0
  184. package/scripts/convert-to-tmpl.mjs +54 -0
  185. package/scripts/cross-review-gate.mjs +35 -12
  186. package/scripts/cross-review-tracker.mjs +21 -8
  187. package/scripts/demo.mjs +35 -17
  188. package/scripts/doctor-diagnose.mjs +284 -0
  189. package/scripts/gen-skill-docs.mjs +7 -2
  190. package/scripts/gen-skill-manifest.mjs +2 -1
  191. package/scripts/headless-guard.mjs +86 -48
  192. package/scripts/hub-ensure.mjs +45 -26
  193. package/scripts/keyword-detector.mjs +41 -20
  194. package/scripts/keyword-rules-expander.mjs +47 -30
  195. package/scripts/lib/claudemd-scanner.mjs +6 -1
  196. package/scripts/lib/context.mjs +3 -3
  197. package/scripts/lib/cross-review-utils.mjs +6 -3
  198. package/scripts/lib/env-probe.mjs +47 -28
  199. package/scripts/lib/gemini-profiles.mjs +44 -10
  200. package/scripts/lib/handoff.mjs +33 -17
  201. package/scripts/lib/hook-utils.mjs +8 -6
  202. package/scripts/lib/keyword-rules.mjs +43 -19
  203. package/scripts/lib/logger.mjs +24 -24
  204. package/scripts/lib/mcp-filter.mjs +377 -239
  205. package/scripts/lib/mcp-guard-engine.mjs +194 -79
  206. package/scripts/lib/mcp-manifest.mjs +23 -13
  207. package/scripts/lib/mcp-server-catalog.mjs +300 -63
  208. package/scripts/lib/psmux-info.mjs +11 -6
  209. package/scripts/lib/remote-spawn-transfer.mjs +44 -14
  210. package/scripts/lib/skill-template.mjs +30 -7
  211. package/scripts/mcp-check.mjs +58 -39
  212. package/scripts/mcp-gateway-config.mjs +83 -39
  213. package/scripts/mcp-gateway-ensure.mjs +43 -35
  214. package/scripts/mcp-gateway-integration-test.mjs +70 -58
  215. package/scripts/mcp-gateway-start.mjs +126 -60
  216. package/scripts/mcp-gateway-verify.mjs +24 -22
  217. package/scripts/mcp-safety-guard.mjs +44 -11
  218. package/scripts/notion-read.mjs +199 -84
  219. package/scripts/pack.mjs +94 -89
  220. package/scripts/preflight-cache.mjs +27 -10
  221. package/scripts/preinstall.mjs +42 -13
  222. package/scripts/remote-spawn.mjs +309 -94
  223. package/scripts/run.cjs +8 -5
  224. package/scripts/session-spawn-helper.mjs +130 -39
  225. package/scripts/session-stale-cleanup.mjs +123 -0
  226. package/scripts/setup.mjs +941 -492
  227. package/scripts/test-lock.mjs +20 -7
  228. package/scripts/test-tfx-route-no-claude-native.mjs +16 -12
  229. package/scripts/tfx-batch-stats.mjs +32 -11
  230. package/scripts/tfx-gate-activate.mjs +11 -4
  231. package/scripts/tfx-route-post.mjs +87 -20
  232. package/scripts/tfx-route-worker.mjs +57 -51
  233. package/scripts/tfx-route.sh +41 -124
  234. package/scripts/tmp-cleanup.mjs +21 -7
  235. package/scripts/token-snapshot.mjs +204 -85
  236. package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +1 -0
  237. package/skills/.omc/state/idle-notif-cooldown.json +3 -0
  238. package/skills/.omc/state/last-tool-error.json +7 -0
  239. package/skills/.omc/state/subagent-tracking.json +7 -0
  240. package/skills/_templates/base.md +1 -6
  241. package/skills/merge-worktree/SKILL.md.tmpl +144 -0
  242. package/skills/shared/telemetry-segment.md +6 -0
  243. package/skills/star-prompt/SKILL.md.tmpl +222 -0
  244. package/skills/tfx-analysis/SKILL.md.tmpl +107 -0
  245. package/skills/tfx-analysis/skill.json +1 -6
  246. package/skills/tfx-auto/SKILL.md +1 -0
  247. package/skills/tfx-auto-codex/SKILL.md.tmpl +106 -0
  248. package/skills/tfx-auto-codex/skill.json +1 -3
  249. package/skills/tfx-autopilot/SKILL.md.tmpl +116 -0
  250. package/skills/tfx-autopilot/skill.json +1 -5
  251. package/skills/tfx-autoresearch/SKILL.md.tmpl +136 -0
  252. package/skills/tfx-autoroute/SKILL.md.tmpl +189 -0
  253. package/skills/tfx-autoroute/skill.json +1 -7
  254. package/skills/tfx-codex/SKILL.md +1 -0
  255. package/skills/tfx-codex/skill.json +1 -3
  256. package/skills/tfx-codex-swarm/SKILL.md.tmpl +16 -0
  257. package/skills/tfx-codex-swarm/evals/evals.json +1 -1
  258. package/skills/tfx-codex-swarm/skill.json +1 -4
  259. package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +54 -12
  260. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +35 -7
  261. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +35 -7
  262. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +25 -5
  263. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +25 -5
  264. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +20 -4
  265. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +16 -4
  266. package/skills/tfx-consensus/SKILL.md.tmpl +146 -0
  267. package/skills/tfx-debate/SKILL.md.tmpl +192 -0
  268. package/skills/tfx-debate/skill.json +1 -7
  269. package/skills/tfx-deep-analysis/SKILL.md.tmpl +228 -0
  270. package/skills/tfx-deep-analysis/skill.json +1 -5
  271. package/skills/tfx-deep-interview/SKILL.md.tmpl +203 -0
  272. package/skills/tfx-deep-plan/SKILL.md.tmpl +282 -0
  273. package/skills/tfx-deep-qa/SKILL.md.tmpl +165 -0
  274. package/skills/tfx-deep-qa/skill.json +1 -6
  275. package/skills/tfx-deep-research/SKILL.md.tmpl +217 -0
  276. package/skills/tfx-deep-review/SKILL.md.tmpl +179 -0
  277. package/skills/tfx-doctor/SKILL.md +21 -0
  278. package/skills/tfx-doctor/SKILL.md.tmpl +172 -0
  279. package/skills/tfx-doctor/skill.json +1 -3
  280. package/skills/tfx-find/SKILL.md +1 -0
  281. package/skills/tfx-forge/SKILL.md.tmpl +187 -0
  282. package/skills/tfx-fullcycle/SKILL.md.tmpl +286 -0
  283. package/skills/tfx-fullcycle/skill.json +1 -6
  284. package/skills/tfx-gemini/SKILL.md.tmpl +91 -0
  285. package/skills/tfx-gemini/skill.json +1 -3
  286. package/skills/tfx-hooks/SKILL.md.tmpl +216 -0
  287. package/skills/tfx-hooks/skill.json +1 -3
  288. package/skills/tfx-hub/SKILL.md.tmpl +212 -0
  289. package/skills/tfx-hub/skill.json +1 -3
  290. package/skills/tfx-index/SKILL.md +1 -0
  291. package/skills/tfx-index/skill.json +1 -6
  292. package/skills/tfx-interview/SKILL.md.tmpl +285 -0
  293. package/skills/tfx-multi/SKILL.md.tmpl +183 -0
  294. package/skills/tfx-multi/skill.json +1 -3
  295. package/skills/tfx-panel/SKILL.md.tmpl +189 -0
  296. package/skills/tfx-panel/skill.json +1 -7
  297. package/skills/tfx-persist/SKILL.md.tmpl +270 -0
  298. package/skills/tfx-persist/skill.json +1 -7
  299. package/skills/tfx-plan/SKILL.md +1 -0
  300. package/skills/tfx-plan/skill.json +1 -6
  301. package/skills/tfx-profile/SKILL.md.tmpl +239 -0
  302. package/skills/tfx-profile/skill.json +1 -3
  303. package/skills/tfx-prune/SKILL.md.tmpl +200 -0
  304. package/skills/tfx-prune/skill.json +1 -7
  305. package/skills/tfx-psmux-rules/SKILL.md.tmpl +326 -0
  306. package/skills/tfx-psmux-rules/skill.json +1 -4
  307. package/skills/tfx-qa/SKILL.md +1 -0
  308. package/skills/tfx-qa/skill.json +1 -6
  309. package/skills/tfx-ralph/SKILL.md.tmpl +28 -0
  310. package/skills/tfx-ralph/skill.json +1 -4
  311. package/skills/tfx-remote-setup/SKILL.md.tmpl +576 -0
  312. package/skills/tfx-remote-setup/skill.json +1 -3
  313. package/skills/tfx-remote-spawn/SKILL.md.tmpl +263 -0
  314. package/skills/tfx-remote-spawn/references/hosts.json +16 -0
  315. package/skills/tfx-remote-spawn/skill.json +1 -4
  316. package/skills/tfx-research/SKILL.md +1 -0
  317. package/skills/tfx-review/SKILL.md +1 -0
  318. package/skills/tfx-review/skill.json +1 -6
  319. package/skills/tfx-setup/SKILL.md.tmpl +504 -0
  320. package/skills/tfx-setup/skill.json +1 -3
  321. package/skills/tfx-swarm/SKILL.md +22 -0
  322. package/skills/tfx-swarm/SKILL.md.tmpl +218 -0
  323. package/tui/codex-profile.mjs +88 -33
  324. package/tui/core.mjs +45 -15
  325. package/tui/doctor.mjs +75 -28
  326. package/tui/gemini-profile.mjs +74 -29
  327. package/tui/monitor-data.mjs +8 -4
  328. package/tui/monitor.mjs +71 -27
  329. package/tui/setup.mjs +133 -42
@@ -1,10 +1,10 @@
1
1
  // hub/cli-adapter-base.mjs — codex/gemini 공통 CLI adapter 인터페이스
2
2
  // Phase 2: codex-adapter.mjs에서 추출한 재사용 가능 유틸리티
3
3
 
4
- import { execSync, spawn } from 'node:child_process';
5
- import { existsSync, readFileSync } from 'node:fs';
4
+ import { execSync, spawn } from "node:child_process";
5
+ import { existsSync, readFileSync } from "node:fs";
6
6
 
7
- import { killProcess, IS_WINDOWS } from './platform.mjs';
7
+ import { IS_WINDOWS, killProcess } from "./platform.mjs";
8
8
 
9
9
  // ── Codex CLI compatibility ─────────────────────────────────────
10
10
 
@@ -18,7 +18,10 @@ let _cachedVersion = null;
18
18
  export function getCodexVersion() {
19
19
  if (_cachedVersion !== null) return _cachedVersion;
20
20
  try {
21
- const out = execSync('codex --version', { encoding: 'utf8', timeout: 5000 }).trim();
21
+ const out = execSync("codex --version", {
22
+ encoding: "utf8",
23
+ timeout: 5000,
24
+ }).trim();
22
25
  const match = out.match(/(\d+)\.(\d+)\.(\d+)/);
23
26
  _cachedVersion = match ? Number.parseInt(match[2], 10) : 0;
24
27
  } catch {
@@ -43,19 +46,27 @@ export function gte(minMinor) {
43
46
  */
44
47
  export const FEATURES = {
45
48
  /** exec 서브커맨드 사용 가능 여부 (0.110+ 이전부터 존재) */
46
- get execSubcommand() { return gte(110); },
49
+ get execSubcommand() {
50
+ return gte(110);
51
+ },
47
52
  /** --output-last-message 플래그 지원 여부 (0.117+) */
48
- get outputLastMessage() { return gte(117); },
53
+ get outputLastMessage() {
54
+ return gte(117);
55
+ },
49
56
  /** --color <COLOR> 플래그 지원 여부 (exec와 동시 도입) */
50
- get colorNever() { return gte(110); },
57
+ get colorNever() {
58
+ return gte(110);
59
+ },
51
60
  /** 플러그인 시스템 지원 여부 (향후 확장용) */
52
- get pluginSystem() { return gte(120); },
61
+ get pluginSystem() {
62
+ return gte(120);
63
+ },
53
64
  };
54
65
 
55
66
  // ── Shell utilities ─────────────────────────────────────────────
56
67
 
57
68
  export function normalizePathForShell(value) {
58
- return IS_WINDOWS ? String(value).replace(/\\/g, '/') : String(value);
69
+ return IS_WINDOWS ? String(value).replace(/\\/g, "/") : String(value);
59
70
  }
60
71
 
61
72
  export function shellQuote(value) {
@@ -77,32 +88,38 @@ export const CODEX_MCP_EXECUTION_EXIT_CODE = 1;
77
88
  * @returns {string} 실행할 셸 커맨드
78
89
  */
79
90
  export function buildExecCommand(prompt, resultFile = null, opts = {}) {
80
- const { profile, skipGitRepoCheck = true, sandboxBypass = true, cwd, mcpServers } = opts;
91
+ const {
92
+ profile,
93
+ skipGitRepoCheck = true,
94
+ sandboxBypass = true,
95
+ cwd,
96
+ mcpServers,
97
+ } = opts;
81
98
 
82
- const parts = ['codex'];
83
- if (profile) parts.push('--profile', profile);
99
+ const parts = ["codex"];
100
+ if (profile) parts.push("--profile", profile);
84
101
 
85
102
  if (FEATURES.execSubcommand) {
86
- parts.push('exec');
87
- if (sandboxBypass) parts.push('--dangerously-bypass-approvals-and-sandbox');
88
- if (skipGitRepoCheck) parts.push('--skip-git-repo-check');
103
+ parts.push("exec");
104
+ if (sandboxBypass) parts.push("--dangerously-bypass-approvals-and-sandbox");
105
+ if (skipGitRepoCheck) parts.push("--skip-git-repo-check");
89
106
  if (resultFile && FEATURES.outputLastMessage) {
90
- parts.push('--output-last-message', resultFile);
107
+ parts.push("--output-last-message", resultFile);
91
108
  }
92
- if (FEATURES.colorNever) parts.push('--color', 'never');
93
- if (cwd) parts.push('--cwd', `'${escapePwshSingleQuoted(cwd)}'`);
109
+ if (FEATURES.colorNever) parts.push("--color", "never");
110
+ if (cwd) parts.push("--cwd", `'${escapePwshSingleQuoted(cwd)}'`);
94
111
  if (Array.isArray(mcpServers)) {
95
112
  for (const server of mcpServers) {
96
- parts.push('-c', `mcp_servers.${server}.enabled=true`);
113
+ parts.push("-c", `mcp_servers.${server}.enabled=true`);
97
114
  }
98
115
  }
99
116
  } else {
100
- parts.push('--dangerously-bypass-approvals-and-sandbox');
101
- if (skipGitRepoCheck) parts.push('--skip-git-repo-check');
117
+ parts.push("--dangerously-bypass-approvals-and-sandbox");
118
+ if (skipGitRepoCheck) parts.push("--skip-git-repo-check");
102
119
  }
103
120
 
104
121
  parts.push(JSON.stringify(prompt));
105
- return parts.join(' ');
122
+ return parts.join(" ");
106
123
  }
107
124
 
108
125
  // ── Sleep ───────────────────────────────────────────────────────
@@ -119,24 +136,28 @@ export function sleep(ms) {
119
136
  export function createResult(ok, extra = {}) {
120
137
  return {
121
138
  ok,
122
- output: '',
123
- stderr: '',
139
+ output: "",
140
+ stderr: "",
124
141
  exitCode: null,
125
142
  duration: 0,
126
143
  retried: false,
127
144
  fellBack: false,
128
- failureMode: ok ? null : 'crash',
145
+ failureMode: ok ? null : "crash",
129
146
  ...extra,
130
147
  };
131
148
  }
132
149
 
133
150
  export function appendWarnings(stderr, warnings = []) {
134
- const text = warnings.map((item) => `[preflight] ${item}`).join('\n');
135
- return [stderr, text].filter(Boolean).join('\n');
151
+ const text = warnings.map((item) => `[preflight] ${item}`).join("\n");
152
+ return [stderr, text].filter(Boolean).join("\n");
136
153
  }
137
154
 
138
155
  // ── Circuit breaker factory ─────────────────────────────────────
139
156
 
157
+ /**
158
+ * @deprecated Use per-account circuit breakers in account-broker.mjs instead.
159
+ * Kept for backward compatibility with code that hasn't migrated yet.
160
+ */
140
161
  export function createCircuitBreaker(opts = {}) {
141
162
  const state = {
142
163
  failures: [],
@@ -147,7 +168,9 @@ export function createCircuitBreaker(opts = {}) {
147
168
  };
148
169
 
149
170
  function pruneFailures(now = Date.now()) {
150
- state.failures = state.failures.filter((stamp) => now - stamp < state.windowMs);
171
+ state.failures = state.failures.filter(
172
+ (stamp) => now - stamp < state.windowMs,
173
+ );
151
174
  }
152
175
 
153
176
  function reset() {
@@ -167,8 +190,13 @@ export function createCircuitBreaker(opts = {}) {
167
190
 
168
191
  function getState(now = Date.now()) {
169
192
  pruneFailures(now);
170
- const withinWindow = state.openedAt && now - state.openedAt < state.windowMs;
171
- const current = withinWindow ? 'open' : (state.openedAt ? 'half-open' : 'closed');
193
+ const withinWindow =
194
+ state.openedAt && now - state.openedAt < state.windowMs;
195
+ const current = withinWindow
196
+ ? "open"
197
+ : state.openedAt
198
+ ? "half-open"
199
+ : "closed";
172
200
  return {
173
201
  state: current,
174
202
  failures: [...state.failures],
@@ -181,9 +209,10 @@ export function createCircuitBreaker(opts = {}) {
181
209
 
182
210
  function canExecute() {
183
211
  const circuit = getState();
184
- if (circuit.state === 'open') return { allowed: false, halfOpen: false };
185
- if (circuit.state === 'half-open' && state.trialInFlight) return { allowed: false, halfOpen: true };
186
- const halfOpen = circuit.state === 'half-open';
212
+ if (circuit.state === "open") return { allowed: false, halfOpen: false };
213
+ if (circuit.state === "half-open" && state.trialInFlight)
214
+ return { allowed: false, halfOpen: true };
215
+ const halfOpen = circuit.state === "half-open";
187
216
  if (halfOpen) state.trialInFlight = true;
188
217
  return { allowed: true, halfOpen };
189
218
  }
@@ -195,14 +224,112 @@ export function createCircuitBreaker(opts = {}) {
195
224
  return { getState, recordFailure, reset, canExecute, clearTrial };
196
225
  }
197
226
 
227
+ // ── Broker-integrated execution ─────────────────────────────────
228
+
229
+ /**
230
+ * Shared execute() logic that uses account-broker for per-account circuit
231
+ * breaking instead of a global breaker.
232
+ *
233
+ * @param {object} params
234
+ * @param {string} params.provider — 'codex' | 'gemini'
235
+ * @param {(prompt: string, workdir: string, preflight: object, attempt: object) => Promise<object>} params.runFn
236
+ * @param {(opts: object) => Promise<object>} params.preflightFn
237
+ * @param {(opts: object, preflight: object) => object[]} params.buildAttemptsFn
238
+ * @param {object} params.opts — caller-supplied execute options
239
+ * @returns {Promise<object>} createResult-shaped result
240
+ */
241
+ export async function executeWithCircuitBroker({
242
+ provider,
243
+ runFn,
244
+ preflightFn,
245
+ buildAttemptsFn,
246
+ opts = {},
247
+ }) {
248
+ // late-import to avoid circular dependency at module load time
249
+ const brokerMod = await import("./account-broker.mjs");
250
+ const { withRetry } = await import("./workers/worker-utils.mjs");
251
+
252
+ // access broker as live binding property (not destructured) so reloadBroker() propagates
253
+ const lease = brokerMod.broker?.lease({ provider });
254
+ if (!lease) {
255
+ return createResult(false, { fellBack: true, failureMode: "circuit_open" });
256
+ }
257
+
258
+ const preflight = await preflightFn(opts);
259
+ if (!preflight.ok) {
260
+ brokerMod.broker.release(lease.id, { ok: false });
261
+ return createResult(false, {
262
+ stderr: appendWarnings("", preflight.warnings),
263
+ fellBack: opts.fallbackToClaude !== false,
264
+ failureMode: "crash",
265
+ });
266
+ }
267
+
268
+ const attempts = buildAttemptsFn(opts, preflight);
269
+ let attemptIndex = 0;
270
+ let lastResult = createResult(false);
271
+
272
+ try {
273
+ lastResult = await withRetry(
274
+ async () => {
275
+ const result = await runFn(
276
+ opts.prompt || "",
277
+ opts.workdir || process.cwd(),
278
+ preflight,
279
+ attempts[attemptIndex],
280
+ );
281
+ const current = {
282
+ ...result,
283
+ stderr: appendWarnings(result.stderr, preflight.warnings),
284
+ retried: attemptIndex > 0,
285
+ };
286
+ const canRetry = !current.ok && attemptIndex < attempts.length - 1;
287
+ attemptIndex += 1;
288
+ if (!canRetry) return current;
289
+ const error = new Error("retry");
290
+ error.retryable = true;
291
+ error.result = current;
292
+ throw error;
293
+ },
294
+ {
295
+ maxAttempts: attempts.length,
296
+ baseDelayMs: 250,
297
+ maxDelayMs: 750,
298
+ shouldRetry: (error) => error?.retryable === true,
299
+ },
300
+ );
301
+ } catch (error) {
302
+ lastResult =
303
+ error?.result ||
304
+ createResult(false, { stderr: String(error?.message || error) });
305
+ }
306
+
307
+ if (lastResult.ok) {
308
+ brokerMod.broker.release(lease.id, { ok: true });
309
+ return lastResult;
310
+ }
311
+
312
+ brokerMod.broker.release(lease.id, { ok: false });
313
+ return {
314
+ ...lastResult,
315
+ retried: attempts.length > 1,
316
+ fellBack: opts.fallbackToClaude !== false,
317
+ };
318
+ }
319
+
198
320
  // ── Process termination ─────────────────────────────────────────
199
321
 
200
322
  export async function terminateChild(pid, opts = {}) {
201
323
  if (!pid) return;
202
324
  const graceMs = opts.graceMs ?? 5000;
203
- killProcess(pid, { signal: 'SIGTERM', tree: true, timeout: graceMs });
325
+ killProcess(pid, { signal: "SIGTERM", tree: true, timeout: graceMs });
204
326
  await sleep(graceMs);
205
- killProcess(pid, { signal: 'SIGKILL', tree: true, force: true, timeout: graceMs });
327
+ killProcess(pid, {
328
+ signal: "SIGKILL",
329
+ tree: true,
330
+ force: true,
331
+ timeout: graceMs,
332
+ });
206
333
  }
207
334
 
208
335
  // ── Process execution with stall detection ──────────────────────
@@ -222,13 +349,13 @@ export async function terminateChild(pid, opts = {}) {
222
349
  */
223
350
  export async function runProcess(command, workdir, timeout, opts = {}) {
224
351
  const startedAt = Date.now();
225
- const inferStallMode = opts.inferStallMode || (() => 'timeout');
352
+ const inferStallMode = opts.inferStallMode || (() => "timeout");
226
353
  const stallCheckIntervalMs = opts.stallCheckIntervalMs ?? 10_000;
227
354
  const stallThresholdMs = opts.stallThresholdMs ?? 30_000;
228
355
  const resultFile = opts.resultFile || null;
229
356
 
230
- let stdout = '';
231
- let stderr = '';
357
+ let stdout = "";
358
+ let stderr = "";
232
359
  let exitCode = null;
233
360
  let failureMode = null;
234
361
  let child;
@@ -236,15 +363,29 @@ export async function runProcess(command, workdir, timeout, opts = {}) {
236
363
  try {
237
364
  child = spawn(command, { cwd: workdir, shell: true, windowsHide: true });
238
365
  } catch (error) {
239
- return createResult(false, { stderr: String(error?.message || error), duration: Date.now() - startedAt });
366
+ return createResult(false, {
367
+ stderr: String(error?.message || error),
368
+ duration: Date.now() - startedAt,
369
+ });
240
370
  }
241
371
 
242
372
  let lastBytes = 0;
243
373
  let lastChange = Date.now();
244
- const touch = () => { lastChange = Date.now(); };
245
- child.stdout?.on('data', (chunk) => { stdout += String(chunk); touch(); });
246
- child.stderr?.on('data', (chunk) => { stderr += String(chunk); touch(); });
247
- child.on('error', (error) => { stderr += String(error?.message || error); failureMode ||= 'crash'; });
374
+ const touch = () => {
375
+ lastChange = Date.now();
376
+ };
377
+ child.stdout?.on("data", (chunk) => {
378
+ stdout += String(chunk);
379
+ touch();
380
+ });
381
+ child.stderr?.on("data", (chunk) => {
382
+ stderr += String(chunk);
383
+ touch();
384
+ });
385
+ child.on("error", (error) => {
386
+ stderr += String(error?.message || error);
387
+ failureMode ||= "crash";
388
+ });
248
389
 
249
390
  const stopFor = async (mode) => {
250
391
  if (failureMode) return;
@@ -252,23 +393,34 @@ export async function runProcess(command, workdir, timeout, opts = {}) {
252
393
  await terminateChild(child.pid);
253
394
  };
254
395
 
255
- const timeoutTimer = setTimeout(() => { void stopFor('timeout'); }, timeout);
396
+ const timeoutTimer = setTimeout(() => {
397
+ void stopFor("timeout");
398
+ }, timeout);
256
399
  const stallTimer = setInterval(() => {
257
400
  const size = Buffer.byteLength(stdout) + Buffer.byteLength(stderr);
258
401
  if (size !== lastBytes) {
259
402
  lastBytes = size;
260
403
  return;
261
404
  }
262
- if (Date.now() - lastChange >= stallThresholdMs) void stopFor(inferStallMode(stdout, stderr));
405
+ if (Date.now() - lastChange >= stallThresholdMs)
406
+ void stopFor(inferStallMode(stdout, stderr));
263
407
  }, stallCheckIntervalMs);
264
408
  timeoutTimer.unref?.();
265
409
  stallTimer.unref?.();
266
410
 
267
- await new Promise((resolve) => child.on('close', (code) => { exitCode = code; resolve(); }));
411
+ await new Promise((resolve) =>
412
+ child.on("close", (code) => {
413
+ exitCode = code;
414
+ resolve();
415
+ }),
416
+ );
268
417
  clearTimeout(timeoutTimer);
269
418
  clearInterval(stallTimer);
270
419
 
271
- const fileOutput = resultFile && existsSync(resultFile) ? readFileSync(resultFile, 'utf8') : '';
420
+ const fileOutput =
421
+ resultFile && existsSync(resultFile)
422
+ ? readFileSync(resultFile, "utf8")
423
+ : "";
272
424
  const output = fileOutput || stdout;
273
425
  const ok = failureMode == null && exitCode === 0;
274
426
  return createResult(ok, {
@@ -276,6 +428,6 @@ export async function runProcess(command, workdir, timeout, opts = {}) {
276
428
  stderr,
277
429
  exitCode,
278
430
  duration: Date.now() - startedAt,
279
- failureMode: ok ? null : (failureMode || 'crash'),
431
+ failureMode: ok ? null : failureMode || "crash",
280
432
  });
281
433
  }
@@ -1,46 +1,54 @@
1
- import { writeFileSync, mkdirSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { tmpdir } from 'node:os';
4
-
5
- import { runPreflight } from './codex-preflight.mjs';
6
- import { withRetry } from './workers/worker-utils.mjs';
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
7
4
  import {
8
- createCircuitBreaker,
9
- createResult,
10
- appendWarnings,
11
5
  buildExecCommand,
6
+ executeWithCircuitBroker,
12
7
  normalizePathForShell,
13
- shellQuote,
14
8
  runProcess,
15
- } from './cli-adapter-base.mjs';
16
-
17
- const breaker = createCircuitBreaker();
9
+ shellQuote,
10
+ } from "./cli-adapter-base.mjs";
11
+ import { runPreflight } from "./codex-preflight.mjs";
18
12
 
19
13
  // ── Codex-specific stall inference ──────────────────────────────
20
14
 
21
15
  function inferStallMode(stdout, stderr) {
22
16
  const text = `${stdout}\n${stderr}`.toLowerCase();
23
- if (/(rate.?limit|quota|throttl|too.many.requests|429|usage.limit)/u.test(text)) return 'rate_limited';
24
- if (/(approval|approve|permission|sandbox|bypass)/u.test(text)) return 'approval_stall';
25
- if (/\bmcp\b|context7|playwright|tavily|exa|brave|sequential|server/u.test(text)) return 'mcp_stall';
26
- return 'timeout';
17
+ if (
18
+ /(rate.?limit|quota|throttl|too.many.requests|429|usage.limit)/u.test(text)
19
+ )
20
+ return "rate_limited";
21
+ if (/(approval|approve|permission|sandbox|bypass)/u.test(text))
22
+ return "approval_stall";
23
+ if (
24
+ /\bmcp\b|context7|playwright|tavily|exa|brave|sequential|server/u.test(text)
25
+ )
26
+ return "mcp_stall";
27
+ return "timeout";
27
28
  }
28
29
 
29
30
  // ── Codex command building ──────────────────────────────────────
30
31
 
31
32
  function commandWithOverrides(command, prompt, codexPath, overrides = []) {
32
- const next = codexPath ? command.replace(/^codex\b/u, shellQuote(codexPath)) : command;
33
+ const next = codexPath
34
+ ? command.replace(/^codex\b/u, shellQuote(codexPath))
35
+ : command;
33
36
  if (!overrides.length) return next;
34
37
  const promptArg = JSON.stringify(prompt);
35
- const flags = overrides.flatMap((value) => ['-c', shellQuote(value)]).join(' ');
38
+ const flags = overrides
39
+ .flatMap((value) => ["-c", shellQuote(value)])
40
+ .join(" ");
36
41
  return next.endsWith(promptArg)
37
42
  ? `${next.slice(0, -promptArg.length)}${flags} ${promptArg}`
38
43
  : `${next} ${flags}`;
39
44
  }
40
45
 
41
46
  function buildOverrides(requested, excluded) {
42
- return [...new Set((requested || []).filter((name) => (excluded || []).includes(name)))]
43
- .map((name) => `mcp_servers.${name}.enabled=false`);
47
+ return [
48
+ ...new Set(
49
+ (requested || []).filter((name) => (excluded || []).includes(name)),
50
+ ),
51
+ ].map((name) => `mcp_servers.${name}.enabled=false`);
44
52
  }
45
53
 
46
54
  function buildAttempts(opts, preflight) {
@@ -63,37 +71,37 @@ function buildAttempts(opts, preflight) {
63
71
  // ── Launch script ───────────────────────────────────────────────
64
72
 
65
73
  function createLaunchScriptText(opts) {
66
- const parts = ['codex'];
67
- if (opts.profile) parts.push('--profile', shellQuote(opts.profile));
74
+ const parts = ["codex"];
75
+ if (opts.profile) parts.push("--profile", shellQuote(opts.profile));
68
76
  parts.push(
69
- 'exec',
70
- '--dangerously-bypass-approvals-and-sandbox',
71
- '--skip-git-repo-check',
77
+ "exec",
78
+ "--dangerously-bypass-approvals-and-sandbox",
79
+ "--skip-git-repo-check",
72
80
  '$(cat "$PROMPT_FILE")',
73
81
  );
74
82
  return [
75
- '#!/usr/bin/env bash',
76
- 'set -euo pipefail',
83
+ "#!/usr/bin/env bash",
84
+ "set -euo pipefail",
77
85
  `cd ${shellQuote(normalizePathForShell(opts.workdir))}`,
78
86
  `PROMPT_FILE=${shellQuote(normalizePathForShell(opts.promptFile))}`,
79
- `TFX_CODEX_TIMEOUT_MS=${shellQuote(String(opts.timeout ?? ''))}`,
80
- parts.join(' '),
81
- '',
82
- ].join('\n');
87
+ `TFX_CODEX_TIMEOUT_MS=${shellQuote(String(opts.timeout ?? ""))}`,
88
+ parts.join(" "),
89
+ "",
90
+ ].join("\n");
83
91
  }
84
92
 
85
93
  export function buildLaunchScript(opts = {}) {
86
- const dir = join(tmpdir(), 'triflux-codex-launch');
94
+ const dir = join(tmpdir(), "triflux-codex-launch");
87
95
  mkdirSync(dir, { recursive: true });
88
- const path = join(dir, `${String(opts.id || 'launch')}.sh`);
89
- writeFileSync(path, createLaunchScriptText(opts), 'utf8');
96
+ const path = join(dir, `${String(opts.id || "launch")}.sh`);
97
+ writeFileSync(path, createLaunchScriptText(opts), "utf8");
90
98
  return path;
91
99
  }
92
100
 
93
101
  // ── Exec args builder ───────────────────────────────────────────
94
102
 
95
103
  export function buildExecArgs(opts = {}) {
96
- const prompt = typeof opts.prompt === 'string' ? opts.prompt : '';
104
+ const prompt = typeof opts.prompt === "string" ? opts.prompt : "";
97
105
  const command = buildExecCommand(prompt, opts.resultFile || null, {
98
106
  profile: opts.profile,
99
107
  skipGitRepoCheck: true,
@@ -101,11 +109,14 @@ export function buildExecArgs(opts = {}) {
101
109
  cwd: opts.cwd,
102
110
  });
103
111
 
104
- if (!prompt) return command.replace(/\s+""$/u, '');
112
+ if (!prompt) return command.replace(/\s+""$/u, "");
105
113
 
106
114
  let result;
107
115
  const quotedPrompt = JSON.stringify(prompt);
108
- if (/^\(Get-Content\b[\s\S]*\)$/u.test(prompt) && command.endsWith(quotedPrompt)) {
116
+ if (
117
+ /^\(Get-Content\b[\s\S]*\)$/u.test(prompt) &&
118
+ command.endsWith(quotedPrompt)
119
+ ) {
109
120
  result = `${command.slice(0, -quotedPrompt.length)}${prompt}`;
110
121
  } else {
111
122
  result = command;
@@ -121,9 +132,12 @@ export function buildExecArgs(opts = {}) {
121
132
  // ── Codex execution ─────────────────────────────────────────────
122
133
 
123
134
  async function runCodex(prompt, workdir, preflight, attempt) {
124
- const dir = join(tmpdir(), 'triflux-codex-exec');
135
+ const dir = join(tmpdir(), "triflux-codex-exec");
125
136
  mkdirSync(dir, { recursive: true });
126
- const resultFile = join(dir, `codex-${Date.now()}-${Math.random().toString(36).slice(2)}.txt`);
137
+ const resultFile = join(
138
+ dir,
139
+ `codex-${Date.now()}-${Math.random().toString(36).slice(2)}.txt`,
140
+ );
127
141
  const command = commandWithOverrides(
128
142
  buildExecCommand(prompt, resultFile, {
129
143
  profile: attempt.profile,
@@ -134,66 +148,32 @@ async function runCodex(prompt, workdir, preflight, attempt) {
134
148
  preflight.codexPath,
135
149
  buildOverrides(attempt.requested, attempt.excluded),
136
150
  );
137
- return runProcess(command, workdir, attempt.timeout, { resultFile, inferStallMode });
151
+ return runProcess(command, workdir, attempt.timeout, {
152
+ resultFile,
153
+ inferStallMode,
154
+ });
138
155
  }
139
156
 
140
157
  // ── Public API ──────────────────────────────────────────────────
141
158
 
142
- export function getCircuitState(now) {
143
- return breaker.getState(now);
159
+ export async function getCircuitState() {
160
+ const brokerMod = await import("./account-broker.mjs");
161
+ if (!brokerMod.broker) return { state: "closed", failures: [] };
162
+ const snap = brokerMod.broker
163
+ .snapshot()
164
+ .filter((a) => a.provider === "codex");
165
+ return snap.length
166
+ ? { state: snap[0].circuitState, accounts: snap }
167
+ : { state: "closed", failures: [] };
144
168
  }
145
169
 
146
- export async function execute(opts = {}) {
147
- const entry = breaker.canExecute();
148
- if (!entry.allowed) {
149
- return createResult(false, { fellBack: true, failureMode: 'circuit_open' });
150
- }
151
-
152
- const preflight = await runPreflight({ mcpServers: opts.mcpServers, subcommand: 'exec' });
153
- if (!preflight.ok) {
154
- breaker.clearTrial();
155
- breaker.recordFailure(entry.halfOpen);
156
- return createResult(false, {
157
- stderr: appendWarnings('', preflight.warnings),
158
- fellBack: opts.fallbackToClaude !== false,
159
- failureMode: 'crash',
160
- });
161
- }
162
-
163
- const attempts = buildAttempts(opts, preflight);
164
- let attemptIndex = 0;
165
- let lastResult = createResult(false);
166
-
167
- try {
168
- lastResult = await withRetry(async () => {
169
- const result = await runCodex(opts.prompt || '', opts.workdir || process.cwd(), preflight, attempts[attemptIndex]);
170
- const current = { ...result, stderr: appendWarnings(result.stderr, preflight.warnings), retried: attemptIndex > 0 };
171
- const canRetry = !current.ok && attemptIndex < attempts.length - 1;
172
- attemptIndex += 1;
173
- if (!canRetry) return current;
174
- const error = new Error('retry');
175
- error.retryable = true;
176
- error.result = current;
177
- throw error;
178
- }, {
179
- maxAttempts: attempts.length,
180
- baseDelayMs: 250,
181
- maxDelayMs: 750,
182
- shouldRetry: (error) => error?.retryable === true,
183
- });
184
- } catch (error) {
185
- lastResult = error?.result || createResult(false, { stderr: String(error?.message || error) });
186
- }
187
-
188
- if (lastResult.ok) {
189
- breaker.reset();
190
- return lastResult;
191
- }
192
-
193
- breaker.recordFailure(entry.halfOpen);
194
- return {
195
- ...lastResult,
196
- retried: attempts.length > 1,
197
- fellBack: opts.fallbackToClaude !== false,
198
- };
170
+ export function execute(opts = {}) {
171
+ return executeWithCircuitBroker({
172
+ provider: "codex",
173
+ runFn: runCodex,
174
+ preflightFn: (o) =>
175
+ runPreflight({ mcpServers: o.mcpServers, subcommand: "exec" }),
176
+ buildAttemptsFn: buildAttempts,
177
+ opts,
178
+ });
199
179
  }
@@ -2,11 +2,11 @@
2
2
 
3
3
  /** @experimental 런타임 미연결 — 향후 통합 예정 */
4
4
  export {
5
+ buildExecCommand,
5
6
  CODEX_MCP_EXECUTION_EXIT_CODE,
6
7
  CODEX_MCP_TRANSPORT_EXIT_CODE,
7
- FEATURES,
8
- buildExecCommand,
9
8
  escapePwshSingleQuoted,
9
+ FEATURES,
10
10
  getCodexVersion,
11
11
  gte,
12
- } from './cli-adapter-base.mjs';
12
+ } from "./cli-adapter-base.mjs";