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.
Files changed (329) hide show
  1. package/LICENSE +21 -21
  2. package/bin/tfx-doctor-tui.mjs +1 -1
  3. package/bin/tfx-doctor.mjs +6 -1
  4. package/bin/tfx-profile.mjs +1 -1
  5. package/bin/tfx-setup-tui.mjs +1 -1
  6. package/bin/tfx-setup.mjs +6 -1
  7. package/bin/triflux.mjs +2396 -1140
  8. package/hooks/agent-route-guard.mjs +12 -8
  9. package/hooks/cross-review-tracker.mjs +21 -8
  10. package/hooks/error-context.mjs +19 -7
  11. package/hooks/hook-adaptive-collector.mjs +18 -16
  12. package/hooks/hook-manager.mjs +93 -32
  13. package/hooks/hook-orchestrator.mjs +108 -24
  14. package/hooks/hook-registry.json +11 -0
  15. package/hooks/keyword-rules.json +6 -10
  16. package/hooks/lib/resolve-root.mjs +1 -1
  17. package/hooks/mcp-config-watcher.mjs +6 -2
  18. package/hooks/pipeline-stop.mjs +3 -6
  19. package/hooks/safety-guard.mjs +99 -28
  20. package/hooks/session-start-fast.mjs +143 -0
  21. package/hooks/subagent-verifier.mjs +5 -4
  22. package/hub/account-broker.mjs +256 -60
  23. package/hub/adaptive-diagnostic.mjs +75 -48
  24. package/hub/adaptive-inject.mjs +95 -57
  25. package/hub/adaptive-memory.mjs +156 -42
  26. package/hub/adaptive.mjs +60 -31
  27. package/hub/assign-callbacks.mjs +67 -30
  28. package/hub/bridge.mjs +0 -1
  29. package/hub/cli-adapter-base.mjs +200 -48
  30. package/hub/codex-adapter.mjs +76 -96
  31. package/hub/codex-compat.mjs +3 -3
  32. package/hub/codex-preflight.mjs +63 -37
  33. package/hub/delegator/contracts.mjs +19 -23
  34. package/hub/delegator/index.mjs +3 -3
  35. package/hub/delegator/service.mjs +88 -64
  36. package/hub/delegator/tool-definitions.mjs +5 -5
  37. package/hub/fullcycle.mjs +33 -17
  38. package/hub/gemini-adapter.mjs +69 -94
  39. package/hub/hitl.mjs +89 -30
  40. package/hub/intent.mjs +161 -38
  41. package/hub/lib/cache-guard.mjs +43 -17
  42. package/hub/lib/mcp-response-cache.mjs +66 -32
  43. package/hub/lib/memory-store.mjs +285 -111
  44. package/hub/lib/path-utils.mjs +35 -37
  45. package/hub/lib/process-utils.mjs +106 -37
  46. package/hub/lib/spawn-trace.mjs +527 -0
  47. package/hub/lib/ssh-command.mjs +34 -4
  48. package/hub/lib/ssh-retry.mjs +5 -1
  49. package/hub/lib/uuidv7.mjs +4 -3
  50. package/hub/memory-doctor.mjs +266 -106
  51. package/hub/middleware/request-logger.mjs +61 -34
  52. package/hub/paths.mjs +9 -9
  53. package/hub/pipeline/gates/confidence.mjs +34 -15
  54. package/hub/pipeline/gates/consensus.mjs +27 -15
  55. package/hub/pipeline/gates/index.mjs +7 -3
  56. package/hub/pipeline/gates/selfcheck.mjs +57 -19
  57. package/hub/pipeline/index.mjs +77 -42
  58. package/hub/pipeline/state.mjs +10 -10
  59. package/hub/pipeline/transitions.mjs +40 -23
  60. package/hub/platform.mjs +57 -48
  61. package/hub/promote-penalties.mjs +25 -7
  62. package/hub/quality/deslop.mjs +70 -49
  63. package/hub/research.mjs +32 -25
  64. package/hub/router.mjs +240 -107
  65. package/hub/routing/complexity.mjs +132 -29
  66. package/hub/routing/index.mjs +17 -12
  67. package/hub/routing/q-learning.mjs +76 -28
  68. package/hub/server.mjs +4 -4
  69. package/hub/session-fingerprint.mjs +126 -60
  70. package/hub/state.mjs +84 -43
  71. package/hub/store-adapter.mjs +59 -26
  72. package/hub/store.mjs +356 -153
  73. package/hub/team/agent-map.json +22 -7
  74. package/hub/team/ansi.mjs +186 -122
  75. package/hub/team/backend.mjs +28 -10
  76. package/hub/team/cli/commands/attach.mjs +29 -9
  77. package/hub/team/cli/commands/control.mjs +29 -8
  78. package/hub/team/cli/commands/debug.mjs +32 -11
  79. package/hub/team/cli/commands/focus.mjs +38 -11
  80. package/hub/team/cli/commands/interrupt.mjs +18 -6
  81. package/hub/team/cli/commands/kill.mjs +16 -5
  82. package/hub/team/cli/commands/list.mjs +11 -4
  83. package/hub/team/cli/commands/send.mjs +19 -6
  84. package/hub/team/cli/commands/start/index.mjs +154 -31
  85. package/hub/team/cli/commands/start/parse-args.mjs +38 -11
  86. package/hub/team/cli/commands/start/start-headless.mjs +112 -36
  87. package/hub/team/cli/commands/start/start-in-process.mjs +12 -2
  88. package/hub/team/cli/commands/start/start-mux.mjs +70 -21
  89. package/hub/team/cli/commands/start/start-wt.mjs +29 -12
  90. package/hub/team/cli/commands/status.mjs +43 -14
  91. package/hub/team/cli/commands/stop.mjs +11 -4
  92. package/hub/team/cli/commands/task.mjs +8 -3
  93. package/hub/team/cli/commands/tasks.mjs +1 -1
  94. package/hub/team/cli/index.mjs +2 -2
  95. package/hub/team/cli/manifest.mjs +38 -8
  96. package/hub/team/cli/render.mjs +30 -8
  97. package/hub/team/cli/services/attach-fallback.mjs +31 -11
  98. package/hub/team/cli/services/hub-client.mjs +42 -14
  99. package/hub/team/cli/services/member-selector.mjs +11 -4
  100. package/hub/team/cli/services/native-control.mjs +48 -21
  101. package/hub/team/cli/services/runtime-mode.mjs +2 -1
  102. package/hub/team/cli/services/state-store.mjs +25 -8
  103. package/hub/team/cli/services/task-model.mjs +16 -6
  104. package/hub/team/conductor-mesh-bridge.mjs +24 -23
  105. package/hub/team/conductor.mjs +8 -4
  106. package/hub/team/dashboard-anchor.mjs +4 -5
  107. package/hub/team/dashboard-layout.mjs +3 -1
  108. package/hub/team/dashboard-open.mjs +41 -21
  109. package/hub/team/dashboard.mjs +76 -28
  110. package/hub/team/event-log.mjs +18 -10
  111. package/hub/team/handoff.mjs +31 -15
  112. package/hub/team/headless.mjs +2 -1
  113. package/hub/team/health-probe.mjs +69 -54
  114. package/hub/team/launcher-template.mjs +16 -13
  115. package/hub/team/native-supervisor.mjs +65 -21
  116. package/hub/team/native.mjs +74 -35
  117. package/hub/team/nativeProxy.mjs +184 -113
  118. package/hub/team/notify.mjs +119 -76
  119. package/hub/team/orchestrator.mjs +9 -4
  120. package/hub/team/pane.mjs +12 -7
  121. package/hub/team/process-cleanup.mjs +25 -16
  122. package/hub/team/psmux.mjs +491 -201
  123. package/hub/team/remote-probe.mjs +68 -52
  124. package/hub/team/remote-session.mjs +117 -59
  125. package/hub/team/remote-watcher.mjs +61 -33
  126. package/hub/team/routing.mjs +51 -25
  127. package/hub/team/runtime-strategy.mjs +3 -1
  128. package/hub/team/session.mjs +98 -34
  129. package/hub/team/staleState.mjs +72 -30
  130. package/hub/team/swarm-locks.mjs +15 -13
  131. package/hub/team/swarm-planner.mjs +32 -21
  132. package/hub/team/swarm-reconciler.mjs +48 -23
  133. package/hub/team/tui-lite.mjs +266 -68
  134. package/hub/team/tui-remote-adapter.mjs +14 -10
  135. package/hub/team/tui-viewer.mjs +99 -43
  136. package/hub/team/tui.mjs +708 -271
  137. package/hub/team/worktree-lifecycle.mjs +152 -58
  138. package/hub/team/wt-manager.mjs +24 -14
  139. package/hub/token-mode.mjs +71 -71
  140. package/hub/tray.mjs +66 -23
  141. package/hub/workers/claude-worker.mjs +162 -118
  142. package/hub/workers/codex-mcp.mjs +192 -141
  143. package/hub/workers/delegator-mcp.mjs +507 -333
  144. package/hub/workers/factory.mjs +8 -8
  145. package/hub/workers/gemini-worker.mjs +115 -84
  146. package/hub/workers/interface.mjs +6 -1
  147. package/hub/workers/worker-utils.mjs +21 -14
  148. package/hud/colors.mjs +27 -9
  149. package/hud/constants.mjs +162 -26
  150. package/hud/context-monitor.mjs +82 -41
  151. package/hud/hud-qos-status.mjs +129 -49
  152. package/hud/mission-board.mjs +6 -3
  153. package/hud/providers/claude.mjs +226 -115
  154. package/hud/providers/codex.mjs +62 -22
  155. package/hud/providers/gemini.mjs +168 -56
  156. package/hud/renderers.mjs +384 -119
  157. package/hud/terminal.mjs +101 -31
  158. package/hud/utils.mjs +78 -38
  159. package/mesh/index.mjs +11 -5
  160. package/mesh/mesh-budget.mjs +18 -9
  161. package/mesh/mesh-heartbeat.mjs +1 -1
  162. package/mesh/mesh-queue.mjs +3 -5
  163. package/mesh/mesh-router.mjs +5 -4
  164. package/package.json +2 -1
  165. package/scripts/__tests__/gen-skill-docs.test.mjs +36 -7
  166. package/scripts/__tests__/keyword-detector.test.mjs +77 -28
  167. package/scripts/__tests__/mcp-guard-engine.test.mjs +58 -20
  168. package/scripts/__tests__/remote-spawn-transfer.test.mjs +30 -19
  169. package/scripts/__tests__/remote-spawn.test.mjs +10 -4
  170. package/scripts/__tests__/session-start-fast.test.mjs +36 -0
  171. package/scripts/__tests__/skill-template.test.mjs +98 -50
  172. package/scripts/__tests__/smoke.test.mjs +1 -1
  173. package/scripts/__tests__/spawn-trace.test.mjs +102 -0
  174. package/scripts/__tests__/tfx-doctor-diagnose.test.mjs +48 -0
  175. package/scripts/cache-doctor.mjs +11 -4
  176. package/scripts/cache-warmup.mjs +96 -37
  177. package/scripts/claudemd-sync.mjs +27 -17
  178. package/scripts/codex-gateway-preflight.mjs +52 -37
  179. package/scripts/codex-mcp-gateway-sync.mjs +59 -39
  180. package/scripts/completions/tfx.bash +47 -47
  181. package/scripts/completions/tfx.fish +44 -44
  182. package/scripts/completions/tfx.zsh +83 -83
  183. package/scripts/config-audit.mjs +232 -0
  184. package/scripts/convert-to-tmpl.mjs +54 -0
  185. package/scripts/cross-review-gate.mjs +35 -12
  186. package/scripts/cross-review-tracker.mjs +21 -8
  187. package/scripts/demo.mjs +35 -17
  188. package/scripts/doctor-diagnose.mjs +284 -0
  189. package/scripts/gen-skill-docs.mjs +7 -2
  190. package/scripts/gen-skill-manifest.mjs +2 -1
  191. package/scripts/headless-guard.mjs +86 -48
  192. package/scripts/hub-ensure.mjs +45 -26
  193. package/scripts/keyword-detector.mjs +41 -20
  194. package/scripts/keyword-rules-expander.mjs +47 -30
  195. package/scripts/lib/claudemd-scanner.mjs +6 -1
  196. package/scripts/lib/context.mjs +3 -3
  197. package/scripts/lib/cross-review-utils.mjs +6 -3
  198. package/scripts/lib/env-probe.mjs +47 -28
  199. package/scripts/lib/gemini-profiles.mjs +44 -10
  200. package/scripts/lib/handoff.mjs +33 -17
  201. package/scripts/lib/hook-utils.mjs +8 -6
  202. package/scripts/lib/keyword-rules.mjs +43 -19
  203. package/scripts/lib/logger.mjs +24 -24
  204. package/scripts/lib/mcp-filter.mjs +377 -239
  205. package/scripts/lib/mcp-guard-engine.mjs +194 -79
  206. package/scripts/lib/mcp-manifest.mjs +23 -13
  207. package/scripts/lib/mcp-server-catalog.mjs +300 -63
  208. package/scripts/lib/psmux-info.mjs +11 -6
  209. package/scripts/lib/remote-spawn-transfer.mjs +44 -14
  210. package/scripts/lib/skill-template.mjs +30 -7
  211. package/scripts/mcp-check.mjs +58 -39
  212. package/scripts/mcp-gateway-config.mjs +83 -39
  213. package/scripts/mcp-gateway-ensure.mjs +43 -35
  214. package/scripts/mcp-gateway-integration-test.mjs +70 -58
  215. package/scripts/mcp-gateway-start.mjs +126 -60
  216. package/scripts/mcp-gateway-verify.mjs +24 -22
  217. package/scripts/mcp-safety-guard.mjs +44 -11
  218. package/scripts/notion-read.mjs +199 -84
  219. package/scripts/pack.mjs +94 -89
  220. package/scripts/preflight-cache.mjs +27 -10
  221. package/scripts/preinstall.mjs +42 -13
  222. package/scripts/remote-spawn.mjs +309 -94
  223. package/scripts/run.cjs +8 -5
  224. package/scripts/session-spawn-helper.mjs +130 -39
  225. package/scripts/session-stale-cleanup.mjs +123 -0
  226. package/scripts/setup.mjs +941 -492
  227. package/scripts/test-lock.mjs +20 -7
  228. package/scripts/test-tfx-route-no-claude-native.mjs +16 -12
  229. package/scripts/tfx-batch-stats.mjs +32 -11
  230. package/scripts/tfx-gate-activate.mjs +11 -4
  231. package/scripts/tfx-route-post.mjs +87 -20
  232. package/scripts/tfx-route-worker.mjs +57 -51
  233. package/scripts/tfx-route.sh +41 -124
  234. package/scripts/tmp-cleanup.mjs +21 -7
  235. package/scripts/token-snapshot.mjs +204 -85
  236. package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +1 -0
  237. package/skills/.omc/state/idle-notif-cooldown.json +3 -0
  238. package/skills/.omc/state/last-tool-error.json +7 -0
  239. package/skills/.omc/state/subagent-tracking.json +7 -0
  240. package/skills/_templates/base.md +1 -6
  241. package/skills/merge-worktree/SKILL.md.tmpl +144 -0
  242. package/skills/shared/telemetry-segment.md +6 -0
  243. package/skills/star-prompt/SKILL.md.tmpl +222 -0
  244. package/skills/tfx-analysis/SKILL.md.tmpl +107 -0
  245. package/skills/tfx-analysis/skill.json +1 -6
  246. package/skills/tfx-auto/SKILL.md +1 -0
  247. package/skills/tfx-auto-codex/SKILL.md.tmpl +106 -0
  248. package/skills/tfx-auto-codex/skill.json +1 -3
  249. package/skills/tfx-autopilot/SKILL.md.tmpl +116 -0
  250. package/skills/tfx-autopilot/skill.json +1 -5
  251. package/skills/tfx-autoresearch/SKILL.md.tmpl +136 -0
  252. package/skills/tfx-autoroute/SKILL.md.tmpl +189 -0
  253. package/skills/tfx-autoroute/skill.json +1 -7
  254. package/skills/tfx-codex/SKILL.md +1 -0
  255. package/skills/tfx-codex/skill.json +1 -3
  256. package/skills/tfx-codex-swarm/SKILL.md.tmpl +16 -0
  257. package/skills/tfx-codex-swarm/evals/evals.json +1 -1
  258. package/skills/tfx-codex-swarm/skill.json +1 -4
  259. package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +54 -12
  260. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +35 -7
  261. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +35 -7
  262. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +25 -5
  263. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +25 -5
  264. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +20 -4
  265. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +16 -4
  266. package/skills/tfx-consensus/SKILL.md.tmpl +146 -0
  267. package/skills/tfx-debate/SKILL.md.tmpl +192 -0
  268. package/skills/tfx-debate/skill.json +1 -7
  269. package/skills/tfx-deep-analysis/SKILL.md.tmpl +228 -0
  270. package/skills/tfx-deep-analysis/skill.json +1 -5
  271. package/skills/tfx-deep-interview/SKILL.md.tmpl +203 -0
  272. package/skills/tfx-deep-plan/SKILL.md.tmpl +282 -0
  273. package/skills/tfx-deep-qa/SKILL.md.tmpl +165 -0
  274. package/skills/tfx-deep-qa/skill.json +1 -6
  275. package/skills/tfx-deep-research/SKILL.md.tmpl +217 -0
  276. package/skills/tfx-deep-review/SKILL.md.tmpl +179 -0
  277. package/skills/tfx-doctor/SKILL.md +21 -0
  278. package/skills/tfx-doctor/SKILL.md.tmpl +172 -0
  279. package/skills/tfx-doctor/skill.json +1 -3
  280. package/skills/tfx-find/SKILL.md +1 -0
  281. package/skills/tfx-forge/SKILL.md.tmpl +187 -0
  282. package/skills/tfx-fullcycle/SKILL.md.tmpl +286 -0
  283. package/skills/tfx-fullcycle/skill.json +1 -6
  284. package/skills/tfx-gemini/SKILL.md.tmpl +91 -0
  285. package/skills/tfx-gemini/skill.json +1 -3
  286. package/skills/tfx-hooks/SKILL.md.tmpl +216 -0
  287. package/skills/tfx-hooks/skill.json +1 -3
  288. package/skills/tfx-hub/SKILL.md.tmpl +212 -0
  289. package/skills/tfx-hub/skill.json +1 -3
  290. package/skills/tfx-index/SKILL.md +1 -0
  291. package/skills/tfx-index/skill.json +1 -6
  292. package/skills/tfx-interview/SKILL.md.tmpl +285 -0
  293. package/skills/tfx-multi/SKILL.md.tmpl +183 -0
  294. package/skills/tfx-multi/skill.json +1 -3
  295. package/skills/tfx-panel/SKILL.md.tmpl +189 -0
  296. package/skills/tfx-panel/skill.json +1 -7
  297. package/skills/tfx-persist/SKILL.md.tmpl +270 -0
  298. package/skills/tfx-persist/skill.json +1 -7
  299. package/skills/tfx-plan/SKILL.md +1 -0
  300. package/skills/tfx-plan/skill.json +1 -6
  301. package/skills/tfx-profile/SKILL.md.tmpl +239 -0
  302. package/skills/tfx-profile/skill.json +1 -3
  303. package/skills/tfx-prune/SKILL.md.tmpl +200 -0
  304. package/skills/tfx-prune/skill.json +1 -7
  305. package/skills/tfx-psmux-rules/SKILL.md.tmpl +326 -0
  306. package/skills/tfx-psmux-rules/skill.json +1 -4
  307. package/skills/tfx-qa/SKILL.md +1 -0
  308. package/skills/tfx-qa/skill.json +1 -6
  309. package/skills/tfx-ralph/SKILL.md.tmpl +28 -0
  310. package/skills/tfx-ralph/skill.json +1 -4
  311. package/skills/tfx-remote-setup/SKILL.md.tmpl +576 -0
  312. package/skills/tfx-remote-setup/skill.json +1 -3
  313. package/skills/tfx-remote-spawn/SKILL.md.tmpl +263 -0
  314. package/skills/tfx-remote-spawn/references/hosts.json +16 -0
  315. package/skills/tfx-remote-spawn/skill.json +1 -4
  316. package/skills/tfx-research/SKILL.md +1 -0
  317. package/skills/tfx-review/SKILL.md +1 -0
  318. package/skills/tfx-review/skill.json +1 -6
  319. package/skills/tfx-setup/SKILL.md.tmpl +504 -0
  320. package/skills/tfx-setup/skill.json +1 -3
  321. package/skills/tfx-swarm/SKILL.md +22 -0
  322. package/skills/tfx-swarm/SKILL.md.tmpl +218 -0
  323. package/tui/codex-profile.mjs +88 -33
  324. package/tui/core.mjs +45 -15
  325. package/tui/doctor.mjs +75 -28
  326. package/tui/gemini-profile.mjs +74 -29
  327. package/tui/monitor-data.mjs +8 -4
  328. package/tui/monitor.mjs +71 -27
  329. package/tui/setup.mjs +133 -42
