triflux 10.3.4 → 10.7.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/LICENSE +21 -21
- package/bin/tfx-doctor-tui.mjs +1 -1
- package/bin/tfx-doctor.mjs +6 -1
- package/bin/tfx-profile.mjs +1 -1
- package/bin/tfx-setup-tui.mjs +1 -1
- package/bin/tfx-setup.mjs +6 -1
- package/bin/triflux.mjs +2396 -1140
- package/hooks/agent-route-guard.mjs +12 -8
- package/hooks/cross-review-tracker.mjs +21 -8
- package/hooks/error-context.mjs +19 -7
- package/hooks/hook-adaptive-collector.mjs +18 -16
- package/hooks/hook-manager.mjs +93 -32
- package/hooks/hook-orchestrator.mjs +108 -24
- package/hooks/hook-registry.json +11 -0
- package/hooks/keyword-rules.json +6 -10
- package/hooks/lib/resolve-root.mjs +1 -1
- package/hooks/mcp-config-watcher.mjs +6 -2
- package/hooks/pipeline-stop.mjs +3 -6
- package/hooks/safety-guard.mjs +99 -28
- package/hooks/session-start-fast.mjs +143 -0
- package/hooks/subagent-verifier.mjs +5 -4
- package/hub/account-broker.mjs +256 -60
- package/hub/adaptive-diagnostic.mjs +75 -48
- package/hub/adaptive-inject.mjs +95 -57
- package/hub/adaptive-memory.mjs +156 -42
- package/hub/adaptive.mjs +60 -31
- package/hub/assign-callbacks.mjs +67 -30
- package/hub/bridge.mjs +0 -1
- package/hub/cli-adapter-base.mjs +200 -48
- package/hub/codex-adapter.mjs +76 -96
- package/hub/codex-compat.mjs +3 -3
- package/hub/codex-preflight.mjs +63 -37
- package/hub/delegator/contracts.mjs +19 -23
- package/hub/delegator/index.mjs +3 -3
- package/hub/delegator/service.mjs +88 -64
- package/hub/delegator/tool-definitions.mjs +5 -5
- package/hub/fullcycle.mjs +33 -17
- package/hub/gemini-adapter.mjs +69 -94
- package/hub/hitl.mjs +89 -30
- package/hub/intent.mjs +161 -38
- package/hub/lib/cache-guard.mjs +43 -17
- package/hub/lib/mcp-response-cache.mjs +66 -32
- package/hub/lib/memory-store.mjs +285 -111
- package/hub/lib/path-utils.mjs +35 -37
- package/hub/lib/process-utils.mjs +106 -37
- package/hub/lib/spawn-trace.mjs +527 -0
- package/hub/lib/ssh-command.mjs +34 -4
- package/hub/lib/ssh-retry.mjs +5 -1
- package/hub/lib/uuidv7.mjs +4 -3
- package/hub/memory-doctor.mjs +266 -106
- package/hub/middleware/request-logger.mjs +61 -34
- package/hub/paths.mjs +9 -9
- package/hub/pipeline/gates/confidence.mjs +34 -15
- package/hub/pipeline/gates/consensus.mjs +27 -15
- package/hub/pipeline/gates/index.mjs +7 -3
- package/hub/pipeline/gates/selfcheck.mjs +57 -19
- package/hub/pipeline/index.mjs +77 -42
- package/hub/pipeline/state.mjs +10 -10
- package/hub/pipeline/transitions.mjs +40 -23
- package/hub/platform.mjs +57 -48
- package/hub/promote-penalties.mjs +25 -7
- package/hub/quality/deslop.mjs +70 -49
- package/hub/research.mjs +32 -25
- package/hub/router.mjs +240 -107
- package/hub/routing/complexity.mjs +132 -29
- package/hub/routing/index.mjs +17 -12
- package/hub/routing/q-learning.mjs +76 -28
- package/hub/server.mjs +4 -4
- package/hub/session-fingerprint.mjs +126 -60
- package/hub/state.mjs +84 -43
- package/hub/store-adapter.mjs +59 -26
- package/hub/store.mjs +356 -153
- package/hub/team/agent-map.json +22 -7
- package/hub/team/ansi.mjs +186 -122
- package/hub/team/backend.mjs +28 -10
- package/hub/team/cli/commands/attach.mjs +29 -9
- package/hub/team/cli/commands/control.mjs +29 -8
- package/hub/team/cli/commands/debug.mjs +32 -11
- package/hub/team/cli/commands/focus.mjs +38 -11
- package/hub/team/cli/commands/interrupt.mjs +18 -6
- package/hub/team/cli/commands/kill.mjs +16 -5
- package/hub/team/cli/commands/list.mjs +11 -4
- package/hub/team/cli/commands/send.mjs +19 -6
- package/hub/team/cli/commands/start/index.mjs +154 -31
- package/hub/team/cli/commands/start/parse-args.mjs +38 -11
- package/hub/team/cli/commands/start/start-headless.mjs +112 -36
- package/hub/team/cli/commands/start/start-in-process.mjs +12 -2
- package/hub/team/cli/commands/start/start-mux.mjs +70 -21
- package/hub/team/cli/commands/start/start-wt.mjs +29 -12
- package/hub/team/cli/commands/status.mjs +43 -14
- package/hub/team/cli/commands/stop.mjs +11 -4
- package/hub/team/cli/commands/task.mjs +8 -3
- package/hub/team/cli/commands/tasks.mjs +1 -1
- package/hub/team/cli/index.mjs +2 -2
- package/hub/team/cli/manifest.mjs +38 -8
- package/hub/team/cli/render.mjs +30 -8
- package/hub/team/cli/services/attach-fallback.mjs +31 -11
- package/hub/team/cli/services/hub-client.mjs +42 -14
- package/hub/team/cli/services/member-selector.mjs +11 -4
- package/hub/team/cli/services/native-control.mjs +48 -21
- package/hub/team/cli/services/runtime-mode.mjs +2 -1
- package/hub/team/cli/services/state-store.mjs +25 -8
- package/hub/team/cli/services/task-model.mjs +16 -6
- package/hub/team/conductor-mesh-bridge.mjs +24 -23
- package/hub/team/conductor.mjs +8 -4
- package/hub/team/dashboard-anchor.mjs +4 -5
- package/hub/team/dashboard-layout.mjs +3 -1
- package/hub/team/dashboard-open.mjs +41 -21
- package/hub/team/dashboard.mjs +76 -28
- package/hub/team/event-log.mjs +18 -10
- package/hub/team/handoff.mjs +31 -15
- package/hub/team/headless.mjs +2 -1
- package/hub/team/health-probe.mjs +69 -54
- package/hub/team/launcher-template.mjs +16 -13
- package/hub/team/native-supervisor.mjs +65 -21
- package/hub/team/native.mjs +74 -35
- package/hub/team/nativeProxy.mjs +184 -113
- package/hub/team/notify.mjs +119 -76
- package/hub/team/orchestrator.mjs +9 -4
- package/hub/team/pane.mjs +12 -7
- package/hub/team/process-cleanup.mjs +25 -16
- package/hub/team/psmux.mjs +491 -201
- package/hub/team/remote-probe.mjs +68 -52
- package/hub/team/remote-session.mjs +117 -59
- package/hub/team/remote-watcher.mjs +61 -33
- package/hub/team/routing.mjs +51 -25
- package/hub/team/runtime-strategy.mjs +3 -1
- package/hub/team/session.mjs +98 -34
- package/hub/team/staleState.mjs +72 -30
- package/hub/team/swarm-locks.mjs +15 -13
- package/hub/team/swarm-planner.mjs +32 -21
- package/hub/team/swarm-reconciler.mjs +48 -23
- package/hub/team/tui-lite.mjs +266 -68
- package/hub/team/tui-remote-adapter.mjs +14 -10
- package/hub/team/tui-viewer.mjs +99 -43
- package/hub/team/tui.mjs +708 -271
- package/hub/team/worktree-lifecycle.mjs +152 -58
- package/hub/team/wt-manager.mjs +24 -14
- package/hub/token-mode.mjs +71 -71
- package/hub/tray.mjs +66 -23
- package/hub/workers/claude-worker.mjs +162 -118
- package/hub/workers/codex-mcp.mjs +192 -141
- package/hub/workers/delegator-mcp.mjs +507 -333
- package/hub/workers/factory.mjs +8 -8
- package/hub/workers/gemini-worker.mjs +115 -84
- package/hub/workers/interface.mjs +6 -1
- package/hub/workers/worker-utils.mjs +21 -14
- package/hud/colors.mjs +27 -9
- package/hud/constants.mjs +162 -26
- package/hud/context-monitor.mjs +82 -41
- package/hud/hud-qos-status.mjs +129 -49
- package/hud/mission-board.mjs +6 -3
- package/hud/providers/claude.mjs +226 -115
- package/hud/providers/codex.mjs +62 -22
- package/hud/providers/gemini.mjs +168 -56
- package/hud/renderers.mjs +384 -119
- package/hud/terminal.mjs +101 -31
- package/hud/utils.mjs +78 -38
- package/mesh/index.mjs +11 -5
- package/mesh/mesh-budget.mjs +18 -9
- package/mesh/mesh-heartbeat.mjs +1 -1
- package/mesh/mesh-queue.mjs +3 -5
- package/mesh/mesh-router.mjs +5 -4
- package/package.json +2 -1
- package/scripts/__tests__/gen-skill-docs.test.mjs +36 -7
- package/scripts/__tests__/keyword-detector.test.mjs +77 -28
- package/scripts/__tests__/mcp-guard-engine.test.mjs +58 -20
- package/scripts/__tests__/remote-spawn-transfer.test.mjs +30 -19
- package/scripts/__tests__/remote-spawn.test.mjs +10 -4
- package/scripts/__tests__/session-start-fast.test.mjs +36 -0
- package/scripts/__tests__/skill-template.test.mjs +98 -50
- package/scripts/__tests__/smoke.test.mjs +1 -1
- package/scripts/__tests__/spawn-trace.test.mjs +102 -0
- package/scripts/__tests__/tfx-doctor-diagnose.test.mjs +48 -0
- package/scripts/cache-doctor.mjs +11 -4
- package/scripts/cache-warmup.mjs +96 -37
- package/scripts/claudemd-sync.mjs +27 -17
- package/scripts/codex-gateway-preflight.mjs +52 -37
- package/scripts/codex-mcp-gateway-sync.mjs +59 -39
- package/scripts/completions/tfx.bash +47 -47
- package/scripts/completions/tfx.fish +44 -44
- package/scripts/completions/tfx.zsh +83 -83
- package/scripts/config-audit.mjs +232 -0
- package/scripts/convert-to-tmpl.mjs +54 -0
- package/scripts/cross-review-gate.mjs +35 -12
- package/scripts/cross-review-tracker.mjs +21 -8
- package/scripts/demo.mjs +35 -17
- package/scripts/doctor-diagnose.mjs +284 -0
- package/scripts/gen-skill-docs.mjs +7 -2
- package/scripts/gen-skill-manifest.mjs +2 -1
- package/scripts/headless-guard.mjs +86 -48
- package/scripts/hub-ensure.mjs +45 -26
- package/scripts/keyword-detector.mjs +41 -20
- package/scripts/keyword-rules-expander.mjs +47 -30
- package/scripts/lib/claudemd-scanner.mjs +6 -1
- package/scripts/lib/context.mjs +3 -3
- package/scripts/lib/cross-review-utils.mjs +6 -3
- package/scripts/lib/env-probe.mjs +47 -28
- package/scripts/lib/gemini-profiles.mjs +44 -10
- package/scripts/lib/handoff.mjs +33 -17
- package/scripts/lib/hook-utils.mjs +8 -6
- package/scripts/lib/keyword-rules.mjs +43 -19
- package/scripts/lib/logger.mjs +24 -24
- package/scripts/lib/mcp-filter.mjs +377 -239
- package/scripts/lib/mcp-guard-engine.mjs +194 -79
- package/scripts/lib/mcp-manifest.mjs +23 -13
- package/scripts/lib/mcp-server-catalog.mjs +300 -63
- package/scripts/lib/psmux-info.mjs +11 -6
- package/scripts/lib/remote-spawn-transfer.mjs +44 -14
- package/scripts/lib/skill-template.mjs +30 -7
- package/scripts/mcp-check.mjs +58 -39
- package/scripts/mcp-gateway-config.mjs +83 -39
- package/scripts/mcp-gateway-ensure.mjs +43 -35
- package/scripts/mcp-gateway-integration-test.mjs +70 -58
- package/scripts/mcp-gateway-start.mjs +126 -60
- package/scripts/mcp-gateway-verify.mjs +24 -22
- package/scripts/mcp-safety-guard.mjs +44 -11
- package/scripts/notion-read.mjs +199 -84
- package/scripts/pack.mjs +94 -89
- package/scripts/preflight-cache.mjs +27 -10
- package/scripts/preinstall.mjs +42 -13
- package/scripts/remote-spawn.mjs +309 -94
- package/scripts/run.cjs +8 -5
- package/scripts/session-spawn-helper.mjs +130 -39
- package/scripts/session-stale-cleanup.mjs +123 -0
- package/scripts/setup.mjs +941 -492
- package/scripts/test-lock.mjs +20 -7
- package/scripts/test-tfx-route-no-claude-native.mjs +16 -12
- package/scripts/tfx-batch-stats.mjs +32 -11
- package/scripts/tfx-gate-activate.mjs +11 -4
- package/scripts/tfx-route-post.mjs +87 -20
- package/scripts/tfx-route-worker.mjs +57 -51
- package/scripts/tfx-route.sh +41 -124
- package/scripts/tmp-cleanup.mjs +21 -7
- package/scripts/token-snapshot.mjs +204 -85
- package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +1 -0
- package/skills/.omc/state/idle-notif-cooldown.json +3 -0
- package/skills/.omc/state/last-tool-error.json +7 -0
- package/skills/.omc/state/subagent-tracking.json +7 -0
- package/skills/_templates/base.md +1 -6
- package/skills/merge-worktree/SKILL.md.tmpl +144 -0
- package/skills/shared/telemetry-segment.md +6 -0
- package/skills/star-prompt/SKILL.md.tmpl +222 -0
- package/skills/tfx-analysis/SKILL.md.tmpl +107 -0
- package/skills/tfx-analysis/skill.json +1 -6
- package/skills/tfx-auto/SKILL.md +1 -0
- package/skills/tfx-auto-codex/SKILL.md.tmpl +106 -0
- package/skills/tfx-auto-codex/skill.json +1 -3
- package/skills/tfx-autopilot/SKILL.md.tmpl +116 -0
- package/skills/tfx-autopilot/skill.json +1 -5
- package/skills/tfx-autoresearch/SKILL.md.tmpl +136 -0
- package/skills/tfx-autoroute/SKILL.md.tmpl +189 -0
- package/skills/tfx-autoroute/skill.json +1 -7
- package/skills/tfx-codex/SKILL.md +1 -0
- package/skills/tfx-codex/skill.json +1 -3
- package/skills/tfx-codex-swarm/SKILL.md.tmpl +16 -0
- package/skills/tfx-codex-swarm/evals/evals.json +1 -1
- package/skills/tfx-codex-swarm/skill.json +1 -4
- package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +54 -12
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +35 -7
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +35 -7
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +25 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +25 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +20 -4
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +16 -4
- package/skills/tfx-consensus/SKILL.md.tmpl +146 -0
- package/skills/tfx-debate/SKILL.md.tmpl +192 -0
- package/skills/tfx-debate/skill.json +1 -7
- package/skills/tfx-deep-analysis/SKILL.md.tmpl +228 -0
- package/skills/tfx-deep-analysis/skill.json +1 -5
- package/skills/tfx-deep-interview/SKILL.md.tmpl +203 -0
- package/skills/tfx-deep-plan/SKILL.md.tmpl +282 -0
- package/skills/tfx-deep-qa/SKILL.md.tmpl +165 -0
- package/skills/tfx-deep-qa/skill.json +1 -6
- package/skills/tfx-deep-research/SKILL.md.tmpl +217 -0
- package/skills/tfx-deep-review/SKILL.md.tmpl +179 -0
- package/skills/tfx-doctor/SKILL.md +21 -0
- package/skills/tfx-doctor/SKILL.md.tmpl +172 -0
- package/skills/tfx-doctor/skill.json +1 -3
- package/skills/tfx-find/SKILL.md +1 -0
- package/skills/tfx-forge/SKILL.md.tmpl +187 -0
- package/skills/tfx-fullcycle/SKILL.md.tmpl +286 -0
- package/skills/tfx-fullcycle/skill.json +1 -6
- package/skills/tfx-gemini/SKILL.md.tmpl +91 -0
- package/skills/tfx-gemini/skill.json +1 -3
- package/skills/tfx-hooks/SKILL.md.tmpl +216 -0
- package/skills/tfx-hooks/skill.json +1 -3
- package/skills/tfx-hub/SKILL.md.tmpl +212 -0
- package/skills/tfx-hub/skill.json +1 -3
- package/skills/tfx-index/SKILL.md +1 -0
- package/skills/tfx-index/skill.json +1 -6
- package/skills/tfx-interview/SKILL.md.tmpl +285 -0
- package/skills/tfx-multi/SKILL.md.tmpl +183 -0
- package/skills/tfx-multi/skill.json +1 -3
- package/skills/tfx-panel/SKILL.md.tmpl +189 -0
- package/skills/tfx-panel/skill.json +1 -7
- package/skills/tfx-persist/SKILL.md.tmpl +270 -0
- package/skills/tfx-persist/skill.json +1 -7
- package/skills/tfx-plan/SKILL.md +1 -0
- package/skills/tfx-plan/skill.json +1 -6
- package/skills/tfx-profile/SKILL.md.tmpl +239 -0
- package/skills/tfx-profile/skill.json +1 -3
- package/skills/tfx-prune/SKILL.md.tmpl +200 -0
- package/skills/tfx-prune/skill.json +1 -7
- package/skills/tfx-psmux-rules/SKILL.md.tmpl +326 -0
- package/skills/tfx-psmux-rules/skill.json +1 -4
- package/skills/tfx-qa/SKILL.md +1 -0
- package/skills/tfx-qa/skill.json +1 -6
- package/skills/tfx-ralph/SKILL.md.tmpl +28 -0
- package/skills/tfx-ralph/skill.json +1 -4
- package/skills/tfx-remote-setup/SKILL.md.tmpl +576 -0
- package/skills/tfx-remote-setup/skill.json +1 -3
- package/skills/tfx-remote-spawn/SKILL.md.tmpl +263 -0
- package/skills/tfx-remote-spawn/references/hosts.json +16 -0
- package/skills/tfx-remote-spawn/skill.json +1 -4
- package/skills/tfx-research/SKILL.md +1 -0
- package/skills/tfx-review/SKILL.md +1 -0
- package/skills/tfx-review/skill.json +1 -6
- package/skills/tfx-setup/SKILL.md.tmpl +504 -0
- package/skills/tfx-setup/skill.json +1 -3
- package/skills/tfx-swarm/SKILL.md +22 -0
- package/skills/tfx-swarm/SKILL.md.tmpl +218 -0
- package/tui/codex-profile.mjs +88 -33
- package/tui/core.mjs +45 -15
- package/tui/doctor.mjs +75 -28
- package/tui/gemini-profile.mjs +74 -29
- package/tui/monitor-data.mjs +8 -4
- package/tui/monitor.mjs +71 -27
- package/tui/setup.mjs +133 -42
package/hud/providers/gemini.mjs
CHANGED
|
@@ -1,31 +1,47 @@
|
|
|
1
1
|
// ============================================================================
|
|
2
2
|
// Gemini 쿼터 API / 세션 토큰 / RPM 트래커
|
|
3
3
|
// ============================================================================
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
7
|
+
import https from "node:https";
|
|
5
8
|
import { homedir } from "node:os";
|
|
6
9
|
import { join } from "node:path";
|
|
7
|
-
import https from "node:https";
|
|
8
|
-
import { spawn } from "node:child_process";
|
|
9
10
|
import {
|
|
10
|
-
GEMINI_OAUTH_PATH, GEMINI_QUOTA_CACHE_PATH, GEMINI_PROJECT_CACHE_PATH,
|
|
11
|
-
GEMINI_SESSION_CACHE_PATH, GEMINI_RPM_TRACKER_PATH,
|
|
12
|
-
LEGACY_GEMINI_QUOTA_CACHE, LEGACY_GEMINI_PROJECT_CACHE,
|
|
13
|
-
LEGACY_GEMINI_SESSION_CACHE, LEGACY_GEMINI_RPM_TRACKER,
|
|
14
|
-
GEMINI_RPM_WINDOW_MS, GEMINI_QUOTA_STALE_MS, GEMINI_SESSION_STALE_MS,
|
|
15
11
|
GEMINI_API_TIMEOUT_MS,
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
GEMINI_OAUTH_PATH,
|
|
13
|
+
GEMINI_PROJECT_CACHE_PATH,
|
|
14
|
+
GEMINI_QUOTA_CACHE_PATH,
|
|
15
|
+
GEMINI_QUOTA_REFRESH_LOCK_PATH,
|
|
16
|
+
GEMINI_QUOTA_STALE_MS,
|
|
17
|
+
GEMINI_REFRESH_FLAG,
|
|
18
|
+
GEMINI_RPM_TRACKER_PATH,
|
|
19
|
+
GEMINI_RPM_WINDOW_MS,
|
|
20
|
+
GEMINI_SESSION_CACHE_PATH,
|
|
21
|
+
GEMINI_SESSION_REFRESH_FLAG,
|
|
22
|
+
GEMINI_SESSION_REFRESH_LOCK_PATH,
|
|
23
|
+
GEMINI_SESSION_STALE_MS,
|
|
24
|
+
LEGACY_GEMINI_PROJECT_CACHE,
|
|
25
|
+
LEGACY_GEMINI_QUOTA_CACHE,
|
|
26
|
+
LEGACY_GEMINI_RPM_TRACKER,
|
|
27
|
+
LEGACY_GEMINI_SESSION_CACHE,
|
|
28
|
+
SPAWN_LOCK_TTL_MS,
|
|
18
29
|
} from "../constants.mjs";
|
|
19
30
|
import {
|
|
20
|
-
|
|
21
|
-
|
|
31
|
+
clampPercent,
|
|
32
|
+
createHttpsPost,
|
|
33
|
+
decodeJwtEmail,
|
|
34
|
+
makeHash,
|
|
35
|
+
readJson,
|
|
36
|
+
readJsonMigrate,
|
|
37
|
+
writeJsonSafe,
|
|
22
38
|
} from "../utils.mjs";
|
|
23
39
|
|
|
24
40
|
const httpsPost = createHttpsPost(https, GEMINI_API_TIMEOUT_MS);
|
|
25
41
|
|
|
26
42
|
// Gemini 모델별 RPM 한도 (실측 기반: Pro 25, Flash 300)
|
|
27
43
|
export function getGeminiRpmLimit(model) {
|
|
28
|
-
if (model
|
|
44
|
+
if (model?.includes("pro")) return 25;
|
|
29
45
|
return 300; // Flash 기본
|
|
30
46
|
}
|
|
31
47
|
|
|
@@ -49,21 +65,36 @@ export function deriveGeminiLimits(bucket) {
|
|
|
49
65
|
if (bucket.remainingAmount != null) {
|
|
50
66
|
const remaining = parseInt(bucket.remainingAmount, 10);
|
|
51
67
|
const limit = fraction > 0 ? Math.round(remaining / fraction) : 0;
|
|
52
|
-
return {
|
|
68
|
+
return {
|
|
69
|
+
usedPct,
|
|
70
|
+
remaining,
|
|
71
|
+
limit,
|
|
72
|
+
resetTime: bucket.resetTime,
|
|
73
|
+
modelId: bucket.modelId,
|
|
74
|
+
};
|
|
53
75
|
}
|
|
54
|
-
return {
|
|
76
|
+
return {
|
|
77
|
+
usedPct,
|
|
78
|
+
remaining: null,
|
|
79
|
+
limit: null,
|
|
80
|
+
resetTime: bucket.resetTime,
|
|
81
|
+
modelId: bucket.modelId,
|
|
82
|
+
};
|
|
55
83
|
}
|
|
56
84
|
|
|
57
85
|
export function getGeminiEmail() {
|
|
58
86
|
try {
|
|
59
87
|
const oauth = readJson(GEMINI_OAUTH_PATH, null);
|
|
60
88
|
return decodeJwtEmail(oauth?.id_token);
|
|
61
|
-
} catch {
|
|
89
|
+
} catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
62
92
|
}
|
|
63
93
|
|
|
64
94
|
export function buildGeminiAuthContext(accountId) {
|
|
65
95
|
const oauth = readJson(GEMINI_OAUTH_PATH, null);
|
|
66
|
-
const tokenSource =
|
|
96
|
+
const tokenSource =
|
|
97
|
+
oauth?.refresh_token || oauth?.id_token || oauth?.access_token || "";
|
|
67
98
|
const tokenFingerprint = tokenSource ? makeHash(tokenSource) : "none";
|
|
68
99
|
const cacheKey = `${accountId || "gemini-main"}::${tokenFingerprint}`;
|
|
69
100
|
return { oauth, tokenFingerprint, cacheKey };
|
|
@@ -78,11 +109,17 @@ export async function fetchGeminiQuota(accountId, options = {}) {
|
|
|
78
109
|
const forceRefresh = options.forceRefresh === true;
|
|
79
110
|
|
|
80
111
|
// 1. 캐시 확인 (계정/토큰별)
|
|
81
|
-
const cache = readJsonMigrate(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
112
|
+
const cache = readJsonMigrate(
|
|
113
|
+
GEMINI_QUOTA_CACHE_PATH,
|
|
114
|
+
LEGACY_GEMINI_QUOTA_CACHE,
|
|
115
|
+
null,
|
|
116
|
+
);
|
|
117
|
+
if (
|
|
118
|
+
!forceRefresh &&
|
|
119
|
+
cache?.cacheKey === cacheKey &&
|
|
120
|
+
cache?.timestamp &&
|
|
121
|
+
Date.now() - cache.timestamp < GEMINI_QUOTA_STALE_MS
|
|
122
|
+
) {
|
|
86
123
|
return cache;
|
|
87
124
|
}
|
|
88
125
|
|
|
@@ -117,12 +154,22 @@ export async function fetchGeminiQuota(accountId, options = {}) {
|
|
|
117
154
|
oauth.access_token,
|
|
118
155
|
);
|
|
119
156
|
const id = loadRes?.cloudaicompanionProject;
|
|
120
|
-
if (id)
|
|
157
|
+
if (id)
|
|
158
|
+
writeJsonSafe(GEMINI_PROJECT_CACHE_PATH, {
|
|
159
|
+
cacheKey,
|
|
160
|
+
projectId: id,
|
|
161
|
+
timestamp: Date.now(),
|
|
162
|
+
});
|
|
121
163
|
return id || null;
|
|
122
164
|
};
|
|
123
165
|
|
|
124
|
-
const projCache = readJsonMigrate(
|
|
125
|
-
|
|
166
|
+
const projCache = readJsonMigrate(
|
|
167
|
+
GEMINI_PROJECT_CACHE_PATH,
|
|
168
|
+
LEGACY_GEMINI_PROJECT_CACHE,
|
|
169
|
+
null,
|
|
170
|
+
);
|
|
171
|
+
let projectId =
|
|
172
|
+
projCache?.cacheKey === cacheKey ? projCache?.projectId : null;
|
|
126
173
|
if (!projectId) projectId = await fetchProjectId();
|
|
127
174
|
if (!projectId) return cache;
|
|
128
175
|
|
|
@@ -146,7 +193,11 @@ export async function fetchGeminiQuota(accountId, options = {}) {
|
|
|
146
193
|
|
|
147
194
|
if (!quotaRes?.buckets) {
|
|
148
195
|
// API 응답에 buckets 없음: 에러 코드 또는 응답 내용을 캐시에 기록
|
|
149
|
-
const apiError =
|
|
196
|
+
const apiError =
|
|
197
|
+
quotaRes?.error?.message ||
|
|
198
|
+
quotaRes?.error?.code ||
|
|
199
|
+
quotaRes?.error ||
|
|
200
|
+
"no buckets in response";
|
|
150
201
|
writeJsonSafe(GEMINI_QUOTA_CACHE_PATH, {
|
|
151
202
|
...(cache || {}),
|
|
152
203
|
timestamp: cache?.timestamp || Date.now(),
|
|
@@ -176,12 +227,17 @@ export async function fetchGeminiQuota(accountId, options = {}) {
|
|
|
176
227
|
export function readGeminiRpm(model) {
|
|
177
228
|
try {
|
|
178
229
|
// 새 경로 → 레거시 경로 fallback
|
|
179
|
-
const rpmPath = existsSync(GEMINI_RPM_TRACKER_PATH)
|
|
180
|
-
|
|
230
|
+
const rpmPath = existsSync(GEMINI_RPM_TRACKER_PATH)
|
|
231
|
+
? GEMINI_RPM_TRACKER_PATH
|
|
232
|
+
: existsSync(LEGACY_GEMINI_RPM_TRACKER)
|
|
233
|
+
? LEGACY_GEMINI_RPM_TRACKER
|
|
234
|
+
: null;
|
|
181
235
|
if (!rpmPath) return { count: 0, percent: 0, remainingSec: 60 };
|
|
182
236
|
const raw = readFileSync(rpmPath, "utf-8");
|
|
183
237
|
const parsed = JSON.parse(raw);
|
|
184
|
-
const timestamps = Array.isArray(parsed.timestamps)
|
|
238
|
+
const timestamps = Array.isArray(parsed.timestamps)
|
|
239
|
+
? parsed.timestamps
|
|
240
|
+
: [];
|
|
185
241
|
const now = Date.now();
|
|
186
242
|
const recent = timestamps.filter((t) => now - t < GEMINI_RPM_WINDOW_MS);
|
|
187
243
|
const count = recent.length;
|
|
@@ -189,9 +245,15 @@ export function readGeminiRpm(model) {
|
|
|
189
245
|
const percent = clampPercent(Math.round((count / rpmLimit) * 100));
|
|
190
246
|
// 가장 오래된 엔트리가 윈도우에서 빠지기까지 남은 초 (0건이면 0s)
|
|
191
247
|
// 5초 단위 반올림으로 HUD 깜빡임 감소
|
|
192
|
-
const rawRemainingSec =
|
|
193
|
-
|
|
194
|
-
|
|
248
|
+
const rawRemainingSec =
|
|
249
|
+
recent.length > 0
|
|
250
|
+
? Math.max(
|
|
251
|
+
0,
|
|
252
|
+
Math.ceil(
|
|
253
|
+
(GEMINI_RPM_WINDOW_MS - (now - Math.min(...recent))) / 1000,
|
|
254
|
+
),
|
|
255
|
+
)
|
|
256
|
+
: 0;
|
|
195
257
|
const remainingSec = Math.ceil(rawRemainingSec / 5) * 5;
|
|
196
258
|
return { count, percent, remainingSec };
|
|
197
259
|
} catch {
|
|
@@ -200,7 +262,11 @@ export function readGeminiRpm(model) {
|
|
|
200
262
|
}
|
|
201
263
|
|
|
202
264
|
export function readGeminiQuotaSnapshot(accountId, authContext) {
|
|
203
|
-
const cache = readJsonMigrate(
|
|
265
|
+
const cache = readJsonMigrate(
|
|
266
|
+
GEMINI_QUOTA_CACHE_PATH,
|
|
267
|
+
LEGACY_GEMINI_QUOTA_CACHE,
|
|
268
|
+
null,
|
|
269
|
+
);
|
|
204
270
|
if (!cache?.buckets) {
|
|
205
271
|
return { quota: null, shouldRefresh: true };
|
|
206
272
|
}
|
|
@@ -209,19 +275,24 @@ export function readGeminiQuotaSnapshot(accountId, authContext) {
|
|
|
209
275
|
const isLegacyCache = !cache.cacheKey;
|
|
210
276
|
const keyMatched = cache.cacheKey === cacheKey;
|
|
211
277
|
const cacheTs = Number(cache.timestamp);
|
|
212
|
-
const ageMs = Number.isFinite(cacheTs)
|
|
278
|
+
const ageMs = Number.isFinite(cacheTs)
|
|
279
|
+
? Date.now() - cacheTs
|
|
280
|
+
: Number.MAX_SAFE_INTEGER;
|
|
213
281
|
const isFresh = ageMs < GEMINI_QUOTA_STALE_MS;
|
|
214
282
|
|
|
215
283
|
if (keyMatched) {
|
|
216
284
|
// resetTime이 지난 버킷의 remainingFraction을 1로 보정 (stale 캐시 방지)
|
|
217
285
|
if (Array.isArray(cache.buckets)) {
|
|
218
286
|
const now = Date.now();
|
|
219
|
-
const patchedBuckets = cache.buckets.map(b =>
|
|
287
|
+
const patchedBuckets = cache.buckets.map((b) =>
|
|
220
288
|
b?.resetTime && new Date(b.resetTime).getTime() <= now
|
|
221
289
|
? { ...b, remainingFraction: 1 }
|
|
222
|
-
: b
|
|
290
|
+
: b,
|
|
223
291
|
);
|
|
224
|
-
return {
|
|
292
|
+
return {
|
|
293
|
+
quota: { ...cache, buckets: patchedBuckets },
|
|
294
|
+
shouldRefresh: !isFresh,
|
|
295
|
+
};
|
|
225
296
|
}
|
|
226
297
|
return { quota: cache, shouldRefresh: !isFresh };
|
|
227
298
|
}
|
|
@@ -238,16 +309,24 @@ export function scheduleGeminiQuotaRefresh(accountId) {
|
|
|
238
309
|
// 스폰 락: 30초 내 이미 스폰했으면 중복 방지
|
|
239
310
|
try {
|
|
240
311
|
if (existsSync(GEMINI_QUOTA_REFRESH_LOCK_PATH)) {
|
|
241
|
-
const lockAge =
|
|
312
|
+
const lockAge =
|
|
313
|
+
Date.now() - readJson(GEMINI_QUOTA_REFRESH_LOCK_PATH, {}).t;
|
|
242
314
|
if (lockAge < SPAWN_LOCK_TTL_MS) return;
|
|
243
315
|
}
|
|
244
316
|
writeJsonSafe(GEMINI_QUOTA_REFRESH_LOCK_PATH, { t: Date.now() });
|
|
245
|
-
} catch {
|
|
317
|
+
} catch {
|
|
318
|
+
/* 락 실패 무시 — 스폰 진행 */
|
|
319
|
+
}
|
|
246
320
|
|
|
247
321
|
try {
|
|
248
322
|
const child = spawn(
|
|
249
323
|
process.execPath,
|
|
250
|
-
[
|
|
324
|
+
[
|
|
325
|
+
scriptPath,
|
|
326
|
+
GEMINI_REFRESH_FLAG,
|
|
327
|
+
"--account",
|
|
328
|
+
accountId || "gemini-main",
|
|
329
|
+
],
|
|
251
330
|
{
|
|
252
331
|
detached: true,
|
|
253
332
|
stdio: "ignore",
|
|
@@ -266,7 +345,11 @@ export function scheduleGeminiQuotaRefresh(accountId) {
|
|
|
266
345
|
}
|
|
267
346
|
|
|
268
347
|
export function readGeminiSessionSnapshot() {
|
|
269
|
-
const cache = readJsonMigrate(
|
|
348
|
+
const cache = readJsonMigrate(
|
|
349
|
+
GEMINI_SESSION_CACHE_PATH,
|
|
350
|
+
LEGACY_GEMINI_SESSION_CACHE,
|
|
351
|
+
null,
|
|
352
|
+
);
|
|
270
353
|
if (!cache?.session) {
|
|
271
354
|
return { session: null, shouldRefresh: true };
|
|
272
355
|
}
|
|
@@ -290,20 +373,29 @@ export function scheduleGeminiSessionRefresh() {
|
|
|
290
373
|
// 스폰 락: 30초 내 이미 스폰했으면 중복 방지
|
|
291
374
|
try {
|
|
292
375
|
if (existsSync(GEMINI_SESSION_REFRESH_LOCK_PATH)) {
|
|
293
|
-
const lockAge =
|
|
376
|
+
const lockAge =
|
|
377
|
+
Date.now() - readJson(GEMINI_SESSION_REFRESH_LOCK_PATH, {}).t;
|
|
294
378
|
if (lockAge < SPAWN_LOCK_TTL_MS) return;
|
|
295
379
|
}
|
|
296
380
|
writeJsonSafe(GEMINI_SESSION_REFRESH_LOCK_PATH, { t: Date.now() });
|
|
297
|
-
} catch {
|
|
381
|
+
} catch {
|
|
382
|
+
/* 락 실패 무시 — 스폰 진행 */
|
|
383
|
+
}
|
|
298
384
|
|
|
299
385
|
try {
|
|
300
|
-
const child = spawn(
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
386
|
+
const child = spawn(
|
|
387
|
+
process.execPath,
|
|
388
|
+
[scriptPath, GEMINI_SESSION_REFRESH_FLAG],
|
|
389
|
+
{
|
|
390
|
+
detached: true,
|
|
391
|
+
stdio: "ignore",
|
|
392
|
+
windowsHide: true,
|
|
393
|
+
},
|
|
394
|
+
);
|
|
305
395
|
child.unref();
|
|
306
|
-
} catch {
|
|
396
|
+
} catch {
|
|
397
|
+
/* 백그라운드 실행 실패 무시 */
|
|
398
|
+
}
|
|
307
399
|
}
|
|
308
400
|
|
|
309
401
|
// ============================================================================
|
|
@@ -315,27 +407,47 @@ export function scanGeminiSessionTokens() {
|
|
|
315
407
|
let best = null;
|
|
316
408
|
let bestTime = 0;
|
|
317
409
|
try {
|
|
318
|
-
const dirs = readdirSync(tmpDir).filter((d) =>
|
|
410
|
+
const dirs = readdirSync(tmpDir).filter((d) =>
|
|
411
|
+
existsSync(join(tmpDir, d, "chats")),
|
|
412
|
+
);
|
|
319
413
|
for (const dir of dirs) {
|
|
320
414
|
const chatsDir = join(tmpDir, dir, "chats");
|
|
321
415
|
let files;
|
|
322
|
-
try {
|
|
416
|
+
try {
|
|
417
|
+
files = readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
|
|
418
|
+
} catch {
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
323
421
|
for (const file of files) {
|
|
324
422
|
try {
|
|
325
423
|
const data = JSON.parse(readFileSync(join(chatsDir, file), "utf-8"));
|
|
326
424
|
const updatedAt = new Date(data.lastUpdated || 0).getTime();
|
|
327
425
|
if (updatedAt <= bestTime) continue;
|
|
328
|
-
let input = 0,
|
|
426
|
+
let input = 0,
|
|
427
|
+
output = 0;
|
|
329
428
|
let model = "unknown";
|
|
330
429
|
for (const msg of data.messages || []) {
|
|
331
|
-
if (msg.tokens) {
|
|
430
|
+
if (msg.tokens) {
|
|
431
|
+
input += msg.tokens.input || 0;
|
|
432
|
+
output += msg.tokens.output || 0;
|
|
433
|
+
}
|
|
332
434
|
if (msg.model) model = msg.model;
|
|
333
435
|
}
|
|
334
436
|
bestTime = updatedAt;
|
|
335
|
-
best = {
|
|
336
|
-
|
|
437
|
+
best = {
|
|
438
|
+
input,
|
|
439
|
+
output,
|
|
440
|
+
total: input + output,
|
|
441
|
+
model,
|
|
442
|
+
lastUpdated: data.lastUpdated,
|
|
443
|
+
};
|
|
444
|
+
} catch {
|
|
445
|
+
/* 무시 */
|
|
446
|
+
}
|
|
337
447
|
}
|
|
338
448
|
}
|
|
339
|
-
} catch {
|
|
449
|
+
} catch {
|
|
450
|
+
/* 무시 */
|
|
451
|
+
}
|
|
340
452
|
return best;
|
|
341
453
|
}
|