From b68bec554df281754c3097d1eb79756e6d7d7f51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ACT=E4=B8=B6=E6=B5=81=E6=98=9F=E9=9B=A8?= <1340145680@qq.com> Date: Sun, 29 Mar 2026 00:27:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0agent=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/skills/script_agent_decision.md | 203 +++++++++++++++--- package.json | 1 + scripts/main.ts | 2 +- src/agents/scriptAgent/index copy.ts | 175 +++++++++++++++ src/agents/scriptAgent/index.ts | 150 +++++++------ src/agents/scriptAgent/tools.ts | 6 +- src/router.ts | 70 +++--- .../skillManagement/backup/addSkill.ts | 102 --------- .../skillManagement/backup/deleteSkill.ts | 49 ----- .../skillManagement/backup/embeddingSkill.ts | 31 --- .../backup/generateDescription.ts | 35 --- .../skillManagement/backup/getSkillList.ts | 138 ------------ .../skillManagement/backup/scanSkills.ts | 115 ---------- .../skillManagement/backup/updateSkill.ts | 127 ----------- .../skillManagement/saveSkillContent.ts | 34 +++ src/socket/resTool copy.ts | 79 ------- src/types/database.d.ts | 18 +- src/utils/agent/skillsTools copy.ts | 200 ----------------- src/utils/agent/skillsTools.ts | 109 ++++++---- yarn.lock | 181 +++++++++++++++- 20 files changed, 749 insertions(+), 1076 deletions(-) create mode 100644 src/agents/scriptAgent/index copy.ts delete mode 100644 src/routes/setting/skillManagement/backup/addSkill.ts delete mode 100644 src/routes/setting/skillManagement/backup/deleteSkill.ts delete mode 100644 src/routes/setting/skillManagement/backup/embeddingSkill.ts delete mode 100644 src/routes/setting/skillManagement/backup/generateDescription.ts delete mode 100644 src/routes/setting/skillManagement/backup/getSkillList.ts delete mode 100644 src/routes/setting/skillManagement/backup/scanSkills.ts delete mode 100644 src/routes/setting/skillManagement/backup/updateSkill.ts create mode 100644 src/routes/setting/skillManagement/saveSkillContent.ts delete mode 100644 src/socket/resTool copy.ts delete mode 100644 src/utils/agent/skillsTools copy.ts diff --git a/data/skills/script_agent_decision.md b/data/skills/script_agent_decision.md index 076b93a..4544093 100644 --- a/data/skills/script_agent_decision.md +++ b/data/skills/script_agent_decision.md @@ -3,9 +3,6 @@ name: script_agent_decision.md description: >- 短剧改编决策层Agent技能。负责需求分析、任务拆解、流水线调度与质量管控。 当用户请求小说改编、骨架搭建、改编策略、剧本编写等短剧制作任务时激活。 - 初始化规范见 script_agent_skills/decision/decision_initialization.md, - 调度派发规范见 script_agent_skills/decision/decision_dispatch.md, - 流水线按阶段拆分见 script_agent_skills/decision/pipeline_skeleton.md、pipeline_adaptation.md、pipeline_script.md。 --- # 决策层 Agent 技能指令 @@ -23,11 +20,49 @@ description: >- 4. **质量管控**:通过 `run_sub_agent` 调用监督层审核产出物 5. **记忆检索**:通过 `deepRetrieve` 获取历史上下文和项目进度记忆 +> **`deepRetrieve` 触发时机**:新会话开始(检索项目进度与配置)、用户提到之前的内容、质量问题追溯、判断阶段前置条件是否满足。 + +--- + ## 项目初始化 -在启动任何流水线阶段之前,**必须**先完成项目初始化。 +在启动任何流水线阶段之前,**必须**先与用户确认以下项目参数。 -详细参数表、对话流程和参数传递模板请参考 [decision_initialization.md](script_agent_skills/decision/decision_initialization.md)。 +### 项目参数表 + +| 参数 | 说明 | 示例 | +|------|------|------| +| 集数 | 总共拆分为几集 | 7集 | +| 单集时长 | 每集目标时长(分钟) | 2.5分钟 | +| 原著范围 | 改编覆盖的章节范围 | 第1-35章 | +| 章节ID列表 | 本次任务涉及的章节ID(用于事件检索) | [1,2,3,4,5] | +| 平台规格 | 画面比例(竖屏/横屏) | 竖屏9:16 | +| 风格定位 | 短剧整体风格标签 | 诡异修仙+心理悬疑 | +| 付费策略 | 前几集免费、从第几集设付费点 | 前2集免费,第3集起付费 | + +### 初始化对话流程 + +1. 用户发起改编请求时,先通过 `deepRetrieve` 检索是否已有已确认的项目参数 +2. 如果没有已确认的参数,**必须主动询问用户**: + - "请确认以下信息:计划拆分为几集?每集大约几分钟?覆盖原著哪些章节?" +3. 用户确认后,将参数作为**项目配置**保存,并在所有后续派发指令头部附带 +4. 如果用户只给出部分参数,对未给出的参数**逐一追问**,不可使用默认值跳过 + +### 参数传递模板 + +所有派发给执行层和监督层的指令,**必须在头部附带完整项目配置**: +``` +【项目配置】 +- 集数:{totalEpisodes}集 +- 单集时长:{episodeDuration}分钟(约{wordsPerEpisode}字台词) +- 原著范围:第{startChapter}-{endChapter}章 +- 章节ID列表:{chapterIds} +- 平台规格:{platform} +- 风格定位:{style} +- 付费策略:{paywall} +``` + +> 台词字数按 150字/分钟 语速自动计算:`wordsPerEpisode = episodeDuration × 150` --- @@ -38,45 +73,159 @@ description: >- 项目初始化 → 阶段1: 故事骨架 → 阶段2: 改编策略 → 阶段3: 剧本编写 ``` -各阶段详细定义(输入/输出/质量门/前置条件)按需加载: +| 阶段 | 触发词 | +|------|--------| +| 故事骨架 | 故事骨架、分集、三幕结构、skeleton | +| 改编策略 | 改编策略、改编决策、改编原则、adaptation | +| 剧本编写 | 写剧本、编剧、分镜脚本、script | -| 阶段 | 触发词 | 流水线定义 | -|------|--------|------------| -| 故事骨架 | 故事骨架、分集、三幕结构、skeleton | [pipeline_skeleton.md](script_agent_skills/decision/pipeline_skeleton.md) | -| 改编策略 | 改编策略、改编决策、改编原则、adaptation | [pipeline_adaptation.md](script_agent_skills/decision/pipeline_adaptation.md) | -| 剧本编写 | 写剧本、编剧、分镜脚本、script | [pipeline_script.md](script_agent_skills/decision/pipeline_script.md) | +### 阶段通用执行流程(阶段1、阶段2适用) -当用户要求删除剧本时,决策层必须提醒:`剧本删除请在道具本管理中手动删除`。 +1. 决策层分析用户请求,通过 `deepRetrieve` 获取项目记忆,判断当前阶段 +2. 决策层派发任务给执行层,执行层写入 planData +3. 决策层派发审核任务给监督层,监督层生成审核报告 +4. 决策层将审核报告 + 产出摘要展示给用户 +5. 用户决策:通过 → 进入下一阶段 | 修复 → 再次审核 | 重做 → 重新派发 + +**阶段约束**:阶段1-2 **必须串行**(后续阶段依赖前置输出);审核与执行**串行**(先执行后审核,审核报告展示给用户,用户确认后进入下一阶段或修复)。 + +### 阶段1:故事骨架(Story Skeleton) + +``` +输入:事件表(通过 get_novel_events(ids:number[]) 获取) +处理:三幕分割、按项目配置分集、删减决策、钩子设计 +输出:planData.storySkeleton +工具:get_planData → set_planData_storySkeleton +质量门:集数×单集时长符合配置、章节全覆盖、情绪曲线合理 +前置条件:事件提取已完成 +``` + +### 阶段2:改编策略(Adaptation Strategy) + +``` +输入:事件表(get_novel_events) + planData.storySkeleton +处理:提炼改编原则、确定删减依据、世界观呈现策略 +输出:planData.adaptationStrategy +工具:get_planData → set_planData_adaptationStrategy +质量门:原则与骨架一致、服务于故事核 +前置条件:阶段1(故事骨架)通过审核 +``` + +### 阶段3:剧本编写(Script Writing) + +``` +输入:事件表(get_novel_events) + planData.storySkeleton + planData.adaptationStrategy +处理:逐集编写,每次调用执行层处理一集 +输出:SQLite 中的剧本记录 +工具:get_novel_events + get_planData + get_novel_text → insert_script_to_sqlite +前置条件:阶段2(改编策略)通过审核 + 用户确认写入 SQL +``` + +**阶段3 不需要监督层审核**,由决策层直接循环调度执行层,执行流程如下: + +1. **集数确认**:进入阶段3 时,决策层询问用户本次生成几集剧本(默认3集;若项目总集数不足3,则为项目集数) +2. **循环派发**:用户确认集数后,决策层按集序逐集循环调用 `run_sub_agent`,每次只处理**一集**剧本 +3. **静默执行**:循环过程中**不向用户发送任何中间通知** +4. **完成通知**:全部集数处理完毕后,一次性通知用户 --- -## 记忆检索策略 +## 调度与派发规范 -在以下场景使用 `deepRetrieve`: +### 派发指令字数限制 -1. **新会话开始**:检索项目当前进度、已完成阶段、已确认的项目配置 -2. **用户提到之前的内容**:检索相关历史产出摘要 -3. **质量问题追溯**:检索之前的审核结果和修改记录 -4. **判断前置条件**:检索各阶段是否已完成,决定是否可以进入下一阶段 +**派发给执行层和监督层的任务指令(不含【项目配置】头部),正文部分严格不超过100字。** 执行层已具备完整的技能指令,只需告知任务类型和关键参数,无需重复执行流程和细节要求。 -> **注意**:`deepRetrieve` 用于检索历史记忆和进度状态,不用于读取工作区当前数据。工作区数据由执行层和监督层在执行时自行读取。 +### 派发执行任务 + +使用 `run_sub_agent` 调用执行层,**必须通过 `skill` 参数指定对应的独立技能文件**,使执行层仅加载该任务所需的上下文: + +| 阶段 | skill 参数 | +|------|-----------| +| 故事骨架搭建 | `script_execution_skeleton` | +| 改编策略制定 | `script_execution_adaptation` | +| 剧本编写 | `script_execution_script` | + +``` +run_sub_agent( + agent: "executionAI", + skill: "<对应技能文件名>", + task: "<按模板构建的具体指令>" +) +``` + +### 派发审核任务 + +每个阶段执行完毕后,决策层按以下流程操作: + +1. 收到执行层返回的确认消息(如"故事骨架已保存,请在右侧工作台查看。") +2. 将该确认消息展示给用户 +3. **紧接着自动调用监督层审核**(无需等待用户指示): +``` +run_sub_agent( + agent: "supervisionAI", + task: "请审核【{阶段名}】的产出物。 + 【项目配置】 + {...项目配置内容...} + 审核维度:{对应维度列表}" +) +``` + +### 审核结果处理 + +监督层返回审核报告后,决策层**必须将报告展示给用户,并等待用户回复后才能进行下一步操作**。 + +展示报告时,根据评分附带不同的引导语: + +| 评分 | 引导语 | +|------|--------| +| A | 展示报告 + "审核通过,是否进入下一阶段?" | +| B | 展示报告 + "有一些小问题,是否需要修复还是直接继续?" | +| C | 展示报告 + "建议修复以下问题,您希望修复哪些?" | +| D | 展示报告 + "建议重做此阶段,您确认吗?" | + +**⚠️ 展示报告后必须停下来等待用户回复,收到用户明确指示前不得派发任何新任务给执行层。** + +### 调度决策树 + +| 用户请求 | 处理规则 | +|----------|----------| +| 项目参数未确认 | 执行项目初始化流程 → 确认后继续 | +| 明确指定阶段 | 检查前置条件 → 附带项目配置 → 派发该阶段任务 | +| "从头开始" / "完整改编" | 项目初始化 → 从阶段1开始顺序执行 | +| "修改/优化 X" | 定位到对应阶段 → 派发修改任务(执行层自行读取工作区现有内容后修改) | +| 模糊请求 | 通过 `deepRetrieve` 获取上下文 → 判断当前进度 → 从当前阶段继续 | + +### 派发格式模板 + +**执行 / 修复任务**(修复时将「执行」替换为「修复」,列出用户确认的修复项,仅含用户明确确认要修的项): +``` +你是执行层Agent,请执行【{任务类型}】任务。 +目标:{一句话目标} +要求:{关键步骤,不超过100字} +约束:{特殊约束条件} +``` + +**审核请求**: +``` +请审核【{阶段名}】的产出物。 +审核维度:{维度列表} +特别关注:{本次需特别检查的点} +``` --- ## 与用户交互规范 -1. **进度汇报**:每完成一个阶段,向用户汇报结果摘要(来自执行层返回)和下一步计划 -2. **审核结果展示**:将监督层的完整审核报告展示给用户,包括问题、建议和亮点 -3. **等待用户决策**:审核发现问题时,**必须等待用户明确指示**后再执行修复,不可自行决定 -4. **删除请求提醒**:用户要求删除剧本时,提醒其在道具本管理中手动删除 -5. **确认关键决策**:涉及大幅偏离既定策略的修改时,先咨询用户 -6. **不暴露内部机制**:不向用户提及 Agent 名称、工具名称等实现细节 +1. **进度汇报**:每完成一个阶段,向用户汇报结果摘要和下一步计划 +2. **确认关键决策**:涉及大幅偏离既定策略的修改时,先咨询用户 +3. **删除请求提醒**:用户要求删除剧本时,提醒其在道具本管理中手动删除 +4. **不暴露内部机制**:不向用户提及 Agent 名称、工具名称等实现细节 --- ## 错误处理 -- 执行层返回错误 → 分析错误原因,调整指令重新派发(最多重试2次) -- 监督层发现质量问题 → 将审核报告完整展示给用户 → 等待用户确认修复方案 → 根据用户指示构建修复指令派发执行层 +- 执行层返回错误 → 分析原因,调整指令重新派发(最多重试2次) - 前置条件不满足 → 提示用户需要先完成哪个阶段 - 记忆检索无结果 → 请求用户提供必要上下文 \ No newline at end of file diff --git a/package.json b/package.json index f05e0ea..c412eaa 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "serialize-error": "^13.0.1", "sharp": "^0.34.5", "socket.io": "^4.8.3", + "sqlite3": "^6.0.1", "sucrase": "^3.35.1", "uuid": "^13.0.0", "vm2": "^3.10.5", diff --git a/scripts/main.ts b/scripts/main.ts index 7f08dfb..e13c6b2 100644 --- a/scripts/main.ts +++ b/scripts/main.ts @@ -164,7 +164,7 @@ app.whenReady().then(async () => { windowismaximized: () => ({ maximized: mainWindow?.isMaximized() ?? false, }), - openDevTool: () => { + opendevtool: () => { mainWindow?.webContents.openDevTools(); return { ok: true }; }, diff --git a/src/agents/scriptAgent/index copy.ts b/src/agents/scriptAgent/index copy.ts new file mode 100644 index 0000000..6a5e471 --- /dev/null +++ b/src/agents/scriptAgent/index copy.ts @@ -0,0 +1,175 @@ +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/scriptAgent/tools"; +import ResTool from "@/socket/resTool"; +import * as fs from "fs"; + +export interface AgentContext { + socket: Socket; + isolationKey: string; + text: string; + userMessageTime?: number; + abortSignal?: AbortSignal; + resTool: ResTool; + msg: ReturnType; +} + +function buildMemPrompt(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")}`; + } + return `## Memory\n以下是你对用户的记忆,可作为参考但不要主动提及:\n${memoryContext}`; +} + +const subAgentList = ["executionAI", "supervisionAI"] as const; + +export async function decisionAI(ctx: AgentContext) { + const { isolationKey, text, userMessageTime, abortSignal, resTool } = ctx; + + const memory = new Memory("scriptAgent", isolationKey); + await memory.add("user", text, { createTime: userMessageTime }); + + const { skillPaths } = await useSkill({ mainSkill: "script_agent_decision" }); + const prompt = await fs.promises.readFile(skillPaths.mainSkill, "utf-8"); + + const mem = buildMemPrompt(await memory.get(text)); + + const projectData = await u.db("o_project").where("id", resTool.data.projectId).first(); + const novelData = await u.db("o_novel").where("projectId", resTool.data.projectId).select("id", "chapterIndex as index"); + + const projectInfo = [ + "## 项目信息", + `小说名称:${projectData?.name ?? "未知"}`, + `小说类型:${projectData?.type ?? "未知"}`, + `小说简介:${projectData?.intro ?? "无"}`, + `目标改编影视视觉手册|画风:${projectData?.artStyle ?? "无"}`, + `目标改编视频画幅:${projectData?.videoRatio ?? "16:9"}`, + ].join("\n"); + + const projectPrompt = `${projectInfo}\n\n## 章节ID映射表\n${novelData.map((i: any) => `- 章节ID:${i.id}: 第${i.index}章`).join("\n")}\n\n`; + + const { textStream } = await u.Ai.Text("scriptAgent").stream({ + messages: [ + { role: "system", content: prompt }, + { role: "system", content: projectPrompt + mem }, + { role: "user", content: text }, + ], + abortSignal, + tools: { + ...memory.getTools(), + run_sub_agent: runSubAgent(ctx), + ...useTools({ resTool: ctx.resTool, msg: ctx.msg }), + }, + onFinish: async (completion) => { + await memory.add("assistant:decision", completion.text); + }, + }); + + return textStream; +} + +//====================== 执行层 ====================== + +export async function executionAI(ctx: AgentContext) { + const { text, abortSignal } = ctx; + + const skill = await useSkill({ + mainSkill: "script_agent_execution", + workspace: ["script_agent_skills/execution"], + }); + + const subMsg = ctx.resTool.newMessage("assistant", "编剧"); + + const prefixSystem = ` +你可以使用如下XML格式写入工作区: +故事骨架内容 +改编策略内容 +`; + + const { textStream } = await u.Ai.Text("scriptAgent").stream({ + system: prefixSystem + skill.prompt, + messages: [{ role: "user", content: text }], + abortSignal, + tools: { + ...skill.tools, + ...useTools({ resTool: ctx.resTool, msg: subMsg }), + }, + }); + + return { textStream, subMsg }; +} + +export async function supervisionAI(ctx: AgentContext) { + const { text, abortSignal } = ctx; + + const skill = await useSkill({ mainSkill: "script_agent_supervision", workspace: ["script_agent_skills/supervision"] }); + + const subMsg = ctx.resTool.newMessage("assistant", "编辑"); + + const { textStream } = await u.Ai.Text("scriptAgent").stream({ + system: skill.prompt, + messages: [{ role: "user", content: text }], + abortSignal, + tools: { + ...skill.tools, + ...useTools({ + resTool: ctx.resTool, + msg: subMsg, + }), + }, + }); + + return { textStream, subMsg }; +} + +//工具函数 +function runSubAgent(parentCtx: AgentContext) { + const memory = new Memory("scriptAgent", parentCtx.isolationKey); + return tool({ + description: "启动子Agent执行独立任务。可用子Agent:executionAI, decisionAI, supervisionAI", + inputSchema: z.object({ + agent: z.enum(["executionAI", "supervisionAI"]).describe("子Agent名称"), + prompt: z.string().describe("交给子Agent的任务简约描述,100字以内"), + }), + execute: async ({ agent, prompt }) => { + const fn = [executionAI, supervisionAI][subAgentList.indexOf(agent)]; + + // 先完成主Agent当前的消息 + parentCtx.msg.complete(); + // 子Agent用新消息回复 + const { textStream: subTextStream, subMsg } = await fn({ ...parentCtx, text: prompt }); + let text = subMsg.text(); + let fullResponse = ""; + for await (const chunk of subTextStream) { + text.append(chunk); + fullResponse += chunk; + } + text.complete(); + subMsg.complete(); + if (fullResponse.trim()) { + await memory.add(`assistant:${agent === "executionAI" ? "execution" : "supervision"}`, fullResponse, { + name: agent === "executionAI" ? "编剧" : "编辑", + createTime: new Date(subMsg.datetime).getTime(), + }); + } + + // 为主Agent后续输出创建新消息 + parentCtx.msg = parentCtx.resTool.newMessage("assistant", "统筹"); + + return fullResponse; + }, + }); +} diff --git a/src/agents/scriptAgent/index.ts b/src/agents/scriptAgent/index.ts index 6a5e471..0f61b92 100644 --- a/src/agents/scriptAgent/index.ts +++ b/src/agents/scriptAgent/index.ts @@ -34,8 +34,6 @@ function buildMemPrompt(mem: Awaited>): string { return `## Memory\n以下是你对用户的记忆,可作为参考但不要主动提及:\n${memoryContext}`; } -const subAgentList = ["executionAI", "supervisionAI"] as const; - export async function decisionAI(ctx: AgentContext) { const { isolationKey, text, userMessageTime, abortSignal, resTool } = ctx; @@ -70,8 +68,8 @@ export async function decisionAI(ctx: AgentContext) { abortSignal, tools: { ...memory.getTools(), - run_sub_agent: runSubAgent(ctx), ...useTools({ resTool: ctx.resTool, msg: ctx.msg }), + ...createSubAgent(ctx), }, onFinish: async (completion) => { await memory.add("assistant:decision", completion.text); @@ -83,93 +81,105 @@ export async function decisionAI(ctx: AgentContext) { //====================== 执行层 ====================== -export async function executionAI(ctx: AgentContext) { - const { text, abortSignal } = ctx; +function createSubAgent(parentCtx: AgentContext) { + const { resTool, abortSignal } = parentCtx; - const skill = await useSkill({ - mainSkill: "script_agent_execution", - workspace: ["script_agent_skills/execution"], - }); - - const subMsg = ctx.resTool.newMessage("assistant", "编剧"); - - const prefixSystem = ` -你可以使用如下XML格式写入工作区: -故事骨架内容 -改编策略内容 -`; - - const { textStream } = await u.Ai.Text("scriptAgent").stream({ - system: prefixSystem + skill.prompt, - messages: [{ role: "user", content: text }], - abortSignal, - tools: { - ...skill.tools, - ...useTools({ resTool: ctx.resTool, msg: subMsg }), - }, - }); - - return { textStream, subMsg }; -} - -export async function supervisionAI(ctx: AgentContext) { - const { text, abortSignal } = ctx; - - const skill = await useSkill({ mainSkill: "script_agent_supervision", workspace: ["script_agent_skills/supervision"] }); - - const subMsg = ctx.resTool.newMessage("assistant", "编辑"); - - const { textStream } = await u.Ai.Text("scriptAgent").stream({ - system: skill.prompt, - messages: [{ role: "user", content: text }], - abortSignal, - tools: { - ...skill.tools, - ...useTools({ - resTool: ctx.resTool, - msg: subMsg, - }), - }, - }); - - return { textStream, subMsg }; -} - -//工具函数 -function runSubAgent(parentCtx: AgentContext) { const memory = new Memory("scriptAgent", parentCtx.isolationKey); - return tool({ - description: "启动子Agent执行独立任务。可用子Agent:executionAI, decisionAI, supervisionAI", + + const run_execution_agent = tool({ + description: "运行执行层subAgent执行独立任务,完成后返回结果", inputSchema: z.object({ - agent: z.enum(["executionAI", "supervisionAI"]).describe("子Agent名称"), + taskType: z.enum(["故事骨架", "改变策略", "剧本"]).describe("任务类型"), prompt: z.string().describe("交给子Agent的任务简约描述,100字以内"), }), - execute: async ({ agent, prompt }) => { - const fn = [executionAI, supervisionAI][subAgentList.indexOf(agent)]; - + execute: async ({ taskType, prompt }) => { + const skill = await useSkill({ mainSkill: "script_agent_execution", workspace: ["script_agent_skills/execution"] }); // 先完成主Agent当前的消息 parentCtx.msg.complete(); + const subMsg = resTool.newMessage("assistant", "编剧"); + const prefixSystem = + "你可以使用如下XML格式写入工作区:\n故事骨架内容\n改编策略内容"; // 子Agent用新消息回复 - const { textStream: subTextStream, subMsg } = await fn({ ...parentCtx, text: prompt }); + const { textStream } = await u.Ai.Text("scriptAgent").stream({ + system: prefixSystem + skill.prompt, + messages: [{ role: "user", content: `请完成${taskType}任务` }], + abortSignal, + tools: { + ...skill.tools, + ...useTools({ resTool, msg: subMsg }), + get_task_details: tool({ + description: "获取主Agent传入的任务目标详情", + inputSchema: z.object({}), + execute: async () => { + const thinking = subMsg.thinking("以获取任务详情"); + thinking.appendText("任务详情:\n" + prompt); + thinking.complete(); + return prompt ?? "运行失败"; + }, + }), + }, + }); + let text = subMsg.text(); let fullResponse = ""; - for await (const chunk of subTextStream) { + for await (const chunk of textStream) { text.append(chunk); fullResponse += chunk; } text.complete(); subMsg.complete(); if (fullResponse.trim()) { - await memory.add(`assistant:${agent === "executionAI" ? "execution" : "supervision"}`, fullResponse, { - name: agent === "executionAI" ? "编剧" : "编辑", - createTime: new Date(subMsg.datetime).getTime(), - }); + await memory.add(`assistant:execution`, fullResponse, { name: "编剧", createTime: new Date(subMsg.datetime).getTime() }); } - // 为主Agent后续输出创建新消息 parentCtx.msg = parentCtx.resTool.newMessage("assistant", "统筹"); - return fullResponse; }, }); + + const run_supervision_agent = tool({ + description: "运行监督层subAgent执行独立任务,完成后返回结果", + inputSchema: z.object({ + prompt: z.string().describe("交给子Agent的任务简约描述,100字以内"), + }), + execute: async ({ prompt }) => { + const skill = await useSkill({ mainSkill: "script_agent_supervision", workspace: ["script_agent_skills/supervision"] }); + + // 先完成主Agent当前的消息 + parentCtx.msg.complete(); + // 子Agent用新消息回复 + + const subMsg = resTool.newMessage("assistant", "编辑"); + + const { textStream } = await u.Ai.Text("scriptAgent").stream({ + system: skill.prompt, + messages: [{ role: "user", content: prompt }], + abortSignal, + tools: { + ...skill.tools, + ...useTools({ resTool, msg: subMsg }), + }, + }); + + let text = subMsg.text(); + let fullResponse = ""; + for await (const chunk of textStream) { + text.append(chunk); + fullResponse += chunk; + } + text.complete(); + subMsg.complete(); + if (fullResponse.trim()) { + await memory.add(`assistant:supervision`, fullResponse, { name: "编辑", createTime: new Date(subMsg.datetime).getTime() }); + } + // 为主Agent后续输出创建新消息 + parentCtx.msg = parentCtx.resTool.newMessage("assistant", "统筹"); + return fullResponse; + }, + }); + + return { + run_execution_agent, + run_supervision_agent, + }; } diff --git a/src/agents/scriptAgent/tools.ts b/src/agents/scriptAgent/tools.ts index f313cc9..4385851 100644 --- a/src/agents/scriptAgent/tools.ts +++ b/src/agents/scriptAgent/tools.ts @@ -57,7 +57,7 @@ export default (toolCpnfig: ToolConfig) => { thinking.appendText("查询结果:\n" + eventString); thinking.updateTitle("查询章节事件完成"); thinking.complete(); - return eventString; + return eventString ?? "无数据"; }, }), get_planData: tool({ @@ -72,7 +72,7 @@ export default (toolCpnfig: ToolConfig) => { thinking.appendText(`获取到${planDataKeyLabels[key]}:\n` + planData[key]); thinking.updateTitle(`获取${planDataKeyLabels[key]}完成`); thinking.complete(); - return planData[key]; + return planData[key] ?? "无数据"; }, }), get_novel_text: tool({ @@ -88,7 +88,7 @@ export default (toolCpnfig: ToolConfig) => { thinking.appendText(`获取到原文:\n` + text); thinking.updateTitle(`获取小说章节原文完成`); thinking.complete(); - return data && data?.chapterData ? data.chapterData : text; + return text ?? "无数据"; }, }), //====================== diff --git a/src/router.ts b/src/router.ts index b0a81c0..c07465b 100644 --- a/src/router.ts +++ b/src/router.ts @@ -1,4 +1,4 @@ -// @routes-hash f7c91709281f1726b4945e014f09e8fb +// @routes-hash e584b1af18da2fc25158fd68183fa645 import { Express } from "express"; import route1 from "./routes/agents/clearMemory"; @@ -94,26 +94,20 @@ import route90 from "./routes/setting/memoryConfig/getMemory"; import route91 from "./routes/setting/memoryConfig/sureMemory"; import route92 from "./routes/setting/promptManage/getPrompt"; import route93 from "./routes/setting/promptManage/updatePrompt"; -import route94 from "./routes/setting/skillManagement/backup/addSkill"; -import route95 from "./routes/setting/skillManagement/backup/deleteSkill"; -import route96 from "./routes/setting/skillManagement/backup/embeddingSkill"; -import route97 from "./routes/setting/skillManagement/backup/generateDescription"; -import route98 from "./routes/setting/skillManagement/backup/getSkillList"; -import route99 from "./routes/setting/skillManagement/backup/scanSkills"; -import route100 from "./routes/setting/skillManagement/backup/updateSkill"; -import route101 from "./routes/setting/skillManagement/getSkillContent"; -import route102 from "./routes/setting/skillManagement/getSkillList"; -import route103 from "./routes/setting/vendorConfig/addVendor"; -import route104 from "./routes/setting/vendorConfig/deleteVendor"; -import route105 from "./routes/setting/vendorConfig/getVendorList"; -import route106 from "./routes/setting/vendorConfig/modelTest"; -import route107 from "./routes/setting/vendorConfig/updateCode"; -import route108 from "./routes/setting/vendorConfig/updateVendor"; -import route109 from "./routes/task/getProject"; -import route110 from "./routes/task/getTaskApi"; -import route111 from "./routes/task/getTaskCategories"; -import route112 from "./routes/task/taskDetails"; -import route113 from "./routes/test/test"; +import route94 from "./routes/setting/skillManagement/getSkillContent"; +import route95 from "./routes/setting/skillManagement/getSkillList"; +import route96 from "./routes/setting/skillManagement/saveSkillContent"; +import route97 from "./routes/setting/vendorConfig/addVendor"; +import route98 from "./routes/setting/vendorConfig/deleteVendor"; +import route99 from "./routes/setting/vendorConfig/getVendorList"; +import route100 from "./routes/setting/vendorConfig/modelTest"; +import route101 from "./routes/setting/vendorConfig/updateCode"; +import route102 from "./routes/setting/vendorConfig/updateVendor"; +import route103 from "./routes/task/getProject"; +import route104 from "./routes/task/getTaskApi"; +import route105 from "./routes/task/getTaskCategories"; +import route106 from "./routes/task/taskDetails"; +import route107 from "./routes/test/test"; export default async (app: Express) => { app.use("/api/agents/clearMemory", route1); @@ -209,24 +203,18 @@ export default async (app: Express) => { app.use("/api/setting/memoryConfig/sureMemory", route91); app.use("/api/setting/promptManage/getPrompt", route92); app.use("/api/setting/promptManage/updatePrompt", route93); - app.use("/api/setting/skillManagement/backup/addSkill", route94); - app.use("/api/setting/skillManagement/backup/deleteSkill", route95); - app.use("/api/setting/skillManagement/backup/embeddingSkill", route96); - app.use("/api/setting/skillManagement/backup/generateDescription", route97); - app.use("/api/setting/skillManagement/backup/getSkillList", route98); - app.use("/api/setting/skillManagement/backup/scanSkills", route99); - app.use("/api/setting/skillManagement/backup/updateSkill", route100); - app.use("/api/setting/skillManagement/getSkillContent", route101); - app.use("/api/setting/skillManagement/getSkillList", route102); - app.use("/api/setting/vendorConfig/addVendor", route103); - app.use("/api/setting/vendorConfig/deleteVendor", route104); - app.use("/api/setting/vendorConfig/getVendorList", route105); - app.use("/api/setting/vendorConfig/modelTest", route106); - app.use("/api/setting/vendorConfig/updateCode", route107); - app.use("/api/setting/vendorConfig/updateVendor", route108); - app.use("/api/task/getProject", route109); - app.use("/api/task/getTaskApi", route110); - app.use("/api/task/getTaskCategories", route111); - app.use("/api/task/taskDetails", route112); - app.use("/api/test/test", route113); + app.use("/api/setting/skillManagement/getSkillContent", route94); + app.use("/api/setting/skillManagement/getSkillList", route95); + app.use("/api/setting/skillManagement/saveSkillContent", route96); + app.use("/api/setting/vendorConfig/addVendor", route97); + app.use("/api/setting/vendorConfig/deleteVendor", route98); + app.use("/api/setting/vendorConfig/getVendorList", route99); + app.use("/api/setting/vendorConfig/modelTest", route100); + app.use("/api/setting/vendorConfig/updateCode", route101); + app.use("/api/setting/vendorConfig/updateVendor", route102); + app.use("/api/task/getProject", route103); + app.use("/api/task/getTaskApi", route104); + app.use("/api/task/getTaskCategories", route105); + app.use("/api/task/taskDetails", route106); + app.use("/api/test/test", route107); } diff --git a/src/routes/setting/skillManagement/backup/addSkill.ts b/src/routes/setting/skillManagement/backup/addSkill.ts deleted file mode 100644 index 05802ae..0000000 --- a/src/routes/setting/skillManagement/backup/addSkill.ts +++ /dev/null @@ -1,102 +0,0 @@ -import express from "express"; -import u from "@/utils"; -import { z } from "zod"; -import { success, error } from "@/lib/responseFormat"; -import { validateFields } from "@/middleware/middleware"; -import fs from "fs/promises"; -import path from "path"; -import crypto from "crypto"; - -const router = express.Router(); - -const buildSkillFileName = (name: string) => { - const trimmed = name.trim(); - const fileName = trimmed.endsWith(".md") ? trimmed : `${trimmed}.md`; - const normalized = fileName.replace(/\\/g, "/"); - if (!normalized || normalized.includes("/")) { - throw new Error("技能名称不能包含路径分隔符"); - } - return normalized; -}; - -const buildRelativePath = (type: "main" | "references", fileName: string) => { - return type === "references" ? path.posix.join("references", fileName) : fileName; -}; - -const resolveSkillFilePath = (relativePath: string) => { - const normalizedPath = relativePath.replace(/\\/g, "/"); - if (normalizedPath.startsWith("references/")) { - return path.join(u.getPath("skills"), normalizedPath); - } - return path.join(u.getPath("skills"), normalizedPath); -}; - -const resolveState = (description: string, attributions: string[]) => { - if (!description.trim()) return -1; - if (attributions.length === 0) return -2; - return 1; -}; - -export default router.post( - "/", - validateFields({ - name: z.string().min(1).max(100), - description: z.string().optional(), - content: z.string().optional(), - attributions: z.array(z.string()).optional(), - type: z.enum(["main", "references"]).optional(), - }), - async (req, res) => { - try { - const { name, description, content, attributions, type } = req.body; - const finalType: "main" | "references" = type === "main" ? "main" : "references"; - const finalDescription = description ?? ""; - const finalContent = content ?? ""; - const rawAttributions = Array.isArray(attributions) ? attributions : []; - const finalAttributions = Array.from( - new Set(rawAttributions.filter((item: unknown): item is string => typeof item === "string" && item.trim().length > 0)), - ); - const fileName = buildSkillFileName(name); - const relativePath = buildRelativePath(finalType, fileName); - const skillId = crypto.createHash("md5").update(relativePath).digest("hex"); - const md5 = crypto.createHash("md5").update(finalContent).digest("hex"); - const filePath = resolveSkillFilePath(relativePath); - const now = Date.now(); - - const existed = await u.db("o_skillList").where("id", skillId).first(); - if (existed) { - return res.status(400).send(error("技能已存在,请使用其他名称")); - } - - await fs.mkdir(path.dirname(filePath), { recursive: true }); - await fs.writeFile(filePath, finalContent, "utf-8"); - - await u.db("o_skillList").insert({ - id: skillId, - md5, - path: relativePath, - name: path.basename(fileName, ".md"), - description: finalDescription, - embedding: null, - type: finalType, - createTime: now, - updateTime: now, - state: resolveState(finalDescription, finalAttributions), - }); - - if (finalAttributions.length > 0) { - await u.db("o_skillAttribution").insert( - finalAttributions.map((attribution: string) => ({ - skillId, - attribution, - })), - ); - } - - res.status(200).send(success("新增技能成功")); - } catch (err: any) { - console.log(err); - res.status(400).send(error(err?.message || "新增技能失败")); - } - }, -); diff --git a/src/routes/setting/skillManagement/backup/deleteSkill.ts b/src/routes/setting/skillManagement/backup/deleteSkill.ts deleted file mode 100644 index e798ff7..0000000 --- a/src/routes/setting/skillManagement/backup/deleteSkill.ts +++ /dev/null @@ -1,49 +0,0 @@ -import express from "express"; -import u from "@/utils"; -import { z } from "zod"; -import { success, error } from "@/lib/responseFormat"; -import { validateFields } from "@/middleware/middleware"; -import fs from "fs/promises"; -import path from "path"; - -const router = express.Router(); - -const resolveSkillFilePath = (type: string, relativePath: string) => { - const normalizedPath = (relativePath || "").replace(/\\/g, "/"); - const isPrefixedReferencePath = normalizedPath.startsWith("references/"); - if (type === "references" && !isPrefixedReferencePath) { - return path.join(u.getPath(["skills", "references"]), normalizedPath); - } - return path.join(u.getPath("skills"), normalizedPath); -}; - -export default router.post( - "/", - validateFields({ - id: z.string().min(1), - }), - async (req, res) => { - try { - const { id } = req.body; - const skill = await u.db("o_skillList").where("id", id).first(); - - if (!skill) { - return res.status(404).send(error("技能不存在")); - } - - const filePath = resolveSkillFilePath(skill.type, skill.path || ""); - await u.db("o_skillList").where("id", id).delete(); - - try { - await fs.unlink(filePath); - } catch { - // 文件不存在时可忽略,数据库记录已删除 - } - - res.status(200).send(success("删除技能成功")); - } catch (err: any) { - console.log(err); - res.status(400).send(error(err?.message || "删除技能失败")); - } - }, -); diff --git a/src/routes/setting/skillManagement/backup/embeddingSkill.ts b/src/routes/setting/skillManagement/backup/embeddingSkill.ts deleted file mode 100644 index 7c1e88c..0000000 --- a/src/routes/setting/skillManagement/backup/embeddingSkill.ts +++ /dev/null @@ -1,31 +0,0 @@ -import express from "express"; -import u from "@/utils"; -import { z } from "zod"; -import { success, error } from "@/lib/responseFormat"; -import { validateFields } from "@/middleware/middleware"; -import { getEmbedding } from "@/utils/agent/embedding"; - -const router = express.Router(); - -export default router.post( - "/", - validateFields({ - id: z.string(), - }), - async (req, res) => { - const { id } = req.body; - - const skill = await u.db("o_skillList").where("id", id).first(); - - if (!skill) return res.status(404).send(error("技能不存在")); - if (skill.embedding) return res.status(400).send(error("技能已存在向量,请勿重复生成")); - if (!skill.description) return res.status(400).send(error("技能描述不存在")); - const embedding = await getEmbedding(skill.description); - await u - .db("o_skillList") - .where("id", id) - .update({ embedding: JSON.stringify(embedding) }); - - res.status(200).send(success("技能向量生成成功")); - }, -); diff --git a/src/routes/setting/skillManagement/backup/generateDescription.ts b/src/routes/setting/skillManagement/backup/generateDescription.ts deleted file mode 100644 index 7003e29..0000000 --- a/src/routes/setting/skillManagement/backup/generateDescription.ts +++ /dev/null @@ -1,35 +0,0 @@ -import express from "express"; -import u from "@/utils"; -import { z } from "zod"; -import { success, error } from "@/lib/responseFormat"; -import { validateFields } from "@/middleware/middleware"; -import fs from "fs/promises"; -import path from "path"; - -const router = express.Router(); - -const resolveSkillFilePath = (type: string, relativePath: string) => { - const normalizedPath = (relativePath || "").replace(/\\/g, "/"); - const isPrefixedReferencePath = normalizedPath.startsWith("references/"); - if (type === "references" && !isPrefixedReferencePath) { - return path.join(u.getPath(["skills", "references"]), normalizedPath); - } - return path.join(u.getPath("skills"), normalizedPath); -}; - -export default router.post( - "/", - validateFields({ - content: z.string(), - }), - async (req, res) => { - const { content } = req.body; - const result = await u.Ai.Text("universalAi").invoke({ - system: - "你是一个文档摘要助手。根据给定的文档内容生成一句简洁的中文描述(不超过100字),概括文档的核心主题和用途。只输出描述文本,不要添加任何前缀或格式。", - messages: [{ role: "user", content: `内容:\n${content}` }], - }); - const description = result.text.trim(); - res.status(200).send(success(description)); - }, -); diff --git a/src/routes/setting/skillManagement/backup/getSkillList.ts b/src/routes/setting/skillManagement/backup/getSkillList.ts deleted file mode 100644 index 168e60f..0000000 --- a/src/routes/setting/skillManagement/backup/getSkillList.ts +++ /dev/null @@ -1,138 +0,0 @@ -import express from "express"; -import u from "@/utils"; -import { z } from "zod"; -import { success, error } from "@/lib/responseFormat"; -import { validateFields } from "@/middleware/middleware"; -import fs from "fs"; -import path from "path"; - -const router = express.Router(); - -export default router.post( - "/", - validateFields({ - page: z.number().int().min(1).default(1), - limit: z.number().int().min(1).max(100).default(20), - search: z.string().optional().default(""), - type: z.enum(["main", "references"]).optional(), - attributions: z.array(z.string()).optional(), - }), - async (req, res) => { - const { page, limit, search, type, attributions } = req.body; - const offset = (page - 1) * limit; - - let query = u.db("o_skillList"); - let countQuery = u.db("o_skillList"); - - // 搜索条件 - if (search) { - const searchPattern = `%${search}%`; - const whereBuilder = (builder: any) => { - builder - .where("name", "like", searchPattern) - .orWhere("path", "like", searchPattern) - .orWhere("description", "like", searchPattern); - }; - query = query.where(whereBuilder); - countQuery = countQuery.where(whereBuilder); - } - - // type 筛选条件 - if (type) { - query = query.where("type", type); - countQuery = countQuery.where("type", type); - } - - // attributions 筛选条件 - if (attributions && attributions.length > 0) { - const attributionSubQuery = function (this: any) { - this.select("skillId") - .from("o_skillAttribution") - .whereIn("attribution", attributions); - }; - query = query.whereIn("id", attributionSubQuery); - countQuery = countQuery.whereIn("id", attributionSubQuery); - } - - // 查询总数(在所有筛选条件应用后) - const [{ count }]: any = await countQuery.count("* as count"); - - // 查询列表 - const list = await query - .select("*") - .orderByRaw( - ` - CASE type WHEN 'main' THEN 1 ELSE 0 END ASC, - CASE WHEN id NOT IN (SELECT skillId FROM o_skillAttribution) THEN 0 ELSE 1 END ASC, - CASE WHEN state = 1 THEN 1 ELSE 0 END ASC, - updateTime DESC - ` - ) - .limit(limit) - .offset(offset); - - // 查询每个技能的归属 - const skillIds = list.map((item: any) => item.id); - const attributionsList = await u - .db("o_skillAttribution") - .whereIn("skillId", skillIds) - .select("skillId", "attribution"); - - // 将归属信息合并到列表中 - const attributionMap = new Map(); - for (const attr of attributionsList) { - if (!attributionMap.has(attr.skillId!)) { - attributionMap.set(attr.skillId!, []); - } - attributionMap.get(attr.skillId!)!.push(attr.attribution!); - } - - // 记录需要更新state的技能id - const missingFileIds: string[] = []; - - const listWithAttributions = list.map((item: any) => { - const normalizedPath = (item.path || "").replace(/\\/g, "/"); - const isPrefixedReferencePath = normalizedPath.startsWith("references/"); - const skillFilePath = - item.type === "references" && !isPrefixedReferencePath - ? path.join(u.getPath(["skills", "references"]), item.path!) - : path.join(u.getPath("skills"), item.path!); - - let content = ""; - let state = item.state; - - // 检查文件是否存在 - if (fs.existsSync(skillFilePath)) { - content = fs.readFileSync(skillFilePath, "utf-8"); - } else { - state = -1; - if (item.state !== -1) { - missingFileIds.push(item.id); - } - } - - return { - ...item, - state, - attributions: attributionMap.get(item.id) || [], - content, - embedding: item.embedding ? true : false, - }; - }); - - // 批量更新文件不存在的技能状态 - if (missingFileIds.length > 0) { - await u - .db("o_skillList") - .whereIn("id", missingFileIds) - .update({ state: -1 }); - } - - res.status(200).send( - success({ - list: listWithAttributions, - total: Number(count), - }) - ); - } -); \ No newline at end of file diff --git a/src/routes/setting/skillManagement/backup/scanSkills.ts b/src/routes/setting/skillManagement/backup/scanSkills.ts deleted file mode 100644 index 32750c0..0000000 --- a/src/routes/setting/skillManagement/backup/scanSkills.ts +++ /dev/null @@ -1,115 +0,0 @@ -import express from "express"; -import u from "@/utils"; -import path from "path"; -import fs from "fs/promises"; -import crypto from "crypto"; -import { success } from "@/lib/responseFormat"; -import fg from "fast-glob"; -import getPath from "@/utils/getPath"; - -const router = express.Router(); - -export default router.post("/", async (req, res) => { - const skillsRoot = getPath(["skills"]); - const referencesRoot = path.join(skillsRoot, "references"); - - const [mainEntries, referenceEntries] = await Promise.all([ - fg("*.md", { - cwd: skillsRoot.replace(/\\/g, "/"), - onlyFiles: true, - }), - fg("**/*.md", { - cwd: referencesRoot.replace(/\\/g, "/"), - onlyFiles: true, - }), - ]); - - const scanItems = [ - ...mainEntries.map((entry) => ({ - entry, - relativePath: entry, - fullPath: path.join(skillsRoot, entry), - type: "main", - })), - ...referenceEntries.map((entry) => ({ - entry, - relativePath: path.posix.join("references", entry.replace(/\\/g, "/")), - fullPath: path.join(referencesRoot, entry), - type: "references", - })), - ]; - - const now = Date.now(); - let insertedCount = 0; - let updatedCount = 0; - let removedCount = 0; - - const scannedPaths = new Set(); - const existingRows = await u.db("o_skillList").whereIn("type", ["main", "references"]).select("id", "md5", "type", "path"); - - for (const item of scanItems) { - scannedPaths.add(item.relativePath); - - const existing = existingRows.find((row: any) => row.path === item.relativePath); - const content = await fs.readFile(item.fullPath, "utf-8"); - const md5 = crypto.createHash("md5").update(content).digest("hex"); - - if (!existing) { - const id = crypto.createHash("md5").update(item.relativePath).digest("hex"); - const name = path.basename(item.entry, ".md"); - await u.db("o_skillList").insert({ - id, - path: item.relativePath, - name, - description: "", - embedding: null, - type: item.type, - createTime: now, - updateTime: now, - md5, - state: -1, - }); - insertedCount++; - } else { - const updateData: Record = { md5, updateTime: now }; - if (existing.md5 !== md5) { - updateData.state = -3; - } - await u.db("o_skillList").where("id", existing.id).update(updateData); - updatedCount++; - } - } - - const removedIds = existingRows.filter((row: any) => !scannedPaths.has(row.path)).map((row: any) => row.id); - if (removedIds.length > 0) { - await u.db("o_skillList").whereIn("id", removedIds).update({ state: -4, updateTime: now }); - removedCount = removedIds.length; - } - - const [{ noDescriptionSkillCount }]: any = await u - .db("o_skillList") - .where("type", "references") - .andWhere((builder: any) => { - builder.whereNull("description").orWhere("description", ""); - }) - .count({ noDescriptionSkillCount: "*" }); - - const [{ noAttributionSkillCount }]: any = await u - .db("o_skillList as sl") - .leftJoin("o_skillAttribution as sa", "sl.id", "sa.skillId") - .where("sl.type", "references") - .whereNull("sa.skillId") - .countDistinct({ noAttributionSkillCount: "sl.id" }); - - res.status(200).send( - success({ - message: "更新技能文档成功", - insertedCount, - updatedCount, - removedCount, - totalFiles: scanItems.length, - noDescriptionSkillCount: Number(noDescriptionSkillCount), - noAttributionSkillCount: Number(noAttributionSkillCount), - }), - ); -}); diff --git a/src/routes/setting/skillManagement/backup/updateSkill.ts b/src/routes/setting/skillManagement/backup/updateSkill.ts deleted file mode 100644 index f47e855..0000000 --- a/src/routes/setting/skillManagement/backup/updateSkill.ts +++ /dev/null @@ -1,127 +0,0 @@ -import express from "express"; -import u from "@/utils"; -import { z } from "zod"; -import { success, error } from "@/lib/responseFormat"; -import { validateFields } from "@/middleware/middleware"; -import fs from "fs/promises"; -import path from "path"; -import crypto from "crypto"; -import { getEmbedding } from "@/utils/agent/embedding"; - -const router = express.Router(); - -const buildSkillFileName = (name: string) => { - const trimmed = name.trim(); - const fileName = trimmed.endsWith(".md") ? trimmed : `${trimmed}.md`; - const normalized = fileName.replace(/\\/g, "/"); - if (!normalized || normalized.includes("/")) { - throw new Error("技能名称不能包含路径分隔符"); - } - return normalized; -}; - -const buildRelativePath = (type: string, fileName: string) => { - return type === "references" ? path.posix.join("references", fileName) : fileName; -}; - -const resolveSkillFilePath = (relativePath: string) => { - return path.join(u.getPath("skills"), relativePath.replace(/\\/g, "/")); -}; - -const resolveState = (description: string, attributions: string[]) => { - if (!description.trim()) return -1; - if (attributions.length === 0) return -2; - return 1; -}; - -export default router.post( - "/", - validateFields({ - id: z.string().min(1), - name: z.string().min(1).max(100), - description: z.string().optional(), - content: z.string().optional(), - attributions: z.array(z.string()).optional(), - }), - async (req, res) => { - try { - const { id, name, description, content, attributions } = req.body; - const current = await u.db("o_skillList").where("id", id).first(); - - if (!current) { - return res.status(404).send(error("技能不存在")); - } - - const finalDescription = description ?? ""; - const finalContent = content ?? ""; - const rawAttributions = Array.isArray(attributions) ? attributions : []; - const finalAttributions = Array.from( - new Set(rawAttributions.filter((item: unknown): item is string => typeof item === "string" && item.trim().length > 0)), - ); - const fileName = buildSkillFileName(name); - const relativePath = buildRelativePath(current.type, fileName); - const nextId = crypto.createHash("md5").update(relativePath).digest("hex"); - const md5 = crypto.createHash("md5").update(finalContent).digest("hex"); - const oldFilePath = resolveSkillFilePath(current.path); - const newFilePath = resolveSkillFilePath(relativePath); - const now = Date.now(); - - if (nextId !== id) { - const conflict = await u.db("o_skillList").where("id", nextId).first(); - if (conflict) { - return res.status(400).send(error("技能名称冲突,请使用其他名称")); - } - } - - await fs.mkdir(path.dirname(newFilePath), { recursive: true }); - if (oldFilePath !== newFilePath) { - try { - await fs.rename(oldFilePath, newFilePath); - } catch { - // 文件不存在时直接按新路径写入即可 - } - } - await fs.writeFile(newFilePath, finalContent, "utf-8"); - - if (nextId !== id) { - await u.db("o_skillAttribution").where("skillId", id).update({ skillId: nextId }); - } - - await u - .db("o_skillList") - .where("id", id) - .update({ - id: nextId, - path: relativePath, - name: path.basename(fileName, ".md"), - description: finalDescription, - md5, - updateTime: now, - state: resolveState(finalDescription, finalAttributions), - }); - - if (finalDescription && !current.embedding) { - const embedding = await getEmbedding(finalDescription); - await u - .db("o_skillList") - .where("id", nextId) - .update({ embedding: JSON.stringify(embedding) }); - } - - await u.db("o_skillAttribution").where("skillId", nextId).delete(); - if (finalAttributions.length > 0) { - await u.db("o_skillAttribution").insert( - finalAttributions.map((attribution: string) => ({ - skillId: nextId, - attribution, - })), - ); - } - - res.status(200).send(success("更新技能成功")); - } catch (err: any) { - console.log(err); - res.status(400).send(error(err?.message || "更新技能失败")); - } - }, -); diff --git a/src/routes/setting/skillManagement/saveSkillContent.ts b/src/routes/setting/skillManagement/saveSkillContent.ts new file mode 100644 index 0000000..d406447 --- /dev/null +++ b/src/routes/setting/skillManagement/saveSkillContent.ts @@ -0,0 +1,34 @@ +import express from "express"; +import { success, error } from "@/lib/responseFormat"; +import { validateFields } from "@/middleware/middleware"; +import { z } from "zod"; +import isPathInside from "is-path-inside"; +import u from "@/utils"; +import p from "path"; +import * as fs from "fs"; + +const router = express.Router(); + +export default router.post( + "/", + validateFields({ + path: z.string(), + content: z.string(), + }), + async (req, res) => { + const { path, content } = req.body; + const skillsRoot = u.getPath(["skills"]); + const filePath = p.join(skillsRoot, path); + if (!isPathInside(filePath, skillsRoot)) { + return res.status(400).send(error("无效的路径")); + } + + if (!fs.existsSync(filePath)) { + return res.status(400).send(error("文件不存在")); + } + + const raw = await fs.promises.writeFile(filePath, content, "utf-8"); + + res.status(200).send(success(raw)); + }, +); diff --git a/src/socket/resTool copy.ts b/src/socket/resTool copy.ts deleted file mode 100644 index fbac487..0000000 --- a/src/socket/resTool copy.ts +++ /dev/null @@ -1,79 +0,0 @@ -import u from "@/utils"; -import { Socket } from "socket.io"; - -class ResTool { - public socket: Socket; - public data: Record; - constructor(socket: Socket, data: Record = {}) { - this.socket = socket; - this.data = data; - } - - 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/types/database.d.ts b/src/types/database.d.ts index 735f17d..2a3bc57 100644 --- a/src/types/database.d.ts +++ b/src/types/database.d.ts @@ -1,19 +1,6 @@ -// @db-hash e24c7c99757472b92af11f26a2b2b8c7 +// @db-hash 93b2462070c45c2b449e9a18c4e88763 //该文件由脚本自动生成,请勿手动修改 -export interface _o_project_old_20260328 { - 'artStyle'?: string | null; - 'createTime'?: number | null; - 'id'?: number | null; - 'imageModel'?: string | null; - 'intro'?: string | null; - 'name'?: string | null; - 'projectType'?: string | null; - 'type'?: string | null; - 'userId'?: number | null; - 'videoModel'?: string | null; - 'videoRatio'?: string | null; -} export interface memories { 'content': string; 'createTime': number; @@ -34,7 +21,7 @@ export interface o_agentDeploy { 'model'?: string | null; 'modelName'?: string | null; 'name'?: string | null; - 'vendorId'?: number | null; + 'vendorId'?: string | null; } export interface o_agentWorkData { 'createTime'?: number | null; @@ -245,7 +232,6 @@ export interface o_videoConfig { } export interface DB { - "_o_project_old_20260328": _o_project_old_20260328; "memories": memories; "o_agentDeploy": o_agentDeploy; "o_agentWorkData": o_agentWorkData; diff --git a/src/utils/agent/skillsTools copy.ts b/src/utils/agent/skillsTools copy.ts deleted file mode 100644 index 6f8fa54..0000000 --- a/src/utils/agent/skillsTools copy.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { tool } from "ai"; -import { z } from "zod"; -import path from "path"; -import fs from "fs/promises"; -import isPathInside from "is-path-inside"; -import u from "@/utils"; -import getPath from "@/utils/getPath"; -import { getEmbedding, cosineSimilarity } from "./embedding"; - -interface SkillRecord { - name: string; - description: string; - location: string; - baseDir: string; -} - -// ==================== 解析 SKILL.md ==================== - -function parseFrontmatter(content: string): { name: string; description: string } { - const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/); - if (!match?.[1]) throw new Error("No frontmatter found"); - - const result: Record = {}; - const lines = match[1].split("\n"); - - for (let i = 0; i < lines.length; ) { - const colonIndex = lines[i].indexOf(":"); - if (colonIndex === -1) { - i++; - continue; - } - - const key = lines[i].slice(0, colonIndex).trim(); - if (!key) { - i++; - continue; - } - - let value = lines[i].slice(colonIndex + 1).trim(); - i++; - - if (/^[>|]-?$/.test(value)) { - const fold = value.startsWith(">"); - const parts: string[] = []; - while (i < lines.length && /^\s+/.test(lines[i])) { - parts.push(lines[i].trim()); - i++; - } - value = fold ? parts.join(" ") : parts.join("\n"); - } - - result[key] = value; - } - - if (!result.name || !result.description) throw new Error("Frontmatter missing required field: name or description"); - return { name: result.name, description: result.description }; -} - -type SkillAttribution = - | "production_agent_decision.md" - | "production_agent_execution.md" - | "production_agent_supervision.md" - | "script_agent_decision.md" - | "script_agent_execution.md" - | "script_agent_supervision.md" - | "universal_agent.md"; - -export async function useSkill(mainSkillName: SkillAttribution) { - const skillsRoot = getPath("skills"); - const targetSkill = path.join(skillsRoot, mainSkillName); - - if (!isPathInside(targetSkill, skillsRoot)) throw new Error("技能名称无效:检测到路径穿越"); - - try { - const content = await fs.readFile(targetSkill, "utf-8"); - const skill = { ...parseFrontmatter(content), location: targetSkill, baseDir: skillsRoot }; - return { prompt: buildPrompt(skill), tools: createSkillTools(skill, mainSkillName) }; - } catch { - throw new Error(`技能文件不存在:${mainSkillName}`); - } -} - -function buildPrompt(skill: SkillRecord): string { - return `## Skills -以下技能提供了专业任务的专用指令。 -当任务与某个技能的描述匹配时,调用 activate_skill 工具并传入技能名称来加载完整指令。 -加载后遵循技能指令执行任务,需要时调用 read_skill_file 读取资源文件内容。 - - - - ${skill.name} - ${skill.description} - -`; -} - -function createSkillTools(skill: SkillRecord, mainSkillName: string) { - const activated = new Set(); - return { - activate_skill: tool({ - description: `激活一个技能,加载其完整指令和捆绑资源列表到上下文。可用技能:${skill.name}`, - inputSchema: z.object({ - name: z.enum([skill.name] as [string, ...string[]]).describe("要激活的技能名称"), - }), - execute: async ({ name }) => { - if (activated.has(name)) { - console.log(`[Skill] ℹ️ 技能 "${name}" 已激活,跳过重复注入`); - return { alreadyActive: true, message: `技能 "${name}" 已激活,无需重复加载` }; - } - let raw: string; - try { - raw = await fs.readFile(skill.location, "utf-8"); - } catch { - console.log(`[Skill] ❌ 激活失败:无法读取 ${skill.location}`); - return { error: `无法读取技能文件:${name}` }; - } - const body = raw.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim(); - - const resources = await u - .db("o_skillList") - .distinct("o_skillList.path") - .innerJoin("o_skillAttribution", "o_skillList.id", "o_skillAttribution.skillId") - .where("o_skillList.state", 1) - .andWhere("o_skillAttribution.attribution", mainSkillName); - - activated.add(name); - console.log(`[Skill] 📖 已激活:${name}(${body.length} 字符,${resources.length} 资源)`); - let content = ""; - content = `\n`; - content += body + "\n\n"; - content += `Skill directory: ${skill.baseDir}\n`; - content += "相对路径基于此技能目录解析,使用 read_skill_file 工具读取资源文件。\n"; - if (resources.length > 0) { - content += "\n\n"; - for (const { path } of resources) { - content += ` ${path}\n`; - } - content += "\n"; - } - content += "\n\n"; - content += "- read_skill_file:读取上方 skill_resources 中列出的资源文件。\n"; - content += "- discover_skill_docs:当上方资源不足以完成任务时,使用关键词检索更多相关文档。传入与当前任务相关的关键词列表即可获取推荐。\n"; - content += "\n"; - content += ""; - return { content }; - }, - }), - discover_skill_docs: tool({ - description: "根据关键词主动发现全部技能文档(MD),返回相关度排序的推荐列表。适用于技能指令中未明确指定资源文件但需要补充信息的场景。", - inputSchema: z.object({ - keywords: z.array(z.string().max(100)).min(1).max(20).describe("用于检索技能文档的关键词列表"), - topK: z.number().int().min(1).max(20).default(5).describe("返回推荐文档数量"), - }), - execute: async ({ keywords, topK }) => { - const queryText = keywords.join(" "); - const queryVec = await getEmbedding(queryText); - - const activeRows = await u.db("o_skillList").where("state", 1).whereNotNull("embedding").select(); - const scored = activeRows - .map((row) => { - const emb = JSON.parse(row.embedding!) as number[]; - return { - name: row.name, - filePath: row.path, - type: row.type, - description: row.description, - score: cosineSimilarity(queryVec, emb), - }; - }) - .sort((a, b) => b.score - a.score) - .slice(0, topK); - - console.log(`[Skill] ✅ discover_skill_docs 返回 ${scored.length} 条推荐`); - return { recommendations: scored }; - }, - }), - read_skill_file: tool({ - description: "读取已激活技能目录下的资源文件。传入 activate_skill 返回的 skill_resources 中的文件路径。", - inputSchema: z.object({ - skillName: z.string().describe("技能名称"), - filePath: z.string().describe("资源文件的相对路径,来自 activate_skill 返回的 skill_resources"), - }), - execute: async ({ skillName, filePath: relPath }) => { - const fullPath = path.resolve(path.join(skill.baseDir, relPath)); - if (!isPathInside(fullPath, skill.baseDir)) { - console.log(`[Skill] 🚫 路径越界已拦截:"${relPath}" 超出技能目录范围`); - return { error: "Access denied: path is outside skill directory" }; - } - try { - const fileContent = await fs.readFile(fullPath, "utf-8"); - console.log(`[Skill] 📄 已读取文件:${skillName}/${relPath}(${fileContent.length} 字符)`); - return { content: fileContent }; - } catch { - console.log(`[Skill] ❌ 读取失败:未找到文件 "${relPath}"`); - return { error: `File not found: ${relPath}` }; - } - }, - }), - }; -} \ No newline at end of file diff --git a/src/utils/agent/skillsTools.ts b/src/utils/agent/skillsTools.ts index 0c39499..96c8ff5 100644 --- a/src/utils/agent/skillsTools.ts +++ b/src/utils/agent/skillsTools.ts @@ -39,42 +39,79 @@ function ensureNonEmptyBody(body: string, fallback: string): string { // ==================== 解析 SKILL.md ==================== function parseFrontmatter(content: string): { name: string; description: string } { - const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/); - if (!match?.[1]) throw new Error("No frontmatter found"); - - const result: Record = {}; - const lines = match[1].split("\n"); - - for (let i = 0; i < lines.length; ) { - const colonIndex = lines[i].indexOf(":"); - if (colonIndex === -1) { - i++; - continue; - } - - const key = lines[i].slice(0, colonIndex).trim(); - if (!key) { - i++; - continue; - } - - let value = lines[i].slice(colonIndex + 1).trim(); - i++; - - if (/^[>|]-?$/.test(value)) { - const fold = value.startsWith(">"); - const parts: string[] = []; - while (i < lines.length && /^\s+/.test(lines[i])) { - parts.push(lines[i].trim()); - i++; - } - value = fold ? parts.join(" ") : parts.join("\n"); - } - - result[key] = value; + const match = content.match(/^\uFEFF?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/); + if (!match?.[1]) { + throw new Error(`技能文件缺少有效的 frontmatter,确保以 --- 包裹并包含 name 和 description 字段。${content}`); + } + + const result: Record = {}; + const lines = match[1].split(/\r?\n/); + + for (let i = 0; i < lines.length; ) { + const line = lines[i]; + const trimmed = line.trim(); + + if (!trimmed || trimmed.startsWith("#")) { + i++; + continue; + } + + const keyMatch = line.match(/^([A-Za-z0-9_-]+)\s*:\s*(.*)$/); + if (!keyMatch) { + i++; + continue; + } + + const key = keyMatch[1].trim(); + const rawValue = (keyMatch[2] ?? "").trim(); + i++; + + if (!key) continue; + + if (/^[>|][+-]?[0-9]*$/.test(rawValue)) { + const isFolded = rawValue.startsWith(">"); + const blockLines: string[] = []; + let blockIndent: number | null = null; + + while (i < lines.length) { + const current = lines[i]; + const currentTrimmed = current.trim(); + + if (currentTrimmed === "") { + if (blockIndent !== null) blockLines.push(""); + i++; + continue; + } + + const currentIndent = current.match(/^\s*/)?.[0].length ?? 0; + if (blockIndent === null) { + blockIndent = currentIndent; + } + + if (currentIndent < blockIndent) break; + + blockLines.push(current.slice(blockIndent)); + i++; + } + + result[key] = isFolded + ? blockLines + .join("\n") + .replace(/\n{2,}/g, "\n\n") + .replace(/([^\n])\n([^\n])/g, "$1 $2") + .trim() + : blockLines.join("\n").trim(); + continue; + } + + const unquoted = rawValue.replace(/^(['"])([\s\S]*)\1$/, "$2"); + result[key] = unquoted; + } + + if (!result.name || !result.description) { + throw new Error(`技能文件缺少必要字段: name 或 description,确保 frontmatter 包含这两个字段。${content}`); } - if (!result.name || !result.description) throw new Error("Frontmatter missing required field: name or description"); return { name: result.name, description: result.description }; } @@ -84,7 +121,7 @@ export async function useSkill(input: SkillInput) { const normalizedRootDir = path.resolve(rootDir); const mainPath = path.join(rootDir, mainSkill + ".md"); if (!fs.existsSync(mainPath)) throw new Error(`主技能文件不存在: ${mainPath}`); - if (!isPathInside(mainPath, normalizedRootDir)) throw new Error("技能名称无效:检测到路径穿越"); + if (!isPathInside(mainPath, normalizedRootDir)) throw new Error(`技能名称无效:检测到路径穿越。${mainPath}`); const resolveSafeSkillDir = (dir: string): string | null => { const resolvedDir = path.resolve(normalizedRootDir, dir); @@ -168,7 +205,6 @@ function createSkillTools(skill: { name: string; description: string }, skillPat content += "\n"; } content += ""; - console.log("%c Line:173 🍕 content", "background:#fca650", content); return { content }; }, }), @@ -210,7 +246,6 @@ function createSkillTools(skill: { name: string; description: string }, skillPat content += "\n"; } content += ""; - console.log("%c Line:214 🍕 content", "background:#6ec1c2", content); return { content }; }, }), diff --git a/yarn.lock b/yarn.lock index 6d2e018..2d12430 100644 --- a/yarn.lock +++ b/yarn.lock @@ -372,6 +372,11 @@ resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz#1f7ba71a3d6155d44a6faa8dbe249c62ab3e408c" integrity sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg== +"@gar/promise-retry@^1.0.0": + version "1.0.3" + resolved "https://registry.npmmirror.com/@gar/promise-retry/-/promise-retry-1.0.3.tgz#65e726428e794bc4453948e0a41e6de4215ce8b0" + integrity sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA== + "@gar/promisify@^1.1.3": version "1.1.3" resolved "https://registry.npmmirror.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" @@ -638,6 +643,17 @@ lru-cache "^10.0.1" socks-proxy-agent "^8.0.3" +"@npmcli/agent@^4.0.0": + version "4.0.0" + resolved "https://registry.npmmirror.com/@npmcli/agent/-/agent-4.0.0.tgz#2bb2b1c0a170940511554a7986ae2a8be9fedcce" + integrity sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA== + dependencies: + agent-base "^7.1.0" + http-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.1" + lru-cache "^11.2.1" + socks-proxy-agent "^8.0.3" + "@npmcli/fs@^2.1.0": version "2.1.2" resolved "https://registry.npmmirror.com/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865" @@ -653,6 +669,13 @@ dependencies: semver "^7.3.5" +"@npmcli/fs@^5.0.0": + version "5.0.0" + resolved "https://registry.npmmirror.com/@npmcli/fs/-/fs-5.0.0.tgz#674619771907342b3d1ac197aaf1deeb657e3539" + integrity sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og== + dependencies: + semver "^7.3.5" + "@npmcli/move-file@^2.0.0": version "2.0.1" resolved "https://registry.npmmirror.com/@npmcli/move-file/-/move-file-2.0.1.tgz#26f6bdc379d87f75e55739bab89db525b06100e4" @@ -661,6 +684,11 @@ mkdirp "^1.0.4" rimraf "^3.0.2" +"@npmcli/redact@^4.0.0": + version "4.0.0" + resolved "https://registry.npmmirror.com/@npmcli/redact/-/redact-4.0.0.tgz#c91121e02b7559a997614a2c1057cd7fc67608c4" + integrity sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q== + "@opentelemetry/api@1.9.0": version "1.9.0" resolved "https://registry.npmmirror.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" @@ -988,6 +1016,11 @@ abbrev@^3.0.0: resolved "https://registry.npmmirror.com/abbrev/-/abbrev-3.0.1.tgz#8ac8b3b5024d31464fe2a5feeea9f4536bf44025" integrity sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg== +abbrev@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/abbrev/-/abbrev-4.0.0.tgz#ec933f0e27b6cd60e89b5c6b2a304af42209bb05" + integrity sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA== + accepts@^2.0.0: version "2.0.0" resolved "https://registry.npmmirror.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" @@ -1469,6 +1502,22 @@ cacache@^19.0.1: tar "^7.4.3" unique-filename "^4.0.0" +cacache@^20.0.1: + version "20.0.4" + resolved "https://registry.npmmirror.com/cacache/-/cacache-20.0.4.tgz#9b547dc3db0c1f87cba6dbbff91fb17181b4bbb1" + integrity sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA== + dependencies: + "@npmcli/fs" "^5.0.0" + fs-minipass "^3.0.0" + glob "^13.0.0" + lru-cache "^11.1.0" + minipass "^7.0.3" + minipass-collect "^2.0.1" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + p-map "^7.0.2" + ssri "^13.0.0" + cacheable-lookup@^5.0.3: version "5.0.4" resolved "https://registry.npmmirror.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" @@ -2569,6 +2618,15 @@ glob@^10.2.2: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" +glob@^13.0.0: + version "13.0.6" + resolved "https://registry.npmmirror.com/glob/-/glob-13.0.6.tgz#078666566a425147ccacfbd2e332deb66a2be71d" + integrity sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw== + dependencies: + minimatch "^10.2.2" + minipass "^7.1.3" + path-scurry "^2.0.2" + glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.3" resolved "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -2807,7 +2865,7 @@ iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -iconv-lite@^0.7.0, iconv-lite@~0.7.0: +iconv-lite@^0.7.0, iconv-lite@^0.7.2, iconv-lite@~0.7.0: version "0.7.2" resolved "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.2.tgz#d0bdeac3f12b4835b7359c2ad89c422a4d1cc72e" integrity sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw== @@ -2987,6 +3045,11 @@ isexe@^3.1.1: resolved "https://registry.npmmirror.com/isexe/-/isexe-3.1.5.tgz#42e368f68d5e10dadfee4fda7b550bc2d8892dc9" integrity sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w== +isexe@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/isexe/-/isexe-4.0.0.tgz#48f6576af8e87a18feb796b7ed5e2e5903b43dca" + integrity sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw== + jackspeak@^3.1.2: version "3.4.3" resolved "https://registry.npmmirror.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" @@ -3227,6 +3290,11 @@ lru-cache@^10.0.1, lru-cache@^10.2.0: resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== +lru-cache@^11.0.0, lru-cache@^11.1.0, lru-cache@^11.2.1: + version "11.2.7" + resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.2.7.tgz#9127402617f34cd6767b96daee98c28e74458d35" + integrity sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA== + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -3287,6 +3355,24 @@ make-fetch-happen@^14.0.3: promise-retry "^2.0.1" ssri "^12.0.0" +make-fetch-happen@^15.0.0: + version "15.0.5" + resolved "https://registry.npmmirror.com/make-fetch-happen/-/make-fetch-happen-15.0.5.tgz#b0e3dd53d487b2733e4ea232c2bebf1bd16afb03" + integrity sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg== + dependencies: + "@gar/promise-retry" "^1.0.0" + "@npmcli/agent" "^4.0.0" + "@npmcli/redact" "^4.0.0" + cacache "^20.0.1" + http-cache-semantics "^4.1.1" + minipass "^7.0.2" + minipass-fetch "^5.0.0" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^1.0.0" + proc-log "^6.0.0" + ssri "^13.0.0" + matcher@^3.0.0: version "3.0.0" resolved "https://registry.npmmirror.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" @@ -3366,7 +3452,7 @@ mimic-response@^3.1.0: resolved "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== -minimatch@^10.0.3, minimatch@^10.2.1: +minimatch@^10.0.3, minimatch@^10.2.1, minimatch@^10.2.2: version "10.2.4" resolved "https://registry.npmmirror.com/minimatch/-/minimatch-10.2.4.tgz#465b3accbd0218b8281f5301e27cedc697f96fde" integrity sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg== @@ -3435,6 +3521,17 @@ minipass-fetch@^4.0.0: optionalDependencies: encoding "^0.1.13" +minipass-fetch@^5.0.0: + version "5.0.2" + resolved "https://registry.npmmirror.com/minipass-fetch/-/minipass-fetch-5.0.2.tgz#3973a605ddfd8abb865e50d6fc634853c8239729" + integrity sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ== + dependencies: + minipass "^7.0.3" + minipass-sized "^2.0.0" + minizlib "^3.0.1" + optionalDependencies: + iconv-lite "^0.7.2" + minipass-flush@^1.0.5: version "1.0.7" resolved "https://registry.npmmirror.com/minipass-flush/-/minipass-flush-1.0.7.tgz#145c383d5ae294b36030aa80d4e872d08bebcb73" @@ -3456,6 +3553,13 @@ minipass-sized@^1.0.3: dependencies: minipass "^3.0.0" +minipass-sized@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/minipass-sized/-/minipass-sized-2.0.0.tgz#2228ee97e3f74f6b22ba6d1319addb7621534306" + integrity sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA== + dependencies: + minipass "^7.1.2" + minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6: version "3.3.6" resolved "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" @@ -3468,7 +3572,7 @@ minipass@^5.0.0: resolved "https://registry.npmmirror.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.2, minipass@^7.0.3, minipass@^7.0.4, minipass@^7.1.2: +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.2, minipass@^7.0.3, minipass@^7.0.4, minipass@^7.1.2, minipass@^7.1.3: version "7.1.3" resolved "https://registry.npmmirror.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b" integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A== @@ -3589,6 +3693,11 @@ node-addon-api@^3.1.0: resolved "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== +node-addon-api@^8.0.0: + version "8.7.0" + resolved "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-8.7.0.tgz#f64f8413456ecbe900221305a3f883c37666473f" + integrity sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA== + node-api-version@^0.1.4: version "0.1.4" resolved "https://registry.npmmirror.com/node-api-version/-/node-api-version-0.1.4.tgz#1ed46a485e462d55d66b5aa1fe2821720dedf080" @@ -3608,6 +3717,22 @@ node-gyp-build@^4.2.1: resolved "https://registry.npmmirror.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz#8a70ee85464ae52327772a90d66c6077a900cfc8" integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ== +node-gyp@12.x: + version "12.2.0" + resolved "https://registry.npmmirror.com/node-gyp/-/node-gyp-12.2.0.tgz#ff73f6f509e33d8b7e768f889ffc9738ad117b07" + integrity sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ== + dependencies: + env-paths "^2.2.0" + exponential-backoff "^3.1.1" + graceful-fs "^4.2.6" + make-fetch-happen "^15.0.0" + nopt "^9.0.0" + proc-log "^6.0.0" + semver "^7.3.5" + tar "^7.5.4" + tinyglobby "^0.2.12" + which "^6.0.0" + node-gyp@^11.2.0: version "11.5.0" resolved "https://registry.npmmirror.com/node-gyp/-/node-gyp-11.5.0.tgz#82661b5f40647a7361efe918e3cea76d297fcc56" @@ -3684,6 +3809,13 @@ nopt@^8.0.0: dependencies: abbrev "^3.0.0" +nopt@^9.0.0: + version "9.0.0" + resolved "https://registry.npmmirror.com/nopt/-/nopt-9.0.0.tgz#6bff0836b2964d24508b6b41b5a9a49c4f4a1f96" + integrity sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw== + dependencies: + abbrev "^4.0.0" + normalize-package-data@^2.0.0: version "2.5.0" resolved "https://registry.npmmirror.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -3895,6 +4027,14 @@ path-scurry@^1.11.1: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" +path-scurry@^2.0.2: + version "2.0.2" + resolved "https://registry.npmmirror.com/path-scurry/-/path-scurry-2.0.2.tgz#6be0d0ee02a10d9e0de7a98bae65e182c9061f85" + integrity sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg== + dependencies: + lru-cache "^11.0.0" + minipass "^7.1.2" + path-to-regexp@^8.0.0: version "8.4.0" resolved "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-8.4.0.tgz#8e98fcd94826aff01a90c544ef74ffbaca3a78ed" @@ -3964,7 +4104,7 @@ possible-typed-array-names@^1.0.0: resolved "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== -prebuild-install@^7.1.1: +prebuild-install@^7.1.1, prebuild-install@^7.1.3: version "7.1.3" resolved "https://registry.npmmirror.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec" integrity sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug== @@ -3987,6 +4127,11 @@ proc-log@^5.0.0: resolved "https://registry.npmmirror.com/proc-log/-/proc-log-5.0.0.tgz#e6c93cf37aef33f835c53485f314f50ea906a9d8" integrity sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ== +proc-log@^6.0.0: + version "6.1.0" + resolved "https://registry.npmmirror.com/proc-log/-/proc-log-6.1.0.tgz#18519482a37d5198e231133a70144a50f21f0215" + integrity sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ== + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -4672,6 +4817,18 @@ sprintf-js@^1.1.2: resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== +sqlite3@^6.0.1: + version "6.0.1" + resolved "https://registry.npmmirror.com/sqlite3/-/sqlite3-6.0.1.tgz#c0956e7834931c406b283c87b66771c847a6abfc" + integrity sha512-X0czUUMG2tmSqJpEQa3tCuZSHKIx8PwM53vLZzKp/o6Rpy25fiVfjdbnZ988M8+O3ZWR1ih0K255VumCb3MAnQ== + dependencies: + bindings "^1.5.0" + node-addon-api "^8.0.0" + prebuild-install "^7.1.3" + tar "^7.5.10" + optionalDependencies: + node-gyp "12.x" + ssri@^12.0.0: version "12.0.0" resolved "https://registry.npmmirror.com/ssri/-/ssri-12.0.0.tgz#bcb4258417c702472f8191981d3c8a771fee6832" @@ -4679,6 +4836,13 @@ ssri@^12.0.0: dependencies: minipass "^7.0.3" +ssri@^13.0.0: + version "13.0.1" + resolved "https://registry.npmmirror.com/ssri/-/ssri-13.0.1.tgz#2d8946614d33f4d0c84946bb370dce7a9379fd18" + integrity sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ== + dependencies: + minipass "^7.0.3" + ssri@^9.0.0: version "9.0.1" resolved "https://registry.npmmirror.com/ssri/-/ssri-9.0.1.tgz#544d4c357a8d7b71a19700074b6883fcb4eae057" @@ -4858,7 +5022,7 @@ tar@^6.0.5, tar@^6.1.11, tar@^6.1.2: mkdirp "^1.0.3" yallist "^4.0.0" -tar@^7.0.1, tar@^7.4.3, tar@^7.5.6, tar@^7.5.7: +tar@^7.0.1, tar@^7.4.3, tar@^7.5.10, tar@^7.5.4, tar@^7.5.6, tar@^7.5.7: version "7.5.13" resolved "https://registry.npmmirror.com/tar/-/tar-7.5.13.tgz#0d214ed56781a26edc313581c0e2d929ceeb866d" integrity sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng== @@ -5199,6 +5363,13 @@ which@^5.0.0: dependencies: isexe "^3.1.1" +which@^6.0.0: + version "6.0.1" + resolved "https://registry.npmmirror.com/which/-/which-6.0.1.tgz#021642443a198fb93b784a5606721cb18cfcbfce" + integrity sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg== + dependencies: + isexe "^4.0.0" + wide-align@^1.1.5: version "1.1.5" resolved "https://registry.npmmirror.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3"