Skip to content
Open
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
40 changes: 30 additions & 10 deletions agent_core/core/impl/llm/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -1710,11 +1710,21 @@ def _generate_byteplus_with_session(
cached_tokens,
)

return {
"tokens_used": total_tokens or 0,
"content": content or "",
"cached_tokens": cached_tokens or 0,
}
result = {"tokens_used": total_tokens or 0, "cached_tokens": cached_tokens or 0}
if exc_obj:
error_str = f"{type(exc_obj).__name__}: {str(exc_obj)}"
result["error"] = error_str
try:
result["error_info_obj"] = classify_llm_error(
exc_obj, provider=self.provider, model=self.model
)
except Exception:
pass
result["content"] = ""
logger.error(f"[BYTEPLUS_SESSION_ERROR] {error_str}")
else:
result["content"] = content or ""
return result

# ───────────────────── Provider‑specific private helpers ─────────────────────
@profile("llm_openai_call", OperationCategory.LLM)
Expand Down Expand Up @@ -2331,11 +2341,21 @@ def _generate_byteplus_with_prefix_cache(
cached_tokens or 0,
)

return {
"tokens_used": total_tokens or 0,
"content": content or "",
"cached_tokens": cached_tokens or 0,
}
result = {"tokens_used": total_tokens or 0, "cached_tokens": cached_tokens or 0}
if exc_obj:
error_str = f"{type(exc_obj).__name__}: {str(exc_obj)}"
result["error"] = error_str
try:
result["error_info_obj"] = classify_llm_error(
exc_obj, provider=self.provider, model=self.model
)
except Exception:
pass
result["content"] = ""
logger.error(f"[BYTEPLUS_PREFIX_CACHE_ERROR] {error_str}")
else:
result["content"] = content or ""
return result

