Fix draft agent handoff continuity

Move optimistic user message reconciliation into the stream reducers and keep draft-created agents in a handoff lifecycle until authoritative history lands.
This commit is contained in:
Mohamed Boudra
2026-05-25 20:53:49 +07:00
parent e5658654c0
commit 53faba64ca
21 changed files with 677 additions and 458 deletions
@@ -48,14 +48,14 @@ function ProbePanel() {
return null;
}
const tab: WorkspaceTabDescriptor = {
const agentTab: WorkspaceTabDescriptor = {
key: "agent_agent-a",
tabId: "agent_agent-a",
kind: "agent",
target: { kind: "agent", agentId: "agent-a" },
};
function buildContent() {
function buildContent(tab: WorkspaceTabDescriptor = agentTab) {
return buildWorkspacePaneContentModel({
tab,
normalizedServerId: "server-a",
@@ -120,4 +120,43 @@ describe("WorkspacePaneContent", () => {
focusPane: expect.any(Function),
});
});
it("keeps pane content mounted when a draft tab is retargeted in place", () => {
container = document.createElement("div");
document.body.appendChild(container);
root = createRoot(container);
const draftContent = buildContent({
key: "draft-a",
tabId: "draft-a",
kind: "draft",
target: { kind: "draft", draftId: "draft-a" },
});
const agentContent = buildContent({
key: "draft-a",
tabId: "draft-a",
kind: "agent",
target: { kind: "agent", agentId: "agent-a" },
});
act(() => {
root?.render(
<WorkspacePaneContent content={draftContent} isPaneFocused isWorkspaceFocused={true} />,
);
});
act(() => {
root?.render(
<WorkspacePaneContent content={agentContent} isPaneFocused isWorkspaceFocused={true} />,
);
});
expect(mountCount).toHaveBeenCalledTimes(1);
expect(unmountCount).not.toHaveBeenCalled();
expect(snapshots).toHaveLength(2);
expect(snapshots[1]?.paneContextValue.tabId).toBe("draft-a");
expect(snapshots[1]?.paneContextValue.target).toEqual({
kind: "agent",
agentId: "agent-a",
});
});
});
@@ -42,7 +42,7 @@ export function buildWorkspacePaneContentModel({
const registration = getPanelRegistration(tab.kind);
invariant(registration, `No panel registration for kind: ${tab.kind}`);
return {
key: `${normalizedServerId}:${normalizedWorkspaceId}:${tab.tabId}:${tab.kind}`,
key: `${normalizedServerId}:${normalizedWorkspaceId}:${tab.tabId}`,
Component: registration.component,
paneContextValue: {
serverId: normalizedServerId,
@@ -1746,9 +1746,6 @@ function WorkspaceScreenContent({
const unpinWorkspaceAgent = useWorkspaceLayoutStore((state) => state.unpinAgent);
const hideWorkspaceAgent = useWorkspaceLayoutStore((state) => state.hideAgent);
const retargetWorkspaceTab = useWorkspaceLayoutStore((state) => state.retargetTab);
const convertWorkspaceDraftToAgent = useWorkspaceLayoutStore(
(state) => state.convertDraftToAgent,
);
const reconcileWorkspaceTabs = useWorkspaceLayoutStore((state) => state.reconcileTabs);
const splitWorkspacePane = useWorkspaceLayoutStore((state) => state.splitPane);
const splitWorkspacePaneEmpty = useWorkspaceLayoutStore((state) => state.splitPaneEmpty);
@@ -2842,10 +2839,6 @@ function WorkspaceScreenContent({
if (!persistenceKey) {
return;
}
if (input.tab.kind === "draft" && target.kind === "agent") {
convertWorkspaceDraftToAgent(persistenceKey, input.tab.tabId, target.agentId);
return;
}
retargetWorkspaceTab(persistenceKey, input.tab.tabId, target);
},
onOpenWorkspaceFile: (request: WorkspaceFileOpenRequest) => {
@@ -2875,7 +2868,6 @@ function WorkspaceScreenContent({
openImportSheet,
openWorkspaceChildTabFocused,
persistenceKey,
convertWorkspaceDraftToAgent,
retargetWorkspaceTab,
],
);