feat(agent): support Responses steering and project-aware tools

Use the dedicated TJWATER_AGENT_URL and Responses stream protocol. Keep running
turns active while users steer or queue follow-ups and edit pending delivery.
Refresh the model catalog before generation and preserve approval mode selection.

Integrate scoped investigation messaging/presentation, geographic place selection,
current Agent composer styles and supporting workbench contracts.

Validation: 77 focused Vitest checks, 21 browser cases, lint, typecheck and
production build. Preserve direct Server/Agent routing and project authentication.
This commit is contained in:
2026-10-19 18:46:58 +08:00
parent 2c5bf15cce
commit ec23d44b02
131 changed files with 6663 additions and 1204 deletions
@@ -19,10 +19,14 @@ import {
} from "@/features/agent";
type AgentUiDataParts = {
agent_message: Record<string, unknown>;
task_context: Record<string, unknown>;
execution_plan: Record<string, unknown>;
progress: Record<string, unknown>;
final_answer: Record<string, unknown>;
credential_refresh_required: Record<string, unknown>;
credential_refreshed: Record<string, unknown>;
credential_refresh_failed: Record<string, unknown>;
todo_update: Record<string, unknown>;
permission_request: Record<string, unknown>;
permission_response: Record<string, unknown>;
@@ -35,6 +39,7 @@ type AgentUiDataParts = {
answer_ui: Record<string, unknown>;
ui_envelope: Record<string, unknown>;
session_title: Record<string, unknown>;
user_input: Record<string, unknown>;
};
export type AgentUiMessage = UIMessage<unknown, AgentUiDataParts>;
@@ -200,6 +205,15 @@ export function applySessionStreamEvent(messages: AgentUiMessage[], event: Agent
return toAgentUiMessages(event.messages);
}
if (event.type === "user_input") {
const id = getDataString(event.data, "id");
const text = getDataString(event.data, "text");
if (event.data.status !== "delivered" || !id || !text || messages.some((message) => message.id === id)) return messages;
const input: AgentUiMessage = { id, role: "user", parts: [{ type: "text", text }] };
const tail = messages.at(-1);
return tail?.role === "assistant" ? [...messages.slice(0, -1), input, tail] : [...messages, input];
}
if (event.type === "token") {
const token = getDataString(event.data, "content");
return token ? updateLastAssistantUiMessage(messages, (message) => appendTextToUiMessage(message, token)) : messages;