mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-27 02:07:51 +00:00
fix(codex): ignore empty rate-limit buckets
This commit is contained in:
@@ -22,6 +22,7 @@ Docs: https://docs.openclaw.ai
|
||||
- LINE: stop cron recovery from inferring lowercased LINE recipients from canonical session keys, so long-running task replies do not silently retry undeliverable push targets. Fixes #81628. (#81704) Thanks @edenfunf.
|
||||
- TTS: preserve channel-derived voice-note delivery for `/tts audio` replies even when the provider output is not natively voice-compatible. (#82174) Thanks @xuruiray.
|
||||
- Codex app-server: preserve inbound sender metadata and source-channel provenance on mirrored user prompts, including failure snapshots, so channel history keeps the original sender identity. (#82184) Thanks @zknicker.
|
||||
- Codex account/status: treat metadata-only rate-limit buckets as returned but empty so `/codex status` and `/codex account` report `none returned` instead of counting phantom limits.
|
||||
- Codex/Lossless: keep Codex explicit compaction on native app-server threads while allowing Lossless through the context-engine slot; `openclaw doctor --fix` now migrates legacy `compaction.provider: "lossless-claw"` config to `plugins.slots.contextEngine`.
|
||||
- Cron/doctor: report scheduled jobs with explicit `payload.model` overrides, including provider namespace counts and default-model mismatches, so stale cron model pins are visible during auth or billing investigations. Fixes #82151. Thanks @mgonto.
|
||||
- Codex app-server: keep the short turn-completion idle watchdog armed after the last non-assistant current-turn item completes, so a quiet Codex app-server releases the OpenClaw session lane before the outer attempt timeout. Fixes #82171. (#82172) Thanks @funmerlin.
|
||||
|
||||
@@ -148,4 +148,55 @@ describe("summarizeCodexRateLimits", () => {
|
||||
blockingReason: "Codex usage limit is reached",
|
||||
});
|
||||
});
|
||||
|
||||
it("ignores metadata-only Codex buckets", () => {
|
||||
expect(
|
||||
summarizeCodexRateLimits({
|
||||
rateLimitsByLimitId: {
|
||||
codex: {
|
||||
limitId: "codex",
|
||||
limitName: "Codex",
|
||||
primary: null,
|
||||
secondary: null,
|
||||
credits: null,
|
||||
planType: "plus",
|
||||
rateLimitReachedType: null,
|
||||
},
|
||||
},
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps displayable buckets when sibling buckets are empty", () => {
|
||||
const nowMs = 1_700_000_000_000;
|
||||
const nowSeconds = nowMs / 1000;
|
||||
|
||||
expect(
|
||||
summarizeCodexRateLimits(
|
||||
{
|
||||
rateLimitsByLimitId: {
|
||||
codex: {
|
||||
limitId: "codex",
|
||||
limitName: "Codex",
|
||||
primary: { usedPercent: 26, windowDurationMins: 300, resetsAt: nowSeconds + 3600 },
|
||||
secondary: null,
|
||||
credits: null,
|
||||
planType: "plus",
|
||||
rateLimitReachedType: null,
|
||||
},
|
||||
"gpt-5.3-codex-spark": {
|
||||
limitId: "gpt-5.3-codex-spark",
|
||||
limitName: "GPT 5.3 Codex Spark",
|
||||
primary: null,
|
||||
secondary: null,
|
||||
credits: null,
|
||||
planType: "plus",
|
||||
rateLimitReachedType: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
nowMs,
|
||||
),
|
||||
).toBe("Codex: primary 74% left ⏱1h");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -372,7 +372,8 @@ function formatCodexRateLimitDetails(value: JsonValue | undefined): string {
|
||||
function summarizeRateLimits(value: JsonValue | undefined): string {
|
||||
const entries = extractArray(value);
|
||||
if (entries.length > 0) {
|
||||
return `${entries.length}`;
|
||||
const count = entries.filter(isMeaningfulRateLimitSnapshot).length;
|
||||
return count > 0 ? `${count}` : "none returned";
|
||||
}
|
||||
if (!isJsonObject(value)) {
|
||||
return "none returned";
|
||||
@@ -388,7 +389,18 @@ function summarizeRateLimits(value: JsonValue | undefined): string {
|
||||
}
|
||||
|
||||
function isMeaningfulRateLimitSnapshot(value: JsonValue | undefined): boolean {
|
||||
return isJsonObject(value) && Object.values(value).some((entry) => entry != null);
|
||||
if (!isJsonObject(value)) {
|
||||
return false;
|
||||
}
|
||||
const reachedType =
|
||||
readString(value, "rateLimitReachedType") ?? readString(value, "rate_limit_reached_type");
|
||||
if (reachedType) {
|
||||
return true;
|
||||
}
|
||||
return ["primary", "secondary"].some((key) => {
|
||||
const window = value[key];
|
||||
return isJsonObject(window) && Object.values(window).some((entry) => entry != null);
|
||||
});
|
||||
}
|
||||
|
||||
function extractArray(value: JsonValue | undefined): JsonValue[] {
|
||||
|
||||
@@ -647,6 +647,17 @@ describe("codex command", () => {
|
||||
const limits = {
|
||||
ok: true as const,
|
||||
value: {
|
||||
rateLimits: [
|
||||
{
|
||||
limitId: "codex",
|
||||
limitName: "Codex",
|
||||
primary: null,
|
||||
secondary: null,
|
||||
credits: null,
|
||||
planType: "plus",
|
||||
rateLimitReachedType: null,
|
||||
},
|
||||
],
|
||||
rateLimitsByLimitId: {
|
||||
premium: {
|
||||
limitId: "premium",
|
||||
@@ -657,6 +668,15 @@ describe("codex command", () => {
|
||||
planType: "pro",
|
||||
rateLimitReachedType: null,
|
||||
},
|
||||
codex: {
|
||||
limitId: "codex",
|
||||
limitName: "Codex",
|
||||
primary: null,
|
||||
secondary: null,
|
||||
credits: null,
|
||||
planType: "plus",
|
||||
rateLimitReachedType: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user