@triflux/core 10.50.1 → 10.50.2

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 (55) hide show
  1. package/hub/index.mjs +2 -8
  2. package/hub/lib/cto-env.mjs +0 -23
  3. package/hub/paths.mjs +0 -6
  4. package/hub/pipeline/index.mjs +0 -190
  5. package/hub/pipeline/transitions.mjs +0 -10
  6. package/hub/reflexion.mjs +1 -84
  7. package/hud/cli-policy.mjs +1 -5
  8. package/hud/colors.mjs +0 -13
  9. package/hud/constants.mjs +4 -135
  10. package/hud/context-monitor.mjs +3 -64
  11. package/hud/hud-qos-status.mjs +43 -225
  12. package/hud/providers/claude.mjs +4 -16
  13. package/hud/providers/gemini.mjs +7 -698
  14. package/hud/renderers.mjs +40 -294
  15. package/hud/terminal.mjs +1 -63
  16. package/hud/utils.mjs +0 -24
  17. package/package.json +1 -2
  18. package/scripts/lib/gemini-profiles.mjs +1 -1
  19. package/scripts/lib/legacy-mcp-cleanup.mjs +14 -3
  20. package/scripts/lib/psmux-info.mjs +18 -49
  21. package/scripts/lib/skill-template.mjs +0 -26
  22. package/hub/codex-compat.mjs +0 -12
  23. package/hub/fullcycle.mjs +0 -113
  24. package/hub/gemini-adapter.mjs +0 -174
  25. package/hub/intent.mjs +0 -321
  26. package/hub/lib/mcp-response-cache.mjs +0 -239
  27. package/hub/lib/path-utils.mjs +0 -165
  28. package/hub/lib/timeline-adapter.mjs +0 -190
  29. package/hub/pipeline/gates/confidence.mjs +0 -75
  30. package/hub/pipeline/gates/consensus.mjs +0 -106
  31. package/hub/pipeline/gates/index.mjs +0 -9
  32. package/hub/pipeline/gates/selfcheck.mjs +0 -120
  33. package/hub/quality/deslop.mjs +0 -274
  34. package/hub/research.mjs +0 -155
  35. package/hub/role-control-events.mjs +0 -113
  36. package/hub/routing/complexity.mjs +0 -269
  37. package/hub/routing/index.mjs +0 -122
  38. package/hub/routing/q-learning.mjs +0 -387
  39. package/hub/token-mode.mjs +0 -228
  40. package/hud/mission-board.mjs +0 -56
  41. package/hud/providers/cto.mjs +0 -76
  42. package/scripts/lib/agent-json.mjs +0 -27
  43. package/scripts/lib/async.mjs +0 -174
  44. package/scripts/lib/cli-agy.mjs +0 -85
  45. package/scripts/lib/cli-codex.mjs +0 -62
  46. package/scripts/lib/cli-gemini.mjs +0 -37
  47. package/scripts/lib/env.mjs +0 -14
  48. package/scripts/lib/hub.mjs +0 -48
  49. package/scripts/lib/pid.mjs +0 -63
  50. package/scripts/lib/quota.mjs +0 -47
  51. package/scripts/lib/skill-state.mjs +0 -220
  52. package/scripts/lib/team.mjs +0 -75
  53. package/scripts/lib/timeout.mjs +0 -66
  54. package/scripts/lib/tmp.mjs +0 -26
  55. package/scripts/lib/toml.mjs +0 -132
@@ -143,7 +143,14 @@ function envWarning(server, home, env, warnings) {
143
143
  }
144
144
  }
145
145
 
