@triflux/core 10.50.2 → 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.
- package/hooks/agy-session-hook.mjs +11 -56
- package/hooks/codex-session-hook.mjs +11 -101
- package/hub/bridge.mjs +1 -79
- package/hub/cli-adapter-base.mjs +12 -90
- package/hub/codex-adapter.mjs +0 -1
- package/hub/codex-preflight.mjs +5 -28
- package/hub/index.mjs +1 -1
- package/hub/lib/known-errors.json +2 -2
- package/hub/lib/process-utils.mjs +0 -123
- package/hub/lib/spawn-trace.mjs +3 -2
- package/hub/lib/timeout-defaults.mjs +0 -1
- package/hub/role-contract.mjs +0 -2
- package/hud/cli-policy.mjs +0 -22
- package/hud/constants.mjs +11 -26
- package/hud/hud-qos-status.mjs +23 -51
- package/hud/providers/gemini.mjs +105 -20
- package/hud/renderers.mjs +50 -104
- package/hud/utils.mjs +2 -2
- package/package.json +1 -3
- package/scripts/lib/agent-route-policy.mjs +162 -14
- package/scripts/lib/claudemd-scanner.mjs +2 -199
- package/scripts/lib/cli-claude.mjs +0 -2
- package/scripts/lib/context.mjs +3 -19
- package/scripts/lib/env-probe.mjs +5 -64
- package/scripts/lib/gemini-profiles.mjs +2 -69
- package/scripts/lib/machine-profile.mjs +3 -17
- package/scripts/lib/mcp-filter.mjs +109 -53
- package/scripts/lib/mcp-health.mjs +2 -2
- package/scripts/lib/mcp-server-catalog.mjs +2 -2
- package/scripts/lib/psmux-info.mjs +11 -59
- package/scripts/lib/session-presence.mjs +1 -1
- package/scripts/lib/skill-template.mjs +0 -245
- package/hub/dynamic-routing-engine.mjs +0 -511
- package/hub/lib/risk-tier.mjs +0 -53
- package/hub/lib/ssh-command.mjs +0 -231
- package/hub/lib/staged-file-detect.mjs +0 -43
- package/hub/lib/tfx-route-args.mjs +0 -353
- package/hub/routing-snapshot.mjs +0 -261
- package/mesh/index.mjs +0 -69
- package/mesh/mesh-budget.mjs +0 -137
- package/mesh/mesh-heartbeat.mjs +0 -100
- package/mesh/mesh-protocol.mjs +0 -96
- package/mesh/mesh-queue.mjs +0 -163
- package/mesh/mesh-registry.mjs +0 -78
- package/mesh/mesh-router.mjs +0 -77
- package/scripts/lib/dynamic-route-cli.mjs +0 -107
|
@@ -488,68 +488,6 @@ function hasExactGbrainServe(commandLine) {
|
|
|
488
488
|
);
|
|
489
489
|
}
|
|
490
490
|
|
|
491
|
-
function hasGbrainServeCommand(commandLine) {
|
|
492
|
-
return /(^|\s)gbrain\s+serve(\s|$)/i.test(commandLine.trim());
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
function hasFsmonitorDaemonCommand(commandLine) {
|
|
496
|
-
return FSMONITOR_DAEMON_PATTERN.test(commandLine);
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
function categoryForShardProcess(proc, context) {
|
|
500
|
-
const name = normalizeName(proc.name);
|
|
501
|
-
const commandLine = normalizeCommandLine(proc.commandLine);
|
|
502
|
-
const worktreePath = normalizeCommandLine(context.worktreePath || "");
|
|
503
|
-
const shardMarker = context.shardName
|
|
504
|
-
? `.codex-swarm\\wt-${context.shardName}`
|
|
505
|
-
: "";
|
|
506
|
-
const hasWorktree = worktreePath && commandLine.includes(worktreePath);
|
|
507
|
-
const hasShardMarker = shardMarker && commandLine.includes(shardMarker);
|
|
508
|
-
|
|
509
|
-
if (name === "node.exe" || name === "node") {
|
|
510
|
-
if (hasWorktree || hasShardMarker) return "node";
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
if (name === "bash.exe" || name === "bash" || name === "sh") {
|
|
514
|
-
if (hasWorktree) return "bash";
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
if (name === "conhost.exe" || name === "conhost") {
|
|
518
|
-
if (
|
|
519
|
-
context.sessionIds?.length > 0 &&
|
|
520
|
-
context.sessionIds.some((id) => id && commandLine.includes(id))
|
|
521
|
-
) {
|
|
522
|
-
return "conhost";
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
if (name === "bun.exe" || name === "bun") {
|
|
527
|
-
if ((hasWorktree || hasShardMarker) && hasGbrainServeCommand(commandLine)) {
|
|
528
|
-
return "bun";
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
if (name === "git.exe" || name === "git") {
|
|
533
|
-
if (
|
|
534
|
-
(hasWorktree || hasShardMarker) &&
|
|
535
|
-
hasFsmonitorDaemonCommand(commandLine)
|
|
536
|
-
) {
|
|
537
|
-
return "git";
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
return null;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
function killPid(
|
|
545
|
-
pid,
|
|
546
|
-
{ killFn = process.kill, protectedPids = new Set() } = {},
|
|
547
|
-
) {
|
|
548
|
-
if (protectedPids.has(pid)) return false;
|
|
549
|
-
killFn(pid, "SIGKILL");
|
|
550
|
-
return true;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
491
|
/**
|
|
554
492
|
* Return a process tree rooted at `rootPid`.
|
|
555
493
|
*
|
|
@@ -730,67 +668,6 @@ export function findProcessesByCommandLine(
|
|
|
730
668
|
});
|
|
731
669
|
}
|
|
732
670
|
|
|
733
|
-
/**
|
|
734
|
-
* Cleanup processes associated with a swarm shard.
|
|
735
|
-
*
|
|
736
|
-
* Sequence: kill known top PID snapshots, scan scoped command lines, skip
|
|
737
|
-
* protected PIDs, and kill only narrowly gated runtime categories.
|
|
738
|
-
*
|
|
739
|
-
* @param {{worktreePath?: string, sessionIds?: string[], topPids?: Array<number | {pid: number}>, runId?: string, shardName?: string, dryRun?: boolean, isWindows?: boolean, spawnSyncFn?: typeof spawnSync, killFn?: typeof process.kill, protectedPids?: Set<number>}} opts
|
|
740
|
-
* @returns {{killed: number, scanned: number, skipped: number, byCategory: {node: number, bash: number, conhost: number, bun: number, git: number}}}
|
|
741
|
-
*/
|
|
742
|
-
export function cleanupShardProcesses({
|
|
743
|
-
worktreePath,
|
|
744
|
-
sessionIds = [],
|
|
745
|
-
topPids = [],
|
|
746
|
-
runId,
|
|
747
|
-
shardName,
|
|
748
|
-
dryRun = false,
|
|
749
|
-
isWindows = IS_WINDOWS,
|
|
750
|
-
spawnSyncFn = spawnSync,
|
|
751
|
-
killFn = process.kill,
|
|
752
|
-
protectedPids,
|
|
753
|
-
} = {}) {
|
|
754
|
-
const protectedSet = getProtectedPids({
|
|
755
|
-
protectedPids,
|
|
756
|
-
isWindows,
|
|
757
|
-
spawnSyncFn,
|
|
758
|
-
includeAncestorScan: true,
|
|
759
|
-
});
|
|
760
|
-
const byCategory = { node: 0, bash: 0, conhost: 0, bun: 0, git: 0 };
|
|
761
|
-
let killed = 0;
|
|
762
|
-
let skipped = 0;
|
|
763
|
-
|
|
764
|
-
if (!dryRun && topPids.length > 0) {
|
|
765
|
-
killed += killProcessTreeSnapshot(topPids, {
|
|
766
|
-
killFn,
|
|
767
|
-
protectedPids: protectedSet,
|
|
768
|
-
}).killed;
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
const scanned = getProcessSnapshot({ isWindows, spawnSyncFn });
|
|
772
|
-
|
|
773
|
-
for (const proc of scanned) {
|
|
774
|
-
const category = categoryForShardProcess(proc, {
|
|
775
|
-
worktreePath,
|
|
776
|
-
sessionIds,
|
|
777
|
-
shardName,
|
|
778
|
-
});
|
|
779
|
-
if (!category) continue;
|
|
780
|
-
byCategory[category]++;
|
|
781
|
-
if (protectedSet.has(proc.pid)) {
|
|
782
|
-
skipped++;
|
|
783
|
-
continue;
|
|
784
|
-
}
|
|
785
|
-
if (dryRun) continue;
|
|
786
|
-
try {
|
|
787
|
-
if (killPid(proc.pid, { killFn, protectedPids: protectedSet })) killed++;
|
|
788
|
-
} catch {}
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
return { killed, scanned: scanned.length, skipped, byCategory };
|
|
792
|
-
}
|
|
793
|
-
|
|
794
671
|
/**
|
|
795
672
|
* Cleanup narrowly gated orphan runtime processes.
|
|
796
673
|
*
|
package/hub/lib/spawn-trace.mjs
CHANGED
|
@@ -7,13 +7,14 @@ import { join } from "node:path";
|
|
|
7
7
|
const LOG_DIR = join(homedir(), ".triflux", "logs");
|
|
8
8
|
const DEDUPE_WINDOW_MS = 5_000;
|
|
9
9
|
const RATE_WINDOW_MS = 1_000;
|
|
10
|
+
const DEFAULT_MAX_SPAWN_PER_SEC = 100;
|
|
10
11
|
// multi-worker headless dispatch 가 1 초 안에 ~25-30+ psmux/tmux 명령을 호출한다.
|
|
11
12
|
// 2-worker dispatchBatch 가 default 30 limit 을 초과해 `rate_limit` throw 로 mac
|
|
12
13
|
// 호환성 회귀 (smoke test 발견, 2026-05-15). 100 으로 상향 — 폭주 안전망은
|
|
13
14
|
// 여전히 (default 100/sec) 유지, env override 로 보수 설정 가능.
|
|
14
15
|
export let MAX_SPAWN_PER_SEC = resolvePositiveInteger(
|
|
15
16
|
process.env.TRIFLUX_MAX_SPAWN_RATE,
|
|
16
|
-
|
|
17
|
+
DEFAULT_MAX_SPAWN_PER_SEC,
|
|
17
18
|
);
|
|
18
19
|
export const MAX_TOTAL_DESCENDANTS = resolvePositiveInteger(
|
|
19
20
|
process.env.TRIFLUX_MAX_DESCENDANTS,
|
|
@@ -160,7 +161,7 @@ export function getMaxSpawnPerSec() {
|
|
|
160
161
|
export function reload() {
|
|
161
162
|
MAX_SPAWN_PER_SEC = resolvePositiveInteger(
|
|
162
163
|
process.env.TRIFLUX_MAX_SPAWN_RATE,
|
|
163
|
-
|
|
164
|
+
DEFAULT_MAX_SPAWN_PER_SEC,
|
|
164
165
|
);
|
|
165
166
|
return getMaxSpawnPerSec();
|
|
166
167
|
}
|
|
@@ -5,7 +5,6 @@ export const MAX_DURATION_MS = 86_400_000;
|
|
|
5
5
|
export const DEFAULT_ASSIGN_TIMEOUT_MS = 600_000;
|
|
6
6
|
export const DEFAULT_ASSIGN_TTL_MS = 600_000;
|
|
7
7
|
export const DEFAULT_HANDOFF_TTL_MS = 600_000;
|
|
8
|
-
export const DEFAULT_SWARM_LOCK_TTL_MS = 600_000;
|
|
9
8
|
export const DEFAULT_REGISTER_TIMEOUT_SEC = 600;
|
|
10
9
|
export const REGISTER_HEARTBEAT_GRACE_MS = 120_000;
|
|
11
10
|
export const DEFAULT_STALL_INTERVENTION_SEC = 1_200;
|
package/hub/role-contract.mjs
CHANGED
|
@@ -216,8 +216,6 @@ export function reduceRoleReachabilityTransition(state, trigger) {
|
|
|
216
216
|
: { state: transition.to, action: transition.action };
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
export const reduceRoleReachability = reduceRoleReachabilityTransition;
|
|
220
|
-
|
|
221
219
|
function snapshotLegacyDetails(snapshot) {
|
|
222
220
|
const legacy = isRecord(snapshot.legacy) ? snapshot.legacy : {};
|
|
223
221
|
return {
|
package/hud/cli-policy.mjs
CHANGED
|
@@ -41,25 +41,3 @@ export function resolveHudCliVisibility(env = process.env, opts = {}) {
|
|
|
41
41
|
return { showCodex: true, antigravityAllowed: true, policy: null };
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* antigravity / gemini 슬롯 행을 그릴지 판정한다.
|
|
47
|
-
*
|
|
48
|
-
* antigravity 가 허용이면 기존 동작 그대로 항상 그린다. 정책으로 꺼졌을 때만
|
|
49
|
-
* gemini 폴백 정보가 하나라도 있는지 보고, 전부 비어 있으면 행을 생략한다.
|
|
50
|
-
*
|
|
51
|
-
* @param {{
|
|
52
|
-
* antigravityAllowed?: boolean,
|
|
53
|
-
* antigravityReady?: boolean,
|
|
54
|
-
* geminiEmail?: unknown,
|
|
55
|
-
* }} [args]
|
|
56
|
-
* @returns {boolean}
|
|
57
|
-
*/
|
|
58
|
-
export function shouldRenderGeminiFallbackRow({
|
|
59
|
-
antigravityAllowed = true,
|
|
60
|
-
antigravityReady = false,
|
|
61
|
-
geminiEmail = null,
|
|
62
|
-
} = {}) {
|
|
63
|
-
if (antigravityAllowed || antigravityReady) return true;
|
|
64
|
-
return Boolean(geminiEmail);
|
|
65
|
-
}
|
package/hud/constants.mjs
CHANGED
|
@@ -31,9 +31,6 @@ export const CLAUDE_BAND_MARKER_DIR = join(
|
|
|
31
31
|
);
|
|
32
32
|
export const CLAUDE_BAND_MARKER_TTL_MS = 12 * 60 * 60 * 1000;
|
|
33
33
|
|
|
34
|
-
// tfx-multi 세션 상태 디렉터리
|
|
35
|
-
export const TEAM_STATE_DIR =
|
|
36
|
-
process.env.TFX_HUB_PID_DIR || join(homedir(), ".claude", "cache", "tfx-hub");
|
|
37
34
|
export const CONTEXT_MONITOR_CACHE_PATH = join(
|
|
38
35
|
homedir(),
|
|
39
36
|
".claude",
|
|
@@ -149,11 +146,9 @@ export const CODEX_REFRESH_LOCK_PATH = join(
|
|
|
149
146
|
);
|
|
150
147
|
export const SPAWN_LOCK_TTL_MS = 30 * 1000; // 30초 spawn dedup
|
|
151
148
|
|
|
152
|
-
export const GEMINI_OAUTH_PATH = join(homedir(), ".gemini", "oauth_creds.json");
|
|
153
149
|
export const ANTIGRAVITY_OAUTH_PATHS = [
|
|
154
150
|
join(homedir(), ".gemini", "antigravity-cli", "oauth_creds.json"),
|
|
155
151
|
join(homedir(), ".gemini", "antigravity-cli", "credentials.json"),
|
|
156
|
-
GEMINI_OAUTH_PATH,
|
|
157
152
|
];
|
|
158
153
|
export const ANTIGRAVITY_KEYCHAIN_SERVICE = "gemini";
|
|
159
154
|
export const ANTIGRAVITY_KEYCHAIN_ACCOUNT = "antigravity";
|
|
@@ -163,33 +158,23 @@ export const ANTIGRAVITY_SETTINGS_PATH = join(
|
|
|
163
158
|
"antigravity-cli",
|
|
164
159
|
"settings.json",
|
|
165
160
|
);
|
|
166
|
-
|
|
167
|
-
export const ANTIGRAVITY_MODEL_ABBREV = {
|
|
168
|
-
// Flash 세대와 등급을 구분한다.
|
|
169
|
-
"Gemini 3.8 Flash (High)": "8h",
|
|
170
|
-
"Gemini 3.8 Flash (Medium)": "8m",
|
|
171
|
-
"Gemini 3.8 Flash (Low)": "8l",
|
|
172
|
-
"Gemini 3.7 Flash (High)": "7h",
|
|
173
|
-
"Gemini 3.7 Flash (Medium)": "7m",
|
|
174
|
-
"Gemini 3.7 Flash (Low)": "7l",
|
|
175
|
-
"Gemini 3.6 Flash (High)": "6h",
|
|
176
|
-
"Gemini 3.6 Flash (Medium)": "6m",
|
|
177
|
-
"Gemini 3.6 Flash (Low)": "6l",
|
|
178
|
-
"Gemini 3.5 Flash (High)": "Fh",
|
|
179
|
-
"Gemini 3.5 Flash (Medium)": "Fm",
|
|
180
|
-
"Gemini 3.5 Flash (Low)": "Fl",
|
|
181
|
-
};
|
|
182
|
-
export const TFX_PREFLIGHT_CACHE_PATH = join(
|
|
161
|
+
export const ANTIGRAVITY_QUOTA_CACHE_PATH = join(
|
|
183
162
|
homedir(),
|
|
184
163
|
".claude",
|
|
185
164
|
"cache",
|
|
186
|
-
"
|
|
165
|
+
"antigravity-quota-cache.json",
|
|
187
166
|
);
|
|
188
|
-
export const
|
|
189
|
-
|
|
167
|
+
export const ANTIGRAVITY_REFRESH_LOCK_PATH = join(
|
|
168
|
+
homedir(),
|
|
169
|
+
".claude",
|
|
170
|
+
"cache",
|
|
171
|
+
".antigravity-refresh-lock",
|
|
172
|
+
);
|
|
173
|
+
export const ANTIGRAVITY_QUOTA_STALE_MS = 5 * 60 * 1000;
|
|
174
|
+
export const ANTIGRAVITY_REFRESH_FLAG = "--refresh-antigravity-quota";
|
|
190
175
|
export const ACCOUNT_LABEL_WIDTH = 10;
|
|
191
176
|
export const PROVIDER_PREFIX_WIDTH = 2;
|
|
192
|
-
export const PERCENT_CELL_WIDTH =
|
|
177
|
+
export const PERCENT_CELL_WIDTH = 4;
|
|
193
178
|
export const TIME_CELL_INNER_WIDTH = 6;
|
|
194
179
|
|
|
195
180
|
export const CLAUDE_REFRESH_FLAG = "--refresh-claude-usage";
|
package/hud/hud-qos-status.mjs
CHANGED
|
@@ -3,10 +3,7 @@
|
|
|
3
3
|
import { existsSync, statSync } from "node:fs";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
-
import {
|
|
7
|
-
resolveHudCliVisibility,
|
|
8
|
-
shouldRenderGeminiFallbackRow,
|
|
9
|
-
} from "./cli-policy.mjs";
|
|
6
|
+
import { resolveHudCliVisibility } from "./cli-policy.mjs";
|
|
10
7
|
import {
|
|
11
8
|
bold,
|
|
12
9
|
claudeOrange,
|
|
@@ -19,12 +16,11 @@ import {
|
|
|
19
16
|
import {
|
|
20
17
|
ACCOUNTS_CONFIG_PATH,
|
|
21
18
|
ACCOUNTS_STATE_PATH,
|
|
19
|
+
ANTIGRAVITY_REFRESH_FLAG,
|
|
22
20
|
CLAUDE_BAND_MARKER_DIR,
|
|
23
21
|
CLAUDE_BAND_MARKER_TTL_MS,
|
|
24
22
|
CLAUDE_REFRESH_FLAG,
|
|
25
23
|
CODEX_REFRESH_FLAG,
|
|
26
|
-
TFX_PREFLIGHT_CACHE_PATH,
|
|
27
|
-
TFX_PREFLIGHT_CACHE_STALE_MS,
|
|
28
24
|
} from "./constants.mjs";
|
|
29
25
|
import { buildContextUsageView } from "./context-monitor.mjs";
|
|
30
26
|
import {
|
|
@@ -41,15 +37,14 @@ import {
|
|
|
41
37
|
} from "./providers/codex.mjs";
|
|
42
38
|
import {
|
|
43
39
|
getAntigravityAccountLabel,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
readAntigravityQuotaSnapshot,
|
|
41
|
+
refreshAntigravityQuotaCache,
|
|
42
|
+
scheduleAntigravityQuotaRefresh,
|
|
47
43
|
} from "./providers/gemini.mjs";
|
|
48
44
|
import {
|
|
49
45
|
getClaudeRows,
|
|
50
46
|
getMicroLine,
|
|
51
47
|
getProviderRow,
|
|
52
|
-
getTeamRow,
|
|
53
48
|
renderAlignedRows,
|
|
54
49
|
} from "./renderers.mjs";
|
|
55
50
|
import { selectTier } from "./terminal.mjs";
|
|
@@ -64,22 +59,21 @@ async function main() {
|
|
|
64
59
|
await refreshCodexRateLimitsCache();
|
|
65
60
|
return;
|
|
66
61
|
}
|
|
62
|
+
if (process.argv.includes(ANTIGRAVITY_REFRESH_FLAG)) {
|
|
63
|
+
refreshAntigravityQuotaCache();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
67
66
|
|
|
68
67
|
const stdinPromise = readStdinJson();
|
|
69
|
-
const preflightCache = readJson(TFX_PREFLIGHT_CACHE_PATH, null);
|
|
70
|
-
const preflightTimestamp = Number(preflightCache?.timestamp);
|
|
71
|
-
const preflightFresh =
|
|
72
|
-
Number.isFinite(preflightTimestamp) &&
|
|
73
|
-
Date.now() - preflightTimestamp <= TFX_PREFLIGHT_CACHE_STALE_MS;
|
|
74
68
|
const { showCodex, antigravityAllowed } = resolveHudCliVisibility();
|
|
75
|
-
const antigravityReady =
|
|
76
|
-
antigravityAllowed &&
|
|
77
|
-
preflightFresh &&
|
|
78
|
-
preflightCache?.antigravity?.ok === true;
|
|
79
69
|
const accountsConfig = readJson(ACCOUNTS_CONFIG_PATH, { providers: {} });
|
|
80
70
|
const accountsState = readJson(ACCOUNTS_STATE_PATH, { providers: {} });
|
|
81
71
|
const claudeUsageSnapshot = readClaudeUsageSnapshot();
|
|
82
72
|
const codexSnapshot = readCodexRateLimitSnapshot();
|
|
73
|
+
const antigravitySnapshot = antigravityAllowed
|
|
74
|
+
? readAntigravityQuotaSnapshot()
|
|
75
|
+
: null;
|
|
76
|
+
if (antigravitySnapshot?.shouldRefresh) scheduleAntigravityQuotaRefresh();
|
|
83
77
|
// 설정이 없는 홈에서는 갱신 프로세스를 시작하지 않는다.
|
|
84
78
|
if (
|
|
85
79
|
claudeUsageSnapshot.shouldRefresh &&
|
|
@@ -102,21 +96,12 @@ async function main() {
|
|
|
102
96
|
: null;
|
|
103
97
|
const codexBuckets = codexSnapshot.buckets;
|
|
104
98
|
const currentTier = selectTier();
|
|
105
|
-
const geminiEmail = getGeminiEmail();
|
|
106
|
-
const showGeminiRow = shouldRenderGeminiFallbackRow({
|
|
107
|
-
antigravityAllowed,
|
|
108
|
-
antigravityReady,
|
|
109
|
-
geminiEmail,
|
|
110
|
-
});
|
|
111
|
-
|
|
112
99
|
if (currentTier === "nano") {
|
|
113
|
-
const microLine = getMicroLine(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
{ showCodex, showGemini: showGeminiRow },
|
|
119
|
-
);
|
|
100
|
+
const microLine = getMicroLine(contextView, claudeUsage, codexBuckets, {
|
|
101
|
+
showCodex,
|
|
102
|
+
showAntigravity: antigravityAllowed,
|
|
103
|
+
antigravityQuota: antigravitySnapshot?.data,
|
|
104
|
+
});
|
|
120
105
|
process.stdout.write(`\x1b[0m${microLine}\n`);
|
|
121
106
|
return;
|
|
122
107
|
}
|
|
@@ -140,38 +125,25 @@ async function main() {
|
|
|
140
125
|
),
|
|
141
126
|
);
|
|
142
127
|
}
|
|
143
|
-
|
|
144
|
-
if (showGeminiRow) {
|
|
145
|
-
geminiRowIndex = rows.length;
|
|
128
|
+
if (antigravityAllowed) {
|
|
146
129
|
rows.push(
|
|
147
130
|
getProviderRow(
|
|
148
131
|
currentTier,
|
|
149
|
-
|
|
150
|
-
|
|
132
|
+
"antigravity",
|
|
133
|
+
"a",
|
|
151
134
|
geminiBlue,
|
|
152
135
|
accountsConfig,
|
|
153
136
|
accountsState,
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
currentAbbrev: getAntigravityModelAbbrev(
|
|
157
|
-
getAntigravityCurrentModel(),
|
|
158
|
-
),
|
|
159
|
-
}
|
|
160
|
-
: null,
|
|
161
|
-
antigravityReady ? getAntigravityAccountLabel() : geminiEmail,
|
|
137
|
+
antigravitySnapshot?.data,
|
|
138
|
+
getAntigravityAccountLabel(),
|
|
162
139
|
),
|
|
163
140
|
);
|
|
164
141
|
}
|
|
165
|
-
const teamRow = getTeamRow(currentTier);
|
|
166
|
-
if (teamRow) rows.push(teamRow);
|
|
167
142
|
|
|
168
143
|
const outputLines = renderAlignedRows(rows);
|
|
169
144
|
if (!codexBuckets && outputLines[codexRowIndex] != null) {
|
|
170
145
|
outputLines[codexRowIndex] = `${DIM}${outputLines[codexRowIndex]}${RESET}`;
|
|
171
146
|
}
|
|
172
|
-
if (!antigravityReady && outputLines[geminiRowIndex] != null) {
|
|
173
|
-
outputLines[geminiRowIndex] = dim(outputLines[geminiRowIndex]);
|
|
174
|
-
}
|
|
175
147
|
// 알림 배너와 TUI 스타일이 HUD 내용에 겹치지 않도록 한다.
|
|
176
148
|
const leadingBreaks = contextView.percent >= 85 ? "\n\n" : "\n";
|
|
177
149
|
const resetLines = outputLines.map((line) => `\x1b[0m${line}`);
|
package/hud/providers/gemini.mjs
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
1
3
|
import {
|
|
2
|
-
ANTIGRAVITY_MODEL_ABBREV,
|
|
3
4
|
ANTIGRAVITY_OAUTH_PATHS,
|
|
5
|
+
ANTIGRAVITY_QUOTA_CACHE_PATH,
|
|
6
|
+
ANTIGRAVITY_QUOTA_STALE_MS,
|
|
7
|
+
ANTIGRAVITY_REFRESH_FLAG,
|
|
8
|
+
ANTIGRAVITY_REFRESH_LOCK_PATH,
|
|
4
9
|
ANTIGRAVITY_SETTINGS_PATH,
|
|
5
|
-
|
|
10
|
+
SPAWN_LOCK_TTL_MS,
|
|
6
11
|
} from "../constants.mjs";
|
|
7
|
-
import { decodeJwtEmail, readJson } from "../utils.mjs";
|
|
12
|
+
import { decodeJwtEmail, readJson, writeJsonSafe } from "../utils.mjs";
|
|
8
13
|
|
|
9
14
|
function getCredentialEmail(credential) {
|
|
10
15
|
return (
|
|
@@ -17,10 +22,6 @@ function getCredentialEmail(credential) {
|
|
|
17
22
|
);
|
|
18
23
|
}
|
|
19
24
|
|
|
20
|
-
export function getGeminiEmail() {
|
|
21
|
-
return getCredentialEmail(readJson(GEMINI_OAUTH_PATH, null));
|
|
22
|
-
}
|
|
23
|
-
|
|
24
25
|
// GCP 프로젝트 인증이면 계정 이메일 대신 프로젝트 ID를 보여 준다.
|
|
25
26
|
export function getAntigravityAccountLabel() {
|
|
26
27
|
const project = readJson(ANTIGRAVITY_SETTINGS_PATH, null)?.gcp?.project;
|
|
@@ -36,20 +37,104 @@ export function getAntigravityAccountLabel() {
|
|
|
36
37
|
return null;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
function isGcpProjectAuth() {
|
|
41
|
+
return Boolean(readJson(ANTIGRAVITY_SETTINGS_PATH, null)?.gcp?.project);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function readAntigravityQuotaSnapshot() {
|
|
45
|
+
// 프로젝트 과금의 기본 응답을 개인 플랜의 0% 사용률로 표시하지 않는다.
|
|
46
|
+
if (isGcpProjectAuth()) return { data: null, shouldRefresh: false };
|
|
47
|
+
const cache = readJson(ANTIGRAVITY_QUOTA_CACHE_PATH, null);
|
|
48
|
+
const matched = cache?.accountLabel === getAntigravityAccountLabel();
|
|
49
|
+
const stale =
|
|
50
|
+
!cache?.timestamp ||
|
|
51
|
+
Date.now() - cache.timestamp >= ANTIGRAVITY_QUOTA_STALE_MS;
|
|
52
|
+
const model = readJson(ANTIGRAVITY_SETTINGS_PATH, null)?.model;
|
|
53
|
+
const buckets = matched && Array.isArray(cache?.buckets) ? cache.buckets : [];
|
|
54
|
+
const bucket = buckets
|
|
55
|
+
.filter(
|
|
56
|
+
(item) =>
|
|
57
|
+
model &&
|
|
58
|
+
item &&
|
|
59
|
+
(item?.name === model || item?.id === model) &&
|
|
60
|
+
Number.isFinite(item.remaining_fraction) &&
|
|
61
|
+
item.remaining_fraction >= 0 &&
|
|
62
|
+
item.remaining_fraction <= 1 &&
|
|
63
|
+
Date.parse(item.reset_time) > Date.now(),
|
|
64
|
+
)
|
|
65
|
+
.sort((a, b) => a.remaining_fraction - b.remaining_fraction)[0];
|
|
66
|
+
return {
|
|
67
|
+
data: bucket
|
|
68
|
+
? {
|
|
69
|
+
usedPercent: Math.round((1 - bucket.remaining_fraction) * 100),
|
|
70
|
+
resetTime: bucket.reset_time,
|
|
71
|
+
stale,
|
|
72
|
+
}
|
|
73
|
+
: null,
|
|
74
|
+
shouldRefresh: !matched || stale,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// /usage는 모델 턴을 시작하지 않는 공식 print-mode 조회다.
|
|
79
|
+
export function refreshAntigravityQuotaCache(execFileSyncFn = execFileSync) {
|
|
80
|
+
if (isGcpProjectAuth()) return null;
|
|
81
|
+
const accountLabel = getAntigravityAccountLabel();
|
|
82
|
+
let buckets = null;
|
|
83
|
+
try {
|
|
84
|
+
const result = JSON.parse(
|
|
85
|
+
execFileSyncFn("agy", ["-p", "/usage", "--output-format", "json"], {
|
|
86
|
+
encoding: "utf8",
|
|
87
|
+
timeout: 15_000,
|
|
88
|
+
maxBuffer: 1024 * 1024,
|
|
89
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
90
|
+
windowsHide: true,
|
|
91
|
+
}),
|
|
92
|
+
);
|
|
93
|
+
if (
|
|
94
|
+
result.status === "SUCCESS" &&
|
|
95
|
+
result.command?.name === "usage" &&
|
|
96
|
+
Array.isArray(result.command.data?.groups)
|
|
97
|
+
) {
|
|
98
|
+
buckets = result.command.data.groups.flatMap((group) =>
|
|
99
|
+
Array.isArray(group?.buckets) ? group.buckets : [],
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
// 실패도 캐시해 인증 오류나 CLI 부재 시 매 렌더마다 실행하지 않는다.
|
|
104
|
+
}
|
|
105
|
+
if (accountLabel === getAntigravityAccountLabel()) {
|
|
106
|
+
writeJsonSafe(ANTIGRAVITY_QUOTA_CACHE_PATH, {
|
|
107
|
+
timestamp: Date.now(),
|
|
108
|
+
accountLabel,
|
|
109
|
+
buckets,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return buckets;
|
|
42
113
|
}
|
|
43
114
|
|
|
44
|
-
export function
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return
|
|
115
|
+
export function scheduleAntigravityQuotaRefresh() {
|
|
116
|
+
const scriptPath = process.argv[1];
|
|
117
|
+
if (
|
|
118
|
+
!scriptPath ||
|
|
119
|
+
!existsSync(ANTIGRAVITY_SETTINGS_PATH) ||
|
|
120
|
+
isGcpProjectAuth()
|
|
121
|
+
)
|
|
122
|
+
return;
|
|
123
|
+
const lock = readJson(ANTIGRAVITY_REFRESH_LOCK_PATH, null);
|
|
124
|
+
if (Date.now() - Number(lock?.t || 0) < SPAWN_LOCK_TTL_MS) return;
|
|
125
|
+
writeJsonSafe(ANTIGRAVITY_REFRESH_LOCK_PATH, { t: Date.now() });
|
|
126
|
+
try {
|
|
127
|
+
const child = spawn(
|
|
128
|
+
process.execPath,
|
|
129
|
+
[scriptPath, ANTIGRAVITY_REFRESH_FLAG],
|
|
130
|
+
{
|
|
131
|
+
detached: true,
|
|
132
|
+
stdio: "ignore",
|
|
133
|
+
windowsHide: true,
|
|
134
|
+
},
|
|
135
|
+
);
|
|
136
|
+
child.unref();
|
|
137
|
+
} catch {
|
|
138
|
+
// 다음 렌더에서 다시 시도한다.
|
|
52
139
|
}
|
|
53
|
-
if (head) return head[1].slice(0, 2);
|
|
54
|
-
return "??";
|
|
55
140
|
}
|