diff --git a/data/skills/script-agent/decision/SKILL.md b/data/skills/script-agent/decision/SKILL.md index 525e77e..6f921c9 100644 --- a/data/skills/script-agent/decision/SKILL.md +++ b/data/skills/script-agent/decision/SKILL.md @@ -130,7 +130,7 @@ description: >- - **触发词**:写剧本、编剧、分镜脚本、script - **输入**:事件表(通过 get_novel_events 获取) + 故事骨架 + 改编策略(阶段2-3产出) -- **输出**:分集剧本(节拍结构 + 分镜脚本) +- **输出**:分集剧本(节拍结构 + 分镜脚本)并写入 SQLite - **前置条件**:改编策略已完成 - **派发指令模板**: @@ -143,9 +143,20 @@ description: >- 2. 按节拍结构编写,严格控制总时长在{episodeDuration}分钟(约{wordsPerEpisode}字台词) 3. 每个节拍包含:场景描述、画面描述、台词、内心独白 4. 竖屏9:16格式,注意画面构图适配 -5. 调用 set_planData_script 保存结果 +5. 仅在用户确认后调用 insert_script_to_sqlite 写入剧本(SQL操作) ``` +### 阶段4额外安全规则(SQL写入) + +`insert_script_to_sqlite` 为 SQL 写入操作,决策层必须先与用户明确对话确认,再允许执行层写入: + +1. 先向用户展示待写入剧本摘要,并询问:`是否确认写入数据库?` +2. 仅当用户明确回复“确认/是/同意”后,才可派发写入指令 +3. 派发剧本写入任务时,指令中必须包含:`用户已确认写入SQL: 是` +4. 若用户未确认或拒绝,禁止派发 SQL 写入 + +当用户要求删除剧本时,决策层必须提醒:`剧本删除请在道具本管理中手动删除`。 + ## 调度规则 ### 派发执行任务 @@ -208,8 +219,10 @@ run_sub_agent( 1. **进度汇报**:每完成一个阶段,向用户汇报结果摘要和下一步计划 2. **审核结果展示**:将监督层的完整审核报告展示给用户,包括问题、建议和亮点 3. **等待用户决策**:审核发现问题时,**必须等待用户明确指示**后再执行修复,不可自行决定 -4. **确认关键决策**:涉及大幅偏离既定策略的修改时,先咨询用户 -5. **不暴露内部机制**:不向用户提及 Agent 名称、工具名称等实现细节 +4. **SQL写入确认**:调用 `insert_script_to_sqlite` 前,必须完成用户明确确认 +5. **删除请求提醒**:用户要求删除剧本时,提醒其在道具本管理中手动删除 +6. **确认关键决策**:涉及大幅偏离既定策略的修改时,先咨询用户 +7. **不暴露内部机制**:不向用户提及 Agent 名称、工具名称等实现细节 ## 错误处理 diff --git a/data/skills/script-agent/decision/references/pipeline.md b/data/skills/script-agent/decision/references/pipeline.md index 09d69b2..513040b 100644 --- a/data/skills/script-agent/decision/references/pipeline.md +++ b/data/skills/script-agent/decision/references/pipeline.md @@ -52,10 +52,11 @@ ``` 输入:事件表(get_novel_events) + planData.storySkeleton + planData.adaptationStrategy 处理:按集编写(可并行或逐集) -输出:planData.script -工具:get_novel_events + get_planData + get_novel_text → set_planData_script +输出:SQLite 中的剧本记录 +工具:get_novel_events + get_planData + get_novel_text → insert_script_to_sqlite 质量门:时长合规、台词字数、画面可执行、资产一致 前置条件:阶段3通过审核 +附加前置条件:用户已明确确认写入 SQL ``` ## 阶段间交互协议 diff --git a/data/skills/script-agent/execution/SKILL.md b/data/skills/script-agent/execution/SKILL.md index b824a49..16c7567 100644 --- a/data/skills/script-agent/execution/SKILL.md +++ b/data/skills/script-agent/execution/SKILL.md @@ -3,14 +3,14 @@ name: execution description: >- 短剧改编执行层Agent技能。负责接收决策层派发的具体任务并执行,涵盖事件提取、 故事骨架搭建、改编策略制定、剧本编写四大任务类型。使用 get_novel_text 读取原著, - 使用 get_novel_events 获取章节事件数据,使用 get_planData 获取工作区状态,使用 set_planData 系列工具保存产出物。 + 使用 get_novel_events 获取章节事件数据,使用 get_planData 获取工作区状态,使用写入工具保存产出物。 当收到决策层的 run_sub_agent 调用时激活。 --- # 执行层 Agent 技能指令 你是短剧改编项目的**执行层 Agent**,只接收决策层派发的任务指令并执行。 -你不与用户直接交互,所有产出物通过 `set_planData_*` 写入工作区。 +你不与用户直接交互,骨架与策略写入 planData,剧本写入 SQLite。 ## 工作区数据结构 @@ -24,7 +24,15 @@ const planData = { - 读取:`get_planData` → 返回完整 planData 对象 - 事件读取:`get_novel_events(ids:number[])` → 返回指定章节ID的事件数据 -- 写入:`set_planData_storySkeleton` / `set_planData_adaptationStrategy` / `set_planData_script` +- 写入:`set_planData_storySkeleton` / `set_planData_adaptationStrategy` / `insert_script_to_sqlite` + +### SQL写入安全约束(剧本) + +`insert_script_to_sqlite` 属于 SQL 写入操作,执行前必须满足以下条件: + +1. 决策层指令中明确包含:`用户已确认写入SQL: 是` +2. 若未包含该确认标记,执行层必须拒绝写入并返回:`缺少用户确认,未执行 SQL 写入` +3. 执行层不处理剧本删除请求;如收到删除诉求,返回提醒:`请在道具本管理中手动删除剧本` ## 项目背景 @@ -54,7 +62,7 @@ const planData = { 1. 根据决策层传入的章节ID,构建 `ids:number[]` 2. 调用 `get_novel_events(ids:number[])` 获取结构化事件数据 -2. 逐章分析,提取以下维度: +3. 逐章分析,提取以下维度: | 字段 | 说明 | 示例 | |------|------|------| @@ -66,8 +74,8 @@ const planData = { | 预估集长 | 秒数 | 45秒 | | 情绪强度 | 情绪标签 | 冲突+恐怖 | -3. 生成汇总统计(总章节、强主线章节数、可压缩章节、预估总时长、目标时长、压缩比) -4. 输出 Markdown 表格格式的事件表,作为后续任务上下文(不写入 planData) +4. 生成汇总统计(总章节、强主线章节数、可压缩章节、预估总时长、目标时长、压缩比) +5. 输出 Markdown 表格格式的事件表,作为后续任务上下文(不写入 planData) **输出格式**:参考 [event-format.md](references/event-format.md) @@ -169,7 +177,7 @@ const planData = { - 画面描述(构图、运镜、视觉重点) - 台词/旁白/内心独白 - 表演指示(情绪、动作细节) -6. 调用 `set_planData_script` 保存 +6. 仅当指令中包含 `用户已确认写入SQL: 是` 时,调用 `insert_script_to_sqlite` 写入剧本 **输出格式**:参考 [script-format.md](references/script-format.md) @@ -186,4 +194,5 @@ const planData = { 2. **增量更新**:如果工作区已有内容,在其基础上修改而非全部覆盖(除非指令明确要求重写) 3. **格式一致**:严格按照对应的输出格式规范,使用 Markdown 格式 4. **任务边界**:只执行指令中明确要求的任务,不越权执行其他阶段 -5. **异常上报**:遇到无法处理的情况(如缺少前置数据),在返回结果中明确说明 \ No newline at end of file +5. **异常上报**:遇到无法处理的情况(如缺少前置数据),在返回结果中明确说明 +6. **SQL安全执行**:未收到明确用户确认时,禁止调用 `insert_script_to_sqlite` \ No newline at end of file diff --git a/src/agents/productionAgent/tools.ts b/src/agents/productionAgent/tools.ts index 8483193..97684c6 100644 --- a/src/agents/productionAgent/tools.ts +++ b/src/agents/productionAgent/tools.ts @@ -6,10 +6,12 @@ import ResTool from "@/socket/resTool"; export const deriveAssetSchema = z.object({ id: z.number().describe("衍生资产ID,如果新增则为空").optional(), assetsId: z.string().describe("关联的资产ID"), + prompt: z.string().describe("生成提示词"), name: z.string().describe("衍生资产名称"), desc: z.string().describe("衍生资产描述"), src: z.string().describe("衍生资产资源路径"), state: z.enum(["未生成", "生成中", "已完成", "生成失败"]).describe("衍生资产生成状态"), + type: z.enum(["role", "tool", "scene", "clip"]).describe("衍生资产类型"), }); export const assetItemSchema = z.object({ assetsId: z.string().describe("资产唯一标识"), diff --git a/src/agents/scriptAgent/index.ts b/src/agents/scriptAgent/index.ts index bffc0b9..1bd4dda 100644 --- a/src/agents/scriptAgent/index.ts +++ b/src/agents/scriptAgent/index.ts @@ -35,14 +35,30 @@ function buildSystemPrompt(skillPrompt: string, mem: Awaited `- ${i.id}: 第${i.index}章`).join("\n")}\n\n`; + console.log("%c Line:57 🍧 prefixSystem", "background:#ea7e5c", prefixSystem); const { textStream } = await u.Ai.Text("scriptAgent").stream({ system: prefixSystem + systemPrompt, @@ -92,7 +108,10 @@ export async function executionAI(ctx: AgentContext) { } export async function supervisionAI(ctx: AgentContext) { - const { isolationKey, text, abortSignal } = ctx; + const { isolationKey, text, abortSignal, resTool } = ctx; + + resTool.systemMessage("监督层AI 接管聊天"); + const memory = new Memory("scriptAgent", isolationKey); const [skill, mem] = await Promise.all([useSkill("script-agent", "supervision"), memory.get(text)]); @@ -127,11 +146,10 @@ function runSubAgent(parentCtx: AgentContext) { //运行子Agent const subTextStream = await fn({ ...parentCtx, text: prompt }); - let msg: ReturnType; + let msg = parentCtx.resTool.textMessage(); let fullResponse = ""; for await (const chunk of subTextStream) { - if (!msg!) msg = parentCtx.resTool.textMessage(); msg.send(chunk); fullResponse += chunk; } diff --git a/src/agents/scriptAgent/tools.ts b/src/agents/scriptAgent/tools.ts index 96f3bda..800850e 100644 --- a/src/agents/scriptAgent/tools.ts +++ b/src/agents/scriptAgent/tools.ts @@ -4,6 +4,17 @@ import { z } from "zod"; import _ from "lodash"; import ResTool from "@/socket/resTool"; +export const AssetSchema = z.object({ + id: z.number().describe("衍生资产ID,如果新增则为空").optional(), + assetsId: z.string().describe("关联的资产ID"), + prompt: z.string().describe("生成提示词"), + name: z.string().describe("衍生资产名称"), + desc: z.string().describe("衍生资产描述"), + src: z.string().describe("衍生资产资源路径").optional(), + state: z.enum(["未生成", "生成中", "已完成", "生成失败"]).describe("衍生资产生成状态,新增默认未生成"), + type: z.enum(["role", "tool", "scene", "clip"]).describe("衍生资产类型"), +}); + export const planData = z.object({ storySkeleton: z.string().describe("故事骨架"), adaptationStrategy: z.string().describe("改编策略"), @@ -26,6 +37,8 @@ export default (resTool: ResTool, toolsNames?: string[]) => { ids: z.array(z.number()).describe("章节id"), }), execute: async ({ ids }) => { + resTool.systemMessage(`正在阅读 章节事件 数据...`); + console.log("[tools] get_novel_events", ids); const data = await u .db("o_novel") .select("id", "chapterIndex as index", "reel", "chapter", "chapterData", "event", "eventState") @@ -76,13 +89,23 @@ export default (resTool: ResTool, toolsNames?: string[]) => { return true; }, }), - set_planData_script: tool({ - description: "保存剧本内容到工作区", - inputSchema: z.object({ value: planData.shape.script }), - execute: async ({ value }) => { - console.log("[tools] set_planData script", value); - resTool.systemMessage("正在保存 剧本 数据"); - socket.emit("setPlanData", { key: "script", value }); + insert_script_to_sqlite: tool({ + description: "将剧本内容插入sqlite数据库,供后续业务使用", + inputSchema: z.object({ + list: z.array(AssetSchema), + }), + execute: async ({ list }) => { + console.log("[tools] insert_script_to_sqlite", list); + await u.db("o_assets").insert( + list.map((i) => ({ + name: i.name, + prompt: i.prompt, + type: i.type, + describe: i.desc, + projectId: resTool.data.projectId, + state: "未生成", + })), + ); return true; }, }), diff --git a/src/lib/initDB.ts b/src/lib/initDB.ts index 12e4d2a..03e12bb 100644 --- a/src/lib/initDB.ts +++ b/src/lib/initDB.ts @@ -328,7 +328,7 @@ export default async (knex: Knex, forceInit: boolean = false): Promise => }, //flowData-剧本 { - name: "o_flowData", + name: "o_agentWorkData", builder: (table) => { table.integer("id").notNullable(); table.integer("projectId"); diff --git a/src/router.ts b/src/router.ts index 3841909..1dea5f9 100644 --- a/src/router.ts +++ b/src/router.ts @@ -1,4 +1,4 @@ -// @routes-hash cd0c360844a1f493a1cf5deb6ab906b0 +// @routes-hash 0ea539b96a91286c69cc811893902704 import { Express } from "express"; import route1 from "./routes/agents/clearMemory"; @@ -60,24 +60,26 @@ import route56 from "./routes/script/delScript"; import route57 from "./routes/script/exportScript"; import route58 from "./routes/script/getScrptApi"; import route59 from "./routes/script/updateScript"; -import route60 from "./routes/setting/agentDeploy/deployAgentModel"; -import route61 from "./routes/setting/agentDeploy/getAgentDeploy"; -import route62 from "./routes/setting/agentDeploy/updateKey"; -import route63 from "./routes/setting/dbConfig/clearData"; -import route64 from "./routes/setting/getTextModel"; -import route65 from "./routes/setting/loginConfig/getUser"; -import route66 from "./routes/setting/loginConfig/updateUserPwd"; -import route67 from "./routes/setting/memoryConfig/getMemory"; -import route68 from "./routes/setting/memoryConfig/sureMemory"; -import route69 from "./routes/setting/vendorConfig/addVendor"; -import route70 from "./routes/setting/vendorConfig/deleteVendor"; -import route71 from "./routes/setting/vendorConfig/getVendorList"; -import route72 from "./routes/setting/vendorConfig/modelTest"; -import route73 from "./routes/setting/vendorConfig/updateVendor"; -import route74 from "./routes/task/getTaskApi"; -import route75 from "./routes/task/getTaskCategories"; -import route76 from "./routes/task/taskDetails"; -import route77 from "./routes/test/test"; +import route60 from "./routes/scriptAgent/getPlanData"; +import route61 from "./routes/scriptAgent/setPlanData"; +import route62 from "./routes/setting/agentDeploy/deployAgentModel"; +import route63 from "./routes/setting/agentDeploy/getAgentDeploy"; +import route64 from "./routes/setting/agentDeploy/updateKey"; +import route65 from "./routes/setting/dbConfig/clearData"; +import route66 from "./routes/setting/getTextModel"; +import route67 from "./routes/setting/loginConfig/getUser"; +import route68 from "./routes/setting/loginConfig/updateUserPwd"; +import route69 from "./routes/setting/memoryConfig/getMemory"; +import route70 from "./routes/setting/memoryConfig/sureMemory"; +import route71 from "./routes/setting/vendorConfig/addVendor"; +import route72 from "./routes/setting/vendorConfig/deleteVendor"; +import route73 from "./routes/setting/vendorConfig/getVendorList"; +import route74 from "./routes/setting/vendorConfig/modelTest"; +import route75 from "./routes/setting/vendorConfig/updateVendor"; +import route76 from "./routes/task/getTaskApi"; +import route77 from "./routes/task/getTaskCategories"; +import route78 from "./routes/task/taskDetails"; +import route79 from "./routes/test/test"; export default async (app: Express) => { app.use("/api/agents/clearMemory", route1); @@ -139,22 +141,24 @@ export default async (app: Express) => { app.use("/api/script/exportScript", route57); app.use("/api/script/getScrptApi", route58); app.use("/api/script/updateScript", route59); - app.use("/api/setting/agentDeploy/deployAgentModel", route60); - app.use("/api/setting/agentDeploy/getAgentDeploy", route61); - app.use("/api/setting/agentDeploy/updateKey", route62); - app.use("/api/setting/dbConfig/clearData", route63); - app.use("/api/setting/getTextModel", route64); - app.use("/api/setting/loginConfig/getUser", route65); - app.use("/api/setting/loginConfig/updateUserPwd", route66); - app.use("/api/setting/memoryConfig/getMemory", route67); - app.use("/api/setting/memoryConfig/sureMemory", route68); - app.use("/api/setting/vendorConfig/addVendor", route69); - app.use("/api/setting/vendorConfig/deleteVendor", route70); - app.use("/api/setting/vendorConfig/getVendorList", route71); - app.use("/api/setting/vendorConfig/modelTest", route72); - app.use("/api/setting/vendorConfig/updateVendor", route73); - app.use("/api/task/getTaskApi", route74); - app.use("/api/task/getTaskCategories", route75); - app.use("/api/task/taskDetails", route76); - app.use("/api/test/test", route77); + app.use("/api/scriptAgent/getPlanData", route60); + app.use("/api/scriptAgent/setPlanData", route61); + app.use("/api/setting/agentDeploy/deployAgentModel", route62); + app.use("/api/setting/agentDeploy/getAgentDeploy", route63); + app.use("/api/setting/agentDeploy/updateKey", route64); + app.use("/api/setting/dbConfig/clearData", route65); + app.use("/api/setting/getTextModel", route66); + app.use("/api/setting/loginConfig/getUser", route67); + app.use("/api/setting/loginConfig/updateUserPwd", route68); + app.use("/api/setting/memoryConfig/getMemory", route69); + app.use("/api/setting/memoryConfig/sureMemory", route70); + app.use("/api/setting/vendorConfig/addVendor", route71); + app.use("/api/setting/vendorConfig/deleteVendor", route72); + app.use("/api/setting/vendorConfig/getVendorList", route73); + app.use("/api/setting/vendorConfig/modelTest", route74); + app.use("/api/setting/vendorConfig/updateVendor", route75); + app.use("/api/task/getTaskApi", route76); + app.use("/api/task/getTaskCategories", route77); + app.use("/api/task/taskDetails", route78); + app.use("/api/test/test", route79); } diff --git a/src/routes/production/getFlowData.ts b/src/routes/production/getFlowData.ts index f9a81e7..b7fa46f 100644 --- a/src/routes/production/getFlowData.ts +++ b/src/routes/production/getFlowData.ts @@ -14,7 +14,7 @@ export default router.post( }), async (req, res) => { const { projectId, episodesId } = req.body; - const sqlData = await u.db("o_flowData").where({ projectId, episodesId }).first(); + const sqlData = await u.db("o_agentWorkData").where({ projectId, episodesId }).first(); const scriptData = await u.db("o_script").where("projectId", projectId).first(); diff --git a/src/routes/production/saveFlowData.ts b/src/routes/production/saveFlowData.ts index 14fd7e1..73b7670 100644 --- a/src/routes/production/saveFlowData.ts +++ b/src/routes/production/saveFlowData.ts @@ -15,16 +15,16 @@ export default router.post( }), async (req, res) => { const { projectId, episodesId } = req.body; - const sqlData = await u.db("o_flowData").where({ projectId, episodesId }).first(); + const sqlData = await u.db("o_agentWorkData").where({ projectId, episodesId }).first(); if (!sqlData) { - await u.db("o_flowData").insert({ + await u.db("o_agentWorkData").insert({ projectId, episodesId, data: JSON.stringify(req.body.data), }); } else { await u - .db("o_flowData") + .db("o_agentWorkData") .where({ projectId, episodesId }) .update({ data: JSON.stringify(req.body.data), diff --git a/src/routes/scriptAgent/getPlanData.ts b/src/routes/scriptAgent/getPlanData.ts new file mode 100644 index 0000000..9ca8ad6 --- /dev/null +++ b/src/routes/scriptAgent/getPlanData.ts @@ -0,0 +1,36 @@ +import express from "express"; +import { success } from "@/lib/responseFormat"; +import u from "@/utils"; +import { z } from "zod"; +import { validateFields } from "@/middleware/middleware"; +const router = express.Router(); + +export default router.post( + "/", + validateFields({ + projectId: z.number(), + agentType: z.enum(["scriptAgent"]), + }), + async (req, res) => { + const { projectId, agentType } = req.body; + const data = await u.db("o_agentWorkData").where({ id: projectId, key: agentType }).first(); + + if (!data) { + await u.db("o_agentWorkData").insert({ + id: projectId, + key: agentType, + data: JSON.stringify({ + storySkeleton: "", + adaptationStrategy: "", + }), + }); + return res.status(200).send( + success({ + storySkeleton: "", + adaptationStrategy: "", + }), + ); + } + res.status(200).send(success(JSON.parse(data.data ?? "{}"))); + }, +); diff --git a/src/routes/scriptAgent/setPlanData.ts b/src/routes/scriptAgent/setPlanData.ts new file mode 100644 index 0000000..e7ac9e3 --- /dev/null +++ b/src/routes/scriptAgent/setPlanData.ts @@ -0,0 +1,28 @@ +import express from "express"; +import { success } from "@/lib/responseFormat"; +import u from "@/utils"; +import { z } from "zod"; +import { validateFields } from "@/middleware/middleware"; +const router = express.Router(); + +export default router.post( + "/", + validateFields({ + projectId: z.number(), + agentType: z.enum(["scriptAgent"]), + data: z.object({ + storySkeleton: z.string(), + adaptationStrategy: z.string(), + }), + }), + async (req, res) => { + const { projectId, agentType, data } = req.body; + await u + .db("o_agentWorkData") + .where({ id: projectId, key: agentType }) + .update({ + data: JSON.stringify(data), + }); + res.status(200).send(success()); + }, +); diff --git a/src/types/database.d.ts b/src/types/database.d.ts index 7e0021e..83d0f35 100644 --- a/src/types/database.d.ts +++ b/src/types/database.d.ts @@ -1,4 +1,4 @@ -// @db-hash 3cdc2f747dac456ddd4bbfd877efe991 +// @db-hash f73a46df6ee14425a01df5c1ff88fcb2 //该文件由脚本自动生成,请勿手动修改 export interface memories { @@ -22,6 +22,15 @@ export interface o_agentDeploy { 'name'?: string | null; 'vendorId'?: number | null; } +export interface o_agentWorkData { + 'createTime'?: number | null; + 'data'?: string | null; + 'espisodeId'?: number | null; + 'id'?: number; + 'key'?: string | null; + 'projectId'?: number | null; + 'updateTime'?: number | null; +} export interface o_artStyle { 'id'?: number; 'name'?: string | null; @@ -193,6 +202,7 @@ export interface o_videoConfig { export interface DB { "memories": memories; "o_agentDeploy": o_agentDeploy; + "o_agentWorkData": o_agentWorkData; "o_artStyle": o_artStyle; "o_assets": o_assets; "o_assets2Storyboard": o_assets2Storyboard;