diff --git a/agent_core/core/prompts/__init__.py b/agent_core/core/prompts/__init__.py index 78517742..72efe67a 100644 --- a/agent_core/core/prompts/__init__.py +++ b/agent_core/core/prompts/__init__.py @@ -81,6 +81,11 @@ LANGUAGE_INSTRUCTION, ) +# Reasoning prompts +from agent_core.core.prompts.reasoning import ( + PROMPT_ENHANCE_REASONING_PROMPT +) + # Routing prompts from agent_core.core.prompts.routing import ( ROUTE_TO_SESSION_PROMPT, @@ -130,6 +135,8 @@ "CURRENT_DATETIME_PROMPT", "AGENT_FILE_SYSTEM_CONTEXT_PROMPT", "LANGUAGE_INSTRUCTION", + # Reasoning prompts + "PROMPT_ENHANCE_REASONING_PROMPT", # Routing prompts "ROUTE_TO_SESSION_PROMPT", # GUI prompts diff --git a/agent_core/core/prompts/reasoning.py b/agent_core/core/prompts/reasoning.py index f9724d71..ffe4d99c 100644 --- a/agent_core/core/prompts/reasoning.py +++ b/agent_core/core/prompts/reasoning.py @@ -6,4 +6,108 @@ Inspired by "Thinking-Claude" repository by richards199999. """ -__all__ = [] +PROMPT_ENHANCE_REASONING_PROMPT=""" +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 a valid JSON object. + +The JSON object must contain exactly one field named "enhanced_prompt". The value of "enhanced_prompt" must be the enhanced prompt as plain prose. + +Do not include markdown, code fences, explanations, or any additional fields. + +Examples of prompt quality (these are examples only, NOT the required output format): + +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." + +Required output example: +{ + "enhanced_prompt": "Open the GitHub pull request at https://github.com/CraftOS-dev/CraftBot/pull/346, review the proposed code changes, identify any bugs, design issues, regressions, or opportunities for improvement, and send me a summary of your findings." +} + +""" + +__all__ = [ + "PROMPT_ENHANCE_REASONING_PROMPT" +] \ No newline at end of file diff --git a/app/agent_base.py b/app/agent_base.py index 0199a095..d93656d0 100644 --- a/app/agent_base.py +++ b/app/agent_base.py @@ -111,6 +111,7 @@ get_memory_max_items, get_memory_prune_target, ) +from app.i18n import classify_provider_error from agent_core import profile, profile_loop, OperationCategory from agent_core import ( # Registries for dependency injection @@ -2553,6 +2554,15 @@ async def _handle_external_event(self, payload: Dict) -> None: except Exception as e: logger.error(f"Error handling external event: {e}", exc_info=True) + async def _handle_prompt_enhance(self, user_message: str) -> str: + try: + from agent_core.core.prompts.reasoning import PROMPT_ENHANCE_REASONING_PROMPT + response = await self.llm.generate_response_async(system_prompt=PROMPT_ENHANCE_REASONING_PROMPT, user_prompt=user_message) + result = json.loads(response) + return result.get('enhanced_prompt', '') + except Exception as e: + logger.error(f"{classify_provider_error(error=e)}") + # ===================================== # Hooks # ===================================== diff --git a/app/ui_layer/adapters/browser_adapter.py b/app/ui_layer/adapters/browser_adapter.py index 9a0ff3e7..9636c5bf 100644 --- a/app/ui_layer/adapters/browser_adapter.py +++ b/app/ui_layer/adapters/browser_adapter.py @@ -1110,6 +1110,17 @@ 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.""" + try: + enhanced: str = await self._controller.handle_prompt_enhance( + user_message=content + ) + await ws.send_json({"type": "prompt_enhanced", "content": enhanced.strip()}) + return + except Exception as e: + logger.warning(f"[BROWSER ADAPTER] enhance_prompt failed: {e}") + def _handle_task_start(self, event: UIEvent) -> None: """Handle task start event with metrics tracking.""" # Call parent implementation @@ -1485,6 +1496,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: + await 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 9abe8f3f..10dfcb3d 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,25 @@ 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]) useEffect(() => { if (replyTarget) inputRef.current?.focus() }, [replyTarget]) @@ -733,6 +756,13 @@ export function Chat({ livingUIId, placeholder, emptyMessage }: ChatProps) {
} variant="ghost" tooltip="Attach file" onClick={handleAttachClick} /> + : } + variant="ghost" + tooltip={enhancing ? 'Enhancing...' : 'AI Enhance'} + onClick={handleEnhancePrompt} + disabled={!input.trim() || enhancing} + />