Skip to content

Commit 211b7d7

Browse files
feat: Add /goal support (#240)
* feat: Add /goal support * Suppress multiple identical notifications * Wait on turns * Wait for goal turn * Add newlines after notifications * Remove unsupported edit subcommand * Handle goal commands without continuation turns * Separate goal status updates from prior text * Potential fix for pull request finding 'Unused variable, import, function or class' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * Interrupt late-started goal commands on cancel * Suppress stale turn notifications after routing them * Wait for thread idle when no goal turn starts * Handle cancellation before goal turn routing * Wait for goal updates before finishing goal set * Add grace periods for goal turn routing * Wait for goal notifications in ACP client * Wait for goal turn completion before resolving * Potential fix for pull request finding 'Unused variable, import, function or class' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
1 parent 50c6adf commit 211b7d7

14 files changed

Lines changed: 1787 additions & 36 deletions

src/CodexAcpClient.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import type {
3333
SkillsListResponse,
3434
SandboxPolicy,
3535
Thread,
36+
ThreadGoalStatus,
3637
ThreadSourceKind,
3738
TurnCompletedNotification,
3839
UserInput,
@@ -334,6 +335,39 @@ export class CodexAcpClient {
334335
await this.codexClient.runCompact({threadId: sessionId});
335336
}
336337

338+
async setGoal(
339+
sessionId: string,
340+
objective: string,
341+
onTurnStarted?: (turnId: string) => void,
342+
): Promise<TurnCompletedNotification | null> {
343+
return await this.codexClient.runGoalSet({
344+
threadId: sessionId,
345+
objective,
346+
status: "active",
347+
}, onTurnStarted);
348+
}
349+
350+
async setGoalStatus(sessionId: string, status: ThreadGoalStatus): Promise<void> {
351+
await this.codexClient.runGoalSet({
352+
threadId: sessionId,
353+
status,
354+
});
355+
}
356+
357+
async resumeGoal(
358+
sessionId: string,
359+
onTurnStarted?: (turnId: string) => void,
360+
): Promise<TurnCompletedNotification | null> {
361+
return await this.codexClient.runGoalSet({
362+
threadId: sessionId,
363+
status: "active",
364+
}, onTurnStarted);
365+
}
366+
367+
async clearGoal(sessionId: string): Promise<void> {
368+
await this.codexClient.runGoalClear({threadId: sessionId});
369+
}
370+
337371
async awaitMcpServerStartup(serverNames: Array<string>, afterVersion: number): Promise<McpStartupResult> {
338372
return await this.codexClient.awaitMcpServerStartup(serverNames, afterVersion);
339373
}

src/CodexAcpServer.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {
1414
ReasoningEffortOption,
1515
Thread,
1616
ThreadItem,
17+
TurnCompletedNotification,
1718
UserInput
1819
} from "./app-server/v2";
1920
import type {RateLimitsMap} from "./RateLimitsMap";
@@ -66,6 +67,12 @@ import packageJson from "../package.json";
6667
import {isJetBrains2026_1Client} from "./JBUtils";
6768
import {resolveTerminalOutputMode, type TerminalOutputMode} from "./TerminalOutputMode";
6869

70+
export interface ThreadGoalSnapshot {
71+
objective: string;
72+
status: string;
73+
tokenBudget: number | null;
74+
}
75+
6976
export interface SessionState {
7077
sessionId: string,
7178
currentModelId: string,
@@ -87,6 +94,7 @@ export interface SessionState {
8794
currentModelSupportsFast: boolean;
8895
sessionMcpServers?: Array<string>;
8996
terminalOutputMode: TerminalOutputMode;
97+
currentGoal?: ThreadGoalSnapshot | null;
9098
}
9199

92100
interface ActiveAuthState {
@@ -1398,6 +1406,13 @@ export class CodexAcpServer {
13981406
sessionState.lastTokenUsage = null;
13991407
const activePrompt = this.trackActivePrompt(params.sessionId);
14001408
let pendingTurnStart: PendingTurnStart | null = null;
1409+
const ensurePendingTurnStart = (): PendingTurnStart => {
1410+
if (pendingTurnStart === null) {
1411+
pendingTurnStart = this.createPendingTurnStart();
1412+
this.pendingTurnStarts.set(params.sessionId, pendingTurnStart);
1413+
}
1414+
return pendingTurnStart;
1415+
};
14011416
const disposePromptRequestCancellation = this.observePromptRequestCancellation(signal, sessionState, activePrompt);
14021417

14031418
try {
@@ -1417,6 +1432,9 @@ export class CodexAcpServer {
14171432
}
14181433

14191434
const commandPromise = this.availableCommands.tryHandleCommand(params.prompt, sessionState, {
1435+
onTurnStartPending: () => {
1436+
ensurePendingTurnStart();
1437+
},
14201438
onTurnStarted: (turnId, threadId) => {
14211439
const turn = {threadId, turnId};
14221440
activePrompt.currentTurn = turn;
@@ -1425,6 +1443,7 @@ export class CodexAcpServer {
14251443
return;
14261444
}
14271445
sessionState.currentTurnId = turnId;
1446+
pendingTurnStart?.resolve(turnId);
14281447
},
14291448
});
14301449
void commandPromise.catch((err) => {
@@ -1483,8 +1502,7 @@ export class CodexAcpServer {
14831502
sessionState.fastModeEnabled,
14841503
sessionState.currentModelSupportsFast,
14851504
);
1486-
pendingTurnStart = this.createPendingTurnStart();
1487-
this.pendingTurnStarts.set(params.sessionId, pendingTurnStart);
1505+
ensurePendingTurnStart();
14881506
const sendPromptPromise = this.runWithProcessCheck(
14891507
() => this.codexAcpClient.sendPrompt(
14901508
params,
@@ -1546,10 +1564,11 @@ export class CodexAcpServer {
15461564
logger.log("Prompt completed", {sessionId: params.sessionId});
15471565
disposePromptRequestCancellation();
15481566
sessionState.currentTurnId = null;
1549-
if (pendingTurnStart !== null && this.pendingTurnStarts.get(params.sessionId) === pendingTurnStart) {
1567+
const registeredPendingTurnStart = this.pendingTurnStarts.get(params.sessionId);
1568+
if (registeredPendingTurnStart !== undefined) {
15501569
this.pendingTurnStarts.delete(params.sessionId);
1570+
registeredPendingTurnStart.resolve(null);
15511571
}
1552-
pendingTurnStart?.resolve(null);
15531572
activePrompt.complete();
15541573
}
15551574
}

0 commit comments

Comments
 (0)