triflux 10.50.2 → 10.52.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 (263) hide show
  1. package/CLAUDE.md +57 -41
  2. package/README.ko.md +41 -45
  3. package/README.md +41 -45
  4. package/adapters/codex/skills/tfx-live/SKILL.md +25 -11
  5. package/bin/tfx-live.mjs +341 -104
  6. package/bin/triflux.mjs +147 -2624
  7. package/config/mcp-registry.json +3 -12
  8. package/cto/brief.mjs +0 -20
  9. package/cto/collect.mjs +1 -200
  10. package/cto/current.schema.json +2 -23
  11. package/cto/hygiene.mjs +2 -61
  12. package/cto/lake-root.mjs +0 -56
  13. package/cto/status.mjs +5 -187
  14. package/hooks/agy-session-hook.mjs +11 -56
  15. package/hooks/codex-session-hook.mjs +11 -101
  16. package/hub/bridge.mjs +65 -1250
  17. package/hub/cli-adapter-base.mjs +12 -90
  18. package/hub/codex-adapter.mjs +0 -1
  19. package/hub/codex-preflight.mjs +5 -28
  20. package/hub/lib/process-utils.mjs +1 -156
  21. package/hub/lib/spawn-trace.mjs +3 -2
  22. package/hub/lib/timeout-defaults.mjs +0 -1
  23. package/hub/team/agent-map.json +0 -1
  24. package/hub/team/agents-row-attach.py +120 -0
  25. package/hub/team/agents-row.mjs +200 -0
  26. package/hub/team/ansi.mjs +1 -35
  27. package/hub/team/backend.mjs +9 -145
  28. package/hub/team/claude-daemon-control.mjs +1 -2
  29. package/hub/team/claude-session-projection.mjs +5 -73
  30. package/hub/team/cli/commands/attach.mjs +1 -17
  31. package/hub/team/cli/commands/control.mjs +12 -38
  32. package/hub/team/cli/commands/debug.mjs +2 -39
  33. package/hub/team/cli/commands/focus.mjs +5 -31
  34. package/hub/team/cli/commands/interrupt.mjs +6 -25
  35. package/hub/team/cli/commands/kill.mjs +1 -30
  36. package/hub/team/cli/commands/list.mjs +0 -16
  37. package/hub/team/cli/commands/send.mjs +6 -26
  38. package/hub/team/cli/commands/start/index.mjs +42 -133
  39. package/hub/team/cli/commands/start/parse-args.mjs +15 -40
  40. package/hub/team/cli/commands/start/start-headless.mjs +138 -105
  41. package/hub/team/cli/commands/start/start-mux.mjs +2 -7
  42. package/hub/team/cli/commands/status.mjs +2 -57
  43. package/hub/team/cli/commands/stop.mjs +14 -19
  44. package/hub/team/cli/help.mjs +4 -5
  45. package/hub/team/cli/manifest.mjs +5 -1
  46. package/hub/team/cli/render.mjs +0 -12
  47. package/hub/team/cli/services/runtime-mode.mjs +27 -98
  48. package/hub/team/cli/services/state-store.mjs +5 -20
  49. package/hub/team/codex-queue.mjs +385 -0
  50. package/hub/team/handoff.mjs +3 -34
  51. package/hub/team/headless.mjs +108 -1296
  52. package/hub/team/orchestrator.mjs +4 -30
  53. package/hub/team/pane.mjs +6 -28
  54. package/hub/team/psmux.mjs +3 -34
  55. package/hub/team/remote-session.mjs +1 -2
  56. package/hub/team/session-context.mjs +1 -1
  57. package/hub/team/session.mjs +7 -91
  58. package/hub/team/tui-core.mjs +1 -1
  59. package/hub/team/tui-viewer.mjs +11 -58
  60. package/hub/team/tui.mjs +8 -306
  61. package/hub/team/uds-orchestrator.mjs +2 -580
  62. package/hub/team/wt-manager.mjs +2 -2
  63. package/hub/workers/factory.mjs +6 -84
  64. package/hub/workers/interface.mjs +1 -7
  65. package/hub/workers/lib/jsonrpc-core.mjs +11 -0
  66. package/hub/workers/lib/jsonrpc-ws-uds.mjs +5 -8
  67. package/hud/cli-policy.mjs +0 -22
  68. package/hud/constants.mjs +13 -26
  69. package/hud/hud-qos-status.mjs +42 -54
  70. package/hud/providers/gemini.mjs +125 -20
  71. package/hud/renderers.mjs +101 -122
  72. package/hud/utils.mjs +10 -6
  73. package/package.json +7 -12
  74. package/scripts/__tests__/mcp-guard-engine-http-headers.test.mjs +6 -6
  75. package/scripts/__tests__/mcp-guard-engine-policy-sync.test.mjs +1 -4
  76. package/scripts/__tests__/mcp-guard-engine-proactive-sync.test.mjs +16 -21
  77. package/scripts/__tests__/mcp-guard-engine-stdio-sync.test.mjs +1 -1
  78. package/scripts/__tests__/mcp-guard-engine-sync-http-headers.test.mjs +2 -2
  79. package/scripts/__tests__/mcp-guard-engine.test.mjs +56 -63
  80. package/scripts/__tests__/spawn-trace.test.mjs +16 -0
  81. package/scripts/check-codex-config-stable.mjs +4 -37
  82. package/scripts/claude-login-detect.mjs +0 -1
  83. package/scripts/claudemd-sync.mjs +1 -28
  84. package/scripts/lib/agent-route-policy.mjs +143 -62
  85. package/scripts/lib/cli-claude.mjs +0 -14
  86. package/scripts/lib/env-probe.mjs +2 -131
  87. package/scripts/lib/gemini-profiles.mjs +2 -69
  88. package/scripts/lib/legacy-mcp-cleanup.mjs +283 -0
  89. package/scripts/lib/machine-profile.mjs +3 -17
  90. package/scripts/lib/mcp-filter.mjs +109 -53
  91. package/scripts/lib/mcp-guard-engine.mjs +40 -98
  92. package/scripts/lib/mcp-health.mjs +2 -2
  93. package/scripts/lib/mcp-server-catalog.mjs +2 -2
  94. package/scripts/lib/psmux-info.mjs +11 -59
  95. package/scripts/lib/session-presence.mjs +2 -100
  96. package/scripts/lib/skill-template.mjs +0 -245
  97. package/scripts/pack.mjs +8 -83
  98. package/scripts/preflight-cache.mjs +5 -18
  99. package/scripts/release/check-packages-mirror.mjs +2 -8
  100. package/scripts/release/version-manifest.json +9 -1
  101. package/scripts/setup.mjs +100 -417
  102. package/scripts/test-lock.mjs +0 -1
  103. package/scripts/tfx-route-post.mjs +5 -69
  104. package/scripts/tfx-route-worker.mjs +4 -79
  105. package/scripts/tfx-route.sh +169 -1495
  106. package/scripts/wt-cli.mjs +1 -2
  107. package/skills/tfx-auto/SKILL.md +123 -741
  108. package/skills/tfx-auto/references/consensus-schema.md +161 -0
  109. package/skills/tfx-auto/references/tmux-observation.md +97 -0
  110. package/skills/tfx-doctor/SKILL.md +5 -6
  111. package/skills/tfx-lead/SKILL.md +195 -0
  112. package/skills/tfx-live/SKILL.md +25 -11
  113. package/skills/tfx-remote/SKILL.md +24 -87
  114. package/skills/tfx-research/SKILL.md +13 -20
  115. package/skills/tfx-review/SKILL.md +33 -35
  116. package/skills/tfx-setup/SKILL.md +1 -1
  117. package/skills/tfx-ship/SKILL.md +6 -27
  118. package/skills/tfx-wt/SKILL.md +34 -68
  119. package/bin/tfx-doctor-tui.mjs +0 -7
  120. package/bin/tfx-setup-tui.mjs +0 -7
  121. package/config/routing-policy.json +0 -124
  122. package/docs/assets/consensus-flow.svg +0 -125
  123. package/docs/assets/deep-vs-light.svg +0 -193
  124. package/docs/assets/demo-dark.gif +0 -0
  125. package/docs/assets/demo-light.gif +0 -0
  126. package/docs/assets/demo-psmux.gif +0 -0
  127. package/hub/adaptive-diagnostic.mjs +0 -350
  128. package/hub/adaptive-inject.mjs +0 -224
  129. package/hub/adaptive-memory.mjs +0 -277
  130. package/hub/adaptive.mjs +0 -172
  131. package/hub/assign-callbacks.mjs +0 -170
  132. package/hub/delegator/contracts.mjs +0 -38
  133. package/hub/delegator/index.mjs +0 -14
  134. package/hub/delegator/schema/delegator-tools.schema.json +0 -250
  135. package/hub/delegator/service.mjs +0 -336
  136. package/hub/delegator/tool-definitions.mjs +0 -35
  137. package/hub/dynamic-routing-engine.mjs +0 -511
  138. package/hub/hitl.mjs +0 -202
  139. package/hub/hub-lifecycle.mjs +0 -248
  140. package/hub/lib/known-errors.json +0 -72
  141. package/hub/lib/memory-store.mjs +0 -1001
  142. package/hub/lib/phase-manager.mjs +0 -493
  143. package/hub/lib/risk-tier.mjs +0 -53
  144. package/hub/lib/ssh-command.mjs +0 -231
  145. package/hub/lib/staged-file-detect.mjs +0 -43
  146. package/hub/lib/state-snapshot.mjs +0 -293
  147. package/hub/lib/tfx-route-args.mjs +0 -353
  148. package/hub/lib/trace-recorder.mjs +0 -153
  149. package/hub/lib/uuidv7.mjs +0 -45
  150. package/hub/memory-doctor.mjs +0 -640
  151. package/hub/middleware/quota-middleware.mjs +0 -70
  152. package/hub/middleware/request-logger.mjs +0 -191
  153. package/hub/paths.mjs +0 -24
  154. package/hub/pipe.mjs +0 -825
  155. package/hub/pipeline/index.mjs +0 -163
  156. package/hub/pipeline/state.mjs +0 -191
  157. package/hub/pipeline/transitions.mjs +0 -131
  158. package/hub/promote-penalties.mjs +0 -138
  159. package/hub/public/dashboard.html +0 -416
  160. package/hub/reflexion.mjs +0 -290
  161. package/hub/role-contract.mjs +0 -314
  162. package/hub/router.mjs +0 -2026
  163. package/hub/routing-snapshot.mjs +0 -261
  164. package/hub/schema.sql +0 -209
  165. package/hub/server.mjs +0 -3048
  166. package/hub/session-fingerprint.mjs +0 -418
  167. package/hub/state.mjs +0 -300
  168. package/hub/store-adapter.mjs +0 -182
  169. package/hub/store.mjs +0 -1736
  170. package/hub/team/build-worker-prompt.mjs +0 -92
  171. package/hub/team/check-mcp-hub.mjs +0 -49
  172. package/hub/team/claude-native-bridge.mjs +0 -987
  173. package/hub/team/cli/commands/start/start-in-process.mjs +0 -50
  174. package/hub/team/cli/commands/start/start-wt.mjs +0 -86
  175. package/hub/team/cli/services/hub-client.mjs +0 -264
  176. package/hub/team/cli/services/native-control.mjs +0 -145
  177. package/hub/team/codex-review.mjs +0 -520
  178. package/hub/team/conductor-mesh-bridge.mjs +0 -122
  179. package/hub/team/conductor-registry.mjs +0 -121
  180. package/hub/team/conductor.mjs +0 -1529
  181. package/hub/team/consensus-meta.mjs +0 -176
  182. package/hub/team/cto-auto-collect.mjs +0 -115
  183. package/hub/team/cto-pull.mjs +0 -31
  184. package/hub/team/daemon-pty-tmux-bridge.mjs +0 -146
  185. package/hub/team/dashboard.mjs +0 -322
  186. package/hub/team/event-log.mjs +0 -84
  187. package/hub/team/execution-mode.mjs +0 -324
  188. package/hub/team/extract-completion-payload.mjs +0 -110
  189. package/hub/team/headless-bridge-session.mjs +0 -17
  190. package/hub/team/health-probe.mjs +0 -435
  191. package/hub/team/interactive-native-launcher.mjs +0 -119
  192. package/hub/team/interactive-native-worker.mjs +0 -80
  193. package/hub/team/interactive-tui-transport.mjs +0 -479
  194. package/hub/team/launcher-template.mjs +0 -144
  195. package/hub/team/lead-control.mjs +0 -191
  196. package/hub/team/mcp-selector.mjs +0 -145
  197. package/hub/team/native-supervisor.mjs +0 -460
  198. package/hub/team/native.mjs +0 -727
  199. package/hub/team/recovery-store.mjs +0 -149
  200. package/hub/team/remote-probe.mjs +0 -292
  201. package/hub/team/remote-watcher.mjs +0 -506
  202. package/hub/team/runtime-strategy.mjs +0 -200
  203. package/hub/team/schemas/worker-completion.json +0 -39
  204. package/hub/team/sentinel-capture.mjs +0 -127
  205. package/hub/team/session-sync.mjs +0 -316
  206. package/hub/team/swarm-cli.mjs +0 -390
  207. package/hub/team/swarm-hypervisor.mjs +0 -2206
  208. package/hub/team/swarm-intent.mjs +0 -216
  209. package/hub/team/swarm-locks.mjs +0 -335
  210. package/hub/team/swarm-planner.mjs +0 -371
  211. package/hub/team/swarm-preflight.mjs +0 -366
  212. package/hub/team/swarm-reconciler.mjs +0 -242
  213. package/hub/team/synapse-cli.mjs +0 -458
  214. package/hub/team/synapse-http.mjs +0 -311
  215. package/hub/team/synapse-registry.mjs +0 -618
  216. package/hub/team/tui-remote-adapter.mjs +0 -377
  217. package/hub/team/tui-synapse.mjs +0 -223
  218. package/hub/team/worker-completion-validator.mjs +0 -79
  219. package/hub/team/worker-sandbox.mjs +0 -125
  220. package/hub/team/worktree-lifecycle.mjs +0 -639
  221. package/hub/team/wt-templates.json +0 -43
  222. package/hub/team-bridge.mjs +0 -27
  223. package/hub/tools.mjs +0 -900
  224. package/hub/workers/antigravity-route-worker.mjs +0 -179
  225. package/hub/workers/codex-app-server-worker.mjs +0 -1067
  226. package/hub/workers/delegator-mcp.mjs +0 -1098
  227. package/hub/workers/lib/jsonrpc-stdio.mjs +0 -204
  228. package/mesh/index.mjs +0 -69
  229. package/mesh/mesh-budget.mjs +0 -137
  230. package/mesh/mesh-heartbeat.mjs +0 -100
  231. package/mesh/mesh-protocol.mjs +0 -96
  232. package/mesh/mesh-queue.mjs +0 -163
  233. package/mesh/mesh-registry.mjs +0 -78
  234. package/mesh/mesh-router.mjs +0 -77
  235. package/scripts/__tests__/skill-surface.test.mjs +0 -93
  236. package/scripts/cache-doctor.mjs +0 -156
  237. package/scripts/cache-warmup.mjs +0 -630
  238. package/scripts/completions/tfx.bash +0 -74
  239. package/scripts/completions/tfx.fish +0 -57
  240. package/scripts/completions/tfx.zsh +0 -98
  241. package/scripts/demo.mjs +0 -197
  242. package/scripts/doctor-dynamic-routing.mjs +0 -87
  243. package/scripts/hub-ensure.mjs +0 -842
  244. package/scripts/hub-watchdog.mjs +0 -90
  245. package/scripts/lib/claudemd-scanner.mjs +0 -223
  246. package/scripts/lib/context.mjs +0 -67
  247. package/scripts/lib/dynamic-route-cli.mjs +0 -107
  248. package/scripts/lib/handoff.mjs +0 -187
  249. package/scripts/lib/logger.mjs +0 -122
  250. package/scripts/lib/stealth-fetch.mjs +0 -176
  251. package/scripts/notion-read.mjs +0 -686
  252. package/scripts/snapshot-codex-state.mjs +0 -37
  253. package/scripts/snapshot-gemini-state.mjs +0 -37
  254. package/scripts/sync-codex-auth.mjs +0 -71
  255. package/scripts/sync-hub-mcp-settings.mjs +0 -697
  256. package/skills/_templates/base.md +0 -4
  257. package/skills/_templates/deep.md +0 -6
  258. package/skills/shared/telemetry-segment.md +0 -6
  259. package/tui/core.mjs +0 -266
  260. package/tui/doctor.mjs +0 -375
  261. package/tui/monitor-data.mjs +0 -152
  262. package/tui/monitor.mjs +0 -318
  263. package/tui/setup.mjs +0 -506
