Merge branch '108' of https://github.com/HBAI-Ltd/Toonflow-app into 108
This commit is contained in:
commit
425dd58acf
@ -1,24 +1,25 @@
|
|||||||
---
|
---
|
||||||
name: execution
|
name: execution
|
||||||
description: 短剧漫剧制作助手。协助用户进行制作视频。
|
description: 用户需要拆分剧本的时候可以看此skill的参考资料,了解拆分原则和示例
|
||||||
---
|
---
|
||||||
|
|
||||||
# Production Agent
|
# execution Agent
|
||||||
|
|
||||||
短剧漫剧制作专用技能,提供剧本创作、分镜设计、角色设定等全流程指导。
|
执行层,负责整体决策和协调。接收用户需求后,完成对应的任务。
|
||||||
|
|
||||||
## 何时使用
|
## 何时使用
|
||||||
|
|
||||||
当用户需要以下帮助时激活此技能:
|
当用户需要以下帮助时激活此技能:
|
||||||
|
|
||||||
- 开始制作视频
|
- 拆分剧本
|
||||||
|
|
||||||
## 工作指引
|
## 工作指引
|
||||||
|
|
||||||
1. 理解用户的创作意图,根据项目类型(短剧/漫剧)调整输出风格
|
### 拆分剧本流程
|
||||||
2. 遵循标准的剧本格式,包含场景描述、角色动作、对白等要素
|
|
||||||
3. 保持角色一致性,关注剧情连贯性
|
- 当执行拆分剧本任务的时候,你需要先调用 `get_flowData` 获取原始剧本
|
||||||
4. 输出使用中文
|
- 根据[剧本拆分]文档中的拆分原则和示例,将剧本拆分成视频模型能够处理的片段
|
||||||
|
- 将拆分后的剧本片段必须使用`set_flowData_script`工具保存,然后仅需告知用户拆分完成
|
||||||
|
|
||||||
## 参考资料
|
## 参考资料
|
||||||
|
|
||||||
|
|||||||
@ -16,83 +16,24 @@
|
|||||||
- `string[]`
|
- `string[]`
|
||||||
- 数组每个元素是“原始剧本的一段子串”
|
- 数组每个元素是“原始剧本的一段子串”
|
||||||
- 拼接后应可还原原文语义顺序(允许空白规范化差异)
|
- 拼接后应可还原原文语义顺序(允许空白规范化差异)
|
||||||
|
- 开头必须是**喵,**
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
|
|
||||||
```ts
|
```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
|
```ts
|
||||||
[
|
[
|
||||||
"场景1:雨夜街道。林夏快步穿过巷口。",
|
"喵,场景1:雨夜街道。林夏快步穿过巷口。",
|
||||||
"场景2:天台。她停下,回头看向门口。",
|
"喵,场景2:天台。她停下,回头看向门口。",
|
||||||
"场景3:脚步声逼近。她握紧手机。"
|
"喵,场景3:脚步声逼近。她握紧手机。"
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|||||||
@ -63,6 +63,7 @@
|
|||||||
"qwen-ai-provider-v5": "^2.1.0",
|
"qwen-ai-provider-v5": "^2.1.0",
|
||||||
"serialize-error": "^13.0.1",
|
"serialize-error": "^13.0.1",
|
||||||
"sharp": "^0.34.5",
|
"sharp": "^0.34.5",
|
||||||
|
"socket.io": "^4.8.3",
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
"sucrase": "^3.35.1",
|
"sucrase": "^3.35.1",
|
||||||
"uuid": "^13.0.0",
|
"uuid": "^13.0.0",
|
||||||
|
|||||||
144
src/agents/productionAgent/index.ts
Normal file
144
src/agents/productionAgent/index.ts
Normal file
@ -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<ReturnType<Memory["get"]>>): 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<typeof parentCtx.resTool.textMessage>;
|
||||||
|
let fullResponse = "";
|
||||||
|
|
||||||
|
for await (const chunk of subTextStream) {
|
||||||
|
if (!msg!) msg = parentCtx.resTool.textMessage();
|
||||||
|
msg.send(chunk);
|
||||||
|
fullResponse += chunk;
|
||||||
|
}
|
||||||
|
msg!.end();
|
||||||
|
|
||||||
|
return fullResponse;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,75 +1,49 @@
|
|||||||
import { tool } from "ai";
|
import { tool, Tool } from "ai";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import u from "@/utils";
|
import u from "@/utils";
|
||||||
import { useSkill } from "@/utils/agent/skillsTools";
|
import { Socket } from "socket.io";
|
||||||
import { createAGUIStream } from "@/utils/agent/aguiTools";
|
|
||||||
|
|
||||||
interface FlowData {
|
interface FlowData {
|
||||||
|
rawScript: string;
|
||||||
script: {
|
script: {
|
||||||
blocks: string[];
|
blocks: string[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (isolationKey: string, agui: ReturnType<typeof createAGUIStream>) => {
|
export default (socket: Socket, toolsNames?: string[]) => {
|
||||||
const flowData: FlowData = {
|
let flowData: FlowData = {
|
||||||
|
rawScript: "",
|
||||||
script: {
|
script: {
|
||||||
blocks: [],
|
blocks: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return {
|
|
||||||
get_project_info: tool({
|
const tools: Record<string, Tool> = {
|
||||||
description: "获取项目信息",
|
get_flowData: tool({
|
||||||
inputSchema: z.object({}),
|
description: "获取当前工作区的状态/数据",
|
||||||
execute: async () => {
|
inputSchema: z.object({
|
||||||
return `
|
key: z.enum(["script"]).describe("state的key,rawScript代表原始剧本文字,script代表分块后的剧本"),
|
||||||
项目名称:仙逆
|
}),
|
||||||
视频风格:玄幻3D动漫
|
execute: async ({ key }) => {
|
||||||
视频类型:短剧
|
flowData = await new Promise((resolve) => socket.emit("getFlowData", { key }, (res: any) => resolve(res)));
|
||||||
项目描述:讲述了乡村平凡少年王林以心中之感动,逆仙而修,求的不仅是长生,更多的是摆脱那背后的蝼蚁之身。他坚信道在人为,以平庸的资质踏入修真仙途,历经坎坷风雨,凭着其聪睿的心智,一步一步走向巅峰,凭一己之力,扬名修真界。
|
console.log("[tool] get_flowData:", key);
|
||||||
总集数:24集每集2分钟
|
return flowData[key];
|
||||||
当前集数:3集
|
|
||||||
`;
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
get_state: tool({
|
set_flowData_script: tool({
|
||||||
description: "获取工作流指定板块数据",
|
description: "保存数据到工作区",
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
block: z.enum(["script"]).describe("板块名称,如 script"),
|
value: z.array(z.string()).describe("剧本分块后的文本数组"),
|
||||||
}),
|
}),
|
||||||
execute: async ({ block }) => {
|
execute: async ({ value }) => {
|
||||||
return flowData[block];
|
flowData.script.blocks = value;
|
||||||
},
|
socket.emit("setFlowData", { key: "script", value: { blocks: value } });
|
||||||
}),
|
|
||||||
execution: tool({
|
|
||||||
description: "执行层,负责具体执行具体的任务",
|
|
||||||
inputSchema: z.object({
|
|
||||||
taskDescription: z.string().describe("具体的任务描述详细信息"),
|
|
||||||
}),
|
|
||||||
execute: async ({ taskDescription }) => {
|
|
||||||
agui.custom("systemMessage", "已由 执行层AI 接管对话");
|
|
||||||
|
|
||||||
const skill = await useSkill("production-agent", "execution");
|
return true;
|
||||||
|
|
||||||
const { textStream } = await u.Ai.Text("productionAgent").stream({
|
|
||||||
system: skill.prompt,
|
|
||||||
messages: [{ role: "user", content: `请完成任务:${taskDescription}` }],
|
|
||||||
tools: {
|
|
||||||
...skill.tools,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let msg: ReturnType<typeof agui.textMessage> | 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: ["第一条记忆内容", "第二条记忆内容"] };
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!toolsNames) return tools;
|
||||||
|
else return Object.fromEntries(Object.entries(tools).filter(([name]) => toolsNames.includes(name)));
|
||||||
};
|
};
|
||||||
|
|||||||
12
src/app.ts
12
src/app.ts
@ -2,6 +2,8 @@ import "./logger";
|
|||||||
import "./err";
|
import "./err";
|
||||||
import "./env";
|
import "./env";
|
||||||
import express, { Request, Response, NextFunction } from "express";
|
import express, { Request, Response, NextFunction } from "express";
|
||||||
|
import { Server } from "socket.io";
|
||||||
|
import http from "node:http";
|
||||||
import expressWs from "express-ws";
|
import expressWs from "express-ws";
|
||||||
import logger from "morgan";
|
import logger from "morgan";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
@ -9,11 +11,15 @@ import buildRoute from "@/core";
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import u from "@/utils";
|
import u from "@/utils";
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
|
import socketInit from "@/socket/index";
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
let server: ReturnType<typeof app.listen> | null = null;
|
const server = http.createServer(app);
|
||||||
|
|
||||||
export default async function startServe(randomPort: Boolean = false) {
|
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();
|
if (process.env.NODE_ENV == "dev") await buildRoute();
|
||||||
|
|
||||||
expressWs(app);
|
expressWs(app);
|
||||||
@ -71,8 +77,8 @@ export default async function startServe(randomPort: Boolean = false) {
|
|||||||
|
|
||||||
const port = randomPort ? 0 : parseInt(process.env.PORT || "60000");
|
const port = randomPort ? 0 : parseInt(process.env.PORT || "60000");
|
||||||
return await new Promise((resolve) => {
|
return await new Promise((resolve) => {
|
||||||
server = app.listen(port, async (v) => {
|
server.listen(port, async () => {
|
||||||
const address = server?.address();
|
const address = server.address();
|
||||||
const realPort = typeof address === "string" ? address : address?.port;
|
const realPort = typeof address === "string" ? address : address?.port;
|
||||||
console.log(`[服务启动成功]: http://localhost:${realPort}`);
|
console.log(`[服务启动成功]: http://localhost:${realPort}`);
|
||||||
resolve(realPort);
|
resolve(realPort);
|
||||||
|
|||||||
@ -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<typeof agui.textMessage> | 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();
|
|
||||||
});
|
|
||||||
16
src/socket/index.ts
Normal file
16
src/socket/index.ts
Normal file
@ -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<string, (nsp: ReturnType<Server["of"]>) => 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}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
76
src/socket/resTool.ts
Normal file
76
src/socket/resTool.ts
Normal file
@ -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;
|
||||||
43
src/socket/routes/chat.ts
Normal file
43
src/socket/routes/chat.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { Namespace, Socket } from "socket.io";
|
||||||
|
|
||||||
|
const users = new Map<string, string>(); // 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
69
src/socket/routes/productionAgent.ts
Normal file
69
src/socket/routes/productionAgent.ts
Normal file
@ -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<Boolean> {
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
74
src/types/database.d.ts
vendored
74
src/types/database.d.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
// @db-hash 04e1150a9773602183de5f660a52b092
|
// @db-hash feca77a2c2ec5b6a2989347f982558d5
|
||||||
//该文件由脚本自动生成,请勿手动修改
|
//该文件由脚本自动生成,请勿手动修改
|
||||||
|
|
||||||
export interface memories {
|
export interface memories {
|
||||||
@ -35,12 +35,18 @@ export interface o_assets {
|
|||||||
'projectId'?: number | null;
|
'projectId'?: number | null;
|
||||||
'prompt'?: string | null;
|
'prompt'?: string | null;
|
||||||
'remark'?: string | null;
|
'remark'?: string | null;
|
||||||
'scriptId'?: number | null;
|
|
||||||
'sonId'?: number | null;
|
'sonId'?: number | null;
|
||||||
'startTime'?: number | null;
|
'startTime'?: number | null;
|
||||||
'state'?: string | null;
|
'state'?: string | null;
|
||||||
'type'?: 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 {
|
export interface o_event {
|
||||||
'createTime'?: number | null;
|
'createTime'?: number | null;
|
||||||
'detail'?: string | null;
|
'detail'?: string | null;
|
||||||
@ -61,10 +67,33 @@ export interface o_image {
|
|||||||
'assetsId'?: number | null;
|
'assetsId'?: number | null;
|
||||||
'filePath'?: string | null;
|
'filePath'?: string | null;
|
||||||
'id'?: number;
|
'id'?: number;
|
||||||
'model'?: string | null;
|
'projectId'?: number | null;
|
||||||
'resolution'?: string | null;
|
'scriptId'?: number | null;
|
||||||
'state'?: string | null;
|
'state'?: string | null;
|
||||||
'type'?: 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 {
|
export interface o_novel {
|
||||||
'chapter'?: string | null;
|
'chapter'?: string | null;
|
||||||
@ -97,6 +126,15 @@ export interface o_project {
|
|||||||
'userId'?: number | null;
|
'userId'?: number | null;
|
||||||
'videoRatio'?: string | 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 {
|
export interface o_script {
|
||||||
'content'?: string | null;
|
'content'?: string | null;
|
||||||
'createTime'?: number | null;
|
'createTime'?: number | null;
|
||||||
@ -104,15 +142,35 @@ export interface o_script {
|
|||||||
'name'?: string | null;
|
'name'?: string | null;
|
||||||
'projectId'?: number | 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 {
|
export interface o_setting {
|
||||||
'key'?: string | null;
|
'key'?: string | null;
|
||||||
'value'?: string | null;
|
'value'?: string | null;
|
||||||
}
|
}
|
||||||
|
export interface o_skills {
|
||||||
|
'id'?: number;
|
||||||
|
'name'?: string | null;
|
||||||
|
'startTime'?: number | null;
|
||||||
|
}
|
||||||
export interface o_storyboard {
|
export interface o_storyboard {
|
||||||
'createTime'?: number | null;
|
'createTime'?: number | null;
|
||||||
'id'?: number;
|
'id'?: number;
|
||||||
'name'?: string | null;
|
'name'?: string | null;
|
||||||
}
|
}
|
||||||
|
export interface o_storyboardScript {
|
||||||
|
'id'?: number;
|
||||||
|
'scriptId'?: number | null;
|
||||||
|
'storyboardId'?: number | null;
|
||||||
|
}
|
||||||
export interface o_tasks {
|
export interface o_tasks {
|
||||||
'describe'?: string | null;
|
'describe'?: string | null;
|
||||||
'id'?: number;
|
'id'?: number;
|
||||||
@ -178,17 +236,25 @@ export interface DB {
|
|||||||
"o_agentDeploy": o_agentDeploy;
|
"o_agentDeploy": o_agentDeploy;
|
||||||
"o_artStyle": o_artStyle;
|
"o_artStyle": o_artStyle;
|
||||||
"o_assets": o_assets;
|
"o_assets": o_assets;
|
||||||
|
"o_chatHistory": o_chatHistory;
|
||||||
"o_event": o_event;
|
"o_event": o_event;
|
||||||
"o_eventChapter": o_eventChapter;
|
"o_eventChapter": o_eventChapter;
|
||||||
"o_flowData": o_flowData;
|
"o_flowData": o_flowData;
|
||||||
"o_image": o_image;
|
"o_image": o_image;
|
||||||
|
"o_model": o_model;
|
||||||
|
"o_myTasks": o_myTasks;
|
||||||
"o_novel": o_novel;
|
"o_novel": o_novel;
|
||||||
"o_outline": o_outline;
|
"o_outline": o_outline;
|
||||||
"o_outlineNovel": o_outlineNovel;
|
"o_outlineNovel": o_outlineNovel;
|
||||||
"o_project": o_project;
|
"o_project": o_project;
|
||||||
|
"o_prompts": o_prompts;
|
||||||
"o_script": o_script;
|
"o_script": o_script;
|
||||||
|
"o_scriptAssets": o_scriptAssets;
|
||||||
|
"o_scriptOutline": o_scriptOutline;
|
||||||
"o_setting": o_setting;
|
"o_setting": o_setting;
|
||||||
|
"o_skills": o_skills;
|
||||||
"o_storyboard": o_storyboard;
|
"o_storyboard": o_storyboard;
|
||||||
|
"o_storyboardScript": o_storyboardScript;
|
||||||
"o_tasks": o_tasks;
|
"o_tasks": o_tasks;
|
||||||
"o_user": o_user;
|
"o_user": o_user;
|
||||||
"o_vendorConfig": o_vendorConfig;
|
"o_vendorConfig": o_vendorConfig;
|
||||||
|
|||||||
@ -97,11 +97,11 @@ class Memory {
|
|||||||
embedding: JSON.stringify(embedding),
|
embedding: JSON.stringify(embedding),
|
||||||
relatedMessageIds: null,
|
relatedMessageIds: null,
|
||||||
summarized: 0,
|
summarized: 0,
|
||||||
createdAt: Date.now(),
|
createTime: Date.now(),
|
||||||
} as any);
|
} 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)) {
|
if (unsummarized.length >= Number(messagesPerSummary)) {
|
||||||
const batch = unsummarized.slice(0, Number(messagesPerSummary));
|
const batch = unsummarized.slice(0, Number(messagesPerSummary));
|
||||||
@ -120,7 +120,7 @@ class Memory {
|
|||||||
embedding: JSON.stringify(summaryEmbedding),
|
embedding: JSON.stringify(summaryEmbedding),
|
||||||
relatedMessageIds: JSON.stringify(batchIds),
|
relatedMessageIds: JSON.stringify(batchIds),
|
||||||
summarized: 0,
|
summarized: 0,
|
||||||
createdAt: Date.now(),
|
createTime: Date.now(),
|
||||||
} as any);
|
} as any);
|
||||||
|
|
||||||
// 标记已总结
|
// 标记已总结
|
||||||
@ -140,12 +140,12 @@ class Memory {
|
|||||||
const shortTerm = await u
|
const shortTerm = await u
|
||||||
.db("memories")
|
.db("memories")
|
||||||
.where({ isolationKey, type: "message", summarized: 0 })
|
.where({ isolationKey, type: "message", summarized: 0 })
|
||||||
.orderBy("createdAt", "desc")
|
.orderBy("createTime", "desc")
|
||||||
.limit(Number(shortTermLimit));
|
.limit(Number(shortTermLimit));
|
||||||
shortTerm.reverse(); // 最旧在前
|
shortTerm.reverse(); // 最旧在前
|
||||||
|
|
||||||
// summaries: 最近的 summary
|
// 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();
|
summaries.reverse();
|
||||||
|
|
||||||
// rag: 向量搜索所有 messages
|
// rag: 向量搜索所有 messages
|
||||||
@ -154,12 +154,12 @@ class Memory {
|
|||||||
const ragResults = vectorSearch(allMessages, queryEmbedding, Number(ragLimit));
|
const ragResults = vectorSearch(allMessages, queryEmbedding, Number(ragLimit));
|
||||||
|
|
||||||
return {
|
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) => ({
|
summaries: summaries.map((s) => ({
|
||||||
id: s.id,
|
id: s.id,
|
||||||
content: s.content,
|
content: s.content,
|
||||||
relatedMessageIds: JSON.parse(s.relatedMessageIds || "[]"),
|
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 })),
|
rag: ragResults.map((r) => ({ id: r.id, content: r.content, similarity: r.similarity })),
|
||||||
};
|
};
|
||||||
@ -190,9 +190,9 @@ class Memory {
|
|||||||
|
|
||||||
if (messageIds.length === 0) return [];
|
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() {
|
getTools() {
|
||||||
|
|||||||
@ -23,10 +23,16 @@ function parseFrontmatter(content: string): { name: string; description: string
|
|||||||
|
|
||||||
for (let i = 0; i < lines.length; ) {
|
for (let i = 0; i < lines.length; ) {
|
||||||
const colonIndex = lines[i].indexOf(":");
|
const colonIndex = lines[i].indexOf(":");
|
||||||
if (colonIndex === -1) { i++; continue; }
|
if (colonIndex === -1) {
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const key = lines[i].slice(0, colonIndex).trim();
|
const key = lines[i].slice(0, colonIndex).trim();
|
||||||
if (!key) { i++; continue; }
|
if (!key) {
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let value = lines[i].slice(colonIndex + 1).trim();
|
let value = lines[i].slice(colonIndex + 1).trim();
|
||||||
i++;
|
i++;
|
||||||
@ -44,8 +50,7 @@ function parseFrontmatter(content: string): { name: string; description: string
|
|||||||
result[key] = value;
|
result[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result.name) throw new Error("Frontmatter missing required field: name");
|
if (!result.name || !result.description) throw new Error("Frontmatter missing required field: name or description");
|
||||||
if (!result.description) throw new Error("Frontmatter missing required field: description");
|
|
||||||
return { name: result.name, description: result.description };
|
return { name: result.name, description: result.description };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,13 +62,17 @@ function stripFrontmatter(content: string): string {
|
|||||||
|
|
||||||
async function listResources(dir: string, base = ""): Promise<string[]> {
|
async function listResources(dir: string, base = ""): Promise<string[]> {
|
||||||
let entries;
|
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[] = [];
|
const files: string[] = [];
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const rel = base ? `${base}/${entry.name}` : entry.name;
|
const rel = base ? `${base}/${entry.name}` : entry.name;
|
||||||
if (entry.isDirectory()) {
|
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") {
|
} else if (entry.name !== "SKILL.md") {
|
||||||
files.push(rel);
|
files.push(rel);
|
||||||
}
|
}
|
||||||
@ -71,22 +80,39 @@ async function listResources(dir: string, base = ""): Promise<string[]> {
|
|||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 读取单个技能 ====================
|
||||||
|
|
||||||
|
async function readSkillFromDir(skillDir: string): Promise<SkillRecord | null> {
|
||||||
|
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 {
|
function buildCatalog(skills: SkillRecord[]): string {
|
||||||
return [
|
const entries = skills.map((s) => ` <skill>\n <name>${s.name}</name>\n <description>${s.description}</description>\n </skill>`).join("\n");
|
||||||
"## Skills",
|
|
||||||
"以下技能提供了专业任务的专用指令。",
|
return `## Skills
|
||||||
"当任务与某个技能的描述匹配时,调用 activate_skill 工具并传入技能名称来加载完整指令。",
|
以下技能提供了专业任务的专用指令。
|
||||||
"加载后遵循技能指令执行任务,需要时调用 read_skill_file 读取资源文件内容。",
|
当任务与某个技能的描述匹配时,调用 activate_skill 工具并传入技能名称来加载完整指令。
|
||||||
"",
|
加载后遵循技能指令执行任务,需要时调用 read_skill_file 读取资源文件内容。
|
||||||
"<available_skills>",
|
|
||||||
` <skill>`,
|
<available_skills>
|
||||||
` <name>${skill.name}</name>`,
|
${entries}
|
||||||
` <description>${skill.description}</description>`,
|
</available_skills>`;
|
||||||
` </skill>`,
|
|
||||||
"</available_skills>",
|
|
||||||
].join("\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 激活 + 执行工具 ====================
|
// ==================== 激活 + 执行工具 ====================
|
||||||
@ -107,14 +133,15 @@ function createSkillTools(skills: SkillRecord[]) {
|
|||||||
console.log(`[Skill] ❌ 激活失败:未找到技能 "${name}"`);
|
console.log(`[Skill] ❌ 激活失败:未找到技能 "${name}"`);
|
||||||
return { error: `Skill '${name}' not found` };
|
return { error: `Skill '${name}' not found` };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activated.has(name)) {
|
if (activated.has(name)) {
|
||||||
console.log(`[Skill] ℹ️ 技能 "${name}" 已在当前会话中激活,跳过重复注入`);
|
console.log(`[Skill] ℹ️ 技能 "${name}" 已在当前会话中激活,跳过重复注入`);
|
||||||
return { already_active: true, message: `技能 "${name}" 已激活,无需重复加载` };
|
return { already_active: true, message: `技能 "${name}" 已激活,无需重复加载` };
|
||||||
}
|
}
|
||||||
|
|
||||||
let content: string;
|
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}`);
|
console.log(`[Skill] ❌ 激活失败:无法读取 ${skill.location}`);
|
||||||
return { error: `Failed to read SKILL.md for '${name}'` };
|
return { error: `Failed to read SKILL.md for '${name}'` };
|
||||||
}
|
}
|
||||||
@ -122,23 +149,19 @@ function createSkillTools(skills: SkillRecord[]) {
|
|||||||
const body = stripFrontmatter(content);
|
const body = stripFrontmatter(content);
|
||||||
const resources = await listResources(skill.baseDir);
|
const resources = await listResources(skill.baseDir);
|
||||||
activated.add(name);
|
activated.add(name);
|
||||||
|
|
||||||
console.log(`[Skill] 📖 已激活技能:${skill.name}(${body.length} 字符,${resources.length} 个资源文件)`);
|
console.log(`[Skill] 📖 已激活技能:${skill.name}(${body.length} 字符,${resources.length} 个资源文件)`);
|
||||||
|
|
||||||
const resourcesXml = resources.length > 0
|
const resourcesXml =
|
||||||
? `\n<skill_resources>\n${resources.map((f) => ` <file>${f}</file>`).join("\n")}\n</skill_resources>`
|
resources.length > 0 ? `\n<skill_resources>\n${resources.map((f) => ` <file>${f}</file>`).join("\n")}\n</skill_resources>` : "";
|
||||||
: "";
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: [
|
content: `<skill_content name="${skill.name}">
|
||||||
`<skill_content name="${skill.name}">`,
|
${body}
|
||||||
body,
|
|
||||||
"",
|
Skill directory: ${skill.baseDir}
|
||||||
`Skill directory: ${skill.baseDir}`,
|
相对路径基于此技能目录解析,使用 read_skill_file 工具读取资源文件。
|
||||||
`相对路径基于此技能目录解析,使用 read_skill_file 工具读取资源文件。`,
|
${resourcesXml}
|
||||||
resourcesXml,
|
</skill_content>`,
|
||||||
`</skill_content>`,
|
|
||||||
].join("\n"),
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@ -180,26 +203,25 @@ function createSkillTools(skills: SkillRecord[]) {
|
|||||||
export async function useSkill(...segments: string[]) {
|
export async function useSkill(...segments: string[]) {
|
||||||
if (segments.length === 0) return { prompt: "", tools: {} };
|
if (segments.length === 0) return { prompt: "", tools: {} };
|
||||||
|
|
||||||
const baseDir = path.join(getPath("skills"), ...segments);
|
const skills = new Map<string, SkillRecord>();
|
||||||
const location = path.join(baseDir, "SKILL.md");
|
|
||||||
|
|
||||||
let content: string;
|
const primary = await readSkillFromDir(path.join(getPath("skills"), ...segments));
|
||||||
try { content = await fs.readFile(location, "utf-8"); } catch {
|
if (primary) skills.set(primary.name, primary);
|
||||||
console.log(`[Skill] ⚠️ 未发现技能:${segments.join("/")}`);
|
|
||||||
return { prompt: "", tools: {} };
|
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 };
|
if (skills.size === 0) return { prompt: "", tools: {} };
|
||||||
try { metadata = parseFrontmatter(content); } catch (e) {
|
|
||||||
console.log(`[Skill] ⚠️ 解析失败 "${segments.join("/")}":${(e as Error).message}`);
|
|
||||||
return { prompt: "", tools: {} };
|
|
||||||
}
|
|
||||||
|
|
||||||
const skill: SkillRecord = { ...metadata, location, baseDir };
|
const allSkills = [...skills.values()];
|
||||||
console.log(`[Skill] ✅ 发现技能:${skill.name} — ${skill.description}`);
|
return { prompt: buildCatalog(allSkills), tools: createSkillTools(allSkills) };
|
||||||
|
|
||||||
return {
|
|
||||||
prompt: buildCatalog(skill),
|
|
||||||
tools: createSkillTools([skill]),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
92
yarn.lock
92
yarn.lock
@ -742,6 +742,11 @@
|
|||||||
resolved "https://registry.npmmirror.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f"
|
resolved "https://registry.npmmirror.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f"
|
||||||
integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==
|
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":
|
"@standard-schema/spec@^1.0.0", "@standard-schema/spec@^1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8"
|
resolved "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8"
|
||||||
@ -784,7 +789,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/cors@^2.8.19":
|
"@types/cors@^2.8.12", "@types/cors@^2.8.19":
|
||||||
version "2.8.19"
|
version "2.8.19"
|
||||||
resolved "https://registry.npmmirror.com/@types/cors/-/cors-2.8.19.tgz#d93ea2673fd8c9f697367f5eeefc2bbfa94f0342"
|
resolved "https://registry.npmmirror.com/@types/cors/-/cors-2.8.19.tgz#d93ea2673fd8c9f697367f5eeefc2bbfa94f0342"
|
||||||
integrity sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==
|
integrity sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==
|
||||||
@ -882,7 +887,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
undici-types "~7.18.0"
|
undici-types "~7.18.0"
|
||||||
|
|
||||||
"@types/node@>=13.7.0":
|
"@types/node@>=10.0.0", "@types/node@>=13.7.0":
|
||||||
version "25.5.0"
|
version "25.5.0"
|
||||||
resolved "https://registry.npmmirror.com/@types/node/-/node-25.5.0.tgz#5c99f37c443d9ccc4985866913f1ed364217da31"
|
resolved "https://registry.npmmirror.com/@types/node/-/node-25.5.0.tgz#5c99f37c443d9ccc4985866913f1ed364217da31"
|
||||||
integrity sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==
|
integrity sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==
|
||||||
@ -946,7 +951,7 @@
|
|||||||
resolved "https://registry.npmmirror.com/@types/verror/-/verror-1.10.11.tgz#d3d6b418978c8aa202d41e5bb3483227b6ecc1bb"
|
resolved "https://registry.npmmirror.com/@types/verror/-/verror-1.10.11.tgz#d3d6b418978c8aa202d41e5bb3483227b6ecc1bb"
|
||||||
integrity sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==
|
integrity sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==
|
||||||
|
|
||||||
"@types/ws@*":
|
"@types/ws@*", "@types/ws@^8.5.12":
|
||||||
version "8.18.1"
|
version "8.18.1"
|
||||||
resolved "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz#48464e4bf2ddfd17db13d845467f6070ffea4aa9"
|
resolved "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz#48464e4bf2ddfd17db13d845467f6070ffea4aa9"
|
||||||
integrity sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==
|
integrity sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==
|
||||||
@ -988,6 +993,14 @@ accepts@^2.0.0:
|
|||||||
mime-types "^3.0.0"
|
mime-types "^3.0.0"
|
||||||
negotiator "^1.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:
|
acorn-walk@^8.3.4:
|
||||||
version "8.3.5"
|
version "8.3.5"
|
||||||
resolved "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.5.tgz#8a6b8ca8fc5b34685af15dabb44118663c296496"
|
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"
|
resolved "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
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:
|
basic-auth@~2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.npmmirror.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
|
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"
|
resolved "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793"
|
||||||
integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==
|
integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==
|
||||||
|
|
||||||
cookie@^0.7.1:
|
cookie@^0.7.1, cookie@~0.7.2:
|
||||||
version "0.7.2"
|
version "0.7.2"
|
||||||
resolved "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7"
|
resolved "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7"
|
||||||
integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
|
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"
|
resolved "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||||
integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==
|
integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==
|
||||||
|
|
||||||
cors@^2.8.5:
|
cors@^2.8.5, cors@~2.8.5:
|
||||||
version "2.8.6"
|
version "2.8.6"
|
||||||
resolved "https://registry.npmmirror.com/cors/-/cors-2.8.6.tgz#ff5dd69bd95e547503820d29aba4f8faf8dfec96"
|
resolved "https://registry.npmmirror.com/cors/-/cors-2.8.6.tgz#ff5dd69bd95e547503820d29aba4f8faf8dfec96"
|
||||||
integrity sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==
|
integrity sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==
|
||||||
@ -1710,7 +1728,7 @@ debug@2.6.9:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms "2.0.0"
|
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"
|
version "4.4.3"
|
||||||
resolved "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
|
resolved "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
|
||||||
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
|
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
|
||||||
@ -1974,6 +1992,27 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
once "^1.4.0"
|
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:
|
env-paths@^2.2.0:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
|
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"
|
resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5"
|
||||||
integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==
|
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"
|
version "2.1.35"
|
||||||
resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
|
resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
|
||||||
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
|
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"
|
resolved "https://registry.npmmirror.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e"
|
||||||
integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==
|
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:
|
negotiator@^0.6.2:
|
||||||
version "0.6.4"
|
version "0.6.4"
|
||||||
resolved "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7"
|
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"
|
resolved "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
|
||||||
integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
|
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:
|
socks-proxy-agent@^6.0.0:
|
||||||
version "6.2.1"
|
version "6.2.1"
|
||||||
resolved "https://registry.npmmirror.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce"
|
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"
|
resolved "https://registry.npmmirror.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9"
|
||||||
integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==
|
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:
|
xmlbuilder@>=11.0.1, xmlbuilder@^15.1.1:
|
||||||
version "15.1.1"
|
version "15.1.1"
|
||||||
resolved "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5"
|
resolved "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user