更新agent框架

This commit is contained in:
ACT丶流星雨 2026-03-29 00:27:33 +08:00
parent 7c51b95992
commit b68bec554d
20 changed files with 749 additions and 1076 deletions

View File

@ -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次
- 前置条件不满足 → 提示用户需要先完成哪个阶段
- 记忆检索无结果 → 请求用户提供必要上下文

View File

@ -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",

View File

@ -164,7 +164,7 @@ app.whenReady().then(async () => {
windowismaximized: () => ({
maximized: mainWindow?.isMaximized() ?? false,
}),
openDevTool: () => {
opendevtool: () => {
mainWindow?.webContents.openDevTools();
return { ok: true };
},

View File

@ -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<ResTool["newMessage"]>;
}
function buildMemPrompt(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")}`;
}
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格式写入工作区
<storySkeleton></storySkeleton>
<adaptationStrategy></adaptationStrategy>
`;
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;
},
});
}

View File

@ -34,8 +34,6 @@ function buildMemPrompt(mem: Awaited<ReturnType<Memory["get"]>>): 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格式写入工作区
<storySkeleton></storySkeleton>
<adaptationStrategy></adaptationStrategy>
`;
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<storySkeleton>故事骨架内容</storySkeleton>\n<adaptationStrategy>改编策略内容</adaptationStrategy>";
// 子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,
};
}

View File

@ -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 ?? "无数据";
},
}),
//======================

View File

@ -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);
}

View File

@ -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 || "新增技能失败"));
}
},
);

View File

@ -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 || "删除技能失败"));
}
},
);

View File

@ -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("技能向量生成成功"));
},
);

View File

@ -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));
},
);

View File

@ -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<string, string[]>();
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),
})
);
}
);

View File

@ -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<string>();
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<string, any> = { 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),
}),
);
});

View File

@ -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 || "更新技能失败"));
}
},
);

View File

@ -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));
},
);

View File

@ -1,79 +0,0 @@
import u from "@/utils";
import { Socket } from "socket.io";
class ResTool {
public socket: Socket;
public data: Record<string, any>;
constructor(socket: Socket, data: Record<string, any> = {}) {
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;

View File

@ -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;

View File

@ -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<string, string> = {};
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
<available_skills>
<skill>
<name>${skill.name}</name>
<description>${skill.description}</description>
</skill>
</available_skills>`;
}
function createSkillTools(skill: SkillRecord, mainSkillName: string) {
const activated = new Set<string>();
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 = `<skill_content name="${name}">\n`;
content += body + "\n\n";
content += `Skill directory: ${skill.baseDir}\n`;
content += "相对路径基于此技能目录解析,使用 read_skill_file 工具读取资源文件。\n";
if (resources.length > 0) {
content += "\n<skill_resources>\n";
for (const { path } of resources) {
content += ` <file>${path}</file>\n`;
}
content += "</skill_resources>\n";
}
content += "\n<skill_tools_guide>\n";
content += "- read_skill_file读取上方 skill_resources 中列出的资源文件。\n";
content += "- discover_skill_docs当上方资源不足以完成任务时使用关键词检索更多相关文档。传入与当前任务相关的关键词列表即可获取推荐。\n";
content += "</skill_tools_guide>\n";
content += "</skill_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}` };
}
},
}),
};
}

View File

@ -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<string, string> = {};
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<string, string> = {};
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 += "</skill_resources>\n";
}
content += "</skill_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 += "</skill_resources>\n";
}
content += "</skill_content>";
console.log("%c Line:214 🍕 content", "background:#6ec1c2", content);
return { content };
},
}),

181
yarn.lock
View File

@ -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"