@triflux/core 10.50.1 → 10.51.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 (90) hide show
  1. package/hooks/agy-session-hook.mjs +11 -56
  2. package/hooks/codex-session-hook.mjs +11 -101
  3. package/hub/bridge.mjs +1 -79
  4. package/hub/cli-adapter-base.mjs +12 -90
  5. package/hub/codex-adapter.mjs +0 -1
  6. package/hub/codex-preflight.mjs +5 -28
  7. package/hub/index.mjs +3 -9
  8. package/hub/lib/cto-env.mjs +0 -23
  9. package/hub/lib/known-errors.json +2 -2
  10. package/hub/lib/process-utils.mjs +0 -123
  11. package/hub/lib/spawn-trace.mjs +3 -2
  12. package/hub/lib/timeout-defaults.mjs +0 -1
  13. package/hub/paths.mjs +0 -6
  14. package/hub/pipeline/index.mjs +0 -190
  15. package/hub/pipeline/transitions.mjs +0 -10
  16. package/hub/reflexion.mjs +1 -84
  17. package/hub/role-contract.mjs +0 -2
  18. package/hud/cli-policy.mjs +0 -26
  19. package/hud/colors.mjs +0 -13
  20. package/hud/constants.mjs +7 -153
  21. package/hud/context-monitor.mjs +3 -64
  22. package/hud/hud-qos-status.mjs +54 -264
  23. package/hud/providers/claude.mjs +4 -16
  24. package/hud/providers/gemini.mjs +83 -689
  25. package/hud/renderers.mjs +59 -367
  26. package/hud/terminal.mjs +1 -63
  27. package/hud/utils.mjs +2 -26
  28. package/package.json +1 -4
  29. package/scripts/lib/agent-route-policy.mjs +162 -14
  30. package/scripts/lib/claudemd-scanner.mjs +2 -199
  31. package/scripts/lib/cli-claude.mjs +0 -2
  32. package/scripts/lib/context.mjs +3 -19
  33. package/scripts/lib/env-probe.mjs +5 -64
  34. package/scripts/lib/gemini-profiles.mjs +2 -69
  35. package/scripts/lib/legacy-mcp-cleanup.mjs +14 -3
  36. package/scripts/lib/machine-profile.mjs +3 -17
  37. package/scripts/lib/mcp-filter.mjs +109 -53
  38. package/scripts/lib/mcp-health.mjs +2 -2
  39. package/scripts/lib/mcp-server-catalog.mjs +2 -2
  40. package/scripts/lib/psmux-info.mjs +25 -104
  41. package/scripts/lib/session-presence.mjs +1 -1
  42. package/scripts/lib/skill-template.mjs +0 -271
  43. package/hub/codex-compat.mjs +0 -12
  44. package/hub/dynamic-routing-engine.mjs +0 -511
  45. package/hub/fullcycle.mjs +0 -113
  46. package/hub/gemini-adapter.mjs +0 -174
  47. package/hub/intent.mjs +0 -321
  48. package/hub/lib/mcp-response-cache.mjs +0 -239
  49. package/hub/lib/path-utils.mjs +0 -165
  50. package/hub/lib/risk-tier.mjs +0 -53
  51. package/hub/lib/ssh-command.mjs +0 -231
  52. package/hub/lib/staged-file-detect.mjs +0 -43
  53. package/hub/lib/tfx-route-args.mjs +0 -353
  54. package/hub/lib/timeline-adapter.mjs +0 -190
  55. package/hub/pipeline/gates/confidence.mjs +0 -75
  56. package/hub/pipeline/gates/consensus.mjs +0 -106
  57. package/hub/pipeline/gates/index.mjs +0 -9
  58. package/hub/pipeline/gates/selfcheck.mjs +0 -120
  59. package/hub/quality/deslop.mjs +0 -274
  60. package/hub/research.mjs +0 -155
  61. package/hub/role-control-events.mjs +0 -113
  62. package/hub/routing/complexity.mjs +0 -269
  63. package/hub/routing/index.mjs +0 -122
  64. package/hub/routing/q-learning.mjs +0 -387
  65. package/hub/routing-snapshot.mjs +0 -261
  66. package/hub/token-mode.mjs +0 -228
  67. package/hud/mission-board.mjs +0 -56
  68. package/hud/providers/cto.mjs +0 -76
  69. package/mesh/index.mjs +0 -69
  70. package/mesh/mesh-budget.mjs +0 -137
  71. package/mesh/mesh-heartbeat.mjs +0 -100
  72. package/mesh/mesh-protocol.mjs +0 -96
  73. package/mesh/mesh-queue.mjs +0 -163
  74. package/mesh/mesh-registry.mjs +0 -78
  75. package/mesh/mesh-router.mjs +0 -77
  76. package/scripts/lib/agent-json.mjs +0 -27
  77. package/scripts/lib/async.mjs +0 -174
  78. package/scripts/lib/cli-agy.mjs +0 -85
  79. package/scripts/lib/cli-codex.mjs +0 -62
  80. package/scripts/lib/cli-gemini.mjs +0 -37
  81. package/scripts/lib/dynamic-route-cli.mjs +0 -107
  82. package/scripts/lib/env.mjs +0 -14
  83. package/scripts/lib/hub.mjs +0 -48
  84. package/scripts/lib/pid.mjs +0 -63
  85. package/scripts/lib/quota.mjs +0 -47
  86. package/scripts/lib/skill-state.mjs +0 -220
  87. package/scripts/lib/team.mjs +0 -75
  88. package/scripts/lib/timeout.mjs +0 -66
  89. package/scripts/lib/tmp.mjs +0 -26
  90. package/scripts/lib/toml.mjs +0 -132
