Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Commit ·
f915e8e
1
Parent(s): e9c82b7
fix: protect system prompt in undo, scan backwards for dangling tool calls, gate input on SDK status
Browse files
agent/context_manager/manager.py
CHANGED
|
@@ -142,18 +142,35 @@ class ContextManager:
|
|
| 142 |
return self.items
|
| 143 |
|
| 144 |
def _patch_dangling_tool_calls(self) -> None:
|
| 145 |
-
"""Add stub tool results for any tool_calls that lack a matching result.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
if not self.items:
|
| 147 |
return
|
| 148 |
-
|
| 149 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
return
|
|
|
|
| 151 |
answered_ids = {
|
| 152 |
getattr(m, "tool_call_id", None)
|
| 153 |
for m in self.items
|
| 154 |
if getattr(m, "role", None) == "tool"
|
| 155 |
}
|
| 156 |
-
for tc in
|
| 157 |
if tc.id not in answered_ids:
|
| 158 |
self.items.append(
|
| 159 |
Message(
|
|
@@ -168,14 +185,14 @@ class ContextManager:
|
|
| 168 |
"""Remove the last complete turn (user msg + all assistant/tool msgs that follow).
|
| 169 |
|
| 170 |
Pops from the end until the last user message is removed, keeping the
|
| 171 |
-
tool_use/tool_result pairing valid.
|
| 172 |
|
| 173 |
Returns True if a user message was found and removed.
|
| 174 |
"""
|
| 175 |
-
if
|
| 176 |
return False
|
| 177 |
|
| 178 |
-
while self.items:
|
| 179 |
msg = self.items.pop()
|
| 180 |
if getattr(msg, "role", None) == "user":
|
| 181 |
return True
|
|
|
|
| 142 |
return self.items
|
| 143 |
|
| 144 |
def _patch_dangling_tool_calls(self) -> None:
|
| 145 |
+
"""Add stub tool results for any tool_calls that lack a matching result.
|
| 146 |
+
|
| 147 |
+
Scans backwards to find the last assistant message with tool_calls,
|
| 148 |
+
which may not be items[-1] if some tool results were already added.
|
| 149 |
+
"""
|
| 150 |
if not self.items:
|
| 151 |
return
|
| 152 |
+
|
| 153 |
+
# Find the last assistant message with tool_calls
|
| 154 |
+
assistant_msg = None
|
| 155 |
+
for i in range(len(self.items) - 1, -1, -1):
|
| 156 |
+
msg = self.items[i]
|
| 157 |
+
if getattr(msg, "role", None) == "assistant" and getattr(msg, "tool_calls", None):
|
| 158 |
+
assistant_msg = msg
|
| 159 |
+
break
|
| 160 |
+
# Stop scanning once we hit a user message — anything before
|
| 161 |
+
# that belongs to a previous (complete) turn.
|
| 162 |
+
if getattr(msg, "role", None) == "user":
|
| 163 |
+
break
|
| 164 |
+
|
| 165 |
+
if not assistant_msg:
|
| 166 |
return
|
| 167 |
+
|
| 168 |
answered_ids = {
|
| 169 |
getattr(m, "tool_call_id", None)
|
| 170 |
for m in self.items
|
| 171 |
if getattr(m, "role", None) == "tool"
|
| 172 |
}
|
| 173 |
+
for tc in assistant_msg.tool_calls:
|
| 174 |
if tc.id not in answered_ids:
|
| 175 |
self.items.append(
|
| 176 |
Message(
|
|
|
|
| 185 |
"""Remove the last complete turn (user msg + all assistant/tool msgs that follow).
|
| 186 |
|
| 187 |
Pops from the end until the last user message is removed, keeping the
|
| 188 |
+
tool_use/tool_result pairing valid. Never removes the system message.
|
| 189 |
|
| 190 |
Returns True if a user message was found and removed.
|
| 191 |
"""
|
| 192 |
+
if len(self.items) <= 1:
|
| 193 |
return False
|
| 194 |
|
| 195 |
+
while len(self.items) > 1:
|
| 196 |
msg = self.items.pop()
|
| 197 |
if getattr(msg, "role", None) == "user":
|
| 198 |
return True
|
frontend/src/components/SessionChat.tsx
CHANGED
|
@@ -25,7 +25,7 @@ export default function SessionChat({ sessionId, isActive, onSessionDead }: Sess
|
|
| 25 |
const { isConnected, isProcessing, setProcessing, activityStatus } = useAgentStore();
|
| 26 |
const { updateSessionTitle } = useSessionStore();
|
| 27 |
|
| 28 |
-
const { messages, sendMessage, stop, undoLastTurn, approveTools } = useAgentChat({
|
| 29 |
sessionId,
|
| 30 |
isActive,
|
| 31 |
onReady: () => logger.log(`Session ${sessionId} ready`),
|
|
@@ -105,9 +105,13 @@ export default function SessionChat({ sessionId, isActive, onSessionDead }: Sess
|
|
| 105 |
prevActiveRef.current = isActive;
|
| 106 |
}, [isActive, messages]);
|
| 107 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
const handleSendMessage = useCallback(
|
| 109 |
async (text: string) => {
|
| 110 |
-
if (!text.trim() ||
|
| 111 |
|
| 112 |
setProcessing(true);
|
| 113 |
sendMessage({ text: text.trim(), metadata: { createdAt: new Date().toISOString() } });
|
|
@@ -129,7 +133,7 @@ export default function SessionChat({ sessionId, isActive, onSessionDead }: Sess
|
|
| 129 |
});
|
| 130 |
}
|
| 131 |
},
|
| 132 |
-
[sessionId, sendMessage, messages, updateSessionTitle,
|
| 133 |
);
|
| 134 |
|
| 135 |
// Don't render UI for background sessions — hooks still run
|
|
@@ -139,14 +143,14 @@ export default function SessionChat({ sessionId, isActive, onSessionDead }: Sess
|
|
| 139 |
<>
|
| 140 |
<MessageList
|
| 141 |
messages={messages}
|
| 142 |
-
isProcessing={
|
| 143 |
approveTools={approveTools}
|
| 144 |
onUndoLastTurn={undoLastTurn}
|
| 145 |
/>
|
| 146 |
<ChatInput
|
| 147 |
onSend={handleSendMessage}
|
| 148 |
onStop={stop}
|
| 149 |
-
isProcessing={
|
| 150 |
disabled={!isConnected || activityStatus.type === 'waiting-approval'}
|
| 151 |
placeholder={activityStatus.type === 'waiting-approval' ? 'Approve or reject pending tools first...' : undefined}
|
| 152 |
/>
|
|
|
|
| 25 |
const { isConnected, isProcessing, setProcessing, activityStatus } = useAgentStore();
|
| 26 |
const { updateSessionTitle } = useSessionStore();
|
| 27 |
|
| 28 |
+
const { messages, sendMessage, stop, status, undoLastTurn, approveTools } = useAgentChat({
|
| 29 |
sessionId,
|
| 30 |
isActive,
|
| 31 |
onReady: () => logger.log(`Session ${sessionId} ready`),
|
|
|
|
| 105 |
prevActiveRef.current = isActive;
|
| 106 |
}, [isActive, messages]);
|
| 107 |
|
| 108 |
+
// SDK status is the ground truth — if it's streaming/submitted, agent is busy
|
| 109 |
+
const sdkBusy = status === 'streaming' || status === 'submitted';
|
| 110 |
+
const busy = isProcessing || sdkBusy;
|
| 111 |
+
|
| 112 |
const handleSendMessage = useCallback(
|
| 113 |
async (text: string) => {
|
| 114 |
+
if (!text.trim() || busy) return;
|
| 115 |
|
| 116 |
setProcessing(true);
|
| 117 |
sendMessage({ text: text.trim(), metadata: { createdAt: new Date().toISOString() } });
|
|
|
|
| 133 |
});
|
| 134 |
}
|
| 135 |
},
|
| 136 |
+
[sessionId, sendMessage, messages, updateSessionTitle, busy, setProcessing],
|
| 137 |
);
|
| 138 |
|
| 139 |
// Don't render UI for background sessions — hooks still run
|
|
|
|
| 143 |
<>
|
| 144 |
<MessageList
|
| 145 |
messages={messages}
|
| 146 |
+
isProcessing={busy}
|
| 147 |
approveTools={approveTools}
|
| 148 |
onUndoLastTurn={undoLastTurn}
|
| 149 |
/>
|
| 150 |
<ChatInput
|
| 151 |
onSend={handleSendMessage}
|
| 152 |
onStop={stop}
|
| 153 |
+
isProcessing={busy}
|
| 154 |
disabled={!isConnected || activityStatus.type === 'waiting-approval'}
|
| 155 |
placeholder={activityStatus.type === 'waiting-approval' ? 'Approve or reject pending tools first...' : undefined}
|
| 156 |
/>
|