def _parse_responses_api_content(self, result: Dict[str, Any]) -> str:
"""Parse content from BytePlus Responses API response.
Expand Down
13 changes: 11 additions & 2 deletions app/data/action/run_shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,10 +353,17 @@ def shell_exec_windows(input_data: dict) -> dict:
command,
]
else:
# Use /d and /s to ensure quoted commands (e.g., paths with spaces) are handled consistently.
args = ["cmd.exe", "/d", "/s", "/c", command]
# cmd.exe's own /C parser uses bespoke quote-stripping that does not
# understand the backslash-escaped quotes Python's list2cmdline()
# produces when a list is passed to Popen. That mismatch corrupts any
# command containing an embedded quoted argument (e.g. `foo -p "..."`)
# with stray literal quote characters. Invoke via shell=True below
# instead, passing this raw string so Python's own shell path drives
# cmd.exe without the extra escaping layer.
args = command

creation_flags = getattr(subprocess, "CREATE_NO_WINDOW", 0)
use_shell = shell_choice == "cmd"

# Background mode: start process and return immediately
if background:
Expand All @@ -365,6 +372,7 @@ def shell_exec_windows(input_data: dict) -> dict:
bg_flags = creation_flags | subprocess.CREATE_NEW_PROCESS_GROUP
process = subprocess.Popen(
args,
shell=use_shell,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
stdin=subprocess.DEVNULL,
Expand Down Expand Up @@ -396,6 +404,7 @@ def shell_exec_windows(input_data: dict) -> dict:
fg_flags = creation_flags | subprocess.CREATE_NEW_PROCESS_GROUP
process = subprocess.Popen(
args,
shell=use_shell,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.DEVNULL,
Expand Down
19 changes: 14 additions & 5 deletions app/ui_layer/browser/frontend/src/components/Chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import type { SlashCommandAutocompleteHandle } from '../ui'
import { useDerivedAgentStatus } from '../../hooks'
import { ChatMessageItem } from '../../pages/Chat/ChatMessage'
import { useAppDispatch, useAppSelector } from '../../store/hooks'
import { selectPendingPrefill } from '../../store/selectors/chatInput'
import { clearPendingPrefill } from '../../store/slices/chatInputSlice'
import { selectPendingPrefill, selectDraftText } from '../../store/selectors/chatInput'
import { clearPendingPrefill, setDraftText, clearDraftText } from '../../store/slices/chatInputSlice'
import styles from './Chat.module.css'

// Pending attachment type
Expand Down Expand Up @@ -132,9 +132,16 @@ export function Chat({ livingUIId, placeholder, emptyMessage }: ChatProps) {
return messages.slice().sort((a, b) => a.timestamp - b.timestamp)
}, [messages])

const [input, setInput] = useState('')
const [enhancing, setEnhancing] = useState(false)
const dispatch = useAppDispatch()
const draftKey = livingUIId ?? 'main'
const input = useAppSelector(selectDraftText(draftKey))
const inputValueRef = useRef(input)
inputValueRef.current = input
const setInput = useCallback((value: string | ((prev: string) => string)) => {
const resolved = typeof value === 'function' ? (value as (prev: string) => string)(inputValueRef.current) : value
dispatch(setDraftText({ key: draftKey, text: resolved }))
}, [dispatch, draftKey])
const [enhancing, setEnhancing] = useState(false)
const pendingPrefill = useAppSelector(selectPendingPrefill)
const [pendingAttachments, setPendingAttachments] = useState<PendingAttachment[]>([])
const [attachmentError, setAttachmentError] = useState<string | null>(null)
Expand Down Expand Up @@ -290,6 +297,8 @@ export function Chat({ livingUIId, placeholder, emptyMessage }: ChatProps) {
// Consume a one-shot prefill payload from the chatInput slice (e.g. when the
// user picks a playbook). Replaces the current input so the prompt is ready
// to send or edit, then clears the payload so it doesn't re-apply.
// Deliberately overwrites any persisted draftText — an explicit prefill
// action takes precedence.
useEffect(() => {
if (pendingPrefill === null) return
setInput(pendingPrefill)
Expand Down Expand Up @@ -438,7 +447,7 @@ export function Chat({ livingUIId, placeholder, emptyMessage }: ChatProps) {
if (!connected) {
showToast('info', 'Reconnecting — your message will send when the connection is restored.')
}
setInput('')
dispatch(clearDraftText(draftKey))
setPendingAttachments([])
setAttachmentError(null)
clearReplyTarget()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { RootState } from '../index'

export const selectPendingPrefill = (state: RootState) => state.chatInput.pendingPrefill

export const selectDraftText = (key: string) => (state: RootState) => state.chatInput.drafts[key] ?? ''
26 changes: 19 additions & 7 deletions app/ui_layer/browser/frontend/src/store/slices/chatInputSlice.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'

// Cross-component prefill channel for the chat input.
// Cross-component channel for the chat input, plus persisted draft text.
//
// The chat input state itself stays local to Chat.tsx — this slice only
// carries a one-shot "pendingPrefill" payload that Chat consumes via
// useEffect and clears immediately. Used by the Playbook modal (and any
// future feature that needs to drop text into the composer from elsewhere
// in the app).
// `pendingPrefill` is a one-shot payload that Chat consumes via useEffect and
// clears immediately. Used by the Playbook modal (and any future feature
// that needs to drop text into the composer from elsewhere in the app).
//
// `drafts` persists each conversation's composer text, keyed by
// `livingUIId ?? 'main'` (Chat.tsx is shared between the main Chat page and
// every Living UI project's chat panel), so it survives the route unmount
// that happens when navigating to another tab (Settings, Tasks & Actions)
// and back.
interface ChatInputState {
pendingPrefill: string | null
drafts: Record<string, string>
}

const initialState: ChatInputState = {
pendingPrefill: null,
drafts: {},
}

const chatInputSlice = createSlice({
Expand All @@ -25,8 +31,14 @@ const chatInputSlice = createSlice({
clearPendingPrefill(state) {
state.pendingPrefill = null
},
setDraftText(state, action: PayloadAction<{ key: string; text: string }>) {
state.drafts[action.payload.key] = action.payload.text
},
clearDraftText(state, action: PayloadAction<string>) {
delete state.drafts[action.payload]
},
},
})

export const { setPendingPrefill, clearPendingPrefill } = chatInputSlice.actions
export const { setPendingPrefill, clearPendingPrefill, setDraftText, clearDraftText } = chatInputSlice.actions
export default chatInputSlice.reducer