mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-14 13:22:37 +00:00
fix(claude): show plan mode when Claude enters it on its own (#6074)
When Claude Code calls its EnterPlanMode tool, it reports the new mode through a system status message with permissionMode "plan". The Claude provider only read the compacting status from those messages, so the session kept the previous mode and never emitted mode_changed: the app, `paseo inspect`, and the stored agent record all kept showing the old mode. The provider now records the mode carried by a status message and emits mode_changed when it differs, sharing the bookkeeping with the init message so the plan-approval resume mode stays the mode Claude left. Closes #6063 Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
This commit is contained in:
@@ -3069,6 +3069,44 @@ describe("ClaudeAgentSession context window usage", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("reports the plan mode Claude enters on its own with EnterPlanMode", async () => {
|
||||
// Claude Code announces a mode it switched to itself as a status message
|
||||
// carrying the new permissionMode, right after the EnterPlanMode tool call.
|
||||
const client = new ClaudeAgentClient({
|
||||
logger,
|
||||
queryFactory: createQueryFactoryForTurns([
|
||||
[
|
||||
{ ...createInitMessage(), permissionMode: "acceptEdits" },
|
||||
{
|
||||
type: "system",
|
||||
subtype: "status",
|
||||
status: null,
|
||||
permissionMode: "plan",
|
||||
session_id: "session-1",
|
||||
},
|
||||
createSuccessResult(),
|
||||
],
|
||||
]),
|
||||
resolveBinary: async () => "/test/claude/bin",
|
||||
});
|
||||
const session = await client.createSession({
|
||||
provider: "claude",
|
||||
cwd: process.cwd(),
|
||||
modeId: "acceptEdits",
|
||||
});
|
||||
|
||||
try {
|
||||
const events = await collectStreamEvents(session, "enter plan mode");
|
||||
|
||||
expect(events).toContainEqual(
|
||||
expect.objectContaining({ type: "mode_changed", currentModeId: "plan" }),
|
||||
);
|
||||
expect(await session.getCurrentMode()).toBe("plan");
|
||||
} finally {
|
||||
await session.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("a compaction abandoned mid-turn does not suppress the next compaction marker", async () => {
|
||||
// The first turn starts compacting and then ends without ever reaching a
|
||||
// compact_boundary, so the marker it opened is never resolved.
|
||||
|
||||
@@ -4321,6 +4321,19 @@ class ClaudeAgentSession implements AgentSession {
|
||||
return;
|
||||
}
|
||||
if (message.subtype === "status") {
|
||||
// Claude Code reports a mode it switched to on its own, such as entering
|
||||
// plan mode with the EnterPlanMode tool, through the status message.
|
||||
if (
|
||||
isPermissionMode(message.permissionMode) &&
|
||||
this.observePermissionMode(message.permissionMode)
|
||||
) {
|
||||
events.push({
|
||||
type: "mode_changed",
|
||||
provider: "claude",
|
||||
currentModeId: this.currentMode,
|
||||
availableModes: this.availableModes,
|
||||
});
|
||||
}
|
||||
const status = toObjectRecord(message)?.status;
|
||||
if (status === "compacting") {
|
||||
this.compacting = true;
|
||||
@@ -4632,10 +4645,7 @@ class ClaudeAgentSession implements AgentSession {
|
||||
notice = this.createClaudeSessionChangedNotice(existingSessionId, newSessionId);
|
||||
}
|
||||
this.availableModes = DEFAULT_MODES;
|
||||
this.currentMode = message.permissionMode;
|
||||
if (this.currentMode !== "plan") {
|
||||
this.planResumeMode = this.currentMode;
|
||||
}
|
||||
this.observePermissionMode(message.permissionMode);
|
||||
this.persistence = null;
|
||||
if (message.model) {
|
||||
const normalizedRuntimeModel = normalizeClaudeRuntimeModelId(message.model);
|
||||
@@ -4654,6 +4664,16 @@ class ClaudeAgentSession implements AgentSession {
|
||||
return { threadStartedSessionId, notice };
|
||||
}
|
||||
|
||||
/** Records a mode Claude Code reports, returning whether it differs from the current one. */
|
||||
private observePermissionMode(mode: PermissionMode): boolean {
|
||||
const changed = this.currentMode !== mode;
|
||||
this.currentMode = mode;
|
||||
if (mode !== "plan") {
|
||||
this.planResumeMode = mode;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
private readMissingResumedConversationError(message: SDKMessage): string | null {
|
||||
if (message.type !== "result" || message.subtype !== "error_during_execution") {
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user