Fork assistant turns into new drafts (#1788)
* feat(chat): fork assistant turns into new drafts Builds chat-history attachments on the daemon and routes them into draft composers so users can continue an assistant turn in an existing workspace tab or New Workspace. * fix(chat): clean up fork draft handling * fix(chat): address fork draft review feedback * fix(server): build fork context from bounded timeline * fix(app): restore assistant turn footer in streams * fix(app): preserve fork draft setup * fix(app): tighten assistant fork footers * fix(app): lock active fork draft handoff * fix(app): preserve attachment-only draft retries * fix(app): unify composer attachment scopes * fix(app): align chat history attachment labels * fix(app): centralize attachment pill content * fix(app): trim fork context for workspace naming
This commit is contained in:
@@ -24,10 +24,7 @@ import {
|
||||
type ToastState,
|
||||
} from "@/components/toast-host";
|
||||
import type { WorkspaceComposerAttachment } from "@/attachments/types";
|
||||
import {
|
||||
useWorkspaceAttachments,
|
||||
useWorkspaceAttachmentScopeKey,
|
||||
} from "@/attachments/workspace-attachments-store";
|
||||
import { useWorkspaceAttachmentScopeKey } from "@/attachments/workspace-attachments-store";
|
||||
import { COMPACT_FORM_FACTOR_WIDTH, useIsCompactFormFactor } from "@/constants/layout";
|
||||
import { isNative, isWeb } from "@/constants/platform";
|
||||
import { useAgentAttentionClear } from "@/hooks/use-agent-attention-clear";
|
||||
@@ -86,10 +83,16 @@ import { buildDraftAgentSetup, type ClientSlashCommand } from "@/client-slash-co
|
||||
interface ChatAgentStateShape {
|
||||
serverId: string | null;
|
||||
id: string | null;
|
||||
provider?: Agent["provider"];
|
||||
status: Agent["status"] | null;
|
||||
cwd: string | null;
|
||||
workspaceId?: string;
|
||||
capabilities?: Agent["capabilities"];
|
||||
currentModeId?: Agent["currentModeId"];
|
||||
model?: Agent["model"];
|
||||
thinkingOptionId?: Agent["thinkingOptionId"];
|
||||
runtimeInfo?: Agent["runtimeInfo"];
|
||||
features?: Agent["features"];
|
||||
lastError?: Agent["lastError"] | null;
|
||||
}
|
||||
|
||||
@@ -130,10 +133,16 @@ function selectChatAgentState(
|
||||
return {
|
||||
serverId: agent.serverId,
|
||||
id: agent.id,
|
||||
provider: agent.provider,
|
||||
status: agent.status,
|
||||
cwd: agent.cwd,
|
||||
workspaceId: agent.workspaceId,
|
||||
capabilities: agent.capabilities,
|
||||
currentModeId: agent.currentModeId,
|
||||
model: agent.model,
|
||||
thinkingOptionId: agent.thinkingOptionId,
|
||||
runtimeInfo: agent.runtimeInfo,
|
||||
features: agent.features,
|
||||
lastError: agent.lastError ?? null,
|
||||
archivedAt: agent.archivedAt ?? null,
|
||||
requiresAttention: agent.requiresAttention ?? false,
|
||||
@@ -151,10 +160,16 @@ function buildChatAgentFromState(
|
||||
return {
|
||||
serverId: state.serverId,
|
||||
id: state.id,
|
||||
provider: state.provider,
|
||||
status: state.status,
|
||||
cwd: state.cwd,
|
||||
workspaceId: state.workspaceId,
|
||||
capabilities: state.capabilities,
|
||||
currentModeId: state.currentModeId,
|
||||
model: state.model,
|
||||
thinkingOptionId: state.thinkingOptionId,
|
||||
runtimeInfo: state.runtimeInfo,
|
||||
features: state.features,
|
||||
lastError: state.lastError ?? null,
|
||||
projectPlacement,
|
||||
};
|
||||
@@ -546,18 +561,7 @@ function AgentPanelBody({
|
||||
(a, b) => a === b || JSON.stringify(a) === JSON.stringify(b),
|
||||
);
|
||||
const agentState = useSessionStore(
|
||||
useShallow((state) => {
|
||||
const agent = resolveChatAgentFromSession(state, serverId, agentId);
|
||||
return {
|
||||
serverId: agent?.serverId ?? null,
|
||||
id: agent?.id ?? null,
|
||||
status: agent?.status ?? null,
|
||||
cwd: agent?.cwd ?? null,
|
||||
workspaceId: agent?.workspaceId,
|
||||
lastError: agent?.lastError ?? null,
|
||||
archivedAt: agent?.archivedAt ?? null,
|
||||
};
|
||||
}),
|
||||
useShallow((state) => selectChatAgentState(state, serverId, agentId)),
|
||||
);
|
||||
const [lookupState, setLookupState] = useState<AgentLookupState>({ tag: "idle" });
|
||||
const lookupAttemptTokenRef = useRef(0);
|
||||
@@ -644,9 +648,16 @@ function AgentPanelBody({
|
||||
? {
|
||||
serverId: agentState.serverId,
|
||||
id: agentState.id,
|
||||
provider: agentState.provider,
|
||||
status: agentState.status,
|
||||
cwd: agentState.cwd,
|
||||
workspaceId: agentState.workspaceId,
|
||||
capabilities: agentState.capabilities,
|
||||
currentModeId: agentState.currentModeId,
|
||||
model: agentState.model,
|
||||
thinkingOptionId: agentState.thinkingOptionId,
|
||||
runtimeInfo: agentState.runtimeInfo,
|
||||
features: agentState.features,
|
||||
lastError: agentState.lastError ?? null,
|
||||
projectPlacement,
|
||||
}
|
||||
@@ -1378,7 +1389,10 @@ function ActiveAgentComposer({
|
||||
cwd,
|
||||
workspaceId,
|
||||
});
|
||||
const workspaceAttachments = useWorkspaceAttachments(workspaceAttachmentScopeKey);
|
||||
const attachmentScopeKeys = useMemo(
|
||||
() => [workspaceAttachmentScopeKey],
|
||||
[workspaceAttachmentScopeKey],
|
||||
);
|
||||
const openFileExplorerForCheckout = usePanelStore((state) => state.openFileExplorerForCheckout);
|
||||
const setExplorerTabForCheckout = usePanelStore((state) => state.setExplorerTabForCheckout);
|
||||
const handleOpenWorkspaceAttachment = useCallback(
|
||||
@@ -1479,7 +1493,7 @@ function ActiveAgentComposer({
|
||||
value={agentInputDraft.text}
|
||||
onChangeText={agentInputDraft.setText}
|
||||
attachments={agentInputDraft.attachments}
|
||||
workspaceAttachments={workspaceAttachments}
|
||||
attachmentScopeKeys={attachmentScopeKeys}
|
||||
onOpenWorkspaceAttachment={handleOpenWorkspaceAttachment}
|
||||
onChangeAttachments={agentInputDraft.setAttachments}
|
||||
cwd={cwd}
|
||||
|
||||
Reference in New Issue
Block a user