Skip to content

Commit ebb5b68

Browse files
committed
Wait for goal turn completion before resolving
1 parent a7e4808 commit ebb5b68

2 files changed

Lines changed: 153 additions & 124 deletions

File tree

src/CodexAppServerClient.ts

Lines changed: 1 addition & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,6 @@ export type McpStartupResult = {
8686
cancelled: Array<string>;
8787
};
8888

89-
type ServerNotificationItemType = Extract<
90-
ServerNotification,
91-
{ params: { item: { type: unknown } } }
92-
>["params"]["item"]["type"];
93-
9489
const CommandExecutionApprovalRequest = new RequestType<
9590
CommandExecutionRequestApprovalParams,
9691
CommandExecutionRequestApprovalResponse,
@@ -116,45 +111,6 @@ const McpServerElicitationRequest = new RequestType<
116111
>('mcpServer/elicitation/request');
117112

118113
const GOAL_RUNTIME_EFFECTS_GRACE_MS = 1_000;
119-
const VISIBLE_TURN_ACTIVITY_METHODS: ReadonlySet<ServerNotification["method"]> = new Set([
120-
"error",
121-
"turn/plan/updated",
122-
"item/autoApprovalReview/started",
123-
"item/autoApprovalReview/completed",
124-
"item/agentMessage/delta",
125-
"item/commandExecution/outputDelta",
126-
"item/commandExecution/terminalInteraction",
127-
"item/mcpToolCall/progress",
128-
"item/reasoning/summaryTextDelta",
129-
"item/reasoning/summaryPartAdded",
130-
"item/reasoning/textDelta",
131-
"model/rerouted",
132-
"warning",
133-
"configWarning",
134-
"fuzzyFileSearch/sessionUpdated",
135-
"fuzzyFileSearch/sessionCompleted",
136-
]);
137-
const VISIBLE_STARTED_ITEM_TYPES: ReadonlySet<ServerNotificationItemType> = new Set([
138-
"fileChange",
139-
"commandExecution",
140-
"mcpToolCall",
141-
"dynamicToolCall",
142-
"webSearch",
143-
"imageView",
144-
"imageGeneration",
145-
"collabAgentToolCall",
146-
]);
147-
const VISIBLE_COMPLETED_ITEM_TYPES: ReadonlySet<ServerNotificationItemType> = new Set([
148-
"fileChange",
149-
"commandExecution",
150-
"mcpToolCall",
151-
"dynamicToolCall",
152-
"webSearch",
153-
"imageView",
154-
"imageGeneration",
155-
"collabAgentToolCall",
156-
"contextCompaction",
157-
]);
158114

