Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions agent_core/core/prompts/reasoning.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@
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.

RULE 8 - PRESERVE INITIAL LANGUAGE
If the user wrote their message in another language, only enhance in the detected
language. Never stray or use another language other than what the user has written in
unless the user said so.
</rules>

<reasoning_protocol>
Expand All @@ -70,6 +75,7 @@
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?
7. What is the intended language?
</reasoning_protocol>

<anti_patterns>
Expand All @@ -81,6 +87,7 @@
- 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
- Do NOT start using another language other than the one written in by the user initially unless asked for by the user
</anti_patterns>

<output_format>
Expand Down
11 changes: 11 additions & 0 deletions app/ui_layer/browser/frontend/src/components/Chat/Chat.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,12 @@
outline: none;
}

.input:disabled {
color: var(--text-muted);
cursor: not-allowed;
opacity: 0.6;
}

.input::placeholder {
color: var(--text-muted);
}
Expand Down Expand Up @@ -596,6 +602,11 @@
color: var(--color-error, #ef4444);
}

.micCombo:disabled {
color: var(--text-muted);
cursor: not-allowed;
}

.micIconWrap {
position: relative;
display: flex;
Expand Down
66 changes: 58 additions & 8 deletions app/ui_layer/browser/frontend/src/components/Chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ interface SuggestedPlaybook {
}

const SUGGESTED_PLAYBOOK_COUNT = 3
const ENHANCE_TIMEOUT_MS = 30000

// Chat-delivery actions — the ones whose visible form IS a chat bubble in
// this interface. A chunk whose only actions are these renders nothing at
Expand Down Expand Up @@ -321,6 +322,22 @@ export function Chat({ sessionId, placeholder }: ChatProps) {
}, [dispatch, sessionId])

const [enhancing, setEnhancing] = useState(false)
// Guards against the user waiting forever if the enhance WS response
// never comes back. Cleared (never fires) on success, disconnect, or the
// draft being emptied — see the effects/handler below.
const enhanceTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)

// Single choke point for turning enhancing off: always cancels the
// timeout alongside it, so success/disconnect/clear-draft paths can't
// leave a stale timer that fires 30s later on an already-finished request.
const stopEnhancing = useCallback(() => {
if (enhanceTimeoutRef.current !== null) {
clearTimeout(enhanceTimeoutRef.current)
enhanceTimeoutRef.current = null
}
setEnhancing(false)
}, [])

// Reply-to-bubble: set from an agent bubble's hover Reply action. The
// next send carries the quoted original so the event stream records
// which message the user replied to. No routing — session is explicit.
Expand Down Expand Up @@ -780,22 +797,51 @@ export function Chat({ sessionId, placeholder }: ChatProps) {
// Consume enhanced prompt from context when WS response arrives
useEffect(() => {
if (enhancedPrompt === null) return
if (input.trim() === ''){
//Is the box cleared? Do not repopulate the deleted text box.
stopEnhancing()
clearEnhancedPrompt()
return
}
setInput(enhancedPrompt)
setEnhancing(false)
stopEnhancing()
clearEnhancedPrompt()
inputRef.current?.focus()
}, [enhancedPrompt, clearEnhancedPrompt, setInput])
// Defer focus: the textarea is still disabled in the DOM this tick
// (enhancing→false hasn't re-rendered yet), so a synchronous focus()
// would be ignored.
setTimeout(() => inputRef.current?.focus(), 0)
}, [enhancedPrompt, clearEnhancedPrompt, setInput, input, stopEnhancing])

// Deleting the draft cancels any in-flight/pending enhance and resets the
//enhance button's display state
useEffect(() => {
if (input.trim() !== '') return
stopEnhancing()
setPlusOpen (false)
},[input, stopEnhancing]
)

// Reset enhancing spinner if the WebSocket disconnects mid-request
useEffect(() => {
if (!connected) setEnhancing(false)
}, [connected])
if (!connected) stopEnhancing()
}, [connected, stopEnhancing])

const handleEnhancePrompt = useCallback(() => {
if (!input.trim() || enhancing) return
setEnhancing(true)
enhancePrompt(input.trim())
}, [input, enhancing, enhancePrompt])
enhanceTimeoutRef.current = setTimeout(() => {
enhanceTimeoutRef.current = null
setEnhancing(false)
showToast('error', 'AI enhance timed out — please try again.')
}, ENHANCE_TIMEOUT_MS)
}, [input, enhancing, enhancePrompt, showToast])

useEffect(() => {
return () => {
if (enhanceTimeoutRef.current !== null) clearTimeout(enhanceTimeoutRef.current)
}
}, [])

const handleOptionClick = useCallback((value: string, messageId: string) => {
if (value === 'open_settings_model') {
Expand Down Expand Up @@ -1385,6 +1431,7 @@ export function Chat({ sessionId, placeholder }: ChatProps) {
rows={1}
lang={micLang}
inputMode="text"
disabled={enhancing}
/>

<div className={styles.inputControls}>
Expand All @@ -1397,7 +1444,9 @@ export function Chat({ sessionId, placeholder }: ChatProps) {
aria-label="Attach and tools"
aria-expanded={plusOpen}
>
<Plus size={18} />
{enhancing
? <Loader2 size={18} className={styles.uploadingSpinner} />
: <Plus size={18} />}
</button>
{plusOpen && (
<div className={styles.plusMenu}>
Expand Down Expand Up @@ -1429,6 +1478,7 @@ export function Chat({ sessionId, placeholder }: ChatProps) {
className={`${styles.micCombo}${isListening ? ` ${styles.micComboActive}` : ''}`}
title={isListening ? 'Stop listening' : 'Voice input'}
onClick={toggleListening}
disabled = {enhancing}
>
<span className={styles.micIconWrap}>
{isListening ? <MicOff size={18} /> : <Mic size={18} />}
Expand Down Expand Up @@ -1476,7 +1526,7 @@ export function Chat({ sessionId, placeholder }: ChatProps) {
type="button"
className={styles.sendBtn}
onClick={handleSend}
disabled={(!input.trim() && pendingAttachments.length === 0) || !attachmentValidation.valid}
disabled={(!input.trim() && pendingAttachments.length === 0) || !attachmentValidation.valid || enhancing}
title="Send"
aria-label="Send message"
>
Expand Down