@@ -1,7 +1,7 @@
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";
4
+ import { spawn } from "node:child_process";
5
5
  import { existsSync, readFileSync, statSync } from "node:fs";
6
6
 
7
7
  import { codexProfileConfigOverrides } from "../scripts/lib/codex-profile-config.mjs";
@@ -70,71 +70,6 @@ export function parseRetryAfterMs(text, provider) {
70
70
  return FALLBACK_COOLDOWN_MS[provider] || 5 * 3600_000;
71
71
  }
72
72
 
73
- // ── Codex CLI compatibility ─────────────────────────────────────
74
-
75
- let _cachedVersion = null;
76
-
77
- /**
78
- * `codex --version` 실행 결과를 파싱하여 마이너 버전 숫자 반환.
79
- * 파싱 실패 시 0 반환 (구버전으로 간주).
80
- * @returns {number} 마이너 버전 (예: 0.117.0 → 117)
81
- */
82
- export function getCodexVersion() {
83
- if (_cachedVersion !== null) return _cachedVersion;
84
- const override = Number(process.env.TFX_CODEX_VERSION_MINOR);
85
- if (Number.isFinite(override) && override > 0) {
86
- _cachedVersion = override;
87
- return _cachedVersion;
88
- }
89
- try {
90
- const out = execSync("codex --version", {
91
- encoding: "utf8",
92
- timeout: 5000,
93
- }).trim();
94
- const match = out.match(/(\d+)\.(\d+)\.(\d+)/);
95
- _cachedVersion = match ? Number.parseInt(match[2], 10) : 0;
96
- } catch {
97
- // Command builders should remain stable in CI even when the real Codex
98
- // CLI is absent. Runtime preflight still reports/install-gates Codex
99
- // separately; this fallback only selects the modern argv shape.
100
- _cachedVersion = 117;
101
- }
102
- return _cachedVersion;
103
- }
104
-
105
- /**
106
- * 최소 마이너 버전 이상인지 확인.
107
- * @param {number} minMinor
108
- * @returns {boolean}
109
- */
110
- export function gte(minMinor) {
111
- return getCodexVersion() >= minMinor;
112
- }
113
-
114
- /**
115
- * Codex CLI 기능별 분기 객체.
116
- * 실측 기반 임계값: 0.114.0에서 exec/skip-git-repo-check/color 확인됨.
117
- * --output-last-message는 0.114.0에 없음 (0.117+ 추정).
118
- */
119
- export const FEATURES = {
120
- /** exec 서브커맨드 사용 가능 여부 (0.110+ 이전부터 존재) */
121
- get execSubcommand() {
122
- return gte(110);
123
- },
124
- /** --output-last-message 플래그 지원 여부 (0.117+) */
125
- get outputLastMessage() {
126
- return gte(117);
127
- },
128
- /** --color <COLOR> 플래그 지원 여부 (exec와 동시 도입) */
129
- get colorNever() {
130
- return gte(110);
131
- },
132
- /** 플러그인 시스템 지원 여부 (향후 확장용) */
133
- get pluginSystem() {
134
- return gte(120);
135
- },
136
- };
137
-
138
73
  // ── Shell utilities ─────────────────────────────────────────────
