diff --git a/data/skills/production-agent/execution/SKILL.md b/data/skills/production-agent/execution/SKILL.md index d4049fe..585e982 100644 --- a/data/skills/production-agent/execution/SKILL.md +++ b/data/skills/production-agent/execution/SKILL.md @@ -1,24 +1,25 @@ --- name: execution -description: 短剧漫剧制作助手。协助用户进行制作视频。 +description: 用户需要拆分剧本的时候可以看此skill的参考资料,了解拆分原则和示例 --- -# Production Agent +# execution Agent -短剧漫剧制作专用技能,提供剧本创作、分镜设计、角色设定等全流程指导。 +执行层,负责整体决策和协调。接收用户需求后,完成对应的任务。 ## 何时使用 当用户需要以下帮助时激活此技能: -- 开始制作视频 +- 拆分剧本 ## 工作指引 -1. 理解用户的创作意图,根据项目类型(短剧/漫剧)调整输出风格 -2. 遵循标准的剧本格式,包含场景描述、角色动作、对白等要素 -3. 保持角色一致性,关注剧情连贯性 -4. 输出使用中文 +### 拆分剧本流程 + +- 当执行拆分剧本任务的时候,你需要先调用 `get_flowData` 获取原始剧本 +- 根据[剧本拆分]文档中的拆分原则和示例,将剧本拆分成视频模型能够处理的片段 +- 将拆分后的剧本片段必须使用`set_flowData_script`工具保存,然后仅需告知用户拆分完成 ## 参考资料 diff --git a/data/skills/production-agent/execution/references/script-splitting.md b/data/skills/production-agent/execution/references/script-splitting.md index 67f04e8..031e546 100644 --- a/data/skills/production-agent/execution/references/script-splitting.md +++ b/data/skills/production-agent/execution/references/script-splitting.md @@ -16,83 +16,24 @@ - `string[]` - 数组每个元素是“原始剧本的一段子串” - 拼接后应可还原原文语义顺序(允许空白规范化差异) +- 开头必须是**喵,** 示例: ```ts [ - "第一段原文...", - "第二段原文...", - "第三段原文..." + "喵,第一段原文...", + "喵,第二段原文...", + "喵,第三段原文..." ] ``` -## 2. 切割原则 - -1. 只切原文,不改写剧情。 -2. 尽量在自然边界切分: - - 场景切换处 - - 段落边界 - - 完整对白后 -3. 禁止在以下位置切分: - - 一句对白中间 - - 一个动作描述中间 - - 专有名词中间 -4. 每段尽量长度均衡,避免极短碎片。 - -## 3. 长度约束(建议) - -按模型容量设置目标长度,建议采用“软上限 + 硬上限”: - -- `targetLen`:目标字符数(例如 800 到 1500) -- `maxLen`:硬上限字符数(例如 1800) - -规则: - -1. 优先接近 `targetLen`。 -2. 若继续追加会超过 `maxLen`,必须切分。 -3. 超长单段(天然超过 `maxLen`)可在最近标点处强制切分。 - -## 4. 切割流程 - -1. 预处理:统一换行符、去除明显重复空行。 -2. 粗切:按双换行或场景标记(如“场景X”“INT/EXT”)分段。 -3. 合并:将粗切段按顺序累加到当前块,直到接近 `targetLen`。 -4. 截断:若超过 `maxLen`,在最近的句末标点处切开。 -5. 收尾:去掉首尾多余空白,输出 `string[]`。 - -## 5. 最低质量检查 - -输出前检查: - -1. 数组不为空。 -2. 每个元素非空字符串。 -3. 顺序与原文一致。 -4. 不存在明显断句错误(例如对白断半句)。 - -## 6. 给智能体的固定执行指令 - -当用户要求“拆分长剧本给多个视频模型并行生成”时: - -1. 仅基于原始剧本文本切割。 -2. 不增加任何结构化字段。 -3. 最终仅输出 `string[]`。 -4. 不输出额外解释性对象。 - -## 7. 简例 - -原文(节选): - -- 场景1:雨夜街道。林夏快步穿过巷口。 -- 场景2:天台。她停下,回头看向门口。 -- 场景3:脚步声逼近。她握紧手机。 - 可能输出: ```ts [ - "场景1:雨夜街道。林夏快步穿过巷口。", - "场景2:天台。她停下,回头看向门口。", - "场景3:脚步声逼近。她握紧手机。" + "喵,场景1:雨夜街道。林夏快步穿过巷口。", + "喵,场景2:天台。她停下,回头看向门口。", + "喵,场景3:脚步声逼近。她握紧手机。" ] ``` diff --git a/package.json b/package.json index f774e23..d66281c 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "qwen-ai-provider-v5": "^2.1.0", "serialize-error": "^13.0.1", "sharp": "^0.34.5", + "socket.io": "^4.8.3", "sqlite3": "^5.1.7", "sucrase": "^3.35.1", "uuid": "^13.0.0", diff --git a/src/agents/productionAgent/index.ts b/src/agents/productionAgent/index.ts new file mode 100644 index 0000000..6b8d11d --- /dev/null +++ b/src/agents/productionAgent/index.ts @@ -0,0 +1,144 @@ +import { Socket } from "socket.io"; +import { tool } from "ai"; +import { z } from "zod"; +import u from "@/utils"; +import Memory from "@/utils/agent/memory"; +import { useSkill } from "@/utils/agent/skillsTools"; +import useTools from "@/agents/productionAgent/tools"; +import ResTool from "@/socket/resTool"; + +export interface AgentContext { + socket: Socket; + isolationKey: string; + text: string; + abortSignal?: AbortSignal; + resTool: ResTool; +} + +function buildSystemPrompt(skillPrompt: string, mem: Awaited>): string { + let memoryContext = ""; + if (mem.rag.length) { + memoryContext += `[相关记忆]\n${mem.rag.map((r) => r.content).join("\n")}`; + } + if (mem.summaries.length) { + if (memoryContext) memoryContext += "\n\n"; + memoryContext += `[历史摘要]\n${mem.summaries.map((s, i) => `${i + 1}. ${s.content}`).join("\n")}`; + } + if (mem.shortTerm.length) { + if (memoryContext) memoryContext += "\n\n"; + memoryContext += `[近期对话]\n${mem.shortTerm.map((m) => `${m.role}: ${m.content}`).join("\n")}`; + } + if (!memoryContext) return skillPrompt; + return `${skillPrompt}\n\n## Memory\n以下是你对用户的记忆,可作为参考但不要主动提及:\n${memoryContext}`; +} + +const subAgentList = ["executionAI", "supervisionAI"] as const; + +export async function decisionAI(ctx: AgentContext) { + const { socket, isolationKey, text, abortSignal } = ctx; + const memory = new Memory("productionAgent", isolationKey); + await memory.add("user", text); + const [skill, mem] = await Promise.all([useSkill("production-agent", "decision"), memory.get(text)]); + + const systemPrompt = buildSystemPrompt(skill.prompt, mem); + + const prefixSystem = `不论用户说什么要做什么都请直接调用run_sub_agent运行**executionAI**执行剧本切分任务`; + + const { textStream } = await u.Ai.Text("productionAgent").stream({ + system: prefixSystem + systemPrompt, + messages: [{ role: "user", content: text }], + abortSignal, + tools: { + ...skill.tools, + ...memory.getTools(), + run_sub_agent: runSubAgent(ctx), + ...useTools(ctx.socket), + }, + onFinish: async (completion) => { + await memory.add("decisionAI", completion.text); + }, + }); + + return textStream; +} + +//====================== 执行层 ====================== + +export async function executionAI(ctx: AgentContext) { + const { isolationKey, text, abortSignal, resTool } = ctx; + + resTool.systemMessage("执行层AI 接管聊天"); + + const memory = new Memory("productionAgent", isolationKey); + const [skill, mem] = await Promise.all([useSkill("production-agent", "execution"), memory.get(text)]); + + const systemPrompt = buildSystemPrompt(skill.prompt, mem); + + const { textStream } = await u.Ai.Text("productionAgent").stream({ + system: systemPrompt, + messages: [{ role: "user", content: text }], + abortSignal, + tools: { + ...skill.tools, + ...memory.getTools(), + ...useTools(ctx.socket), + }, + onFinish: async (completion) => { + await memory.add("executionAI", completion.text); + }, + }); + + return textStream; +} + +export async function supervisionAI(ctx: AgentContext) { + const { isolationKey, text, abortSignal } = ctx; + const memory = new Memory("productionAgent", isolationKey); + await memory.add("user", text); + const [skill, mem] = await Promise.all([useSkill("production-agent", "supervision"), memory.get(text)]); + + const systemPrompt = buildSystemPrompt(skill.prompt, mem); + + const { textStream } = await u.Ai.Text("productionAgent").stream({ + system: systemPrompt, + messages: [{ role: "user", content: text }], + abortSignal, + tools: { + ...skill.tools, + ...memory.getTools(), + }, + onFinish: async (completion) => { + await memory.add("supervisionAI", completion.text); + }, + }); + + return textStream; +} + +//工具函数 +function runSubAgent(parentCtx: AgentContext) { + return tool({ + description: "启动子Agent执行独立任务。可用子Agent:executionAI, decisionAI, supervisionAI", + inputSchema: z.object({ + agent: z.enum(["executionAI", "supervisionAI"]).describe("子Agent名称"), + prompt: z.string().describe("交给子Agent的任务描述"), + }), + execute: async ({ agent, prompt }) => { + const fn = [executionAI, supervisionAI][subAgentList.indexOf(agent)]; + //运行子Agent + const subTextStream = await fn({ ...parentCtx, text: prompt }); + + let msg: ReturnType; + let fullResponse = ""; + + for await (const chunk of subTextStream) { + if (!msg!) msg = parentCtx.resTool.textMessage(); + msg.send(chunk); + fullResponse += chunk; + } + msg!.end(); + + return fullResponse; + }, + }); +} diff --git a/src/agents/productionAgent/tools.ts b/src/agents/productionAgent/tools.ts index 44780db..49437c0 100644 --- a/src/agents/productionAgent/tools.ts +++ b/src/agents/productionAgent/tools.ts @@ -1,75 +1,49 @@ -import { tool } from "ai"; +import { tool, Tool } from "ai"; import { z } from "zod"; import u from "@/utils"; -import { useSkill } from "@/utils/agent/skillsTools"; -import { createAGUIStream } from "@/utils/agent/aguiTools"; +import { Socket } from "socket.io"; interface FlowData { + rawScript: string; script: { blocks: string[]; }; } -export default (isolationKey: string, agui: ReturnType) => { - const flowData: FlowData = { +export default (socket: Socket, toolsNames?: string[]) => { + let flowData: FlowData = { + rawScript: "", script: { blocks: [], }, }; - return { - get_project_info: tool({ - description: "获取项目信息", - inputSchema: z.object({}), - execute: async () => { - return ` - 项目名称:仙逆 - 视频风格:玄幻3D动漫 - 视频类型:短剧 - 项目描述:讲述了乡村平凡少年王林以心中之感动,逆仙而修,求的不仅是长生,更多的是摆脱那背后的蝼蚁之身。他坚信道在人为,以平庸的资质踏入修真仙途,历经坎坷风雨,凭着其聪睿的心智,一步一步走向巅峰,凭一己之力,扬名修真界。 - 总集数:24集每集2分钟 - 当前集数:3集 - `; + + const tools: Record = { + get_flowData: tool({ + description: "获取当前工作区的状态/数据", + inputSchema: z.object({ + key: z.enum(["script"]).describe("state的key,rawScript代表原始剧本文字,script代表分块后的剧本"), + }), + execute: async ({ key }) => { + flowData = await new Promise((resolve) => socket.emit("getFlowData", { key }, (res: any) => resolve(res))); + console.log("[tool] get_flowData:", key); + return flowData[key]; }, }), - get_state: tool({ - description: "获取工作流指定板块数据", + set_flowData_script: tool({ + description: "保存数据到工作区", inputSchema: z.object({ - block: z.enum(["script"]).describe("板块名称,如 script"), + value: z.array(z.string()).describe("剧本分块后的文本数组"), }), - execute: async ({ block }) => { - return flowData[block]; - }, - }), - execution: tool({ - description: "执行层,负责具体执行具体的任务", - inputSchema: z.object({ - taskDescription: z.string().describe("具体的任务描述详细信息"), - }), - execute: async ({ taskDescription }) => { - agui.custom("systemMessage", "已由 执行层AI 接管对话"); + execute: async ({ value }) => { + flowData.script.blocks = value; + socket.emit("setFlowData", { key: "script", value: { blocks: value } }); - const skill = await useSkill("production-agent", "execution"); - - const { textStream } = await u.Ai.Text("productionAgent").stream({ - system: skill.prompt, - messages: [{ role: "user", content: `请完成任务:${taskDescription}` }], - tools: { - ...skill.tools, - }, - }); - - let msg: ReturnType | null = null; - let fullResponse = ""; - - for await (const chunk of textStream) { - if (!msg) msg = agui.textMessage(); - msg.send(chunk); - fullResponse += chunk; - } - msg?.end(); - - return { found: true, memories: ["第一条记忆内容", "第二条记忆内容"] }; + return true; }, }), }; + + if (!toolsNames) return tools; + else return Object.fromEntries(Object.entries(tools).filter(([name]) => toolsNames.includes(name))); }; diff --git a/src/app.ts b/src/app.ts index d3713ad..170b444 100644 --- a/src/app.ts +++ b/src/app.ts @@ -2,6 +2,8 @@ import "./logger"; import "./err"; import "./env"; import express, { Request, Response, NextFunction } from "express"; +import { Server } from "socket.io"; +import http from "node:http"; import expressWs from "express-ws"; import logger from "morgan"; import cors from "cors"; @@ -9,11 +11,15 @@ import buildRoute from "@/core"; import fs from "fs"; import u from "@/utils"; import jwt from "jsonwebtoken"; +import socketInit from "@/socket/index"; const app = express(); -let server: ReturnType | null = null; +const server = http.createServer(app); export default async function startServe(randomPort: Boolean = false) { + const io = new Server(server, { cors: { origin: "*" } }); + socketInit(io); + if (process.env.NODE_ENV == "dev") await buildRoute(); expressWs(app); @@ -71,8 +77,8 @@ export default async function startServe(randomPort: Boolean = false) { const port = randomPort ? 0 : parseInt(process.env.PORT || "60000"); return await new Promise((resolve) => { - server = app.listen(port, async (v) => { - const address = server?.address(); + server.listen(port, async () => { + const address = server.address(); const realPort = typeof address === "string" ? address : address?.port; console.log(`[服务启动成功]: http://localhost:${realPort}`); resolve(realPort); diff --git a/src/routes/agents/productionAgent.ts b/src/routes/agents/productionAgent.ts deleted file mode 100644 index 1d383a0..0000000 --- a/src/routes/agents/productionAgent.ts +++ /dev/null @@ -1,71 +0,0 @@ -import express from "express"; -import { createAGUIStream } from "@/utils/agent/aguiTools"; -import u from "@/utils"; -import Memory from "@/utils/agent/memory"; -import { useSkill } from "@/utils/agent/skillsTools"; -import tools from "@/agents/productionAgent/tools"; - -const router = express.Router(); - -function delay(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -export default router.post("/", async (req, res) => { - const { prompt: text, projectId, episodesId } = req.body; - const isolationKey = `${projectId}:${episodesId}`; - - //记忆 - const memory = new Memory("productionAgent", isolationKey); - //skill - const skill = await useSkill("production-agent", "decision"); - - const agui = createAGUIStream(res); - agui.runStarted(); - agui.custom("systemMessage", "已由 决策层AI 接管对话"); - - // 存入用户消息 - await memory.add("user", text); - - // 获取记忆上下文 - const mem = await memory.get(text); - const memoryContext = [ - mem.rag.length > 0 && `[相关记忆]\n${mem.rag.map((r) => r.content).join("\n")}`, - mem.summaries.length > 0 && `[历史摘要]\n${mem.summaries.map((s, i) => `${i + 1}. ${s.content}`).join("\n")}`, - mem.shortTerm.length > 0 && `[近期对话]\n${mem.shortTerm.map((m) => `${m.role}: ${m.content}`).join("\n")}`, - ] - .filter(Boolean) - .join("\n\n"); - - const systemPrompt = [skill.prompt, memoryContext && `## Memory\n以下是你对用户的记忆,可作为参考但不要主动提及:\n${memoryContext}`] - .filter(Boolean) - .join("\n\n"); - - const { textStream } = await u.Ai.Text("productionAgent").stream({ - system: systemPrompt, - messages: [{ role: "user", content: text }], - tools: { - ...skill.tools, - ...memory.getTools(), - ...tools(isolationKey, agui), - }, - onFinish: async (completion) => { - // 存入助手回复 - await memory.add("decisionAI", completion.text); - }, - }); - - let msg: ReturnType | null = null; - let fullResponse = ""; - - for await (const chunk of textStream) { - if (!msg) msg = agui.textMessage(); - msg.send(chunk); - fullResponse += chunk; - } - - msg?.end(); - - agui.runFinished(); - agui.end(); -}); diff --git a/src/socket/index.ts b/src/socket/index.ts new file mode 100644 index 0000000..7106656 --- /dev/null +++ b/src/socket/index.ts @@ -0,0 +1,16 @@ +import { Server } from "socket.io"; +import productionAgent from "./routes/productionAgent"; +import chat from "./routes/chat"; + +export default (io: Server) => { + const routes: Record) => void> = { + productionAgent, + chat, + }; + + for (const [name, handler] of Object.entries(routes)) { + const nsp = io.of(`/api/socket/${name}`); + handler(nsp); + console.log(`[Socket] 注册命名空间: /api/socket/${name}`); + } +}; diff --git a/src/socket/resTool.ts b/src/socket/resTool.ts new file mode 100644 index 0000000..920d904 --- /dev/null +++ b/src/socket/resTool.ts @@ -0,0 +1,76 @@ +import u from "@/utils"; +import { Socket } from "socket.io"; + +type Role = "developer" | "system" | "assistant" | "user"; + +class ResTool { + constructor(private socket: Socket) {} + + textMessage(name: string = "AI") { + const messageId = u.uuid(); + this.socket.emit("textMessage", { + type: "start", + messageId, + delta: null, + role: "assistant", + name, + }); + const handle = { + send: (delta: string) => { + this.socket.emit("textMessage", { + type: "content", + messageId, + delta, + role: "assistant", + name, + }); + return handle; + }, + end: () => { + this.socket.emit("textMessage", { + type: "end", + messageId, + delta: null, + role: "assistant", + name, + }); + }, + }; + return handle; + } + thinkMessage() { + const messageId = u.uuid(); + this.socket.emit("thinkMessage", { + type: "start", + messageId, + delta: null, + role: "assistant", + }); + const handle = { + send: (delta: string) => { + this.socket.emit("thinkMessage", { + type: "content", + messageId, + delta, + role: "assistant", + }); + return handle; + }, + end: () => { + this.socket.emit("thinkMessage", { + type: "end", + messageId, + delta: null, + role: "assistant", + }); + }, + }; + return handle; + } + systemMessage(content: string) { + const messageId = u.uuid(); + this.socket.emit("systemMessage", { messageId, content }); + } +} + +export default ResTool; diff --git a/src/socket/routes/chat.ts b/src/socket/routes/chat.ts new file mode 100644 index 0000000..bedd1ac --- /dev/null +++ b/src/socket/routes/chat.ts @@ -0,0 +1,43 @@ +import { Namespace, Socket } from "socket.io"; + +const users = new Map(); // socketId -> username + +export default (nsp: Namespace) => { + nsp.on("connection", (socket: Socket) => { + console.log("[chat] 用户已连接:", socket.id); + + socket.on("userLogin", (username: string) => { + users.set(socket.id, username); + socket.broadcast.emit("notification", `${username} 加入了聊天室`); + }); + + socket.on("sendMessage", (data: { message: string }) => { + const username = users.get(socket.id) || "匿名"; + const msg = { + type: "user" as const, + username, + message: data.message, + time: new Date().toLocaleTimeString(), + }; + nsp.emit("newMessage", msg); + }); + + socket.on("typing", () => { + const username = users.get(socket.id); + if (username) socket.broadcast.emit("userTyping", username); + }); + + socket.on("stopTyping", () => { + socket.broadcast.emit("userStopTyping"); + }); + + socket.on("disconnect", () => { + const username = users.get(socket.id); + if (username) { + users.delete(socket.id); + socket.broadcast.emit("notification", `${username} 离开了聊天室`); + } + console.log("[chat] 用户已断开:", socket.id); + }); + }); +}; diff --git a/src/socket/routes/productionAgent.ts b/src/socket/routes/productionAgent.ts new file mode 100644 index 0000000..a7248a6 --- /dev/null +++ b/src/socket/routes/productionAgent.ts @@ -0,0 +1,69 @@ +import jwt from "jsonwebtoken"; +import u from "@/utils"; +import { Namespace, Socket } from "socket.io"; +import * as agent from "@/agents/productionAgent/index"; +import ResTool from "@/socket/resTool"; + +async function verifyToken(rawToken: string): Promise { + const setting = await u.db("o_setting").where("key", "tokenKey").select("value").first(); + if (!setting) return false; + const { value: tokenKey } = setting; + if (!rawToken) return false; + const token = rawToken.replace("Bearer ", ""); + try { + jwt.verify(token, tokenKey as string); + return true; + } catch (err) { + return false; + } +} + +export default (nsp: Namespace) => { + nsp.on("connection", async (socket: Socket) => { + const token = socket.handshake.auth.token; + if (!token || !(await verifyToken(token))) { + console.log("[productionAgent] 连接失败,token无效"); + socket.disconnect(); + return; + } + const isolationKey = socket.handshake.auth.isolationKey; + if (!isolationKey) { + console.log("[productionAgent] 连接失败,缺少 isolationKey"); + socket.disconnect(); + return; + } + + console.log("[productionAgent] 已连接:", socket.id); + + const resTool = new ResTool(socket); + let abortController: AbortController | null = null; + + socket.on("message", async (text: string) => { + abortController?.abort(); + abortController = new AbortController(); + const currentController = abortController; + + const textStream = await agent.decisionAI({ socket, isolationKey, text, abortSignal: currentController.signal, resTool }); + + let msg = resTool.textMessage(); + + try { + for await (const chunk of textStream) { + msg.send(chunk); + } + } catch (err: any) { + if (err.name !== "AbortError") throw err; + } finally { + msg.end(); + if (abortController === currentController) { + abortController = null; + } + } + }); + + socket.on("stop", () => { + abortController?.abort(); + abortController = null; + }); + }); +}; diff --git a/src/utils/agent/aguiTools.ts b/src/socket/utils/aguiTools.ts similarity index 100% rename from src/utils/agent/aguiTools.ts rename to src/socket/utils/aguiTools.ts diff --git a/src/types/database.d.ts b/src/types/database.d.ts index e3a7e5f..4c3b490 100644 --- a/src/types/database.d.ts +++ b/src/types/database.d.ts @@ -1,4 +1,4 @@ -// @db-hash 04e1150a9773602183de5f660a52b092 +// @db-hash feca77a2c2ec5b6a2989347f982558d5 //该文件由脚本自动生成,请勿手动修改 export interface memories { @@ -35,12 +35,18 @@ export interface o_assets { 'projectId'?: number | null; 'prompt'?: string | null; 'remark'?: string | null; - 'scriptId'?: number | null; 'sonId'?: number | null; 'startTime'?: number | null; 'state'?: string | null; 'type'?: string | null; } +export interface o_chatHistory { + 'data'?: string | null; + 'id'?: number; + 'novel'?: string | null; + 'projectId'?: number | null; + 'type'?: string | null; +} export interface o_event { 'createTime'?: number | null; 'detail'?: string | null; @@ -61,10 +67,33 @@ export interface o_image { 'assetsId'?: number | null; 'filePath'?: string | null; 'id'?: number; - 'model'?: string | null; - 'resolution'?: string | null; + 'projectId'?: number | null; + 'scriptId'?: number | null; 'state'?: string | null; 'type'?: string | null; + 'videoId'?: number | null; +} +export interface o_model { + 'apiKey'?: string | null; + 'baseUrl'?: string | null; + 'createTime'?: number | null; + 'id'?: number; + 'index'?: number | null; + 'manufacturer'?: string | null; + 'model'?: string | null; + 'modelType'?: string | null; + 'type'?: string | null; +} +export interface o_myTasks { + 'describe'?: string | null; + 'id'?: number; + 'model'?: string | null; + 'projectId'?: number | null; + 'reason'?: string | null; + 'relatedObjects'?: string | null; + 'startTime'?: number | null; + 'state'?: string | null; + 'taskClass'?: string | null; } export interface o_novel { 'chapter'?: string | null; @@ -97,6 +126,15 @@ export interface o_project { 'userId'?: number | null; 'videoRatio'?: string | null; } +export interface o_prompts { + 'code'?: string | null; + 'customValue'?: string | null; + 'defaultValue'?: string | null; + 'id'?: number; + 'name'?: string | null; + 'parentCode'?: string | null; + 'type'?: string | null; +} export interface o_script { 'content'?: string | null; 'createTime'?: number | null; @@ -104,15 +142,35 @@ export interface o_script { 'name'?: string | null; 'projectId'?: number | null; } +export interface o_scriptAssets { + 'assetsId'?: number | null; + 'id'?: number; + 'scriptId'?: number | null; +} +export interface o_scriptOutline { + 'id'?: number; + 'outlineId'?: number | null; + 'scriptId'?: number | null; +} export interface o_setting { 'key'?: string | null; 'value'?: string | null; } +export interface o_skills { + 'id'?: number; + 'name'?: string | null; + 'startTime'?: number | null; +} export interface o_storyboard { 'createTime'?: number | null; 'id'?: number; 'name'?: string | null; } +export interface o_storyboardScript { + 'id'?: number; + 'scriptId'?: number | null; + 'storyboardId'?: number | null; +} export interface o_tasks { 'describe'?: string | null; 'id'?: number; @@ -178,17 +236,25 @@ export interface DB { "o_agentDeploy": o_agentDeploy; "o_artStyle": o_artStyle; "o_assets": o_assets; + "o_chatHistory": o_chatHistory; "o_event": o_event; "o_eventChapter": o_eventChapter; "o_flowData": o_flowData; "o_image": o_image; + "o_model": o_model; + "o_myTasks": o_myTasks; "o_novel": o_novel; "o_outline": o_outline; "o_outlineNovel": o_outlineNovel; "o_project": o_project; + "o_prompts": o_prompts; "o_script": o_script; + "o_scriptAssets": o_scriptAssets; + "o_scriptOutline": o_scriptOutline; "o_setting": o_setting; + "o_skills": o_skills; "o_storyboard": o_storyboard; + "o_storyboardScript": o_storyboardScript; "o_tasks": o_tasks; "o_user": o_user; "o_vendorConfig": o_vendorConfig; diff --git a/src/utils/agent/memory.ts b/src/utils/agent/memory.ts index 94a8081..16fde04 100644 --- a/src/utils/agent/memory.ts +++ b/src/utils/agent/memory.ts @@ -97,11 +97,11 @@ class Memory { embedding: JSON.stringify(embedding), relatedMessageIds: null, summarized: 0, - createdAt: Date.now(), + createTime: Date.now(), } as any); // 检查未总结消息数量 - const unsummarized = await u.db("memories").where({ isolationKey, type: "message", summarized: 0 }).orderBy("createdAt", "asc"); + const unsummarized = await u.db("memories").where({ isolationKey, type: "message", summarized: 0 }).orderBy("createTime", "asc"); if (unsummarized.length >= Number(messagesPerSummary)) { const batch = unsummarized.slice(0, Number(messagesPerSummary)); @@ -120,7 +120,7 @@ class Memory { embedding: JSON.stringify(summaryEmbedding), relatedMessageIds: JSON.stringify(batchIds), summarized: 0, - createdAt: Date.now(), + createTime: Date.now(), } as any); // 标记已总结 @@ -140,12 +140,12 @@ class Memory { const shortTerm = await u .db("memories") .where({ isolationKey, type: "message", summarized: 0 }) - .orderBy("createdAt", "desc") + .orderBy("createTime", "desc") .limit(Number(shortTermLimit)); shortTerm.reverse(); // 最旧在前 // summaries: 最近的 summary - const summaries = await u.db("memories").where({ isolationKey, type: "summary" }).orderBy("createdAt", "desc").limit(Number(summaryLimit)); + const summaries = await u.db("memories").where({ isolationKey, type: "summary" }).orderBy("createTime", "desc").limit(Number(summaryLimit)); summaries.reverse(); // rag: 向量搜索所有 messages @@ -154,12 +154,12 @@ class Memory { const ragResults = vectorSearch(allMessages, queryEmbedding, Number(ragLimit)); return { - shortTerm: shortTerm.map((m: any) => ({ id: m.id, role: m.role, content: m.content, createdAt: m.createdAt })), + shortTerm: shortTerm.map((m: any) => ({ id: m.id, role: m.role, content: m.content, createTime: m.createTime })), summaries: summaries.map((s) => ({ id: s.id, content: s.content, relatedMessageIds: JSON.parse(s.relatedMessageIds || "[]"), - createdAt: (s as any).createdAt, + createTime: (s as any).createTime, })), rag: ragResults.map((r) => ({ id: r.id, content: r.content, similarity: r.similarity })), }; @@ -190,9 +190,9 @@ class Memory { if (messageIds.length === 0) return []; - const messages = await u.db("memories").whereIn("id", messageIds).orderBy("createdAt", "asc"); + const messages = await u.db("memories").whereIn("id", messageIds).orderBy("createTime", "asc"); - return messages.map((m) => ({ id: m.id, content: m.content, createdAt: m.createdAt })); + return messages.map((m) => ({ id: m.id, content: m.content, createTime: m.createTime })); } getTools() { diff --git a/src/utils/agent/skillsTools.ts b/src/utils/agent/skillsTools.ts index 1094ca7..21077ea 100644 --- a/src/utils/agent/skillsTools.ts +++ b/src/utils/agent/skillsTools.ts @@ -23,10 +23,16 @@ function parseFrontmatter(content: string): { name: string; description: string for (let i = 0; i < lines.length; ) { const colonIndex = lines[i].indexOf(":"); - if (colonIndex === -1) { i++; continue; } + if (colonIndex === -1) { + i++; + continue; + } const key = lines[i].slice(0, colonIndex).trim(); - if (!key) { i++; continue; } + if (!key) { + i++; + continue; + } let value = lines[i].slice(colonIndex + 1).trim(); i++; @@ -44,8 +50,7 @@ function parseFrontmatter(content: string): { name: string; description: string result[key] = value; } - if (!result.name) throw new Error("Frontmatter missing required field: name"); - if (!result.description) throw new Error("Frontmatter missing required field: description"); + if (!result.name || !result.description) throw new Error("Frontmatter missing required field: name or description"); return { name: result.name, description: result.description }; } @@ -57,13 +62,17 @@ function stripFrontmatter(content: string): string { async function listResources(dir: string, base = ""): Promise { let entries; - try { entries = await fs.readdir(dir, { withFileTypes: true }); } catch { return []; } + try { + entries = await fs.readdir(dir, { withFileTypes: true }); + } catch { + return []; + } const files: string[] = []; for (const entry of entries) { const rel = base ? `${base}/${entry.name}` : entry.name; if (entry.isDirectory()) { - files.push(...await listResources(path.join(dir, entry.name), rel)); + files.push(...(await listResources(path.join(dir, entry.name), rel))); } else if (entry.name !== "SKILL.md") { files.push(rel); } @@ -71,22 +80,39 @@ async function listResources(dir: string, base = ""): Promise { return files; } +// ==================== 读取单个技能 ==================== + +async function readSkillFromDir(skillDir: string): Promise { + const location = path.join(skillDir, "SKILL.md"); + let content: string; + try { + content = await fs.readFile(location, "utf-8"); + } catch { + return null; + } + try { + const meta = parseFrontmatter(content); + console.log(`[Skill] ✅ 发现技能:${meta.name} — ${meta.description}`); + return { ...meta, location, baseDir: skillDir }; + } catch (e) { + console.log(`[Skill] ⚠️ 解析失败 "${skillDir}":${(e as Error).message}`); + return null; + } +} + // ==================== 构建技能目录 ==================== -function buildCatalog(skill: SkillRecord): string { - return [ - "## Skills", - "以下技能提供了专业任务的专用指令。", - "当任务与某个技能的描述匹配时,调用 activate_skill 工具并传入技能名称来加载完整指令。", - "加载后遵循技能指令执行任务,需要时调用 read_skill_file 读取资源文件内容。", - "", - "", - ` `, - ` ${skill.name}`, - ` ${skill.description}`, - ` `, - "", - ].join("\n"); +function buildCatalog(skills: SkillRecord[]): string { + const entries = skills.map((s) => ` \n ${s.name}\n ${s.description}\n `).join("\n"); + + return `## Skills +以下技能提供了专业任务的专用指令。 +当任务与某个技能的描述匹配时,调用 activate_skill 工具并传入技能名称来加载完整指令。 +加载后遵循技能指令执行任务,需要时调用 read_skill_file 读取资源文件内容。 + + +${entries} +`; } // ==================== 激活 + 执行工具 ==================== @@ -107,14 +133,15 @@ function createSkillTools(skills: SkillRecord[]) { console.log(`[Skill] ❌ 激活失败:未找到技能 "${name}"`); return { error: `Skill '${name}' not found` }; } - if (activated.has(name)) { console.log(`[Skill] ℹ️ 技能 "${name}" 已在当前会话中激活,跳过重复注入`); return { already_active: true, message: `技能 "${name}" 已激活,无需重复加载` }; } let content: string; - try { content = await fs.readFile(skill.location, "utf-8"); } catch { + try { + content = await fs.readFile(skill.location, "utf-8"); + } catch { console.log(`[Skill] ❌ 激活失败:无法读取 ${skill.location}`); return { error: `Failed to read SKILL.md for '${name}'` }; } @@ -122,23 +149,19 @@ function createSkillTools(skills: SkillRecord[]) { const body = stripFrontmatter(content); const resources = await listResources(skill.baseDir); activated.add(name); - console.log(`[Skill] 📖 已激活技能:${skill.name}(${body.length} 字符,${resources.length} 个资源文件)`); - const resourcesXml = resources.length > 0 - ? `\n\n${resources.map((f) => ` ${f}`).join("\n")}\n` - : ""; + const resourcesXml = + resources.length > 0 ? `\n\n${resources.map((f) => ` ${f}`).join("\n")}\n` : ""; return { - content: [ - ``, - body, - "", - `Skill directory: ${skill.baseDir}`, - `相对路径基于此技能目录解析,使用 read_skill_file 工具读取资源文件。`, - resourcesXml, - ``, - ].join("\n"), + content: ` +${body} + +Skill directory: ${skill.baseDir} +相对路径基于此技能目录解析,使用 read_skill_file 工具读取资源文件。 +${resourcesXml} +`, }; }, }), @@ -180,26 +203,25 @@ function createSkillTools(skills: SkillRecord[]) { export async function useSkill(...segments: string[]) { if (segments.length === 0) return { prompt: "", tools: {} }; - const baseDir = path.join(getPath("skills"), ...segments); - const location = path.join(baseDir, "SKILL.md"); + const skills = new Map(); - let content: string; - try { content = await fs.readFile(location, "utf-8"); } catch { - console.log(`[Skill] ⚠️ 未发现技能:${segments.join("/")}`); - return { prompt: "", tools: {} }; + const primary = await readSkillFromDir(path.join(getPath("skills"), ...segments)); + if (primary) skills.set(primary.name, primary); + + const publicDir = path.join(getPath("skills"), "public"); + try { + const entries = await fs.readdir(publicDir, { withFileTypes: true }); + for (const entry of entries) { + if (!entry.isDirectory()) continue; + const skill = await readSkillFromDir(path.join(publicDir, entry.name)); + if (skill && !skills.has(skill.name)) skills.set(skill.name, skill); + } + } catch { + /* public dir not found */ } - let metadata: { name: string; description: string }; - try { metadata = parseFrontmatter(content); } catch (e) { - console.log(`[Skill] ⚠️ 解析失败 "${segments.join("/")}":${(e as Error).message}`); - return { prompt: "", tools: {} }; - } + if (skills.size === 0) return { prompt: "", tools: {} }; - const skill: SkillRecord = { ...metadata, location, baseDir }; - console.log(`[Skill] ✅ 发现技能:${skill.name} — ${skill.description}`); - - return { - prompt: buildCatalog(skill), - tools: createSkillTools([skill]), - }; + const allSkills = [...skills.values()]; + return { prompt: buildCatalog(allSkills), tools: createSkillTools(allSkills) }; } diff --git a/yarn.lock b/yarn.lock index 31f5e73..9c6f5a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -742,6 +742,11 @@ resolved "https://registry.npmmirror.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== +"@socket.io/component-emitter@~3.1.0": + version "3.1.2" + resolved "https://registry.npmmirror.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2" + integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== + "@standard-schema/spec@^1.0.0", "@standard-schema/spec@^1.1.0": version "1.1.0" resolved "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8" @@ -784,7 +789,7 @@ dependencies: "@types/node" "*" -"@types/cors@^2.8.19": +"@types/cors@^2.8.12", "@types/cors@^2.8.19": version "2.8.19" resolved "https://registry.npmmirror.com/@types/cors/-/cors-2.8.19.tgz#d93ea2673fd8c9f697367f5eeefc2bbfa94f0342" integrity sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg== @@ -882,7 +887,7 @@ dependencies: undici-types "~7.18.0" -"@types/node@>=13.7.0": +"@types/node@>=10.0.0", "@types/node@>=13.7.0": version "25.5.0" resolved "https://registry.npmmirror.com/@types/node/-/node-25.5.0.tgz#5c99f37c443d9ccc4985866913f1ed364217da31" integrity sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw== @@ -946,7 +951,7 @@ resolved "https://registry.npmmirror.com/@types/verror/-/verror-1.10.11.tgz#d3d6b418978c8aa202d41e5bb3483227b6ecc1bb" integrity sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg== -"@types/ws@*": +"@types/ws@*", "@types/ws@^8.5.12": version "8.18.1" resolved "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz#48464e4bf2ddfd17db13d845467f6070ffea4aa9" integrity sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg== @@ -988,6 +993,14 @@ accepts@^2.0.0: mime-types "^3.0.0" negotiator "^1.0.0" +accepts@~1.3.4: + version "1.3.8" + resolved "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + acorn-walk@^8.3.4: version "8.3.5" resolved "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.5.tgz#8a6b8ca8fc5b34685af15dabb44118663c296496" @@ -1230,6 +1243,11 @@ base64-js@^1.3.1, base64-js@^1.5.1: resolved "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +base64id@2.0.0, base64id@~2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" + integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== + basic-auth@~2.0.1: version "2.0.1" resolved "https://registry.npmmirror.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" @@ -1661,7 +1679,7 @@ cookie-signature@^1.2.1: resolved "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== -cookie@^0.7.1: +cookie@^0.7.1, cookie@~0.7.2: version "0.7.2" resolved "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== @@ -1671,7 +1689,7 @@ core-util-is@1.0.2: resolved "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== -cors@^2.8.5: +cors@^2.8.5, cors@~2.8.5: version "2.8.6" resolved "https://registry.npmmirror.com/cors/-/cors-2.8.6.tgz#ff5dd69bd95e547503820d29aba4f8faf8dfec96" integrity sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw== @@ -1710,7 +1728,7 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3, debug@^4.3.4, debug@^4.4.0, debug@^4.4.3: +debug@4, debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3, debug@^4.3.4, debug@^4.4.0, debug@^4.4.3, debug@~4.4.1: version "4.4.3" resolved "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== @@ -1974,6 +1992,27 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1: dependencies: once "^1.4.0" +engine.io-parser@~5.2.1: + version "5.2.3" + resolved "https://registry.npmmirror.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz#00dc5b97b1f233a23c9398d0209504cf5f94d92f" + integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q== + +engine.io@~6.6.0: + version "6.6.6" + resolved "https://registry.npmmirror.com/engine.io/-/engine.io-6.6.6.tgz#9942111e7a4dc31f057e73470d7b7fcc7f74c390" + integrity sha512-U2SN0w3OpjFRVlrc17E6TMDmH58Xl9rai1MblNjAdwWp07Kk+llmzX0hjDpQdrDGzwmvOtgM5yI+meYX6iZ2xA== + dependencies: + "@types/cors" "^2.8.12" + "@types/node" ">=10.0.0" + "@types/ws" "^8.5.12" + accepts "~1.3.4" + base64id "2.0.0" + cookie "~0.7.2" + cors "~2.8.5" + debug "~4.4.1" + engine.io-parser "~5.2.1" + ws "~8.18.3" + env-paths@^2.2.0: version "2.2.1" resolved "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" @@ -3124,7 +3163,7 @@ mime-db@^1.54.0: resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== -mime-types@^2.1.12: +mime-types@^2.1.12, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -3337,6 +3376,11 @@ napi-build-utils@^2.0.0: resolved "https://registry.npmmirror.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e" integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA== +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + negotiator@^0.6.2: version "0.6.4" resolved "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" @@ -4296,6 +4340,35 @@ smart-buffer@^4.0.2, smart-buffer@^4.2.0: resolved "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== +socket.io-adapter@~2.5.2: + version "2.5.6" + resolved "https://registry.npmmirror.com/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz#c697f609d36a676a46749782274607d8df52c1d8" + integrity sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ== + dependencies: + debug "~4.4.1" + ws "~8.18.3" + +socket.io-parser@~4.2.4: + version "4.2.6" + resolved "https://registry.npmmirror.com/socket.io-parser/-/socket.io-parser-4.2.6.tgz#19156bf179af3931abd05260cfb1491822578a6f" + integrity sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.4.1" + +socket.io@^4.8.3: + version "4.8.3" + resolved "https://registry.npmmirror.com/socket.io/-/socket.io-4.8.3.tgz#ca6ba1431c69532e1e0a6f496deebeb601dbc4df" + integrity sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A== + dependencies: + accepts "~1.3.4" + base64id "~2.0.0" + cors "~2.8.5" + debug "~4.4.1" + engine.io "~6.6.0" + socket.io-adapter "~2.5.2" + socket.io-parser "~4.2.4" + socks-proxy-agent@^6.0.0: version "6.2.1" resolved "https://registry.npmmirror.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce" @@ -4932,6 +5005,11 @@ ws@^7.4.6: resolved "https://registry.npmmirror.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== +ws@~8.18.3: + version "8.18.3" + resolved "https://registry.npmmirror.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472" + integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== + xmlbuilder@>=11.0.1, xmlbuilder@^15.1.1: version "15.1.1" resolved "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5"