diff --git a/src/claude/runClaude.ts b/src/claude/runClaude.ts index dc5d1982..dc46b86a 100644 --- a/src/claude/runClaude.ts +++ b/src/claude/runClaude.ts @@ -271,7 +271,7 @@ export async function runClaude(credentials: Credentials, options: StartOptions disallowedTools: messageDisallowedTools }; messageQueue.pushIsolateAndClear(specialCommand.originalMessage || message.content.text, enhancedMode); - logger.debugLargeJson('[start] /compact command pushed to queue:', message); + logger.debugLargeJson('[start] /clear command pushed to queue:', message); return; } @@ -303,7 +303,7 @@ export async function runClaude(credentials: Credentials, options: StartOptions archivedBy: 'cli', archiveReason: 'User terminated' })); - + // Send session death message session.sendSessionDeath(); await session.flush(); diff --git a/src/codex/runCodex.ts b/src/codex/runCodex.ts index 62b4b5fd..70c31e36 100644 --- a/src/codex/runCodex.ts +++ b/src/codex/runCodex.ts @@ -27,6 +27,7 @@ import { notifyDaemonSessionStarted } from "@/daemon/controlClient"; import { registerKillSessionHandler } from "@/claude/registerKillSessionHandler"; import { delay } from "@/utils/time"; import { stopCaffeinate } from "@/utils/caffeinate"; +import { parseSpecialCommand } from '@/parsers/specialCommands'; type ReadyEventOptions = { pending: unknown; @@ -175,7 +176,13 @@ export async function runCodex(opts: { permissionMode: messagePermissionMode || 'default', model: messageModel, }; - messageQueue.push(message.content.text, enhancedMode); + + const specialCommand = parseSpecialCommand(message.content.text); + if (specialCommand.type === 'clear') { + messageQueue.pushIsolateAndClear(message.content.text, enhancedMode); + } else { + messageQueue.push(message.content.text, enhancedMode); + } }); let thinking = false; session.keepAlive(thinking, 'remote'); @@ -239,7 +246,7 @@ export async function runCodex(opts: { storedSessionIdForResume = client.storeSessionForResume(); logger.debug('[Codex] Stored session for resume:', storedSessionIdForResume); } - + abortController.abort(); messageQueue.reset(); permissionHandler.reset(); @@ -274,7 +281,7 @@ export async function runCodex(opts: { archivedBy: 'cli', archiveReason: 'User terminated' })); - + // Send session death message session.sendSessionDeath(); await session.flush(); @@ -615,6 +622,30 @@ export async function runCodex(opts: { messageBuffer.addMessage(message.message, 'user'); currentModeHash = message.hash; + const specialCommand = parseSpecialCommand(message.message); + if (specialCommand.type === 'clear') { + logger.debug('[Codex] Handling /clear command - resetting session'); + client.clearSession(); + wasCreated = false; + currentModeHash = null; + + // Reset processors/permissions + permissionHandler.reset(); + reasoningProcessor.abort(); + diffProcessor.reset(); + thinking = false; + session.keepAlive(thinking, 'remote'); + + messageBuffer.addMessage('Session reset.', 'status'); + emitReadyIfIdle({ + pending, + queueSize: () => messageQueue.size(), + shouldExit, + sendReady, + }); + continue; + } + try { // Map permission mode to approval policy and sandbox for startSession const approvalPolicy = (() => { @@ -644,10 +675,10 @@ export async function runCodex(opts: { if (message.mode.model) { startConfig.model = message.mode.model; } - + // Check for resume file from multiple sources let resumeFile: string | null = null; - + // Priority 1: Explicit resume file from mode change if (nextExperimentalResume) { resumeFile = nextExperimentalResume; @@ -664,12 +695,12 @@ export async function runCodex(opts: { } storedSessionIdForResume = null; // consume once } - + // Apply resume file if found if (resumeFile) { (startConfig.config as any).experimental_resume = resumeFile; } - + await client.startSession( startConfig, { signal: abortController.signal } @@ -686,7 +717,7 @@ export async function runCodex(opts: { } catch (error) { logger.warn('Error in codex session:', error); const isAbortError = error instanceof Error && error.name === 'AbortError'; - + if (isAbortError) { messageBuffer.addMessage('Aborted by user', 'status'); session.sendSessionEvent({ type: 'message', message: 'Aborted by user' }); diff --git a/src/parsers/specialCommands.ts b/src/parsers/specialCommands.ts index 69226460..8aaa4c0c 100644 --- a/src/parsers/specialCommands.ts +++ b/src/parsers/specialCommands.ts @@ -22,21 +22,21 @@ export interface SpecialCommandResult { */ export function parseCompact(message: string): CompactCommandResult { const trimmed = message.trim(); - + if (trimmed === '/compact') { return { isCompact: true, originalMessage: trimmed }; } - + if (trimmed.startsWith('/compact ')) { return { isCompact: true, originalMessage: trimmed }; } - + return { isCompact: false, originalMessage: message @@ -49,7 +49,7 @@ export function parseCompact(message: string): CompactCommandResult { */ export function parseClear(message: string): ClearCommandResult { const trimmed = message.trim(); - + return { isClear: trimmed === '/clear' }; @@ -67,14 +67,14 @@ export function parseSpecialCommand(message: string): SpecialCommandResult { originalMessage: compactResult.originalMessage }; } - + const clearResult = parseClear(message); if (clearResult.isClear) { return { type: 'clear' }; } - + return { type: null };