mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:00:43 +00:00
fix(codex): flush pending steering on completion
This commit is contained in:
@@ -34,6 +34,7 @@ Docs: https://docs.openclaw.ai
|
||||
- CLI/status: resolve read-only channel setup runtime fallback from the packaged OpenClaw dist root, so `status --all`, `status --deep`, channel, and doctor paths do not crash when an external channel plugin needs setup metadata. Fixes #74693. Thanks @giangthb.
|
||||
- Google Meet: block managed Chrome intro/test speech until browser health proves the participant is in-call, and expose `speechReady` diagnostics so login, admission, permission, and audio-bridge blockers no longer look like successful speech. Refs #72478. Thanks @DougButdorf.
|
||||
- Slack/commands: keep native command argument menus on select controls for encoded choice values up to Slack's option limit and truncate fallback button labels to Slack's button-text limit, so long valid choices no longer render invalid Slack blocks. Thanks @slackapi.
|
||||
- Agents/Codex: flush accepted debounced steering messages before normal app-server turn cleanup, so inbound follow-ups acknowledged as queued are not dropped when the turn completes before the debounce fires. Thanks @vincentkoc.
|
||||
- CLI/update: scope packaged Node compile caches by OpenClaw version and install metadata, so global installs no longer reuse stale compiled chunks after package updates. Thanks @pashpashpash.
|
||||
- Channels/Voice call: keep pre-auth webhook in-flight limiting active when socket remote address metadata is missing, so slow-body requests from stripped-IP proxy paths still share the fallback bucket. (#74453) Thanks @davidangularme.
|
||||
- Plugin SDK/testing: lazy-load TypeScript from the plugin test-contract runtime and add release checks for critical SDK contract entrypoint imports and bundle size, so published packages fail preflight before shipping ESM-incompatible or oversized contract helpers. Thanks @vincentkoc.
|
||||
|
||||
@@ -1089,6 +1089,31 @@ describe("runCodexAppServerAttempt", () => {
|
||||
await run;
|
||||
});
|
||||
|
||||
it("flushes pending default queued steering during normal turn cleanup", async () => {
|
||||
const { requests, waitForMethod, completeTurn } = createStartedThreadHarness();
|
||||
|
||||
const run = runCodexAppServerAttempt(
|
||||
createParams(path.join(tempDir, "session.jsonl"), path.join(tempDir, "workspace")),
|
||||
);
|
||||
await waitForMethod("turn/start");
|
||||
|
||||
expect(queueAgentHarnessMessage("session-1", "late steer", { debounceMs: 30_000 })).toBe(true);
|
||||
|
||||
await completeTurn({ threadId: "thread-1", turnId: "turn-1" });
|
||||
await run;
|
||||
|
||||
expect(requests.filter((entry) => entry.method === "turn/steer")).toEqual([
|
||||
{
|
||||
method: "turn/steer",
|
||||
params: {
|
||||
threadId: "thread-1",
|
||||
expectedTurnId: "turn-1",
|
||||
input: [{ type: "text", text: "late steer", text_elements: [] }],
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps legacy queue steering as separate turn/steer requests", async () => {
|
||||
const { requests, waitForMethod, completeTurn } = createStartedThreadHarness();
|
||||
|
||||
|
||||
@@ -193,6 +193,9 @@ function createCodexSteeringQueue(params: {
|
||||
void flushBatch();
|
||||
}, debounceMs);
|
||||
},
|
||||
async flushPending() {
|
||||
await flushBatch();
|
||||
},
|
||||
cancel() {
|
||||
clearBatchTimer();
|
||||
batchedTexts = [];
|
||||
@@ -453,6 +456,7 @@ export async function runCodexAppServerAttempt(
|
||||
let turnId: string | undefined;
|
||||
const pendingNotifications: CodexServerNotification[] = [];
|
||||
let userInputBridge: ReturnType<typeof createCodexUserInputBridge> | undefined;
|
||||
let steeringQueue: ReturnType<typeof createCodexSteeringQueue> | undefined;
|
||||
let completed = false;
|
||||
let timedOut = false;
|
||||
let turnCompletionIdleTimedOut = false;
|
||||
@@ -586,6 +590,9 @@ export async function runCodexAppServerAttempt(
|
||||
});
|
||||
} finally {
|
||||
if (isTurnCompletion) {
|
||||
if (!timedOut && !runAbortController.signal.aborted) {
|
||||
await steeringQueue?.flushPending();
|
||||
}
|
||||
completed = true;
|
||||
clearTurnCompletionIdleTimer();
|
||||
resolveCompletion?.();
|
||||
@@ -814,17 +821,18 @@ export async function runCodexAppServerAttempt(
|
||||
});
|
||||
}
|
||||
|
||||
const steeringQueue = createCodexSteeringQueue({
|
||||
const activeSteeringQueue = createCodexSteeringQueue({
|
||||
client,
|
||||
threadId: thread.threadId,
|
||||
turnId: activeTurnId,
|
||||
answerPendingUserInput: (text) => userInputBridge?.handleQueuedMessage(text) ?? false,
|
||||
signal: runAbortController.signal,
|
||||
});
|
||||
steeringQueue = activeSteeringQueue;
|
||||
const handle = {
|
||||
kind: "embedded" as const,
|
||||
queueMessage: async (text: string, options?: CodexSteeringQueueOptions) =>
|
||||
steeringQueue.queue(text, options),
|
||||
activeSteeringQueue.queue(text, options),
|
||||
isStreaming: () => !completed,
|
||||
isCompacting: () => projector?.isCompacting() ?? false,
|
||||
cancel: () => runAbortController.abort("cancelled"),
|
||||
@@ -991,6 +999,9 @@ export async function runCodexAppServerAttempt(
|
||||
await trajectoryRecorder?.flush();
|
||||
},
|
||||
});
|
||||
if (!timedOut && !runAbortController.signal.aborted) {
|
||||
await steeringQueue?.flushPending();
|
||||
}
|
||||
userInputBridge?.cancelPending();
|
||||
clearTimeout(timeout);
|
||||
clearTurnCompletionIdleTimer();
|
||||
@@ -999,7 +1010,7 @@ export async function runCodexAppServerAttempt(
|
||||
nativeHookRelay?.unregister();
|
||||
runAbortController.signal.removeEventListener("abort", abortListener);
|
||||
params.abortSignal?.removeEventListener("abort", abortFromUpstream);
|
||||
steeringQueue.cancel();
|
||||
steeringQueue?.cancel();
|
||||
clearActiveEmbeddedRun(params.sessionId, handle, params.sessionKey);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user