triflux 10.24.0 → 10.25.1
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/bin/triflux.mjs +118 -4
- package/config/mcp-registry.json +85 -0
- package/config/routing-policy.json +124 -0
- package/hooks/safety-guard.mjs +8 -1
- package/hub/team/backend.mjs +44 -6
- package/hub/team/claude-daemon-control.mjs +219 -0
- package/hub/team/claude-native-bridge.mjs +785 -0
- package/hub/team/claude-session-projection.mjs +65 -0
- package/hub/team/cli/commands/start/index.mjs +7 -0
- package/hub/team/cli/commands/start/parse-args.mjs +51 -1
- package/hub/team/cli/commands/start/start-headless.mjs +12 -0
- package/hub/team/headless-bridge-session.mjs +17 -0
- package/hub/team/headless.mjs +418 -50
- package/hub/team/swarm-cli.mjs +15 -0
- package/hub/team/swarm-hypervisor.mjs +171 -7
- package/hub/team/worktree-lifecycle.mjs +4 -16
- package/hud/constants.mjs +6 -0
- package/hud/hud-qos-status.mjs +11 -3
- package/hud/renderers.mjs +5 -11
- package/package.json +6 -3
- package/scripts/__tests__/install-mcp-gateway-startup.test.mjs +367 -0
- package/scripts/__tests__/mcp-gateway-health-check.test.mjs +208 -0
- package/scripts/__tests__/mcp-gateway-wrapper-check.test.mjs +54 -0
- package/scripts/__tests__/mcp-guard-engine-http-headers.test.mjs +49 -3
- package/scripts/__tests__/mcp-guard-engine-policy-sync.test.mjs +203 -0
- package/scripts/__tests__/mcp-guard-engine-stdio-sync.test.mjs +171 -0
- package/scripts/__tests__/mcp-guard-engine-sync-http-headers.test.mjs +47 -1
- package/scripts/__tests__/mcp-guard-engine-watch-http-headers.test.mjs +3 -4
- package/scripts/__tests__/mcp-guard-engine.test.mjs +87 -2
- package/scripts/__tests__/release-governance.test.mjs +161 -1
- package/scripts/__tests__/tfx-route-bash-node-parity.test.mjs +204 -0
- package/scripts/__tests__/tfx-route-node-entry.test.mjs +403 -0
- package/scripts/__tests__/tfx-route-phase1-modules.test.mjs +268 -0
- package/scripts/codex-mcp-gateway-sync.mjs +1 -1
- package/scripts/doctor-diagnose.mjs +14 -8
- package/scripts/headless-guard.mjs +9 -4
- package/scripts/install-mcp-gateway-startup.mjs +503 -0
- package/scripts/lib/agent-json.mjs +27 -0
- package/scripts/lib/async.mjs +174 -0
- package/scripts/lib/cli-agy.mjs +62 -0
- package/scripts/lib/cli-claude.mjs +78 -0
- package/scripts/lib/cli-codex.mjs +199 -0
- package/scripts/lib/cli-gemini.mjs +67 -0
- package/scripts/lib/env.mjs +14 -0
- package/scripts/lib/hub.mjs +48 -0
- package/scripts/lib/mcp-gateway-health-check.mjs +159 -0
- package/scripts/lib/mcp-gateway-servers.mjs +70 -0
- package/scripts/lib/mcp-gateway-wrapper-check.mjs +35 -0
- package/scripts/lib/mcp-guard-engine.mjs +524 -25
- package/scripts/lib/mcp-manifest.mjs +2 -2
- package/scripts/lib/pid.mjs +63 -0
- package/scripts/lib/quota.mjs +47 -0
- package/scripts/lib/team.mjs +75 -0
- package/scripts/lib/timeout.mjs +66 -0
- package/scripts/lib/tmp.mjs +26 -0
- package/scripts/lib/toml.mjs +113 -0
- package/scripts/mcp-gateway-config.mjs +2 -31
- package/scripts/mcp-gateway-ensure.mjs +24 -6
- package/scripts/mcp-gateway-start.mjs +16 -59
- package/scripts/mcp-safety-guard.mjs +3 -1
- package/scripts/pack.mjs +23 -18
- package/scripts/preflight-cache.mjs +79 -9
- package/scripts/release/check-packages-mirror.mjs +20 -4
- package/scripts/release/prepare.mjs +2 -1
- package/scripts/release/publish.mjs +100 -7
- package/scripts/release/verify.mjs +39 -13
- package/scripts/release/version-manifest.json +8 -0
- package/scripts/setup.mjs +40 -7
- package/scripts/sync-hub-mcp-settings.mjs +353 -28
- package/scripts/tfx-route.mjs +302 -0
- package/scripts/tfx-route.sh +163 -26
- package/skills/tfx-analysis/SKILL.md +12 -12
- package/skills/tfx-auto/SKILL.md +14 -12
- package/skills/tfx-auto/SKILL.md.tmpl +5 -5
- package/skills/tfx-consensus/SKILL.md.tmpl +8 -8
- package/skills/tfx-debate/SKILL.md +1 -1
- package/skills/tfx-debate/SKILL.md.tmpl +5 -5
- package/skills/tfx-doctor/SKILL.md +5 -5
- package/skills/tfx-doctor/SKILL.md.tmpl +5 -5
- package/skills/tfx-fullcycle/SKILL.md.tmpl +8 -8
- package/skills/tfx-hub/SKILL.md +2 -2
- package/skills/tfx-hub/SKILL.md.tmpl +2 -2
- package/skills/tfx-index/SKILL.md +9 -9
- package/skills/tfx-index/SKILL.md.tmpl +9 -9
- package/skills/tfx-interview/SKILL.md +29 -29
- package/skills/tfx-interview/SKILL.md.tmpl +24 -24
- package/skills/tfx-multi/SKILL.md.tmpl +4 -4
- package/skills/tfx-multi/references/agent-wrapper-rules.md +3 -3
- package/skills/tfx-panel/SKILL.md.tmpl +5 -5
- package/skills/tfx-persist/SKILL.md.tmpl +15 -15
- package/skills/tfx-plan/SKILL.md +14 -14
- package/skills/tfx-plan/SKILL.md.tmpl +6 -6
- package/skills/tfx-plan/skill.json +1 -1
- package/skills/tfx-profile/SKILL.md +10 -10
- package/skills/tfx-profile/SKILL.md.tmpl +10 -10
- package/skills/tfx-profile/skill.json +1 -1
- package/skills/tfx-prune/SKILL.md +4 -4
- package/skills/tfx-prune/SKILL.md.tmpl +4 -4
- package/skills/tfx-qa/SKILL.md +4 -4
- package/skills/tfx-research/SKILL.md +9 -9
- package/skills/tfx-research/SKILL.md.tmpl +12 -12
- package/skills/tfx-research/skill.json +1 -1
- package/skills/tfx-review/SKILL.md +13 -13
- package/skills/tfx-setup/SKILL.md +25 -8
- package/skills/tfx-setup/SKILL.md.tmpl +8 -8
- package/skills/tfx-ship/SKILL.md +4 -5
- package/skills/tfx-swarm/SKILL.md.tmpl +4 -4
package/bin/triflux.mjs
CHANGED
|
@@ -61,6 +61,7 @@ import { serializeHandoff } from "../scripts/lib/handoff.mjs";
|
|
|
61
61
|
import {
|
|
62
62
|
addRegistryServer,
|
|
63
63
|
createDefaultRegistry,
|
|
64
|
+
discoverProjectMcpTargets,
|
|
64
65
|
inspectRegistry,
|
|
65
66
|
inspectRegistryStatus,
|
|
66
67
|
removeRegistryServer,
|
|
@@ -333,7 +334,8 @@ const CLI_COMMAND_SCHEMAS = Object.freeze({
|
|
|
333
334
|
],
|
|
334
335
|
},
|
|
335
336
|
sync: {
|
|
336
|
-
usage:
|
|
337
|
+
usage:
|
|
338
|
+
"tfx mcp sync [--json] [--all-projects [root]] [--dry-run] [--exclude <glob>]",
|
|
337
339
|
options: [
|
|
338
340
|
{
|
|
339
341
|
name: "--json",
|
|
@@ -2515,11 +2517,12 @@ function buildMcpStatusRows(statusInfo) {
|
|
|
2515
2517
|
.filter((row) => row.type === "registry")
|
|
2516
2518
|
.map((row) => {
|
|
2517
2519
|
let detail = "";
|
|
2518
|
-
if (row.status === "present")
|
|
2520
|
+
if (row.status === "present")
|
|
2521
|
+
detail = row.actualUrl || row.actualCommand || row.expectedUrl;
|
|
2519
2522
|
else if (row.status === "missing") detail = "registry only";
|
|
2520
2523
|
else if (row.status === "missing-file") detail = "config missing";
|
|
2521
2524
|
else if (row.status === "mismatch")
|
|
2522
|
-
detail = `expected ${row.expectedUrl}`;
|
|
2525
|
+
detail = `expected ${row.expectedUrl || row.expectedCommand}`;
|
|
2523
2526
|
else if (row.status === "invalid-config") detail = "parse error";
|
|
2524
2527
|
else if (row.status === "stdio") detail = "configured as stdio";
|
|
2525
2528
|
return [
|
|
@@ -4667,6 +4670,68 @@ async function cmdDoctor(options = {}) {
|
|
|
4667
4670
|
}
|
|
4668
4671
|
}
|
|
4669
4672
|
|
|
4673
|
+
// ── MCP Gateway Health ──
|
|
4674
|
+
// install-mcp-gateway-startup 으로 띄운 LaunchAgent/systemd daemon 의 stdout
|
|
4675
|
+
// (~/.local/state/triflux/mcp-gateway.out.log) 를 파싱해 missing-env 등으로
|
|
4676
|
+
// skip 된 server 를 잡는다. 로그가 없으면 gateway 미설치/미실행으로 침묵.
|
|
4677
|
+
section("MCP Gateway Health");
|
|
4678
|
+
{
|
|
4679
|
+
const { checkMcpGatewayHealth, summarizeMcpGatewayHealth } = await import(
|
|
4680
|
+
"../scripts/lib/mcp-gateway-health-check.mjs"
|
|
4681
|
+
);
|
|
4682
|
+
const gatewayHealth = checkMcpGatewayHealth();
|
|
4683
|
+
const summary = summarizeMcpGatewayHealth(gatewayHealth);
|
|
4684
|
+
addDoctorCheck(report, {
|
|
4685
|
+
name: "mcp-gateway-health",
|
|
4686
|
+
status: summary.level === "warn" ? "warning" : "ok",
|
|
4687
|
+
log_path: gatewayHealth.logPath,
|
|
4688
|
+
findings: gatewayHealth.findings,
|
|
4689
|
+
started: gatewayHealth.started,
|
|
4690
|
+
skipped: gatewayHealth.skipped,
|
|
4691
|
+
...(summary.fix ? { fix: summary.fix } : {}),
|
|
4692
|
+
});
|
|
4693
|
+
if (summary.level === "skip") {
|
|
4694
|
+
info(summary.message);
|
|
4695
|
+
} else if (summary.level === "ok") {
|
|
4696
|
+
ok(summary.message);
|
|
4697
|
+
} else {
|
|
4698
|
+
warn(summary.message);
|
|
4699
|
+
if (summary.fix) info(`수정: ${summary.fix}`);
|
|
4700
|
+
issues++;
|
|
4701
|
+
}
|
|
4702
|
+
}
|
|
4703
|
+
|
|
4704
|
+
// ── MCP Gateway Wrapper ──
|
|
4705
|
+
// 로그가 생기기 전 단계에서 wrapper 자체가 secrets.env 를 source 하는지 확인한다.
|
|
4706
|
+
section("MCP Gateway Wrapper");
|
|
4707
|
+
{
|
|
4708
|
+
const { checkWrapperSourcing } = await import(
|
|
4709
|
+
"../scripts/lib/mcp-gateway-wrapper-check.mjs"
|
|
4710
|
+
);
|
|
4711
|
+
const wrapperCheck = await checkWrapperSourcing();
|
|
4712
|
+
addDoctorCheck(report, {
|
|
4713
|
+
name: "mcp-gateway-wrapper-sourcing",
|
|
4714
|
+
status:
|
|
4715
|
+
wrapperCheck.status === "warn" ? "warning" : wrapperCheck.status,
|
|
4716
|
+
path: wrapperCheck.wrapperPath,
|
|
4717
|
+
...(wrapperCheck.message ? { message: wrapperCheck.message } : {}),
|
|
4718
|
+
...(wrapperCheck.suggestedFix
|
|
4719
|
+
? { fix: wrapperCheck.suggestedFix }
|
|
4720
|
+
: {}),
|
|
4721
|
+
});
|
|
4722
|
+
|
|
4723
|
+
if (wrapperCheck.status === "ok") {
|
|
4724
|
+
ok("wrapper sources secrets.env");
|
|
4725
|
+
} else if (wrapperCheck.status === "warn") {
|
|
4726
|
+
warn(wrapperCheck.message);
|
|
4727
|
+
info(`수정: ${wrapperCheck.suggestedFix}`);
|
|
4728
|
+
issues++;
|
|
4729
|
+
} else {
|
|
4730
|
+
warn("mcp-gateway wrapper not installed");
|
|
4731
|
+
if (wrapperCheck.message) info(wrapperCheck.message);
|
|
4732
|
+
}
|
|
4733
|
+
}
|
|
4734
|
+
|
|
4670
4735
|
// ── Codex Config Health (BUG-H #132) ──
|
|
4671
4736
|
// _codex_config_swap 의 restore 가 Windows lock/ACL 로 실패하면
|
|
4672
4737
|
// ~/.codex/config.toml.pre-exec 가 남아 [mcp_servers.*] 섹션이 영구 손실된다.
|
|
@@ -5776,10 +5841,45 @@ function cmdMcp(args = [], options = {}) {
|
|
|
5776
5841
|
|
|
5777
5842
|
case "sync": {
|
|
5778
5843
|
const registryState = ensureValidRegistryState();
|
|
5779
|
-
const
|
|
5844
|
+
const allProjectsIndex = args.indexOf("--all-projects");
|
|
5845
|
+
const allProjectsRoot =
|
|
5846
|
+
allProjectsIndex >= 0 &&
|
|
5847
|
+
args[allProjectsIndex + 1] &&
|
|
5848
|
+
!String(args[allProjectsIndex + 1]).startsWith("--")
|
|
5849
|
+
? args[allProjectsIndex + 1]
|
|
5850
|
+
: null;
|
|
5851
|
+
const dryRun = args.includes("--dry-run");
|
|
5852
|
+
const excludes = args.flatMap((arg, index) =>
|
|
5853
|
+
arg === "--exclude" && args[index + 1] ? [args[index + 1]] : [],
|
|
5854
|
+
);
|
|
5855
|
+
const allProjects =
|
|
5856
|
+
allProjectsIndex >= 0
|
|
5857
|
+
? discoverProjectMcpTargets({
|
|
5858
|
+
root: allProjectsRoot || undefined,
|
|
5859
|
+
exclude: excludes,
|
|
5860
|
+
})
|
|
5861
|
+
: null;
|
|
5862
|
+
const result = dryRun
|
|
5863
|
+
? { actions: [] }
|
|
5864
|
+
: syncRegistryTargets({
|
|
5865
|
+
registry: registryState.registry,
|
|
5866
|
+
...(allProjects ? { targets: allProjects.targets } : {}),
|
|
5867
|
+
});
|
|
5780
5868
|
if (json) {
|
|
5781
5869
|
printJson({
|
|
5782
5870
|
registry_path: registryState.path,
|
|
5871
|
+
...(allProjects
|
|
5872
|
+
? {
|
|
5873
|
+
dry_run: dryRun,
|
|
5874
|
+
all_projects: {
|
|
5875
|
+
root: allProjects.root,
|
|
5876
|
+
maxDepth: allProjects.maxDepth,
|
|
5877
|
+
exclude: allProjects.exclude,
|
|
5878
|
+
count: allProjects.targets.length,
|
|
5879
|
+
},
|
|
5880
|
+
targets: allProjects.targets,
|
|
5881
|
+
}
|
|
5882
|
+
: {}),
|
|
5783
5883
|
actions: result.actions,
|
|
5784
5884
|
});
|
|
5785
5885
|
return;
|
|
@@ -5787,8 +5887,22 @@ function cmdMcp(args = [], options = {}) {
|
|
|
5787
5887
|
|
|
5788
5888
|
console.log(`\n ${AMBER}${BOLD}⬡ triflux mcp sync${RESET} ${VER}\n`);
|
|
5789
5889
|
console.log(` ${LINE}`);
|
|
5890
|
+
if (allProjects) {
|
|
5891
|
+
section("Project Targets");
|
|
5892
|
+
info(`${allProjects.targets.length}개 파일 (${allProjects.root})`);
|
|
5893
|
+
for (const target of allProjects.targets) {
|
|
5894
|
+
info(formatPathForDisplay(target.filePath));
|
|
5895
|
+
}
|
|
5896
|
+
if (dryRun) {
|
|
5897
|
+
console.log("");
|
|
5898
|
+
return;
|
|
5899
|
+
}
|
|
5900
|
+
}
|
|
5790
5901
|
section("Actions");
|
|
5791
5902
|
for (const action of result.actions) {
|
|
5903
|
+
for (const warning of action.warnings || []) {
|
|
5904
|
+
process.stderr.write(`${warning}\n`);
|
|
5905
|
+
}
|
|
5792
5906
|
const label = `${action.label} ${DIM}(${formatPathForDisplay(action.filePath)})${RESET}`;
|
|
5793
5907
|
if (action.status === "updated") ok(`${label} → updated`);
|
|
5794
5908
|
else if (action.status === "warning") warn(`${label} → warning`);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "mcp-registry-schema",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"description": "MCP 서버 중앙 레지스트리 — 진실의 원천",
|
|
5
|
+
"defaults": {
|
|
6
|
+
"transport": "hub-url",
|
|
7
|
+
"hub_base": "http://127.0.0.1:27888"
|
|
8
|
+
},
|
|
9
|
+
"policy_notes": {
|
|
10
|
+
"policy": "Server policy is the SSOT for client sync shape: hosted writes url+headers, gateway-sse writes http://127.0.0.1:81XX/sse with SSE metadata, and stdio writes command/args/env.",
|
|
11
|
+
"transport": "Server transport accepts \"hub-url\" for triflux Hub URL flow, \"http\" for direct Streamable HTTP MCP endpoints, or \"stdio\" for upstream-stdio-only MCP servers. Gateway-backed stdio upstreams use policy:\"gateway-sse\" plus gateway_port so clients receive URL/SSE config.",
|
|
12
|
+
"headers": "Optional headers are allowed only for HTTP-compatible transports. Each header value must be a descriptor: {\"value\":\"literal\"} for non-secret static values, {\"env\":\"ENV_VAR_NAME\"} for secrets resolved at sync/runtime, or {\"env\":\"ENV_VAR_NAME\",\"prefix\":\"Bearer \"} for common authorization formats.",
|
|
13
|
+
"secret_safety": "Resolved secret values must not be written back to this registry file. Missing env vars warn during sync and do not emit empty secret headers.",
|
|
14
|
+
"sync_denylist": "Array of client:server strings skipped by proactive registry sync, for example gemini:tfx-hub."
|
|
15
|
+
},
|
|
16
|
+
"servers": {
|
|
17
|
+
"tfx-hub": {
|
|
18
|
+
"policy": "hosted",
|
|
19
|
+
"transport": "hub-url",
|
|
20
|
+
"url": "http://127.0.0.1:27888/mcp",
|
|
21
|
+
"safe": true,
|
|
22
|
+
"targets": ["claude", "gemini", "codex", "antigravity"],
|
|
23
|
+
"description": "triflux Hub MCP 서버"
|
|
24
|
+
},
|
|
25
|
+
"context7": {
|
|
26
|
+
"policy": "hosted",
|
|
27
|
+
"transport": "http",
|
|
28
|
+
"url": "https://mcp.context7.com/mcp",
|
|
29
|
+
"safe": true,
|
|
30
|
+
"targets": ["claude", "gemini", "codex", "antigravity"],
|
|
31
|
+
"description": "Upstash Context7 — 라이브러리 문서/코드 컨텍스트 (HTTP MCP, API key 불필요)"
|
|
32
|
+
},
|
|
33
|
+
"exa": {
|
|
34
|
+
"policy": "hosted",
|
|
35
|
+
"transport": "http",
|
|
36
|
+
"url": "https://mcp.exa.ai/mcp",
|
|
37
|
+
"headers": {
|
|
38
|
+
"Authorization": { "env": "EXA_API_KEY", "prefix": "Bearer " }
|
|
39
|
+
},
|
|
40
|
+
"safe": true,
|
|
41
|
+
"targets": ["claude", "gemini", "codex", "antigravity"],
|
|
42
|
+
"description": "Exa neural/semantic web search — 학술/기술 깊이. Key 발급: https://exa.ai/dashboard → secrets.env의 EXA_API_KEY"
|
|
43
|
+
},
|
|
44
|
+
"serena": {
|
|
45
|
+
"policy": "gateway-sse",
|
|
46
|
+
"gateway_port": 8105,
|
|
47
|
+
"safe": true,
|
|
48
|
+
"targets": ["claude", "gemini", "codex", "antigravity"],
|
|
49
|
+
"description": "Serena MCP — supergateway singleton SSE endpoint (:8105)"
|
|
50
|
+
},
|
|
51
|
+
"brave-search": {
|
|
52
|
+
"policy": "gateway-sse",
|
|
53
|
+
"gateway_port": 8101,
|
|
54
|
+
"safe": true,
|
|
55
|
+
"targets": ["claude", "gemini", "codex", "antigravity"],
|
|
56
|
+
"description": "Brave Search MCP — supergateway singleton SSE endpoint (:8101). BRAVE_API_KEY 환경변수 필요 (https://brave.com/search/api/). secrets.env 의 BRAVE_API_KEY 참조."
|
|
57
|
+
},
|
|
58
|
+
"tavily": {
|
|
59
|
+
"policy": "hosted",
|
|
60
|
+
"transport": "http",
|
|
61
|
+
"url": "https://mcp.tavily.com/mcp",
|
|
62
|
+
"headers": {
|
|
63
|
+
"Authorization": { "env": "TAVILY_API_KEY", "prefix": "Bearer " }
|
|
64
|
+
},
|
|
65
|
+
"safe": true,
|
|
66
|
+
"targets": ["claude", "gemini", "codex", "antigravity"],
|
|
67
|
+
"description": "Tavily research — 비용/운영/DX/일반 웹. Key 발급: https://app.tavily.com/home → secrets.env의 TAVILY_API_KEY"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"policies": {
|
|
71
|
+
"stdio_action": "replace-with-hub",
|
|
72
|
+
"unknown_server_action": "warn",
|
|
73
|
+
"sync_denylist": [],
|
|
74
|
+
"watched_paths": [
|
|
75
|
+
"~/.gemini/settings.json",
|
|
76
|
+
"~/.codex/config.toml",
|
|
77
|
+
"~/.claude.json",
|
|
78
|
+
"~/.claude/settings.json",
|
|
79
|
+
"~/.claude/settings.local.json",
|
|
80
|
+
".claude/mcp.json",
|
|
81
|
+
".mcp.json",
|
|
82
|
+
"~/.gemini/config/mcp_config.json"
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"freshness": {
|
|
4
|
+
"max_snapshot_age_ms": 900000,
|
|
5
|
+
"max_cross_provider_skew_ms": 300000,
|
|
6
|
+
"local_clock_skew_guard_ms": 60000
|
|
7
|
+
},
|
|
8
|
+
"thresholds": {
|
|
9
|
+
"reset_imminent_ms": 86400000,
|
|
10
|
+
"exhaustion_high_percent": 80,
|
|
11
|
+
"exhaustion_low_percent": 20,
|
|
12
|
+
"expiry_drain_window_ms": 604800000,
|
|
13
|
+
"drain_usage_ceiling_percent": 50
|
|
14
|
+
},
|
|
15
|
+
"scenarios": [
|
|
16
|
+
{
|
|
17
|
+
"id": "S1",
|
|
18
|
+
"name": "claude-reset-imminent-drain",
|
|
19
|
+
"when": {
|
|
20
|
+
"provider": "claude",
|
|
21
|
+
"weeklyResetsAtLteMs": 86400000,
|
|
22
|
+
"weeklyPercentLt": 100,
|
|
23
|
+
"hasMultipleAccounts": true
|
|
24
|
+
},
|
|
25
|
+
"then": {
|
|
26
|
+
"lane": "multi",
|
|
27
|
+
"primary_cli": "claude",
|
|
28
|
+
"reserve_codex_share": 0.1,
|
|
29
|
+
"reason": "claude-weekly-reset-imminent-drain-fleet"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "S2",
|
|
34
|
+
"name": "codex-reset-imminent-drain",
|
|
35
|
+
"when": {
|
|
36
|
+
"provider": "codex",
|
|
37
|
+
"weeklyResetsAtLteMs": 86400000,
|
|
38
|
+
"weeklyPercentLt": 100
|
|
39
|
+
},
|
|
40
|
+
"then": {
|
|
41
|
+
"lane": "swarm",
|
|
42
|
+
"primary_cli": "codex",
|
|
43
|
+
"reserve_claude_share": 0.1,
|
|
44
|
+
"reason": "codex-weekly-reset-imminent-drain-fleet"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "S3",
|
|
49
|
+
"name": "claude-burned-codex-fresh",
|
|
50
|
+
"when": {
|
|
51
|
+
"claude_weekly_percent_gte": 80,
|
|
52
|
+
"codex_weekly_percent_lt": 20,
|
|
53
|
+
"claude_weekly_resets_at_gt_ms": 172800000
|
|
54
|
+
},
|
|
55
|
+
"then": {
|
|
56
|
+
"lane": "single",
|
|
57
|
+
"primary_cli": "codex",
|
|
58
|
+
"claude_lanes_allowed": ["explore", "final-challenge"],
|
|
59
|
+
"reason": "claude-burned-codex-fresh-rebalance"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "S4",
|
|
64
|
+
"name": "claude-multi-account-staggered",
|
|
65
|
+
"when": {
|
|
66
|
+
"provider": "claude",
|
|
67
|
+
"accountCountGte": 2,
|
|
68
|
+
"staggered": true
|
|
69
|
+
},
|
|
70
|
+
"then": {
|
|
71
|
+
"lane": "multi",
|
|
72
|
+
"primary_cli": "claude",
|
|
73
|
+
"rank_by": "expiring_capacity_score",
|
|
74
|
+
"reason": "claude-multi-account-staggered-reset"
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"id": "S5",
|
|
79
|
+
"name": "claude-parallel-leasemany",
|
|
80
|
+
"when": {
|
|
81
|
+
"provider": "claude",
|
|
82
|
+
"request_team_size_gte": 2,
|
|
83
|
+
"accountCountGte": 2
|
|
84
|
+
},
|
|
85
|
+
"then": {
|
|
86
|
+
"lane": "swarm",
|
|
87
|
+
"primary_cli": "claude",
|
|
88
|
+
"atomic": "leaseMany",
|
|
89
|
+
"atomic_policy": "balanced",
|
|
90
|
+
"reason": "claude-parallel-multi-account"
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"id": "S6",
|
|
95
|
+
"name": "account-expiry-drain",
|
|
96
|
+
"when": {
|
|
97
|
+
"expiresAtLteMs": 604800000,
|
|
98
|
+
"weeklyPercentLt": 50,
|
|
99
|
+
"circuitStateNotOpen": true
|
|
100
|
+
},
|
|
101
|
+
"then": {
|
|
102
|
+
"lane": "single",
|
|
103
|
+
"riskTier": "drain",
|
|
104
|
+
"elevated_weight": 2.0,
|
|
105
|
+
"reason": "account-expiry-drain-before-loss"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
],
|
|
109
|
+
"fallback": {
|
|
110
|
+
"default_mode": "codex-default",
|
|
111
|
+
"default_cli": "codex",
|
|
112
|
+
"default_model": "gpt-5.5",
|
|
113
|
+
"triggers": [
|
|
114
|
+
"hud_null",
|
|
115
|
+
"hud_stale_gte_15min",
|
|
116
|
+
"weekly_reset_in_past",
|
|
117
|
+
"cross_provider_skew_exceeded",
|
|
118
|
+
"broker_empty",
|
|
119
|
+
"all_circuits_open",
|
|
120
|
+
"mirror_drift",
|
|
121
|
+
"flag_disabled"
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
}
|
package/hooks/safety-guard.mjs
CHANGED
|
@@ -340,7 +340,14 @@ function main() {
|
|
|
340
340
|
return hasSegmentInvocation(cmd, WT_DIRECT_PATTERNS);
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
-
|
|
343
|
+
// wt.exe는 Windows Terminal 전용. macOS/Linux에서는 PATH에 존재하지 않아
|
|
344
|
+
// 차단해도 dormant이며, 문서/로그 텍스트의 wt 단어가 false positive로 잡힐
|
|
345
|
+
// 위험만 있다. win32에서만 차단 적용.
|
|
346
|
+
// SAFETY_GUARD_FORCE_PLATFORM env로 테스트에서 platform 시뮬레이션 가능
|
|
347
|
+
// (mac CI에서 win32 차단 동작을 검증하기 위함).
|
|
348
|
+
const guardPlatform =
|
|
349
|
+
process.env.SAFETY_GUARD_FORCE_PLATFORM || process.platform;
|
|
350
|
+
if (guardPlatform === "win32" && isWtDirectInvocation(command)) {
|
|
344
351
|
blockCommand(WT_DIRECT_BLOCK_MESSAGE, command);
|
|
345
352
|
}
|
|
346
353
|
|
package/hub/team/backend.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// hub/team/backend.mjs — CLI 백엔드 추상화 레이어
|
|
2
|
-
// 각 CLI(codex/gemini/claude)의 명령 빌드 로직을 클래스로 캡슐화한다.
|
|
2
|
+
// 각 CLI(codex/gemini/claude/antigravity)의 명령 빌드 로직을 클래스로 캡슐화한다.
|
|
3
3
|
// v7.2.2
|
|
4
|
+
import { writeFileSync } from "node:fs";
|
|
4
5
|
import { createRequire } from "node:module";
|
|
5
6
|
|
|
6
7
|
import { buildExecArgs } from "../codex-adapter.mjs";
|
|
@@ -16,6 +17,22 @@ export function buildGeminiCommand(prompt, resultFile, { isWindows } = {}) {
|
|
|
16
17
|
return `gemini --yolo --prompt ${prompt} --output-format text > '${resultFile}' 2>'${resultFile}.err' < /dev/null`;
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
export function buildAntigravityCommand(
|
|
21
|
+
prompt,
|
|
22
|
+
resultFile,
|
|
23
|
+
{ isWindows } = {},
|
|
24
|
+
) {
|
|
25
|
+
// Persist prompt to a sibling file so the shell never interpolates raw
|
|
26
|
+
// prompt content. Without this the Unix branch let `$()`, backticks, `\\`,
|
|
27
|
+
// and `"` reach the shell verbatim (P1: shell injection).
|
|
28
|
+
const promptFile = `${resultFile}.prompt`;
|
|
29
|
+
writeFileSync(promptFile, prompt);
|
|
30
|
+
if (isWindows) {
|
|
31
|
+
return `Get-Content -Raw '${promptFile}' | agy --print --dangerously-skip-permissions > '${resultFile}' 2>'${resultFile}.err'`;
|
|
32
|
+
}
|
|
33
|
+
return `agy --print --dangerously-skip-permissions < '${promptFile}' > '${resultFile}' 2>'${resultFile}.err'`;
|
|
34
|
+
}
|
|
35
|
+
|
|
19
36
|
const _require = createRequire(import.meta.url);
|
|
20
37
|
|
|
21
38
|
// ── 백엔드 클래스 ──────────────────────────────────────────────────────────
|
|
@@ -77,19 +94,40 @@ export class ClaudeBackend {
|
|
|
77
94
|
}
|
|
78
95
|
}
|
|
79
96
|
|
|
97
|
+
export class AntigravityBackend {
|
|
98
|
+
name() {
|
|
99
|
+
return "antigravity";
|
|
100
|
+
}
|
|
101
|
+
command() {
|
|
102
|
+
return "agy";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
buildArgs(prompt, resultFile, opts = {}) {
|
|
106
|
+
return buildAntigravityCommand(prompt, resultFile, {
|
|
107
|
+
isWindows: IS_WINDOWS,
|
|
108
|
+
...opts,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
env() {
|
|
113
|
+
return {};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
80
117
|
// ── 레지스트리 ─────────────────────────────────────────────────────────────
|
|
81
118
|
|
|
82
|
-
/** @type {Map<string, CodexBackend|GeminiBackend|ClaudeBackend>} */
|
|
119
|
+
/** @type {Map<string, CodexBackend|GeminiBackend|ClaudeBackend|AntigravityBackend>} */
|
|
83
120
|
const backends = new Map([
|
|
84
121
|
["codex", new CodexBackend()],
|
|
85
122
|
["gemini", new GeminiBackend()],
|
|
86
123
|
["claude", new ClaudeBackend()],
|
|
124
|
+
["antigravity", new AntigravityBackend()],
|
|
87
125
|
]);
|
|
88
126
|
|
|
89
127
|
/**
|
|
90
128
|
* 백엔드 이름으로 조회한다.
|
|
91
|
-
* @param {string} name — "codex" | "gemini" | "claude"
|
|
92
|
-
* @returns {CodexBackend|GeminiBackend|ClaudeBackend}
|
|
129
|
+
* @param {string} name — "codex" | "gemini" | "claude" | "antigravity"
|
|
130
|
+
* @returns {CodexBackend|GeminiBackend|ClaudeBackend|AntigravityBackend}
|
|
93
131
|
* @throws {Error} 등록되지 않은 이름
|
|
94
132
|
*/
|
|
95
133
|
export function getBackend(name) {
|
|
@@ -102,7 +140,7 @@ export function getBackend(name) {
|
|
|
102
140
|
* 에이전트명 또는 CLI명을 Backend로 해석한다.
|
|
103
141
|
* agent-map.json을 통해 에이전트명 → CLI명으로 변환 후 레지스트리에서 조회한다.
|
|
104
142
|
* @param {string} agentOrCli — "executor", "codex", "designer" 등
|
|
105
|
-
* @returns {CodexBackend|GeminiBackend|ClaudeBackend}
|
|
143
|
+
* @returns {CodexBackend|GeminiBackend|ClaudeBackend|AntigravityBackend}
|
|
106
144
|
*/
|
|
107
145
|
export function getBackendForAgent(agentOrCli) {
|
|
108
146
|
const agentMap = _require("./agent-map.json");
|
|
@@ -112,7 +150,7 @@ export function getBackendForAgent(agentOrCli) {
|
|
|
112
150
|
|
|
113
151
|
/**
|
|
114
152
|
* 등록된 모든 백엔드를 반환한다.
|
|
115
|
-
* @returns {Array<CodexBackend|GeminiBackend|ClaudeBackend>}
|
|
153
|
+
* @returns {Array<CodexBackend|GeminiBackend|ClaudeBackend|AntigravityBackend>}
|
|
116
154
|
*/
|
|
117
155
|
export function listBackends() {
|
|
118
156
|
return Array.from(backends.values());
|