@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.
- package/hub/index.mjs +2 -8
- package/hub/lib/cto-env.mjs +0 -23
- package/hub/paths.mjs +0 -6
- package/hub/pipeline/index.mjs +0 -190
- package/hub/pipeline/transitions.mjs +0 -10
- package/hub/reflexion.mjs +1 -84
- package/hud/cli-policy.mjs +1 -5
- package/hud/colors.mjs +0 -13
- package/hud/constants.mjs +4 -135
- package/hud/context-monitor.mjs +3 -64
- package/hud/hud-qos-status.mjs +43 -225
- package/hud/providers/claude.mjs +4 -16
- package/hud/providers/gemini.mjs +7 -698
- package/hud/renderers.mjs +40 -294
- package/hud/terminal.mjs +1 -63
- package/hud/utils.mjs +0 -24
- package/package.json +1 -2
- package/scripts/lib/gemini-profiles.mjs +1 -1
- package/scripts/lib/legacy-mcp-cleanup.mjs +14 -3
- package/scripts/lib/psmux-info.mjs +18 -49
- package/scripts/lib/skill-template.mjs +0 -26
- package/hub/codex-compat.mjs +0 -12
- package/hub/fullcycle.mjs +0 -113
- package/hub/gemini-adapter.mjs +0 -174
- package/hub/intent.mjs +0 -321
- package/hub/lib/mcp-response-cache.mjs +0 -239
- package/hub/lib/path-utils.mjs +0 -165
- package/hub/lib/timeline-adapter.mjs +0 -190
- package/hub/pipeline/gates/confidence.mjs +0 -75
- package/hub/pipeline/gates/consensus.mjs +0 -106
- package/hub/pipeline/gates/index.mjs +0 -9
- package/hub/pipeline/gates/selfcheck.mjs +0 -120
- package/hub/quality/deslop.mjs +0 -274
- package/hub/research.mjs +0 -155
- package/hub/role-control-events.mjs +0 -113
- package/hub/routing/complexity.mjs +0 -269
- package/hub/routing/index.mjs +0 -122
- package/hub/routing/q-learning.mjs +0 -387
- package/hub/token-mode.mjs +0 -228
- package/hud/mission-board.mjs +0 -56
- package/hud/providers/cto.mjs +0 -76
- package/scripts/lib/agent-json.mjs +0 -27
- package/scripts/lib/async.mjs +0 -174
- package/scripts/lib/cli-agy.mjs +0 -85
- package/scripts/lib/cli-codex.mjs +0 -62
- package/scripts/lib/cli-gemini.mjs +0 -37
- package/scripts/lib/env.mjs +0 -14
- package/scripts/lib/hub.mjs +0 -48
- package/scripts/lib/pid.mjs +0 -63
- package/scripts/lib/quota.mjs +0 -47
- package/scripts/lib/skill-state.mjs +0 -220
- package/scripts/lib/team.mjs +0 -75
- package/scripts/lib/timeout.mjs +0 -66
- package/scripts/lib/tmp.mjs +0 -26
- 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(
|
|
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
|
|
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 === "
|
|
57
|
-
|
|
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 === "
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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, {
|
package/hub/codex-compat.mjs
DELETED
|
@@ -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
|
-
}
|
package/hub/gemini-adapter.mjs
DELETED
|
@@ -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
|
-
}
|