@@ -1,20 +1,33 @@
1
1
  // ============================================================================
2
2
  // Claude Usage API (api.anthropic.com/api/oauth/usage)
3
3
  // ============================================================================
4
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
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
- CLAUDE_CREDENTIALS_PATH, CLAUDE_USAGE_CACHE_PATH, OMC_PLUGIN_USAGE_CACHE_PATH,
11
- CLAUDE_USAGE_STALE_MS_SOLO, CLAUDE_USAGE_STALE_MS_WITH_OMC,
12
- CLAUDE_USAGE_429_BACKOFF_MS, CLAUDE_USAGE_ERROR_BACKOFF_MS,
13
- CLAUDE_API_TIMEOUT_MS, FIVE_HOUR_MS, SEVEN_DAY_MS,
14
- DEFAULT_OAUTH_CLIENT_ID, CLAUDE_REFRESH_FLAG,
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) ? Math.max(0, consecutive429s) : 0;
43
- const next429s = outcome === "rate_limit" ? current429s + 1 : 0;
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) ? Math.min(1, Math.max(0, sample)) : 0.5;
50
- const jitterFactor = 1 + ((normalized * 2) - 1) * jitterRatio;
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) ? Date.now() - timestamp : Number.MAX_SAFE_INTEGER;
86
+ const ageMs = Number.isFinite(timestamp)
87
+ ? Date.now() - timestamp
88
+ : Number.MAX_SAFE_INTEGER;
69
89
  const fallbackMs = cache?.error
