feat(agent): complete non-video business grants with verified contracts
This commit is contained in:
parent
df7e6ee0e1
commit
2fb3ed8512
@ -7983,6 +7983,12 @@ export function CanvasChatPanel({
|
|||||||
const eventChatId = inFlightChatIdRef.current || activeChatId;
|
const eventChatId = inFlightChatIdRef.current || activeChatId;
|
||||||
const isEventForActiveChat = eventChatId === activeChatIdRef.current;
|
const isEventForActiveChat = eventChatId === activeChatIdRef.current;
|
||||||
|
|
||||||
|
if (dataPart.type === "data-canvasProjectMetadata") {
|
||||||
|
const payload = dataPart.data as { projectId?: string; title?: string };
|
||||||
|
const state = useCanvasStore.getState();
|
||||||
|
if (payload.projectId === state.projectId && typeof payload.title === "string") state.setProjectTitle(payload.title);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (dataPart.type === "data-canvasAgentRun") {
|
if (dataPart.type === "data-canvasAgentRun") {
|
||||||
window.dispatchEvent(new CustomEvent("canvas-agent-run:update", { detail: dataPart.data }));
|
window.dispatchEvent(new CustomEvent("canvas-agent-run:update", { detail: dataPart.data }));
|
||||||
return;
|
return;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -2340,6 +2340,19 @@
|
|||||||
"previouslyExposed": false,
|
"previouslyExposed": false,
|
||||||
"execution": "server"
|
"execution": "server"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "skills.post",
|
||||||
|
"title": "可访问技能列表",
|
||||||
|
"group": "技能",
|
||||||
|
"path": "/api/agents",
|
||||||
|
"method": "POST",
|
||||||
|
"hint": "query: categoryId、search(可选)。执行技能前用 loadProjectSkill 读取真实提示词和参数,再由当前原生核心编排已有工具。",
|
||||||
|
"readOnly": false,
|
||||||
|
"result": "json",
|
||||||
|
"authorized": true,
|
||||||
|
"previouslyExposed": false,
|
||||||
|
"execution": "server"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "skill-categories.get",
|
"id": "skill-categories.get",
|
||||||
"title": "技能分类",
|
"title": "技能分类",
|
||||||
@ -3302,6 +3315,266 @@
|
|||||||
"previouslyExposed": false,
|
"previouslyExposed": false,
|
||||||
"execution": "server"
|
"execution": "server"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "skill-item.get",
|
||||||
|
"title": "自有技能读取、编辑、删除",
|
||||||
|
"group": "技能",
|
||||||
|
"path": "/api/agents/[id]",
|
||||||
|
"method": "GET",
|
||||||
|
"hint": "pathParams: id;修改仅限本人技能,不能借管理员身份修改别人的公共技能。发布需用户明确要求。",
|
||||||
|
"readOnly": true,
|
||||||
|
"result": "json",
|
||||||
|
"authorized": true,
|
||||||
|
"previouslyExposed": false,
|
||||||
|
"execution": "server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "skill-item.put",
|
||||||
|
"title": "自有技能读取、编辑、删除",
|
||||||
|
"group": "技能",
|
||||||
|
"path": "/api/agents/[id]",
|
||||||
|
"method": "PUT",
|
||||||
|
"hint": "pathParams: id;修改仅限本人技能,不能借管理员身份修改别人的公共技能。发布需用户明确要求。",
|
||||||
|
"readOnly": false,
|
||||||
|
"result": "json",
|
||||||
|
"authorized": true,
|
||||||
|
"previouslyExposed": false,
|
||||||
|
"execution": "server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "skill-item.delete",
|
||||||
|
"title": "自有技能读取、编辑、删除",
|
||||||
|
"group": "技能",
|
||||||
|
"path": "/api/agents/[id]",
|
||||||
|
"method": "DELETE",
|
||||||
|
"hint": "pathParams: id;修改仅限本人技能,不能借管理员身份修改别人的公共技能。发布需用户明确要求。",
|
||||||
|
"readOnly": false,
|
||||||
|
"result": "json",
|
||||||
|
"authorized": true,
|
||||||
|
"previouslyExposed": false,
|
||||||
|
"execution": "server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "skill-fork.post",
|
||||||
|
"title": "复制可访问的技能",
|
||||||
|
"group": "技能",
|
||||||
|
"path": "/api/agents/[id]/fork",
|
||||||
|
"method": "POST",
|
||||||
|
"hint": "pathParams: id;复制到当前用户名下,不修改原技能",
|
||||||
|
"readOnly": false,
|
||||||
|
"result": "json",
|
||||||
|
"authorized": true,
|
||||||
|
"previouslyExposed": false,
|
||||||
|
"execution": "server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "skill-tags.get",
|
||||||
|
"title": "快捷技能标签与个人顺序",
|
||||||
|
"group": "技能",
|
||||||
|
"path": "/api/canvas-skill-tags",
|
||||||
|
"method": "GET",
|
||||||
|
"hint": "PATCH body: tagIds=用户希望的标签编号顺序,仅调整个人顺序;不修改全站标签配置",
|
||||||
|
"readOnly": true,
|
||||||
|
"result": "json",
|
||||||
|
"authorized": true,
|
||||||
|
"previouslyExposed": false,
|
||||||
|
"execution": "server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "skill-tags.patch",
|
||||||
|
"title": "快捷技能标签与个人顺序",
|
||||||
|
"group": "技能",
|
||||||
|
"path": "/api/canvas-skill-tags",
|
||||||
|
"method": "PATCH",
|
||||||
|
"hint": "PATCH body: tagIds=用户希望的标签编号顺序,仅调整个人顺序;不修改全站标签配置",
|
||||||
|
"readOnly": false,
|
||||||
|
"result": "json",
|
||||||
|
"authorized": true,
|
||||||
|
"previouslyExposed": false,
|
||||||
|
"execution": "server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "canvas-metadata.put",
|
||||||
|
"title": "修改当前图片画布名称与描述",
|
||||||
|
"group": "项目",
|
||||||
|
"path": "/api/canvas/[id]",
|
||||||
|
"method": "PUT",
|
||||||
|
"hint": "pathParams: id=当前画布编号;body仅允许title、description、thumbnail,不允许覆盖canvasData或修改工作区类型",
|
||||||
|
"readOnly": false,
|
||||||
|
"result": "json",
|
||||||
|
"authorized": true,
|
||||||
|
"previouslyExposed": false,
|
||||||
|
"execution": "server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "canvas-title-suggestion.post",
|
||||||
|
"title": "建议画布标题(不保存)",
|
||||||
|
"group": "项目",
|
||||||
|
"path": "/api/canvas/auto-title",
|
||||||
|
"method": "POST",
|
||||||
|
"hint": "body: prompt等上下文。此工具仅建议标题;需要改名时再调用canvas-metadata.put实际保存。",
|
||||||
|
"readOnly": false,
|
||||||
|
"result": "json",
|
||||||
|
"authorized": true,
|
||||||
|
"previouslyExposed": false,
|
||||||
|
"execution": "server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "prompt-tools.post",
|
||||||
|
"title": "提示词润色、翻译与分析",
|
||||||
|
"group": "详情与设计",
|
||||||
|
"path": "/api/canvas/prompt-tools",
|
||||||
|
"method": "POST",
|
||||||
|
"hint": "body: text或prompt及action等;使用实际接口契约,不执行视频任务",
|
||||||
|
"readOnly": false,
|
||||||
|
"result": "json",
|
||||||
|
"authorized": true,
|
||||||
|
"previouslyExposed": false,
|
||||||
|
"execution": "server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "layout-batches.get",
|
||||||
|
"title": "图片模板替换批次列表与创建",
|
||||||
|
"group": "详情与设计",
|
||||||
|
"path": "/api/canvas/layout-replace/batches",
|
||||||
|
"method": "GET",
|
||||||
|
"hint": "query/body: 当前projectId、真实模板与商品图;异步状态必须查询,不重复创建收费批次",
|
||||||
|
"readOnly": true,
|
||||||
|
"result": "json",
|
||||||
|
"authorized": true,
|
||||||
|
"previouslyExposed": false,
|
||||||
|
"execution": "server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "layout-batches.post",
|
||||||
|
"title": "图片模板替换批次列表与创建",
|
||||||
|
"group": "详情与设计",
|
||||||
|
"path": "/api/canvas/layout-replace/batches",
|
||||||
|
"method": "POST",
|
||||||
|
"hint": "query/body: 当前projectId、真实模板与商品图;异步状态必须查询,不重复创建收费批次",
|
||||||
|
"readOnly": false,
|
||||||
|
"result": "json",
|
||||||
|
"authorized": true,
|
||||||
|
"previouslyExposed": false,
|
||||||
|
"execution": "server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "layout-batch.get",
|
||||||
|
"title": "图片模板替换批次状态与修改",
|
||||||
|
"group": "详情与设计",
|
||||||
|
"path": "/api/canvas/layout-replace/batches/[batchId]",
|
||||||
|
"method": "GET",
|
||||||
|
"hint": "pathParams: batchId;只处理当前用户批次,先读取状态后修改",
|
||||||
|
"readOnly": true,
|
||||||
|
"result": "json",
|
||||||
|
"authorized": true,
|
||||||
|
"previouslyExposed": false,
|
||||||
|
"execution": "server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "layout-batch.patch",
|
||||||
|
"title": "图片模板替换批次状态与修改",
|
||||||
|
"group": "详情与设计",
|
||||||
|
"path": "/api/canvas/layout-replace/batches/[batchId]",
|
||||||
|
"method": "PATCH",
|
||||||
|
"hint": "pathParams: batchId;只处理当前用户批次,先读取状态后修改",
|
||||||
|
"readOnly": false,
|
||||||
|
"result": "json",
|
||||||
|
"authorized": true,
|
||||||
|
"previouslyExposed": false,
|
||||||
|
"execution": "server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "agent-detail-batches.get",
|
||||||
|
"title": "详情替换工作流批次",
|
||||||
|
"group": "详情与设计",
|
||||||
|
"path": "/api/agents/detail-replacement/batches",
|
||||||
|
"method": "GET",
|
||||||
|
"hint": "query/body按真实详情替换批次接口契约;仅当前用户创作项目,异步任务必须查询实际结果",
|
||||||
|
"readOnly": true,
|
||||||
|
"result": "json",
|
||||||
|
"authorized": true,
|
||||||
|
"previouslyExposed": false,
|
||||||
|
"execution": "server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "agent-detail-batches.post",
|
||||||
|
"title": "详情替换工作流批次",
|
||||||
|
"group": "详情与设计",
|
||||||
|
"path": "/api/agents/detail-replacement/batches",
|
||||||
|
"method": "POST",
|
||||||
|
"hint": "query/body按真实详情替换批次接口契约;仅当前用户创作项目,异步任务必须查询实际结果",
|
||||||
|
"readOnly": false,
|
||||||
|
"result": "json",
|
||||||
|
"authorized": true,
|
||||||
|
"previouslyExposed": false,
|
||||||
|
"execution": "server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "execution-snapshot.get",
|
||||||
|
"title": "本人创作执行快照",
|
||||||
|
"group": "项目",
|
||||||
|
"path": "/api/agent-execution-snapshots/[id]",
|
||||||
|
"method": "GET",
|
||||||
|
"hint": "pathParams: id;只读取当前用户有权限的执行记录",
|
||||||
|
"readOnly": true,
|
||||||
|
"result": "json",
|
||||||
|
"authorized": true,
|
||||||
|
"previouslyExposed": false,
|
||||||
|
"execution": "server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "image-quota.get",
|
||||||
|
"title": "当前用户图片额度",
|
||||||
|
"group": "项目",
|
||||||
|
"path": "/api/user/image-quota",
|
||||||
|
"method": "GET",
|
||||||
|
"hint": "无需参数,不读取其他用户账户",
|
||||||
|
"readOnly": true,
|
||||||
|
"result": "json",
|
||||||
|
"authorized": true,
|
||||||
|
"previouslyExposed": false,
|
||||||
|
"execution": "server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "image-stats.get",
|
||||||
|
"title": "当前用户生图统计",
|
||||||
|
"group": "项目",
|
||||||
|
"path": "/api/user/image-stats",
|
||||||
|
"method": "GET",
|
||||||
|
"hint": "无需参数",
|
||||||
|
"readOnly": true,
|
||||||
|
"result": "json",
|
||||||
|
"authorized": true,
|
||||||
|
"previouslyExposed": false,
|
||||||
|
"execution": "server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tutorials.get",
|
||||||
|
"title": "使用教程",
|
||||||
|
"group": "帮助",
|
||||||
|
"path": "/api/tutorial-videos",
|
||||||
|
"method": "GET",
|
||||||
|
"hint": "只读取教程,不新增或修改全站教程",
|
||||||
|
"readOnly": true,
|
||||||
|
"result": "json",
|
||||||
|
"authorized": true,
|
||||||
|
"previouslyExposed": false,
|
||||||
|
"execution": "server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "navigation.get",
|
||||||
|
"title": "业务导航与帮助入口",
|
||||||
|
"group": "帮助",
|
||||||
|
"path": "/api/nav-links",
|
||||||
|
"method": "GET",
|
||||||
|
"hint": "只读取导航,不修改全站配置",
|
||||||
|
"readOnly": true,
|
||||||
|
"result": "json",
|
||||||
|
"authorized": true,
|
||||||
|
"previouslyExposed": false,
|
||||||
|
"execution": "server"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "getWeather",
|
"id": "getWeather",
|
||||||
"title": "getWeather",
|
"title": "getWeather",
|
||||||
@ -3348,8 +3621,11 @@
|
|||||||
"execution": "server"
|
"execution": "server"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"verifiedRoutes": 74,
|
"verifiedRoutes": 96,
|
||||||
"deploymentOnly": [
|
"deploymentOnly": [
|
||||||
"smart-edit.get"
|
"smart-edit.get",
|
||||||
|
"prompt-tools.post",
|
||||||
|
"agent-detail-batches.get",
|
||||||
|
"agent-detail-batches.post"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ function add(group: string, id: string, title: string, path: string, methods: st
|
|||||||
id: `${id}.${method.toLowerCase()}`, title, group, path, method, hint, readOnly: method === "GET", result: "json", authorized: true,
|
id: `${id}.${method.toLowerCase()}`, title, group, path, method, hint, readOnly: method === "GET", result: "json", authorized: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
add("技能", "skills", "可访问技能列表", "/api/agents", "GET", "query: categoryId、search(可选)。执行技能前用 loadProjectSkill 读取真实提示词和参数,再由当前原生核心编排已有工具。");
|
add("技能", "skills", "可访问技能列表", "/api/agents", "GET,POST", "query: categoryId、search(可选)。执行技能前用 loadProjectSkill 读取真实提示词和参数,再由当前原生核心编排已有工具。");
|
||||||
add("技能", "skill-categories", "技能分类", "/api/agent-categories", "GET", "无需参数");
|
add("技能", "skill-categories", "技能分类", "/api/agent-categories", "GET", "无需参数");
|
||||||
add("素材", "materials", "商品 SKU/SPU 素材检索", "/api/agent-workflow/material-search", "GET", "query: keyword必填,source=primary/secondary、queryType=sku/spu、requiredCount可选");
|
add("素材", "materials", "商品 SKU/SPU 素材检索", "/api/agent-workflow/material-search", "GET", "query: keyword必填,source=primary/secondary、queryType=sku/spu、requiredCount可选");
|
||||||
add("素材", "references", "参考图检索", "/api/agent-workflow/reference-search", "GET", "query: keyword、source、queryType;以接口返回的实际素材为准");
|
add("素材", "references", "参考图检索", "/api/agent-workflow/reference-search", "GET", "query: keyword、source、queryType;以接口返回的实际素材为准");
|
||||||
@ -64,6 +64,20 @@ add("3D", "world-task", "场景生成任务状态", "/api/canvas/world/tasks/[ta
|
|||||||
add("智能编辑", "smart-edit", "智能图层编辑能力", "/api/canvas/smart-edit/capabilities", "GET", "无需参数;仅当该部署版本已有此功能时可用");
|
add("智能编辑", "smart-edit", "智能图层编辑能力", "/api/canvas/smart-edit/capabilities", "GET", "无需参数;仅当该部署版本已有此功能时可用");
|
||||||
add("文档", "documents", "读取与保存用户文档", "/api/document", "GET,POST", "query: id=文档UUID;POST body: title、kind=text/code/sheet/image、content。更新前先读取原文档,不操作其他用户文档。");
|
add("文档", "documents", "读取与保存用户文档", "/api/document", "GET,POST", "query: id=文档UUID;POST body: title、kind=text/code/sheet/image、content。更新前先读取原文档,不操作其他用户文档。");
|
||||||
add("图像处理", "enhance-pro", "专业高清处理任务与查询", "/api/enhance/pro", "GET,POST", "POST body按接口实际模型与尺寸要求,GET query: taskId。仅任务成功后才能交付,不能重复付费提交。");
|
add("图像处理", "enhance-pro", "专业高清处理任务与查询", "/api/enhance/pro", "GET,POST", "POST body按接口实际模型与尺寸要求,GET query: taskId。仅任务成功后才能交付,不能重复付费提交。");
|
||||||
|
add("技能", "skill-item", "自有技能读取、编辑、删除", "/api/agents/[id]", "GET,PUT,DELETE", "pathParams: id;修改仅限本人技能,不能借管理员身份修改别人的公共技能。发布需用户明确要求。");
|
||||||
|
add("技能", "skill-fork", "复制可访问的技能", "/api/agents/[id]/fork", "POST", "pathParams: id;复制到当前用户名下,不修改原技能");
|
||||||
|
add("技能", "skill-tags", "快捷技能标签与个人顺序", "/api/canvas-skill-tags", "GET,PATCH", "PATCH body: tagIds=用户希望的标签编号顺序,仅调整个人顺序;不修改全站标签配置");
|
||||||
|
add("项目", "canvas-metadata", "修改当前图片画布名称与描述", "/api/canvas/[id]", "PUT", "pathParams: id=当前画布编号;body仅允许title、description、thumbnail,不允许覆盖canvasData或修改工作区类型");
|
||||||
|
add("项目", "canvas-title-suggestion", "建议画布标题(不保存)", "/api/canvas/auto-title", "POST", "body: prompt等上下文。此工具仅建议标题;需要改名时再调用canvas-metadata.put实际保存。");
|
||||||
|
add("详情与设计", "prompt-tools", "提示词润色、翻译与分析", "/api/canvas/prompt-tools", "POST", "body: text或prompt及action等;使用实际接口契约,不执行视频任务");
|
||||||
|
add("详情与设计", "layout-batches", "图片模板替换批次列表与创建", "/api/canvas/layout-replace/batches", "GET,POST", "query/body: 当前projectId、真实模板与商品图;异步状态必须查询,不重复创建收费批次");
|
||||||
|
add("详情与设计", "layout-batch", "图片模板替换批次状态与修改", "/api/canvas/layout-replace/batches/[batchId]", "GET,PATCH", "pathParams: batchId;只处理当前用户批次,先读取状态后修改");
|
||||||
|
add("详情与设计", "agent-detail-batches", "详情替换工作流批次", "/api/agents/detail-replacement/batches", "GET,POST", "query/body按真实详情替换批次接口契约;仅当前用户创作项目,异步任务必须查询实际结果");
|
||||||
|
add("项目", "execution-snapshot", "本人创作执行快照", "/api/agent-execution-snapshots/[id]", "GET", "pathParams: id;只读取当前用户有权限的执行记录");
|
||||||
|
add("项目", "image-quota", "当前用户图片额度", "/api/user/image-quota", "GET", "无需参数,不读取其他用户账户");
|
||||||
|
add("项目", "image-stats", "当前用户生图统计", "/api/user/image-stats", "GET", "无需参数");
|
||||||
|
add("帮助", "tutorials", "使用教程", "/api/tutorial-videos", "GET", "只读取教程,不新增或修改全站教程");
|
||||||
|
add("帮助", "navigation", "业务导航与帮助入口", "/api/nav-links", "GET", "只读取导航,不修改全站配置");
|
||||||
export function projectOperationAuthorized(operation: ProjectAgentOperation) {
|
export function projectOperationAuthorized(operation: ProjectAgentOperation) {
|
||||||
return operation.group !== "分镜任务";
|
return operation.group !== "分镜任务";
|
||||||
}
|
}
|
||||||
@ -87,3 +101,9 @@ export function sanitizeProjectToolOutput(value: unknown): unknown {
|
|||||||
.map(([key, entry]) => [key, sanitizeProjectToolOutput(entry)]));
|
.map(([key, entry]) => [key, sanitizeProjectToolOutput(entry)]));
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
export function validateCanvasMetadataInput(body: Record<string, unknown>) {
|
||||||
|
if (!Object.keys(body).length || Object.keys(body).some(key=>!["title","description","thumbnail"].includes(key))) throw new Error("仅允许修改画布名称、描述和封面");
|
||||||
|
if (body.title !== undefined && (typeof body.title !== "string" || !body.title.trim() || body.title.length>256)) throw new Error("画布名称必须为1到256字的文本");
|
||||||
|
if (body.description !== undefined && body.description !== null && (typeof body.description !== "string" || body.description.length>4096)) throw new Error("画布描述格式无效");
|
||||||
|
if (body.thumbnail !== undefined && body.thumbnail !== null && (typeof body.thumbnail !== "string" || body.thumbnail.length>4000 || (body.thumbnail && !/^https?:\/\//.test(body.thumbnail)))) throw new Error("封面必须为有效图片链接");
|
||||||
|
}
|
||||||
|
|||||||
@ -25,6 +25,32 @@
|
|||||||
"sort"
|
"sort"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"skills.post": {
|
||||||
|
"source": "app/api/agents/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"GET",
|
||||||
|
"POST"
|
||||||
|
],
|
||||||
|
"schemas": {
|
||||||
|
"listQuerySchema": "z.object({\n scope: z.enum([\"all\", \"public\", \"private\"]).default(\"all\"),\n type: z.enum([\"image\", \"video\", \"text\"]).optional(),\n sectionId: z.string().trim().max(64).optional(),\n level1Id: z.string().trim().max(64).optional(),\n level2Id: z.string().trim().max(64).optional(),\n level3Id: z.string().trim().max(64).optional(),\n productCategory: z.string().trim().max(64).optional(),\n audienceTag: z.string().trim().max(64).optional(),\n usageScenario: z.string().trim().max(64).optional(),\n brand: z.string().trim().max(64).optional(),\n keyword: z.string().trim().max(128).optional(),\n sort: z.enum([\"latest\", \"hottest\", \"most_used\"]).default(\"latest\"),\n})",
|
||||||
|
"slotSchema": "z.object({\n title: z.string().trim().max(128).optional(),\n promptTemplate: z.string().trim().min(1).max(8000),\n defaultParams: z.record(z.string(), z.any()).optional(),\n requireUpload: z.boolean().optional(),\n uploadLabel: z.string().trim().max(128).optional(),\n referenceAssetIds: z.array(z.string().uuid()).max(12).optional(),\n})"
|
||||||
|
},
|
||||||
|
"interfaces": {},
|
||||||
|
"queryKeys": [
|
||||||
|
"scope",
|
||||||
|
"type",
|
||||||
|
"sectionId",
|
||||||
|
"level1Id",
|
||||||
|
"level2Id",
|
||||||
|
"level3Id",
|
||||||
|
"productCategory",
|
||||||
|
"audienceTag",
|
||||||
|
"usageScenario",
|
||||||
|
"brand",
|
||||||
|
"keyword",
|
||||||
|
"sort"
|
||||||
|
]
|
||||||
|
},
|
||||||
"skill-categories.get": {
|
"skill-categories.get": {
|
||||||
"source": "app/api/agent-categories/route.ts",
|
"source": "app/api/agent-categories/route.ts",
|
||||||
"methods": [
|
"methods": [
|
||||||
@ -818,6 +844,16 @@
|
|||||||
"providerId"
|
"providerId"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"smart-edit.get": {
|
||||||
|
"source": "app/api/canvas/smart-edit/capabilities/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"GET"
|
||||||
|
],
|
||||||
|
"schemas": {},
|
||||||
|
"interfaces": {},
|
||||||
|
"queryKeys": [],
|
||||||
|
"verifiedOnDeployment": true
|
||||||
|
},
|
||||||
"documents.get": {
|
"documents.get": {
|
||||||
"source": "app/(chat)/api/document/route.ts",
|
"source": "app/(chat)/api/document/route.ts",
|
||||||
"methods": [
|
"methods": [
|
||||||
@ -869,5 +905,248 @@
|
|||||||
"queryKeys": [
|
"queryKeys": [
|
||||||
"taskId"
|
"taskId"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"skill-item.get": {
|
||||||
|
"source": "app/api/agents/[id]/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"GET",
|
||||||
|
"PUT",
|
||||||
|
"DELETE"
|
||||||
|
],
|
||||||
|
"schemas": {
|
||||||
|
"slotSchema": "z.object({\n title: z.string().trim().max(128).optional(),\n promptTemplate: z.string().trim().min(1).max(8000),\n defaultParams: z.record(z.string(), z.any()).nullable().optional(),\n requireUpload: z.boolean().optional(),\n uploadLabel: z.string().trim().max(128).optional(),\n referenceAssetIds: z.array(z.string().uuid()).max(12).optional(),\n})"
|
||||||
|
},
|
||||||
|
"interfaces": {},
|
||||||
|
"queryKeys": []
|
||||||
|
},
|
||||||
|
"skill-item.put": {
|
||||||
|
"source": "app/api/agents/[id]/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"GET",
|
||||||
|
"PUT",
|
||||||
|
"DELETE"
|
||||||
|
],
|
||||||
|
"schemas": {
|
||||||
|
"slotSchema": "z.object({\n title: z.string().trim().max(128).optional(),\n promptTemplate: z.string().trim().min(1).max(8000),\n defaultParams: z.record(z.string(), z.any()).nullable().optional(),\n requireUpload: z.boolean().optional(),\n uploadLabel: z.string().trim().max(128).optional(),\n referenceAssetIds: z.array(z.string().uuid()).max(12).optional(),\n})"
|
||||||
|
},
|
||||||
|
"interfaces": {},
|
||||||
|
"queryKeys": []
|
||||||
|
},
|
||||||
|
"skill-item.delete": {
|
||||||
|
"source": "app/api/agents/[id]/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"GET",
|
||||||
|
"PUT",
|
||||||
|
"DELETE"
|
||||||
|
],
|
||||||
|
"schemas": {
|
||||||
|
"slotSchema": "z.object({\n title: z.string().trim().max(128).optional(),\n promptTemplate: z.string().trim().min(1).max(8000),\n defaultParams: z.record(z.string(), z.any()).nullable().optional(),\n requireUpload: z.boolean().optional(),\n uploadLabel: z.string().trim().max(128).optional(),\n referenceAssetIds: z.array(z.string().uuid()).max(12).optional(),\n})"
|
||||||
|
},
|
||||||
|
"interfaces": {},
|
||||||
|
"queryKeys": []
|
||||||
|
},
|
||||||
|
"skill-fork.post": {
|
||||||
|
"source": "app/api/agents/[id]/fork/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"POST"
|
||||||
|
],
|
||||||
|
"schemas": {
|
||||||
|
"forkSchema": "z.object({\n name: z.string().trim().min(1).max(128).optional(),\n})"
|
||||||
|
},
|
||||||
|
"interfaces": {},
|
||||||
|
"queryKeys": []
|
||||||
|
},
|
||||||
|
"skill-tags.get": {
|
||||||
|
"source": "app/api/canvas-skill-tags/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"GET",
|
||||||
|
"PATCH",
|
||||||
|
"PUT"
|
||||||
|
],
|
||||||
|
"schemas": {},
|
||||||
|
"interfaces": {},
|
||||||
|
"queryKeys": []
|
||||||
|
},
|
||||||
|
"skill-tags.patch": {
|
||||||
|
"source": "app/api/canvas-skill-tags/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"GET",
|
||||||
|
"PATCH",
|
||||||
|
"PUT"
|
||||||
|
],
|
||||||
|
"schemas": {},
|
||||||
|
"interfaces": {},
|
||||||
|
"queryKeys": []
|
||||||
|
},
|
||||||
|
"canvas-metadata.put": {
|
||||||
|
"source": "app/api/canvas/[id]/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"GET",
|
||||||
|
"PUT",
|
||||||
|
"DELETE"
|
||||||
|
],
|
||||||
|
"schemas": {},
|
||||||
|
"interfaces": {},
|
||||||
|
"queryKeys": []
|
||||||
|
},
|
||||||
|
"canvas-title-suggestion.post": {
|
||||||
|
"source": "app/api/canvas/auto-title/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"POST"
|
||||||
|
],
|
||||||
|
"schemas": {},
|
||||||
|
"interfaces": {},
|
||||||
|
"queryKeys": []
|
||||||
|
},
|
||||||
|
"prompt-tools.post": {
|
||||||
|
"source": "app/api/canvas/prompt-tools/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"POST"
|
||||||
|
],
|
||||||
|
"schemas": {
|
||||||
|
"promptToolSchema": "z.object({\n mode: z.enum([\"optimize\", \"translate\"]),\n prompt: z.string().trim().min(1, \"请先输入提示词\").max(12000, \"提示词不能超过 12000 字\"),\n model: z.string().trim().max(200).optional(),\n})"
|
||||||
|
},
|
||||||
|
"interfaces": {},
|
||||||
|
"queryKeys": [],
|
||||||
|
"verifiedOnDeployment": true
|
||||||
|
},
|
||||||
|
"layout-batches.get": {
|
||||||
|
"source": "app/api/canvas/layout-replace/batches/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"GET",
|
||||||
|
"POST"
|
||||||
|
],
|
||||||
|
"schemas": {},
|
||||||
|
"interfaces": {},
|
||||||
|
"queryKeys": [
|
||||||
|
"projectId",
|
||||||
|
"sourceNodeId"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"layout-batches.post": {
|
||||||
|
"source": "app/api/canvas/layout-replace/batches/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"GET",
|
||||||
|
"POST"
|
||||||
|
],
|
||||||
|
"schemas": {},
|
||||||
|
"interfaces": {},
|
||||||
|
"queryKeys": [
|
||||||
|
"projectId",
|
||||||
|
"sourceNodeId"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"layout-batch.get": {
|
||||||
|
"source": "app/api/canvas/layout-replace/batches/[batchId]/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"GET",
|
||||||
|
"PATCH"
|
||||||
|
],
|
||||||
|
"schemas": {},
|
||||||
|
"interfaces": {},
|
||||||
|
"queryKeys": []
|
||||||
|
},
|
||||||
|
"layout-batch.patch": {
|
||||||
|
"source": "app/api/canvas/layout-replace/batches/[batchId]/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"GET",
|
||||||
|
"PATCH"
|
||||||
|
],
|
||||||
|
"schemas": {},
|
||||||
|
"interfaces": {},
|
||||||
|
"queryKeys": []
|
||||||
|
},
|
||||||
|
"agent-detail-batches.get": {
|
||||||
|
"source": "app/api/agents/detail-replacement/batches/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"GET",
|
||||||
|
"POST"
|
||||||
|
],
|
||||||
|
"schemas": {},
|
||||||
|
"interfaces": {
|
||||||
|
"DetailReplacementBatchRequest": "export type DetailReplacementBatchRequest = {\n taskKind: \"agent-detail-replacement-batch\";\n projectId: string;\n chatId: string;\n submissionKey?: string;\n model: string;\n prompt: string;\n userPrompt: string;\n imageSize: string;\n replacementStrategy: \"product_only\" | \"scene_replace\";\n productImageUrls: string[];\n modelImageUrl: string | null;\n references: DetailReplacementBatchReference[];\n};"
|
||||||
|
},
|
||||||
|
"queryKeys": [
|
||||||
|
"projectId",
|
||||||
|
"taskId"
|
||||||
|
],
|
||||||
|
"verifiedOnDeployment": true
|
||||||
|
},
|
||||||
|
"agent-detail-batches.post": {
|
||||||
|
"source": "app/api/agents/detail-replacement/batches/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"GET",
|
||||||
|
"POST"
|
||||||
|
],
|
||||||
|
"schemas": {},
|
||||||
|
"interfaces": {
|
||||||
|
"DetailReplacementBatchRequest": "export type DetailReplacementBatchRequest = {\n taskKind: \"agent-detail-replacement-batch\";\n projectId: string;\n chatId: string;\n submissionKey?: string;\n model: string;\n prompt: string;\n userPrompt: string;\n imageSize: string;\n replacementStrategy: \"product_only\" | \"scene_replace\";\n productImageUrls: string[];\n modelImageUrl: string | null;\n references: DetailReplacementBatchReference[];\n};"
|
||||||
|
},
|
||||||
|
"queryKeys": [
|
||||||
|
"projectId",
|
||||||
|
"taskId"
|
||||||
|
],
|
||||||
|
"verifiedOnDeployment": true
|
||||||
|
},
|
||||||
|
"execution-snapshot.get": {
|
||||||
|
"source": "app/api/agent-execution-snapshots/[id]/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"GET"
|
||||||
|
],
|
||||||
|
"schemas": {},
|
||||||
|
"interfaces": {},
|
||||||
|
"queryKeys": []
|
||||||
|
},
|
||||||
|
"image-quota.get": {
|
||||||
|
"source": "app/api/user/image-quota/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"GET"
|
||||||
|
],
|
||||||
|
"schemas": {},
|
||||||
|
"interfaces": {},
|
||||||
|
"queryKeys": []
|
||||||
|
},
|
||||||
|
"image-stats.get": {
|
||||||
|
"source": "app/api/user/image-stats/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"GET"
|
||||||
|
],
|
||||||
|
"schemas": {},
|
||||||
|
"interfaces": {},
|
||||||
|
"queryKeys": []
|
||||||
|
},
|
||||||
|
"tutorials.get": {
|
||||||
|
"source": "app/api/tutorial-videos/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"GET",
|
||||||
|
"POST",
|
||||||
|
"PUT",
|
||||||
|
"DELETE"
|
||||||
|
],
|
||||||
|
"schemas": {
|
||||||
|
"createSchema": "z.object({\n title: z.string().min(1).max(128),\n videoUrl: z.string().url(),\n coverUrl: z.string().url().optional().or(z.literal(\"\")).transform(v => v || null),\n description: z.string().nullable().optional(),\n order: z.number().int().default(0),\n isEnabled: z.boolean().default(true),\n})",
|
||||||
|
"updateSchema": "z.object({\n id: z.string().uuid(),\n title: z.string().min(1).max(128).optional(),\n videoUrl: z.string().url().optional(),\n coverUrl: z.string().url().optional().or(z.literal(\"\")).transform(v => v || null),\n description: z.string().nullable().optional(),\n order: z.number().int().optional(),\n isEnabled: z.boolean().optional(),\n})"
|
||||||
|
},
|
||||||
|
"interfaces": {},
|
||||||
|
"queryKeys": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"navigation.get": {
|
||||||
|
"source": "app/api/nav-links/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"GET",
|
||||||
|
"POST",
|
||||||
|
"PUT",
|
||||||
|
"DELETE"
|
||||||
|
],
|
||||||
|
"schemas": {
|
||||||
|
"createSchema": "z.object({\n name: z.string().min(1).max(64),\n url: z.string().url(),\n order: z.number().int().default(0),\n isEnabled: z.boolean().default(true),\n toolboxGroup: z.number().int().min(1).max(2).default(1), // 属于哪个工具箱\n})",
|
||||||
|
"updateSchema": "z.object({\n id: z.string().uuid(),\n name: z.string().min(1).max(64).optional(),\n url: z.string().url().optional(),\n order: z.number().int().optional(),\n isEnabled: z.boolean().optional(),\n toolboxGroup: z.number().int().min(1).max(2).optional(), // 属于哪个工具箱\n})"
|
||||||
|
},
|
||||||
|
"interfaces": {},
|
||||||
|
"queryKeys": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"smart-edit.get": {
|
||||||
|
"source": "app/api/canvas/smart-edit/capabilities/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"GET"
|
||||||
|
],
|
||||||
|
"schemas": {},
|
||||||
|
"interfaces": {},
|
||||||
|
"queryKeys": []
|
||||||
|
},
|
||||||
|
"prompt-tools.post": {
|
||||||
|
"source": "app/api/canvas/prompt-tools/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"POST"
|
||||||
|
],
|
||||||
|
"schemas": {
|
||||||
|
"promptToolSchema": "z.object({\n mode: z.enum([\"optimize\", \"translate\"]),\n prompt: z.string().trim().min(1, \"请先输入提示词\").max(12000, \"提示词不能超过 12000 字\"),\n model: z.string().trim().max(200).optional(),\n})"
|
||||||
|
},
|
||||||
|
"interfaces": {},
|
||||||
|
"queryKeys": []
|
||||||
|
},
|
||||||
|
"agent-detail-batches.get": {
|
||||||
|
"source": "app/api/agents/detail-replacement/batches/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"GET",
|
||||||
|
"POST"
|
||||||
|
],
|
||||||
|
"schemas": {},
|
||||||
|
"interfaces": {
|
||||||
|
"DetailReplacementBatchRequest": "export type DetailReplacementBatchRequest = {\n taskKind: \"agent-detail-replacement-batch\";\n projectId: string;\n chatId: string;\n submissionKey?: string;\n model: string;\n prompt: string;\n userPrompt: string;\n imageSize: string;\n replacementStrategy: \"product_only\" | \"scene_replace\";\n productImageUrls: string[];\n modelImageUrl: string | null;\n references: DetailReplacementBatchReference[];\n};"
|
||||||
|
},
|
||||||
|
"queryKeys": [
|
||||||
|
"projectId",
|
||||||
|
"taskId"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"agent-detail-batches.post": {
|
||||||
|
"source": "app/api/agents/detail-replacement/batches/route.ts",
|
||||||
|
"methods": [
|
||||||
|
"GET",
|
||||||
|
"POST"
|
||||||
|
],
|
||||||
|
"schemas": {},
|
||||||
|
"interfaces": {
|
||||||
|
"DetailReplacementBatchRequest": "export type DetailReplacementBatchRequest = {\n taskKind: \"agent-detail-replacement-batch\";\n projectId: string;\n chatId: string;\n submissionKey?: string;\n model: string;\n prompt: string;\n userPrompt: string;\n imageSize: string;\n replacementStrategy: \"product_only\" | \"scene_replace\";\n productImageUrls: string[];\n modelImageUrl: string | null;\n references: DetailReplacementBatchReference[];\n};"
|
||||||
|
},
|
||||||
|
"queryKeys": [
|
||||||
|
"projectId",
|
||||||
|
"taskId"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,7 +11,7 @@ import { getWeather } from "@/lib/ai/tools/get-weather";
|
|||||||
import { createDocument } from "@/lib/ai/tools/create-document";
|
import { createDocument } from "@/lib/ai/tools/create-document";
|
||||||
import { updateDocument } from "@/lib/ai/tools/update-document";
|
import { updateDocument } from "@/lib/ai/tools/update-document";
|
||||||
import { requestSuggestions } from "@/lib/ai/tools/request-suggestions";
|
import { requestSuggestions } from "@/lib/ai/tools/request-suggestions";
|
||||||
import { PROJECT_AGENT_OPERATIONS, projectOperationAuthorized, projectOperationUrl, sanitizeProjectToolOutput } from "./project-operation-catalog";
|
import { PROJECT_AGENT_OPERATIONS, projectOperationAuthorized, projectOperationUrl, sanitizeProjectToolOutput, validateCanvasMetadataInput } from "./project-operation-catalog";
|
||||||
import { isExplicitDestructiveIntent } from "./capability-access";
|
import { isExplicitDestructiveIntent } from "./capability-access";
|
||||||
import type { CanvasAgentTool } from "./loop";
|
import type { CanvasAgentTool } from "./loop";
|
||||||
|
|
||||||
@ -41,6 +41,16 @@ export async function registerProjectAgentTools(params: {
|
|||||||
if (operation.path.startsWith("/api/canvas/[id]")) {
|
if (operation.path.startsWith("/api/canvas/[id]")) {
|
||||||
if (args.pathParams.id !== params.projectId) throw new Error("画布业务接口仅授权当前画布,不能跨画布读取或写入");
|
if (args.pathParams.id !== params.projectId) throw new Error("画布业务接口仅授权当前画布,不能跨画布读取或写入");
|
||||||
}
|
}
|
||||||
|
if (operation.id === "canvas-metadata.put") {
|
||||||
|
try { validateCanvasMetadataInput(args.body || {}); }
|
||||||
|
catch { throw new AgentToolRejectedError("画布元数据格式无效,未提交操作;只允许名称、描述与封面,不能覆盖节点或更改工作区类型"); }
|
||||||
|
}
|
||||||
|
if (operation.id.startsWith("skill-item.") && !operation.readOnly) {
|
||||||
|
const preset = await getAccessibleAgentPresetById({ id: args.pathParams.id, userId });
|
||||||
|
if (!preset || preset.ownerUserId !== userId) throw new Error("仅授权修改本人技能,公共技能管理请使用管理员界面");
|
||||||
|
}
|
||||||
|
if ((args.body?.visibility === "public" || args.body?.isPublic === true) && !/发布|公开/.test(params.objective))
|
||||||
|
throw new Error("公开发布需要用户明确要求,不能把创建草稿当作发布授权");
|
||||||
if (operation.method === "DELETE" && (!isExplicitDestructiveIntent(params.objective) || !Object.values(args.pathParams).some(id => params.objective.includes(id))))
|
if (operation.method === "DELETE" && (!isExplicitDestructiveIntent(params.objective) || !Object.values(args.pathParams).some(id => params.objective.includes(id))))
|
||||||
throw new Error("删除项目文件需要用户明确提出删除,并在要求中指定文件编号;模型不能自行授权");
|
throw new Error("删除项目文件需要用户明确提出删除,并在要求中指定文件编号;模型不能自行授权");
|
||||||
if (operation.id === "canvas-list.post" && !/新建|创建|新增/.test(params.objective)) throw new Error("请先确认需要新建画布");
|
if (operation.id === "canvas-list.post" && !/新建|创建|新增/.test(params.objective)) throw new Error("请先确认需要新建画布");
|
||||||
@ -62,8 +72,12 @@ export async function registerProjectAgentTools(params: {
|
|||||||
if ([400,401,403,404,405,422].includes(response.status)) throw new AgentToolRejectedError(`业务工具参数或权限未通过(${response.status}),未提交操作。${typeof payload?.error === "string" ? payload.error.slice(0,200) : "请先读取该操作的真实请求契约"}`);
|
if ([400,401,403,404,405,422].includes(response.status)) throw new AgentToolRejectedError(`业务工具参数或权限未通过(${response.status}),未提交操作。${typeof payload?.error === "string" ? payload.error.slice(0,200) : "请先读取该操作的真实请求契约"}`);
|
||||||
if (!response.ok || payload?.success === false || payload?.error) throw new Error(`业务工具 ${operation.title} 未成功(${response.status}):${typeof payload?.error === "string" ? payload.error.slice(0, 200) : "请核对输入参数或底层服务权限"}`);
|
if (!response.ok || payload?.success === false || payload?.error) throw new Error(`业务工具 ${operation.title} 未成功(${response.status}):${typeof payload?.error === "string" ? payload.error.slice(0, 200) : "请核对输入参数或底层服务权限"}`);
|
||||||
if (operation.id === "canvas-list.get" && Array.isArray(payload?.data)) payload.data = payload.data.filter((row: { workspaceType?: string }) => row.workspaceType !== "video");
|
if (operation.id === "canvas-list.get" && Array.isArray(payload?.data)) payload.data = payload.data.filter((row: { workspaceType?: string }) => row.workspaceType !== "video");
|
||||||
const status = payload?.processing || ["pending", "running", "queued"].includes(payload?.status) ? "pending" : payload?.status === "failed" ? "failed" : "succeeded";
|
const envelope = payload?.data && typeof payload.data === "object" && !Array.isArray(payload.data) ? payload.data : payload?.batch || payload;
|
||||||
return { projectService: true, operationId: operation.id, status, taskId: payload?.taskId,
|
const state = payload?.status || envelope?.status;
|
||||||
|
const status = payload?.processing || envelope?.processing || ["pending", "running", "queued", "processing"].includes(state) ? "pending" : state === "failed" ? "failed" : "succeeded";
|
||||||
|
if (operation.id === "canvas-metadata.put" && typeof payload?.data?.title === "string")
|
||||||
|
params.emit("data-canvasProjectMetadata", { projectId: params.projectId, title: payload.data.title });
|
||||||
|
return { projectService: true, operationId: operation.id, status, taskId: payload?.taskId || envelope?.taskId || (payload?.batch ? envelope.id : undefined),
|
||||||
result: sanitizeProjectToolOutput(payload), message: status === "pending" ? "仅已受理,必须轮询状态确认实际结果;禁止重复创建任务。" : "已获得真实业务接口结果" };
|
result: sanitizeProjectToolOutput(payload), message: status === "pending" ? "仅已受理,必须轮询状态确认实际结果;禁止重复创建任务。" : "已获得真实业务接口结果" };
|
||||||
}
|
}
|
||||||
register("readProjectService", "读取项目业务数据。先 listProjectServices 查看 operationId、pathParams和query要求;仅执行已授权GET接口。", invokeSchema, (input, id) => invoke(input, id, true));
|
register("readProjectService", "读取项目业务数据。先 listProjectServices 查看 operationId、pathParams和query要求;仅执行已授权GET接口。", invokeSchema, (input, id) => invoke(input, id, true));
|
||||||
|
|||||||
@ -2,14 +2,15 @@ import ts from 'typescript';
|
|||||||
import { readFile, writeFile, access } from 'node:fs/promises';
|
import { readFile, writeFile, access } from 'node:fs/promises';
|
||||||
import { execFileSync } from 'node:child_process';
|
import { execFileSync } from 'node:child_process';
|
||||||
const catalog = JSON.parse(execFileSync(process.execPath, ['node_modules/tsx/dist/cli.mjs','-e', "import {PROJECT_AGENT_OPERATIONS} from './lib/ai/canvas-agent-runtime/project-operation-catalog';console.log(JSON.stringify(PROJECT_AGENT_OPERATIONS))"], { encoding: 'utf8' }));
|
const catalog = JSON.parse(execFileSync(process.execPath, ['node_modules/tsx/dist/cli.mjs','-e', "import {PROJECT_AGENT_OPERATIONS} from './lib/ai/canvas-agent-runtime/project-operation-catalog';console.log(JSON.stringify(PROJECT_AGENT_OPERATIONS))"], { encoding: 'utf8' }));
|
||||||
const contracts = {}; const mismatch = [];
|
const productionContracts = JSON.parse(await readFile('lib/ai/canvas-agent-runtime/project-operation-production-contracts.json','utf8'));
|
||||||
|
const contracts = {}; const mismatch = []; const deploymentOnly = [];
|
||||||
for (const operation of catalog) {
|
for (const operation of catalog) {
|
||||||
let file;
|
let file;
|
||||||
for (const root of ['app/api','app/(chat)/api']) {
|
for (const root of ['app/api','app/(chat)/api']) {
|
||||||
const path = `${root}/${operation.path.slice('/api/'.length)}/route.ts`;
|
const path = `${root}/${operation.path.slice('/api/'.length)}/route.ts`;
|
||||||
try { await access(path); file = path; break; } catch {}
|
try { await access(path); file = path; break; } catch {}
|
||||||
}
|
}
|
||||||
if (!file) { if (operation.id !== 'smart-edit.get') mismatch.push(`${operation.id}: missing route`); continue; }
|
if (!file) { deploymentOnly.push(operation.id); const contract = productionContracts[operation.id]; if (!contract || !contract.methods.includes(operation.method)) mismatch.push(`${operation.id}: missing verified deployment route`); else contracts[operation.id] = { ...contract, verifiedOnDeployment: true }; continue; }
|
||||||
const source = await readFile(file,'utf8');
|
const source = await readFile(file,'utf8');
|
||||||
const methods = [...source.matchAll(/export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE)/g)].map(match => match[1]);
|
const methods = [...source.matchAll(/export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE)/g)].map(match => match[1]);
|
||||||
if (!methods.includes(operation.method)) mismatch.push(`${operation.id}: method not implemented`);
|
if (!methods.includes(operation.method)) mismatch.push(`${operation.id}: method not implemented`);
|
||||||
@ -26,7 +27,7 @@ for (const operation of catalog) {
|
|||||||
}
|
}
|
||||||
if (mismatch.length) throw new Error(mismatch.join('\n'));
|
if (mismatch.length) throw new Error(mismatch.join('\n'));
|
||||||
await writeFile('lib/ai/canvas-agent-runtime/project-operation-contracts.json',JSON.stringify(contracts,null,2)+'\n');
|
await writeFile('lib/ai/canvas-agent-runtime/project-operation-contracts.json',JSON.stringify(contracts,null,2)+'\n');
|
||||||
console.log(JSON.stringify({ operations: catalog.length, routeContracts: Object.keys(contracts).length, deploymentOnly: 'smart-edit.get', unsupportedMethods: mismatch.length }));
|
console.log(JSON.stringify({ operations: catalog.length, routeContracts: Object.keys(contracts).length, deploymentOnly, unsupportedMethods: mismatch.length }));
|
||||||
|
|
||||||
const canvas = JSON.parse(execFileSync(process.execPath, ['node_modules/tsx/dist/cli.mjs','-e', "import {getCanvasAgentAccessManifest} from './lib/ai/canvas-agent-runtime/capability-access';console.log(JSON.stringify(getCanvasAgentAccessManifest()))"], { encoding: 'utf8' }));
|
const canvas = JSON.parse(execFileSync(process.execPath, ['node_modules/tsx/dist/cli.mjs','-e', "import {getCanvasAgentAccessManifest} from './lib/ai/canvas-agent-runtime/capability-access';console.log(JSON.stringify(getCanvasAgentAccessManifest()))"], { encoding: 'utf8' }));
|
||||||
const project = catalog.map(item=>({...item, authorized:item.group !== '分镜任务', previouslyExposed:false, execution:'server'}));
|
const project = catalog.map(item=>({...item, authorized:item.group !== '分镜任务', previouslyExposed:false, execution:'server'}));
|
||||||
@ -36,9 +37,9 @@ const legacyNames = [...(await readFile('lib/ai/tools-registry.ts','utf8')).matc
|
|||||||
if (legacyNames.some(name=>!legacyAliases[name])) throw new Error('Legacy tool missing from authorization audit');
|
if (legacyNames.some(name=>!legacyAliases[name])) throw new Error('Legacy tool missing from authorization audit');
|
||||||
const report = { legacyAliases, policy:'图片画布及非视频项目业务已授权;视频画布与分镜暂不授权;底层用户权限与服务开关仍生效。管理、密钥、支付、身份认证、系统任务和Claw不属于创作Agent工具。',
|
const report = { legacyAliases, policy:'图片画布及非视频项目业务已授权;视频画布与分镜暂不授权;底层用户权限与服务开关仍生效。管理、密钥、支付、身份认证、系统任务和Claw不属于创作Agent工具。',
|
||||||
upstreamCommit:'0a1fc56d279fd1001f78387bd81aec1c26767411', rows:[...canvas,...project,...utilities],
|
upstreamCommit:'0a1fc56d279fd1001f78387bd81aec1c26767411', rows:[...canvas,...project,...utilities],
|
||||||
verifiedRoutes:Object.keys(contracts).length, deploymentOnly:['smart-edit.get'] };
|
verifiedRoutes:Object.keys(contracts).length, deploymentOnly };
|
||||||
await writeFile('docs/specs/zcode-canvas/authorization-audit.json',JSON.stringify(report,null,2)+'\n');
|
await writeFile('docs/specs/zcode-canvas/authorization-audit.json',JSON.stringify(report,null,2)+'\n');
|
||||||
const esc=value=>String(value??'').replace(/[&<>"']/g,char=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[char]));
|
const esc=value=>String(value??'').replace(/[&<>"']/g,char=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[char]));
|
||||||
const rows=report.rows.map(row=>`<tr><td>${esc(row.workspace||row.group||'项目')}</td><td>${esc(row.title)}</td><td>${row.authorized?'已授权':'暂未授权'}</td><td>${esc(row.entry||row.id)}</td><td>${esc(row.note||row.hint||'')}</td></tr>`).join('');
|
const rows=report.rows.map(row=>`<tr><td>${esc(row.workspace||row.group||'项目')}</td><td>${esc(row.title)}</td><td>${row.authorized?'已授权':'暂未授权'}</td><td>${esc(row.entry||row.id)}</td><td>${esc(row.note||row.hint||'')}</td></tr>`).join('');
|
||||||
await writeFile('docs/specs/zcode-canvas/authorization-audit.html',`<!doctype html><html lang="zh-CN"><meta charset="utf-8"><title>ZCode Agent 工具授权清单</title><style>body{font:14px system-ui;margin:32px;color:#222}table{border-collapse:collapse;width:100%}td,th{border:1px solid #ddd;padding:9px;text-align:left}th{background:#f4f4f4;position:sticky;top:0}tr:nth-child(even){background:#fafafa}</style><h1>ZCode Agent 工具授权清单</h1><p>${esc(report.policy)}</p><p>共 ${report.rows.length} 项能力;每一项均保留实际执行方式与限制。接口鉴权不等于业务结果已完成。</p><table><thead><tr><th>范围</th><th>能力</th><th>授权</th><th>工具 / 操作编号</th><th>执行限制</th></tr></thead><tbody>${rows}</tbody></table></html>`);
|
await writeFile('docs/specs/zcode-canvas/authorization-audit.html',`<!doctype html><html lang="zh-CN"><meta charset="utf-8"><title>ZCode Agent 工具授权清单</title><style>body{font:14px system-ui;margin:32px;color:#222}table{border-collapse:collapse;width:100%}td,th{border:1px solid #ddd;padding:9px;text-align:left}th{background:#f4f4f4;position:sticky;top:0}tr:nth-child(even){background:#fafafa}</style><h1>ZCode Agent 工具授权清单</h1><p>${esc(report.policy)}</p><p>共 ${report.rows.length} 项授权检查(包含界面入口和接口映射);每一项均保留实际执行方式与限制。接口鉴权不等于业务结果已完成。</p><table><thead><tr><th>范围</th><th>能力</th><th>授权</th><th>工具 / 操作编号</th><th>执行限制</th></tr></thead><tbody>${rows}</tbody></table></html>`);
|
||||||
console.log(JSON.stringify({auditRows:report.rows.length, authorized:report.rows.filter(row=>row.authorized).length, paused:report.rows.filter(row=>!row.authorized).length, addedProjectOperations:project.filter(row=>row.authorized).length}));
|
console.log(JSON.stringify({auditRows:report.rows.length, authorized:report.rows.filter(row=>row.authorized).length, paused:report.rows.filter(row=>!row.authorized).length, addedProjectOperations:project.filter(row=>row.authorized).length}));
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { LEGACY_AGENT_TOOL_MAP } from "@/lib/ai/canvas-agent-runtime/legacy-tool-map";
|
import { LEGACY_AGENT_TOOL_MAP } from "@/lib/ai/canvas-agent-runtime/legacy-tool-map";
|
||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from "@playwright/test";
|
||||||
import { readFileSync, existsSync } from "node:fs";
|
import { readFileSync, existsSync } from "node:fs";
|
||||||
import { PROJECT_AGENT_OPERATIONS, projectOperationAuthorized, projectOperationUrl, sanitizeProjectToolOutput } from "@/lib/ai/canvas-agent-runtime/project-operation-catalog";
|
import { PROJECT_AGENT_OPERATIONS, projectOperationAuthorized, projectOperationUrl, sanitizeProjectToolOutput, validateCanvasMetadataInput } from "@/lib/ai/canvas-agent-runtime/project-operation-catalog";
|
||||||
import { shouldUseCanvasAgentRuntime } from "@/lib/ai/canvas-agent-runtime/routing";
|
import { shouldUseCanvasAgentRuntime } from "@/lib/ai/canvas-agent-runtime/routing";
|
||||||
import { getCanvasAgentAccessManifest } from "@/lib/ai/canvas-agent-runtime/capability-access";
|
import { getCanvasAgentAccessManifest } from "@/lib/ai/canvas-agent-runtime/capability-access";
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ test("business grant uses only fixed implemented route/method pairs and no manag
|
|||||||
expect(op.path).not.toMatch(/\/admin\/|\/internal\/|\/jobs\/|\/auth\/|\/payment\/|access-keys|clawd-chat|agent-chat/);
|
expect(op.path).not.toMatch(/\/admin\/|\/internal\/|\/jobs\/|\/auth\/|\/payment\/|access-keys|clawd-chat|agent-chat/);
|
||||||
const relative=op.path.slice('/api/'.length)+'/route.ts';
|
const relative=op.path.slice('/api/'.length)+'/route.ts';
|
||||||
const path=['app/api/','app/(chat)/api/'].map(base=>base+relative).find(existsSync);
|
const path=['app/api/','app/(chat)/api/'].map(base=>base+relative).find(existsSync);
|
||||||
if (!path) { expect(op.id).toBe('smart-edit.get'); continue; }
|
if (!path) { expect(['smart-edit.get','prompt-tools.post','agent-detail-batches.get','agent-detail-batches.post']).toContain(op.id); continue; }
|
||||||
expect(readFileSync(path,'utf8')).toMatch(new RegExp(`export\\s+(?:async\\s+)?function\\s+${op.method}\\b`));
|
expect(readFileSync(path,'utf8')).toMatch(new RegExp(`export\\s+(?:async\\s+)?function\\s+${op.method}\\b`));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -45,3 +45,9 @@ test("image workspace mode menus cannot bypass the paused video authorization",
|
|||||||
expect(manifest.find(item=>item.id===`canvas.ui.${key}`)).toMatchObject({workspace:'video',authorized:false});
|
expect(manifest.find(item=>item.id===`canvas.ui.${key}`)).toMatchObject({workspace:'video',authorized:false});
|
||||||
expect(manifest.find(item=>item.id==='canvas.ui.saveCanvas')).toMatchObject({workspace:'canvas',authorized:true});
|
expect(manifest.find(item=>item.id==='canvas.ui.saveCanvas')).toMatchObject({workspace:'canvas',authorized:true});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("project title updates cannot overwrite nodes, change workspace, or insert malformed metadata", () => {
|
||||||
|
expect(()=>validateCanvasMetadataInput({title:'正常标题'})).not.toThrow();
|
||||||
|
for (const body of [{title:{}},{title:''},{canvasData:{nodes:[]}},{workspaceType:'video'},{thumbnail:'file:///root/secret'}])
|
||||||
|
expect(()=>validateCanvasMetadataInput(body)).toThrow();
|
||||||
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user