146
- function patchJson(original, file, home, env, warnings) {
146
+ function patchJson(
147
+ original,
148
+ file,
149
+ home,
150
+ env,
151
+ warnings,
152
+ { withEnv = true } = {},
153
+ ) {
147
154
  const data = JSON.parse(original);
148
155
  if (!data || typeof data !== "object" || Array.isArray(data))
149
156
  throw new Error(`${file}: JSON 객체가 아닙니다`);
@@ -190,7 +197,7 @@ function patchJson(original, file, home, env, warnings) {
190
197
  ...kept,
191
198
  command: server.command,
192
199
  args: server.args,
193
- ...(server.envVars.length
200
+ ...(withEnv && server.envVars.length
194
201
  ? {
195
202
  env: {
196
203
  ...kept.env,
@@ -710,6 +717,8 @@ export function cleanupLegacyMcp({
710
717
  [join(repoRoot, ".mcp.json"), "json"],
711
718
  [join(home, ".codex/config.toml"), "toml"],
712
719
  [join(home, ".gemini/settings.json"), "json"],
720
+ // agy 는 \${VAR} 참조를 풀지 않으므로 env 없이 바꾸고 셸 환경을 물려받게 한다.
721
+ [join(home, ".gemini/config/mcp_config.json"), "agy"],
713
722
  ];
714
723
  const plans = [];
715
724
  let blocked = false;
@@ -721,7 +730,9 @@ export function cleanupLegacyMcp({
721
730
  const plan =
722
731
  kind === "toml"
723
732
  ? patchToml(original, file, home, env, result.warnings)
724
- : patchJson(original, file, home, env, result.warnings);
733
+ : patchJson(original, file, home, env, result.warnings, {
734
+ withEnv: kind !== "agy",
735
+ });
725
736
  if (plan.blocked) blocked = true;
726
737
  if (plan.count) plans.push({ file, target, original, ...plan });
727
738
  } catch {
@@ -10,7 +10,7 @@ export const PSMUX_REQUIRED_COMMANDS = [
10
10
 
11
11
  export const PSMUX_OPTIONAL_COMMANDS = ["detach-client"];
12
12
 
13
- // Windows 패키지 매니저 — winget/scoop/choco 는 Windows 전용
13
+ // Windows 전용 설치 명령
14
14
  export const PSMUX_INSTALL_COMMANDS = [
15
15
  "winget install psmux",
16
16
  "scoop install psmux",
@@ -25,50 +25,18 @@ export const PSMUX_UPDATE_COMMANDS = [
25
25
  "cargo install psmux --force",
26
26
  ];
27
27
 
28
- // macOS 전용 — brew 가 표준, cargo 는 cross-platform fallback
29
- export const PSMUX_INSTALL_COMMANDS_DARWIN = [
30
- "brew install psmux",
31
- "cargo install psmux",
32
- ];
33
-
34
- export const PSMUX_UPDATE_COMMANDS_DARWIN = [
35
- "brew upgrade psmux",
36
- "cargo install psmux --force",
37
- ];
38
-
39
- // Linux 전용 — cargo 만 cross-platform 지원, distro 패키지는 미제공
40
- export const PSMUX_INSTALL_COMMANDS_LINUX = ["cargo install psmux"];
41
-
42
- export const PSMUX_UPDATE_COMMANDS_LINUX = ["cargo install psmux --force"];
43
-
44
- // PR #258 OS-primary 분기 정책: macOS/Linux 는 tmux 가 POSIX 표준 primary,
45
- // psmux 는 선택형이다. 따라서 macOS/Linux 에서 "psmux 미설치"는 fallback 사유가
46
- // 아니며, tmux 까지 없을 때만 native/in-process fallback 을 고려한다.
47
- export const PSMUX_FALLBACK_NOTE_DARWIN =
48
- "macOS 에서는 tmux 가 표준 멀티플렉서이며, " +
49
- "psmux 미설치는 fallback 사유가 아닙니다. tmux 도 없을 때만 in-process/native fallback 을 사용합니다.";
50
-
51
- export const PSMUX_FALLBACK_NOTE_LINUX =
52
- "Linux 에서는 tmux 가 표준 멀티플렉서이며, " +
53
- "psmux 미설치는 fallback 사유가 아닙니다. tmux 도 없을 때만 in-process/native fallback 을 사용합니다.";
54
-
55
28
  export function getPsmuxInstallCommandsFor(platform = process.platform) {
56
- if (platform === "darwin") return PSMUX_INSTALL_COMMANDS_DARWIN;
57
- if (platform === "linux") return PSMUX_INSTALL_COMMANDS_LINUX;
58
- // win32 또는 기타 (BSD 등) → Windows 풀 리스트로 폴백 (변경 전 동작 유지)
59
- return PSMUX_INSTALL_COMMANDS;
29
+ if (platform === "win32") return PSMUX_INSTALL_COMMANDS;
30
+ return [platform === "darwin" ? "brew install tmux" : "apt install tmux"];
60
31
  }
61
32
 
62
33
  export function getPsmuxUpdateCommandsFor(platform = process.platform) {
63
- if (platform === "darwin") return PSMUX_UPDATE_COMMANDS_DARWIN;
64
- if (platform === "linux") return PSMUX_UPDATE_COMMANDS_LINUX;
65
- return PSMUX_UPDATE_COMMANDS;
66
- }
67
-
68
- export function getPsmuxFallbackNoteFor(platform = process.platform) {
69
- if (platform === "darwin") return PSMUX_FALLBACK_NOTE_DARWIN;
70
- if (platform === "linux") return PSMUX_FALLBACK_NOTE_LINUX;
71
- return null;
34
+ if (platform === "win32") return PSMUX_UPDATE_COMMANDS;
35
+ return [
36
+ platform === "darwin"
37
+ ? "brew upgrade tmux"
38
+ : "apt install --only-upgrade tmux",
39
+ ];
72
40
  }
73
41
 
74
42
  export function formatPsmuxCommandList(
@@ -82,10 +50,7 @@ export function formatPsmuxInstallGuidance(
82
50
  indent = "",
83
51
  platform = process.platform,
84
52
  ) {
85
- const commands = getPsmuxInstallCommandsFor(platform);
86
- const list = formatPsmuxCommandList(commands, indent);
87
- const note = getPsmuxFallbackNoteFor(platform);
88
- return note ? `${list}\n${indent}(${note})` : list;
53
+ return formatPsmuxCommandList(getPsmuxInstallCommandsFor(platform), indent);
89
54
  }
90
55
 
91
56
  export function formatPsmuxUpdateGuidance(
@@ -160,8 +125,8 @@ export function probePsmuxSupport(options = {}) {
160
125
  missingCommands,
161
126
  missingOptionalCommands,
162
127
  hasHelp: helpOutput.length > 0,
163
- installHint: formatPsmuxInstallGuidance(" "),
164
- updateHint: formatPsmuxUpdateGuidance(" "),
128
+ installHint: formatPsmuxInstallGuidance(" ", "win32"),
129
+ updateHint: formatPsmuxUpdateGuidance(" ", "win32"),
165
130
  };
166
131
  } catch {
167
132
  return {
@@ -173,8 +138,8 @@ export function probePsmuxSupport(options = {}) {
173
138
  missingCommands: [...PSMUX_REQUIRED_COMMANDS],
174
139
  missingOptionalCommands: [...PSMUX_OPTIONAL_COMMANDS],
175
140
  hasHelp: false,
176
- installHint: formatPsmuxInstallGuidance(" "),
177
- updateHint: formatPsmuxUpdateGuidance(" "),
141
+ installHint: formatPsmuxInstallGuidance(" ", "win32"),
142
+ updateHint: formatPsmuxUpdateGuidance(" ", "win32"),
178
143
  };
179
144
  }
180
145
  }
@@ -207,6 +172,8 @@ export function probePrimaryMultiplexerSupport(options = {}) {
207
172
  recommended: true,
208
173
  missingCommands: [],
209
174
  missingOptionalCommands: [],
175
+ installHint: formatPsmuxInstallGuidance(" ", platform),
176
+ updateHint: formatPsmuxUpdateGuidance(" ", platform),
210
177
  };
211
178
  } catch (error) {
212
179
  return {
@@ -218,6 +185,8 @@ export function probePrimaryMultiplexerSupport(options = {}) {
218
185
  recommended: false,
219
186
  missingCommands: ["tmux"],
220
187
  missingOptionalCommands: [],
188
+ installHint: formatPsmuxInstallGuidance(" ", platform),
189
+ updateHint: formatPsmuxUpdateGuidance(" ", platform),
221
190
  error: error?.message || String(error),
222
191
  };
223
192
  }
@@ -340,32 +340,6 @@ export function loadTemplatePartials(partialsDir) {
340
340
  return partials;
341
341
  }
342
342
 
343
- export function loadSkillManifest(skillDir) {
344
- const manifestPath = join(skillDir, "skill.json");
345
- if (!existsSync(manifestPath)) return null;
346
-
347
- const raw = readFileSync(manifestPath, "utf8");
348
- return JSON.parse(raw);
349
- }
350
-
351
- export function parseFrontmatterWithManifest(source, skillDir) {
352
- const manifest = skillDir ? loadSkillManifest(skillDir) : null;
353
- const { data: yamlData, body } = parseFrontmatter(source);
354
-
355
- if (!manifest) return { data: yamlData, body };
356
-
357
- const merged = { ...yamlData };
358
- if (manifest.name) merged.name = manifest.name;
359
- if (manifest.description) merged.description = manifest.description;
360
- if (manifest.triggers) merged.triggers = manifest.triggers;
361
- if (manifest.argument_hint) merged["argument-hint"] = manifest.argument_hint;
362
- if (manifest.internal != null) merged.internal = manifest.internal;
363
- if (manifest.deprecated != null) merged.deprecated = manifest.deprecated;
364
- if (manifest.superseded_by) merged["superseded-by"] = manifest.superseded_by;
365
-
366
- return { data: merged, body };
367
- }
368
-
369
343
  export function renderSkillTemplate(template, context = {}, options = {}) {
370
344
  const { partials = {}, includes = {}, includeBaseDir = "" } = options;
371
345
  return renderWithContext(template, context, partials, {
@@ -1,12 +0,0 @@
1
- // hub/codex-compat.mjs — backward-compatible facade for legacy imports
2
-
3
- /** @experimental 런타임 미연결 — 향후 통합 예정 */
4
- export {
5
- buildExecCommand,
6
- CODEX_MCP_EXECUTION_EXIT_CODE,
7
- CODEX_MCP_TRANSPORT_EXIT_CODE,
8
- escapePwshSingleQuoted,
9
- FEATURES,
10
- getCodexVersion,
11
- gte,
12
- } from "./cli-adapter-base.mjs";
package/hub/fullcycle.mjs DELETED
@@ -1,113 +0,0 @@
1
- // hub/fullcycle.mjs — tfx-fullcycle runtime artifact/state helpers
2
-
3
- import {
4
- existsSync,
5
- mkdirSync,
6
- readdirSync,
7
- readFileSync,
8
- statSync,
9
- writeFileSync,
10
- } from "node:fs";
11
- import { join, resolve } from "node:path";
12
- import { ensureTfxDirs, TFX_FULLCYCLE_DIR, TFX_PLANS_DIR } from "./paths.mjs";
13
-
14
- function safeResolve(baseDir, relativePath) {
15
- const base = resolve(baseDir);
16
- const target = resolve(join(baseDir, relativePath));
17
- if (!target.startsWith(base)) {
18
- throw new Error("Invalid fullcycle path: path traversal detected");
19
- }
20
- return target;
21
- }
22
-
23
- /** @experimental 런타임 미연결 — 향후 통합 예정 */
24
- export function createFullcycleRunId(now = new Date()) {
25
- return now
26
- .toISOString()
27
- .replace(/[:.]/g, "-")
28
- .replace("T", "_")
29
- .replace("Z", "Z");
30
- }
31
-
32
- export function getFullcycleRunDir(runId, baseDir = process.cwd()) {
33
- return safeResolve(baseDir, join(TFX_FULLCYCLE_DIR, runId));
34
- }
35
-
36
- export function ensureFullcycleRunDir(runId, baseDir = process.cwd()) {
37
- ensureTfxDirs(baseDir);
38
- const dir = getFullcycleRunDir(runId, baseDir);
39
- mkdirSync(dir, { recursive: true });
40
- return dir;
41
- }
42
-
43
- export function saveFullcycleArtifact(
44
- runId,
45
- filename,
46
- content,
47
- baseDir = process.cwd(),
48
- ) {
49
- if (!filename || typeof filename !== "string") {
50
- throw new Error("Artifact filename is required");
51
- }
52
-
53
- const dir = ensureFullcycleRunDir(runId, baseDir);
54
- const path = safeResolve(dir, filename);
55
- writeFileSync(path, content, "utf8");
56
- return path;
57
- }
58
-
59
- export function readFullcycleArtifact(
60
- runId,
61
- filename,
62
- baseDir = process.cwd(),
63
- ) {
64
- const dir = getFullcycleRunDir(runId, baseDir);
65
- const path = safeResolve(dir, filename);
66
- if (!existsSync(path)) return null;
67
- return readFileSync(path, "utf8");
68
- }
69
-
70
- export function writeFullcycleState(runId, state, baseDir = process.cwd()) {
71
- const payload = typeof state === "object" && state !== null ? state : {};
72
- const serialized = JSON.stringify(payload, null, 2);
73
- return saveFullcycleArtifact(runId, "state.json", serialized, baseDir);
74
- }
75
-
76
- export function readFullcycleState(runId, baseDir = process.cwd()) {
77
- const content = readFullcycleArtifact(runId, "state.json", baseDir);
78
- if (!content) return null;
79
- try {
80
- return JSON.parse(content);
81
- } catch {
82
- return null;
83
- }
84
- }
85
-
86
- export function findLatestInterviewPlan(baseDir = process.cwd()) {
87
- const plansDir = safeResolve(baseDir, TFX_PLANS_DIR);
88
- if (!existsSync(plansDir)) return null;
89
-
90
- const candidates = readdirSync(plansDir)
91
- .filter((name) => /^interview-.*\.md$/i.test(name))
92
- .map((name) => {
93
- const path = join(plansDir, name);
94
- const stats = statSync(path);
95
- return { name, path, mtimeMs: stats.mtimeMs };
96
- })
97
- .sort((a, b) => b.mtimeMs - a.mtimeMs);
98
-
99
- return candidates[0]?.path || null;
100
- }
101
-
102
- export function shouldStopQaLoop(failureHistory = [], maxRepeats = 3) {
103
- if (!Array.isArray(failureHistory) || maxRepeats <= 1) return false;
104
-
105
- const normalized = failureHistory
106
- .map((entry) => String(entry ?? "").trim())
107
- .filter(Boolean);
108
-
109
- if (normalized.length < maxRepeats) return false;
110
- const target = normalized.at(-1);
111
- const tail = normalized.slice(-maxRepeats);
112
- return tail.every((entry) => entry === target);
113
- }
@@ -1,174 +0,0 @@
1
- // hub/gemini-adapter.mjs — legacy Gemini adapter compatibility layer
2
- // The "gemini" route is retained as an alias, but execution is Antigravity/agy.
3
-
4
- import { mkdirSync } from "node:fs";
5
- import { tmpdir } from "node:os";
6
- import { join } from "node:path";
7
- import {
8
- resolveGeminiModel,
9
- resolveGeminiProfileForPurpose,
10
- } from "../scripts/lib/gemini-profiles.mjs";
11
- import {
12
- executeWithCircuitBroker,
13
- normalizePathForShell,
14
- runProcess,
15
- } from "./cli-adapter-base.mjs";
16
- import { isActivityLifecycleEnabled } from "./lib/worker-lifecycle.mjs";
17
- import { whichCommandAsync } from "./platform.mjs";
18
-
19
- function shellSingleQuote(value) {
20
- return `'${String(value).replace(/'/g, "'\\''")}'`;
21
- }
22
-
23
- // ── Antigravity stall inference ─────────────────────────────────
24
-
25
- function inferStallMode(stdout, stderr) {
26
- const text = `${stdout}\n${stderr}`.toLowerCase();
27
- if (/(rate.?limit|quota|resource.?exhaust|429)/u.test(text))
28
- return "rate_limited";
29
- if (
30
- /(unauthorized|forbidden|auth|login|token|credential|api.?key)/u.test(text)
31
- )
32
- return "auth_stall";
33
- if (/\bmcp\b|playwright|tavily|brave|sequential|server/u.test(text))
34
- return "mcp_stall";
35
- return "timeout";
36
- }
37
-
38
- // ── Preflight ───────────────────────────────────────────────────
39
-
40
- async function runPreflight(opts = {}) {
41
- const agyPath = await whichCommandAsync("agy");
42
- if (!agyPath) {
43
- return {
44
- agyPath: null,
45
- warnings: [
46
- "Antigravity CLI not found. Install Antigravity and ensure `agy` is available on PATH.",
47
- ],
48
- excludeMcpServers: [],
49
- ok: false,
50
- };
51
- }
52
-
53
- const warnings = [];
54
- const excludeMcpServers = [];
55
-
56
- for (const name of Array.isArray(opts.mcpServers) ? opts.mcpServers : []) {
57
- const server = String(name ?? "").trim();
58
- if (!server) continue;
59
- // Antigravity MCP health는 best-effort: 실행 시점 probe는 수행하지 않음.
60
- }
61
-
62
- return { agyPath, warnings, excludeMcpServers, ok: true };
63
- }
64
-
65
- // ── Command building ────────────────────────────────────────────
66
-
67
- function buildGeminiCommand(prompt, resultFile, opts = {}) {
68
- // agy 의 --print 는 값(프롬프트)을 받는 플래그다. 1.1.27 부터 stdin 프롬프트는
69
- // "empty prompt" 로 거부되므로 프롬프트를 --print 의 값으로, 맨 끝에 넘긴다.
70
- const parts = ["agy", "--dangerously-skip-permissions"];
71
- if (typeof opts.model === "string" && opts.model.trim()) {
72
- parts.push("--model", shellSingleQuote(opts.model.trim()));
73
- }
74
- parts.push("--print", shellSingleQuote(prompt));
75
-
76
- if (resultFile) {
77
- return `${parts.join(" ")} > ${shellSingleQuote(normalizePathForShell(resultFile))} 2>${shellSingleQuote(normalizePathForShell(resultFile + ".err"))}`;
78
- }
79
-
80
- return parts.join(" ");
81
- }
82
-
83
- function resolveAntigravityModel(opts = {}) {
84
- // effort 는 역할별 SSOT(scripts/lib/gemini-profiles.mjs). 명시 model 이 우선한다.
85
- const explicit = typeof opts.model === "string" ? opts.model.trim() : "";
86
- if (explicit)
87
- return resolveGeminiModel(explicit, {
88
- profilesPath: opts.geminiProfilesPath,
89
- });
90
- if (!opts.role && !opts.agent) return "";
91
- return resolveGeminiModel(
92
- resolveGeminiProfileForPurpose(opts.role || opts.agent),
93
- {
94
- profilesPath: opts.geminiProfilesPath,
95
- },
96
- );
97
- }
98
-
99
- function buildAttempts(opts, preflight) {
100
- const timeout = Number.isFinite(opts.timeout) ? opts.timeout : 900_000;
101
- const base = {
102
- timeout,
103
- model: resolveAntigravityModel(opts),
104
- allowedMcpServers: Array.isArray(opts.mcpServers)
105
- ? [...opts.mcpServers]
106
- : [],
107
- excludeMcpServers: [...(preflight.excludeMcpServers || [])],
108
- };
109
- if (opts.retryOnFail === false) return [base];
110
- const retryTimeout = isActivityLifecycleEnabled() ? timeout : timeout * 2;
111
- return [base, { ...base, timeout: retryTimeout, allowedMcpServers: [] }];
112
- }
113
-
114
- // ── Public: buildExecArgs ───────────────────────────────────────
115
-
116
- export function buildExecArgs(opts = {}) {
117
- const prompt = typeof opts.prompt === "string" ? opts.prompt : "";
118
- return buildGeminiCommand(prompt, opts.resultFile || null, {
119
- model: resolveAntigravityModel(opts),
120
- allowedMcpServers: opts.mcpServers,
121
- });
122
- }
123
-
124
- // ── Execution ───────────────────────────────────────────────────
125
-
126
- async function runGemini(prompt, workdir, preflight, attempt, lease) {
127
- const dir = join(tmpdir(), "triflux-antigravity-exec");
128
- mkdirSync(dir, { recursive: true });
129
- const resultFile = join(
130
- dir,
131
- `agy-${Date.now()}-${Math.random().toString(36).slice(2)}.txt`,
132
- );
133
- const command = buildGeminiCommand(prompt, resultFile, {
134
- model: attempt.model,
135
- allowedMcpServers: attempt.allowedMcpServers,
136
- excludeMcpServers: attempt.excludeMcpServers,
137
- });
138
- // PRD A1: lease.env 가 있으면 spawn env 에 적용 (예: GOOGLE_API_KEY 등 account-specific
139
- // 환경변수). lease 가 null 이면 default 동작 유지 (부모 env inherit).
140
- const spawnEnv =
141
- lease?.env && typeof lease.env === "object"
142
- ? { ...process.env, ...lease.env }
143
- : undefined;
144
- return runProcess(command, workdir, attempt.timeout, {
145
- resultFile,
146
- inferStallMode,
147
- spawnEnv,
148
- });
149
- }
150
-
151
- // ── Public API ──────────────────────────────────────────────────
152
-
153
- export async function getCircuitState() {
154
- const brokerMod = await import("./account-broker.mjs");
155
- if (!brokerMod.broker) return { state: "closed", failures: [] };
156
- const snap = brokerMod.broker
157
- .snapshot()
158
- .filter((a) => a.provider === "gemini");
159
- return snap.length
160
- ? { state: snap[0].circuitState, accounts: snap }
161
- : { state: "closed", failures: [] };
162
- }
163
-
164
- export function execute(opts = {}) {
165
- return executeWithCircuitBroker({
166
- // Account-broker still stores this compatibility bucket as "gemini";
167
- // execution itself is routed to agy above.
168
- provider: "gemini",
169
- runFn: runGemini,
170
- preflightFn: (o) => runPreflight({ mcpServers: o.mcpServers }),
171
- buildAttemptsFn: buildAttempts,
172
- opts,
173
- });
174
- }