70
- ? (cache.errorType === "rate_limit" ? CLAUDE_USAGE_429_BACKOFF_MS : CLAUDE_USAGE_ERROR_BACKOFF_MS)
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 = process.env.CLAUDE_CODE_OAUTH_CLIENT_ID || DEFAULT_OAUTH_CLIENT_ID;
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
- hostname: "platform.claude.com",
101
- path: "/v1/oauth/token",
102
- method: "POST",
103
- headers: {
104
- "Content-Type": "application/x-www-form-urlencoded",
105
- "Content-Length": Buffer.byteLength(body),
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
- timeout: CLAUDE_API_TIMEOUT_MS,
108
- }, (res) => {
109
- let data = "";
110
- res.on("data", (chunk) => { data += chunk; });
111
- res.on("end", () => {
112
- if (res.statusCode === 200) {
113
- try {
114
- const parsed = JSON.parse(data);
115
- if (parsed.access_token) {
116
- resolve({
117
- accessToken: parsed.access_token,
118
- refreshToken: parsed.refresh_token || refreshToken,
119
- expiresAt: parsed.expires_in
120
- ? Date.now() + parsed.expires_in * 1000
121
- : parsed.expires_at,
122
- });
123
- return;
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
- } catch { /* parse 실패 */ }
126
- }
127
- resolve(null);
128
- });
129
- });
155
+ }
156
+ resolve(null);
157
+ });
158
+ },
159
+ );
130
160
  req.on("error", () => resolve(null));
