feat(studio): Agent 编排服务端保存 + 我的编排列表(owner 隔离)

编排好的 Agent 现在可命名保存到服务端、跨会话可见;左侧「我的编排」列出本人全部。

- store: sundynix_agent 表(owner+name 唯一,Graph=React Flow {nodes,edges} JSON 含布局,
  UpdatedAt);ListAgents(最近在前)/SaveAgent(OnConflict 覆盖图+时间)/DeleteAgent。AutoMigrate +Agent。
- gateway: GET/POST/DELETE /api/v1/agents(owner 隔离,身份取自 X-User-ID)。
- 前端:api listAgents/saveAgent/deleteAgent;StudioView 左面板下半区「我的编排(N)」列出本人编排,
  点击载入(含布局)、悬停删除;工具栏 编排名+保存(服务端),去掉 localStorage 模板。

验证:curl 保存「合同审查流程」→ wt 列表含之,alice 列表为空(隔离)。Preview:示例图填名「尽调问答
Agent」保存 → 左「我的编排(2)」即时出现两条、可点载入。tsc+vite+gateway build 通过;重建 .app。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-06-13 16:23:03 +08:00
parent 337d4d7619
commit 4bf614a07c
6 changed files with 212 additions and 82 deletions
+32
View File
@@ -126,6 +126,38 @@ export interface VaultDoc {
content: string;
}
// ---- 我的 Agent 编排(服务端保存,owner 隔离)----
export interface AgentInfo {
name: string;
graph: string; // {nodes,edges} JSON
updated_at?: string;
}
export async function listAgents(id: Identity): Promise<AgentInfo[]> {
const res = await fetch(`${GATEWAY}/api/v1/agents`, { headers: idHeaders(id) });
const data = (await res.json()) as { agents?: AgentInfo[]; error?: string };
if (!res.ok) throw new Error(data.error ?? `list agents failed: ${res.status}`);
return data.agents ?? [];
}
export async function saveAgent(id: Identity, name: string, graph: string): Promise<void> {
const res = await fetch(`${GATEWAY}/api/v1/agents`, {
method: "POST",
headers: { "Content-Type": "application/json", ...idHeaders(id) },
body: JSON.stringify({ name, graph }),
});
const data = (await res.json()) as { name?: string; error?: string };
if (!res.ok || !data.name) throw new Error(data.error ?? `save agent failed: ${res.status}`);
}
export async function deleteAgent(id: Identity, name: string): Promise<void> {
const res = await fetch(`${GATEWAY}/api/v1/agents?name=${encodeURIComponent(name)}`, { method: "DELETE", headers: idHeaders(id) });
if (!res.ok) {
const data = (await res.json().catch(() => ({}))) as { error?: string };
throw new Error(data.error ?? `delete agent failed: ${res.status}`);
}
}
// listChatModels: GET /api/v1/admin/models?kind=chat —— 已登记的对话模型名(供编排 Agent 节点选择)。
export async function listChatModels(): Promise<string[]> {
try {