feat: extend agent-driven map workbench

This commit is contained in:
2026-07-20 19:57:35 +08:00
parent 86e9b08235
commit 7fbd8a5618
63 changed files with 4506 additions and 457 deletions
+21 -16
View File
@@ -8,6 +8,7 @@ import useSWRImmutable from "swr/immutable";
import type { PersonaState } from "@/shared/ai-elements/persona";
import { showMapNotice } from "@/features/map/core/components/notice";
import type {
AgentApprovalMode,
AgentChatMessage,
AgentModelOption,
AgentPermissionReply,
@@ -34,11 +35,11 @@ import {
import type { FrontendActionRequest } from "@/features/agent/frontend-action";
import { FrontendActionExecutor } from "@/features/agent/frontend-action/executor";
import {
appendStreamRenderToken,
applySessionStreamEvent,
createCompletedStreamRenderState,
getDataString,
markLastAssistantStreamDone,
markStreamRenderPending,
readBodyString,
toAgentChatMessages,
toAgentUiMessages,
@@ -62,10 +63,9 @@ export function useWorkbenchAgent({ onUiEnvelope, onFrontendAction }: UseWorkben
const collapseTimerRef = useRef<number | null>(null);
const mobileCollapseTimerRef = useRef<number | null>(null);
const sessionStreamAbortRef = useRef<AbortController | null>(null);
const sessionStreamIdRef = useRef<string | null>(null);
const streamRenderChunkIdRef = useRef(0);
const clientRef = useRef(createAgentApiClient());
const sessionIdRef = useRef<string | null>(null);
const approvalModeRef = useRef<AgentApprovalMode>("request");
const registryRef = useRef<UIRegistry | null>(null);
const frontendActionEnabledRef = useRef(false);
const onFrontendActionRef = useRef(onFrontendAction);
@@ -82,6 +82,7 @@ export function useWorkbenchAgent({ onUiEnvelope, onFrontendAction }: UseWorkben
const [resumedStreaming, setResumedStreaming] = useState(false);
const [modelOptions, setModelOptions] = useState<AgentModelOption[]>([]);
const [selectedModel, setSelectedModel] = useState("");
const [approvalMode, setApprovalModeState] = useState<AgentApprovalMode>("request");
const [streamRenderState, setStreamRenderState] = useState<AgentStreamRenderState>({});
const [permissionOverrides, setPermissionOverrides] = useState<Record<string, PermissionOverride>>({});
const [questionOverrides, setQuestionOverrides] = useState<Record<string, QuestionOverride>>({});
@@ -90,6 +91,11 @@ export function useWorkbenchAgent({ onUiEnvelope, onFrontendAction }: UseWorkben
sessionIdRef.current = sessionId;
}, [sessionId]);
const setApprovalMode = useCallback((mode: AgentApprovalMode) => {
approvalModeRef.current = mode;
setApprovalModeState(mode);
}, []);
useEffect(() => {
registryRef.current = registry;
}, [registry]);
@@ -148,11 +154,10 @@ export function useWorkbenchAgent({ onUiEnvelope, onFrontendAction }: UseWorkben
applySessionId(eventSessionId);
if (part.type === "data-stream_token") {
appendStreamRenderToken(
markStreamRenderPending(
part.data,
chatRef.current.messages,
setStreamRenderState,
streamRenderChunkIdRef
setStreamRenderState
);
return;
}
@@ -214,7 +219,7 @@ export function useWorkbenchAgent({ onUiEnvelope, onFrontendAction }: UseWorkben
trigger,
messageId,
session_id: readBodyString(body, "session_id") ?? sessionIdRef.current ?? undefined,
approval_mode: readBodyString(body, "approval_mode") ?? "request",
approval_mode: readBodyString(body, "approval_mode") ?? approvalModeRef.current,
capabilities: frontendActionEnabledRef.current ? ["frontend-action@1"] : []
}
};
@@ -281,7 +286,6 @@ export function useWorkbenchAgent({ onUiEnvelope, onFrontendAction }: UseWorkben
const stopSessionStreamSubscription = useCallback(() => {
sessionStreamAbortRef.current?.abort();
sessionStreamAbortRef.current = null;
sessionStreamIdRef.current = null;
setResumedStreaming(false);
}, []);
@@ -418,11 +422,10 @@ export function useWorkbenchAgent({ onUiEnvelope, onFrontendAction }: UseWorkben
setSessionTitle(title);
}
} else if (event.type === "token") {
appendStreamRenderToken(
markStreamRenderPending(
event.data,
chatRef.current.messages,
setStreamRenderState,
streamRenderChunkIdRef
setStreamRenderState
);
} else if (event.type === "ui_envelope") {
const payload = parseUiEnvelopePayload(event.data);
@@ -473,7 +476,6 @@ export function useWorkbenchAgent({ onUiEnvelope, onFrontendAction }: UseWorkben
const controller = new AbortController();
sessionStreamAbortRef.current = controller;
sessionStreamIdRef.current = nextSessionId;
setResumedStreaming(true);
void clientRef.current
@@ -496,7 +498,6 @@ export function useWorkbenchAgent({ onUiEnvelope, onFrontendAction }: UseWorkben
.finally(() => {
if (sessionStreamAbortRef.current === controller) {
sessionStreamAbortRef.current = null;
sessionStreamIdRef.current = null;
setResumedStreaming(false);
void refreshSessionHistory();
}
@@ -608,7 +609,7 @@ export function useWorkbenchAgent({ onUiEnvelope, onFrontendAction }: UseWorkben
async (nextSessionId: string) => {
if (streaming) {
showMapNotice({ tone: "warning", message: "Agent 正在处理上一条指令,完成后再删除会话。" });
return;
return false;
}
try {
@@ -623,6 +624,7 @@ export function useWorkbenchAgent({ onUiEnvelope, onFrontendAction }: UseWorkben
}
await refreshSessionHistory();
return true;
} catch (error) {
showMapNotice({
id: "agent-history-status",
@@ -630,6 +632,7 @@ export function useWorkbenchAgent({ onUiEnvelope, onFrontendAction }: UseWorkben
title: "Agent 历史记录删除失败",
message: error instanceof Error ? error.message : "无法删除这个历史会话。"
});
return false;
}
},
[mutateSessionHistory, refreshSessionHistory, resetDraftSession, streaming]
@@ -664,12 +667,12 @@ export function useWorkbenchAgent({ onUiEnvelope, onFrontendAction }: UseWorkben
body: {
session_id: sessionIdRef.current ?? undefined,
model: selectedModel || undefined,
approval_mode: "request"
approval_mode: approvalMode
}
}
);
},
[chat, selectedModel, streaming]
[approvalMode, chat, selectedModel, streaming]
);
const stopPrompt = useCallback(() => {
@@ -788,6 +791,7 @@ export function useWorkbenchAgent({ onUiEnvelope, onFrontendAction }: UseWorkben
}, []);
return {
approvalMode,
collapsePanel,
expandPanel,
mobileOpen,
@@ -811,6 +815,7 @@ export function useWorkbenchAgent({ onUiEnvelope, onFrontendAction }: UseWorkben
statusLabel,
streamRenderState,
selectedModel,
setApprovalMode,
setSelectedModel,
stopPrompt,
streaming,