131
- req.on("timeout", () => { req.destroy(); resolve(null); });
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
- hostname: "api.anthropic.com",
152
- path: "/api/oauth/usage",
153
- method: "GET",
154
- headers: {
155
- "Authorization": `Bearer ${accessToken}`,
156
- "anthropic-beta": "oauth-2025-04-20",
157
- "Content-Type": "application/json",
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
- timeout: CLAUDE_API_TIMEOUT_MS,
160
- }, (res) => {
161
- let data = "";
162
- res.on("data", (chunk) => { data += chunk; });
163
- res.on("end", () => {
164
- if (res.statusCode === 200) {
165
- try { resolve({ ok: true, data: JSON.parse(data) }); } catch { resolve({ ok: false, status: 0 }); }
166
- } else {
167
- resolve({ ok: false, status: res.statusCode });
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", () => { req.destroy(); resolve({ ok: false, status: 0, error: "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)) copy.fiveHourResetsAt = new Date(advanceToNextCycle(t, FIVE_HOUR_MS)).toISOString();
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)) copy.weeklyResetsAt = new Date(advanceToNextCycle(t, SEVEN_DAY_MS)).toISOString();
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 = cache?.errorType === "rate_limit"
212
- && Number.isFinite(schedule.nextRefreshAt)
213
- && Date.now() < schedule.nextRefreshAt;
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 (data.fiveHourResetsAt && new Date(data.fiveHourResetsAt).getTime() <= now) {
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 = omcCache?.data?.fiveHourPercent != null ? stripStaleResets(omcCache.data) : null;
248
- return { data: staleData, shouldRefresh: false, isStale: staleBackoffActive };
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) ? Date.now() - omcCache.timestamp : Number.MAX_SAFE_INTEGER;
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 { data: omcCache.data, shouldRefresh: omcAge > getClaudeUsageStaleMs(), isStale: false };
325
+ return {
326
+ data: omcCache.data,
327
+ shouldRefresh: omcAge > getClaudeUsageStaleMs(),
328
+ isStale: false,
329
+ };
260
330
  }
