triflux 10.44.0 → 10.46.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 (203) hide show
  1. package/README.md +1 -1
  2. package/adapters/codex/skills/tfx-harness/SKILL.md +45 -9
  3. package/bin/tfx-live.mjs +1298 -100
  4. package/bin/triflux.mjs +9 -6
  5. package/config/routing-policy.json +1 -1
  6. package/hooks/codex-session-hook.mjs +67 -1
  7. package/hooks/cto-north-star-brief.mjs +3 -16
  8. package/hooks/error-context.mjs +3 -6
  9. package/hooks/hook-manager.mjs +1 -1
  10. package/hooks/hook-registry.json +0 -11
  11. package/hooks/keyword-rules.json +8 -4
  12. package/hooks/permission-safe-allow.mjs +36 -2
  13. package/hooks/session-start-fast.mjs +50 -1
  14. package/hooks/session-start-lake.mjs +3 -16
  15. package/hub/bridge.mjs +111 -5
  16. package/hub/dynamic-routing-engine.mjs +1 -1
  17. package/hub/gemini-adapter.mjs +29 -11
  18. package/hub/lib/memory-store.mjs +66 -30
  19. package/hub/lib/tfx-route-args.mjs +41 -4
  20. package/hub/pipe.mjs +10 -15
  21. package/hub/reflexion.mjs +0 -57
  22. package/hub/router.mjs +202 -24
  23. package/hub/schema.sql +1 -0
  24. package/hub/server.mjs +172 -93
  25. package/hub/store.mjs +106 -33
  26. package/hub/team/backend.mjs +4 -2
  27. package/hub/team/claude-daemon-control.mjs +2 -0
  28. package/hub/team/cli/commands/start/start-headless.mjs +2 -0
  29. package/hub/team/cli/commands/start/start-mux.mjs +24 -3
  30. package/hub/team/cli/services/runtime-mode.mjs +23 -9
  31. package/hub/team/codex-review.mjs +2 -3
  32. package/hub/team/conductor.mjs +80 -24
  33. package/hub/team/cto-auto-collect.mjs +2 -6
  34. package/hub/team/cto-pull.mjs +4 -0
  35. package/hub/team/dashboard-open.mjs +1 -0
  36. package/hub/team/execution-mode.mjs +38 -2
  37. package/hub/team/headless.mjs +493 -82
  38. package/hub/team/interactive-tui-transport.mjs +15 -1
  39. package/hub/team/launcher-template.mjs +19 -2
  40. package/hub/team/orchestrator.mjs +36 -9
  41. package/hub/team/pane.mjs +176 -14
  42. package/hub/team/psmux.mjs +79 -70
  43. package/hub/team/retry-state-machine.mjs +47 -16
  44. package/hub/team/session.mjs +57 -0
  45. package/hub/team/terminal-opener.mjs +68 -13
  46. package/hub/team/tui-viewer.mjs +65 -14
  47. package/hub/team/uds-orchestrator.mjs +384 -83
  48. package/hub/tools.mjs +6 -0
  49. package/hub/workers/codex-mcp.mjs +35 -6
  50. package/hud/cli-policy.mjs +69 -0
  51. package/hud/constants.mjs +20 -17
  52. package/hud/context-monitor.mjs +31 -21
  53. package/hud/hud-qos-status.mjs +81 -91
  54. package/hud/providers/claude.mjs +150 -84
  55. package/hud/providers/codex.mjs +10 -1
  56. package/hud/renderers.mjs +21 -59
  57. package/hud/utils.mjs +1 -15
  58. package/package.json +1 -1
  59. package/scripts/__tests__/keyword-detector.test.mjs +89 -1
  60. package/scripts/__tests__/release-governance.test.mjs +329 -12
  61. package/scripts/__tests__/release-publish-ancestor.test.mjs +67 -0
  62. package/scripts/__tests__/release-verify-wait.test.mjs +169 -0
  63. package/scripts/__tests__/resolve-auto-release.test.mjs +216 -0
  64. package/scripts/__tests__/skill-surface.test.mjs +6 -4
  65. package/scripts/__tests__/tfx-route-bash-node-parity.test.mjs +7 -5
  66. package/scripts/__tests__/tfx-route-node-entry.test.mjs +27 -21
  67. package/scripts/claude-login-detect.mjs +107 -25
  68. package/scripts/ensure-agy-hooks.mjs +31 -1
  69. package/scripts/keyword-detector.mjs +11 -1
  70. package/scripts/lib/agent-route-policy.mjs +47 -39
  71. package/scripts/lib/cli-agy.mjs +24 -4
  72. package/scripts/lib/codex-profile-config.mjs +45 -13
  73. package/scripts/lib/codex-recovery.sh +19 -0
  74. package/scripts/lib/gemini-profiles.mjs +113 -29
  75. package/scripts/lib/keyword-rules.mjs +12 -0
  76. package/scripts/lib/logger.mjs +60 -43
  77. package/scripts/lib/machine-profile.mjs +293 -0
  78. package/scripts/lib/mcp-health.mjs +44 -18
  79. package/scripts/notion-read.mjs +2 -1
  80. package/scripts/release/check-packages-mirror.mjs +53 -1
  81. package/scripts/release/lib.mjs +1 -2
  82. package/scripts/release/prepare.mjs +24 -9
  83. package/scripts/release/publish.mjs +16 -1
  84. package/scripts/release/resolve-auto-release.mjs +136 -0
  85. package/scripts/release/verify.mjs +74 -25
  86. package/scripts/remote-spawn.mjs +830 -32
  87. package/scripts/session-stale-cleanup.mjs +0 -1
  88. package/scripts/setup.mjs +487 -150
  89. package/scripts/sync-hub-mcp-settings.mjs +11 -3
  90. package/scripts/test-lock.mjs +15 -1
  91. package/scripts/tfx-gate-activate.mjs +4 -4
  92. package/scripts/tfx-route-post.mjs +90 -13
  93. package/scripts/tfx-route.sh +520 -141
  94. package/skills/merge-worktree/SKILL.md +3 -3
  95. package/skills/star-prompt/skill.json +1 -1
  96. package/skills/tfx-analysis/SKILL.md +8 -9
  97. package/skills/tfx-analysis/skill.json +1 -10
  98. package/skills/tfx-auto/SKILL.md +208 -50
  99. package/skills/tfx-auto/skill.json +2 -23
  100. package/skills/tfx-doctor/skill.json +0 -3
  101. package/skills/tfx-find/skill.json +0 -7
  102. package/skills/tfx-forge/SKILL.md +2 -2
  103. package/skills/tfx-forge/skill.json +0 -7
  104. package/skills/tfx-goal-clarify/skill.json +5 -0
  105. package/skills/tfx-harness/SKILL.md +26 -3
  106. package/skills/tfx-harness/skill.json +5 -0
  107. package/skills/tfx-hooks/SKILL.md +0 -2
  108. package/skills/tfx-hooks/skill.json +0 -3
  109. package/skills/tfx-hub/SKILL.md +1 -6
  110. package/skills/tfx-hub/skill.json +0 -3
  111. package/skills/tfx-index/skill.json +0 -6
  112. package/skills/tfx-interview/SKILL.md +0 -2
  113. package/skills/tfx-live/SKILL.md +131 -5
  114. package/skills/tfx-live/skill.json +0 -5
  115. package/skills/tfx-plan/SKILL.md +7 -8
  116. package/skills/tfx-plan/skill.json +1 -12
  117. package/skills/tfx-profile/SKILL.md +15 -13
  118. package/skills/tfx-profile/skill.json +0 -3
  119. package/skills/tfx-prune/SKILL.md +4 -6
  120. package/skills/tfx-qa/SKILL.md +7 -7
  121. package/skills/tfx-qa/skill.json +1 -11
  122. package/skills/tfx-ralph/SKILL.md +8 -11
  123. package/skills/tfx-remote/SKILL.md +2 -2
  124. package/skills/tfx-remote/skill.json +0 -3
  125. package/skills/tfx-research/SKILL.md +2 -2
  126. package/skills/tfx-research/skill.json +1 -16
  127. package/skills/tfx-review/SKILL.md +11 -11
  128. package/skills/tfx-review/skill.json +1 -11
  129. package/skills/tfx-setup/SKILL.md +5 -6
  130. package/skills/tfx-setup/skill.json +0 -3
  131. package/skills/tfx-ship/SKILL.md +43 -36
  132. package/skills/tfx-ship/skill.json +1 -9
  133. package/skills/tfx-wt/skill.json +0 -7
  134. package/tui/codex-profile.mjs +3 -3
  135. package/tui/gemini-profile.mjs +11 -24
  136. package/tui/monitor.mjs +1 -0
  137. package/tui/setup.mjs +7 -6
  138. package/scripts/headless-guard-fast.sh +0 -29
  139. package/scripts/headless-guard.mjs +0 -523
  140. package/skills/tfx-autopilot/SKILL.md +0 -51
  141. package/skills/tfx-autopilot/skill.json +0 -12
  142. package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +0 -75
  143. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/eval_metadata.json +0 -42
  144. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +0 -39
  145. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/analysis.md +0 -87
  146. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/classification.md +0 -35
  147. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/commands.sh +0 -275
  148. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/outputs/routing.md +0 -56
  149. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/timing.json +0 -5
  150. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +0 -39
  151. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/analysis.md +0 -92
  152. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/classification.md +0 -71
  153. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/commands.sh +0 -264
  154. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/outputs/routing.md +0 -113
  155. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/timing.json +0 -5
  156. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/eval_metadata.json +0 -32
  157. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +0 -29
  158. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/analysis.md +0 -96
  159. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/classification.md +0 -38
  160. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/commands.sh +0 -151
  161. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/outputs/routing.md +0 -51
  162. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/timing.json +0 -5
  163. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +0 -29
  164. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/analysis.md +0 -127
  165. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/classification.md +0 -57
  166. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/commands.sh +0 -129
  167. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/outputs/routing.md +0 -84
  168. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/timing.json +0 -5
  169. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/eval_metadata.json +0 -27
  170. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +0 -24
  171. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/analysis.md +0 -98
  172. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/classification.md +0 -65
  173. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/commands.sh +0 -123
  174. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/outputs/routing.md +0 -66
  175. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/timing.json +0 -5
  176. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +0 -20
  177. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/analysis.md +0 -88
  178. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/classification.md +0 -40
  179. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/commands.sh +0 -130
  180. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/outputs/routing.md +0 -61
  181. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/timing.json +0 -5
  182. package/skills/tfx-consensus/SKILL.md +0 -56
  183. package/skills/tfx-consensus/skill.json +0 -8
  184. package/skills/tfx-debate/SKILL.md +0 -60
  185. package/skills/tfx-debate/skill.json +0 -14
  186. package/skills/tfx-fullcycle/SKILL.md +0 -47
  187. package/skills/tfx-fullcycle/skill.json +0 -13
  188. package/skills/tfx-multi/SKILL.md +0 -45
  189. package/skills/tfx-multi/references/agent-wrapper-rules.md +0 -94
  190. package/skills/tfx-multi/references/thorough-pipeline.md +0 -105
  191. package/skills/tfx-multi/skill.json +0 -10
  192. package/skills/tfx-panel/SKILL.md +0 -60
  193. package/skills/tfx-panel/skill.json +0 -14
  194. package/skills/tfx-persist/SKILL.md +0 -63
  195. package/skills/tfx-persist/skill.json +0 -14
  196. package/skills/tfx-psmux-rules/SKILL.md +0 -41
  197. package/skills/tfx-psmux-rules/skill.json +0 -10
  198. package/skills/tfx-remote-setup/SKILL.md +0 -42
  199. package/skills/tfx-remote-setup/skill.json +0 -10
  200. package/skills/tfx-remote-spawn/SKILL.md +0 -52
  201. package/skills/tfx-remote-spawn/skill.json +0 -10
  202. package/skills/tfx-swarm/SKILL.md +0 -49
  203. package/skills/tfx-swarm/skill.json +0 -13
