mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 16:14:44 +00:00
fix(codex): yield app-server notification projection (#82333)
* fix(codex): yield app-server notification projection * docs(changelog): note codex notification yield fix
This commit is contained in:
@@ -44,6 +44,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 app-server: yield projector work to the event loop between embedded-run notifications while preserving pre-turn rate-limit capture, reducing gateway stalls from account and MCP status notifications. Fixes #81936. (#82333) Thanks @joshavant.
|
||||
- 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.
|
||||
|
||||
@@ -39,7 +39,11 @@ import {
|
||||
resolveCodexPluginAppCacheEndpoint,
|
||||
} from "./plugin-app-cache-key.js";
|
||||
import type { CodexServerNotification } from "./protocol.js";
|
||||
import { rememberCodexRateLimits, resetCodexRateLimitCacheForTests } from "./rate-limit-cache.js";
|
||||
import {
|
||||
readRecentCodexRateLimits,
|
||||
rememberCodexRateLimits,
|
||||
resetCodexRateLimitCacheForTests,
|
||||
} from "./rate-limit-cache.js";
|
||||
import {
|
||||
runCodexAppServerAttempt as runCodexAppServerAttemptImpl,
|
||||
__testing,
|
||||
@@ -1982,6 +1986,29 @@ describe("runCodexAppServerAttempt", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("yields a macrotask before processing queued app-server notifications", async () => {
|
||||
const harness = createStartedThreadHarness();
|
||||
const params = createParams(
|
||||
path.join(tempDir, "session.jsonl"),
|
||||
path.join(tempDir, "workspace"),
|
||||
);
|
||||
params.timeoutMs = 1_000;
|
||||
|
||||
const run = runCodexAppServerAttempt(params);
|
||||
await harness.waitForMethod("turn/start");
|
||||
|
||||
const notification = rateLimitsUpdated(Date.now() + 60_000);
|
||||
const processing = harness.notify(notification);
|
||||
await Promise.resolve();
|
||||
|
||||
expect(readRecentCodexRateLimits()).toBeUndefined();
|
||||
await processing;
|
||||
expect(readRecentCodexRateLimits()).toEqual(notification.params);
|
||||
|
||||
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
|
||||
await expect(run).resolves.toMatchObject({ aborted: false, timedOut: false });
|
||||
});
|
||||
|
||||
it("releases the session when a completed agent message item goes quiet", async () => {
|
||||
let notify: (notification: CodexServerNotification) => Promise<void> = async () => undefined;
|
||||
const request = vi.fn(async (method: string) => {
|
||||
@@ -3250,7 +3277,7 @@ describe("runCodexAppServerAttempt", () => {
|
||||
if (!harnessRef.current) {
|
||||
throw new Error("Expected Codex app-server harness to be initialized");
|
||||
}
|
||||
await harnessRef.current.notify(rateLimitsUpdated(resetsAt));
|
||||
void harnessRef.current.notify(rateLimitsUpdated(resetsAt));
|
||||
throw Object.assign(new Error("You've reached your usage limit."), {
|
||||
data: { codexErrorInfo: "usageLimitExceeded" },
|
||||
});
|
||||
|
||||
@@ -1319,6 +1319,7 @@ export async function runCodexAppServerAttempt(
|
||||
isCodexTurnAbortMarkerNotification(notification, { currentPromptText: promptBuild.prompt });
|
||||
const isTurnTerminal = isTurnCompletion || isTurnAbortMarker;
|
||||
try {
|
||||
await waitForCodexNotificationDispatchTurn();
|
||||
await projector.handleNotification(notification);
|
||||
} catch (error) {
|
||||
embeddedAgentLog.debug("codex app-server projector notification threw", {
|
||||
@@ -1342,6 +1343,11 @@ export async function runCodexAppServerAttempt(
|
||||
}
|
||||
};
|
||||
const enqueueNotification = (notification: CodexServerNotification): Promise<void> => {
|
||||
if (!projector || !turnId) {
|
||||
userInputBridge?.handleNotification(notification);
|
||||
pendingNotifications.push(notification);
|
||||
return Promise.resolve();
|
||||
}
|
||||
notificationQueue = notificationQueue.then(
|
||||
() => handleNotification(notification),
|
||||
() => handleNotification(notification),
|
||||
@@ -3255,6 +3261,12 @@ function prependCurrentTurnContext(
|
||||
return text ? [text, prompt].filter(Boolean).join("\n\n") : prompt;
|
||||
}
|
||||
|
||||
function waitForCodexNotificationDispatchTurn(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setImmediate(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
function handleApprovalRequest(params: {
|
||||
method: string;
|
||||
params: JsonValue | undefined;
|
||||
|
||||
Reference in New Issue
Block a user