mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-11 20:27:44 +02:00
[aidan] chat: isolate branch history, fresh session on edit/switch (#62)
This commit is contained in:
@@ -1768,27 +1768,20 @@ class AgentManager:
|
||||
except Exception as e:
|
||||
logger.debug(f"thinking_level param injection skipped: {e}")
|
||||
|
||||
# MCPActivate fresh-restart path: when the session has prior
|
||||
# turns AND the user just activated a new MCP, the bundled CLI
|
||||
# won't re-read mcp_servers from a `resume + fork_session`
|
||||
# combo (the transport snapshot from the original launch is
|
||||
# what serves tool schemas). Symptom: model calls hallucinated
|
||||
# names like `Searchgmail`/`Listemails` instead of the real
|
||||
# `mcp__google-workspace__query_gmail_emails` because it
|
||||
# never received the schemas. Soft restart: drop resume +
|
||||
# sdk_session_id, replay history via the prompt, let the SDK
|
||||
# build a clean transport with the activated server in its
|
||||
# mcp_servers dict from the start. Costs one cold-start TTFT
|
||||
# (~200-400ms) on the auto-continuation turn; that turn is
|
||||
# already happening anyway because pending_continuation fires
|
||||
# right after MCPActivate.
|
||||
if session.needs_fresh_session and session.sdk_session_id:
|
||||
logger.info(
|
||||
f"[MCP-DEBUG] Fresh-session restart for {session_id}: dropping "
|
||||
f"sdk_session_id={session.sdk_session_id} so the new MCP servers "
|
||||
f"({session.active_mcps}) take effect."
|
||||
)
|
||||
session.sdk_session_id = None
|
||||
# Fresh-restart path: some session changes must not reuse the
|
||||
# CLI's resume transcript. MCPActivate needs a new transport so
|
||||
# tool schemas are reread; branch edits/switches need the model
|
||||
# to see only _get_branch_messages(session), not facts from the
|
||||
# old branch's SDK transcript. Soft restart: drop resume +
|
||||
# sdk_session_id, replay local history via the prompt, let the
|
||||
# SDK build a clean session from the current app state.
|
||||
if session.needs_fresh_session:
|
||||
if session.sdk_session_id:
|
||||
logger.info(
|
||||
f"Fresh-session restart for {session_id}: dropping "
|
||||
f"sdk_session_id={session.sdk_session_id}; active_mcps={session.active_mcps}"
|
||||
)
|
||||
session.sdk_session_id = None
|
||||
session.needs_fresh_session = False
|
||||
session.needs_fork = False # superseded by the fresh restart
|
||||
|
||||
@@ -3569,6 +3562,7 @@ class AgentManager:
|
||||
)
|
||||
session.branches[new_branch_id] = new_branch
|
||||
session.active_branch_id = new_branch_id
|
||||
session.needs_fresh_session = True
|
||||
|
||||
|
||||
edited_msg = Message(
|
||||
@@ -3617,6 +3611,7 @@ class AgentManager:
|
||||
if branch_id not in session.branches:
|
||||
raise ValueError(f"Branch {branch_id} not found")
|
||||
session.active_branch_id = branch_id
|
||||
session.needs_fresh_session = True
|
||||
await ws_manager.send_to_session(session_id, "agent:branch_switched", {
|
||||
"session_id": session_id,
|
||||
"active_branch_id": branch_id,
|
||||
|
||||
@@ -927,6 +927,7 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
|
||||
}
|
||||
|
||||
const isActive = session.status === 'running' || session.status === 'waiting_approval' || session.status === 'draft';
|
||||
const branchNavLocked = agentBusy || hasStreaming;
|
||||
const statusStyle = STATUS_STYLES[session.status] || { color: c.text.tertiary, bg: c.bg.secondary };
|
||||
|
||||
return (
|
||||
@@ -1386,11 +1387,14 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
|
||||
? {
|
||||
currentIndex: Math.max(0, currentBranchIdx),
|
||||
totalBranches: siblings.length,
|
||||
disabled: branchNavLocked,
|
||||
onPrevious: () => {
|
||||
if (branchNavLocked) return;
|
||||
const prevBranch = siblings[Math.max(0, currentBranchIdx - 1)];
|
||||
if (prevBranch && id) dispatch(switchBranch({ sessionId: id, branchId: prevBranch }));
|
||||
},
|
||||
onNext: () => {
|
||||
if (branchNavLocked) return;
|
||||
const nextBranch = siblings[Math.min(siblings.length - 1, currentBranchIdx + 1)];
|
||||
if (nextBranch && id) dispatch(switchBranch({ sessionId: id, branchId: nextBranch }));
|
||||
},
|
||||
|
||||
@@ -21,6 +21,7 @@ import FeedbackDialog, { Sentiment } from './FeedbackDialog';
|
||||
interface BranchNavProps {
|
||||
currentIndex: number;
|
||||
totalBranches: number;
|
||||
disabled?: boolean;
|
||||
onPrevious: () => void;
|
||||
onNext: () => void;
|
||||
}
|
||||
@@ -108,7 +109,7 @@ const MessageActionBar: React.FC<Props> = ({
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={branchNav.onPrevious}
|
||||
disabled={branchNav.currentIndex === 0}
|
||||
disabled={branchNav.disabled || branchNav.currentIndex === 0}
|
||||
sx={btnSx(c)}
|
||||
>
|
||||
<ChevronLeftIcon sx={{ fontSize: 16 }} />
|
||||
@@ -127,7 +128,7 @@ const MessageActionBar: React.FC<Props> = ({
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={branchNav.onNext}
|
||||
disabled={branchNav.currentIndex === branchNav.totalBranches - 1}
|
||||
disabled={branchNav.disabled || branchNav.currentIndex === branchNav.totalBranches - 1}
|
||||
sx={btnSx(c)}
|
||||
>
|
||||
<ChevronRightIcon sx={{ fontSize: 16 }} />
|
||||
@@ -199,6 +200,7 @@ export default React.memo(MessageActionBar, (prev, next) => (
|
||||
&& !!prev.onBranch === !!next.onBranch
|
||||
&& prev.branchNav?.currentIndex === next.branchNav?.currentIndex
|
||||
&& prev.branchNav?.totalBranches === next.branchNav?.totalBranches
|
||||
&& prev.branchNav?.disabled === next.branchNav?.disabled
|
||||
&& prev.messageId === next.messageId
|
||||
&& prev.sessionId === next.sessionId
|
||||
));
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
setTurnLabel,
|
||||
clearTurnLabel,
|
||||
} from '../state/agentsSlice';
|
||||
import { streamStart, streamDelta, streamEnd } from '../state/streamingSlice';
|
||||
import { streamStart, streamDelta, streamEnd, clearStreamingForSession } from '../state/streamingSlice';
|
||||
import { addBrowserCardFromBackend, removeBrowserCard, setBrowserCardPosition, setGlowingBrowserCards, GRID_GAP } from '../state/dashboardLayoutSlice';
|
||||
import { upsertOutput } from '../state/outputsSlice';
|
||||
import { getAuthToken } from '../config';
|
||||
@@ -493,6 +493,7 @@ class WebSocketManager {
|
||||
// before its own aux call lands.
|
||||
if (session_id && (data.status === 'completed' || data.status === 'error' || data.status === 'stopped')) {
|
||||
store.dispatch(clearTurnLabel(session_id));
|
||||
store.dispatch(clearStreamingForSession(session_id));
|
||||
}
|
||||
}
|
||||
// Clean spawned browser cards when the PARENT finishes, never per
|
||||
|
||||
Reference in New Issue
Block a user