@songsid/agend 2.1.12-beta.1 → 2.1.12-beta.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/dist/agent-cli.js +36 -4
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +3 -1
- package/dist/agent-endpoint.js +7 -1
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/backend/claude-code.d.ts +22 -0
- package/dist/backend/claude-code.js +86 -27
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.d.ts +20 -0
- package/dist/backend/codex.js +55 -5
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/muse.d.ts +6 -0
- package/dist/backend/muse.js +8 -0
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/session-signals.d.ts +154 -0
- package/dist/backend/session-signals.js +430 -0
- package/dist/backend/session-signals.js.map +1 -0
- package/dist/backend/types.d.ts +14 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +38 -1
- package/dist/channel/adapters/discord.js +131 -8
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +19 -1
- package/dist/channel/adapters/telegram.js +55 -0
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-tools.js +5 -1
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/message-queue.d.ts +6 -0
- package/dist/channel/message-queue.js +21 -0
- package/dist/channel/message-queue.js.map +1 -1
- package/dist/channel/sticker-download.d.ts +14 -0
- package/dist/channel/sticker-download.js +42 -0
- package/dist/channel/sticker-download.js.map +1 -0
- package/dist/channel/tool-router.js +12 -5
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +52 -0
- package/dist/cli-flags.d.ts +8 -0
- package/dist/cli-flags.js +22 -0
- package/dist/cli-flags.js.map +1 -0
- package/dist/config-validator.js +25 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +91 -2
- package/dist/daemon.js +462 -22
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +8 -0
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/event-loop-watch.d.ts +25 -0
- package/dist/event-loop-watch.js +40 -0
- package/dist/event-loop-watch.js.map +1 -0
- package/dist/fleet-manager.d.ts +38 -3
- package/dist/fleet-manager.js +248 -12
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/persona-emoji/SKILL.md +4 -2
- package/dist/locale.js +6 -2
- package/dist/locale.js.map +1 -1
- package/dist/outbound-handlers.js +11 -2
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +15 -1
- package/dist/outbound-schemas.js +19 -1
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/tool-permissions.js +8 -1
- package/dist/tool-permissions.js.map +1 -1
- package/dist/transcript-jsonl.d.ts +8 -0
- package/dist/transcript-jsonl.js +27 -0
- package/dist/transcript-jsonl.js.map +1 -0
- package/dist/transcript-monitor.js +13 -24
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.js +2 -17
- package/dist/transcript-sources.js.map +1 -1
- package/dist/turn-reply-guard.d.ts +32 -1
- package/dist/turn-reply-guard.js +47 -1
- package/dist/turn-reply-guard.js.map +1 -1
- package/dist/turn-resume.d.ts +80 -0
- package/dist/turn-resume.js +217 -0
- package/dist/turn-resume.js.map +1 -0
- package/dist/ui/view.html +17 -4
- package/dist/ui/web-terminal/terminal.css +1 -1
- package/dist/ui/web-terminal/terminal.js +5 -1
- package/dist/usage/format-rich.js +3 -2
- package/dist/usage/format-rich.js.map +1 -1
- package/dist/usage/i18n-keys.d.ts +1 -1
- package/dist/usage/i18n-keys.js +1 -1
- package/dist/usage/i18n-keys.js.map +1 -1
- package/dist/usage/i18n.d.ts +5 -0
- package/dist/usage/i18n.js +18 -1
- package/dist/usage/i18n.js.map +1 -1
- package/dist/usage/providers.d.ts +16 -0
- package/dist/usage/providers.js +57 -1
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/usage-api.js +6 -2
- package/dist/usage/usage-api.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #1209 ACP-resume-like cross-restart turn resume, stacked on the
|
|
3
|
+
* TurnFingerprint seam (`session-signals.ts`, #1227).
|
|
4
|
+
*
|
|
5
|
+
* This is tmux-outer resume-*like* state machinery, not the ACP protocol:
|
|
6
|
+
* when a channel turn is armed, a one-shot `in-flight-turn.json` marker is
|
|
7
|
+
* written (with a seam fingerprint checkpoint); whatever survives a restart
|
|
8
|
+
* is, by construction, an interrupted turn. After boot + CLI spawn, the gate
|
|
9
|
+
* below decides exactly once whether to re-inject one bounded continuation
|
|
10
|
+
* tied to the original correlation.
|
|
11
|
+
*
|
|
12
|
+
* Decision order (all in `decideTurnResume`, pure and matrix-tested):
|
|
13
|
+
* marker absent → none (covers cancel: cancel deletes the marker, #1199);
|
|
14
|
+
* crash-loop boot → skip (clean start, #835);
|
|
15
|
+
* marker delivery still pending in the outbox → skip (durable path owns it);
|
|
16
|
+
* store session id changed → skip (conversation is gone);
|
|
17
|
+
* seam reengaged → skip (CLI-native resume already drives; never double-drive);
|
|
18
|
+
* seam unknown → wait out the flush grace, then skip (default-deny);
|
|
19
|
+
* seam quiet → inject (compare only reports quiet outside the grace).
|
|
20
|
+
*
|
|
21
|
+
* The marker is consumed BEFORE gating (episode-once per interrupted turn —
|
|
22
|
+
* a second boot never repeats), and completion/cancel paths delete it so a
|
|
23
|
+
* finished turn never resumes. Backends without a seam reader resolve to
|
|
24
|
+
* unknown and therefore never continue.
|
|
25
|
+
*
|
|
26
|
+
* Hit-rate note (P3c): post-boot writes that look like turns — the warmup
|
|
27
|
+
* steering reload, durable outbox redelivery — advance the turn tail and
|
|
28
|
+
* read as reengaged, so this gate skips. Safe (never double-drives) at the
|
|
29
|
+
* cost of a missed resume; the seam cannot tell "the CLI resumed the work"
|
|
30
|
+
* from "the daemon just drove the CLI".
|
|
31
|
+
*/
|
|
32
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
33
|
+
import { homedir } from "node:os";
|
|
34
|
+
import { join } from "node:path";
|
|
35
|
+
import { CodexBackend } from "./backend/codex.js";
|
|
36
|
+
import { compareFingerprints, claudeFingerprint, codexFingerprint, DEFAULT_FLUSH_GRACE_MS, museFingerprint, } from "./backend/session-signals.js";
|
|
37
|
+
export const IN_FLIGHT_TURN_FILE = "in-flight-turn.json";
|
|
38
|
+
/** Extra settle past the flush grace before a held decision is retaken. */
|
|
39
|
+
export const RESUME_GRACE_SETTLE_MS = 1_000;
|
|
40
|
+
/** Outbox states in which the durable path may still drive the delivery. */
|
|
41
|
+
export const RESUME_BLOCKING_DELIVERY_STATES = [
|
|
42
|
+
"queued",
|
|
43
|
+
"delivering",
|
|
44
|
+
"submission_started",
|
|
45
|
+
"reconciliation_pending",
|
|
46
|
+
"retry_wait",
|
|
47
|
+
];
|
|
48
|
+
/** Accept both factory ids and binary names; anything else has no seam reader. */
|
|
49
|
+
export function normalizeResumeBackend(backend) {
|
|
50
|
+
const key = (backend ?? "").trim().toLowerCase();
|
|
51
|
+
if (key === "claude" || key === "claude-code")
|
|
52
|
+
return "claude";
|
|
53
|
+
if (key === "codex")
|
|
54
|
+
return "codex";
|
|
55
|
+
if (key === "muse")
|
|
56
|
+
return "muse";
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Best-effort current fingerprint for a backend; null when unsupported or
|
|
61
|
+
* unreadable (which the gate treats as unknown → default-deny). Synchronous
|
|
62
|
+
* blocking I/O — arm-time only, never on a hot loop.
|
|
63
|
+
*/
|
|
64
|
+
export function readResumeFingerprint(backend, roots) {
|
|
65
|
+
if (!backend || !roots.workingDirectory)
|
|
66
|
+
return null;
|
|
67
|
+
try {
|
|
68
|
+
if (backend === "claude") {
|
|
69
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR?.trim() || join(homedir(), ".claude");
|
|
70
|
+
return claudeFingerprint({ configDir, cwd: roots.workingDirectory });
|
|
71
|
+
}
|
|
72
|
+
if (backend === "codex") {
|
|
73
|
+
return codexFingerprint({
|
|
74
|
+
stateDbPath: join(CodexBackend.shortHomeFor(roots.instanceDir), "state_5.sqlite"),
|
|
75
|
+
cwd: roots.workingDirectory,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return museFingerprint({
|
|
79
|
+
sessionsRoot: join(homedir(), ".local", "share", "muse", "sessions"),
|
|
80
|
+
cwd: roots.workingDirectory,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** Persist the in-flight marker (best effort; sync so a crash cannot interleave). */
|
|
88
|
+
export function writeInFlightTurnMarker(instanceDir, marker) {
|
|
89
|
+
try {
|
|
90
|
+
writeFileSync(join(instanceDir, IN_FLIGHT_TURN_FILE), JSON.stringify(marker));
|
|
91
|
+
}
|
|
92
|
+
catch { /* best effort — no marker just means no resume candidate */ }
|
|
93
|
+
}
|
|
94
|
+
/** A finished or cancelled turn must never resume after a later restart. */
|
|
95
|
+
export function clearInFlightTurnMarker(instanceDir) {
|
|
96
|
+
try {
|
|
97
|
+
unlinkSync(join(instanceDir, IN_FLIGHT_TURN_FILE));
|
|
98
|
+
}
|
|
99
|
+
catch { /* already gone */ }
|
|
100
|
+
}
|
|
101
|
+
function parseInFlightTurnMarker(raw) {
|
|
102
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
103
|
+
return null;
|
|
104
|
+
const m = raw;
|
|
105
|
+
if (m.version !== 1 || typeof m.chatId !== "string" || m.chatId === "")
|
|
106
|
+
return null;
|
|
107
|
+
if (typeof m.cwd !== "string" || typeof m.backend !== "string")
|
|
108
|
+
return null;
|
|
109
|
+
const str = (v) => (typeof v === "string" && v !== "" ? v : undefined);
|
|
110
|
+
const before = m.before;
|
|
111
|
+
return {
|
|
112
|
+
version: 1,
|
|
113
|
+
deliveryId: str(m.deliveryId),
|
|
114
|
+
correlationId: str(m.correlationId),
|
|
115
|
+
messageId: str(m.messageId),
|
|
116
|
+
chatId: m.chatId,
|
|
117
|
+
threadId: str(m.threadId),
|
|
118
|
+
adapterId: str(m.adapterId),
|
|
119
|
+
backend: m.backend,
|
|
120
|
+
cwd: m.cwd,
|
|
121
|
+
armedAt: typeof m.armedAt === "number" ? m.armedAt : 0,
|
|
122
|
+
before: before && typeof before === "object" ? before : null,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Read and consume the marker in one step: episode-once whatever happens
|
|
127
|
+
* after. A corrupt file is still unlinked (crash-state.json semantics) so it
|
|
128
|
+
* cannot suppress or repeat forever.
|
|
129
|
+
*/
|
|
130
|
+
export function consumeInFlightTurnMarker(instanceDir) {
|
|
131
|
+
const path = join(instanceDir, IN_FLIGHT_TURN_FILE);
|
|
132
|
+
try {
|
|
133
|
+
if (!existsSync(path))
|
|
134
|
+
return null;
|
|
135
|
+
let marker = null;
|
|
136
|
+
try {
|
|
137
|
+
marker = parseInFlightTurnMarker(JSON.parse(readFileSync(path, "utf-8")));
|
|
138
|
+
}
|
|
139
|
+
catch { /* corrupt — still consumed below */ }
|
|
140
|
+
try {
|
|
141
|
+
unlinkSync(path);
|
|
142
|
+
}
|
|
143
|
+
catch { /* best effort */ }
|
|
144
|
+
return marker;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Pure #1209 gate. Only a quiet seam OUTSIDE the flush grace injects; a
|
|
152
|
+
* changed session id and a reengaged CLI both skip (never double-drive);
|
|
153
|
+
* unknown holds through the grace window (wait) and denies after it.
|
|
154
|
+
*/
|
|
155
|
+
export function decideTurnResume(input) {
|
|
156
|
+
const flushGraceMs = input.flushGraceMs ?? DEFAULT_FLUSH_GRACE_MS;
|
|
157
|
+
if (!input.marker)
|
|
158
|
+
return { action: "skip", reason: "none", detail: "no interrupted turn" };
|
|
159
|
+
if (input.crashLoopBoot) {
|
|
160
|
+
return { action: "skip", reason: "crash-loop", detail: "crash-loop boot stays a clean start" };
|
|
161
|
+
}
|
|
162
|
+
if (input.deliveryPending) {
|
|
163
|
+
return { action: "skip", reason: "delivery-pending", detail: "durable delivery path still owns the turn" };
|
|
164
|
+
}
|
|
165
|
+
const beforeId = input.before?.sessionId ?? null;
|
|
166
|
+
const afterId = input.after?.sessionId ?? null;
|
|
167
|
+
if (beforeId && afterId && beforeId !== afterId) {
|
|
168
|
+
return { action: "skip", reason: "session-changed", detail: "store now belongs to a different conversation" };
|
|
169
|
+
}
|
|
170
|
+
const verdict = compareFingerprints(input.before, input.after, {
|
|
171
|
+
daemonCausedMtimeMs: input.daemonCausedMtimeMs,
|
|
172
|
+
nowMs: input.nowMs,
|
|
173
|
+
flushGraceMs,
|
|
174
|
+
});
|
|
175
|
+
if (verdict === "reengaged") {
|
|
176
|
+
return { action: "skip", reason: "cli-reengaged", detail: "CLI-native resume already drives the turn" };
|
|
177
|
+
}
|
|
178
|
+
const inGrace = input.daemonCausedMtimeMs >= 0 && input.nowMs - input.daemonCausedMtimeMs <= flushGraceMs;
|
|
179
|
+
if (verdict === "unknown") {
|
|
180
|
+
if (inGrace) {
|
|
181
|
+
return {
|
|
182
|
+
action: "wait",
|
|
183
|
+
waitMs: flushGraceMs - (input.nowMs - input.daemonCausedMtimeMs) + RESUME_GRACE_SETTLE_MS,
|
|
184
|
+
detail: "holding the flush-grace window before deciding",
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
return { action: "skip", reason: "unobservable", detail: "store unreadable or unsupported; default-deny" };
|
|
188
|
+
}
|
|
189
|
+
// A quiet verdict is only reachable outside the grace (compare holds
|
|
190
|
+
// inside-grace reads as unknown), so it is safe to inject here.
|
|
191
|
+
return { action: "inject", detail: "interrupted turn, CLI idle, nothing else drives it" };
|
|
192
|
+
}
|
|
193
|
+
/** One bounded continuation tied to the original request — never a re-paste. */
|
|
194
|
+
export function buildResumeContinuation(marker) {
|
|
195
|
+
const ref = marker.correlationId ?? marker.messageId ?? marker.deliveryId ?? "unknown";
|
|
196
|
+
const text = "[RESUME — the previous process died mid-turn. "
|
|
197
|
+
+ "Continue exactly this interrupted work, nothing else. "
|
|
198
|
+
+ `Original request: ${ref}.]`;
|
|
199
|
+
const meta = {
|
|
200
|
+
user: "instance:resume",
|
|
201
|
+
user_id: "instance:resume",
|
|
202
|
+
from_instance: "",
|
|
203
|
+
chat_id: marker.chatId,
|
|
204
|
+
message_id: `resume-${marker.armedAt}`,
|
|
205
|
+
};
|
|
206
|
+
if (marker.threadId)
|
|
207
|
+
meta.thread_id = marker.threadId;
|
|
208
|
+
if (marker.adapterId)
|
|
209
|
+
meta.adapter_id = marker.adapterId;
|
|
210
|
+
if (marker.correlationId)
|
|
211
|
+
meta.correlation_id = marker.correlationId;
|
|
212
|
+
if (marker.deliveryId ?? marker.messageId) {
|
|
213
|
+
meta.resumedContinuationOf = String(marker.deliveryId ?? marker.messageId);
|
|
214
|
+
}
|
|
215
|
+
return { text, meta };
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=turn-resume.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turn-resume.js","sourceRoot":"","sources":["../src/turn-resume.ts"],"names":[],"mappings":"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"}
|
package/dist/ui/view.html
CHANGED
|
@@ -251,7 +251,7 @@
|
|
|
251
251
|
usageResets: "resets in {t}", usageSoon: "resets soon", usageFail: "Could not load usage data.",
|
|
252
252
|
"usage.title": "AI Subscription Usage", "usage.title_plain": "AI subscription usage", "usage.empty_active": "No active backends with usage tracking",
|
|
253
253
|
"usage.not_logged_in": "not logged in", "usage.error_fallback": "error", "usage.no_data": "no data",
|
|
254
|
-
"usage.reset.in": "resets in {0}", "usage.reset.soon": "resets soon",
|
|
254
|
+
"usage.reset.in": "resets in {0}", "usage.reset.soon": "resets soon", "usage.ticket_expiry": "🎫 Nearest expiry: {0} (in {1})",
|
|
255
255
|
"usage.duration.days_hours": "{0}d {1}h", "usage.duration.hours_minutes": "{0}h {1}m", "usage.duration.minutes": "{0}m",
|
|
256
256
|
"usage.metric.session": "Session", "usage.metric.weekly": "Weekly", "usage.metric.monthly": "Monthly",
|
|
257
257
|
"usage.metric.days_window": "{0}-day window", "usage.metric.named": "{0}",
|
|
@@ -315,7 +315,7 @@
|
|
|
315
315
|
usageResets: "{t}後重置", usageSoon: "即將重置", usageFail: "無法載入用量資料。",
|
|
316
316
|
"usage.title": "AI 訂閱用量", "usage.title_plain": "AI 訂閱用量", "usage.empty_active": "沒有使用中且支援用量追蹤的 Backend",
|
|
317
317
|
"usage.not_logged_in": "未登入", "usage.error_fallback": "錯誤", "usage.no_data": "無資料",
|
|
318
|
-
"usage.reset.in": "{0} 後重置", "usage.reset.soon": "即將重置",
|
|
318
|
+
"usage.reset.in": "{0} 後重置", "usage.reset.soon": "即將重置", "usage.ticket_expiry": "🎫 最近過期:{0}({1} 後)",
|
|
319
319
|
"usage.duration.days_hours": "{0}d{1}h", "usage.duration.hours_minutes": "{0}h{1}m", "usage.duration.minutes": "{0}m",
|
|
320
320
|
"usage.metric.session": "Session", "usage.metric.weekly": "每週", "usage.metric.monthly": "每月",
|
|
321
321
|
"usage.metric.days_window": "{0} 天視窗", "usage.metric.named": "{0}",
|
|
@@ -1001,6 +1001,18 @@
|
|
|
1001
1001
|
const ms = new Date(iso).getTime() - Date.now();
|
|
1002
1002
|
if (isNaN(ms)) return "";
|
|
1003
1003
|
if (ms <= 0) return T("usage.reset.soon");
|
|
1004
|
+
return usageLocalized("", { key: "usage.reset.in", args: [usageDuration(ms)] });
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// #1244: "🎫 Nearest expiry: 10/22 (in 15d 4h)" — when the soonest reset ticket expires. An expiry, not a reset.
|
|
1008
|
+
function usageExpiryText(iso) {
|
|
1009
|
+
if (!iso) return "";
|
|
1010
|
+
const at = new Date(iso), ms = at.getTime() - Date.now();
|
|
1011
|
+
if (!(ms > 0)) return "";
|
|
1012
|
+
return usageLocalized("", { key: "usage.ticket_expiry", args: [`${at.getMonth() + 1}/${at.getDate()}`, usageDuration(ms)] });
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
function usageDuration(ms) {
|
|
1004
1016
|
const totalMinutes = Math.max(1, Math.ceil(ms / 60000));
|
|
1005
1017
|
const d = Math.floor(totalMinutes / 1440), h = Math.floor((totalMinutes % 1440) / 60), m = totalMinutes % 60;
|
|
1006
1018
|
const duration = totalMinutes >= 2880
|
|
@@ -1008,7 +1020,7 @@
|
|
|
1008
1020
|
: totalMinutes >= 60
|
|
1009
1021
|
? usageLocalized("", { key: "usage.duration.hours_minutes", args: [Math.floor(totalMinutes / 60), m] })
|
|
1010
1022
|
: usageLocalized("", { key: "usage.duration.minutes", args: [totalMinutes] });
|
|
1011
|
-
return
|
|
1023
|
+
return duration;
|
|
1012
1024
|
}
|
|
1013
1025
|
|
|
1014
1026
|
function usageLocalized(fallback, ref) {
|
|
@@ -1039,8 +1051,9 @@
|
|
|
1039
1051
|
const value = usageLocalized(m.value ?? "", m.valueI18n);
|
|
1040
1052
|
const unit = usageLocalized(m.unit ?? "", m.unitI18n);
|
|
1041
1053
|
const val = `${value} ${unit}`.trim();
|
|
1054
|
+
const sub = [note, usageExpiryText(m.expiresAt)].filter(Boolean).join(" · ");
|
|
1042
1055
|
return `<div class="u-metric"><div class="u-row"><span class="u-label">${esc(label)}</span><span class="u-val">${esc(val)}</span></div>` +
|
|
1043
|
-
(
|
|
1056
|
+
(sub ? `<div class="u-sub">${esc(sub)}</div>` : "") + `</div>`;
|
|
1044
1057
|
}
|
|
1045
1058
|
|
|
1046
1059
|
const USAGE_ICON = '<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" aria-hidden="true"><path d="M2.5 13.5h11"/><path d="M4.5 11V8"/><path d="M8 11V4.5"/><path d="M11.5 11V6.5"/></svg>';
|
|
@@ -9,7 +9,7 @@ body { display: flex; flex-direction: column; }
|
|
|
9
9
|
#gate input { flex: 1; font: 16px ui-monospace, monospace; letter-spacing: 1px; padding: 8px; background: #000; color: #fff; border: 1px solid #555; border-radius: 4px; }
|
|
10
10
|
#gate button { padding: 8px 16px; background: #246; color: #fff; border: 0; border-radius: 4px; cursor: pointer; }
|
|
11
11
|
#gate-msg { color: #f88; min-height: 1.5em; }
|
|
12
|
-
#term { flex: 1; min-height: 0; padding: 4px; }
|
|
12
|
+
#term { flex: 1; min-height: 0; padding: 4px; overflow: hidden; } /* never drawn over the rows below it (#1242) */
|
|
13
13
|
#code-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 8px 10px; background: #1c1c1c; border-top: 1px solid #333; }
|
|
14
14
|
#code-row[hidden] { display: none; }
|
|
15
15
|
#code-row input { flex: 1; min-width: 12em; font: 16px ui-monospace, monospace; padding: 8px; background: #000; color: #fff; border: 1px solid #555; border-radius: 4px; }
|
|
@@ -87,7 +87,11 @@
|
|
|
87
87
|
term.onData(function (s) { send(new TextEncoder().encode(s)); });
|
|
88
88
|
term.onBinary(function (s) { var b = new Uint8Array(s.length); for (var i = 0; i < s.length; i++) b[i] = s.charCodeAt(i) & 255; send(b); });
|
|
89
89
|
term.onResize(function (size) { if (ws && ws.readyState === 1) ws.send(JSON.stringify({ t: "resize", cols: size.cols, rows: size.rows })); });
|
|
90
|
-
|
|
90
|
+
// Re-fit whenever the terminal's box changes, not only when the window does (#1242): the sign-in code row
|
|
91
|
+
// appears after the first fit (on "hello"), and a terminal fitted to the taller box ran under that row and
|
|
92
|
+
// covered it — the code box was not on screen until a zoom change (a window resize) re-fitted it.
|
|
93
|
+
if (typeof ResizeObserver === "function") new ResizeObserver(function () { if (fit) fit.fit(); }).observe(termEl);
|
|
94
|
+
else window.addEventListener("resize", function () { if (fit) fit.fit(); });
|
|
91
95
|
} else {
|
|
92
96
|
term.reset(); // the server replays its buffer on (re)connect
|
|
93
97
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isVisibleUsageMetric } from "./usage-api.js";
|
|
2
2
|
import { t } from "../locale.js";
|
|
3
|
-
import { usageResetText, usageText } from "./i18n.js";
|
|
3
|
+
import { usageExpiryText, usageResetText, usageText } from "./i18n.js";
|
|
4
4
|
const BAR_WIDTH = 10;
|
|
5
5
|
/** `████░░░░░░` for 40%. Clamped — a vendor "105% used" must not overflow. */
|
|
6
6
|
export function usageBar(percent) {
|
|
@@ -48,7 +48,8 @@ function metricLine(m) {
|
|
|
48
48
|
}
|
|
49
49
|
case "count": {
|
|
50
50
|
const unit = m.unit ? usageText(m.unit, m.unitI18n) : "";
|
|
51
|
-
|
|
51
|
+
const expiry = usageExpiryText(m.expiresAt);
|
|
52
|
+
return { bar: null, text: `${label}: ${m.value ?? "?"}${unit ? ` ${unit}` : ""}${note ? ` (${note})` : ""}${expiry ? ` · ${expiry}` : ""}` };
|
|
52
53
|
}
|
|
53
54
|
case "text": {
|
|
54
55
|
const value = m.value != null ? usageText(String(m.value), m.valueI18n) : null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format-rich.js","sourceRoot":"","sources":["../../src/usage/format-rich.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,oBAAoB,EAAqB,MAAM,gBAAgB,CAAC;AACzE,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"format-rich.js","sourceRoot":"","sources":["../../src/usage/format-rich.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* Keep this list explicit: the parity test rejects a key missing from either
|
|
4
4
|
* locale surface before a mixed-language release can ship.
|
|
5
5
|
*/
|
|
6
|
-
export declare const USAGE_I18N_KEYS: readonly ["usage.title", "usage.title_plain", "usage.empty_active", "usage.not_logged_in", "usage.error_fallback", "usage.no_data", "usage.reset.in", "usage.reset.soon", "usage.duration.days_hours", "usage.duration.hours_minutes", "usage.duration.minutes", "usage.metric.session", "usage.metric.weekly", "usage.metric.monthly", "usage.metric.days_window", "usage.metric.named", "usage.metric.named_session", "usage.metric.named_weekly", "usage.metric.named_monthly", "usage.metric.named_days", "usage.metric.extra_usage", "usage.metric.rate_limit_resets", "usage.metric.credits", "usage.metric.weekly_limit", "usage.metric.pay_as_you_go", "usage.metric.bonus_credits", "usage.metric.overage_charges", "usage.metric.pay_per_use", "usage.unit.available", "usage.unit.credits", "usage.metric.usage_monthly", "usage.note.used_limit_unit", "usage.note.bonus_codes", "usage.value.disabled", "usage.value.cap", "usage.value.unlimited", "usage.note.binding", "usage.note.binding_severity", "usage.note.busiest_model", "usage.note.busiest_models", "usage.metric.agy_claude_others", "usage.metric.agy_claude_others_session", "usage.metric.agy_claude_others_weekly", "usage.hint.statusline_now", "usage.hint.statusline_minutes", "usage.hint.claude_api_key", "usage.hint.login_claude", "usage.hint.login_agy", "usage.hint.no_quota", "usage.hint.login_codex", "usage.hint.codex_no_oauth", "usage.hint.login_grok", "usage.hint.grok_no_login", "usage.hint.login_kiro", "usage.hint.kiro_signed_out", "usage.hint.token_refreshing", "usage.hint.subscription_no_credit", "usage.hint.no_quota_account", "usage.error.unreachable", "usage.error.token_rejected", "usage.error.rate_limited", "usage.error.http", "usage.error.billing_http", "usage.error.invalid_response", "usage.error.invalid_billing_response", "usage.error.invalid_kiro_response", "usage.error.claude_token_expired", "usage.error.codex_token_expired", "usage.error.grok_expired", "usage.error.grok_response_changed", "usage.error.agy_cap", "usage.error.agy_no_refresh", "usage.error.agy_token_expired", "usage.error.agy_login_expired", "usage.error.agy_refresh_empty", "usage.error.grok_auth_readonly", "usage.error.grok_no_refresh", "usage.error.grok_refresh_unreachable", "usage.error.grok_invalid_refresh", "usage.error.grok_persist"];
|
|
6
|
+
export declare const USAGE_I18N_KEYS: readonly ["usage.title", "usage.title_plain", "usage.empty_active", "usage.not_logged_in", "usage.error_fallback", "usage.no_data", "usage.reset.in", "usage.reset.soon", "usage.duration.days_hours", "usage.duration.hours_minutes", "usage.duration.minutes", "usage.metric.session", "usage.metric.weekly", "usage.metric.monthly", "usage.metric.days_window", "usage.metric.named", "usage.metric.named_session", "usage.metric.named_weekly", "usage.metric.named_monthly", "usage.metric.named_days", "usage.metric.extra_usage", "usage.metric.rate_limit_resets", "usage.ticket_expiry", "usage.metric.credits", "usage.metric.weekly_limit", "usage.metric.pay_as_you_go", "usage.metric.bonus_credits", "usage.metric.overage_charges", "usage.metric.pay_per_use", "usage.unit.available", "usage.unit.credits", "usage.metric.usage_monthly", "usage.note.used_limit_unit", "usage.note.bonus_codes", "usage.value.disabled", "usage.value.cap", "usage.value.unlimited", "usage.note.binding", "usage.note.binding_severity", "usage.note.busiest_model", "usage.note.busiest_models", "usage.metric.agy_claude_others", "usage.metric.agy_claude_others_session", "usage.metric.agy_claude_others_weekly", "usage.hint.statusline_now", "usage.hint.statusline_minutes", "usage.hint.claude_api_key", "usage.hint.login_claude", "usage.hint.login_agy", "usage.hint.no_quota", "usage.hint.login_codex", "usage.hint.codex_no_oauth", "usage.hint.login_grok", "usage.hint.grok_no_login", "usage.hint.login_kiro", "usage.hint.kiro_signed_out", "usage.hint.token_refreshing", "usage.hint.subscription_no_credit", "usage.hint.no_quota_account", "usage.error.unreachable", "usage.error.token_rejected", "usage.error.rate_limited", "usage.error.http", "usage.error.billing_http", "usage.error.invalid_response", "usage.error.invalid_billing_response", "usage.error.invalid_kiro_response", "usage.error.claude_token_expired", "usage.error.codex_token_expired", "usage.error.grok_expired", "usage.error.grok_response_changed", "usage.error.agy_cap", "usage.error.agy_no_refresh", "usage.error.agy_token_expired", "usage.error.agy_login_expired", "usage.error.agy_refresh_empty", "usage.error.grok_auth_readonly", "usage.error.grok_no_refresh", "usage.error.grok_refresh_unreachable", "usage.error.grok_invalid_refresh", "usage.error.grok_persist"];
|
|
7
7
|
export type UsageI18nKey = typeof USAGE_I18N_KEYS[number];
|
package/dist/usage/i18n-keys.js
CHANGED
|
@@ -10,7 +10,7 @@ export const USAGE_I18N_KEYS = [
|
|
|
10
10
|
"usage.metric.session", "usage.metric.weekly", "usage.metric.monthly", "usage.metric.days_window",
|
|
11
11
|
"usage.metric.named", "usage.metric.named_session", "usage.metric.named_weekly",
|
|
12
12
|
"usage.metric.named_monthly", "usage.metric.named_days", "usage.metric.extra_usage",
|
|
13
|
-
"usage.metric.rate_limit_resets", "usage.metric.credits", "usage.metric.weekly_limit",
|
|
13
|
+
"usage.metric.rate_limit_resets", "usage.ticket_expiry", "usage.metric.credits", "usage.metric.weekly_limit",
|
|
14
14
|
"usage.metric.pay_as_you_go", "usage.metric.bonus_credits", "usage.metric.overage_charges",
|
|
15
15
|
"usage.metric.pay_per_use", "usage.unit.available", "usage.unit.credits",
|
|
16
16
|
"usage.metric.usage_monthly", "usage.note.used_limit_unit", "usage.note.bonus_codes",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18n-keys.js","sourceRoot":"","sources":["../../src/usage/i18n-keys.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,aAAa,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,qBAAqB;IAC/E,sBAAsB,EAAE,eAAe,EAAE,gBAAgB,EAAE,kBAAkB;IAC7E,2BAA2B,EAAE,8BAA8B,EAAE,wBAAwB;IACrF,sBAAsB,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,0BAA0B;IACjG,oBAAoB,EAAE,4BAA4B,EAAE,2BAA2B;IAC/E,4BAA4B,EAAE,yBAAyB,EAAE,0BAA0B;IACnF,gCAAgC,EAAE,sBAAsB,EAAE,2BAA2B;
|
|
1
|
+
{"version":3,"file":"i18n-keys.js","sourceRoot":"","sources":["../../src/usage/i18n-keys.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,aAAa,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,qBAAqB;IAC/E,sBAAsB,EAAE,eAAe,EAAE,gBAAgB,EAAE,kBAAkB;IAC7E,2BAA2B,EAAE,8BAA8B,EAAE,wBAAwB;IACrF,sBAAsB,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,0BAA0B;IACjG,oBAAoB,EAAE,4BAA4B,EAAE,2BAA2B;IAC/E,4BAA4B,EAAE,yBAAyB,EAAE,0BAA0B;IACnF,gCAAgC,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,2BAA2B;IAC5G,4BAA4B,EAAE,4BAA4B,EAAE,8BAA8B;IAC1F,0BAA0B,EAAE,sBAAsB,EAAE,oBAAoB;IACxE,4BAA4B,EAAE,4BAA4B,EAAE,wBAAwB;IACpF,sBAAsB,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,oBAAoB;IACxF,6BAA6B,EAAE,0BAA0B,EAAE,2BAA2B;IACtF,gCAAgC,EAAE,wCAAwC,EAAE,uCAAuC;IACnH,2BAA2B,EAAE,+BAA+B,EAAE,2BAA2B;IACzF,yBAAyB,EAAE,sBAAsB,EAAE,qBAAqB;IACxE,wBAAwB,EAAE,2BAA2B,EAAE,uBAAuB;IAC9E,0BAA0B,EAAE,uBAAuB,EAAE,4BAA4B;IACjF,6BAA6B,EAAE,mCAAmC,EAAE,6BAA6B;IACjG,yBAAyB,EAAE,4BAA4B,EAAE,0BAA0B;IACnF,kBAAkB,EAAE,0BAA0B,EAAE,8BAA8B;IAC9E,sCAAsC,EAAE,mCAAmC;IAC3E,kCAAkC,EAAE,iCAAiC;IACrE,0BAA0B,EAAE,mCAAmC,EAAE,qBAAqB;IACtF,4BAA4B,EAAE,+BAA+B,EAAE,+BAA+B;IAC9F,+BAA+B,EAAE,gCAAgC,EAAE,6BAA6B;IAChG,sCAAsC,EAAE,kCAAkC,EAAE,0BAA0B;CAC9F,CAAC"}
|
package/dist/usage/i18n.d.ts
CHANGED
|
@@ -3,3 +3,8 @@ import type { UsageI18nRef } from "./providers.js";
|
|
|
3
3
|
export declare function usageText(fallback: string, ref?: UsageI18nRef): string;
|
|
4
4
|
/** Minute-precision duration shared by the chat and MCP usage renderers. */
|
|
5
5
|
export declare function usageResetText(resetsAt?: string | null): string;
|
|
6
|
+
/**
|
|
7
|
+
* "🎫 Nearest expiry: 10/22 (in 15d 4h)" — when the soonest of a metric's tickets expires (#1244); "" when none is
|
|
8
|
+
* still ahead. An expiry, never a reset: the ticket is gone, nothing refills.
|
|
9
|
+
*/
|
|
10
|
+
export declare function usageExpiryText(expiresAt?: string | null): string;
|
package/dist/usage/i18n.js
CHANGED
|
@@ -13,6 +13,23 @@ export function usageResetText(resetsAt) {
|
|
|
13
13
|
const remainingMs = at.getTime() - Date.now();
|
|
14
14
|
if (remainingMs <= 0)
|
|
15
15
|
return t("usage.reset.soon");
|
|
16
|
+
return t("usage.reset.in", usageDuration(remainingMs));
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* "🎫 Nearest expiry: 10/22 (in 15d 4h)" — when the soonest of a metric's tickets expires (#1244); "" when none is
|
|
20
|
+
* still ahead. An expiry, never a reset: the ticket is gone, nothing refills.
|
|
21
|
+
*/
|
|
22
|
+
export function usageExpiryText(expiresAt) {
|
|
23
|
+
if (!expiresAt)
|
|
24
|
+
return "";
|
|
25
|
+
const at = new Date(expiresAt);
|
|
26
|
+
const remainingMs = at.getTime() - Date.now();
|
|
27
|
+
if (!(remainingMs > 0))
|
|
28
|
+
return "";
|
|
29
|
+
return t("usage.ticket_expiry", `${at.getMonth() + 1}/${at.getDate()}`, usageDuration(remainingMs));
|
|
30
|
+
}
|
|
31
|
+
/** "2d 3h" from two days, "5h 12m" from an hour, "7m" below that — minute precision, rounded up. */
|
|
32
|
+
function usageDuration(remainingMs) {
|
|
16
33
|
const totalMinutes = Math.max(1, Math.ceil(remainingMs / 60_000));
|
|
17
34
|
const days = Math.floor(totalMinutes / 1_440);
|
|
18
35
|
const hours = Math.floor((totalMinutes % 1_440) / 60);
|
|
@@ -22,6 +39,6 @@ export function usageResetText(resetsAt) {
|
|
|
22
39
|
: totalMinutes >= 60
|
|
23
40
|
? t("usage.duration.hours_minutes", Math.floor(totalMinutes / 60), minutes)
|
|
24
41
|
: t("usage.duration.minutes", totalMinutes);
|
|
25
|
-
return
|
|
42
|
+
return duration;
|
|
26
43
|
}
|
|
27
44
|
//# sourceMappingURL=i18n.js.map
|
package/dist/usage/i18n.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../../src/usage/i18n.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAC;AAGjC,mFAAmF;AACnF,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,GAAkB;IAC5D,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC1D,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,cAAc,CAAC,QAAwB;IACrD,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,CAAC;IACzB,MAAM,EAAE,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC9B,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,CAAC;IAC1C,MAAM,WAAW,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC9C,IAAI,WAAW,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../../src/usage/i18n.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAC;AAGjC,mFAAmF;AACnF,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,GAAkB;IAC5D,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC1D,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,cAAc,CAAC,QAAwB;IACrD,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,CAAC;IACzB,MAAM,EAAE,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC9B,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,CAAC;IAC1C,MAAM,WAAW,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC9C,IAAI,WAAW,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,CAAC;IACnD,OAAO,CAAC,CAAC,gBAAgB,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC;AACzD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,SAAyB;IACvD,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,CAAC;IAC1B,MAAM,EAAE,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC;IAC/B,MAAM,WAAW,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC9C,IAAI,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,OAAO,CAAC,CAAC,qBAAqB,EAAE,GAAG,EAAE,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC;AACtG,CAAC;AAED,oGAAoG;AACpG,SAAS,aAAa,CAAC,WAAmB;IACxC,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,CAAC,CAAC;IAClE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,YAAY,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;IACtD,MAAM,OAAO,GAAG,YAAY,GAAG,EAAE,CAAC;IAClC,MAAM,QAAQ,GAAG,YAAY,IAAI,KAAK;QACpC,CAAC,CAAC,CAAC,CAAC,2BAA2B,EAAE,IAAI,EAAE,KAAK,CAAC;QAC7C,CAAC,CAAC,YAAY,IAAI,EAAE;YAClB,CAAC,CAAC,CAAC,CAAC,8BAA8B,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,EAAE,CAAC,EAAE,OAAO,CAAC;YAC3E,CAAC,CAAC,CAAC,CAAC,wBAAwB,EAAE,YAAY,CAAC,CAAC;IAChD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -19,6 +19,16 @@ export interface UsageMetric {
|
|
|
19
19
|
note?: string;
|
|
20
20
|
noteI18n?: UsageI18nRef;
|
|
21
21
|
resetsAt?: string | null;
|
|
22
|
+
/**
|
|
23
|
+
* "expiry": `resetsAt` is when this allowance runs out for good (Kiro bonus credits), not when it refills. Such a
|
|
24
|
+
* time is never a provider's next reset (#1232 review). Absent: a genuine reset.
|
|
25
|
+
*/
|
|
26
|
+
resetKind?: "expiry";
|
|
27
|
+
/**
|
|
28
|
+
* When the soonest of the things this metric counts expires (#1244: Codex's rate-limit reset tickets), as an ISO
|
|
29
|
+
* time. An expiry, never a reset — it is shown on the metric's own line, and nothing treats it as a reset time.
|
|
30
|
+
*/
|
|
31
|
+
expiresAt?: string | null;
|
|
22
32
|
windowMs?: number | null;
|
|
23
33
|
}
|
|
24
34
|
export interface ProviderUsage {
|
|
@@ -134,6 +144,12 @@ interface AgyStoredToken {
|
|
|
134
144
|
* uses) is read alongside `expiry` (RFC3339) for the same reason.
|
|
135
145
|
*/
|
|
136
146
|
export declare function readAgyToken(file: unknown): AgyStoredToken;
|
|
147
|
+
/**
|
|
148
|
+
* When the soonest of the still-available reset tickets expires (#1244): min(expires_at) over the tickets whose status
|
|
149
|
+
* is "available", skipping ones with no expiry or one already past. null when there is none — or when the list cannot
|
|
150
|
+
* be read: the count still shows, without an expiry.
|
|
151
|
+
*/
|
|
152
|
+
export declare function codexTicketsNearestExpiry(body: unknown, nowMs: number): string | null;
|
|
137
153
|
export declare function fetchCodexUsage(storeHome?: string): Promise<Omit<ProviderUsage, "id" | "name">>;
|
|
138
154
|
type KiroToken = KiroStoredToken;
|
|
139
155
|
/**
|
package/dist/usage/providers.js
CHANGED
|
@@ -622,6 +622,15 @@ async function fetchAntigravityUsage() {
|
|
|
622
622
|
}
|
|
623
623
|
// ── Codex ────────────────────────────────────────────────────────────────────
|
|
624
624
|
const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
625
|
+
/** The reset tickets one by one (read only; the CLI redeems through `…/consume`, which AgEnD never calls). */
|
|
626
|
+
const CODEX_RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
|
|
627
|
+
/**
|
|
628
|
+
* The ticket list is optional — only the expiry hangs on it — so it gets a short budget, and never more than the time
|
|
629
|
+
* left before the provider deadline minus a margin (#1246 review): a slow usage answer followed by a stalled list must
|
|
630
|
+
* still return the numbers already in hand, not lose the whole row to the deadline.
|
|
631
|
+
*/
|
|
632
|
+
const CODEX_TICKET_LOOKUP_MS = 3_000;
|
|
633
|
+
const CODEX_TICKET_DEADLINE_MARGIN_MS = 1_000;
|
|
625
634
|
const CREDIT_USD_RATE = 0.04;
|
|
626
635
|
function codexResetIso(win, nowMs) {
|
|
627
636
|
if (typeof win.reset_at === "number" && Number.isFinite(win.reset_at))
|
|
@@ -670,7 +679,47 @@ function codexPlan(planType) {
|
|
|
670
679
|
default: return planType.trim().split("_").map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
671
680
|
}
|
|
672
681
|
}
|
|
682
|
+
/**
|
|
683
|
+
* When the soonest of the still-available reset tickets expires (#1244): min(expires_at) over the tickets whose status
|
|
684
|
+
* is "available", skipping ones with no expiry or one already past. null when there is none — or when the list cannot
|
|
685
|
+
* be read: the count still shows, without an expiry.
|
|
686
|
+
*/
|
|
687
|
+
export function codexTicketsNearestExpiry(body, nowMs) {
|
|
688
|
+
const credits = body?.credits;
|
|
689
|
+
let best = null;
|
|
690
|
+
for (const c of Array.isArray(credits) ? credits : []) {
|
|
691
|
+
const ticket = c;
|
|
692
|
+
if (ticket?.status !== "available" || typeof ticket.expires_at !== "string")
|
|
693
|
+
continue;
|
|
694
|
+
const at = Date.parse(ticket.expires_at);
|
|
695
|
+
if (Number.isFinite(at) && at > nowMs && (best === null || at < best))
|
|
696
|
+
best = at;
|
|
697
|
+
}
|
|
698
|
+
return best === null ? null : new Date(best).toISOString();
|
|
699
|
+
}
|
|
700
|
+
async function codexNearestTicketExpiry(headers, nowMs, budgetMs) {
|
|
701
|
+
if (!(budgetMs > 0))
|
|
702
|
+
return null;
|
|
703
|
+
const abort = new AbortController();
|
|
704
|
+
let timer;
|
|
705
|
+
// The timer both aborts the request and settles the race, so a fetch that ignores its signal cannot hold the row.
|
|
706
|
+
const timedOut = new Promise(resolve => { timer = setTimeout(() => { abort.abort(); resolve(null); }, budgetMs); });
|
|
707
|
+
const lookup = (async () => {
|
|
708
|
+
const res = await fetch(CODEX_RESET_CREDITS_URL, { headers, signal: abort.signal });
|
|
709
|
+
if (!res.ok)
|
|
710
|
+
return null;
|
|
711
|
+
return codexTicketsNearestExpiry(await res.json(), nowMs);
|
|
712
|
+
})().catch(() => null);
|
|
713
|
+
try {
|
|
714
|
+
return await Promise.race([lookup, timedOut]);
|
|
715
|
+
}
|
|
716
|
+
finally {
|
|
717
|
+
clearTimeout(timer);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
673
720
|
export async function fetchCodexUsage(storeHome) {
|
|
721
|
+
// Monotonic: the deadline this budget is measured against is a timer, which a wall-clock change does not move.
|
|
722
|
+
const startedAt = performance.now();
|
|
674
723
|
// A credential profile owns its own auth.json; without one this is the shared
|
|
675
724
|
// login, exactly as before.
|
|
676
725
|
const home = storeHome ?? process.env.CODEX_HOME ?? join(homedir(), ".codex");
|
|
@@ -750,10 +799,15 @@ export async function fetchCodexUsage(storeHome) {
|
|
|
750
799
|
}
|
|
751
800
|
const resets = body.rate_limit_reset_credits;
|
|
752
801
|
if (typeof resets?.available_count === "number" && resets.available_count >= 0) {
|
|
802
|
+
const available = Math.floor(resets.available_count);
|
|
803
|
+
// #1244: the usage endpoint gives only the count; when each ticket expires is on the tickets' own list.
|
|
804
|
+
const budgetMs = Math.min(CODEX_TICKET_LOOKUP_MS, providerDeadlineMs - (performance.now() - startedAt) - CODEX_TICKET_DEADLINE_MARGIN_MS);
|
|
805
|
+
const expiresAt = available > 0 ? await codexNearestTicketExpiry(headers, nowMs, budgetMs) : null;
|
|
753
806
|
metrics.push({
|
|
754
807
|
label: "Rate limit resets", labelI18n: i18n("usage.metric.rate_limit_resets"),
|
|
755
|
-
type: "count", value:
|
|
808
|
+
type: "count", value: available,
|
|
756
809
|
unit: "available", unitI18n: i18n("usage.unit.available"),
|
|
810
|
+
...(expiresAt ? { expiresAt } : {}),
|
|
757
811
|
});
|
|
758
812
|
}
|
|
759
813
|
const credits = body.credits;
|
|
@@ -1093,7 +1147,9 @@ export async function fetchKiroUsage(storeHome) {
|
|
|
1093
1147
|
labelI18n: i18n("usage.metric.bonus_credits"),
|
|
1094
1148
|
type: "percent",
|
|
1095
1149
|
used: Math.min(100, (bUsed / bLimit) * 100),
|
|
1150
|
+
// When the first code expires — the credits go, nothing refills.
|
|
1096
1151
|
resetsAt: expiries.length ? kiroEpochIso(Math.min(...expiries)) : null,
|
|
1152
|
+
resetKind: "expiry",
|
|
1097
1153
|
note: `${+bUsed.toFixed(1)} / ${+bLimit.toFixed(0)} ${unit} · ${bonuses.length} codes`,
|
|
1098
1154
|
noteI18n: i18n("usage.note.bonus_codes", +bUsed.toFixed(1), +bLimit.toFixed(0), unit, bonuses.length),
|
|
1099
1155
|
});
|