Skip to content

Commit 7872909

Browse files
committed
fix(status): restore codex status usage
1 parent f27a263 commit 7872909

6 files changed

Lines changed: 106 additions & 47 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ Docs: https://docs.openclaw.ai
174174
- Agents/context-engine: share loop-hook checkpoints with the after-turn finalizer so messages are not replayed. Fixes #79630.
175175
- Codex app-server: keep native hook relays alive for long-running turns so shell and file approvals stay reachable until the configured run window finishes. (#77533) Thanks @rubencu.
176176
- Gateway/macOS: clear ignored SIGUSR1 restart state, skip redundant package-update restarts when the refreshed LaunchAgent already serves the expected version, and give launchd a 10s throttle plus 20s shutdown window so update restarts do not leave old gateways alive or fight supervisor recovery. Fixes #79577; refs #78699 and #60885. Thanks @BunsDev.
177+
- Status/Codex: route Codex-harness `openai/*` usage through the OpenAI Codex quota provider and scope CLI status usage to the default agent auth store so `/status` and `openclaw status --usage` show Codex quota windows again. Fixes #79312. Thanks @buhusa.
177178
- Gateway/agent: pass the session-key agent id into inline image attachment validation so the first image in a fresh per-agent session uses the agent's vision-capable model override instead of the text-only system default. Fixes #79407. Thanks @pandadev66.
178179
- Gateway/maintenance: prune dedupe overflow against a stable excess count and keep active agent retries from starting duplicate runs after cache eviction. (#73841) Thanks @thesomewhatyou.
179180
- Control UI/subagents: suppress internal `subagent_announce` handoff prompts from requester transcripts and hide legacy inter-session wrapper rows so completed subagent results no longer surface runtime context in WebChat history. (#79618) Thanks @joshavant.

src/commands/status-runtime-shared.test.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,32 @@ describe("status-runtime-shared", () => {
102102
});
103103

104104
it("resolves usage summaries with the provided timeout", async () => {
105-
await resolveStatusUsageSummary(1234);
105+
await resolveStatusUsageSummary({
106+
timeoutMs: 1234,
107+
config: { gateway: {} },
108+
});
109+
110+
expect(mocks.loadProviderUsageSummary).toHaveBeenCalledWith(
111+
expect.objectContaining({
112+
timeoutMs: 1234,
113+
config: { gateway: {} },
114+
agentDir: expect.stringContaining("main"),
115+
}),
116+
);
117+
});
106118

107-
expect(mocks.loadProviderUsageSummary).toHaveBeenCalledWith({ timeoutMs: 1234 });
119+
it("resolves usage summaries with explicit agent scope", async () => {
120+
await resolveStatusUsageSummary({
121+
timeoutMs: 2345,
122+
config: { gateway: {} },
123+
agentDir: "/tmp/status-agent",
124+
});
125+
126+
expect(mocks.loadProviderUsageSummary).toHaveBeenCalledWith({
127+
timeoutMs: 2345,
128+
config: { gateway: {} },
129+
agentDir: "/tmp/status-agent",
130+
});
108131
});
109132

110133
it("resolves gateway health with the shared probe call shape", async () => {
@@ -205,7 +228,13 @@ describe("status-runtime-shared", () => {
205228
gatewayService: { label: "LaunchAgent" },
206229
nodeService: { label: "node" },
207230
});
208-
expect(mocks.loadProviderUsageSummary).toHaveBeenCalledWith({ timeoutMs: 1234 });
231+
expect(mocks.loadProviderUsageSummary).toHaveBeenCalledWith(
232+
expect.objectContaining({
233+
timeoutMs: 1234,
234+
config: { gateway: {} },
235+
agentDir: expect.stringContaining("main"),
236+
}),
237+
);
209238
expect(mocks.callGateway).toHaveBeenNthCalledWith(1, {
210239
method: "health",
211240
params: { probe: true },

src/commands/status-runtime-shared.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { resolveDefaultAgentDir } from "../agents/agent-scope.js";
12
import { resolveReadOnlyChannelPluginsForConfig } from "../channels/plugins/read-only.js";
23
import type { OpenClawConfig } from "../config/types.js";
34
import type { HeartbeatEventPayload } from "../infra/heartbeat-events.js";
@@ -45,9 +46,19 @@ export async function resolveStatusSecurityAudit(params: {
4546
});
4647
}
4748

48-
export async function resolveStatusUsageSummary(timeoutMs?: number) {
49+
type StatusUsageSummaryOptions = {
50+
config: OpenClawConfig;
51+
timeoutMs?: number;
52+
agentDir?: string;
53+
};
54+
55+
export async function resolveStatusUsageSummary(params: StatusUsageSummaryOptions) {
4956
const { loadProviderUsageSummary } = await loadProviderUsage();
50-
return await loadProviderUsageSummary({ timeoutMs });
57+
return await loadProviderUsageSummary({
58+
timeoutMs: params.timeoutMs,
59+
config: params.config,
60+
agentDir: params.agentDir ?? resolveDefaultAgentDir(params.config),
61+
});
5162
}
5263

5364
export async function loadStatusProviderUsageModule() {
@@ -126,15 +137,20 @@ export async function resolveStatusRuntimeDetails(params: {
126137
deep?: boolean;
127138
gatewayReachable: boolean;
128139
suppressHealthErrors?: boolean;
129-
resolveUsage?: (timeoutMs?: number) => Promise<StatusUsageSummary>;
140+
resolveUsage?: (input: StatusUsageSummaryOptions) => Promise<StatusUsageSummary>;
130141
resolveHealth?: (input: {
131142
config: OpenClawConfig;
132143
timeoutMs?: number;
133144
}) => Promise<StatusGatewayHealth>;
134145
}) {
135146
const resolveUsageSummary = params.resolveUsage ?? resolveStatusUsageSummary;
136147
const resolveGatewayHealthSummary = params.resolveHealth ?? resolveStatusGatewayHealth;
137-
const usage = params.usage ? await resolveUsageSummary(params.timeoutMs) : undefined;
148+
const usage = params.usage
149+
? await resolveUsageSummary({
150+
timeoutMs: params.timeoutMs,
151+
config: params.config,
152+
})
153+
: undefined;
138154
const health = params.deep
139155
? params.suppressHealthErrors
140156
? await resolveGatewayHealthSummary({
@@ -183,7 +199,7 @@ export async function resolveStatusRuntimeSnapshot(params: {
183199
config: OpenClawConfig;
184200
sourceConfig: OpenClawConfig;
185201
}) => Promise<StatusSecurityAudit>;
186-
resolveUsage?: (timeoutMs?: number) => Promise<StatusUsageSummary>;
202+
resolveUsage?: (input: StatusUsageSummaryOptions) => Promise<StatusUsageSummary>;
187203
resolveHealth?: (input: {
188204
config: OpenClawConfig;
189205
timeoutMs?: number;

src/commands/status.command.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,14 +177,14 @@ export async function statusCommand(
177177
},
178178
async () => await resolveStatusSecurityAudit(input),
179179
),
180-
resolveUsage: async (timeoutMs) =>
180+
resolveUsage: async (input) =>
181181
await withProgress(
182182
{
183183
label: "Fetching usage snapshot…",
184184
indeterminate: true,
185185
enabled: opts.json !== true,
186186
},
187-
async () => await resolveStatusUsageSummary(timeoutMs),
187+
async () => await resolveStatusUsageSummary(input),
188188
),
189189
resolveHealth: async (input) =>
190190
await withProgress(

src/commands/status.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,10 @@ vi.mock("./status-runtime-shared.ts", () => ({
788788
),
789789
}));
790790

791+
import {
792+
resolveStatusRuntimeSnapshot,
793+
resolveStatusUsageSummary,
794+
} from "./status-runtime-shared.ts";
791795
import { resolvePairingRecoveryContext, statusCommand } from "./status.command.js";
792796

793797
const runtime = {
@@ -1020,6 +1024,37 @@ describe("statusCommand", () => {
10201024
);
10211025
});
10221026

1027+
it("scopes usage resolution to the scanned config", async () => {
1028+
const snapshotMock = resolveStatusRuntimeSnapshot as Mock;
1029+
const usageMock = resolveStatusUsageSummary as Mock;
1030+
snapshotMock.mockClear();
1031+
usageMock.mockClear();
1032+
1033+
await statusCommand({ usage: true, timeoutMs: 1234 }, runtime as never);
1034+
1035+
const params = snapshotMock.mock.calls.at(-1)?.[0] as
1036+
| {
1037+
config: unknown;
1038+
timeoutMs?: number;
1039+
usage?: boolean;
1040+
resolveUsage?: (input: { config: unknown; timeoutMs?: number }) => Promise<unknown>;
1041+
}
1042+
| undefined;
1043+
expect(params).toBeDefined();
1044+
expect(params).toMatchObject({ usage: true, timeoutMs: 1234 });
1045+
if (!params?.resolveUsage) {
1046+
throw new Error("missing status usage resolver");
1047+
}
1048+
await params.resolveUsage({
1049+
timeoutMs: 1234,
1050+
config: params.config,
1051+
});
1052+
expect(usageMock).toHaveBeenCalledWith({
1053+
timeoutMs: 1234,
1054+
config: params?.config,
1055+
});
1056+
});
1057+
10231058
it("keeps default text status off the security audit path", async () => {
10241059
await statusCommand({}, runtime as never);
10251060

src/status/status-text.ts

Lines changed: 15 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ async function resolveStatusHarnessId(params: {
152152
}
153153
}
154154

155-
function resolveStatusAuthProvider(params: {
155+
function resolveStatusRuntimeProvider(params: {
156156
provider: string;
157157
effectiveHarness?: string;
158158
}): string {
@@ -167,18 +167,6 @@ function resolveStatusAuthProvider(params: {
167167
return params.provider;
168168
}
169169

170-
function resolveStatusUsageProvider(params: {
171-
provider: string;
172-
effectiveHarness?: string;
173-
}): string {
174-
const harness = normalizeOptionalLowercaseString(params.effectiveHarness);
175-
const provider = normalizeOptionalLowercaseString(params.provider);
176-
if (harness === "codex" && provider === "openai") {
177-
return "openai-codex";
178-
}
179-
return params.provider;
180-
}
181-
182170
function formatAgentTaskCountsLine(agentId: string): string | undefined {
183171
const snapshot = buildTaskStatusSnapshot(listTasksForAgentIdForStatus(agentId));
184172
if (snapshot.totalCount === 0) {
@@ -241,13 +229,19 @@ export async function buildStatusText(params: BuildStatusTextParams): Promise<st
241229
sessionKey,
242230
sessionEntry,
243231
}));
232+
const selectedStatusProvider = resolveStatusRuntimeProvider({
233+
provider,
234+
effectiveHarness,
235+
});
236+
const activeProvider = modelRefs.active.provider || provider;
237+
const activeStatusProvider = resolveStatusRuntimeProvider({
238+
provider: activeProvider,
239+
effectiveHarness,
240+
});
244241
const selectedModelAuth = Object.hasOwn(params, "modelAuthOverride")
245242
? params.modelAuthOverride
246243
: resolveModelAuthLabel({
247-
provider: resolveStatusAuthProvider({
248-
provider,
249-
effectiveHarness,
250-
}),
244+
provider: selectedStatusProvider,
251245
cfg,
252246
sessionEntry,
253247
agentDir: statusAgentDir,
@@ -258,29 +252,17 @@ export async function buildStatusText(params: BuildStatusTextParams): Promise<st
258252
? params.activeModelAuthOverride
259253
: modelRefs.activeDiffers
260254
? resolveModelAuthLabel({
261-
provider: resolveStatusAuthProvider({
262-
provider: modelRefs.active.provider,
263-
effectiveHarness,
264-
}),
255+
provider: activeStatusProvider,
265256
cfg,
266257
sessionEntry,
267258
agentDir: statusAgentDir,
268259
workspaceDir: statusWorkspaceDir,
269260
includeExternalProfiles: false,
270261
})
271262
: selectedModelAuth;
272-
const usageStatusProvider = resolveStatusUsageProvider({
273-
provider: modelRefs.active.provider || provider,
274-
effectiveHarness,
275-
});
276263
const usageAuthLabel = modelRefs.activeDiffers ? activeModelAuth : selectedModelAuth;
277-
const currentUsageProvider = (() => {
278-
try {
279-
return resolveUsageProviderId(usageStatusProvider);
280-
} catch {
281-
return undefined;
282-
}
283-
})();
264+
const currentUsageProvider =
265+
resolveUsageProviderId(activeStatusProvider) ?? resolveUsageProviderId(activeProvider);
284266
let usageLine: string | null = null;
285267
if (
286268
currentUsageProvider &&
@@ -376,13 +358,9 @@ export async function buildStatusText(params: BuildStatusTextParams): Promise<st
376358
const explicitThinkingDefault =
377359
(agentConfig?.thinkingDefault as ThinkLevel | undefined) ??
378360
(agentDefaults.thinkingDefault as ThinkLevel | undefined);
379-
const runtimeContextProvider = resolveStatusAuthProvider({
380-
provider: modelRefs.active.provider || provider,
381-
effectiveHarness,
382-
});
383361
const runtimeContextTokens = resolveStatusRuntimeContextTokens({
384362
cfg,
385-
provider: runtimeContextProvider,
363+
provider: activeStatusProvider,
386364
model: modelRefs.active.model || model,
387365
});
388366
return buildStatusMessage({

0 commit comments

Comments
 (0)