@@ -21,10 +21,12 @@ export function buildAntigravityCommand(
21
21
  // and `"` reach the shell verbatim (P1: shell injection).
22
22
  const promptFile = `${resultFile}.prompt`;
23
23
  writeFileSync(promptFile, prompt);
24
+ // agy 1.1.27 부터 --print 는 값(프롬프트)이 필수이고 stdin 프롬프트는 거부된다.
25
+ // 파일 내용을 셸이 재해석하지 않는 형태(따옴표 안 명령 치환 / Get-Content 식)로 값에 넣는다.
24
26
  if (isWindows) {
25
- return `Get-Content -Raw '${promptFile}' | agy --print --dangerously-skip-permissions > '${resultFile}' 2>'${resultFile}.err'`;
27
+ return `agy --dangerously-skip-permissions --print (Get-Content -Raw '${promptFile}') > '${resultFile}' 2>'${resultFile}.err'`;
26
28
  }
27
- return `agy --print --dangerously-skip-permissions < '${promptFile}' > '${resultFile}' 2>'${resultFile}.err'`;
29
+ return `agy --dangerously-skip-permissions --print "$(cat '${promptFile}')" > '${resultFile}' 2>'${resultFile}.err'`;
28
30
  }
29
31
 
30
32
  const _require = createRequire(import.meta.url);