261
331
  // stale이어도 data: null보다는 오래된 데이터를 fallback으로 표시
262
- return { data: stripStaleResets(omcCache.data), shouldRefresh: true, isStale: false };
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(data, errorInfo = null, pollState = null) {
270
- const state = pollState || (errorInfo
271
- ? {
272
- consecutive429s: errorInfo.type === "rate_limit" ? 1 : 0,
273
- baseDelayMs: errorInfo.type === "rate_limit"
274
- ? CLAUDE_USAGE_429_BACKOFF_MS
275
- : CLAUDE_USAGE_ERROR_BACKOFF_MS,
276
- delayMs: errorInfo.type === "rate_limit"
277
- ? CLAUDE_USAGE_429_BACKOFF_MS
278
- : CLAUDE_USAGE_ERROR_BACKOFF_MS,
279
- }
280
- : computeClaudeUsagePollState({ outcome: "success" }));
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, // "rate_limit" | "auth" | "network" | "unknown"
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) ? cache.consecutive429s : 0;
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 = result.status === 429 ? "rate_limit"
331
- : result.status === 401 || result.status === 403 ? "auth"
332
- : result.error === "timeout" || result.error === "network" ? "network"
333
- : "unknown";
334
- const pollState = errorType === "rate_limit"
335
- ? computeClaudeUsagePollState({ consecutive429s, outcome: "rate_limit" })
336
- : null;
337
- writeClaudeUsageCache(existingSnapshot.data, { type: errorType, status: result.status }, pollState);
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 ? computeClaudeUsagePollState({ outcome: "success" }) : null;
342
- writeClaudeUsageCache(usage, usage ? null : { type: "unknown", status: 0 }, pollState);
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) ? Date.now() - omcCache.timestamp : Infinity;
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(lockPath)) {
366
- const lockAge = Date.now() - readJson(lockPath, {}).t;
367
- if (lockAge < 1000) return; // 짧은 중복 스폰만 억제, 실제 폴링 주기는 nextRefreshAt가 제어
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(lockPath, { t: Date.now() });
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, { type: "network", status: 0, hint: String(spawnErr?.message || spawnErr) });
488
+ writeClaudeUsageCache(null, {
489
+ type: "network",
490
+ status: 0,
491
+ hint: String(spawnErr?.message || spawnErr),
492
+ });
382
493
  }