139
74
 
140
75
  export function normalizePathForShell(value) {
@@ -182,7 +117,7 @@ export function buildExecCommand(prompt, resultFile = null, opts = {}) {
182
117
  enforceCanonicalProfile,
183
118
  } = opts;
184
119
 
185
- const parts = ["codex"];
120
+ const parts = ["codex", "exec"];
186
121
  // Select the effort profile via `-c` config overrides instead of
187
122
  // `--profile <name>`. codex 0.134+ rejects `--profile X` whenever config.toml
188
123
  // still contains an inline [profiles.X] table (and codex re-injects such
@@ -196,30 +131,17 @@ export function buildExecCommand(prompt, resultFile = null, opts = {}) {
196
131
  })
197
132
  : [];
198
133
 
199
- if (FEATURES.execSubcommand) {
200
- parts.push("exec");
201
- if (sandboxBypass) parts.push("--dangerously-bypass-approvals-and-sandbox");
202
- if (skipGitRepoCheck) parts.push("--skip-git-repo-check");
203
- if (resultFile && FEATURES.outputLastMessage) {
204
- parts.push("--output-last-message", resultFile);
205
- }
206
- if (FEATURES.colorNever) parts.push("--color", "never");
207
- for (const override of profileOverrides)
208
- parts.push("-c", shellQuote(override));
209
- // NOTE: `codex exec`는 --cwd 플래그를 지원하지 않는다. Node spawn의 cwd
210
- // 옵션으로 child process의 working directory를 제어한다 (conductor.mjs 참조).
211
- // opts.cwd는 기록용으로만 받아두고 CLI command에는 반영하지 않는다.
212
- // (이전 커밋에서 잘못 추가되어 shard 전체가 exit 2로 크래시한 회귀 #94 후속 이슈)
213
- if (Array.isArray(mcpServers)) {
214
- for (const server of mcpServers) {
215
- parts.push("-c", `mcp_servers.${server}.enabled=true`);
216
- }
134
+ if (sandboxBypass) parts.push("--dangerously-bypass-approvals-and-sandbox");
135
+ if (skipGitRepoCheck) parts.push("--skip-git-repo-check");
136
+ if (resultFile) parts.push("--output-last-message", resultFile);
137
+ parts.push("--color", "never");
138
+ for (const override of profileOverrides)
139
+ parts.push("-c", shellQuote(override));
140
+ // `codex exec`는 --cwd를 받지 않아 child process의 cwd로 제어한다.
141
+ if (Array.isArray(mcpServers)) {
142
+ for (const server of mcpServers) {
143
+ parts.push("-c", `mcp_servers.${server}.enabled=true`);
217
144
  }
218
- } else {
219
- parts.push("--dangerously-bypass-approvals-and-sandbox");
220
- if (skipGitRepoCheck) parts.push("--skip-git-repo-check");
221
- for (const override of profileOverrides)
222
- parts.push("-c", shellQuote(override));
223
145
  }
224
146
 
225
147
  const useStdin = resolveStdinPromptMode(stdinPrompt);
@@ -126,7 +126,6 @@ export function buildExecArgs(opts = {}) {
126
126
  cwd: opts.cwd,
127
127
  // stdinPrompt 가 명시되면 buildExecCommand 로 forward.
128
128
  // headless 워커 spawn (backend.mjs) 은 default (stdin on, macOS hook fix).
129
- // launcher path (launcher-template.mjs) 는 결정론 위해 false 전달.
130
129
  stdinPrompt: opts.stdinPrompt,
131
130
  });
132
131
 
@@ -5,7 +5,6 @@ import { join } from "node:path";
5
5
 
6
6
  import { whichCommandAsync } from "./platform.mjs";
7
7
 
8
- const MIN_RECOMMENDED_MINOR = 118;
9
8
  let _cachedVersion = null;
10
9
 
11
10
  function escapeRegExp(value) {
@@ -74,25 +73,11 @@ async function checkCodexInstalled() {
74
73
  };
75
74
  }
76
75
 
77
- function checkCodexVersion() {
78
- const version = getCodexVersion();
79
- const warnings =
80
- version >= MIN_RECOMMENDED_MINOR
81
- ? []
82
- : [
83
- `Codex CLI 0.${version}.x detected; 0.${MIN_RECOMMENDED_MINOR}.x or newer is recommended.`,
84
- ];
85
- return { version, warnings };
86
- }
87
-
88
76
  function checkApprovalMode(configText, opts = {}) {
89
- const approvalMode = readTomlString(configText, "approval_mode");
90
- const sandbox = readTomlString(configText, "sandbox");
77
+ const approvalPolicy = readTomlString(configText, "approval_policy");
91
78
  const subcommand = opts.subcommand || "exec";
92
79
  return {
93
- needsBypass: subcommand === "exec" || approvalMode !== "full-auto",
94
- approvalMode,
95
- sandbox,
80
+ needsBypass: subcommand === "exec" || approvalPolicy !== "never",
96
81
  };
97
82
  }
98
83
 
@@ -154,7 +139,7 @@ async function checkMcpHealth(mcpServers, configText) {
154
139
  /**
155
140
  * Detects codex config drift between global (~/.codex/config.toml) and the
156
141
  * target workdir. Covers:
157
- * 1. workdir-local `.codex/config.toml` overriding global approval_mode/sandbox
142
+ * 1. workdir-local `.codex/config.toml` overriding global approval_policy/sandbox_mode
158
143
  * 2. instruction-file asymmetry (AGENTS.md vs CLAUDE.md)
159
144
  *
160
145
  * @param {string} [workdir] absolute path to the invocation workdir
@@ -173,7 +158,7 @@ export function detectWorkdirDrift(workdir, globalConfigText = "") {
173
158
  } catch {
174
159
  localText = "";
175
160
  }
176
- const keys = ["approval_mode", "sandbox", "model"];
161
+ const keys = ["approval_policy", "sandbox_mode", "model"];
177
162
  for (const key of keys) {
178
163
  const local = readTomlString(localText, key);
179
164
  const global = readTomlString(globalConfigText, key);
@@ -212,18 +197,10 @@ export async function runPreflight(opts = {}) {
212
197
  }
213
198
 
214
199
  const warnings = [...install.warnings];
215
- const { version, warnings: versionWarnings } = checkCodexVersion();
216
- warnings.push(...versionWarnings);
200
+ const version = getCodexVersion();
217
201
 
218
202
  const configText = readConfigText(opts.configPath);
219
203
  const approval = checkApprovalMode(configText, opts);
220
- if (approval.approvalMode !== "full-auto") {
221
- warnings.push(
222
- `approval_mode is '${approval.approvalMode || "unset"}'; bypass flag will be used.`,
223
- );
224
- }
225
- if (approval.sandbox)
226
- warnings.push(`sandbox mode from config.toml: ${approval.sandbox}`);
227
204
 
228
205
  const mcp = await checkMcpHealth(opts.mcpServers, configText);
229
206
  warnings.push(...mcp.warnings);
@@ -9,7 +9,7 @@ import {
9
9
  unlinkSync,
10
10
  writeFileSync,
11
11
  } from "node:fs";
12
- import { homedir, tmpdir } from "node:os";
12
+ import { tmpdir } from "node:os";
13
13
  import { join } from "node:path";
14
14
  import { IS_WINDOWS, killProcess } from "../platform.mjs";
15
15
 
@@ -387,23 +387,6 @@ function getProcessSnapshot({
387
387
  }
388
388
  }
389
389
 
390
- function addHubPid(protectedPids) {
391
- try {
392
- const hubPidPath = join(
393
- homedir(),
394
- ".claude",
395
- "cache",
396
- "tfx-hub",
397
- "hub.pid",
398
- );
399
- if (existsSync(hubPidPath)) {
400
- const hubInfo = JSON.parse(readFileSync(hubPidPath, "utf8"));
401
- const hubPid = normalizePid(hubInfo?.pid);
402
- if (hubPid) protectedPids.add(hubPid);
403
- }
404
- } catch {}
405
- }
406
-
407
390
  function getProtectedPids({
408
391
  protectedPids,
409
392
  isWindows = IS_WINDOWS,
@@ -415,7 +398,6 @@ function getProtectedPids({
415
398
  if (Number.isInteger(process.ppid) && process.ppid > 0) {
416
399
  result.add(process.ppid);
417
400
  }
418
- addHubPid(result);
419
401
  if (!includeAncestorScan) return result;
420
402
 
421
403
  const snapshot = getProcessSnapshot({ isWindows, spawnSyncFn });
@@ -488,68 +470,6 @@ function hasExactGbrainServe(commandLine) {
488
470
  );
489
471
  }
490
472
 
491
- function hasGbrainServeCommand(commandLine) {
492
- return /(^|\s)gbrain\s+serve(\s|$)/i.test(commandLine.trim());
493
- }
494
-
495
- function hasFsmonitorDaemonCommand(commandLine) {
496
- return FSMONITOR_DAEMON_PATTERN.test(commandLine);
497
- }
498
-
499
- function categoryForShardProcess(proc, context) {
500
- const name = normalizeName(proc.name);
501
- const commandLine = normalizeCommandLine(proc.commandLine);
502
- const worktreePath = normalizeCommandLine(context.worktreePath || "");
503
- const shardMarker = context.shardName
504
- ? `.codex-swarm\\wt-${context.shardName}`
505
- : "";
506
- const hasWorktree = worktreePath && commandLine.includes(worktreePath);
507
- const hasShardMarker = shardMarker && commandLine.includes(shardMarker);
508
-
509
- if (name === "node.exe" || name === "node") {
510
- if (hasWorktree || hasShardMarker) return "node";
511
- }
512
-
513
- if (name === "bash.exe" || name === "bash" || name === "sh") {
514
- if (hasWorktree) return "bash";
515
- }
516
-
517
- if (name === "conhost.exe" || name === "conhost") {
518
- if (
519
- context.sessionIds?.length > 0 &&
520
- context.sessionIds.some((id) => id && commandLine.includes(id))
521
- ) {
522
- return "conhost";
523
- }
524
- }
525
-
526
- if (name === "bun.exe" || name === "bun") {
527
- if ((hasWorktree || hasShardMarker) && hasGbrainServeCommand(commandLine)) {
528
- return "bun";
529
- }
530
- }
531
-
532
- if (name === "git.exe" || name === "git") {
533
- if (
534
- (hasWorktree || hasShardMarker) &&
535
- hasFsmonitorDaemonCommand(commandLine)
536
- ) {
537
- return "git";
538
- }
539
- }
540
-
541
- return null;
542
- }
543
-
544
- function killPid(
545
- pid,
546
- { killFn = process.kill, protectedPids = new Set() } = {},
547
- ) {
548
- if (protectedPids.has(pid)) return false;
549
- killFn(pid, "SIGKILL");
550
- return true;
551
- }
552
-
553
473
  /**
554
474
  * Return a process tree rooted at `rootPid`.
555
475
  *
@@ -730,67 +650,6 @@ export function findProcessesByCommandLine(
730
650
  });
731
651
  }
732
652
 
733
- /**
734
- * Cleanup processes associated with a swarm shard.
735
- *
736
- * Sequence: kill known top PID snapshots, scan scoped command lines, skip
737
- * protected PIDs, and kill only narrowly gated runtime categories.
738
- *
739
- * @param {{worktreePath?: string, sessionIds?: string[], topPids?: Array<number | {pid: number}>, runId?: string, shardName?: string, dryRun?: boolean, isWindows?: boolean, spawnSyncFn?: typeof spawnSync, killFn?: typeof process.kill, protectedPids?: Set<number>}} opts
740
- * @returns {{killed: number, scanned: number, skipped: number, byCategory: {node: number, bash: number, conhost: number, bun: number, git: number}}}
741
- */
742
- export function cleanupShardProcesses({
743
- worktreePath,
744
- sessionIds = [],
745
- topPids = [],
746
- runId,
747
- shardName,
748
- dryRun = false,
749
- isWindows = IS_WINDOWS,
750
- spawnSyncFn = spawnSync,
751
- killFn = process.kill,
752
- protectedPids,
753
- } = {}) {
754
- const protectedSet = getProtectedPids({
755
- protectedPids,
756
- isWindows,
757
- spawnSyncFn,
758
- includeAncestorScan: true,
759
- });
760
- const byCategory = { node: 0, bash: 0, conhost: 0, bun: 0, git: 0 };
761
- let killed = 0;
762
- let skipped = 0;
763
-
764
- if (!dryRun && topPids.length > 0) {
765
- killed += killProcessTreeSnapshot(topPids, {
766
- killFn,
767
- protectedPids: protectedSet,
768
- }).killed;
769
- }
770
-
771
- const scanned = getProcessSnapshot({ isWindows, spawnSyncFn });
772
-
773
- for (const proc of scanned) {
774
- const category = categoryForShardProcess(proc, {
775
- worktreePath,
776
- sessionIds,
777
- shardName,
778
- });
779
- if (!category) continue;
780
- byCategory[category]++;
781
- if (protectedSet.has(proc.pid)) {
782
- skipped++;
783
- continue;
784
- }
785
- if (dryRun) continue;
786
- try {
787
- if (killPid(proc.pid, { killFn, protectedPids: protectedSet })) killed++;
788
- } catch {}
789
- }
790
-
791
- return { killed, scanned: scanned.length, skipped, byCategory };
792
- }
793
-
794
653
  /**
795
654
  * Cleanup narrowly gated orphan runtime processes.
796
655
  *
@@ -968,22 +827,8 @@ export function cleanupStaleFsmonitorDaemons({
968
827
  function cleanupOrphansUnix() {
969
828
  const myPid = process.pid;
970
829
 
971
- // Hub PID 보호
972
830
  const protectedPids = new Set();
973
831
  protectedPids.add(myPid);
974
- try {
975
- const hubPidPath = join(
976
- homedir(),
977
- ".claude",
978
- "cache",
979
- "tfx-hub",
980
- "hub.pid",
981
- );
982
- if (existsSync(hubPidPath)) {
983
- const hubPid = Number(JSON.parse(readFileSync(hubPidPath, "utf8"))?.pid);
984
- if (Number.isFinite(hubPid) && hubPid > 0) protectedPids.add(hubPid);
985
- }
986
- } catch {}
987
832
 
988
833
  // 현재 프로세스의 조상 트리 보호
989
834
  try {
@@ -7,13 +7,14 @@ import { join } from "node:path";
7
7
  const LOG_DIR = join(homedir(), ".triflux", "logs");
8
8
  const DEDUPE_WINDOW_MS = 5_000;
9
9
  const RATE_WINDOW_MS = 1_000;
10
+ const DEFAULT_MAX_SPAWN_PER_SEC = 100;
10
11
  // multi-worker headless dispatch 가 1 초 안에 ~25-30+ psmux/tmux 명령을 호출한다.
11
12
  // 2-worker dispatchBatch 가 default 30 limit 을 초과해 `rate_limit` throw 로 mac
12
13
  // 호환성 회귀 (smoke test 발견, 2026-05-15). 100 으로 상향 — 폭주 안전망은
13
14
  // 여전히 (default 100/sec) 유지, env override 로 보수 설정 가능.
14
15
  export let MAX_SPAWN_PER_SEC = resolvePositiveInteger(
15
16
  process.env.TRIFLUX_MAX_SPAWN_RATE,
16
- 100,
17
+ DEFAULT_MAX_SPAWN_PER_SEC,
17
18
  );
18
19
  export const MAX_TOTAL_DESCENDANTS = resolvePositiveInteger(
19
20
  process.env.TRIFLUX_MAX_DESCENDANTS,
@@ -160,7 +161,7 @@ export function getMaxSpawnPerSec() {
160
161
  export function reload() {
161
162
  MAX_SPAWN_PER_SEC = resolvePositiveInteger(
162
163
  process.env.TRIFLUX_MAX_SPAWN_RATE,
163
- 30,
164
+ DEFAULT_MAX_SPAWN_PER_SEC,
164
165
  );
165
166
  return getMaxSpawnPerSec();
166
167
  }
@@ -5,7 +5,6 @@ export const MAX_DURATION_MS = 86_400_000;
5
5
  export const DEFAULT_ASSIGN_TIMEOUT_MS = 600_000;
6
6
  export const DEFAULT_ASSIGN_TTL_MS = 600_000;
7
7
  export const DEFAULT_HANDOFF_TTL_MS = 600_000;
8
- export const DEFAULT_SWARM_LOCK_TTL_MS = 600_000;
9
8
  export const DEFAULT_REGISTER_TIMEOUT_SEC = 600;
10
9
  export const REGISTER_HEARTBEAT_GRACE_MS = 120_000;
11
10
  export const DEFAULT_STALL_INTERVENTION_SEC = 1_200;
@@ -23,7 +23,6 @@
23
23
  "test-engineer": "codex",
24
24
  "qa-tester": "codex",
25
25
  "codex": "codex",
26
- "gemini": "antigravity",
27
26
  "antigravity": "antigravity",
28
27
  "agy": "antigravity",
29
28
  "claude": "claude"
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env python3
2
+ # claude agents 행의 백킹 PTY 안에서 워커 tmux pane 에 붙는다.
3
+ # bg-pty-host 가 출력 이력을 다시 재생하면 바깥 터미널이 tmux 의 오래된 질의에 또 답한다.
4
+ # tmux 는 늦게 온 응답을 키 입력으로 넘기므로 여기서 응답 시퀀스를 걸러 낸다.
5
+ import fcntl
6
+ import os
7
+ import pty
8
+ import re
9
+ import select
10
+ import signal
11
+ import sys
12
+ import termios
13
+ import time
14
+ import tty
15
+
16
+ # DA1/DA2/DA3, 창 크기 보고(CSI ... t), DECRQM, kitty 키보드 플래그 응답, DCS, OSC
17
+ REPLY = re.compile(
18
+ rb"\x1b\[[?>=][0-9;]*c"
19
+ rb"|\x1b\[[0-9]+(?:;[0-9]+)*t"
20
+ rb"|\x1b\[\?[0-9;]*\$y"
21
+ rb"|\x1b\[\?[0-9]*u"
22
+ rb"|\x1bP[^\x1b]*\x1b\\"
23
+ rb"|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)"
24
+ )
25
+ # 응답이 read 경계에서 잘렸을 때 잠깐 붙잡아 둘 접두
26
+ PARTIAL = re.compile(
27
+ rb"(?:\x1b\[[?>=]?[0-9;$]*|\x1bP[^\x1b]*\x1b?|\x1b\][^\x07\x1b]*\x1b?)\Z"
28
+ )
29
+ HOLD_SEC = 0.05
30
+
31
+
32
+ def strip_replies(buf):
33
+ """걸러 낸 바이트와, 다음 read 까지 보류할 꼬리를 돌려준다."""
34
+ cleaned = REPLY.sub(b"", buf)
35
+ m = PARTIAL.search(cleaned)
36
+ if m and cleaned[m.start():] != b"\x1b":
37
+ return cleaned[: m.start()], cleaned[m.start():]
38
+ return cleaned, b""
39
+
40
+
41
+ def copy_winsize(src, dst):
42
+ try:
43
+ size = fcntl.ioctl(src, termios.TIOCGWINSZ, b"\0" * 8)
44
+ fcntl.ioctl(dst, termios.TIOCSWINSZ, size)
45
+ except OSError:
46
+ pass
47
+
48
+
49
+ def relay(argv, env):
50
+ pid, fd = pty.fork()
51
+ if pid == 0:
52
+ os.execvpe(argv[0], argv, env)
53
+ stdin = sys.stdin.fileno()
54
+ stdout = sys.stdout.fileno()
55
+ copy_winsize(stdin, fd)
56
+ signal.signal(signal.SIGWINCH, lambda *_: copy_winsize(stdin, fd))
57
+ saved = termios.tcgetattr(stdin) if os.isatty(stdin) else None
58
+ if saved:
59
+ tty.setraw(stdin)
60
+ pending = b""
61
+ deadline = 0.0
62
+ try:
63
+ while True:
64
+ # 자식 출력이 계속 바빠도 보류한 바이트가 HOLD_SEC 를 넘겨 묶이지 않게 마감을 둔다.
65
+ timeout = max(0.0, deadline - time.monotonic()) if pending else None
66
+ try:
67
+ ready, _, _ = select.select([stdin, fd], [], [], timeout)
68
+ except InterruptedError:
69
+ continue
70
+ if pending and time.monotonic() >= deadline:
71
+ os.write(fd, pending)
72
+ pending = b""
73
+ if fd in ready:
74
+ try:
75
+ data = os.read(fd, 65536)
76
+ except OSError:
77
+ break
78
+ if not data:
79
+ break
80
+ os.write(stdout, data)
81
+ if stdin in ready:
82
+ # 행 PTY 가 닫히면 slave 읽기는 EIO 를 던진다.
83
+ try:
84
+ data = os.read(stdin, 65536)
85
+ except OSError:
86
+ break
87
+ if not data:
88
+ break
89
+ out, held = strip_replies(pending + data)
90
+ if held and not pending:
91
+ deadline = time.monotonic() + HOLD_SEC
92
+ pending = held
93
+ if out:
94
+ os.write(fd, out)
95
+ finally:
96
+ if saved:
97
+ try:
98
+ termios.tcsetattr(stdin, termios.TCSADRAIN, saved)
99
+ except termios.error:
100
+ pass
101
+ # 행 PTY 가 닫히면 attach client 도 끝낸다. 워커 방은 그대로 남는다.
102
+ os.close(fd)
103
+ _, status = os.waitpid(pid, 0)
104
+ return os.waitstatus_to_exitcode(status)
105
+
106
+
107
+ def main():
108
+ # 사용법: agents-row-attach.py -- <tmux attach 명령>
109
+ argv = sys.argv[1:]
110
+ if argv[:1] == ["--"]:
111
+ argv = argv[1:]
112
+ if not argv:
113
+ print("usage: agents-row-attach.py -- <command>", file=sys.stderr)
114
+ return 2
115
+ env = {k: v for k, v in os.environ.items() if k not in ("TMUX", "TMUX_PANE")}
116
+ return relay(argv, env)
117
+
118
+
119
+ if __name__ == "__main__":
120
+ sys.exit(main())