159115
/**
160116
* A type-safe client over the Codex App Server's JSON-RPC API.
@@ -171,7 +127,6 @@ export class CodexAppServerClient {
171127
private readonly pendingCompactionCompletionResolvers = new Map<string, Set<(event: CompactionCompletedNotification) => void>>();
172128
private readonly turnCompletionCaptures = new Map<string, Set<(event: TurnCompletedNotification) => void>>();
173129
private readonly turnRoutingCaptures = new Map<string, Set<(turnId: string) => void>>();
174-
private readonly turnActivityCaptures = new Map<string, Set<(turnId: string) => void>>();
175130
private readonly threadStatusCaptures = new Map<string, Set<(status: ThreadStatus) => void>>();
176131
private readonly threadGoalUpdateCaptures = new Map<string, Set<(event: ThreadGoalUpdatedNotification) => void>>();
177132
private readonly threadGoalClearedCaptures = new Map<string, Set<() => void>>();
@@ -213,7 +168,6 @@ export class CodexAppServerClient {
213168
if (this.handleStaleTurnNotification(serverNotification, routing)) {
214169
return;
215170
}
216-
this.recordVisibleTurnActivity(serverNotification, routing);
217171
this.notify(serverNotification);
218172
for (const callback of this.codexEventHandlers) {
219173
callback({ eventType: "notification", ...serverNotification });
@@ -357,10 +311,6 @@ export class CodexAppServerClient {
357311
const matchingGoalUpdateHandled = new Promise<null>((resolve) => {
358312
resolveGoalUpdateHandled = () => resolve(null);
359313
});
360-
let resolveGoalTurnActivity: () => void = () => {};
361-
const goalTurnActivity = new Promise<null>((resolve) => {
362-
resolveGoalTurnActivity = () => resolve(null);
363-
});
364314
let goalUpdateHandled = false;
365315
let expectedGoal: ThreadGoal | null = null;
366316
const noGoalTurnStarted = this.createNoGoalTurnStartedPromise(runtimeEffectsGraceMs);
@@ -373,12 +323,6 @@ export class CodexAppServerClient {
373323
onTurnStarted?.(turnId);
374324
resolveGoalTurnStarted(turnId);
375325
});
376-
const releaseActivityCapture = this.captureTurnActivities(params.threadId, (turnId) => {
377-
if (!goalUpdateHandled || goalTurnId !== turnId) {
378-
return;
379-
}
380-
resolveGoalTurnActivity();
381-
});
382326
const releaseGoalUpdateCapture = this.captureThreadGoalUpdates(params.threadId, (event) => {
383327
capturedGoalUpdates.push(event);
384328
if (expectedGoal !== null && goalsMatch(event.goal, expectedGoal)) {
@@ -412,20 +356,17 @@ export class CodexAppServerClient {
412356
releaseStatusCapture();
413357
releaseGoalUpdateCapture();
414358
if (turnId === null) {
415-
releaseActivityCapture();
416359
return null;
417360
}
418361
const earlyCompletion = capturedCompletions.find(event => event.turn.id === turnId);
419362
if (earlyCompletion) {
420-
releaseActivityCapture();
421363
return earlyCompletion;
422364
}
423-
return await Promise.race([goalTurnCompleted, goalTurnActivity]);
365+
return await goalTurnCompleted;
424366
} finally {
425367
noGoalTurnStarted.release();
426368
releaseCompletionCapture();
427369
releaseRoutingCapture();
428-
releaseActivityCapture();
429370
releaseStatusCapture();
430371
releaseGoalUpdateCapture();
431372
}
@@ -779,22 +720,6 @@ export class CodexAppServerClient {
779720
}
780721
}
781722

782-
private recordVisibleTurnActivity(
783-
notification: ServerNotification,
784-
routing: { threadId: string | null, turnId: string | null },
785-
): void {
786-
if (routing.threadId === null || routing.turnId === null || !isVisibleTurnActivityNotification(notification)) {
787-
return;
788-
}
789-
const captures = this.turnActivityCaptures.get(routing.threadId);
790-
if (!captures) {
791-
return;
792-
}
793-
for (const capture of captures) {
794-
capture(routing.turnId);
795-
}
796-
}
797-
798723
private handleStaleTurnNotification(
799724
notification: ServerNotification,
800725
routing: { threadId: string | null, turnId: string | null },
@@ -873,23 +798,6 @@ export class CodexAppServerClient {
873798
};
874799
}
875800

876-
private captureTurnActivities(threadId: string, capture: (turnId: string) => void): () => void {
877-
const captures = this.turnActivityCaptures.get(threadId) ?? new Set<(turnId: string) => void>();
878-
captures.add(capture);
879-
this.turnActivityCaptures.set(threadId, captures);
880-
let released = false;
881-
return () => {
882-
if (released) {
883-
return;
884-
}
885-
released = true;
886-
captures.delete(capture);
887-
if (captures.size === 0) {
888-
this.turnActivityCaptures.delete(threadId);
889-
}
890-
};
891-
}
892-
893801
private captureThreadStatuses(threadId: string, capture: (status: ThreadStatus) => void): () => void {
894802
const captures = this.threadStatusCaptures.get(threadId) ?? new Set<(status: ThreadStatus) => void>();
895803
captures.add(capture);
@@ -1081,31 +989,6 @@ function goalsMatch(left: ThreadGoal, right: ThreadGoal): boolean {
1081989
&& left.updatedAt === right.updatedAt;
1082990
}
1083991

1084-
function isVisibleTurnActivityNotification(notification: ServerNotification): boolean {
1085-
if (notification.method === "item/started") {
1086-
return isVisibleStartedItemType(notification.params.item.type);
1087-
}
1088-
if (notification.method === "item/completed") {
1089-
const item = notification.params.item;
1090-
if (item.type === "reasoning") {
1091-
return item.summary.length > 0 || item.content.length > 0;
1092-
}
1093-
if (item.type === "exitedReviewMode") {
1094-
return item.review.trim().length > 0;
1095-
}
1096-
return isVisibleCompletedItemType(item.type);
1097-
}
1098-
return VISIBLE_TURN_ACTIVITY_METHODS.has(notification.method);
1099-
}
1100-
1101-
function isVisibleStartedItemType(itemType: ServerNotificationItemType): boolean {
1102-
return VISIBLE_STARTED_ITEM_TYPES.has(itemType);
1103-
}
1104-
1105-
function isVisibleCompletedItemType(itemType: ServerNotificationItemType): boolean {
1106-
return VISIBLE_COMPLETED_ITEM_TYPES.has(itemType);
1107-
}
1108-
1109992
function extractThreadId(notification: ServerNotification): string | null {
1110993
const params = notification.params as { threadId?: unknown } | undefined;
1111994
if (params && typeof params.threadId === "string") {

0 commit comments

Comments
 (0)