diff --git a/.superpowers/sdd/fix-report.md b/.superpowers/sdd/fix-report.md
new file mode 100644
index 00000000..8f22c37e
--- /dev/null
+++ b/.superpowers/sdd/fix-report.md
@@ -0,0 +1,23 @@
+# Code Review Fix Report
+
+## Branch: V1.4.0 — Enhance Prompt feature
+
+### Fixes Applied
+
+**Fix 1 (Important) — Spinner stuck on disconnect + double-send on WS error**
+- File: `app/ui_layer/adapters/browser_adapter.py`
+- Split single try/except in `_handle_enhance_prompt` into two independent blocks: one for the LLM call (returns on success), one for the fallback send. A closed socket on the fallback is now swallowed silently rather than raising unhandled.
+
+**Fix 2 (Important) — Reset `enhancing` state on disconnect**
+- File: `app/ui_layer/browser/frontend/src/components/Chat/Chat.tsx`
+- Added `useEffect(() => { if (!connected) setEnhancing(false) }, [connected])` after the existing `enhancedPrompt` effect. `connected` was already destructured from `useWebSocket()`.
+
+**Fix 3 (Minor) — Remove duplicate `.spinIcon` CSS class**
+- File: `app/ui_layer/browser/frontend/src/components/Chat/Chat.module.css` — deleted `.spinIcon { animation: spin 1s linear infinite; }` (duplicated `.uploadingSpinner`).
+- File: `app/ui_layer/browser/frontend/src/components/Chat/Chat.tsx` — changed `className={styles.spinIcon}` → `className={styles.uploadingSpinner}` on the Loader2 IconButton.
+
+**Fix 4 (Minor) — Type the `ws` parameter**
+- The `_handle_enhance_prompt` signature already matches the rest of the file's pattern (`ws` with no explicit type). No change needed — consistency preserved.
+
+### Typecheck
+`npx tsc --noEmit`: 0 new errors (all pre-existing issues unrelated to these changes).
diff --git a/app/ui_layer/adapters/browser_adapter.py b/app/ui_layer/adapters/browser_adapter.py
index d7cbde5c..95c9c95e 100644
--- a/app/ui_layer/adapters/browser_adapter.py
+++ b/app/ui_layer/adapters/browser_adapter.py
@@ -1062,6 +1062,119 @@ async def submit_message(
living_ui_id=living_ui_id,
)
+ async def _handle_enhance_prompt(self, content: str, ws) -> None:
+ """Enhance a user's prompt using the LLM for clarity and precision."""
+ SYSTEM = """
+You are a prompt enhancer for CraftBot — a proactive autonomous AI agent that
+controls a computer (file system, CLI, browser, MCP tools, external
+integrations, and a task scheduler).
+
+Your output feeds directly into CraftBot's task pipeline. A poorly written
+prompt causes wrong skill selection, wrong action sets, misrouted sessions,
+or the agent executing the wrong thing entirely. Your job is to eliminate
+every source of ambiguity before the agent ever sees the instruction.
+
+
+RULE 1 — PRESERVE INTENT EXACTLY
+Never change, expand, or restrict what the user asked for.
+Clarify; do not invent. If uncertain, keep the original scope.
+
+RULE 2 — NAME THE TARGET EXPLICITLY
+Vague references to apps, files, or services cause the agent to guess wrong.
+- "my emails" → name the email client (Gmail, Outlook, etc.) if known;
+ otherwise write "the default email client or browser"
+- "that file" → name the file or folder path
+- "send a message" → name the platform (Telegram, WhatsApp, Slack, Discord)
+ if implied by context; this prevents wrong platform routing
+- "remind me" → write "create a proactive scheduled task"
+
+RULE 3 — STATE THE DONE-CONDITION
+The agent verifies tasks against a done-condition. If it is missing, the
+agent either over-executes or loops asking for confirmation.
+End every enhanced prompt with what success looks like:
+"...and confirm to me when complete."
+"...and save the result to the workspace folder."
+"...and send me a summary of what was found."
+
+RULE 4 — SIGNAL TASK COMPLEXITY
+CraftBot routes to simple_task (fast, no plan) or complex_task (todos,
+verification, user approval). Use these signals so routing is correct:
+- For quick lookups, checks, or single-step actions: keep the prompt direct
+ and short — this naturally triggers simple_task mode
+- For multi-step work, file changes, or anything needing verification:
+ include the phrase "and verify the result before reporting back to me"
+ — this signals complex_task mode
+
+RULE 5 — HONOUR SCHEDULING SIGNALS
+CraftBot has a built-in proactive scheduler. If the user implies recurrence
+("every day", "each week", "automatically", "whenever X happens"), write
+"Set up a recurring proactive task to..." — this ensures the scheduler
+system is invoked, not a one-off task.
+
+RULE 6 — ELIMINATE PRONOUN AMBIGUITY
+"it", "this", "that", "them", "there" — replace every pronoun with the
+actual noun it refers to, using context from the conversation if available.
+
+RULE 7 — ONE ACTION FRAME
+Do not chain unrelated actions into one prompt. If the user asked for one
+thing, keep it as one thing. Do not add "and also..." unless the user said so.
+
+
+
+Before writing the enhanced prompt, silently work through:
+1. What is the single core intent? (state it in one clause)
+2. What nouns are vague or missing? (app, file, platform, service)
+3. What is the done-condition? (file saved, message sent, result shown)
+4. simple or complex task? (single-shot vs. multi-step + verify)
+5. Any scheduling signal? (one-time vs. recurring)
+6. Any pronouns to replace with actual nouns?
+
+
+
+NEVER do these:
+- Do NOT add scope the user didn't ask for ("...and also back up your files")
+- Do NOT produce bullet lists or numbered steps — output is one prose block
+- Do NOT include preamble ("Here is the improved prompt:", "Enhanced:", etc.)
+- Do NOT wrap the output in quotes
+- Do NOT exceed 4 sentences
+- Do NOT use passive voice — use active imperative verbs
+- Do NOT leave platform names implicit when a platform is involved
+
+
+
+Return ONLY the enhanced prompt as plain prose. Nothing else.
+
+BAD: "check my emails"
+GOOD: "Open Gmail in the browser, check for unread emails received in the
+ last 24 hours, and send me a plain-text summary of any messages that
+ need a reply or action."
+
+BAD: "remind me about the standup"
+GOOD: "Create a recurring proactive task that sends me a reminder message
+ 5 minutes before my daily standup meeting, using the schedule defined
+ in my calendar or a fixed daily time I confirm."
+
+BAD: "clean it up"
+GOOD: "Open the Downloads folder, identify duplicate files and files not
+ accessed in the last 30 days, list them for my review, and move only
+ the confirmed items to Trash."
+
+"""
+ try:
+ enhanced = await self._controller.agent.llm.generate_response_async(
+ system_prompt=SYSTEM,
+ user_prompt=content,
+ log_response=False,
+ )
+ await ws.send_json({"type": "prompt_enhanced", "content": enhanced.strip()})
+ return
+ except Exception as e:
+ logger.warning(f"[BROWSER ADAPTER] enhance_prompt failed: {e}")
+ try:
+ await ws.send_json({"type": "prompt_enhanced", "content": content})
+ except Exception as send_err:
+ logger.warning(f"[BROWSER ADAPTER] enhance_prompt fallback send failed: {send_err}")
+
def _handle_task_start(self, event: UIEvent) -> None:
"""Handle task start event with metrics tracking."""
# Call parent implementation
@@ -1437,6 +1550,11 @@ async def _handle_ws_message(self, data: Dict[str, Any], ws=None) -> None:
if command:
await self.submit_message(command)
+ elif msg_type == "enhance_prompt":
+ content = data.get("content", "")
+ if content and ws:
+ asyncio.create_task(self._handle_enhance_prompt(content, ws))
+
elif msg_type == "chat_history":
before_timestamp = data.get("beforeTimestamp")
limit = data.get("limit", 50)
diff --git a/app/ui_layer/browser/frontend/src/components/Chat/Chat.tsx b/app/ui_layer/browser/frontend/src/components/Chat/Chat.tsx
index 3780f357..a571d271 100644
--- a/app/ui_layer/browser/frontend/src/components/Chat/Chat.tsx
+++ b/app/ui_layer/browser/frontend/src/components/Chat/Chat.tsx
@@ -1,5 +1,5 @@
import React, { useState, useRef, useEffect, useLayoutEffect, KeyboardEvent, useCallback, ChangeEvent, useMemo } from 'react'
-import { Send, Paperclip, X, Loader2, File, AlertCircle, Reply, Mic, MicOff, ChevronDown } from 'lucide-react'
+import { Send, Paperclip, X, Loader2, File, AlertCircle, Reply, Mic, MicOff, ChevronDown, Sparkles } from 'lucide-react'
import { useVirtualizer } from '@tanstack/react-virtual'
import { useWebSocket } from '../../contexts/WebSocketContext'
import { useToast } from '../../contexts/ToastContext'
@@ -114,6 +114,9 @@ export function Chat({ livingUIId, placeholder, emptyMessage }: ChatProps) {
loadOlderMessages,
hasMoreMessages,
loadingOlderMessages,
+ enhancedPrompt,
+ enhancePrompt,
+ clearEnhancedPrompt,
} = useWebSocket()
const status = useDerivedAgentStatus({ actions, messages, connected })
@@ -130,6 +133,7 @@ export function Chat({ livingUIId, placeholder, emptyMessage }: ChatProps) {
}, [messages])
const [input, setInput] = useState('')
+ const [enhancing, setEnhancing] = useState(false)
const dispatch = useAppDispatch()
const pendingPrefill = useAppSelector(selectPendingPrefill)
const [pendingAttachments, setPendingAttachments] = useState([])
@@ -301,6 +305,26 @@ export function Chat({ livingUIId, placeholder, emptyMessage }: ChatProps) {
}, 0)
}, [pendingPrefill, dispatch])
+ // Consume enhanced prompt from context when WS response arrives
+ useEffect(() => {
+ if (enhancedPrompt === null) return
+ setInput(enhancedPrompt)
+ setEnhancing(false)
+ clearEnhancedPrompt()
+ inputRef.current?.focus()
+ }, [enhancedPrompt, clearEnhancedPrompt])
+
+ // Reset enhancing spinner if the WebSocket disconnects mid-request
+ useEffect(() => {
+ if (!connected) setEnhancing(false)
+ }, [connected])
+
+ const handleEnhancePrompt = useCallback(() => {
+ if (!input.trim() || enhancing) return
+ setEnhancing(true)
+ enhancePrompt(input.trim())
+ }, [input, enhancing, enhancePrompt])
+
const handleChatReply = useCallback((
sessionId: string | undefined,
displayName: string,
@@ -730,6 +754,13 @@ export function Chat({ livingUIId, placeholder, emptyMessage }: ChatProps) {