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/claude.mjs
CHANGED
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
// ============================================================================
|
|
2
2
|
// Claude Usage API (api.anthropic.com/api/oauth/usage)
|
|
3
3
|
// ============================================================================
|
|
4
|
-
|
|
5
|
-
import { homedir } from "node:os";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
import https from "node:https";
|
|
4
|
+
|
|
8
5
|
import { spawn } from "node:child_process";
|
|
6
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
7
|
+
import https from "node:https";
|
|
9
8
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
CLAUDE_API_TIMEOUT_MS,
|
|
10
|
+
CLAUDE_CREDENTIALS_PATH,
|
|
11
|
+
CLAUDE_REFRESH_FLAG,
|
|
12
|
+
CLAUDE_REFRESH_LOCK_PATH,
|
|
13
|
+
CLAUDE_USAGE_429_BACKOFF_MS,
|
|
14
|
+
CLAUDE_USAGE_CACHE_PATH,
|
|
15
|
+
CLAUDE_USAGE_ERROR_BACKOFF_MS,
|
|
16
|
+
CLAUDE_USAGE_STALE_MS_SOLO,
|
|
17
|
+
CLAUDE_USAGE_STALE_MS_WITH_OMC,
|
|
18
|
+
DEFAULT_OAUTH_CLIENT_ID,
|
|
19
|
+
FIVE_HOUR_MS,
|
|
20
|
+
OMC_PLUGIN_USAGE_CACHE_PATH,
|
|
21
|
+
SEVEN_DAY_MS,
|
|
22
|
+
SPAWN_LOCK_TTL_MS,
|
|
15
23
|
} from "../constants.mjs";
|
|
16
|
-
import { readJson, writeJsonSafe, clampPercent, advanceToNextCycle } from "../utils.mjs";
|
|
17
24
|
import { readContextMonitorSnapshot } from "../context-monitor.mjs";
|
|
25
|
+
import {
|
|
26
|
+
advanceToNextCycle,
|
|
27
|
+
clampPercent,
|
|
28
|
+
readJson,
|
|
29
|
+
writeJsonSafe,
|
|
30
|
+
} from "../utils.mjs";
|
|
18
31
|
|
|
19
32
|
export const CLAUDE_USAGE_POLL_BASE_MS = 5_000;
|
|
20
33
|
export const CLAUDE_USAGE_POLL_JITTER_RATIO = 0.2;
|
|
@@ -39,15 +52,20 @@ export function computeClaudeUsagePollState({
|
|
|
39
52
|
random = Math.random,
|
|
40
53
|
jitterRatio = CLAUDE_USAGE_POLL_JITTER_RATIO,
|
|
41
54
|
} = {}) {
|
|
42
|
-
const current429s = Number.isFinite(consecutive429s)
|
|
43
|
-
|
|
44
|
-
const stepIndex = outcome === "rate_limit"
|
|
45
|
-
? Math.min(next429s, CLAUDE_USAGE_RATE_LIMIT_BACKOFF_MS.length - 1)
|
|
55
|
+
const current429s = Number.isFinite(consecutive429s)
|
|
56
|
+
? Math.max(0, consecutive429s)
|
|
46
57
|
: 0;
|
|
58
|
+
const next429s = outcome === "rate_limit" ? current429s + 1 : 0;
|
|
59
|
+
const stepIndex =
|
|
60
|
+
outcome === "rate_limit"
|
|
61
|
+
? Math.min(next429s, CLAUDE_USAGE_RATE_LIMIT_BACKOFF_MS.length - 1)
|
|
62
|
+
: 0;
|
|
47
63
|
const baseDelayMs = CLAUDE_USAGE_RATE_LIMIT_BACKOFF_MS[stepIndex];
|
|
48
64
|
const sample = Number(random?.());
|
|
49
|
-
const normalized = Number.isFinite(sample)
|
|
50
|
-
|
|
65
|
+
const normalized = Number.isFinite(sample)
|
|
66
|
+
? Math.min(1, Math.max(0, sample))
|
|
67
|
+
: 0.5;
|
|
68
|
+
const jitterFactor = 1 + (normalized * 2 - 1) * jitterRatio;
|
|
51
69
|
return {
|
|
52
70
|
consecutive429s: next429s,
|
|
53
71
|
baseDelayMs,
|
|
@@ -65,9 +83,13 @@ function getSnapshotSchedule(cache) {
|
|
|
65
83
|
};
|
|
66
84
|
}
|
|
67
85
|
|
|
68
|
-
const ageMs = Number.isFinite(timestamp)
|
|
86
|
+
const ageMs = Number.isFinite(timestamp)
|
|
87
|
+
? Date.now() - timestamp
|
|
88
|
+
: Number.MAX_SAFE_INTEGER;
|
|
69
89
|
const fallbackMs = cache?.error
|
|
70
|
-
?
|
|
90
|
+
? cache.errorType === "rate_limit"
|
|
91
|
+
? CLAUDE_USAGE_429_BACKOFF_MS
|
|
92
|
+
: CLAUDE_USAGE_ERROR_BACKOFF_MS
|
|
71
93
|
: getClaudeUsageStaleMs();
|
|
72
94
|
|
|
73
95
|
return {
|
|
@@ -90,45 +112,56 @@ export function readClaudeCredentials() {
|
|
|
90
112
|
|
|
91
113
|
export function refreshClaudeAccessToken(refreshToken) {
|
|
92
114
|
return new Promise((resolve) => {
|
|
93
|
-
const clientId =
|
|
115
|
+
const clientId =
|
|
116
|
+
process.env.CLAUDE_CODE_OAUTH_CLIENT_ID || DEFAULT_OAUTH_CLIENT_ID;
|
|
94
117
|
const body = new URLSearchParams({
|
|
95
118
|
grant_type: "refresh_token",
|
|
96
119
|
refresh_token: refreshToken,
|
|
97
120
|
client_id: clientId,
|
|
98
121
|
}).toString();
|
|
99
|
-
const req = https.request(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
122
|
+
const req = https.request(
|
|
123
|
+
{
|
|
124
|
+
hostname: "platform.claude.com",
|
|
125
|
+
path: "/v1/oauth/token",
|
|
126
|
+
method: "POST",
|
|
127
|
+
headers: {
|
|
128
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
129
|
+
"Content-Length": Buffer.byteLength(body),
|
|
130
|
+
},
|
|
131
|
+
timeout: CLAUDE_API_TIMEOUT_MS,
|
|
106
132
|
},
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
133
|
+
(res) => {
|
|
134
|
+
let data = "";
|
|
135
|
+
res.on("data", (chunk) => {
|
|
136
|
+
data += chunk;
|
|
137
|
+
});
|
|
138
|
+
res.on("end", () => {
|
|
139
|
+
if (res.statusCode === 200) {
|
|
140
|
+
try {
|
|
141
|
+
const parsed = JSON.parse(data);
|
|
142
|
+
if (parsed.access_token) {
|
|
143
|
+
resolve({
|
|
144
|
+
accessToken: parsed.access_token,
|
|
145
|
+
refreshToken: parsed.refresh_token || refreshToken,
|
|
146
|
+
expiresAt: parsed.expires_in
|
|
147
|
+
? Date.now() + parsed.expires_in * 1000
|
|
148
|
+
: parsed.expires_at,
|
|
149
|
+
});
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
} catch {
|
|
153
|
+
/* parse 실패 */
|
|
124
154
|
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
155
|
+
}
|
|
156
|
+
resolve(null);
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
);
|
|
130
160
|
req.on("error", () => resolve(null));
|
|
131
|
-
req.on("timeout", () => {
|
|
161
|
+
req.on("timeout", () => {
|
|
162
|
+
req.destroy();
|
|
163
|
+
resolve(null);
|
|
164
|
+
});
|
|
132
165
|
req.end(body);
|
|
133
166
|
});
|
|
134
167
|
}
|
|
@@ -142,34 +175,48 @@ export function writeBackClaudeCredentials(creds) {
|
|
|
142
175
|
if (creds.expiresAt != null) target.expiresAt = creds.expiresAt;
|
|
143
176
|
if (creds.refreshToken) target.refreshToken = creds.refreshToken;
|
|
144
177
|
writeFileSync(CLAUDE_CREDENTIALS_PATH, JSON.stringify(data, null, 2));
|
|
145
|
-
} catch {
|
|
178
|
+
} catch {
|
|
179
|
+
/* 쓰기 실패 무시 */
|
|
180
|
+
}
|
|
146
181
|
}
|
|
147
182
|
|
|
148
183
|
export function fetchClaudeUsageFromApi(accessToken) {
|
|
149
184
|
return new Promise((resolve) => {
|
|
150
|
-
const req = https.request(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
185
|
+
const req = https.request(
|
|
186
|
+
{
|
|
187
|
+
hostname: "api.anthropic.com",
|
|
188
|
+
path: "/api/oauth/usage",
|
|
189
|
+
method: "GET",
|
|
190
|
+
headers: {
|
|
191
|
+
Authorization: `Bearer ${accessToken}`,
|
|
192
|
+
"anthropic-beta": "oauth-2025-04-20",
|
|
193
|
+
"Content-Type": "application/json",
|
|
194
|
+
},
|
|
195
|
+
timeout: CLAUDE_API_TIMEOUT_MS,
|
|
158
196
|
},
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
197
|
+
(res) => {
|
|
198
|
+
let data = "";
|
|
199
|
+
res.on("data", (chunk) => {
|
|
200
|
+
data += chunk;
|
|
201
|
+
});
|
|
202
|
+
res.on("end", () => {
|
|
203
|
+
if (res.statusCode === 200) {
|
|
204
|
+
try {
|
|
205
|
+
resolve({ ok: true, data: JSON.parse(data) });
|
|
206
|
+
} catch {
|
|
207
|
+
resolve({ ok: false, status: 0 });
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
resolve({ ok: false, status: res.statusCode });
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
},
|
|
214
|
+
);
|
|
171
215
|
req.on("error", () => resolve({ ok: false, status: 0, error: "network" }));
|
|
172
|
-
req.on("timeout", () => {
|
|
216
|
+
req.on("timeout", () => {
|
|
217
|
+
req.destroy();
|
|
218
|
+
resolve({ ok: false, status: 0, error: "timeout" });
|
|
219
|
+
});
|
|
173
220
|
req.end();
|
|
174
221
|
});
|
|
175
222
|
}
|
|
@@ -195,11 +242,17 @@ export function stripStaleResets(data) {
|
|
|
195
242
|
const copy = { ...data };
|
|
196
243
|
if (copy.fiveHourResetsAt) {
|
|
197
244
|
const t = new Date(copy.fiveHourResetsAt).getTime();
|
|
198
|
-
if (!isNaN(t))
|
|
245
|
+
if (!Number.isNaN(t))
|
|
246
|
+
copy.fiveHourResetsAt = new Date(
|
|
247
|
+
advanceToNextCycle(t, FIVE_HOUR_MS),
|
|
248
|
+
).toISOString();
|
|
199
249
|
}
|
|
200
250
|
if (copy.weeklyResetsAt) {
|
|
201
251
|
const t = new Date(copy.weeklyResetsAt).getTime();
|
|
202
|
-
if (!isNaN(t))
|
|
252
|
+
if (!Number.isNaN(t))
|
|
253
|
+
copy.weeklyResetsAt = new Date(
|
|
254
|
+
advanceToNextCycle(t, SEVEN_DAY_MS),
|
|
255
|
+
).toISOString();
|
|
203
256
|
}
|
|
204
257
|
return copy;
|
|
205
258
|
}
|
|
@@ -208,9 +261,10 @@ export function readClaudeUsageSnapshot() {
|
|
|
208
261
|
const cache = readJson(CLAUDE_USAGE_CACHE_PATH, null);
|
|
209
262
|
const ts = Number(cache?.timestamp);
|
|
210
263
|
const schedule = getSnapshotSchedule(cache);
|
|
211
|
-
const staleBackoffActive =
|
|
212
|
-
&&
|
|
213
|
-
|
|
264
|
+
const staleBackoffActive =
|
|
265
|
+
cache?.errorType === "rate_limit" &&
|
|
266
|
+
Number.isFinite(schedule.nextRefreshAt) &&
|
|
267
|
+
Date.now() < schedule.nextRefreshAt;
|
|
214
268
|
|
|
215
269
|
// 1차: 자체 캐시에 유효 데이터가 있는 경우
|
|
216
270
|
if (cache?.data) {
|
|
@@ -225,7 +279,10 @@ export function readClaudeUsageSnapshot() {
|
|
|
225
279
|
// resets_at이 지난 윈도우의 percent를 0으로 보정 (stale 캐시 방지)
|
|
226
280
|
const data = { ...cache.data };
|
|
227
281
|
const now = Date.now();
|
|
228
|
-
if (
|
|
282
|
+
if (
|
|
283
|
+
data.fiveHourResetsAt &&
|
|
284
|
+
new Date(data.fiveHourResetsAt).getTime() <= now
|
|
285
|
+
) {
|
|
229
286
|
data.fiveHourPercent = 0;
|
|
230
287
|
}
|
|
231
288
|
if (data.weeklyResetsAt && new Date(data.weeklyResetsAt).getTime() <= now) {
|
|
@@ -244,8 +301,15 @@ export function readClaudeUsageSnapshot() {
|
|
|
244
301
|
return { data: omcCache.data, shouldRefresh: false, isStale: false };
|
|
245
302
|
}
|
|
246
303
|
// stale OMC fallback 또는 null (--% 플레이스홀더 표시, 가짜 0% 방지)
|
|
247
|
-
const staleData =
|
|
248
|
-
|
|
304
|
+
const staleData =
|
|
305
|
+
omcCache?.data?.fiveHourPercent != null
|
|
306
|
+
? stripStaleResets(omcCache.data)
|
|
307
|
+
: null;
|
|
308
|
+
return {
|
|
309
|
+
data: staleData,
|
|
310
|
+
shouldRefresh: false,
|
|
311
|
+
isStale: staleBackoffActive,
|
|
312
|
+
};
|
|
249
313
|
}
|
|
250
314
|
}
|
|
251
315
|
|
|
@@ -253,36 +317,54 @@ export function readClaudeUsageSnapshot() {
|
|
|
253
317
|
const OMC_CACHE_MAX_AGE_MS = 30 * 60 * 1000;
|
|
254
318
|
const omcCache = readJson(OMC_PLUGIN_USAGE_CACHE_PATH, null);
|
|
255
319
|
if (omcCache?.data?.fiveHourPercent != null) {
|
|
256
|
-
const omcAge = Number.isFinite(omcCache.timestamp)
|
|
320
|
+
const omcAge = Number.isFinite(omcCache.timestamp)
|
|
321
|
+
? Date.now() - omcCache.timestamp
|
|
322
|
+
: Number.MAX_SAFE_INTEGER;
|
|
257
323
|
if (omcAge < OMC_CACHE_MAX_AGE_MS) {
|
|
258
324
|
writeClaudeUsageCache(omcCache.data);
|
|
259
|
-
return {
|
|
325
|
+
return {
|
|
326
|
+
data: omcCache.data,
|
|
327
|
+
shouldRefresh: omcAge > getClaudeUsageStaleMs(),
|
|
328
|
+
isStale: false,
|
|
329
|
+
};
|
|
260
330
|
}
|
|
261
331
|
// stale이어도 data: null보다는 오래된 데이터를 fallback으로 표시
|
|
262
|
-
return {
|
|
332
|
+
return {
|
|
333
|
+
data: stripStaleResets(omcCache.data),
|
|
334
|
+
shouldRefresh: true,
|
|
335
|
+
isStale: false,
|
|
336
|
+
};
|
|
263
337
|
}
|
|
264
338
|
|
|
265
339
|
// 캐시/fallback 모두 없음: null 반환 → --% 플레이스홀더 + 리프레시 시도
|
|
266
340
|
return { data: null, shouldRefresh: true, isStale: false };
|
|
267
341
|
}
|
|
268
342
|
|
|
269
|
-
export function writeClaudeUsageCache(
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
343
|
+
export function writeClaudeUsageCache(
|
|
344
|
+
data,
|
|
345
|
+
errorInfo = null,
|
|
346
|
+
pollState = null,
|
|
347
|
+
) {
|
|
348
|
+
const state =
|
|
349
|
+
pollState ||
|
|
350
|
+
(errorInfo
|
|
351
|
+
? {
|
|
352
|
+
consecutive429s: errorInfo.type === "rate_limit" ? 1 : 0,
|
|
353
|
+
baseDelayMs:
|
|
354
|
+
errorInfo.type === "rate_limit"
|
|
355
|
+
? CLAUDE_USAGE_429_BACKOFF_MS
|
|
356
|
+
: CLAUDE_USAGE_ERROR_BACKOFF_MS,
|
|
357
|
+
delayMs:
|
|
358
|
+
errorInfo.type === "rate_limit"
|
|
359
|
+
? CLAUDE_USAGE_429_BACKOFF_MS
|
|
360
|
+
: CLAUDE_USAGE_ERROR_BACKOFF_MS,
|
|
361
|
+
}
|
|
362
|
+
: computeClaudeUsagePollState({ outcome: "success" }));
|
|
281
363
|
const entry = {
|
|
282
364
|
timestamp: Date.now(),
|
|
283
365
|
data,
|
|
284
366
|
error: !!errorInfo,
|
|
285
|
-
errorType: errorInfo?.type || null,
|
|
367
|
+
errorType: errorInfo?.type || null, // "rate_limit" | "auth" | "network" | "unknown"
|
|
286
368
|
errorStatus: errorInfo?.status || null, // HTTP 상태 코드
|
|
287
369
|
consecutive429s: state.consecutive429s,
|
|
288
370
|
nextRefreshBaseMs: state.baseDelayMs,
|
|
@@ -305,7 +387,9 @@ export async function fetchClaudeUsage(forceRefresh = false) {
|
|
|
305
387
|
return existingSnapshot.data || null;
|
|
306
388
|
}
|
|
307
389
|
const cache = readJson(CLAUDE_USAGE_CACHE_PATH, null);
|
|
308
|
-
const consecutive429s = Number.isFinite(cache?.consecutive429s)
|
|
390
|
+
const consecutive429s = Number.isFinite(cache?.consecutive429s)
|
|
391
|
+
? cache.consecutive429s
|
|
392
|
+
: 0;
|
|
309
393
|
let creds = readClaudeCredentials();
|
|
310
394
|
if (!creds) {
|
|
311
395
|
writeClaudeUsageCache(null, { type: "auth", status: 0 });
|
|
@@ -327,19 +411,37 @@ export async function fetchClaudeUsage(forceRefresh = false) {
|
|
|
327
411
|
const result = await fetchClaudeUsageFromApi(creds.accessToken);
|
|
328
412
|
if (!result.ok) {
|
|
329
413
|
// 에러 유형별 분류하여 backoff 차등 적용
|
|
330
|
-
const errorType =
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
414
|
+
const errorType =
|
|
415
|
+
result.status === 429
|
|
416
|
+
? "rate_limit"
|
|
417
|
+
: result.status === 401 || result.status === 403
|
|
418
|
+
? "auth"
|
|
419
|
+
: result.error === "timeout" || result.error === "network"
|
|
420
|
+
? "network"
|
|
421
|
+
: "unknown";
|
|
422
|
+
const pollState =
|
|
423
|
+
errorType === "rate_limit"
|
|
424
|
+
? computeClaudeUsagePollState({
|
|
425
|
+
consecutive429s,
|
|
426
|
+
outcome: "rate_limit",
|
|
427
|
+
})
|
|
428
|
+
: null;
|
|
429
|
+
writeClaudeUsageCache(
|
|
430
|
+
existingSnapshot.data,
|
|
431
|
+
{ type: errorType, status: result.status },
|
|
432
|
+
pollState,
|
|
433
|
+
);
|
|
338
434
|
return existingSnapshot.data || null;
|
|
339
435
|
}
|
|
340
436
|
const usage = parseClaudeUsageResponse(result.data);
|
|
341
|
-
const pollState = usage
|
|
342
|
-
|
|
437
|
+
const pollState = usage
|
|
438
|
+
? computeClaudeUsagePollState({ outcome: "success" })
|
|
439
|
+
: null;
|
|
440
|
+
writeClaudeUsageCache(
|
|
441
|
+
usage,
|
|
442
|
+
usage ? null : { type: "unknown", status: 0 },
|
|
443
|
+
pollState,
|
|
444
|
+
);
|
|
343
445
|
return usage;
|
|
344
446
|
}
|
|
345
447
|
|
|
@@ -351,23 +453,28 @@ export function scheduleClaudeUsageRefresh() {
|
|
|
351
453
|
try {
|
|
352
454
|
const omcCache = readJson(OMC_PLUGIN_USAGE_CACHE_PATH, null);
|
|
353
455
|
if (omcCache?.data?.fiveHourPercent != null) {
|
|
354
|
-
const omcAge = Number.isFinite(omcCache.timestamp)
|
|
456
|
+
const omcAge = Number.isFinite(omcCache.timestamp)
|
|
457
|
+
? Date.now() - omcCache.timestamp
|
|
458
|
+
: Infinity;
|
|
355
459
|
if (omcAge < getClaudeUsageStaleMs()) {
|
|
356
460
|
writeClaudeUsageCache(omcCache.data); // HUD 캐시에 복사만
|
|
357
461
|
return;
|
|
358
462
|
}
|
|
359
463
|
}
|
|
360
|
-
} catch {
|
|
464
|
+
} catch {
|
|
465
|
+
/* 무시 */
|
|
466
|
+
}
|
|
361
467
|
|
|
362
468
|
// 스폰 락: 30초 내 이미 스폰했으면 중복 방지 (첫 설치 시 429 방지)
|
|
363
|
-
const lockPath = join(homedir(), ".claude", "cache", ".claude-refresh-lock");
|
|
364
469
|
try {
|
|
365
|
-
if (existsSync(
|
|
366
|
-
const lockAge = Date.now() - readJson(
|
|
367
|
-
if (lockAge <
|
|
470
|
+
if (existsSync(CLAUDE_REFRESH_LOCK_PATH)) {
|
|
471
|
+
const lockAge = Date.now() - readJson(CLAUDE_REFRESH_LOCK_PATH, {}).t;
|
|
472
|
+
if (lockAge < SPAWN_LOCK_TTL_MS) return;
|
|
368
473
|
}
|
|
369
|
-
writeJsonSafe(
|
|
370
|
-
} catch {
|
|
474
|
+
writeJsonSafe(CLAUDE_REFRESH_LOCK_PATH, { t: Date.now() });
|
|
475
|
+
} catch {
|
|
476
|
+
/* 락 실패 무시 — 스폰 진행 */
|
|
477
|
+
}
|
|
371
478
|
|
|
372
479
|
try {
|
|
373
480
|
const child = spawn(process.execPath, [scriptPath, CLAUDE_REFRESH_FLAG], {
|
|
@@ -378,7 +485,11 @@ export function scheduleClaudeUsageRefresh() {
|
|
|
378
485
|
child.unref();
|
|
379
486
|
} catch (spawnErr) {
|
|
380
487
|
// spawn 실패 시 에러 유형을 캐시에 기록 (HUD에서 원인 힌트 표시 가능)
|
|
381
|
-
writeClaudeUsageCache(null, {
|
|
488
|
+
writeClaudeUsageCache(null, {
|
|
489
|
+
type: "network",
|
|
490
|
+
status: 0,
|
|
491
|
+
hint: String(spawnErr?.message || spawnErr),
|
|
492
|
+
});
|
|
382
493
|
}
|
|
383
494
|
}
|
|
384
495
|
|
package/hud/providers/codex.mjs
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
// ============================================================================
|
|
2
2
|
// Codex rate limits 추출 / 캐싱
|
|
3
3
|
// ============================================================================
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
5
7
|
import { homedir } from "node:os";
|
|
6
8
|
import { join } from "node:path";
|
|
7
|
-
import { spawn } from "node:child_process";
|
|
8
9
|
import {
|
|
9
|
-
CODEX_AUTH_PATH,
|
|
10
|
-
CODEX_MIN_BUCKETS,
|
|
11
|
-
|
|
10
|
+
CODEX_AUTH_PATH,
|
|
11
|
+
CODEX_MIN_BUCKETS,
|
|
12
|
+
CODEX_QUOTA_CACHE_PATH,
|
|
13
|
+
CODEX_QUOTA_STALE_MS,
|
|
14
|
+
CODEX_REFRESH_FLAG,
|
|
15
|
+
CODEX_REFRESH_LOCK_PATH,
|
|
16
|
+
SPAWN_LOCK_TTL_MS,
|
|
12
17
|
} from "../constants.mjs";
|
|
13
|
-
import { readJson, writeJsonSafe
|
|
18
|
+
import { decodeJwtEmail, readJson, writeJsonSafe } from "../utils.mjs";
|
|
14
19
|
|
|
15
20
|
// window_minutes 기반 5h/1w 슬롯 분류
|
|
16
21
|
export function classifyBucket(bucket) {
|
|
@@ -37,7 +42,9 @@ export function getCodexEmail() {
|
|
|
37
42
|
try {
|
|
38
43
|
const auth = JSON.parse(readFileSync(CODEX_AUTH_PATH, "utf-8"));
|
|
39
44
|
return decodeJwtEmail(auth?.tokens?.id_token);
|
|
40
|
-
} catch {
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
// resets_at이 지난 윈도우의 used_percent를 0으로 보정
|
|
@@ -46,13 +53,22 @@ export function expireStaleCodexBuckets(buckets) {
|
|
|
46
53
|
const nowSec = Math.floor(Date.now() / 1000);
|
|
47
54
|
const result = {};
|
|
48
55
|
for (const [key, bucket] of Object.entries(buckets)) {
|
|
49
|
-
if (!bucket) {
|
|
56
|
+
if (!bucket) {
|
|
57
|
+
result[key] = bucket;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
50
60
|
let updated = bucket;
|
|
51
61
|
if (bucket.primary?.resets_at && bucket.primary.resets_at <= nowSec) {
|
|
52
|
-
updated = {
|
|
62
|
+
updated = {
|
|
63
|
+
...updated,
|
|
64
|
+
primary: { ...updated.primary, used_percent: 0 },
|
|
65
|
+
};
|
|
53
66
|
}
|
|
54
67
|
if (bucket.secondary?.resets_at && bucket.secondary.resets_at <= nowSec) {
|
|
55
|
-
updated = {
|
|
68
|
+
updated = {
|
|
69
|
+
...updated,
|
|
70
|
+
secondary: { ...updated.secondary, used_percent: 0 },
|
|
71
|
+
};
|
|
56
72
|
}
|
|
57
73
|
result[key] = updated;
|
|
58
74
|
}
|
|
@@ -73,15 +89,23 @@ export function getCodexRateLimits() {
|
|
|
73
89
|
for (let dayOffset = 0; dayOffset <= 6; dayOffset++) {
|
|
74
90
|
const d = new Date(now.getTime() - dayOffset * 86_400_000);
|
|
75
91
|
const sessDir = join(
|
|
76
|
-
homedir(),
|
|
92
|
+
homedir(),
|
|
93
|
+
".codex",
|
|
94
|
+
"sessions",
|
|
77
95
|
String(d.getFullYear()),
|
|
78
96
|
String(d.getMonth() + 1).padStart(2, "0"),
|
|
79
97
|
String(d.getDate()).padStart(2, "0"),
|
|
80
98
|
);
|
|
81
99
|
if (!existsSync(sessDir)) continue;
|
|
82
100
|
let files;
|
|
83
|
-
try {
|
|
84
|
-
|
|
101
|
+
try {
|
|
102
|
+
files = readdirSync(sessDir)
|
|
103
|
+
.filter((f) => f.endsWith(".jsonl"))
|
|
104
|
+
.sort()
|
|
105
|
+
.reverse();
|
|
106
|
+
} catch {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
85
109
|
|
|
86
110
|
const mergedBuckets = {};
|
|
87
111
|
for (const file of files) {
|
|
@@ -96,33 +120,47 @@ export function getCodexRateLimits() {
|
|
|
96
120
|
// window_minutes 기준으로 5h/1w 슬롯 정규화
|
|
97
121
|
const { primary, secondary } = normalizeBuckets(rl);
|
|
98
122
|
mergedBuckets[rl.limit_id] = {
|
|
99
|
-
limitId: rl.limit_id,
|
|
100
|
-
|
|
123
|
+
limitId: rl.limit_id,
|
|
124
|
+
limitName: rl.limit_name,
|
|
125
|
+
primary,
|
|
126
|
+
secondary,
|
|
101
127
|
credits: rl.credits,
|
|
102
128
|
tokens: evt.payload?.info?.total_token_usage,
|
|
103
129
|
contextWindow: evt.payload?.info?.model_context_window,
|
|
104
130
|
timestamp: evt.timestamp,
|
|
105
131
|
};
|
|
106
|
-
} else if (
|
|
132
|
+
} else if (
|
|
133
|
+
dayOffset <= 1 &&
|
|
134
|
+
!rl &&
|
|
135
|
+
evt?.payload?.info?.total_token_usage &&
|
|
136
|
+
!syntheticBucket
|
|
137
|
+
) {
|
|
107
138
|
// 2일 이내 token_count: 합성 버킷 (rate_limits가 null일 때 행 활성화용, stale 방지)
|
|
108
139
|
syntheticBucket = {
|
|
109
|
-
limitId: "codex",
|
|
110
|
-
|
|
140
|
+
limitId: "codex",
|
|
141
|
+
limitName: "codex-session",
|
|
142
|
+
primary: null,
|
|
143
|
+
secondary: null,
|
|
111
144
|
credits: null,
|
|
112
145
|
tokens: evt.payload.info.total_token_usage,
|
|
113
146
|
contextWindow: evt.payload.info.model_context_window,
|
|
114
147
|
timestamp: evt.timestamp,
|
|
115
148
|
};
|
|
116
149
|
}
|
|
117
|
-
} catch {
|
|
150
|
+
} catch {
|
|
151
|
+
/* 라인 파싱 실패 무시 */
|
|
152
|
+
}
|
|
118
153
|
if (Object.keys(mergedBuckets).length >= CODEX_MIN_BUCKETS) break;
|
|
119
154
|
}
|
|
120
|
-
} catch {
|
|
155
|
+
} catch {
|
|
156
|
+
/* 파일 읽기 실패 무시 */
|
|
157
|
+
}
|
|
121
158
|
}
|
|
122
159
|
// 실제 rate_limits 발견 → 토큰 데이터 병합 후 즉시 반환
|
|
123
160
|
if (Object.keys(mergedBuckets).length > 0) {
|
|
124
161
|
if (syntheticBucket) {
|
|
125
|
-
const main =
|
|
162
|
+
const main =
|
|
163
|
+
mergedBuckets.codex || mergedBuckets[Object.keys(mergedBuckets)[0]];
|
|
126
164
|
if (main && !main.tokens) main.tokens = syntheticBucket.tokens;
|
|
127
165
|
}
|
|
128
166
|
expireStaleCodexBuckets(mergedBuckets);
|
|
@@ -163,7 +201,9 @@ export function scheduleCodexRateLimitRefresh() {
|
|
|
163
201
|
if (lockAge < SPAWN_LOCK_TTL_MS) return;
|
|
164
202
|
}
|
|
165
203
|
writeJsonSafe(CODEX_REFRESH_LOCK_PATH, { t: Date.now() });
|
|
166
|
-
} catch {
|
|
204
|
+
} catch {
|
|
205
|
+
/* 락 실패 무시 — 스폰 진행 */
|
|
206
|
+
}
|
|
167
207
|
|
|
168
208
|
try {
|
|
169
209
|
const child = spawn(process.execPath, [scriptPath, CODEX_REFRESH_FLAG], {
|