@@ -2,23 +2,11 @@
2
2
 
3
3
  import { argv, exit, stdin, stdout } from "node:process";
4
4
  import { pathToFileURL } from "node:url";
5
- import { drainPendingSynapse as defaultDrainPendingSynapse } from "../hub/team/synapse-http.mjs";
6
5
  import {
7
- heartbeatInteractiveSession as defaultHeartbeatInteractiveSession,
8
- registerInteractiveSession as defaultRegisterInteractiveSession,
6
+ emitParticipantSessionStarted,
7
+ shouldSkipInteractiveRegistration,
9
8
  } from "../scripts/lib/session-presence.mjs";
10
9
 
11
- // hub-ensure is loaded lazily so the byte-identical packages/core mirror of this
12
- // file loads cleanly. packages/core mirrors scripts/lib only (not scripts/*), so a
13
- // static `../scripts/hub-ensure.mjs` import would make the core copy throw
14
- // ERR_MODULE_NOT_FOUND at load time. Mirrors codex-session-hook.mjs's pattern.
15
- async function defaultHubEnsureRun(stdinData) {
16
- const { run } = await import(
17
- new URL("../scripts/hub-ensure.mjs", import.meta.url).href
18
- );
19
- return run(stdinData);
20
- }
21
-
22
10
  function parsePayload(stdinData) {
23
11
  try {
24
12
  const raw = typeof stdinData === "string" ? stdinData : "";
@@ -76,8 +64,7 @@ export function normalizeMode(argvMode, payload) {
76
64
  if (Number.isFinite(invocationNum)) {
77
65
  return invocationNum <= 1 ? "register" : "heartbeat";
78
66
  }
79
- // Unknown invocation index: register is idempotent and also ensures the hub,
80
- // so it is the safe default for a first-contact payload.
67
+ // 첫 호출 정보가 없으면 시작 기록을 남긴다.
81
68
  return "register";
82
69
  }
83
70
 
@@ -108,23 +95,6 @@ async function runHookSideEffectsWithStdoutSuppressed(fn) {
108
95
  }
109
96
  }
110
97
 
111
- /**
112
- * Execute the observational Antigravity session hook.
113
- *
114
- * The hook always returns/writes an empty JSON object so hook stdout remains
115
- * JSON-only and hook failures never block the user session.
116
- *
117
- * @param {string} stdinData
118
- * @param {{
119
- * argvMode?: string,
120
- * writeStdout?: boolean,
121
- * hubEnsureRun?: (stdinData: string) => Promise<unknown> | unknown,
122
- * registerInteractiveSession?: (stdinData: string) => Promise<unknown> | unknown,
123
- * heartbeatInteractiveSession?: (stdinData: string) => Promise<unknown> | unknown,
124
- * drainPendingSynapse?: (timeoutMs?: number) => Promise<unknown> | unknown,
125
- * }} [opts]
126
- * @returns {Promise<string>}
127
- */
128
98
  export async function runAgySessionHook(stdinData, opts = {}) {
129
99
  const output = "{}\n";
130
100
  const parsed = parsePayload(stdinData);
@@ -142,37 +112,22 @@ export async function runAgySessionHook(stdinData, opts = {}) {
142
112
  }
143
113
 
144
114
  const mode = normalizeMode(opts.argvMode ?? argv[2], parsed.payload);
145
- const hubEnsureRun = opts.hubEnsureRun || defaultHubEnsureRun;
146
- const registerInteractiveSession =
147
- opts.registerInteractiveSession || defaultRegisterInteractiveSession;
148
- const heartbeatInteractiveSession =
149
- opts.heartbeatInteractiveSession || defaultHeartbeatInteractiveSession;
150
- const drainPendingSynapse =
151
- opts.drainPendingSynapse || defaultDrainPendingSynapse;
115
+ const emitSessionStarted =
116
+ opts.emitSessionStarted || emitParticipantSessionStarted;
152
117
 
153
118
  try {
154
119
  await runHookSideEffectsWithStdoutSuppressed(async () => {
155
- if (mode === "register") {
156
- try {
157
- await hubEnsureRun(sessionPayload);
158
- } catch {}
159
- try {
160
- await Promise.resolve(registerInteractiveSession(sessionPayload));
161
- } catch {}
162
- try {
163
- await drainPendingSynapse(1000);
164
- } catch {}
165
- } else if (mode === "heartbeat") {
166
- try {
167
- heartbeatInteractiveSession(sessionPayload);
168
- } catch {}
120
+ if (
121
+ mode === "register" &&
122
+ !shouldSkipInteractiveRegistration(JSON.parse(sessionPayload), opts)
123
+ ) {
169
124
  try {
170
- await drainPendingSynapse(500);
125
+ await emitSessionStarted(sessionPayload);
171
126
  } catch {}
172
127
  }
173
128
  });
174
129
  } catch {
175
- // agy session hooks are observational and must never block the session.
130
+ // 로컬 기록 실패가 세션을 막지 않게 한다.
176
131
  }
177
132
 
178
133
  if (opts.writeStdout !== false) {
@@ -1,23 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { spawn } from "node:child_process";
4
3
  import { argv, exit, stdin, stdout } from "node:process";
5
- import { fileURLToPath, pathToFileURL } from "node:url";
4
+ import { pathToFileURL } from "node:url";
6
5
  import { writeCodexSessionRecord } from "../hub/lib/codex-session-registry.mjs";
7
- import { drainPendingSynapse as defaultDrainPendingSynapse } from "../hub/team/synapse-http.mjs";
8
6
  import {
9
- heartbeatInteractiveSession as defaultHeartbeatInteractiveSession,
10
- registerInteractiveSession as defaultRegisterInteractiveSession,
7
+ emitParticipantSessionStarted,
8
+ shouldSkipInteractiveRegistration,
11
9
  } from "../scripts/lib/session-presence.mjs";
12
10
 
13
- // hub-ensure가 없는 core 미러도 불러올 수 있도록 지연 import한다.
14
- async function defaultHubEnsureRun(stdinData) {
15
- const { run } = await import(
16
- new URL("../scripts/hub-ensure.mjs", import.meta.url).href
17
- );
18
- return run(stdinData);
19
- }
20
-
21
11
  function parsePayload(stdinData) {
22
12
  try {
23
13
  const raw = typeof stdinData === "string" ? stdinData : "";
@@ -48,66 +38,6 @@ function normalizeMode(mode, payload) {
48
38
  return "";
49
39
  }
50
40
 
51
- /**
52
- * Start a detached bridge call so Codex/OMX presence reaches both the Synapse
53
- * session registry and the hub agents table. This is intentionally independent
54
- * of the legacy session-start work below: a missing or unavailable hub must
55
- * never delay or fail SessionStart.
56
- */
57
- export function launchCodexPresenceRegistration(payload, opts = {}) {
58
- try {
59
- const sessionId = String(payload?.session_id || "").trim();
60
- if (!sessionId) return false;
61
-
62
- const cwd =
63
- typeof payload?.cwd === "string" && payload.cwd.trim()
64
- ? payload.cwd
65
- : process.cwd();
66
- const bridgePath =
67
- opts.bridgePath ||
68
- fileURLToPath(new URL("../hub/bridge.mjs", import.meta.url));
69
- const spawnFn = opts.spawnFn || spawn;
70
- const canonicalSessionId = String(process.env.OMX_SESSION_ID || "").trim();
71
- const tmuxSession = String(
72
- process.env.OMX_TMUX_SESSION_NAME || process.env.OMX_TMUX_SESSION || "",
73
- ).trim();
74
- const tmuxPane = String(process.env.TMUX_PANE || "").trim();
75
- const args = [
76
- bridgePath,
77
- "register",
78
- "--session-id",
79
- sessionId,
80
- "--cwd",
81
- cwd,
82
- "--worktree-path",
83
- cwd,
84
- "--session-kind",
85
- "interactive",
86
- "--host",
87
- "local",
88
- "--codex-session-id",
89
- sessionId,
90
- ];
91
- if (canonicalSessionId && canonicalSessionId !== sessionId) {
92
- args.push("--omx-session-id", canonicalSessionId);
93
- }
94
- if (tmuxSession) args.push("--tmux-session", tmuxSession);
95
- if (tmuxPane) args.push("--tmux-pane", tmuxPane);
96
-
97
- const child = spawnFn(process.execPath, args, {
98
- detached: true,
99
- stdio: "ignore",
100
- windowsHide: true,
101
- env: process.env,
102
- });
103
- child?.once?.("error", () => {});
104
- child?.unref?.();
105
- return true;
106
- } catch {
107
- return false;
108
- }
109
- }
110
-
111
41
  function swallowStdoutWrite(_chunk, encodingOrCallback, callback) {
112
42
  const done =
113
43
  typeof encodingOrCallback === "function" ? encodingOrCallback : callback;
@@ -142,15 +72,8 @@ export async function runCodexSessionHook(stdinData, opts = {}) {
142
72
  ? normalizeMode(opts.argvMode ?? argv[2], parsed.payload)
143
73
  : "";
144
74
  const writeSessionRecord = opts.writeSessionRecord || writeCodexSessionRecord;
145
- const hubEnsureRun = opts.hubEnsureRun || defaultHubEnsureRun;
146
- const registerInteractiveSession =
147
- opts.registerInteractiveSession || defaultRegisterInteractiveSession;
148
- const heartbeatInteractiveSession =
149
- opts.heartbeatInteractiveSession || defaultHeartbeatInteractiveSession;
150
- const drainPendingSynapse =
151
- opts.drainPendingSynapse || defaultDrainPendingSynapse;
152
- const launchPresenceRegistration =
153
- opts.launchPresenceRegistration || launchCodexPresenceRegistration;
75
+ const emitSessionStarted =
76
+ opts.emitSessionStarted || emitParticipantSessionStarted;
154
77
 
155
78
  try {
156
79
  await runHookSideEffectsWithStdoutSuppressed(async () => {
@@ -159,30 +82,17 @@ export async function runCodexSessionHook(stdinData, opts = {}) {
159
82
  writeSessionRecord(parsed.payload);
160
83
  } catch {}
161
84
  }
162
- if (mode === "register") {
163
- try {
164
- launchPresenceRegistration(parsed.payload);
165
- } catch {}
166
- try {
167
- await hubEnsureRun(stdinData);
168
- } catch {}
169
- try {
170
- await Promise.resolve(registerInteractiveSession(stdinData));
171
- } catch {}
172
- try {
173
- await drainPendingSynapse(1000);
174
- } catch {}
175
- } else if (mode === "heartbeat") {
176
- try {
177
- heartbeatInteractiveSession(stdinData);
178
- } catch {}
85
+ if (
86
+ mode === "register" &&
87
+ !shouldSkipInteractiveRegistration(parsed.payload, opts)
88
+ ) {
179
89
  try {
180
- await drainPendingSynapse(500);
90
+ await emitSessionStarted(stdinData);
181
91
  } catch {}
182
92
  }
183
93
  });
184
94
  } catch {
185
- // Codex session hooks are observational and must never block the session.
95
+ // 로컬 기록 실패가 세션을 막지 않게 한다.
186
96
  }
187
97
 
188
98
  if (opts.writeStdout !== false) {
package/hub/bridge.mjs CHANGED
@@ -5,15 +5,8 @@
5
5
  // Named Pipe/Unix Socket 제어 채널을 우선 사용하고,
6
6
  // 연결이 없을 때만 HTTP /bridge/* 엔드포인트로 내려간다.
7
7
 
8
- import { spawn } from "node:child_process";
9
8
  import { randomUUID } from "node:crypto";
10
- import {
11
- existsSync,
12
- mkdirSync,
13
- openSync,
14
- readFileSync,
15
- writeFileSync,
16
- } from "node:fs";
9
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
17
10
  import net from "node:net";
18
11
  import { homedir } from "node:os";
19
12
  import { join } from "node:path";
@@ -515,56 +508,6 @@ function buildPublishBody(from, to, type, payload) {
515
508
  };
516
509
  }
517
510
 
518
- // Hub 자동 재시작 (Pipe+HTTP 모두 실패 시 1회 시도, 최대 4초 대기)
519
- async function tryRestartHub() {
520
- const serverPath = join(PROJECT_ROOT, "hub", "server.mjs");
521
- if (!existsSync(serverPath)) return false;
522
-
523
- if (existsSync(HUB_PID_FILE)) {
524
- try {
525
- const info = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
526
- if (info.pid) {
527
- try {
528
- process.kill(info.pid, 0);
529
- return false;
530
- } catch (e) {
531
- // still alive
532
- if (e.code === "EPERM") return false;
533
- } // alive, no permission
534
- }
535
- } catch {} // corrupt PID file, proceed with restart
536
- }
537
-
538
- try {
539
- const logDir = join(process.cwd(), ".tfx", "logs");
540
- if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
541
- const logFile = join(logDir, "hub-restart.log");
542
- const logFd = openSync(logFile, "a");
543
- const child = spawn(process.execPath, [serverPath], {
544
- detached: true,
545
- stdio: ["ignore", "ignore", logFd],
546
- windowsHide: true,
547
- });
548
- child.unref();
549
- } catch {
550
- return false;
551
- }
552
-
553
- for (let i = 0; i < 8; i++) {
554
- await new Promise((r) => setTimeout(r, 500));
555
- try {
556
- const res = await fetch(`${getHubUrl()}/status`, {
557
- signal: AbortSignal.timeout(1000),
558
- });
559
- if (res.ok) {
560
- const data = await res.json();
561
- if (data?.hub?.state === "healthy") return true;
562
- }
563
- } catch {}
564
- }
565
- return false;
566
- }
567
-
568
511
  async function requestHub(operation, body, timeoutMs = 3000, fallback = null) {
569
512
  const viaPipe =
570
513
  operation.transport === "command"
@@ -585,27 +528,6 @@ async function requestHub(operation, body, timeoutMs = 3000, fallback = null) {
585
528
  return { transport: "http", result: viaHttp };
586
529
  }
587
530
 
588
- // Hub 재시작 시도 → Pipe/HTTP 재시도
589
- if (await tryRestartHub()) {
590
- const retryPipe =
591
- operation.transport === "command"
592
- ? await pipeCommand(operation.action, body, timeoutMs)
593
- : await pipeQuery(operation.action, body, timeoutMs);
594
- if (retryPipe) {
595
- return { transport: "pipe", result: retryPipe };
596
- }
597
- const retryHttp = operation.httpPath
598
- ? await requestJson(operation.httpPath, {
599
- method: operation.httpMethod || "POST",
600
- body: operation.httpMethod === "GET" ? undefined : body,
601
- timeoutMs: Math.max(timeoutMs, 5000),
602
- })
603
- : null;
604
- if (retryHttp) {
605
- return { transport: "http", result: retryHttp };
606
- }
607
- }
608
-
609
531
  if (!fallback) return null;
610
532
  const viaFallback = await fallback();
611
533
  if (!viaFallback) return null;
@@ -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);
package/hub/index.mjs CHANGED
@@ -1,13 +1,11 @@
1
1
  // @triflux/core — barrel export (auto-generated by pack.mjs)
2
2
  // Adapters
3
- export { buildExecCommand, sleep, gte, FEATURES, CODEX_MCP_TRANSPORT_EXIT_CODE, normalizePathForShell, shellQuote } from './cli-adapter-base.mjs';
3
+ export { buildExecCommand, sleep, CODEX_MCP_TRANSPORT_EXIT_CODE, normalizePathForShell, shellQuote } from './cli-adapter-base.mjs';
4
4
  export { execute as executeCodex, buildExecArgs as buildCodexArgs, buildLaunchScript, getCircuitState as getCodexCircuit } from './codex-adapter.mjs';
5
- export { execute as executeGemini, buildExecArgs as buildGeminiArgs, getCircuitState as getGeminiCircuit } from './gemini-adapter.mjs';
6
5
  export { getCodexVersion, runPreflight } from './codex-preflight.mjs';
7
6
 
8
- // Routing & Intent
7
+ // Routing
9
8
  export { createRouter } from './router.mjs';
10
- export { classifyIntent, quickClassify, INTENT_CATEGORIES, refineClassification } from './intent.mjs';
11
9
 
12
10
  // State & Paths & Platform
13
11
  export * from './paths.mjs';
@@ -18,12 +16,8 @@ export { createHitlManager } from './hitl.mjs';
18
16
  export { createAssignCallbackServer } from './assign-callbacks.mjs';
19
17
  export { getHubUrl, getHubPipePath, requestJson, post, connectPipe, parseArgs, parseJsonSafe } from './bridge.mjs';
20
18
 
21
- // Fullcycle & Research
22
- export { createFullcycleRunId, getFullcycleRunDir, ensureFullcycleRunDir, saveFullcycleArtifact, readFullcycleArtifact, writeFullcycleState, readFullcycleState, shouldStopQaLoop } from './fullcycle.mjs';
23
- export { generateQueries, normalizeResults, buildReport, saveReport } from './research.mjs';
24
-
25
19
  // Reflexion (adaptive error learning)
26
- export { normalizeError, lookupSolution, learnFromError, reportOutcome, adaptiveRuleFromError, getActiveAdaptiveRules } from './reflexion.mjs';
20
+ export { normalizeError, adaptiveRuleFromError } from './reflexion.mjs';
27
21
 
28
22
  // Neural Memory (Lake 1.5)
29
23
  export { createAdaptiveEngine } from './adaptive.mjs';
@@ -64,30 +64,7 @@ export function isCtoDisabled() {
64
64
  return isExplicitlyOff("TFX_CTO");
65
65
  }
66
66
 
67
- export function isCtoManagerEnabled() {
68
- if (isCtoDisabled()) return false;
69
- return isExplicitlyOn("TFX_CTO_MANAGER");
70
- }
71
-
72
- export function getCtoHygieneApplyMode() {
73
- if (isExplicitlyOff("TFX_CTO")) return "off";
74
- return normalizeEnvValue("TFX_CTO_HYGIENE_APPLY") === "archive"
75
- ? "archive"
76
- : "off";
77
- }
78
-
79
67
  export function isCtoRetentionEnabled() {
80
68
  if (isExplicitlyOff("TFX_CTO")) return false;
81
69
  return isExplicitlyOn("TFX_CTO_RETENTION");
82
70
  }
83
-
84
- export function getCtoMode() {
85
- return normalizeEnvValue("TFX_CTO_MODE") === "bridge" ? "bridge" : "bounded";
86
- }
87
-
88
- export function getCtoMaxTokens() {
89
- const raw = normalizeEnvValue("TFX_CTO_MAX_TOKENS");
90
- if (!raw) return 0;
91
- const value = Number(raw);
92
- return Number.isSafeInteger(value) && value > 0 ? value : 0;
93
- }
@@ -9,7 +9,7 @@
9
9
  "root_cause": "원격 호스트가 PowerShell인데 bash 문법(2>/dev/null)을 사용",
10
10
  "dna_factor": "remote.os",
11
11
  "rule_template": "원격 호스트 {host}는 PowerShell 기본 셸. 2>/dev/null 대신 2>$null, && 대신 ;, $(cmd) 대신 $(cmd) PowerShell 구문 사용.",
12
- "fix": "hub/lib/ssh-command.mjs의 suppressStderr(os) 사용",
12
+ "fix": "PowerShell 호스트에서는 2>/dev/null 대신 2>$null 사용",
13
13
  "confidence_base": 0.95,
14
14
  "severity": "high"
15
15
  },
@@ -20,7 +20,7 @@
20
20
  "root_cause": "원격 호스트가 PowerShell인데 bash의 && 연결자를 사용",
21
21
  "dna_factor": "remote.os",
22
22
  "rule_template": "원격 호스트 {host}는 PowerShell 기본 셸. && 대신 ; 사용.",
23
- "fix": "hub/lib/ssh-command.mjs의 commandJoin(os) 사용",
23
+ "fix": "PowerShell 호스트에서는 && 대신 ; 사용",
24
24
  "confidence_base": 0.95,
25
25
  "severity": "high"
26
26
  },