Skip to content

Commit bae159c

Browse files
authored
fix: Emit plan contents in plan mode (#326)
Closes #307
1 parent a2a63ee commit bae159c

8 files changed

Lines changed: 313 additions & 12 deletions

src/CodexAcpServer.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1469,7 +1469,7 @@ export class CodexAcpServer {
14691469
case "contextCompaction":
14701470
return [createCompletedContextCompactionUpdate(item)];
14711471
case "plan":
1472-
return [this.createPlanUpdate(item)];
1472+
return [this.createPlanMessageUpdate(item)];
14731473
}
14741474
}
14751475

@@ -1520,16 +1520,14 @@ export class CodexAcpServer {
15201520
};
15211521
}
15221522

1523-
private createPlanUpdate(
1523+
private createPlanMessageUpdate(
15241524
item: ThreadItem & { type: "plan" }
15251525
): UpdateSessionEvent {
1526-
return {
1527-
sessionUpdate: "agent_message_chunk",
1528-
content: {
1529-
type: "text",
1530-
text: `Plan:\n${item.text}`,
1531-
},
1532-
};
1526+
return createAgentTextMessageChunk(
1527+
item.text,
1528+
item.id,
1529+
createCodexMessagePhaseMeta("final_answer"),
1530+
);
15331531
}
15341532

15351533
private userInputToContentBlocks(input: UserInput): acp.ContentBlock[] {

src/CodexEventHandler.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type {
1919
ItemStartedNotification,
2020
ThreadItem,
2121
ModelReroutedNotification,
22+
PlanDeltaNotification,
2223
ReasoningSummaryPartAddedNotification,
2324
ReasoningSummaryTextDeltaNotification,
2425
ReasoningTextDeltaNotification,
@@ -76,6 +77,7 @@ export class CodexEventHandler {
7677
private readonly activeGuardianApprovalReviews = new Set<string>();
7778
private readonly activeImageGenerationItems = new Set<string>();
7879
private readonly emittedImageViewItems = new Set<string>();
80+
private readonly planDeltaTextByItemId = new Map<string, string>();
7981
private readonly seenReasoningDeltaItemIds = new Set<string>();
8082
private readonly terminalCommandIds = new Set<string>();
8183
private readonly terminalCommandOutputIds = new Set<string>();
@@ -110,6 +112,8 @@ export class CodexEventHandler {
110112
switch (notification.method) {
111113
case "item/agentMessage/delta":
112114
return await this.createTextEvent(notification.params);
115+
case "item/plan/delta":
116+
return this.createPlanDeltaEvent(notification.params);
113117
case "item/started":
114118
return await this.createItemEvent(notification.params);
115119
case "item/completed":
@@ -223,7 +227,6 @@ export class CodexEventHandler {
223227
case "rawResponseItem/completed":
224228
case "rawResponse/completed":
225229
case "thread/started":
226-
case "item/plan/delta":
227230
case "remoteControl/status/changed":
228231
case "app/list/updated":
229232
case "thread/settings/updated":
@@ -293,6 +296,15 @@ export class CodexEventHandler {
293296
return this.createAgentThoughtEvent(event.delta, event.itemId);
294297
}
295298

299+
private createPlanDeltaEvent(event: PlanDeltaNotification): UpdateSessionEvent | null {
300+
if (event.delta.length === 0) {
301+
return null;
302+
}
303+
const text = this.planDeltaTextByItemId.get(event.itemId) ?? "";
304+
this.planDeltaTextByItemId.set(event.itemId, text + event.delta);
305+
return null;
306+
}
307+
296308
private createReasoningSectionBreakEvent(event: ReasoningSummaryPartAddedNotification): UpdateSessionEvent {
297309
this.seenReasoningDeltaItemIds.add(event.itemId);
298310
return this.createAgentThoughtEvent("\n\n", event.itemId);
@@ -389,6 +401,11 @@ export class CodexEventHandler {
389401
case "agentMessage":
390402
this.rememberAgentMessagePhase(event.item);
391403
return null;
404+
case "plan": {
405+
const deltaText = this.planDeltaTextByItemId.get(event.item.id) ?? "";
406+
this.planDeltaTextByItemId.delete(event.item.id);
407+
return this.createCompletedPlanEvent(event.item, deltaText);
408+
}
392409
case "exitedReviewMode":
393410
return this.createExitedReviewModeEvent(event.item);
394411
case "contextCompaction":
@@ -404,7 +421,6 @@ export class CodexEventHandler {
404421
case "userMessage":
405422
case "hookPrompt":
406423
case "enteredReviewMode":
407-
case "plan":
408424
return null;
409425

410426
}
@@ -423,6 +439,25 @@ export class CodexEventHandler {
423439
return this.createAgentThoughtEvent(text, item.id);
424440
}
425441

442+
private createCompletedPlanEvent(
443+
item: ThreadItem & { type: "plan" },
444+
deltaText: string,
445+
): UpdateSessionEvent | null {
446+
const text = item.text.length > 0 ? item.text : deltaText;
447+
if (text.length === 0) {
448+
return null;
449+
}
450+
return this.createPlanTextEvent(text, item.id);
451+
}
452+
453+
private createPlanTextEvent(text: string, messageId: string): UpdateSessionEvent {
454+
return createAgentTextMessageChunk(
455+
text,
456+
messageId,
457+
createCodexMessagePhaseMeta("final_answer"),
458+
);
459+
}
460+
426461
private createExitedReviewModeEvent(item: ThreadItem & { type: "exitedReviewMode" }): UpdateSessionEvent | null {
427462
const text = item.review.trim();
428463
if (text.length === 0) {

src/__tests__/CodexACPAgent/data/load-session-response-item-history-fallback.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,15 @@
151151
"sessionId": "session-legacy",
152152
"update": {
153153
"sessionUpdate": "agent_message_chunk",
154+
"messageId": "item-plan-1",
154155
"content": {
155156
"type": "text",
156-
"text": "Plan:\nInspect project files"
157+
"text": "Inspect project files"
158+
},
159+
"_meta": {
160+
"codex": {
161+
"phase": "final_answer"
162+
}
157163
}
158164
}
159165
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"method": "sessionUpdate",
3+
"args": [
4+
{
5+
"sessionId": "test-session-id",
6+
"update": {
7+
"sessionUpdate": "plan",
8+
"entries": [
9+
{
10+
"status": "completed",
11+
"content": "Add the event mapping",
12+
"priority": "medium"
13+
},
14+
{
15+
"status": "in_progress",
16+
"content": "Verify it in Zed",
17+
"priority": "medium"
18+
}
19+
]
20+
}
21+
}
22+
]
23+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"method": "sessionUpdate",
3+
"args": [
4+
{
5+
"sessionId": "test-session-id",
6+
"update": {
7+
"sessionUpdate": "agent_message_chunk",
8+
"messageId": "plan-2",
9+
"content": {
10+
"type": "text",
11+
"text": "### Fallback plan\n\n1. Use the completed item."
12+
},
13+
"_meta": {
14+
"codex": {
15+
"phase": "final_answer"
16+
}
17+
}
18+
}
19+
}
20+
]
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"method": "sessionUpdate",
3+
"args": [
4+
{
5+
"sessionId": "test-session-id",
6+
"update": {
7+
"sessionUpdate": "agent_message_chunk",
8+
"messageId": "plan-2",
9+
"content": {
10+
"type": "text",
11+
"text": "### Buffered plan\n\n1. Use the buffered fallback."
12+
},
13+
"_meta": {
14+
"codex": {
15+
"phase": "final_answer"
16+
}
17+
}
18+
}
19+
}
20+
]
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"method": "sessionUpdate",
3+
"args": [
4+
{
5+
"sessionId": "test-session-id",
6+
"update": {
7+
"sessionUpdate": "agent_message_chunk",
8+
"messageId": "plan-1",
9+
"content": {
10+
"type": "text",
11+
"text": "Completed text should not duplicate the streamed plan."
12+
},
13+
"_meta": {
14+
"codex": {
15+
"phase": "final_answer"
16+
}
17+
}
18+
}
19+
}
20+
]
21+
}

0 commit comments

Comments
 (0)