更新agent框架
This commit is contained in:
parent
7c51b95992
commit
b68bec554d
@ -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次)
|
||||
- 前置条件不满足 → 提示用户需要先完成哪个阶段
|
||||
- 记忆检索无结果 → 请求用户提供必要上下文
|
||||
@ -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",
|
||||
|
||||
@ -164,7 +164,7 @@ app.whenReady().then(async () => {
|
||||
windowismaximized: () => ({
|
||||
maximized: mainWindow?.isMaximized() ?? false,
|
||||
}),
|
||||
openDevTool: () => {
|
||||
opendevtool: () => {
|
||||
mainWindow?.webContents.openDevTools();
|
||||
return { ok: true };
|
||||
},
|
||||
|
||||
175
src/agents/scriptAgent/index copy.ts
Normal file
175
src/agents/scriptAgent/index copy.ts
Normal 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;
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@ -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 ?? "无数据";
|
||||
},
|
||||
}),
|
||||
//======================
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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 || "新增技能失败"));
|
||||
}
|
||||
},
|
||||
);
|
||||
@ -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 || "删除技能失败"));
|
||||
}
|
||||
},
|
||||
);
|
||||
@ -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("技能向量生成成功"));
|
||||
},
|
||||
);
|
||||
@ -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));
|
||||
},
|
||||
);
|
||||
@ -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),
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
@ -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),
|
||||
}),
|
||||
);
|
||||
});
|
||||
@ -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 || "更新技能失败"));
|
||||
}
|
||||
},
|
||||
);
|
||||
34
src/routes/setting/skillManagement/saveSkillContent.ts
Normal file
34
src/routes/setting/skillManagement/saveSkillContent.ts
Normal 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));
|
||||
},
|
||||
);
|
||||
@ -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;
|
||||
18
src/types/database.d.ts
vendored
18
src/types/database.d.ts
vendored
@ -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;
|
||||
|
||||
@ -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}` };
|
||||
}
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
@ -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 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("\n");
|
||||
const lines = match[1].split(/\r?\n/);
|
||||
|
||||
for (let i = 0; i < lines.length; ) {
|
||||
const colonIndex = lines[i].indexOf(":");
|
||||
if (colonIndex === -1) {
|
||||
const line = lines[i];
|
||||
const trimmed = line.trim();
|
||||
|
||||
if (!trimmed || trimmed.startsWith("#")) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const key = lines[i].slice(0, colonIndex).trim();
|
||||
if (!key) {
|
||||
const keyMatch = line.match(/^([A-Za-z0-9_-]+)\s*:\s*(.*)$/);
|
||||
if (!keyMatch) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
let value = lines[i].slice(colonIndex + 1).trim();
|
||||
const key = keyMatch[1].trim();
|
||||
const rawValue = (keyMatch[2] ?? "").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());
|
||||
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++;
|
||||
}
|
||||
value = fold ? parts.join(" ") : parts.join("\n");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
result[key] = value;
|
||||
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
181
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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user