diff --git a/apps/x/apps/main/src/ipc.ts b/apps/x/apps/main/src/ipc.ts index 481c5e5d..f04d820d 100644 --- a/apps/x/apps/main/src/ipc.ts +++ b/apps/x/apps/main/src/ipc.ts @@ -525,7 +525,7 @@ export function setupIpcHandlers() { return runsCore.createRun(args); }, 'runs:createMessage': async (_event, args) => { - return { messageId: await runsCore.createMessage(args.runId, args.message, args.voiceInput, args.voiceOutput, args.searchEnabled, args.middlePaneContext) }; + return { messageId: await runsCore.createMessage(args.runId, args.message, args.voiceInput, args.voiceOutput, args.searchEnabled, args.middlePaneContext, args.codeMode) }; }, 'runs:authorizePermission': async (_event, args) => { await runsCore.authorizePermission(args.runId, args.authorization); diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index 04359de5..a06d6e4f 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -954,7 +954,7 @@ function App() { voice.start() }, [voice]) - const handlePromptSubmitRef = useRef<((message: PromptInputMessage, mentions?: FileMention[], stagedAttachments?: StagedAttachment[], searchEnabled?: boolean) => Promise) | null>(null) + const handlePromptSubmitRef = useRef<((message: PromptInputMessage, mentions?: FileMention[], stagedAttachments?: StagedAttachment[], searchEnabled?: boolean, codeMode?: 'claude' | 'codex') => Promise) | null>(null) const pendingVoiceInputRef = useRef(false) // Palette: per-tab editor handles for capturing cursor context on Cmd+K, and pending payload @@ -2136,6 +2136,19 @@ function App() { status: 'running', timestamp: Date.now(), }]) + // Detect acpx-driven coding-agent runs so the composer can retroactively + // flip code mode on with the right agent (when the user reached the skill + // via plain prompt rather than the explicit toggle). + if (llmEvent.toolName === 'executeCommand') { + const input = llmEvent.input as { command?: unknown } | undefined + const cmd = typeof input?.command === 'string' ? input.command : '' + const match = cmd.match(/\bacpx\b[\s\S]*?\b(claude|codex)\b/) + if (match) { + window.dispatchEvent(new CustomEvent('code-mode-detected', { + detail: { runId: event.runId, agent: match[1] as 'claude' | 'codex' }, + })) + } + } } else if (llmEvent.type === 'finish-step') { const nextUsage = normalizeUsage(llmEvent.usage) if (nextUsage) { @@ -2428,6 +2441,7 @@ function App() { mentions?: FileMention[], stagedAttachments: StagedAttachment[] = [], searchEnabled?: boolean, + codeMode?: 'claude' | 'codex', ) => { if (isProcessing) return @@ -2535,6 +2549,7 @@ function App() { voiceInput: pendingVoiceInputRef.current || undefined, voiceOutput: ttsEnabledRef.current ? ttsModeRef.current : undefined, searchEnabled: searchEnabled || undefined, + codeMode: codeMode || undefined, middlePaneContext, }) analytics.chatMessageSent({ @@ -2550,6 +2565,7 @@ function App() { voiceInput: pendingVoiceInputRef.current || undefined, voiceOutput: ttsEnabledRef.current ? ttsModeRef.current : undefined, searchEnabled: searchEnabled || undefined, + codeMode: codeMode || undefined, middlePaneContext, }) analytics.chatMessageSent({ @@ -5736,6 +5752,7 @@ function App() { handleAskHumanResponse(request.toolCallId, request.subflow, response)} isProcessing={isActive && isProcessing} /> diff --git a/apps/x/apps/renderer/src/components/ai-elements/ask-human-request.tsx b/apps/x/apps/renderer/src/components/ai-elements/ask-human-request.tsx index 2e92e2ca..6571e54e 100644 --- a/apps/x/apps/renderer/src/components/ai-elements/ask-human-request.tsx +++ b/apps/x/apps/renderer/src/components/ai-elements/ask-human-request.tsx @@ -9,6 +9,7 @@ import { useState, useRef, useEffect } from "react"; export type AskHumanRequestProps = ComponentProps<"div"> & { query: string; + options?: string[]; onResponse: (response: string) => void; isProcessing?: boolean; }; @@ -16,17 +17,21 @@ export type AskHumanRequestProps = ComponentProps<"div"> & { export const AskHumanRequest = ({ className, query, + options, onResponse, isProcessing = false, ...props }: AskHumanRequestProps) => { const [response, setResponse] = useState(""); const textareaRef = useRef(null); + const hasOptions = Array.isArray(options) && options.length > 0; useEffect(() => { - // Auto-focus the textarea when component mounts - textareaRef.current?.focus(); - }, []); + // Auto-focus the textarea when in free-text mode; nothing to focus for buttons. + if (!hasOptions) { + textareaRef.current?.focus(); + } + }, [hasOptions]); const handleSubmit = () => { const trimmed = response.trim(); @@ -36,6 +41,11 @@ export const AskHumanRequest = ({ } }; + const handleOptionClick = (option: string) => { + if (isProcessing) return; + onResponse(option); + }; + const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); @@ -65,30 +75,47 @@ export const AskHumanRequest = ({ {query}

-
-