feat: expand intelligent investigation workspace
Generic Container CI/CD / test-build-publish (push) Successful in 1m52s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 1m52s

This commit is contained in:
2026-10-30 17:30:19 +08:00
parent ddd03dbff6
commit c0e4231b83
223 changed files with 13155 additions and 5304 deletions
@@ -13,6 +13,7 @@ import {
useInteractionRequests,
useAgentConnection,
FrontendActionExecutor,
AnswerViewStateScope,
agentTaskDiscussionContextSchema,
type AgentTaskDiscussionContext,
agentBootstrapKey,
@@ -103,6 +104,12 @@ export function useWorkbenchAgent({
const projectScopeKey = `project:${getWorkbenchProject(getAccessToken)?.project_id ?? "default"}`;
const scopeKey = projectScopeKey;
const { connectionStatus, recheck: checkAgentConnection, recoveryVersion } = useAgentConnection(scopeClient, scopeKey);
const answerStateScope = useMemo(() => new AnswerViewStateScope(`${resources.id}:${scopeKey}`, scopeClient), [resources.id, scopeClient, scopeKey]);
useEffect(() => {
answerStateScope.retain();
resources.disposers.add(answerStateScope.dispose);
return () => { resources.disposers.delete(answerStateScope.dispose); answerStateScope.release(); };
}, [answerStateScope, resources]);
const history = useAgentHistory(scopeClient, scopeKey);
const topics = useAgentTopics(scopeClient, scopeKey);
const runtimeSummaries: AgentChatSessionSummary[] = agentRuntimes(resources).filter((entry) => entry.scope === scopeKey && entry.sessionId && entry.purpose !== "task_execution")
@@ -184,7 +191,7 @@ export function useWorkbenchAgent({
try {
await target.chat.sendMessage(alreadyPresent ? undefined : replyMessage, { body: {
session_id: owningSessionId, interaction_id: interactionId, model: selectedModel || undefined,
capabilities: frontendActionEnabledRef.current ? ["host-question@1", "frontend-action@1"] : ["host-question@1"],
capabilities: frontendActionEnabledRef.current ? ["host-question@1", "frontend-action@2"] : ["host-question@1"],
approval_mode: approvalMode, task_context: target.taskDiscussion?.context,
} });
} catch (error) { target.runStatus = "error"; throw error; }
@@ -344,8 +351,14 @@ export function useWorkbenchAgent({
[refreshInteractionRequests, refreshAgentMessageInbox, applySessionId, claimEnvelope, handleCredentialRefresh, handleFrontendAction, onUiEnvelope]
);
const synchronizeModelCatalog = useCallback((catalog: Awaited<ReturnType<AgentApiClient["getModels"]>>) => {
// A background request from an old project must not replace this project's controls.
if (resources.disposed || resources.activeAgent?.scope !== scopeKey) return;
setModelOptions(catalog.models);
setSelectedModel((current) => resolveAgentModelSelection(catalog, current));
}, [resources, scopeKey]);
const transport = useMemo(() => createWorkbenchAgentTransport(scopeClient, getAccessToken,
getProjectId), [getAccessToken, getProjectId, scopeClient]);
getProjectId, synchronizeModelCatalog), [getAccessToken, getProjectId, scopeClient, synchronizeModelCatalog]);
const defaultRuntime = useMemo(() => {
const created = createAgentRuntime(resources, scopeKey, scopeClient, transport);
return created;
@@ -768,7 +781,7 @@ export function useWorkbenchAgent({
const loaded = await projectClient.loadSession(id, { messageLimit: 40 });
if (!loaded) throw new Error("任务协作会话未能打开,请重试。");
target = createAgentRuntime(resources, projectScopeKey, projectClient,
createWorkbenchAgentTransport(projectClient, getAccessToken, getProjectId), loaded);
createWorkbenchAgentTransport(projectClient, getAccessToken, getProjectId, synchronizeModelCatalog), loaded);
if (loaded.isStreaming || loaded.runStatus === "running") resumeAgentRuntime(resources, target);
}
const titleBeforeOpen = titlesBeforeOpen.get(target);
@@ -781,7 +794,7 @@ export function useWorkbenchAgent({
activateRuntime(target);
setPromptFocusRequest((current) => current + 1);
return id;
}, [activateRuntime, getAccessToken, getProjectId, projectScopeKey, resources]);
}, [activateRuntime, getAccessToken, getProjectId, projectScopeKey, resources, synchronizeModelCatalog]);
const openTaskExecutionHistory = useCallback(async (taskId: string, runId: string, executionSessionId: string) => {
const normalizedTaskId = taskId.trim();
@@ -809,7 +822,7 @@ export function useWorkbenchAgent({
resources,
projectScopeKey,
projectClient,
createWorkbenchAgentTransport(projectClient, getAccessToken, getProjectId),
createWorkbenchAgentTransport(projectClient, getAccessToken, getProjectId, synchronizeModelCatalog),
loaded
);
} else {
@@ -833,7 +846,7 @@ export function useWorkbenchAgent({
activateRuntime(target);
setStatusLabel(loaded.runStatus === "running" ? "调查执行记录自动同步中" : "调查执行记录已打开");
return loaded.sessionId;
}, [activateRuntime, getAccessToken, getProjectId, projectScopeKey, resources]);
}, [activateRuntime, getAccessToken, getProjectId, projectScopeKey, resources, synchronizeModelCatalog]);
useEffect(() => {
const target = runtimeRef.current;
@@ -1084,7 +1097,7 @@ export function useWorkbenchAgent({
topic_id: !activeSessionId ? runtimeRef.current?.topicId ?? undefined : undefined,
model: selectedModel || undefined,
approval_mode: approvalMode,
capabilities: frontendActionEnabledRef.current ? ["frontend-action@1", "host-question@1"] : ["host-question@1"],
capabilities: frontendActionEnabledRef.current ? ["frontend-action@2", "host-question@1"] : ["host-question@1"],
task_context: taskDiscussion?.sessionId === sessionIdRef.current ? taskDiscussion.context : undefined
}
}
@@ -1108,7 +1121,7 @@ export function useWorkbenchAgent({
setStatusLabel("Agent 正在调查水质投诉事件");
void target.chat.sendMessage({ text: prompt }, { body: {
session_id: id, model: selectedModel || undefined, approval_mode: approvalMode, trigger_context: trigger,
capabilities: frontendActionEnabledRef.current ? ["frontend-action@1", "host-question@1"] : ["host-question@1"],
capabilities: frontendActionEnabledRef.current ? ["frontend-action@2", "host-question@1"] : ["host-question@1"],
} }).catch(() => {});
return { sessionId: id, reused: reserved.event_duplicate === true };
}, [activateRuntime, approvalMode, resources, scopeClient, scopeKey, selectedModel, transport]);
@@ -1185,6 +1198,7 @@ export function useWorkbenchAgent({
return {
apiClient: clientRef.current,
answerStateScope,
contentReference: runtime.reference,
setContentReference: (value: import("@/features/agent").AnswerUiSelection | undefined) => {
const active = resources.activeAgent;
@@ -1313,7 +1327,7 @@ function prefersReducedMotion() {
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}
function createWorkbenchAgentTransport(client: AgentApiClient, access?: WorkbenchAccess, getProjectId?: () => Promise<string>) {
function createWorkbenchAgentTransport(client: AgentApiClient, access?: WorkbenchAccess, getProjectId?: () => Promise<string>, onModels?: (catalog: Awaited<ReturnType<AgentApiClient["getModels"]>>) => void) {
return new ResponsesChatTransport<AgentUiMessage>({
api: `${env.TJWATER_AGENT_URL.replace(/\/$/, "")}/api/v1/responses`,
headers: async (): Promise<Record<string, string>> => {
@@ -1323,12 +1337,13 @@ function createWorkbenchAgentTransport(client: AgentApiClient, access?: Workbenc
},
async prepareSendMessagesRequest({ messages, body }) {
const catalog = await client.getModels();
onModels?.(catalog);
const model = resolveAgentModelSelection(catalog, readBodyString(body, "model") ?? "");
if (!model) throw new Error("Agent 后端当前没有可用模型,请检查模型配置。");
return { body: createResponseRequest(messages, {
conversation: readBodyString(body, "session_id"), interactionId: readBodyString(body, "interaction_id"), model, topicId: readBodyString(body, "topic_id"),
approvalMode: readBodyString(body, "approval_mode") ?? "request",
capabilities: Array.isArray(body?.capabilities) ? body.capabilities.filter((value): value is string => value === "frontend-action@1" || value === "host-question@1") : [], triggerContext: body?.trigger_context, taskContext: body?.task_context,
capabilities: Array.isArray(body?.capabilities) ? body.capabilities.filter((value): value is string => value === "frontend-action@2" || value === "host-question@1") : [], triggerContext: body?.trigger_context, taskContext: body?.task_context,
}) };
},
});