feat: add project-aware agent workspace
Generic Container CI/CD / test-build-publish (push) Successful in 2m9s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 2m39s

This commit is contained in:
2026-10-22 18:52:18 +08:00
parent c6a0e74e72
commit 2d8506fea0
191 changed files with 12373 additions and 3556 deletions
+85 -8
View File
@@ -39,12 +39,15 @@ export type AgentModelsResponse = {
};
export type AgentInputDelivery = "steer" | "queue";
export type AgentResponseInput = { id: string; text: string; delivery: AgentInputDelivery; status: "pending" | "delivered" };
export type AgentResponseInput = { id: string; text: string; delivery: AgentInputDelivery; status: "pending" | "delivered" | "cancelled" };
export type AgentTopic = { id: string; name: string; createdAt: string; updatedAt: string };
export type AgentChatSession = {
id?: string;
session_id: string;
title?: string;
topic_id?: string | null;
status?: string;
run_status?: AgentRunStatus;
is_streaming?: boolean;
@@ -64,12 +67,16 @@ export type AgentChatSessionSummary = {
trigger?: { subjectId: string; workflow: "water_quality_complaint"; occurredAt: string };
id: string;
title: string;
topicId?: string | null;
topicName?: string;
searchSnippet?: string;
createdAt: number;
updatedAt: number;
status?: string;
parentSessionId?: string;
isStreaming?: boolean;
runStatus?: AgentRunStatus;
needsInput?: boolean;
purpose: AgentSessionPurpose;
taskId?: string;
};
@@ -89,10 +96,15 @@ export type AgentPromptSuggestion = {
activityId: string | null;
};
export type AgentMessagePagination = { hasMore: boolean; beforeCursor: string | null };
export type AgentSessionPage = { sessions: AgentChatSessionSummary[]; hasMore: boolean; nextCursor: string | null };
export type AgentHistoryMessagePage = { messages: unknown[]; messagePage: AgentMessagePagination };
export type AgentLoadedChatSession = AgentChatSessionSummary & {
sessionId: string;
isTitleManuallyEdited: boolean;
messages: unknown[];
messagePage?: AgentMessagePagination;
};
export type AgentSessionStreamDataEventType =
@@ -124,6 +136,7 @@ export type AgentSessionStreamEvent =
| {
type: "state";
executionPlans?: unknown[];
messagePage?: AgentMessagePagination;
sessionId: string;
messages: unknown[];
isStreaming: boolean;
@@ -136,20 +149,30 @@ export type AgentSessionStreamEvent =
};
export type AgentApiClient = {
downloadFile: (path: string) => Promise<void>;
submitResponseInput: (sessionId: string, text: string, delivery: AgentInputDelivery, id: string) => Promise<AgentResponseInput>;
listResponseInputs: (sessionId: string) => Promise<AgentResponseInput[]>;
updateResponseInput: (sessionId: string, id: string, delivery: AgentInputDelivery) => Promise<AgentResponseInput>;
cancelResponseInput: (sessionId: string, id: string) => Promise<AgentResponseInput>;
listAgentMessages: (sessionId: string, options?: { cursor?: string; signal?: AbortSignal }) => Promise<AgentMessagePage>;
streamAgentMessages: (sessionId: string, options: { signal?: AbortSignal; onMessage: () => void }) => Promise<void>;
generatePromptSuggestions: (activities: AgentRecentActivity[], model: string, signal?: AbortSignal) => Promise<AgentPromptSuggestion[]>;
createSession: (options?: { triggerContext?: AgentEventTriggerContext }) => Promise<AgentChatSession>;
createSession: (options?: { triggerContext?: AgentEventTriggerContext; topicId?: string }) => Promise<AgentChatSession>;
listSessions: (options?: { purpose?: AgentSessionPurpose; taskId?: string }) => Promise<AgentChatSessionSummary[]>;
listSessionPage: (options?: { q?: string; cursor?: string; limit?: number; purpose?: AgentSessionPurpose; topicId?: string; includeCollaboration?: boolean; signal?: AbortSignal }) => Promise<AgentSessionPage>;
listTopics: () => Promise<AgentTopic[]>;
createTopic: (name: string) => Promise<AgentTopic>;
renameTopic: (id: string, name: string) => Promise<AgentTopic>;
deleteTopic: (id: string) => Promise<void>;
moveSessionToTopic: (sessionId: string, topicId: string | null) => Promise<void>;
loadOlderMessages: (sessionId: string, before: string, signal?: AbortSignal) => Promise<AgentHistoryMessagePage>;
createTaskCollaborationSession: (taskId: string) => Promise<AgentChatSession>;
loadSession: (sessionId: string) => Promise<AgentLoadedChatSession | null>;
loadSession: (sessionId: string, options?: { messageLimit?: number; signal?: AbortSignal }) => Promise<AgentLoadedChatSession | null>;
streamSession: (
sessionId: string,
options: {
signal?: AbortSignal;
messageLimit?: number;
onEvent: (event: AgentSessionStreamEvent) => void;
}
) => Promise<void>;
@@ -218,6 +241,19 @@ export function createAgentApiClient(
};
return {
async downloadFile(path) {
const match = /^\/api\/v1(\/session\/[^/?#]+\/files\/mcp-[a-f0-9-]{36}\.(?:zip|xlsx))$/.exec(path);
if (!match) throw new Error("无效的会话文件地址");
const response = await request(match[1], { redirect: "error" });
if (!response.ok) throw new Error("文件不可用或当前账号无权下载");
const blob = await response.blob();
const url = URL.createObjectURL(blob);
const anchor = document.createElement("a");
anchor.href = url;
anchor.download = path.split("/").at(-1) ?? "export.zip";
anchor.click();
setTimeout(() => URL.revokeObjectURL(url), 1000);
},
async submitResponseInput(sessionId, text, delivery, id) {
return requestJson<AgentResponseInput>("/responses", {
method: "POST", headers: { "Content-Type": "application/json" },
@@ -233,6 +269,11 @@ export function createAgentApiClient(
method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ delivery })
});
},
async cancelResponseInput(sessionId, id) {
return requestJson<AgentResponseInput>(`/session/${encodeURIComponent(sessionId)}/inputs/${encodeURIComponent(id)}`, {
method: "DELETE"
});
},
async generatePromptSuggestions(activities, model, signal) {
const result = await requestJson<unknown>("/suggestions", {
method: "POST",
@@ -260,7 +301,7 @@ export function createAgentApiClient(
return requestJson<AgentChatSession>("/session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ trigger_context: options?.triggerContext })
body: JSON.stringify({ trigger_context: options?.triggerContext, topic_id: options?.topicId })
});
},
@@ -274,6 +315,31 @@ export function createAgentApiClient(
return (payload.sessions ?? []).map(toSessionSummary).filter(isPresent).sort(compareSessionSummaries);
},
async listSessionPage(options) {
const params = new URLSearchParams({ purpose: options?.purpose ?? "ordinary_chat", limit: String(options?.limit ?? 30) });
if (options?.q) params.set("q", options.q);
if (options?.topicId) params.set("topic_id", options.topicId);
if (options?.includeCollaboration) params.set("include_collaboration", "true");
if (options?.cursor) params.set("cursor", options.cursor);
const payload = await requestJson<{ sessions?: unknown[]; has_more?: boolean; next_cursor?: string | null }>(
`/sessions?${params}`, { signal: options?.signal });
return { sessions: (payload.sessions ?? []).map(toSessionSummary).filter(isPresent),
hasMore: payload.has_more === true, nextCursor: typeof payload.next_cursor === "string" ? payload.next_cursor : null };
},
async listTopics() { return (await requestJson<{ topics: AgentTopic[] }>("/topics")).topics; },
async createTopic(name) { return requestJson<AgentTopic>("/topics", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name }) }); },
async renameTopic(id, name) { return requestJson<AgentTopic>(`/topics/${encodeURIComponent(id)}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name }) }); },
async deleteTopic(id) { await requestJson(`/topics/${encodeURIComponent(id)}`, { method: "DELETE" }); },
async moveSessionToTopic(sessionId, topicId) { await requestJson(`/session/${encodeURIComponent(sessionId)}/topic`, {
method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ topic_id: topicId }),
}); },
async loadOlderMessages(sessionId, before, signal) {
const query = new URLSearchParams({ limit: "40", before });
const payload = await requestJson<{ messages?: unknown[]; message_page?: unknown }>(
`/session/${encodeURIComponent(sessionId)}/messages?${query}`, { signal });
return { messages: payload.messages ?? [], messagePage: readMessagePagination(payload.message_page) ?? { hasMore: false, beforeCursor: null } };
},
async createTaskCollaborationSession(taskId) {
return requestJson<AgentChatSession>(
`/task/${encodeURIComponent(taskId)}/collaboration/session`,
@@ -293,8 +359,9 @@ export function createAgentApiClient(
});
},
async loadSession(sessionId) {
const response = await request(`/session/${encodeURIComponent(sessionId)}`);
async loadSession(sessionId, options) {
const query = options?.messageLimit ? `?message_limit=${options.messageLimit}` : "";
const response = await request(`/session/${encodeURIComponent(sessionId)}${query}`, { signal: options?.signal });
const text = await response.text();
const data = text ? JSON.parse(text) : null;
@@ -310,7 +377,7 @@ export function createAgentApiClient(
async streamSession(sessionId, options) {
const response = await request(
`/session/${encodeURIComponent(sessionId)}/stream`,
`/session/${encodeURIComponent(sessionId)}/stream${options.messageLimit ? `?message_limit=${options.messageLimit}` : ""}`,
{ signal: options.signal }
);
@@ -564,6 +631,9 @@ function toSessionSummary(value: unknown): AgentChatSessionSummary | null {
return {
id,
title: normalizeTitle(value.title),
topicId: typeof value.topic_id === "string" ? value.topic_id : null,
topicName: typeof value.topic_name === "string" ? value.topic_name : undefined,
searchSnippet: typeof value.search_snippet === "string" ? value.search_snippet : undefined,
createdAt: toMillis(value.created_at),
updatedAt: toMillis(value.updated_at),
status: typeof value.status === "string" ? value.status : undefined,
@@ -591,7 +661,8 @@ function toLoadedSession(value: unknown): AgentLoadedChatSession {
sessionId: typeof value.session_id === "string" ? value.session_id : summary.id,
isTitleManuallyEdited:
typeof value.is_title_manually_edited === "boolean" ? value.is_title_manually_edited : false,
messages: Array.isArray(value.messages) ? value.messages : []
messages: Array.isArray(value.messages) ? value.messages : [],
messagePage: readMessagePagination(value.message_page)
};
}
@@ -657,6 +728,7 @@ function emitSessionSseEvent(
onEvent({
type: "state",
executionPlans: Array.isArray(data.execution_plans) ? data.execution_plans : undefined,
messagePage: readMessagePagination(data.message_page),
sessionId,
messages: Array.isArray(data.messages) ? data.messages : [],
isStreaming: data.is_streaming === true,
@@ -717,3 +789,8 @@ function compareSessionSummaries(left: AgentChatSessionSummary, right: AgentChat
return right.id.localeCompare(left.id);
}
function readMessagePagination(value: unknown): AgentMessagePagination | undefined {
if (!isRecord(value)) return undefined;
return { hasMore: value.has_more === true, beforeCursor: typeof value.before_cursor === "string" ? value.before_cursor : null };
}