@@ -321,10 +321,12 @@ export async function probeClaudeDaemonCandidates({
321
321
  short,
322
322
  sessionId,
323
323
  timeoutMs = 6000,
324
+ tmpRoot = "/tmp",
324
325
  } = {}) {
325
326
  const candidates = await buildClaudeDaemonDiscoveryCandidates({
326
327
  configDir,
327
328
  env,
329
+ tmpRoot,
328
330
  });
329
331
  const callerProvenance = detectCallerProvenance(env);
330
332
  const targetRequested = Boolean(short || sessionId);
@@ -88,6 +88,8 @@ export async function startHeadlessTeam({
88
88
  timeoutSec: timeoutSec || 900,
89
89
  layout,
90
90
  autoAttach: !!autoAttach,
91
+ leadPane: process.env.TMUX_PANE,
92
+ leadTmux: process.env.TMUX,
91
93
  dashboard: !!dashboard,
92
94
  dashboardLayout,
93
95
  dashboardSize: dashboardSize ?? 0.5,
@@ -5,6 +5,7 @@ import {
5
5
  attachSession,
6
6
  configureTeammateKeybindings,
7
7
  createSession,
8
+ splitAttachToClient,
8
9
  } from "../../../session.mjs";
9
10
  import { BOLD, DIM, GREEN, RESET } from "../../../shared.mjs";
10
11
  import { ok, warn } from "../../render.mjs";
@@ -62,12 +63,23 @@ export async function startMuxTeam({
62
63
 
63
64
  ok("CLI 초기화 대기 (3초)...");
64
65
  await new Promise((resolve) => setTimeout(resolve, 3000));
65
- await orchestrate(sessionId, assignments, {
66
+ const promptInjectionFailures = await orchestrate(sessionId, assignments, {
66
67
  hubUrl,
67
68
  teammateMode,
68
69
  lead: { target: leadTarget, cli: lead, task },
70
+ onInjectionFailure(failure) {
71
+ warn(
72
+ `${failure.cli} ${failure.role} prompt 제출 실패 (${failure.target}): ${failure.message}`,
73
+ );
74
+ },
69
75
  });
70
- ok("리드/워커 프롬프트 주입 완료");
76
+ if (promptInjectionFailures.length === 0) {
77
+ ok("리드/워커 프롬프트 주입 완료");
78
+ } else {
79
+ warn(
80
+ `prompt 제출 실패 ${promptInjectionFailures.length}건 — 세션 상태에 기록함`,
81
+ );
82
+ }
71
83
 
72
84
  return {
73
85
  sessionName: sessionId,
@@ -78,6 +90,7 @@ export async function startMuxTeam({
78
90
  teammateMode,
79
91
  startedAt: Date.now(),
80
92
  hubUrl,
93
+ promptInjectionFailures,
81
94
  members,
82
95
  panes: Object.fromEntries(
83
96
  members.map((member) => [
@@ -113,7 +126,15 @@ export async function startMuxTeam({
113
126
  );
114
127
  console.log(` ${DIM}Ctrl+B → D: 세션 분리 (백그라운드)${RESET}\n`);
115
128
  if (process.stdout.isTTY && process.stdin.isTTY) attachSession(sessionId);
116
- else {
129
+ else if (
130
+ splitAttachToClient(sessionId, {
131
+ targetPane: process.env.TMUX_PANE,
132
+ tmuxEnv: process.env.TMUX,
133
+ })
134
+ ) {
135
+ ok("non-TTY 환경: 바깥 tmux 클라이언트 창을 분할해 attach함");
136
+ console.log(` ${DIM}분할 pane에서 나가기: Ctrl+B → D${RESET}\n`);
137
+ } else {
117
138
  warn("TTY 미지원 환경이라 자동 attach를 생략함");
118
139
  console.log(` ${DIM}수동 연결: tfx multi attach${RESET}\n`);
119
140
  }
@@ -5,16 +5,28 @@ import {
5
5
  sessionExists,
6
6
  } from "../../session.mjs";
7
7
 
8
- export function normalizeTeammateMode(mode = "auto") {
8
+ export function normalizeTeammateMode(mode = "auto", deps = {}) {
9
+ const platform = deps.platform || process.platform;
10
+ const env = deps.env || process.env;
11
+ const detectMux = deps.detectMultiplexer || detectMultiplexer;
9
12
  const raw = String(mode).toLowerCase();
10
13
  if (raw === "inline" || raw === "native") return "in-process";
11
14
  if (raw === "headless" || raw === "hl") return "headless";
12
- if (raw === "in-process" || raw === "tmux" || raw === "psmux" || raw === "wt")
13
- return raw;
14
- if (raw === "windows-terminal" || raw === "windows_terminal") return "wt";
15
+ if (raw === "in-process" || raw === "tmux") return raw;
16
+ if (raw === "psmux") return platform === "win32" ? "psmux" : "in-process";
17
+ if (
18
+ raw === "wt" ||
19
+ raw === "windows-terminal" ||
20
+ raw === "windows_terminal"
21
+ ) {
22
+ return platform === "win32" ? "wt" : "in-process";
23
+ }
15
24
  if (raw === "auto") {
16
- if (process.env.TMUX) return "tmux";
17
- const mux = detectMultiplexer();
25
+ if (env.TMUX) return "tmux";
26
+ const mux = detectMux();
27
+ if (platform !== "win32") {
28
+ return mux === "tmux" ? "tmux" : "in-process";
29
+ }
18
30
  if (mux === "psmux") return "psmux";
19
31
  if (mux === "tmux") return "tmux";
20
32
  return "in-process";
@@ -57,9 +69,11 @@ export function isTeamAlive(state) {
57
69
  return sessionExists(state.sessionName);
58
70
  }
59
71
 
60
- export function ensureTmuxOrExit() {
61
- const mux = detectMultiplexer();
62
- if (mux) return mux;
72
+ export function ensureTmuxOrExit(deps = {}) {
73
+ const platform = deps.platform || process.platform;
74
+ const detectMux = deps.detectMultiplexer || detectMultiplexer;
75
+ const mux = detectMux();
76
+ if (mux && (platform === "win32" || mux === "tmux")) return mux;
63
77
  const error = new Error("tmux 미발견");
64
78
  error.code = "TMUX_REQUIRED";
65
79
  throw error;
@@ -1,7 +1,6 @@
1
1
  // Codex headless review helper. Runs a cross-model review of a git diff
2
- // without going through Bash-level headless-guard (the guard only inspects
3
- // `codex exec` invoked from the Bash tool — node `spawn` bypasses it
4
- // intentionally because `tfx` is the sanctioned entry point).
2
+ // through node `spawn` rather than the Bash tool, because `tfx` is the
3
+ // sanctioned entry point for Codex.
5
4
  //
6
5
  // Contract:
7
6
  // runCodexReview({ ref = "HEAD", base?: string, timeoutMs = 180_000 })
@@ -21,6 +21,11 @@ import { dirname, join } from "node:path";
21
21
  import { createRegistry } from "../../mesh/mesh-registry.mjs";
22
22
  import { resolveNestedCodexAgentProfile } from "../../scripts/lib/cli-codex.mjs";
23
23
  import { codexProfileConfigOverrides } from "../../scripts/lib/codex-profile-config.mjs";
24
+ import {
25
+ buildDisabledCliError,
26
+ normalizeCliName,
27
+ resolveCliPolicy,
28
+ } from "../../scripts/lib/machine-profile.mjs";
24
29
  import { broker } from "../account-broker.mjs";
25
30
  import { runPreflight as runCodexPreflight } from "../codex-preflight.mjs";
26
31
  import {
@@ -606,30 +611,53 @@ export function createConductor(opts = {}) {
606
611
  }
607
612
  }
608
613
 
609
- const spawnSpec = buildSpawnSpecForMode(MODES.HEADLESS, {
610
- cli: session.config.agent,
611
- prompt: session.config.prompt,
612
- profile: session.config.profile,
613
- role: session.config.role,
614
- model: session.config.model,
615
- env: session.config.env,
616
- mcpServers: session.config.mcpServers,
617
- excludeMcpServers,
618
- resolveCommand: opts.deps?.resolveCliExecutable,
619
- onWarning: (message, details = {}) => {
620
- eventLog.append("mcp_server_skipped", {
621
- session: session.id,
622
- message,
623
- ...details,
624
- });
625
- emitter.emit("warning", {
626
- type: "mcp_server_skipped",
627
- session: session.id,
628
- message,
629
- ...details,
630
- });
631
- },
632
- });
614
+ let spawnSpec;
615
+ try {
616
+ spawnSpec = buildSpawnSpecForMode(MODES.HEADLESS, {
617
+ cli: session.config.agent,
618
+ prompt: session.config.prompt,
619
+ profile: session.config.profile,
620
+ role: session.config.role,
621
+ model: session.config.model,
622
+ env: session.config.env,
623
+ mcpServers: session.config.mcpServers,
624
+ excludeMcpServers,
625
+ resolveCommand: opts.deps?.resolveCliExecutable,
626
+ onWarning: (message, details = {}) => {
627
+ eventLog.append("mcp_server_skipped", {
628
+ session: session.id,
629
+ message,
630
+ ...details,
631
+ });
632
+ emitter.emit("warning", {
633
+ type: "mcp_server_skipped",
634
+ session: session.id,
635
+ message,
636
+ ...details,
637
+ });
638
+ },
639
+ });
640
+ } catch (err) {
641
+ // respawnSession 은 `void` 로 호출되므로 여기서 던지면 처리되지 않은 거부가
642
+ // 된다. spec 생성 실패를 명시 실패로 접어 세션이 매달리지 않게 한다.
643
+ // 정책 차단과 그 밖의 spec 생성 실패는 원인이 다르므로 이벤트를 나눈다.
644
+ const message = err?.message || String(err);
645
+ const blockedByPolicy = err?.code === "TFX_CLI_DISABLED";
646
+ const eventName = blockedByPolicy
647
+ ? "session_spawn_blocked"
648
+ : "session_spawn_failed";
649
+ const reason = blockedByPolicy ? "spawn_blocked" : "spawn_failed";
650
+ const detail = {
651
+ session: session.id,
652
+ agent: session.config.agent,
653
+ code: err?.code || null,
654
+ error: message,
655
+ };
656
+ eventLog.append(eventName, detail);
657
+ emitter.emit("warning", { type: eventName, ...detail });
658
+ handleFailure(session, `${reason}:${message}`);
659
+ return;
660
+ }
633
661
 
634
662
  // #90 branch guard: shard spawn cwd가 main 브랜치면 즉시 abort.
635
663
  // swarm shard는 반드시 shard 전용 worktree 브랜치에서 실행되어야 한다.
@@ -1176,6 +1204,34 @@ export function createConductor(opts = {}) {
1176
1204
  }
1177
1205
  }
1178
1206
 
1207
+ // TFX_DISABLE_* SSOT 집행. 계정 임대와 worker sandbox 생성보다 앞에 둔다.
1208
+ // 뒤에 두면 정책으로 거부된 세션이 샌드박스 디렉터리와 auth.json 사본을
1209
+ // 남기고, 임대 반납이 회로 차단기 실패로 집계돼 멀쩡한 계정이 cooldown 에
1210
+ // 들어간다. 다른 CLI 로 자동 전환하지 않고 호출자에게 그대로 던진다.
1211
+ // claude 처럼 정책 대상이 아닌 agent 는 프로파일 파일을 읽지 않는다 (F4 와 동일).
1212
+ if (!config.remote && normalizeCliName(config.agent)) {
1213
+ const blockedCli = buildDisabledCliError(
1214
+ config.agent,
1215
+ resolveCliPolicy(process.env),
1216
+ );
1217
+ if (blockedCli) {
1218
+ eventLog.append("session_spawn_blocked", {
1219
+ session: config.id,
1220
+ agent: config.agent,
1221
+ code: blockedCli.code || null,
1222
+ error: blockedCli.message,
1223
+ });
1224
+ emitter.emit("warning", {
1225
+ type: "session_spawn_blocked",
1226
+ session: config.id,
1227
+ agent: config.agent,
1228
+ code: blockedCli.code || null,
1229
+ error: blockedCli.message,
1230
+ });
1231
+ throw blockedCli;
1232
+ }
1233
+ }
1234
+
1179
1235
  // broker lease (graceful — broker null if accounts.json absent)
1180
1236
  let lease = null;
1181
1237
  const activeBroker = getActiveBroker();
@@ -2,16 +2,12 @@ import { existsSync, statSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
 
4
4
  import { resolveLakeRootDir } from "../../cto/lake-root.mjs";
5
+ import { resolveRoleControlSnapshot } from "../lib/cto-env.mjs";
5
6
 
6
7
  const DEFAULT_DEBOUNCE_MS = 120_000;
7
- const OFF_VALUES = new Set(["0", "false", "off", "no"]);
8
8
 
9
9
  export function isCtoAutoCollectDisabled(env = process.env) {
10
- return OFF_VALUES.has(
11
- String(env?.TFX_CTO_AUTO_COLLECT ?? "")
12
- .trim()
13
- .toLowerCase(),
14
- );
10
+ return !resolveRoleControlSnapshot(env).auto_collect_enabled;
15
11
  }
16
12
 
17
13
  function freshCurrentMd(projectRoot, now, debounceMs) {
@@ -1,6 +1,8 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
 
4
+ import { resolveRoleControlSnapshot } from "../lib/cto-env.mjs";
5
+
4
6
  function defaultLakeRoot() {
5
7
  return join(process.cwd(), ".triflux", "lake");
6
8
  }
@@ -9,9 +11,11 @@ function defaultLakeRoot() {
9
11
  * Read the CTO north-star lake snapshot without affecting runtime control flow.
10
12
  * @param {object} [opts]
11
13
  * @param {string} [opts.lakeRoot] Directory containing current.json.
14
+ * @param {object} [opts.env] Environment values used for the CTO kill-switch.
12
15
  * @returns {object|null}
13
16
  */
14
17
  export function readCtoSnapshot(opts = {}) {
18
+ if (!resolveRoleControlSnapshot(opts.env).north_star_enabled) return null;
15
19
  try {
16
20
  const lakeRoot = opts.lakeRoot || defaultLakeRoot();
17
21
  const parsed = JSON.parse(
@@ -47,6 +47,7 @@ export async function openHeadlessDashboardTarget(sessionName, opts = {}) {
47
47
  title: title || `▲ ${safeSession}`,
48
48
  cwd,
49
49
  profile: opts.profile ?? "triflux",
50
+ targetPane: opts.targetPane,
50
51
  });
51
52
  return await opened;
52
53
  } catch {
@@ -4,6 +4,15 @@ import { existsSync, readFileSync } from "node:fs";
4
4
  import { dirname, resolve as pathResolve } from "node:path";
5
5
  import { resolveNestedCodexAgentProfile } from "../../scripts/lib/cli-codex.mjs";
6
6
  import { codexProfileConfigOverrides } from "../../scripts/lib/codex-profile-config.mjs";
7
+ import {
8
+ resolveGeminiModel,
9
+ resolveGeminiProfileForPurpose,
10
+ } from "../../scripts/lib/gemini-profiles.mjs";
11
+ import {
12
+ buildDisabledCliError,
13
+ normalizeCliName,
14
+ resolveCliPolicy,
15
+ } from "../../scripts/lib/machine-profile.mjs";
7
16
  import { whichCommand } from "../platform.mjs";
8
17
 
9
18
  const WIN32_EXT_PRECEDENCE = [".cmd", ".exe", ".bat", ".ps1"];
@@ -96,6 +105,19 @@ export function resolveCliExecutable(cli, opts = {}) {
96
105
 
97
106
  export function buildSpawnSpecForMode(mode, opts = {}) {
98
107
  const cli = normalizeCli(opts.cli || "codex");
108
+
109
+ // TFX_DISABLE_* SSOT 집행. route 를 우회하는 spawn 경로도 tfx-route.sh 와 같은
110
+ // 정책을 따른다. 정책 판독은 런타임 process.env 와 machine profile 만 쓰고,
111
+ // 자식에게 넘길 세션 env(opts.env)는 쓰지 않는다. 주입 시임은 opts.cliPolicy
112
+ // (계산된 정책)와 opts.policyEnv(env 객체) 둘뿐이다.
113
+ // claude 처럼 정책 대상이 아닌 CLI 는 프로파일 파일을 읽지 않는다.
114
+ if (normalizeCliName(cli)) {
115
+ const cliPolicy =
116
+ opts.cliPolicy ?? resolveCliPolicy(opts.policyEnv ?? process.env);
117
+ const blockedCli = buildDisabledCliError(cli, cliPolicy);
118
+ if (blockedCli) throw blockedCli;
119
+ }
120
+
99
121
  const prompt = asPrompt(opts.prompt);
100
122
  const commandName = cli === "antigravity" ? "agy" : cli;
101
123
  const resolvedCommand = resolveCliExecutable(commandName, opts);
@@ -126,12 +148,26 @@ export function buildSpawnSpecForMode(mode, opts = {}) {
126
148
  };
127
149
 
128
150
  if (cli === "antigravity") {
129
- const args = ["--print", "--dangerously-skip-permissions"];
151
+ // agy 1.1.27: --print 는 값(프롬프트)을 받는 마지막 인자다. stdin 프롬프트는
152
+ // "empty prompt" 로 거부되므로 argv 로 넘긴다.
153
+ // effort 는 역할별 SSOT(scripts/lib/gemini-profiles.mjs). 명시 model 이 우선한다.
154
+ const model =
155
+ typeof opts.model === "string" && opts.model.trim()
156
+ ? resolveGeminiModel(opts.model.trim(), {
157
+ profilesPath: opts.geminiProfilesPath,
158
+ })
159
+ : resolveGeminiModel(
160
+ resolveGeminiProfileForPurpose(opts.role || opts.agent),
161
+ { profilesPath: opts.geminiProfilesPath },
162
+ );
163
+ const args = ["--dangerously-skip-permissions"];
164
+ if (model) args.push("--model", model);
165
+ args.push("--print", prompt);
130
166
  return {
131
167
  ...wrap(args),
132
168
  useExec: true,
133
169
  shell: false,
134
- stdinPrompt: prompt.length > 0,
170
+ stdinPrompt: false,
135
171
  prompt,
136
172
  };
137
173
  }