383
494
  }
384
495
 
@@ -1,16 +1,21 @@
1
1
  // ============================================================================
2
2
  // Codex rate limits 추출 / 캐싱
3
3
  // ============================================================================
4
- import { existsSync, readFileSync, readdirSync } from "node:fs";
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, CODEX_QUOTA_CACHE_PATH, CODEX_QUOTA_STALE_MS,
10
- CODEX_MIN_BUCKETS, CODEX_REFRESH_FLAG,
11
- CODEX_REFRESH_LOCK_PATH, SPAWN_LOCK_TTL_MS,
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, decodeJwtEmail } from "../utils.mjs";
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 { return null; }
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) { result[key] = bucket; continue; }
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 = { ...updated, primary: { ...updated.primary, used_percent: 0 } };
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 = { ...updated, secondary: { ...updated.secondary, used_percent: 0 } };
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(), ".codex", "sessions",
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 { files = readdirSync(sessDir).filter((f) => f.endsWith(".jsonl")).sort().reverse(); }
84
- catch { continue; }
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, limitName: rl.limit_name,
100
- primary, secondary,
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 (dayOffset <= 1 && !rl && evt?.payload?.info?.total_token_usage && !syntheticBucket) {
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", limitName: "codex-session",
110
- primary: null, secondary: null,
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 = mergedBuckets.codex || mergedBuckets[Object.keys(mergedBuckets)[0]];
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], {