tokentracker-cli 1.5.1 → 1.5.3
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/dashboard/dist/assets/{AchievementBadge-mB7HZSGE.js → AchievementBadge-uuDGO9Mq.js} +1 -1
- package/dashboard/dist/assets/{AchievementsPage-DLzeq_eo.js → AchievementsPage-BCTVT7Jo.js} +2 -2
- package/dashboard/dist/assets/{AchievementsSection-DrXQyDns.js → AchievementsSection-B6fJSJSr.js} +1 -1
- package/dashboard/dist/assets/{BadgeDetailModal-WrgKRybi.js → BadgeDetailModal-FjUN__9G.js} +1 -1
- package/dashboard/dist/assets/{BotAnimated-DFRaixVK.js → BotAnimated-B-KqY_Wr.js} +1 -1
- package/dashboard/dist/assets/{Card-bFq2tlO8.js → Card-DS8T8piP.js} +1 -1
- package/dashboard/dist/assets/{CloudCheckoutPage-gwhGxavy.js → CloudCheckoutPage-_0UjsFPu.js} +1 -1
- package/dashboard/dist/assets/{CloudPage-BD4iBYeZ.js → CloudPage-BstgVBaV.js} +2 -2
- package/dashboard/dist/assets/{CommandPalette-C_KlPl0U.js → CommandPalette-DtVsYZgc.js} +1 -1
- package/dashboard/dist/assets/{CommunityStatsModal-Cl1Bgv8M.js → CommunityStatsModal-DTpEJqV3.js} +1 -1
- package/dashboard/dist/assets/{ConfirmModal-NWoYevYC.js → ConfirmModal-Lfdh2vr7.js} +1 -1
- package/dashboard/dist/assets/{Controls-D2-D2cfA.js → Controls-DpwQ7pkq.js} +1 -1
- package/dashboard/dist/assets/DashboardPage-DUFWejkE.js +1 -0
- package/dashboard/dist/assets/{DateRangePopover-8EMVXuH6.js → DateRangePopover-CrWgrzT2.js} +1 -1
- package/dashboard/dist/assets/{DevicePage-DbP8d6bM.js → DevicePage-DbK5KWJS.js} +1 -1
- package/dashboard/dist/assets/{DialogClose-CEstgLa1.js → DialogClose-Dd2B_o03.js} +1 -1
- package/dashboard/dist/assets/DialogDescription-CLy0arbt.js +1 -0
- package/dashboard/dist/assets/{DialogTitle-DP5TLLbz.js → DialogTitle-3JQBVha3.js} +1 -1
- package/dashboard/dist/assets/{ElectricLogo-Uvi3yMum.js → ElectricLogo-LYAqG8pG.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-Dpzihg6f.js → FadeIn-DhDvUor7.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-DllqemKr.js → HeaderGithubStar-9f1XmgYC.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-Dc-TAH6l.js → HoverTooltip-BXhHAkP7.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-jd1D5ZKX.js → IpCheckPage-ChPdDeMl.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-CB1ZVw-a.js → LandingPage-DnJ7Y9zw.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardPage-Bt2cRRmE.js → LeaderboardPage-BF9isLzu.js} +2 -2
- package/dashboard/dist/assets/{LeaderboardProBadge-BVlyVOFf.js → LeaderboardProBadge-BM4JKgkC.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfileModal-CwQFRJg_.js → LeaderboardProfileModal-DfZABOpN.js} +3 -3
- package/dashboard/dist/assets/{LeaderboardProfilePage-CeqcTbUG.js → LeaderboardProfilePage-BaZ38iT5.js} +1 -1
- package/dashboard/dist/assets/LimitsPage-C7LbG3LX.js +6 -0
- package/dashboard/dist/assets/{LocalOnlyNotice-B2_HoCVD.js → LocalOnlyNotice-DvhlYoqQ.js} +1 -1
- package/dashboard/dist/assets/{LoginCard-nw9JOc2d.js → LoginCard-g_Gbnzhs.js} +1 -1
- package/dashboard/dist/assets/{LoginModal-BHNLTK1D.js → LoginModal-DkJrI6b7.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-LI2GZKhM.js → LoginPage-CqCKSo0n.js} +1 -1
- package/dashboard/dist/assets/{PetPage-CK6sPeI0.js → PetPage-DbF7ROsj.js} +2 -2
- package/dashboard/dist/assets/{PopoverPopup-BsXchEVa.js → PopoverPopup-4WuH41ZT.js} +1 -1
- package/dashboard/dist/assets/{ProviderIcon-vgCEy0dh.js → ProviderIcon-6j0A2KD9.js} +1 -1
- package/dashboard/dist/assets/{ResetPasswordPage-B4CPj2vX.js → ResetPasswordPage-DBd-iwuM.js} +1 -1
- package/dashboard/dist/assets/{Select-BkAy6P2r.js → Select-BBEpLeiG.js} +1 -1
- package/dashboard/dist/assets/{SelectItemText-C0RNqDrD.js → SelectItemText-Cn_U_rrO.js} +1 -1
- package/dashboard/dist/assets/{SelfHostPage-B-_w-Z-a.js → SelfHostPage-2WZc6Rml.js} +1 -1
- package/dashboard/dist/assets/ServiceStatusPage-B-g4MDl0.js +1 -0
- package/dashboard/dist/assets/{SessionsPage-CmWXBqD2.js → SessionsPage-CWS0iQ1L.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-CA70zP-t.js → SettingsPage-rXNr96i-.js} +3 -3
- package/dashboard/dist/assets/{SkillsPage-CNamhfzU.js → SkillsPage-CudTvbZL.js} +1 -1
- package/dashboard/dist/assets/{TokenGalaxy-CXrVBSk6.js → TokenGalaxy-BRKrbTsJ.js} +1 -1
- package/dashboard/dist/assets/{TooltipPopup-CR4MqElX.js → TooltipPopup-CsLKXhWC.js} +1 -1
- package/dashboard/dist/assets/{TrendMonitor-DQfcrEve.js → TrendMonitor-8onUTbkk.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-BBXxtZbP.js → WidgetsPage-CcwE0lz8.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-C8i2KlMF.js → WrappedPage-Cj9LLYqP.js} +1 -1
- package/dashboard/dist/assets/__vite-browser-external-07djSGmy.js +45 -0
- package/dashboard/dist/assets/__vite-browser-external-BDldtsD2.css +1 -0
- package/dashboard/dist/assets/{agent-logos-CxQb8vir.js → agent-logos-ChBA1nAH.js} +1 -1
- package/dashboard/dist/assets/{arrow-left-D6zkcLVt.js → arrow-left-Bb5mfoPc.js} +1 -1
- package/dashboard/dist/assets/{arrow-right-BGb-XVn_.js → arrow-right-D1Ti2XUI.js} +1 -1
- package/dashboard/dist/assets/{arrow-up-right-XTStagDJ.js → arrow-up-right-B_CjA_y8.js} +1 -1
- package/dashboard/dist/assets/{calendar-days-Cz3NGZiS.js → calendar-days-DdfrAqie.js} +1 -1
- package/dashboard/dist/assets/{check-1z_vmHPD.js → check-29zGw1MK.js} +1 -1
- package/dashboard/dist/assets/{chevron-down-CkSBY3bE.js → chevron-down-DAzlFX7g.js} +1 -1
- package/dashboard/dist/assets/{cloud-checkout-pD5C4r_S.js → cloud-checkout-C2SeCjFM.js} +1 -1
- package/dashboard/dist/assets/{composite-BtDzx3a2.js → composite-DIf735Hq.js} +1 -1
- package/dashboard/dist/assets/{copy-Ctv_wxuD.js → copy-BOuOM0HQ.js} +1 -1
- package/dashboard/dist/assets/{download-DKbKMGSR.js → download-50tQRpGe.js} +1 -1
- package/dashboard/dist/assets/{external-link-g9cU017t.js → external-link-BhlQClVe.js} +1 -1
- package/dashboard/dist/assets/{flame-DZq3kXUt.js → flame-CeGfA-0L.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-CXQfx0PC.js → format-tokens-Cq7YiAkJ.js} +1 -1
- package/dashboard/dist/assets/{gift-CKlKjGP6.js → gift-Do-UFhiT.js} +1 -1
- package/dashboard/dist/assets/{icons-xPGMnBjl.js → icons-C6EZ_HmP.js} +1 -1
- package/dashboard/dist/assets/{index-fVbP5wOv.js → index-BFrbVqsm.js} +1 -1
- package/dashboard/dist/assets/{index-CDBOKcDh.js → index-BWhbBAd9.js} +1 -1
- package/dashboard/dist/assets/{index-Dl0oFLNr.js → index-D9kwYWLX.js} +1 -1
- package/dashboard/dist/assets/{index-BwOjMtB2.js → index-c0crpe9Z.js} +1 -1
- package/dashboard/dist/assets/{info-D9xNIuGh.js → info-B4MXmsNE.js} +1 -1
- package/dashboard/dist/assets/{link-2-Bk5wYt2n.js → link-2-DuWCslzl.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-CcN3pHWb.js → loader-circle-C2I3NZAO.js} +1 -1
- package/dashboard/dist/assets/{local-usage-transfer-CYcy1ici.js → local-usage-transfer-BQasvsUF.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-DbpEzkJe.js → modifiers.esm-DbeNS6FT.js} +1 -1
- package/dashboard/dist/assets/{monitor-smartphone-WkWv1qIS.js → monitor-smartphone-C6Y4ktsc.js} +1 -1
- package/dashboard/dist/assets/{play-2yZGWMes.js → play-CwgcDJC2.js} +1 -1
- package/dashboard/dist/assets/{plus-mOF8tG1F.js → plus-B796wsUO.js} +1 -1
- package/dashboard/dist/assets/{provider-display-7gaOvgMp.js → provider-display-Cw26RFDm.js} +1 -1
- package/dashboard/dist/assets/{react-CdPiPDhk.js → react-d7Bk1-_K.js} +1 -1
- package/dashboard/dist/assets/{safePolygon-C7FJLAlh.js → safePolygon-CDE0dNv_.js} +1 -1
- package/dashboard/dist/assets/{search-C63nCrjo.js → search-DtKvsdRs.js} +1 -1
- package/dashboard/dist/assets/{server-BaG1rnuv.js → server-gAURHUyz.js} +1 -1
- package/dashboard/dist/assets/{shield-check-D_4-Cm6L.js → shield-check-DFVdDTwh.js} +1 -1
- package/dashboard/dist/assets/{skills-api-DfbzwpDv.js → skills-api-2rdPUrJI.js} +1 -1
- package/dashboard/dist/assets/{sortable.esm-D-v8FRX5.js → sortable.esm-DFlfwnJr.js} +1 -1
- package/dashboard/dist/assets/{store-Dv_FYrXK.js → store-BKPCk_de.js} +1 -1
- package/dashboard/dist/assets/{terminal-FQi6MaYt.js → terminal-BtWFakvS.js} +1 -1
- package/dashboard/dist/assets/{trash-TbK96dEz.js → trash-bpr9zTAM.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-DGoWVZuk.js → use-community-stats-DMnGgLne.js} +1 -1
- package/dashboard/dist/assets/{use-latest-request-guard-Dg6uSaYt.js → use-latest-request-guard-BSBlJJHe.js} +1 -1
- package/dashboard/dist/assets/use-limits-display-prefs-eYlvnUKn.js +1 -0
- package/dashboard/dist/assets/{use-native-settings-DIk0AF3b.js → use-native-settings-OthqiF6g.js} +1 -1
- package/dashboard/dist/assets/{use-ordered-list-CUqV3Zgl.js → use-ordered-list-DpAtr0R-.js} +1 -1
- package/dashboard/dist/assets/use-reduced-motion-DF0grwHs.js +1 -0
- package/dashboard/dist/assets/{use-transform-D_-w22c8.js → use-transform-BRz9dz_2.js} +1 -1
- package/dashboard/dist/assets/use-usage-limits-CwmhudAX.js +1 -0
- package/dashboard/dist/assets/{useCurrency-B_AuLCsL.js → useCurrency-CAA-tQ27.js} +1 -1
- package/dashboard/dist/assets/{usePositioner-DfxvIif8.js → usePositioner-Cj2DPmwM.js} +1 -1
- package/dashboard/dist/assets/{useTokenFormat-bbO13V4s.js → useTokenFormat-Bf5-f7Hx.js} +1 -1
- package/dashboard/dist/assets/{zap-k07-EXTK.js → zap-B8Fg6PaC.js} +1 -1
- package/dashboard/dist/index.html +6 -2
- package/dashboard/dist/ip-check.html +2 -2
- package/dashboard/dist/leaderboard.html +2 -2
- package/dashboard/dist/share.html +2 -2
- package/package.json +1 -1
- package/src/commands/serve.js +22 -13
- package/src/lib/codex-rollout-parser.js +43 -6
- package/src/lib/codex-usage-record.js +21 -0
- package/src/lib/deepseek-status.js +157 -0
- package/src/lib/local-api.js +44 -4
- package/src/lib/pricing/curated-overrides.json +94 -0
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/provider-status.js +15 -2
- package/src/lib/qoder-limits.js +95 -23
- package/src/lib/rollout.js +68 -14
- package/src/lib/session-analytics.js +10 -3
- package/src/lib/usage-limits.js +187 -43
- package/src/lib/workbuddy-limits.js +292 -27
- package/src/lib/zcode-limits.js +457 -42
- package/dashboard/dist/assets/DashboardPage-vITMnPXx.js +0 -1
- package/dashboard/dist/assets/DialogDescription-0wcygxtH.js +0 -1
- package/dashboard/dist/assets/LimitsPage-Bp4ZORmR.js +0 -6
- package/dashboard/dist/assets/ServiceStatusPage-5YevsGGD.js +0 -1
- package/dashboard/dist/assets/__vite-browser-external-BEEzY7mq.js +0 -45
- package/dashboard/dist/assets/__vite-browser-external-Br-KAXh6.css +0 -1
- package/dashboard/dist/assets/use-limits-display-prefs-Djbdp7fi.js +0 -1
- package/dashboard/dist/assets/use-reduced-motion-DMCok-vZ.js +0 -1
- package/dashboard/dist/assets/use-usage-limits-h7aGsIQr.js +0 -1
package/src/lib/usage-limits.js
CHANGED
|
@@ -35,7 +35,7 @@ const { fetchDevinLimits } = require("./devin-limits");
|
|
|
35
35
|
const { fetchQoderLimits, fetchQoderCnLimits } = require("./qoder-limits");
|
|
36
36
|
const { fetchArkCodingPlanLimits } = require("./ark-coding-plan-limits");
|
|
37
37
|
const { fetchArkAgentPlanLimits } = require("./ark-agent-plan-limits");
|
|
38
|
-
const { fetchWorkbuddyLimits, fetchWorkbuddyAiLimits
|
|
38
|
+
const { fetchWorkbuddyLimits, fetchWorkbuddyAiLimits } = require("./workbuddy-limits");
|
|
39
39
|
const { fetchProviderServiceStatus } = require("./provider-status");
|
|
40
40
|
const { resolveKimiProfile } = require("./kimi-profile");
|
|
41
41
|
const { readSqliteJsonRows, readSqliteJsonRowsAsync } = require("./sqlite-reader");
|
|
@@ -50,21 +50,54 @@ const execFileAsync = promisify(cp.execFile);
|
|
|
50
50
|
// 2-minute in-memory cache. It also expires early at the earliest upcoming window
|
|
51
51
|
// reset in the cached data (see cacheExpiresAtMs), floored so a provider reporting
|
|
52
52
|
// a reset "right now" can't turn every poll into a full upstream round.
|
|
53
|
-
// Partitioned by the Devin opt-in selection AND the Antigravity quota opt-out
|
|
54
|
-
// a request made while Devin is off
|
|
55
|
-
//
|
|
56
|
-
// is never served after the opt-out is set
|
|
57
|
-
|
|
53
|
+
// Partitioned by the Devin opt-in selection AND the Antigravity quota opt-out AND
|
|
54
|
+
// the per-request hidden-provider set (#956), so a request made while Devin is off
|
|
55
|
+
// is never served (or joined onto) a response fetched while it was on, an aggregate
|
|
56
|
+
// fetched while Antigravity was enabled is never served after the opt-out is set,
|
|
57
|
+
// and a response fetched for a visibility selection is never served to one made
|
|
58
|
+
// under a different one. Key = `${devin}-${antigravity}[-<hidden ids>]`.
|
|
59
|
+
const USAGE_LIMITS_BASE_SELECTION_KEYS = ["off-on", "off-off", "on-on", "on-off"];
|
|
58
60
|
const cacheBySelection = {};
|
|
59
|
-
for (const key of
|
|
61
|
+
for (const key of USAGE_LIMITS_BASE_SELECTION_KEYS) cacheBySelection[key] = { data: null, expiresAtMs: 0 };
|
|
60
62
|
// Credential replacements (provider-key save) bump this generation so a fetch
|
|
61
63
|
// that started under the old credential cannot repopulate the memory or disk
|
|
62
64
|
// caches after invalidation.
|
|
63
65
|
let usageLimitsCacheGeneration = 0;
|
|
66
|
+
// Provider row keys of the usage-limits aggregate — the id space `hidden`
|
|
67
|
+
// parameter values are validated against (dashboard rows, menu-bar
|
|
68
|
+
// LimitsSettingsStore.allProviders, and this payload all share it).
|
|
69
|
+
const USAGE_LIMITS_PROVIDER_IDS = Object.freeze([
|
|
70
|
+
"claude", "codex", "cursor", "gemini", "kimi", "kiro", "grok",
|
|
71
|
+
"copilot", "antigravity", "zcode", "opencodeGo", "commandCode",
|
|
72
|
+
"qoder", "qoderCn", "codingPlan", "agentPlan", "devin",
|
|
73
|
+
"workbuddy", "workbuddyAi",
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
// Normalize a caller-supplied hidden-provider list (array, Set, or
|
|
77
|
+
// comma-separated query value) into a sorted, de-duplicated id array, or null
|
|
78
|
+
// when nothing is hidden. Unknown ids are dropped: the parameter only ever
|
|
79
|
+
// narrows the fetch, so a silently ignored typo must not widen the cache-key
|
|
80
|
+
// space (and leniency matches the devin query param, which accepts any value
|
|
81
|
+
// that is not "1"/"true" as off).
|
|
82
|
+
function normalizeHiddenProviders(input) {
|
|
83
|
+
const raw = input instanceof Set
|
|
84
|
+
? [...input]
|
|
85
|
+
: Array.isArray(input)
|
|
86
|
+
? input
|
|
87
|
+
: typeof input === "string" && input.trim().length > 0
|
|
88
|
+
? input.split(",")
|
|
89
|
+
: [];
|
|
90
|
+
const ids = [...new Set(
|
|
91
|
+
raw.map((id) => String(id).trim()).filter((id) => USAGE_LIMITS_PROVIDER_IDS.includes(id)),
|
|
92
|
+
)].sort();
|
|
93
|
+
return ids.length > 0 ? ids : null;
|
|
94
|
+
}
|
|
95
|
+
|
|
64
96
|
function usageLimitsSelectionKey(options) {
|
|
65
97
|
const devin = options?.devinEnabled === true ? "on" : "off";
|
|
66
98
|
const antigravity = isAntigravityQuotaDisabled() ? "off" : "on";
|
|
67
|
-
|
|
99
|
+
const hidden = normalizeHiddenProviders(options?.hiddenProviders);
|
|
100
|
+
return `${devin}-${antigravity}${hidden ? `-${hidden.join("+")}` : ""}`;
|
|
68
101
|
}
|
|
69
102
|
const CACHE_TTL_MS = 2 * 60 * 1000;
|
|
70
103
|
// Must stay below the macOS app's post-reset re-fetch grace (10s in
|
|
@@ -237,6 +270,43 @@ function withAbortableProviderTimeout(start, label, timeoutMs) {
|
|
|
237
270
|
.finally(() => controller.abort());
|
|
238
271
|
}
|
|
239
272
|
|
|
273
|
+
// WorkBuddy bounds its own live chain (the IPC proxy timeout is internal), and
|
|
274
|
+
// its disk-cache fallback runs after the abort lands: the abort makes the
|
|
275
|
+
// socket fail within milliseconds and the cache read is synchronous. A plain
|
|
276
|
+
// provider-timeout race would settle on the timeout first and drop that
|
|
277
|
+
// fallback, so after the budget expires, abort and give the attempt a short
|
|
278
|
+
// grace to land its fallback — its outcome (cache hit or specific error)
|
|
279
|
+
// replaces the generic timeout. The hard bound is timeoutMs + graceMs.
|
|
280
|
+
function withWorkbuddyProviderTimeout(start, label, timeoutMs, graceMs = 2_000) {
|
|
281
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return start(undefined);
|
|
282
|
+
const controller = new AbortController();
|
|
283
|
+
const work = start(controller.signal);
|
|
284
|
+
let timeoutTimer;
|
|
285
|
+
let graceTimer;
|
|
286
|
+
let deadlineFired = false;
|
|
287
|
+
// Once the budget aborts the attempt, any rejection it produces is the
|
|
288
|
+
// deadline's doing — the IPC socket fails with its cancellation error, a
|
|
289
|
+
// fetch with an AbortError — so report the labeled timeout instead of the
|
|
290
|
+
// symptom. A post-abort resolution still wins the grace race (the
|
|
291
|
+
// disk-cache fallback).
|
|
292
|
+
const guardedWork = work.catch((error) => {
|
|
293
|
+
if (deadlineFired) throw new Error(`${label} usage request timed out.`);
|
|
294
|
+
throw error;
|
|
295
|
+
});
|
|
296
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
297
|
+
timeoutTimer = setTimeout(() => {
|
|
298
|
+
deadlineFired = true;
|
|
299
|
+
controller.abort();
|
|
300
|
+
graceTimer = setTimeout(() => reject(new Error(`${label} usage request timed out.`)), graceMs);
|
|
301
|
+
}, timeoutMs);
|
|
302
|
+
});
|
|
303
|
+
return Promise.race([guardedWork, timeoutPromise]).finally(() => {
|
|
304
|
+
clearTimeout(timeoutTimer);
|
|
305
|
+
clearTimeout(graceTimer);
|
|
306
|
+
controller.abort();
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
240
310
|
function parseRetryAfterSeconds(headers) {
|
|
241
311
|
const ra = headers?.get ? headers.get("retry-after") : null;
|
|
242
312
|
const sec = ra ? Number.parseInt(ra, 10) : NaN;
|
|
@@ -3938,7 +4008,7 @@ function normalizePlanLabel(raw, brand) {
|
|
|
3938
4008
|
let s = String(raw).replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
3939
4009
|
if (!s) return null;
|
|
3940
4010
|
const lower = s.toLowerCase();
|
|
3941
|
-
if (["free", "none", "unknown"].includes(lower)) return null;
|
|
4011
|
+
if (["free", "none", "unknown", "invalid"].includes(lower)) return null;
|
|
3942
4012
|
if (brand && lower === brand.toLowerCase()) return null; // e.g. Kiro defaults plan_name to "Kiro" when parse fails
|
|
3943
4013
|
if (brand) {
|
|
3944
4014
|
s = s.replace(new RegExp("^" + brand + "\\s+", "i"), "").trim();
|
|
@@ -3959,7 +4029,6 @@ function withPlanLabel(obj, raw, brand) {
|
|
|
3959
4029
|
// local-api.js): a refresh arriving while a fetch is already running reuses that
|
|
3960
4030
|
// in-flight fetch and returns its result.
|
|
3961
4031
|
const inFlightBySelection = {};
|
|
3962
|
-
for (const key of USAGE_LIMITS_SELECTION_KEYS) inFlightBySelection[key] = null;
|
|
3963
4032
|
|
|
3964
4033
|
// Codex stamps reset_at as unix seconds; every other provider (and Claude's
|
|
3965
4034
|
// resets_at) uses ISO strings. Numbers that look like epoch milliseconds are
|
|
@@ -4001,7 +4070,9 @@ async function getUsageLimits(options = {}) {
|
|
|
4001
4070
|
const selection = usageLimitsSelectionKey(options);
|
|
4002
4071
|
const cache = cacheBySelection[selection];
|
|
4003
4072
|
const nowMs = Date.now();
|
|
4004
|
-
|
|
4073
|
+
// Unseen selections (a hidden set never requested before this process's
|
|
4074
|
+
// lifetime) simply miss — partitions are created lazily on first fetch.
|
|
4075
|
+
if (cache?.data && nowMs < cache.expiresAtMs) {
|
|
4005
4076
|
return cache.data;
|
|
4006
4077
|
}
|
|
4007
4078
|
if (inFlightBySelection[selection]) {
|
|
@@ -4037,6 +4108,11 @@ async function fetchUsageLimitsUncached({
|
|
|
4037
4108
|
providerTimeoutMs = DEFAULT_PROVIDER_TIMEOUT_MS,
|
|
4038
4109
|
forceRefresh = false,
|
|
4039
4110
|
devinEnabled = false,
|
|
4111
|
+
// Provider visibility gate (#956): ids to skip entirely — no upstream call,
|
|
4112
|
+
// no credential read, no subprocess, no cache read/write. Null/empty means
|
|
4113
|
+
// "fetch every configured provider" (the legacy behavior clients that don't
|
|
4114
|
+
// send the parameter keep getting).
|
|
4115
|
+
hiddenProviders = null,
|
|
4040
4116
|
// Generation captured by getUsageLimits before the fetch began. A provider
|
|
4041
4117
|
// key save bumps it, and this fetch must then stop at returning its own
|
|
4042
4118
|
// result: publishing it would restore pre-save data for every later caller.
|
|
@@ -4045,13 +4121,30 @@ async function fetchUsageLimitsUncached({
|
|
|
4045
4121
|
// here would let an opt-out flipped mid-fetch store a pre-opt-out result in the
|
|
4046
4122
|
// post-opt-out slot.
|
|
4047
4123
|
selectionKey,
|
|
4124
|
+
// Injectable clock for tests. Production leaves it unset so per-provider
|
|
4125
|
+
// cache gates sample the time when they actually run (the live attempt can
|
|
4126
|
+
// spend the whole budget before its fallback reads the cache) instead of
|
|
4127
|
+
// inheriting this fetch's start instant.
|
|
4128
|
+
nowMs,
|
|
4048
4129
|
} = {}) {
|
|
4049
|
-
const
|
|
4050
|
-
|
|
4130
|
+
const injectedNowMs = Number.isFinite(nowMs) ? nowMs : undefined;
|
|
4131
|
+
nowMs = injectedNowMs ?? Date.now();
|
|
4132
|
+
const hiddenSet = new Set(normalizeHiddenProviders(hiddenProviders) || []);
|
|
4133
|
+
|
|
4134
|
+
// A hidden provider must not have its local credentials read at all (#956):
|
|
4135
|
+
// these upfront token/subscription probes are the first file and keychain
|
|
4136
|
+
// touches of the Claude/Codex chains, so they are gated here and every
|
|
4137
|
+
// downstream step keys off the resulting null token.
|
|
4051
4138
|
const [claudeOauth, claudeSubscription, codexAuth] = await Promise.all([
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4139
|
+
hiddenSet.has("claude")
|
|
4140
|
+
? Promise.resolve(null)
|
|
4141
|
+
: Promise.resolve().then(() => readClaudeCodeOauthToken({ platform, securityRunner, home, nowMs })),
|
|
4142
|
+
hiddenSet.has("claude")
|
|
4143
|
+
? Promise.resolve(null)
|
|
4144
|
+
: Promise.resolve().then(() => detectClaudeCodeSubscriptionDetails({ platform, securityRunner, home })),
|
|
4145
|
+
hiddenSet.has("codex")
|
|
4146
|
+
? Promise.resolve(null)
|
|
4147
|
+
: readCodexAuthBundle({ home, env }),
|
|
4055
4148
|
]);
|
|
4056
4149
|
const claudeToken = claudeOauth?.accessToken || null;
|
|
4057
4150
|
const claudeTokenExpiresAtMs = claudeOauth?.expiresAtMs ?? null;
|
|
@@ -4107,6 +4200,15 @@ async function fetchUsageLimitsUncached({
|
|
|
4107
4200
|
: null;
|
|
4108
4201
|
|
|
4109
4202
|
const providerFetch = withFetchTimeout(fetchImpl, providerTimeoutMs);
|
|
4203
|
+
// What a hidden provider's Promise.all slot resolves to. Both shapes flow
|
|
4204
|
+
// through the same assembly branches as a genuinely unconfigured provider —
|
|
4205
|
+
// the CommandCode/Devin fulfilled branches keep the value verbatim, the
|
|
4206
|
+
// OpenCode Go branch passes `configured === false` through, Claude/Codex
|
|
4207
|
+
// naturally produce `{ configured: false }` from their gated null tokens —
|
|
4208
|
+
// so no hidden error, cache read, or credential touch can reach the payload
|
|
4209
|
+
// (#956).
|
|
4210
|
+
const hiddenSentinel = Promise.resolve({ configured: false });
|
|
4211
|
+
const hiddenSettled = Promise.resolve({ status: "fulfilled", value: { configured: false } });
|
|
4110
4212
|
const [claudeResult, codexResult, cursor, kimi, gemini, kiro, antigravity, copilot, grok, zcode, opencodeGoRaw, qoder, qoderCn, codingPlan, agentPlan, commandCodeRaw, devinRaw, workbuddy, workbuddyAi, claudeServiceStatus] = await Promise.all([
|
|
4111
4213
|
claudeToken && !freshClaudeCache && !claudeRetryAtMs
|
|
4112
4214
|
? withProviderTimeout(fetchClaudeUsageLimits(claudeToken, { fetchImpl: providerFetch, maxAttempts: 1 }), "Claude", providerTimeoutMs).then(
|
|
@@ -4124,17 +4226,17 @@ async function fetchUsageLimitsUncached({
|
|
|
4124
4226
|
(reason) => ({ status: "rejected", reason }),
|
|
4125
4227
|
)
|
|
4126
4228
|
: Promise.resolve(null),
|
|
4127
|
-
withProviderTimeout(fetchCursorLimits({ home, fetchImpl: providerFetch }), "Cursor", providerTimeoutMs)
|
|
4229
|
+
hiddenSet.has("cursor") ? hiddenSentinel : withProviderTimeout(fetchCursorLimits({ home, fetchImpl: providerFetch }), "Cursor", providerTimeoutMs)
|
|
4128
4230
|
.catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
4129
|
-
withProviderTimeout(fetchKimiLimits({ home, env, fetchImpl: providerFetch }), "Kimi", providerTimeoutMs)
|
|
4231
|
+
hiddenSet.has("kimi") ? hiddenSentinel : withProviderTimeout(fetchKimiLimits({ home, env, fetchImpl: providerFetch }), "Kimi", providerTimeoutMs)
|
|
4130
4232
|
.catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
4131
|
-
withProviderTimeout(fetchGeminiLimits({ home, env, fetchImpl: providerFetch, commandRunner }), "Gemini", providerTimeoutMs)
|
|
4233
|
+
hiddenSet.has("gemini") ? hiddenSentinel : withProviderTimeout(fetchGeminiLimits({ home, env, fetchImpl: providerFetch, commandRunner }), "Gemini", providerTimeoutMs)
|
|
4132
4234
|
.catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
4133
|
-
fetchKiroLimits({ commandRunner, now, platform, home }),
|
|
4235
|
+
hiddenSet.has("kiro") ? hiddenSentinel : fetchKiroLimits({ commandRunner, now, platform, home }),
|
|
4134
4236
|
// Antigravity's own budget keeps the serial chain inside providerTimeoutMs; this
|
|
4135
4237
|
// outer race is the enforcing backstop every other provider already has, and the
|
|
4136
4238
|
// signal makes a fired race actually kill the spawned scans and open sockets.
|
|
4137
|
-
withAbortableProviderTimeout(
|
|
4239
|
+
hiddenSet.has("antigravity") ? hiddenSentinel : withAbortableProviderTimeout(
|
|
4138
4240
|
(signal) => fetchAntigravityLimits({
|
|
4139
4241
|
home,
|
|
4140
4242
|
commandRunner,
|
|
@@ -4150,18 +4252,18 @@ async function fetchUsageLimitsUncached({
|
|
|
4150
4252
|
"Antigravity",
|
|
4151
4253
|
providerTimeoutMs,
|
|
4152
4254
|
).catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
4153
|
-
withProviderTimeout(fetchCopilotLimits({ home, env, fetchImpl: providerFetch, platform, securityRunner }), "GitHub Copilot", providerTimeoutMs)
|
|
4255
|
+
hiddenSet.has("copilot") ? hiddenSentinel : withProviderTimeout(fetchCopilotLimits({ home, env, fetchImpl: providerFetch, platform, securityRunner }), "GitHub Copilot", providerTimeoutMs)
|
|
4154
4256
|
.catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
4155
|
-
withProviderTimeout(fetchGrokLimits({ home, env, fetchImpl: providerFetch, timeoutMs: providerTimeoutMs }), "Grok Build", providerTimeoutMs)
|
|
4257
|
+
hiddenSet.has("grok") ? hiddenSentinel : withProviderTimeout(fetchGrokLimits({ home, env, fetchImpl: providerFetch, timeoutMs: providerTimeoutMs }), "Grok Build", providerTimeoutMs)
|
|
4156
4258
|
.catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
4157
|
-
withProviderTimeout(fetchZcodeLimits({ home, env, fetchImpl: providerFetch, providerTimeoutMs }), "ZCode", providerTimeoutMs)
|
|
4259
|
+
hiddenSet.has("zcode") ? hiddenSentinel : withProviderTimeout(fetchZcodeLimits({ home, env, fetchImpl: providerFetch, providerTimeoutMs }), "ZCode", providerTimeoutMs)
|
|
4158
4260
|
.catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
4159
4261
|
// OpenCode Go: authoritative subscription windows come from the dashboard
|
|
4160
4262
|
// scrape; local opencode.db cost is available only as an explicit estimate.
|
|
4161
4263
|
// See src/lib/opencode-go-limits.js.
|
|
4162
|
-
withProviderTimeout(fetchOpencodeGoLimits({ home, trackerDir, env, fetchImpl: providerFetch }), "OpenCode Go", providerTimeoutMs)
|
|
4264
|
+
hiddenSet.has("opencodeGo") ? hiddenSentinel : withProviderTimeout(fetchOpencodeGoLimits({ home, trackerDir, env, fetchImpl: providerFetch }), "OpenCode Go", providerTimeoutMs)
|
|
4163
4265
|
.catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
4164
|
-
withProviderTimeout(
|
|
4266
|
+
hiddenSet.has("qoder") ? hiddenSentinel : withProviderTimeout(
|
|
4165
4267
|
fetchQoderLimits({
|
|
4166
4268
|
home,
|
|
4167
4269
|
env,
|
|
@@ -4171,7 +4273,7 @@ async function fetchUsageLimitsUncached({
|
|
|
4171
4273
|
"Qoder",
|
|
4172
4274
|
providerTimeoutMs,
|
|
4173
4275
|
).catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
4174
|
-
withProviderTimeout(
|
|
4276
|
+
hiddenSet.has("qoderCn") ? hiddenSentinel : withProviderTimeout(
|
|
4175
4277
|
fetchQoderCnLimits({
|
|
4176
4278
|
home,
|
|
4177
4279
|
env,
|
|
@@ -4185,7 +4287,7 @@ async function fetchUsageLimitsUncached({
|
|
|
4185
4287
|
// products sharing the same arkcli binary. No token-consumption source —
|
|
4186
4288
|
// consumption for the compatible CLIs is already counted from their
|
|
4187
4289
|
// local files; these only surface the 5h/week/month quota percentages.
|
|
4188
|
-
withAbortableProviderTimeout(
|
|
4290
|
+
hiddenSet.has("codingPlan") ? hiddenSentinel : withAbortableProviderTimeout(
|
|
4189
4291
|
(signal) => fetchArkCodingPlanLimits({
|
|
4190
4292
|
commandRunner,
|
|
4191
4293
|
home,
|
|
@@ -4197,7 +4299,7 @@ async function fetchUsageLimitsUncached({
|
|
|
4197
4299
|
"Ark Coding Plan",
|
|
4198
4300
|
providerTimeoutMs,
|
|
4199
4301
|
).catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
4200
|
-
withAbortableProviderTimeout(
|
|
4302
|
+
hiddenSet.has("agentPlan") ? hiddenSentinel : withAbortableProviderTimeout(
|
|
4201
4303
|
(signal) => fetchArkAgentPlanLimits({
|
|
4202
4304
|
commandRunner,
|
|
4203
4305
|
home,
|
|
@@ -4217,7 +4319,7 @@ async function fetchUsageLimitsUncached({
|
|
|
4217
4319
|
// outer race bounds the whole slot: without it three sequential per-request
|
|
4218
4320
|
// timeouts (whoami, then credits+subscriptions) could hold the aggregate
|
|
4219
4321
|
// ~2x longer than any sibling provider.
|
|
4220
|
-
withProviderTimeout(fetchCommandcodeLimits({ home, trackerDir, env, fetchImpl: providerFetch }), "CommandCode", providerTimeoutMs)
|
|
4322
|
+
hiddenSet.has("commandCode") ? hiddenSettled : withProviderTimeout(fetchCommandcodeLimits({ home, trackerDir, env, fetchImpl: providerFetch }), "CommandCode", providerTimeoutMs)
|
|
4221
4323
|
.then(
|
|
4222
4324
|
(value) => ({ status: "fulfilled", value }),
|
|
4223
4325
|
(reason) => ({ status: "rejected", reason }),
|
|
@@ -4227,17 +4329,19 @@ async function fetchUsageLimitsUncached({
|
|
|
4227
4329
|
// ~/.local/share/devin/credentials.toml. No local fallback — window state
|
|
4228
4330
|
// lives server-side. fetchDevinLimits throws on auth expiry so the
|
|
4229
4331
|
// assemble step below can flag auth_action_required.
|
|
4230
|
-
withProviderTimeout(fetchDevinLimits({ home, env, enabled: devinEnabled === true, fetchImpl: providerFetch }), "Devin", providerTimeoutMs)
|
|
4332
|
+
hiddenSet.has("devin") ? hiddenSettled : withProviderTimeout(fetchDevinLimits({ home, env, enabled: devinEnabled === true, fetchImpl: providerFetch }), "Devin", providerTimeoutMs)
|
|
4231
4333
|
.then(
|
|
4232
4334
|
(value) => ({ status: "fulfilled", value }),
|
|
4233
4335
|
(reason) => ({ status: "rejected", reason }),
|
|
4234
4336
|
),
|
|
4235
4337
|
// WorkBuddy CN and WorkBuddy AI are separate editions. Each one calls only
|
|
4236
4338
|
// its own local authenticated billing proxy. Abort closes the IPC socket
|
|
4237
|
-
// when the provider budget expires
|
|
4238
|
-
|
|
4339
|
+
// when the provider budget expires; the post-abort grace lets the edition's
|
|
4340
|
+
// disk-cache fallback land instead of the bare timeout. Reward credits are
|
|
4341
|
+
// not a plan window.
|
|
4342
|
+
hiddenSet.has("workbuddy") ? hiddenSentinel : withWorkbuddyProviderTimeout((signal) => fetchWorkbuddyLimits({ home, env, fetchImpl: providerFetch, signal, nowMs: injectedNowMs }), "WorkBuddy", providerTimeoutMs)
|
|
4239
4343
|
.catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
4240
|
-
|
|
4344
|
+
hiddenSet.has("workbuddyAi") ? hiddenSentinel : withWorkbuddyProviderTimeout((signal) => fetchWorkbuddyAiLimits({ home, env, fetchImpl: providerFetch, signal, nowMs: injectedNowMs }), "WorkBuddy AI", providerTimeoutMs)
|
|
4241
4345
|
.catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
|
|
4242
4346
|
// Public status-page probe (fail-soft, own 5-min cache in provider-status.js).
|
|
4243
4347
|
// Only probed for configured accounts — without a token the Claude section
|
|
@@ -4255,8 +4359,10 @@ async function fetchUsageLimitsUncached({
|
|
|
4255
4359
|
// that still exists means the latter — reporting `configured: false` there hides the
|
|
4256
4360
|
// whole Claude section while the usage bars (parsed from local logs, no auth needed)
|
|
4257
4361
|
// keep updating, which reads as "TokenTracker doesn't support my plan" rather than
|
|
4258
|
-
// "your CLI login expired". Existence-only probe: no secret is read here
|
|
4259
|
-
|
|
4362
|
+
// "your CLI login expired". Existence-only probe: no secret is read here — and a
|
|
4363
|
+
// hidden Claude skips even that (#956): its row is the plain sentinel either way.
|
|
4364
|
+
const credentialsPresent = !hiddenSet.has("claude")
|
|
4365
|
+
&& detectClaudeCodeCredentialsPresence({ platform, securityRunner, home });
|
|
4260
4366
|
claude = credentialsPresent
|
|
4261
4367
|
? { configured: true, error: CLAUDE_AUTH_EXPIRED_MESSAGE, auth_action_required: "reauth" }
|
|
4262
4368
|
: { configured: false };
|
|
@@ -4506,12 +4612,18 @@ async function fetchUsageLimitsUncached({
|
|
|
4506
4612
|
codingPlan: withPlanLabel(codingPlan, codingPlan?.plan_label, "Ark Coding Plan"),
|
|
4507
4613
|
agentPlan: withPlanLabel(agentPlan, agentPlan?.plan_label, "Ark Agent Plan"),
|
|
4508
4614
|
// WorkBuddy preserves provider English names and official edition labels
|
|
4509
|
-
// verbatim (including Free, STANDARD and Pro-Trial).
|
|
4510
|
-
|
|
4615
|
+
// verbatim (including Free, STANDARD and Pro-Trial). China and AI publish
|
|
4616
|
+
// as separate providers — one row per signed-in edition, never merged.
|
|
4617
|
+
workbuddy,
|
|
4618
|
+
workbuddyAi,
|
|
4511
4619
|
};
|
|
4512
4620
|
|
|
4513
4621
|
for (const [providerName, provider] of Object.entries(data)) {
|
|
4514
4622
|
if (providerName === "fetched_at" || !provider || typeof provider !== "object") continue;
|
|
4623
|
+
// An exact not-configured sentinel (hidden provider #956, or an opted-out
|
|
4624
|
+
// Devin) is metadata-free by contract — it was never fetched, so it must
|
|
4625
|
+
// not be aged into a "disk-cache"/stale provenance.
|
|
4626
|
+
if (provider.configured === false && Object.keys(provider).length === 1) continue;
|
|
4515
4627
|
const capturedAt = provider.cached_at || data.fetched_at;
|
|
4516
4628
|
const ageMs = Math.max(0, nowMs - Date.parse(capturedAt || ""));
|
|
4517
4629
|
const stale = provider.stale === true || (Number.isFinite(ageMs) && ageMs > 10 * 60 * 1000);
|
|
@@ -4532,7 +4644,8 @@ async function fetchUsageLimitsUncached({
|
|
|
4532
4644
|
}
|
|
4533
4645
|
|
|
4534
4646
|
if (cacheGeneration === usageLimitsCacheGeneration) {
|
|
4535
|
-
|
|
4647
|
+
pruneSelectionPartitions(nowMs);
|
|
4648
|
+
cacheBySelection[selectionKey || usageLimitsSelectionKey({ devinEnabled, hiddenProviders })] = {
|
|
4536
4649
|
data,
|
|
4537
4650
|
expiresAtMs: cacheExpiresAtMs(data, nowMs),
|
|
4538
4651
|
};
|
|
@@ -4540,20 +4653,50 @@ async function fetchUsageLimitsUncached({
|
|
|
4540
4653
|
return data;
|
|
4541
4654
|
}
|
|
4542
4655
|
|
|
4656
|
+
// Hidden-set keys make the partition space user-shaped and unbounded in
|
|
4657
|
+
// principle, while realistic sessions only ever hold a handful of selections.
|
|
4658
|
+
// Keep it bounded: once past the soft cap, drop expired partitions first and
|
|
4659
|
+
// then the oldest still-unexpired ones — they are simply refetched on demand.
|
|
4660
|
+
const SELECTION_PARTITION_CAP = 48;
|
|
4661
|
+
function pruneSelectionPartitions(nowMs) {
|
|
4662
|
+
const keys = Object.keys(cacheBySelection);
|
|
4663
|
+
if (keys.length <= SELECTION_PARTITION_CAP) return;
|
|
4664
|
+
const expired = [];
|
|
4665
|
+
const live = [];
|
|
4666
|
+
for (const key of keys) {
|
|
4667
|
+
(cacheBySelection[key].expiresAtMs <= nowMs ? expired : live).push(key);
|
|
4668
|
+
}
|
|
4669
|
+
const dropCount = keys.length - SELECTION_PARTITION_CAP;
|
|
4670
|
+
const drop = expired.length >= dropCount
|
|
4671
|
+
? expired.slice(0, dropCount)
|
|
4672
|
+
: [...expired, ...live.slice(0, dropCount - expired.length)];
|
|
4673
|
+
for (const key of drop) delete cacheBySelection[key];
|
|
4674
|
+
}
|
|
4675
|
+
|
|
4543
4676
|
function resetUsageLimitsCache({ invalidateInFlight = false } = {}) {
|
|
4544
4677
|
if (invalidateInFlight) usageLimitsCacheGeneration += 1;
|
|
4545
|
-
|
|
4678
|
+
// Selection partitions are created lazily (hidden sets extend the key
|
|
4679
|
+
// space), so reset whatever exists instead of a fixed key list.
|
|
4680
|
+
for (const key of Object.keys(cacheBySelection)) {
|
|
4546
4681
|
cacheBySelection[key] = { data: null, expiresAtMs: 0 };
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4682
|
+
}
|
|
4683
|
+
// Ordinary resets still let a running fetch serve refresh=1 callers; a
|
|
4684
|
+
// credential replacement must not, so drop the old-key promises too. The
|
|
4685
|
+
// in-flight map is walked on its own: a selection can hold a promise before
|
|
4686
|
+
// its partition exists (stored only when the fetch completes).
|
|
4687
|
+
if (invalidateInFlight) {
|
|
4688
|
+
for (const key of Object.keys(inFlightBySelection)) {
|
|
4689
|
+
delete inFlightBySelection[key];
|
|
4690
|
+
}
|
|
4550
4691
|
}
|
|
4551
4692
|
}
|
|
4552
4693
|
|
|
4553
4694
|
module.exports = {
|
|
4554
4695
|
getUsageLimits,
|
|
4555
4696
|
normalizePlanLabel,
|
|
4697
|
+
normalizeHiddenProviders,
|
|
4556
4698
|
resetUsageLimitsCache,
|
|
4699
|
+
USAGE_LIMITS_PROVIDER_IDS,
|
|
4557
4700
|
OPENCODE_GO_LIMITS_CACHE_FILE,
|
|
4558
4701
|
resolveOpencodeGoLimitsCachePath,
|
|
4559
4702
|
cacheExpiresAtMs,
|
|
@@ -4599,4 +4742,5 @@ module.exports = {
|
|
|
4599
4742
|
fetchQoderCnLimits,
|
|
4600
4743
|
fetchWorkbuddyLimits,
|
|
4601
4744
|
fetchWorkbuddyAiLimits,
|
|
4745
|
+
withWorkbuddyProviderTimeout,
|
|
4602
4746
|
};
|