mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-29 12:42:57 +00:00
fix codex stalled runs on idle thread status
This commit is contained in:
@@ -118,4 +118,27 @@ describe("Codex app-server provider", () => {
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test("only synthesizes completion from idle thread status while a run is active", () => {
|
||||
expect(
|
||||
__codexAppServerInternals.shouldSynthesizeTurnCompletionFromThreadStatus({
|
||||
statusType: "idle",
|
||||
hasActiveRun: true,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
__codexAppServerInternals.shouldSynthesizeTurnCompletionFromThreadStatus({
|
||||
statusType: "idle",
|
||||
hasActiveRun: false,
|
||||
}),
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
__codexAppServerInternals.shouldSynthesizeTurnCompletionFromThreadStatus({
|
||||
statusType: "running",
|
||||
hasActiveRun: true,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1195,6 +1195,18 @@ const ThreadStartedNotificationSchema = z
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
const ThreadStatusChangedNotificationSchema = z
|
||||
.object({
|
||||
threadId: z.string().optional(),
|
||||
status: z
|
||||
.object({
|
||||
type: z.string().optional(),
|
||||
})
|
||||
.passthrough()
|
||||
.optional(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
const TurnStartedNotificationSchema = z
|
||||
.object({
|
||||
turn: z.object({ id: z.string() }).passthrough(),
|
||||
@@ -1402,6 +1414,16 @@ const ItemFileChangeOutputDeltaNotificationSchema = z
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
const ItemCommandExecutionOutputDeltaNotificationSchema = z
|
||||
.object({
|
||||
itemId: z.string().optional(),
|
||||
callId: z.string().optional(),
|
||||
stream: z.string().optional(),
|
||||
delta: z.string().optional(),
|
||||
chunk: z.string().optional(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
const CodexEventTurnDiffNotificationSchema = z
|
||||
.object({
|
||||
msg: z
|
||||
@@ -1416,6 +1438,7 @@ const CodexEventTurnDiffNotificationSchema = z
|
||||
|
||||
type ParsedCodexNotification =
|
||||
| { kind: "thread_started"; threadId: string }
|
||||
| { kind: "thread_status_changed"; threadId: string | null; statusType: string | null }
|
||||
| { kind: "turn_started"; turnId: string }
|
||||
| { kind: "turn_completed"; status: string; errorMessage: string | null }
|
||||
| { kind: "plan_updated"; plan: Array<{ step: string | null; status: string | null }> }
|
||||
@@ -1499,6 +1522,25 @@ const CodexNotificationSchema = z.union([
|
||||
params,
|
||||
}),
|
||||
),
|
||||
z
|
||||
.object({
|
||||
method: z.literal("thread/status/changed"),
|
||||
params: ThreadStatusChangedNotificationSchema,
|
||||
})
|
||||
.transform(
|
||||
({ params }): ParsedCodexNotification => ({
|
||||
kind: "thread_status_changed",
|
||||
threadId: params.threadId ?? null,
|
||||
statusType: params.status?.type ?? null,
|
||||
}),
|
||||
),
|
||||
z.object({ method: z.literal("thread/status/changed"), params: z.unknown() }).transform(
|
||||
({ method, params }): ParsedCodexNotification => ({
|
||||
kind: "invalid_payload",
|
||||
method,
|
||||
params,
|
||||
}),
|
||||
),
|
||||
z
|
||||
.object({ method: z.literal("turn/started"), params: TurnStartedNotificationSchema })
|
||||
.transform(
|
||||
@@ -1872,6 +1914,28 @@ const CodexNotificationSchema = z.union([
|
||||
params,
|
||||
}),
|
||||
),
|
||||
z
|
||||
.object({
|
||||
method: z.literal("item/commandExecution/outputDelta"),
|
||||
params: ItemCommandExecutionOutputDeltaNotificationSchema,
|
||||
})
|
||||
.transform(
|
||||
({ params }): ParsedCodexNotification => ({
|
||||
kind: "exec_command_output_delta",
|
||||
callId: params.callId ?? params.itemId ?? null,
|
||||
stream: params.stream ?? null,
|
||||
chunk: params.delta ?? params.chunk ?? null,
|
||||
}),
|
||||
),
|
||||
z
|
||||
.object({ method: z.literal("item/commandExecution/outputDelta"), params: z.unknown() })
|
||||
.transform(
|
||||
({ method, params }): ParsedCodexNotification => ({
|
||||
kind: "invalid_payload",
|
||||
method,
|
||||
params,
|
||||
}),
|
||||
),
|
||||
z
|
||||
.object({
|
||||
method: z.literal("codex/event/turn_diff"),
|
||||
@@ -2035,6 +2099,7 @@ export async function codexAppServerTurnInputFromPrompt(
|
||||
export const __codexAppServerInternals = {
|
||||
mapCodexPatchNotificationToToolCall,
|
||||
shouldBufferCodexEventUntilTurnStarted,
|
||||
shouldSynthesizeTurnCompletionFromThreadStatus,
|
||||
};
|
||||
|
||||
function isTerminalAgentStreamEvent(event: AgentStreamEvent): boolean {
|
||||
@@ -2057,6 +2122,13 @@ function shouldBufferCodexEventUntilTurnStarted(event: AgentStreamEvent): boolea
|
||||
return true;
|
||||
}
|
||||
|
||||
function shouldSynthesizeTurnCompletionFromThreadStatus(input: {
|
||||
statusType: string | null;
|
||||
hasActiveRun: boolean;
|
||||
}): boolean {
|
||||
return input.hasActiveRun && input.statusType === "idle";
|
||||
}
|
||||
|
||||
class CodexAppServerAgentSession implements AgentSession {
|
||||
readonly provider = CODEX_PROVIDER;
|
||||
readonly capabilities = CODEX_APP_SERVER_CAPABILITIES;
|
||||
@@ -2095,6 +2167,7 @@ class CodexAppServerAgentSession implements AgentSession {
|
||||
private warnedInvalidNotificationPayloads = new Set<string>();
|
||||
private warnedIncompleteEditToolCallIds = new Set<string>();
|
||||
private latestUsage: AgentUsage | undefined;
|
||||
private runAwaitingTerminal = false;
|
||||
private connected = false;
|
||||
private collaborationModes: Array<{
|
||||
name: string;
|
||||
@@ -2521,6 +2594,7 @@ class CodexAppServerAgentSession implements AgentSession {
|
||||
}
|
||||
|
||||
await this.client.request("turn/start", params, TURN_START_TIMEOUT_MS);
|
||||
this.runAwaitingTerminal = true;
|
||||
|
||||
let sawTurnStarted = false;
|
||||
for await (const event of queue) {
|
||||
@@ -2542,6 +2616,7 @@ class CodexAppServerAgentSession implements AgentSession {
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.runAwaitingTerminal = false;
|
||||
if (this.eventQueue === queue) {
|
||||
this.eventQueue = null;
|
||||
}
|
||||
@@ -2875,6 +2950,31 @@ class CodexAppServerAgentSession implements AgentSession {
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsed.kind === "thread_status_changed") {
|
||||
if (
|
||||
shouldSynthesizeTurnCompletionFromThreadStatus({
|
||||
statusType: parsed.statusType,
|
||||
hasActiveRun: this.runAwaitingTerminal,
|
||||
})
|
||||
) {
|
||||
this.runAwaitingTerminal = false;
|
||||
this.currentTurnId = null;
|
||||
this.emittedItemStartedIds.clear();
|
||||
this.emittedItemCompletedIds.clear();
|
||||
this.emittedExecCommandStartedCallIds.clear();
|
||||
this.emittedExecCommandCompletedCallIds.clear();
|
||||
this.pendingCommandOutputDeltas.clear();
|
||||
this.pendingFileChangeOutputDeltas.clear();
|
||||
this.warnedIncompleteEditToolCallIds.clear();
|
||||
this.emitEvent({
|
||||
type: "turn_completed",
|
||||
provider: CODEX_PROVIDER,
|
||||
usage: this.latestUsage,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsed.kind === "turn_started") {
|
||||
this.currentTurnId = parsed.turnId;
|
||||
this.emittedItemStartedIds.clear();
|
||||
@@ -2889,6 +2989,8 @@ class CodexAppServerAgentSession implements AgentSession {
|
||||
}
|
||||
|
||||
if (parsed.kind === "turn_completed") {
|
||||
this.runAwaitingTerminal = false;
|
||||
this.currentTurnId = null;
|
||||
if (parsed.status === "failed") {
|
||||
this.emitEvent({
|
||||
type: "turn_failed",
|
||||
|
||||
Reference in New Issue
Block a user