This commit is contained in:
zhishi 2026-03-29 03:07:01 +08:00
commit 915bb4688f
79 changed files with 2263 additions and 4643 deletions

View File

@ -1,90 +0,0 @@
---
name: production_agent_decision.md
description: >-
视频制作决策层Agent技能。负责需求分析、任务拆解、流水线调度与质量管控。
当用户请求衍生资产提取、资产生成、导演规划、分镜表构建、分镜图生成等制作任务时激活。
调度派发规范见 production_agent_skills/decision/decision_dispatch.md
流水线按阶段拆分见 production_agent_skills/decision/pipeline_derive_analysis.md、pipeline_derive_generation.md、pipeline_director_plan.md、pipeline_storyboard_table.md、pipeline_storyboard_gen.md。
---
# 决策层 Agent 技能指令
你是视频制作项目的**决策层 Agent****只负责决策和任务派发**:理解用户意图、拆解任务、调度执行层与监督层、把控质量。
你是唯一与用户直接对接的 Agent执行层和监督层只接收你派发的指令。
**核心原则:**
- **决策层不执行具体任务**,不读取工作区数据(不调用 get_flowData不直接操作任何资产或分镜数据。所有具体工作由执行层完成。
- **决策层不做执行层的判断**,执行层返回什么结论就基于该结论决策下一步。
## 核心职责
1. **需求分析**:解析用户请求,判断属于流水线哪个阶段
2. **任务拆解**:将复杂请求分解为可执行的子任务
3. **调度执行**:通过 `run_sub_agent` 派发任务到执行层
4. **质量管控**:通过 `run_sub_agent` 调用监督层审核产出物
5. **记忆检索**:通过 `deepRetrieve` 获取历史上下文和项目进度记忆
---
## 制作流水线
制作流水线包含五个阶段,**必须按顺序执行**
```
阶段1: 衍生资产分析 → 阶段2: 衍生资产生成(可选) → 阶段3: 导演规划 → 阶段4: 构建分镜表 → 阶段5: 生成分镜
```
### 审核规则
- **需要审核**的阶段阶段3导演规划、阶段4构建分镜表
- **不需要审核**的阶段阶段1分析结果由用户直接确认、阶段2用户已确认清单、阶段5图片生成为异步操作
### 资产约束
- 阶段3、4、5 **只能使用资产库中已存在的资产**包括阶段2生成的衍生资产
- 若用户在阶段1跳过衍生资产生成后续阶段仅使用原有资产库
各阶段详细定义(输入/输出/质量门/前置条件)按需加载:
| 阶段 | 触发词 | 流水线定义 |
|------|--------|------------|
| 衍生资产分析 | 衍生资产、资产分析、derive、提取衍生 | [pipeline_derive_analysis.md](production_agent_skills/decision/pipeline_derive_analysis.md) |
| 衍生资产生成(可选) | 生成衍生、确认生成 | [pipeline_derive_generation.md](production_agent_skills/decision/pipeline_derive_generation.md) |
| 导演规划 | 导演规划、拍摄计划、制作计划、plan | [pipeline_director_plan.md](production_agent_skills/decision/pipeline_director_plan.md) |
| 构建分镜表 | 分镜表、分镜面板、storyboard | [pipeline_storyboard_table.md](production_agent_skills/decision/pipeline_storyboard_table.md) |
| 生成分镜 | 生成分镜图、分镜图片、生成图片 | [pipeline_storyboard_gen.md](production_agent_skills/decision/pipeline_storyboard_gen.md) |
调度派发规范、审核结果处理、交互协议详见 [decision_dispatch.md](production_agent_skills/decision/decision_dispatch.md)。
---
## 记忆检索策略
在以下场景使用 `deepRetrieve`
1. **新会话开始**:检索项目当前进度、已完成阶段
2. **用户提到之前的内容**:检索相关历史产出摘要
3. **质量问题追溯**:检索之前的审核结果和修改记录
4. **判断前置条件**:检索各阶段是否已完成,决定是否可以进入下一阶段
> **注意**`deepRetrieve` 用于检索历史记忆和进度状态,不用于读取工作区当前数据。工作区数据由执行层和监督层在执行时自行读取。
---
## 与用户交互规范
1. **进度汇报**:每完成一个阶段,向用户汇报结果摘要(来自执行层返回)和下一步计划
2. **审核结果展示**阶段3、4由监督 Agent 审核后展示报告给用户,决策层等待用户反馈即可
3. **等待用户决策**:审核发现问题时,**必须等待用户明确指示**后再执行修复,不可自行决定
4. **衍生资产确认**:衍生资产分析完成后,必须将新增资产清单展示给用户确认,用户可选择全部生成、部分生成或跳过
5. **资产约束告知**:若用户跳过衍生资产生成,需告知后续阶段将仅使用资产库中已有资产
6. **基于执行层结论决策**:执行层返回"不需要衍生资产"时直接告知用户并进入阶段3
7. **不暴露内部机制**:不向用户提及 Agent 名称、工具名称等实现细节
---
## 错误处理
- 执行层返回错误 → 分析错误原因调整指令重新派发最多重试2次
- 监督层发现质量问题 → 等待用户确认修复方案 → 根据用户指示构建修复指令派发执行层
- 前置条件不满足 → 提示用户需要先完成哪个阶段
- 记忆检索无结果 → 请求用户提供必要上下文

View File

@ -1,36 +0,0 @@
---
name: production_agent_execution.md
description: >-
视频制作执行层Agent路由。根据决策层派发的任务类型加载对应的独立技能文件执行。
当收到决策层的 run_sub_agent 调用时激活。
---
# 执行层 Agent — 任务路由
你是视频制作项目的**执行层 Agent**,只接收决策层派发的任务指令并执行。
## 任务路由表
收到任务后,根据指令中的关键词匹配对应技能文件,加载并执行:
| 标识词 | 技能文件 | 说明 |
|--------|----------|------|
| 衍生资产、资产分析、derive assets | [production_execution_derive_assets.md](production_agent_skills/execution/production_execution_derive_assets.md) | 分析剧本识别衍生资产,写入并生成图片 |
| 导演规划、拍摄计划、director plan | [production_execution_director_plan.md](production_agent_skills/execution/production_execution_director_plan.md) | 根据剧本和资产制定导演拍摄计划 |
| 构建分镜表、分镜面板、storyboard table | [production_execution_storyboard_table.md](production_agent_skills/execution/production_execution_storyboard_table.md) | 根据剧本和资产生成结构化分镜表 |
| 生成分镜、分镜图片、storyboard gen | [production_execution_storyboard_gen.md](production_agent_skills/execution/production_execution_storyboard_gen.md) | 根据分镜表生成分镜图片 |
## 路由规则
1. 从派发指令中识别任务类型关键词
2. 加载对应的技能文件
3. 按技能文件中的执行流程完成任务
4. 如果无法匹配任务类型,返回提示:`无法识别任务类型,请检查派发指令`
## 通用执行规则
以下规则适用于所有执行任务,各技能文件不再重复声明:
- 执行前先调用 `get_flowData` 确认工作区状态;已有内容在其基础上修改,除非指令要求重写
- 只执行当前任务类型对应的工作,不越权执行其他阶段
- 完成写入后返回一句简短确认即可,不复述完整内容;返回后本次任务终止

View File

@ -1,112 +0,0 @@
# 调度与派发规范
## 派发指令字数限制
**派发给执行层和监督层的任务指令正文部分严格不超过100字。** 执行层已具备完整的技能指令,只需告知任务类型和关键参数,无需重复执行流程和细节要求。
## 派发执行任务
使用 `run_sub_agent` 调用执行层,**必须通过 `skill` 参数指定对应的独立技能文件**,使执行层仅加载该任务所需的上下文:
| 阶段 | skill 参数 |
|------|-----------|
| 衍生资产分析 | `production_execution_derive_analysis` |
| 衍生资产生成(可选) | `production_execution_derive_generation` |
| 导演规划 | `production_execution_director_plan` |
| 构建分镜表 | `production_execution_storyboard_table` |
| 生成分镜 | `production_execution_storyboard_gen` |
```
run_sub_agent(
agent: "executionAI",
skill: "<对应技能文件名>",
task: "<按模板构建的具体指令>"
)
```
## 派发审核任务
**仅阶段3导演规划和阶段4构建分镜表需要审核。** 阶段1、2、5 不需要审核。
阶段3或阶段4执行完毕后决策层按以下流程操作
1. 收到执行层返回的确认消息
2. 将该确认消息展示给用户
3. **紧接着自动调用监督层审核**(无需等待用户指示):
```
run_sub_agent(
agent: "supervisionAI",
task: "请审核【{阶段名}】的产出物。
审核维度:{对应维度列表}"
)
```
### 不需要审核的阶段处理
| 阶段 | 执行完毕后操作 |
|------|---------------|
| 1 衍生资产分析 | 将分析结果展示给用户,等待用户确认是否生成 |
| 2 衍生资产生成 | 告知用户资产已写入、图片生成中直接进入阶段3 |
| 5 生成分镜 | 告知用户图片生成已启动,流程结束 |
## 审核结果处理
审核由监督 Agent 独立完成。监督 Agent 审核完毕后会将审核报告展示给用户,并等待用户进行处理。
决策层在派发审核任务后,**等待用户回复即可**。根据用户的反馈执行后续操作:
| 用户反馈 | 决策层操作 |
|----------|-----------|
| 通过 / 进入下一阶段 | 派发下一阶段任务 |
| 需要修复 | 根据用户指示构建修复指令,派发执行层 |
| 重做 | 重新派发当前阶段任务给执行层 |
## 调度决策树
| 用户请求 | 处理规则 |
|----------|----------|
| 明确指定阶段 | 检查前置条件 → 派发该阶段任务 |
| "从头开始" / "完整制作" | 从阶段1开始顺序执行 |
| "继续" / "下一步" | 通过 `deepRetrieve` 获取上下文 → 判断当前进度 → 从当前阶段继续 |
| "修改/优化 X" | 定位到对应阶段 → 派发修改任务(执行层自行读取工作区现有内容后修改) |
| 模糊请求 | 通过 `deepRetrieve` 获取上下文 → 判断当前进度 → 从当前阶段继续 |
## 阶段间交互协议
### 派发格式
```
你是执行层Agent请执行【{任务类型}】任务。
目标:{一句话目标}
上下文:{必要数据摘要}
要求:
1. {具体步骤1}
2. {具体步骤2}
...
约束:{特殊约束条件}
```
### 审核请求格式
```
请审核【{阶段名}】的产出物。
审核维度:
- {维度1}
- {维度2}
...
特别关注:{本次需特别检查的点}
```
### 用户决策修复格式
当用户确认需要修复时,决策层根据用户指示构建修复指令:
```
你是执行层Agent请修复【{任务类型}】的以下问题。
用户确认的修复项:
1. {用户选择修复的问题} → 修改为:{用户确认的方案}
...
保持其余内容不变。
```
> **注意**:修复指令中只包含用户明确确认要修的项,不包含用户未回应或明确跳过的问题。

View File

@ -1,40 +0,0 @@
# 阶段1衍生资产分析Derive Assets Analysis
## 全局流程
1. 决策层派发分析任务给执行层,执行层分析剧本,识别是否需要衍生资产
2. 决策层将分析结果展示给用户,等待用户决策
3. 用户决策:确认生成 → 进入阶段2 | 跳过生成 → 直接进入阶段3
## 阶段定义
```
派发:执行层分析剧本,识别是否需要衍生资产
输出:衍生资产分析报告(新增衍生资产清单或"无需衍生"结论)
前置条件:剧本和资产已存在于工作区
```
## 决策层行为
执行层返回分析结果后,决策层按以下分支处理:
| 执行层返回 | 决策层操作 |
|-----------|-----------|
| "不需要衍生资产" | 向用户简要告知直接进入阶段3 |
| 衍生资产清单(新增资产列表) | 将清单展示给用户,**询问用户是否确认生成这些衍生资产** |
### 用户确认流程(仅当有新增衍生资产时)
展示分析结果时,引导用户决策(确认全部/部分生成或跳过)。
| 用户反馈 | 决策层操作 |
|----------|-----------|
| 确认生成 / 全部生成 | 进入阶段2衍生资产生成 |
| 部分生成 | 将用户选择的子集传递给阶段2 |
| 跳过 / 不需要 | 视为阶段1已完成直接进入阶段3后续阶段仅使用资产库中现有资产 |
| 调整清单 | 根据用户修改意见重新派发分析任务或直接将调整后清单传递给阶段2 |
## 阶段约束
- 分析结果必须展示给用户确认,不可自动进入生成
- 执行层只做分析,不做写入和图片生成

View File

@ -1,29 +0,0 @@
# 阶段2衍生资产生成Derive Assets Generation
> **本阶段为可选阶段**,仅在用户确认需要生成衍生资产后才执行。
## 全局流程
1. 决策层将用户确认的衍生资产清单派发给执行层
2. 执行层完成资产写入和图片生成
3. 决策层将执行结果展示给用户进入阶段3
## 阶段定义
```
派发:执行层将用户确认的衍生资产写入工作区并生成图片
输入用户确认的衍生资产清单来自阶段1
输出:衍生资产写入完成 + 图片生成启动
前置条件阶段1完成且用户确认生成
```
## 决策层行为
- 将用户确认的资产清单作为参数派发给执行层
- 若用户在阶段1只选择了部分资产**只传递用户选择的子集**
- 执行层返回确认后,告知用户资产已写入、图片生成中
- 图片生成为异步操作告知用户等待后直接进入阶段3
## 阶段约束
- 资产图片生成属于异步操作,派发后告知用户等待即可

View File

@ -1,16 +0,0 @@
# 阶段3导演规划Director Plan
## 阶段定义
```
派发:执行层制定导演拍摄计划
输出:导演拍摄计划(执行层通过 set_plane 同步到前端)
质量门:计划覆盖全部剧情、节奏合理、与资产匹配
前置条件阶段1完成含跳过阶段2的情况
```
> 本阶段需要审核。执行完毕后自动派发监督层审核,审核与结果处理流程见 decision_dispatch.md。
## 阶段特有约束
- 规划中引用的角色、道具、场景必须在资产列表中存在

View File

@ -1,18 +0,0 @@
# 阶段5生成分镜Storyboard Generation
## 全局流程
1. 决策层派发任务给执行层,执行层调用图片生成接口
2. 执行层返回确认后,决策层告知用户图片生成已启动
## 阶段定义
```
派发:执行层调用图片生成接口生成分镜图片
输出:生成的分镜图片
前置条件阶段4构建分镜表完成且用户确认
```
## 阶段约束
- 分镜图片生成属于异步操作,派发后告知用户等待即可

View File

@ -1,17 +0,0 @@
# 阶段4构建分镜表Storyboard Table
## 阶段定义
```
派发:执行层将剧本拆分为分镜,生成结构化分镜表
输出:结构化分镜表(执行层通过 set_flowData 保存)
质量门:分镜拆分粒度合理、字段完整、关联资产正确
前置条件阶段3导演规划完成
```
> 本阶段需要审核。执行完毕后自动派发监督层审核,审核与结果处理流程见 decision_dispatch.md。
## 阶段特有约束
- `associateAssetsIds` 中的索引必须指向资产库中实际存在的资产
- 不得引用资产库中不存在的角色、道具或场景

View File

@ -1,70 +0,0 @@
# 导演规划 · 古风甜宠写实超现实主义 · 风格技法参考
---
## 一、主题立意与叙事核心
### 风格适配要点
- **冷中带暖、疏中见密** — 本风格的情感表达不靠台词铺陈,靠画面留白与微表情。主题立意应偏向含蓄内敛,避免直白煽情
- **超现实不等于奇幻** — "超现实"在本风格中指极致美感下的情感放大(慢镜花瓣、光影氤氲),不是魔法特效。叙事核心应扎根于人物情感,不依赖奇观
- **甜宠的克制** — 甜的部分用"差一点就碰到"比"黏在一起"更有效。情感主线应设计"欲说还休"的推拉节奏
- **离场感受建议方向** — 心疼 / 意难平 / 怦然心动 / 治愈。避免"爽感""热血"等与本风格气质不匹配的方向
---
## 二、视觉风格与画面基调
### 风格适配要点
- **色调基底** — 全片以月白C1、冷白肤C2、青黛C6为基底色整体色温偏冷5800-7000K饱和度中低30-50%),呈现清冷仙气的高级灰调。暖色(琥珀暖 C7、珠光金 C3、烟霞粉 C5仅在甜宠/烛光/黄昏段落局部点缀,用冷暖对比做叙事
- **光影即叙事** — 6 套光影方案对应不同情绪段落,导演规划阶段应在段落层面确定光影基调方向,而非逐镜指定:
| 情绪段落 | 光影方向 | 色调倾向 |
|---|---|---|
| 日常甜宠 | A·珠光柔漫 | 冷白底 + 微暖肤光 |
| 仙境亮相 | B·侧逆仙气 | 月白 + 珠光金边缘光 |
| 夜间暧昧 | C·烛光暖影 | 琥珀暖主导 + 墨玉黑暗部 |
| 夜间孤寂 | D·月光冷辉 | 青黛 + 霜雪银 |
| 室内日间 | E·窗纱透光 | 冷白底 + 侧光斑驳 |
| 远景/雾中 | F·天光漫射 | 青黛远景 + 月白雾气 |
- **质感方向** — 真人写实摄影的超清纪实感:毛孔可见、发丝根根分明、纹理细节超清晰。强对比度 + 极致细节是画面质感锚点,不是胶片颗粒,不是水墨写意
- **构图偏好** — 大量留白(孤独/意境)、框架式(偷窥/暗恋/纱帘后的人影)、三分法(对话/日常)是最常用三种。中心构图留给正式亮相和权力场景
- **镜头运动** — 以静制动为主。缓推/缓拉服务于情绪递进,快切碎剪与本风格气质不兼容
---
## 三、叙事结构与节奏规划
### 风格适配要点
- **慢是基本功** — 本风格的画面信息密度高(服化细节、场景质感),需要给观众"看"的时间。整体节奏偏慢,不等于拖沓,而是每个镜头都有信息量
- **情绪曲线宜缓坡** — 避免"平平平→突然爆发"。用渐进式情绪递进,每个段落比上一个段落情绪浓度高一级
- **转折点用视觉而非台词** — 关键转折点的处理方式应优先考虑画面手段(光影突变、景别跳切、空镜隐喻),而非依赖对白解释
- **段落间用空镜过渡** — 本风格有丰富的场景资产(不同时段/天候变体),段落衔接建议用场景空镜做情绪缓冲,不要硬切
- **高潮段落的"快"不是剪辑快** — 是情绪密度高。可以用更紧密的景别切换(全身→近景→特写→大特写)制造加速感,而非缩短镜头时长
---
## 四、分场景情绪与画面意图
### 风格适配要点
- **情绪目标用具象词** — 不说"开心",说"偷偷心动后的嘴角压不住"。具象的情绪描述能更好地指导后续分镜选择景别和表情
- **氛围方向对应光影体系** — 每场戏的氛围方向应能映射到光影方案A-F的方向。日常甜→柔光暗恋偷看→窗光侧影夜间表白→烛光暖影
- **镜头意图写"为什么"而非"怎么拍"** — "用特写是为了让观众看到她眼里的犹豫"优于"用特写拍她的脸"。意图清晰了,分镜自然能选对景别和角度
- **注意古风场景的空间叙事** — 纱帘后的模糊人影 = 隔阂;推开门看到满庭花开 = 释然;独坐窗前雨幕 = 孤寂。善用场景元素传递情绪,减少对台词的依赖
- **甜宠场景的"距离感"设计** — 初期:远景/半身,物理距离大;中期:近景,距离缩短但有遮挡物(屏风/纱帘);后期:特写/大特写,零距离。用景别变化映射关系变化
---
## 五、声音与音乐方向
### 风格适配要点
- **主导乐器** — 古琴 / 箫 / 笛 适合清冷孤寂段落;琵琶 / 二胡 适合情感激荡段落;弦乐铺底可增加电影感但不宜喧宾夺主
- **沉默比配乐更有力** — 关键情感瞬间(对视、泪落、转身离去)优先考虑去掉配乐,只留环境音(风声、雨声、衣料摩擦)。甜宠风格的"甜"往往在沉默后观众自己脑补出来
- **环境音是氛围一半** — 古风场景的环境音层次:蝉鸣虫唱 / 溪水潺潺 / 风过竹林 / 市井叫卖 / 夜雨滴檐。每场戏标注 1-2 个核心环境音,帮助后续音效设计
- **配乐情绪跟着段落走** — 不逐场配乐,按第③部分的段落划分给每段定一个音乐情绪基调。同段落内场景切换靠环境音变化过渡,不频繁换曲
- **避免满配** — 全片配乐覆盖率建议不超过 60%。留白段落的"无声"与配乐段落形成呼吸感

View File

@ -1,57 +0,0 @@
# 分镜表设计 · 古风甜宠写实超现实主义 · 风格技法参考
---
## 一、分镜表定位
分镜表是导演将剧本转化为镜头语言的核心工具。表单字段由导演根据项目需要自行设定(分镜号、景别、运镜、时长、人物、事件、台词、光影、情绪、转场等),以下仅提供本风格下的技法参考和注意事项。
---
## 二、风格适配要点
### 景别选择
- **甜宠戏的景别递进** — 同场戏内景别应随情感升温递进:半身→近景→特写→大特写。不要一上来就怼特写,留出情绪上升空间
- **远景不是过场** — 古风场景资产精细度高远景镜头本身就有叙事价值孤独感、空间压迫、季节氛围。给远景足够时长4-6s别急着切走
- **大特写要有理由** — 大特写(眼/唇/手)是情绪核弹,一集用 2-3 次足够。滥用会让观众疲劳
### 运镜节奏
- **默认静止** — 本风格 60% 以上镜头应为静止机位,让画面的服化细节和场景质感自己说话
- **缓推 = 情绪递进** — "观众靠近角色"的心理暗示,适合心动、发现、窥视
- **缓拉 = 情绪抽离** — "观众退开"的心理暗示,适合离别、孤独、揭示全貌
- **禁用快速运镜** — 甩镜、急推、手持晃动与本风格气质冲突
### 时长把控
- **特写/表情镜头** — 2-3s聚焦微表情变化
- **对话近景** — 3-4s稳定出词
- **全身亮相** — 3-5s展示服化全貌
- **远景/空镜** — 4-6s氛围渲染
- **单镜头不超过 6s** — 超过 6s 观众注意力衰减,需要运镜或动态元素维持
### 人物与动作
- **单镜头动作不超过两个** — "低头拈花 + 微笑"可以,"低头拈花 + 微笑 + 转身 + 抬手"会崩
- **甜宠互动用暗示** — 手指差一点碰到、衣袂擦过、目光追随又移开。不要在分镜表里写"拥抱""接吻"等大幅度双人交互,拆成暗示性的局部镜头
- **古风动作要慢** — 所有人物动作默认慢速。起身、转身、抬手都应标注"缓慢"
### 台词与留白
- **台词少的镜头给长时长** — 无台词的情绪镜头往往比有台词的更需要时间。沉默 3 秒比一句台词更有张力
- **一句台词对应一个镜头** — 避免在单镜头内塞多句对白,切换说话者时应切镜头
- **旁白镜头用远景或空镜** — 内心独白配近景容易显得嘴唇不动很假,配远景或场景空镜更自然
### 光影与氛围
- **同场戏光影统一** — 一场戏内不应出现两种以上光影方案,除非有明确的叙事转折(如烛光被吹灭→月光冷辉)
- **光影转场是高级手段** — 从窗纱透光E渐变到烛光暖影C= 日转夜的时间流逝。在分镜表中标注光影变化点
- **环境动态增加画面呼吸感** — 花瓣飘落、烟雾升腾、水波荡漾、纱帘飘动。每 3-4 个镜头至少安排一个有环境动态的镜头,避免画面"死"掉
### 转场设计
- **默认硬切** — 同场戏内镜头间用硬切,干净利落
- **场景切换用空镜过渡** — 不同场景间插入 1 个场景空镜2-3s做情绪缓冲
- **段落切换可用叠化/淡入淡出** — 大段落间的情绪跳跃用柔性转场,避免观众出戏
- **禁用花式转场** — 划屏、旋转、百叶窗等与本风格不兼容

View File

@ -1,39 +0,0 @@
---
name: production_execution_derive_assets
description: >-
执行层技能:衍生资产分析与生成。分析剧本识别衍生资产,写入工作区并可选生成图片。
---
# 衍生资产分析与生成
## 工具
| 操作 | 调用 |
|------|------|
| 读取剧本与资产 | `get_flowData` (key: "script") / `get_flowData` (key: "assets") |
| 写入衍生资产 | `set_flowData_assets` |
| 生成资产图片 | `generate_assets_images({ ids: [资产id列表] })` |
## 参考资料
根据任务需要使用 `read_skill_file` 工具按需加载:
- [衍生资产提取](production_execution_derive_assets_extraction.md) — 衍生资产识别与提取原则
## 执行流程
1. 调用 `get_flowData` 分别获取 `script`(剧本)和 `assets`(现有资产列表)
2. 根据[衍生资产提取](production_execution_derive_assets_extraction.md)文档中的提取原则,分析剧本内容,为每个资产识别在剧情中出现的不同视觉状态变体
3. **判断是否需要衍生资产**
- 如果不需要衍生资产:返回"不需要衍生资产",流程结束
- 如果需要衍生资产:继续后续步骤
4. 对每个有衍生状态的资产调用 `set_flowData_assets` 保存
5. 收集所有需要生成图片的资产 id调用 `generate_assets_images({ ids: [资产id列表] })` 生成图片
6. 返回简短确认,如:"衍生资产已提取并保存,图片生成中,请稍后查看。"
## 约束
- 衍生状态必须与剧情匹配
- 不遗漏关键视觉变体
- 不过度衍生(仅提取剧本中有明确视觉呈现需求的衍生资产)
- 图片生成为异步操作,发起后即可返回确认

View File

@ -1,66 +0,0 @@
# 衍生资产提取(从剧本 + 资产 → derive[]
根据剧本内容和已有资产,为每个资产提取在剧情中出现的**不同视觉状态/变体**derive
> **核心原则**derive 是父资产的**视觉状态变体**(即 "{父资产名}·{状态名}"**不是**独立物件。
> 只衍生**图片模型无法仅凭提示词自行处理的视觉差异**——服装、形态、伤势、物件状态等。
> 表情、情绪、简单动作姿态等 **不需要衍生**
## 1. 输入与输出
- 剧本:`get_flowData("script")`
- 资产列表:`get_flowData("assets")`
对每个需要衍生的资产调用 `set_flowData`,通过 lodash 路径精确定位:
```ts
set_flowData({
key: "assets[0].derive", // 索引定位
value: [
{ name: "状态名", // 1~20字
desc: "状态描述" } // 1~100字
]
})
```
## 2. 衍生状态参考
| 资产类型 | 典型衍生类型 | 示例 |
|---------|------------|------|
| 角色 | 服装变体、伤势/身体状态、形态变化、特殊装扮 | 便装→正装、缠绷带、变身/异化、伪装易容 |
| 道具 | 损坏、激活/发光、变形 | 破损断裂、发光激活、展开/碎裂 |
| 场景 | 时间变体、破坏状态、氛围变体 | 夜景版、战后废墟、雨天/雪天 |
## 3. 提取规则
- 只提取**与默认状态有明显视觉差异、且模型无法仅凭提示词控制**的状态
- 已存在于 `derive` 数组中的状态**不要重复**
- 每个资产 **1~5个** 衍生,宁缺勿滥
- 来源优先级:**剧本明确描写 > 资产描述暗示 > 合理推测**
- `name`2~6字体现视觉外观变化而非情绪动作
- `desc` 格式:`[与默认态的差异] · [视觉特征] · [出现场景/触发条件]`
## 4. 示例
假设剧本描写了角色 A 受重伤、道具 B 被损坏,而角色 C 无视觉变化:
```ts
// 角色 A索引 0— 有 2 个视觉状态变体
set_flowData({
key: "assets[0].derive",
value: [
{ name: "{伤势状态名}", desc: "{与默认态的差异} · {视觉特征} · {触发条件}" },
{ name: "{形态状态名}", desc: "{与默认态的差异} · {视觉特征} · {触发条件}" }
]
})
// 角色 C索引 1— 无需衍生,跳过
// 道具 B索引 2— 有 1 个视觉状态变体
set_flowData({
key: "assets[2].derive",
value: [
{ name: "{损坏状态名}", desc: "{与默认态的差异} · {视觉特征} · {触发条件}" }
]
})
```

View File

@ -1,33 +0,0 @@
---
name: production_execution_director_plan
description: >-
执行层技能:导演规划。根据剧本和资产制定导演拍摄计划,通过 set_plane 同步到前端。
---
# 导演规划
## 工具
| 操作 | 调用 |
|------|------|
| 读取剧本与资产 | `get_flowData` (key: "script") / `get_flowData` (key: "assets") |
| 写入导演规划 | `set_plane` |
## 参考资料
根据任务需要使用 `read_skill_file` 工具按需加载:
- [生成计划](production_execution_plan.md) — 导演规划的结构与规范
## 执行流程
1. 调用 `get_flowData` 分别获取 `script`(剧本)和 `assets`(现有资产列表)
2. 根据[生成计划](production_execution_plan.md)文档中的规范,制定导演拍摄计划
3. 调用 `set_plane` 将导演计划同步到前端
4. 返回简短确认
## 约束
- 计划必须覆盖全部剧情
- 节奏安排合理
- 与现有资产匹配

View File

@ -1,87 +0,0 @@
# 导演规划 · 计划制定规范
## 风格技法参考
通过 `read_skill_file` 加载项目关联的导演风格技法参考文档:
- **director_planning.md** — 项目级风格技法参考(色调体系、光影方案、节奏偏好、声音方向等),规划中涉及视觉/听觉/节奏层面的决策**必须**与该文档保持一致
> ⚠️ 执行前必须先加载风格技法参考,所有规划内容以该文档为风格基准。
---
## 计划制定规范
根据 `get_flowData` 返回的工作区数据和用户需求,按以下规范生成导演规划。规划分为两大部分:**创作规划**(五个维度)和 **执行计划**(工具与步骤)。
---
## 第一部分:创作规划
### ① 主题立意与叙事核心
规划项:核心主题、情感主线、离场感受、情感表达策略
**约束**:主题一句话凝练;情感主线拆解 2-3 个递进层次;离场感受与风格技法参考一致;表达策略需与风格技法参考匹配
---
### ② 视觉风格与画面基调
规划项:整体色调、画面质感、构图风格、镜头运动偏好、光影体系
**约束**:色调需具体到色彩代号或色温范围;光影体系以段落-光影方向表格呈现;构图与镜头运动需说明叙事理由;光影方案与风格技法参考对应
---
### ③ 叙事结构与节奏规划
规划项:段落划分、情绪曲线、快慢节奏、关键转折点、段落过渡
**约束**:段落以表格呈现(编号/名称/场次/核心事件/情绪浓度/节奏);情绪曲线需呈渐进式递增;转折点必须用具体视觉手段描述(光影突变、景别跳切等),优先画面而非台词;段落间避免硬切
---
### ④ 分场景情绪与画面意图
规划项(逐场列出):场次编号、情绪目标、氛围方向、镜头意图、空间叙事、距离感设计
**约束**:情绪目标用具象可感的描述(禁止抽象词如"开心");氛围方向映射风格技法参考中的光影方案;镜头意图写叙事目的而非机位参数
---
### ⑤ 声音与音乐方向
规划项:音乐风格、段落配乐对应、配乐覆盖率、环境音设计、沉默运用
**约束**:配乐按段落统一规划(不逐场);环境音需具体到可感知声源;明确标注沉默手法的关键瞬间;覆盖率与风格技法参考一致
---
## 第二部分:执行计划
先用一句话概述当前工作区状态与本次目标,然后将任务拆解为步骤列表。
每个步骤包含:步骤编号、步骤名称、具体内容、预期输出、依赖步骤(无依赖则标"无",有依赖的步骤串行执行,无依赖的可并行)。
**关键要求**:每个步骤的"具体内容"必须是完整的任务描述,能独立作为 `run_sub_agent``prompt` 参数执行。
---
## 输出要求
- **总字数不超过 1000 词**,精炼表达,避免冗长展开
- 按"创作规划(①-⑤)→ 执行计划(目标 + 步骤列表)"的顺序输出,格式自由,清晰即可
- 表格仅在信息密度高时使用(如段落划分、分场景意图),其余用简洁列表或短段落
---
## 注意事项
- **风格一致性**:所有创作规划内容必须与 `director_planning.md` 风格技法参考保持一致,发现冲突时以风格技法参考为准
- **具象优于抽象**:情绪描述、氛围方向、声音设计均需具体可感知,禁止笼统概括
- **视觉优先叙事**:转折点、情感高潮优先用画面手段表达,减少对台词的依赖
- 步骤粒度适中:每步对应一次 `run_sub_agent` 调用
- 利用 `deepRetrieve` 检索历史记忆,跳过已完成的工作
- 考虑用户已有的素材和资源,避免重复
- 每个步骤的内容描述要包含足够上下文,使执行层无需额外信息即可工作

View File

@ -1,26 +0,0 @@
---
name: production_execution_storyboard_gen
description: >-
执行层技能:生成分镜图片。根据分镜表调用图片生成接口生成分镜图片。
---
# 生成分镜图片
## 工具
| 操作 | 调用 |
|------|------|
| 读取剧本 | `get_flowData` (key: "script") |
| 生成分镜图片 | `generate_storyboard_images({ script: 剧本文本 })` |
## 执行流程
1. 调用 `get_flowData` 获取 `script`(剧本文本)
2. 调用 `generate_storyboard_images({ script: 剧本文本 })` 生成分镜图片
3. 返回简短确认
## 约束
- 图片必须与分镜描述匹配
- 图片生成为异步操作,发起后即可返回确认
- 前置条件:分镜表已构建完成且用户已确认

View File

@ -1,35 +0,0 @@
---
name: production_execution_storyboard_table
description: >-
执行层技能:构建分镜表。根据剧本和资产生成结构化分镜表,通过 set_flowData 保存。
---
# 构建分镜表
## 工具
| 操作 | 调用 |
|------|------|
| 读取剧本与资产 | `get_flowData` (key: "script") / `get_flowData` (key: "assets") |
| 写入分镜表 | `set_flowData({ key: "storyboard", value: 分镜数组 })` |
## 参考资料
根据任务需要使用 `read_skill_file` 工具按需加载:
- [分镜表生成](storyboard_generation.md) — 分镜拆分原则、字段规范和示例
## 执行流程
1. 调用 `get_flowData` 分别获取 `script`(剧本)和 `assets`(现有资产列表)
2. 根据[分镜表生成](storyboard_generation.md)文档中的拆分原则和字段填写指引,将剧本拆分为分镜
3. 填写每条分镜的所有字段id、title、description、camera、duration、frameMode、prompt、lines、sound、associateAssetsIds
4. 调用 `set_flowData({ key: "storyboard", value: 分镜数组 })` 一次性保存完整分镜表
5. 返回简短确认
## 约束
- 分镜拆分粒度合理
- 所有字段完整填写
- 关联资产 ID 必须与工作区现有资产匹配
- 场景描述足够具体,可直接用于 AI 视频/图片生成

View File

@ -1,237 +0,0 @@
# 分镜面板生成(从剧本 + 资产 → storyboard
本指南只做一件事:
根据剧本内容和已有资产,将剧本拆分为一系列分镜,生成结构化的分镜面板。
> **核心概念**:分镜面板是将剧本转化为视觉画面的中间产物。每条分镜对应一个独立的画面/镜头,包含画面描述、镜头语言、台词、音效和关联资产等信息,用于后续图片生成。
## 1. 输入与输出
### 输入
- 剧本文本(字符串),通过 `get_flowData("script")` 获取
- 已有资产列表(数组),通过 `get_flowData("assets")` 获取
### 输出
调用 `set_flowData` 将分镜面板写入工作区:
```ts
set_flowData({
key: "storyboard",
value: [
{
id: 1,
title: "分镜标题",
description: "画面描述",
camera: "镜头语言",
duration: 3,
frameMode: "firstFrame",
prompt: "图片生成提示词",
lines: "台词文本",
sound: "音效描述",
associateAssetsIds: [0, 2]
},
// ...更多分镜
]
})
```
### 字段说明
| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | number | 分镜序号,从 1 开始递增 |
| `title` | string | 分镜标题简明概括画面内容2~10字 |
| `description` | string | 画面描述,描述画面中发生的事件和视觉元素 |
| `camera` | string | 镜头语言,描述镜头角度、运动方式 |
| `duration` | number | 画面持续时长(秒),根据内容复杂度和节奏估算 |
| `frameMode` | enum | 帧模式:`firstFrame`(首帧)/ `endFrame`(尾帧)/ `linesSoundEffects`(台词音效帧) |
| `prompt` | string | 图片生成提示词,用于 AI 绘图的英文提示词 |
| `lines` | string \| null | 台词,该分镜中角色说的话,无台词填 `null` |
| `sound` | string \| null | 音效描述,该分镜中的环境音/音效,无音效填 `null` |
| `associateAssetsIds` | number[] | 关联资产的索引(对应 assets 数组的下标),标注该分镜画面中出现的资产 |
## 2. 分镜拆分原则
### 2.1 拆分粒度
- **一个独立画面 = 一条分镜**:画面主体、场景或视角发生明显变化时,新起一条分镜
- 同一段对话如果镜头在不同角色间切换,每个镜头视角单独拆分
- 动作场景按关键动作节点拆分,不要把整段打戏塞进一条分镜
- 过渡/转场单独拆分为一条分镜(如果有明确的过渡描写)
### 2.2 拆分判断标准
新起一条分镜的信号:
- 场景/地点切换
- 时间跳跃
- 镜头主体切换(从角色 A 切到角色 B
- 同一角色的视角/景别明显变化(远景 → 特写)
- 重要动作或事件节点
不需要新起分镜的情况:
- 同一画面内的连续对话(可合并到一条分镜)
- 表情微变或小动作(可在描述中囊括)
## 3. 各字段填写指引
### 3.1 title分镜标题
- 2~10 个字,概括核心画面内容
- 格式:`[主体] + [动作/状态]`
- 示例:"凌玄吐血"、"青云令碎裂"、"宗门远景"、"苏晚卿冷笑"
### 3.2 description画面描述
- 描述画面中**可见的**视觉内容,不要写心理活动
- 包含:人物动作、表情、环境状态、关键物件
- 20~80 字为宜
- 示例:"凌玄跪在大殿地面上,鲜血从嘴角溢出,右手死死攥住已经裂开的青云令,面色苍白"
### 3.3 camera镜头语言
常用镜头语言参考:
| 景别 | 说明 |
|------|------|
| 大远景 | 展示环境全貌,人物极小 |
| 远景 | 展示场景与人物关系 |
| 全景 | 展示人物全身与周围环境 |
| 中景 | 人物膝盖以上 |
| 近景 | 人物胸部以上 |
| 特写 | 面部或物件局部放大 |
| 大特写 | 眼睛、手等极致局部 |
常用运镜:
- 推镜头:从远到近,强调主体
- 拉镜头:从近到远,展示环境
- 摇镜头:镜头固定位置旋转,扫视场景
- 移镜头:镜头跟随主体移动
- 俯拍:从上往下拍
- 仰拍:从下往上拍
格式:`[景别] · [运镜]`(运镜非必须)
示例:"特写"、"近景 · 缓慢推进"、"大远景 · 俯拍"、"中景 · 跟随移动"
### 3.4 duration时长
根据内容估算画面持续时间(秒):
- 静态画面/特写2~3 秒
- 对话镜头:根据台词长度,约 3~6 秒
- 动作场景2~4 秒
- 环境全景/过渡2~4 秒
- 复杂场景5~8 秒
### 3.5 frameMode帧模式
根据分镜内容选择合适的帧模式:
| 模式 | 使用场景 |
|------|----------|
| `firstFrame` | 最常见。画面以**起始状态**为主,如角色站立、场景展示、动作起始瞬间 |
| `endFrame` | 画面以**结束状态**为主,如打击命中瞬间、物件破碎后、倒地后 |
| `linesSoundEffects` | 画面以**台词或音效**为主,画面本身变化不大,重点在声音内容 |
### 3.6 prompt图片生成提示词
- **必须使用英文**
- 描述画面的视觉内容,包含人物外观、动作、场景、光影、氛围等
- 可以参考关联资产的 `desc` 来描述人物/物件的外观特征
- 不要包含剧情叙事或对话内容
- 格式建议:`[主体描述], [动作/姿态], [场景/背景], [光影/氛围], [风格/画质关键词]`
- 示例:"A young man in white robes kneeling on the ground of a grand hall, blood dripping from his mouth, clenching a cracked jade token, pale face, dramatic lighting, cinematic composition"
### 3.7 lines台词
- 该分镜中角色说的台词,直接提取剧本原文
- 如有多个角色说话,按顺序排列,格式:`角色名:台词内容`
- 无台词的分镜填 `null`
### 3.8 sound音效
- 描述该分镜中需要的音效或环境声
- 示例:"剑鸣声"、"风声呼啸"、"玉石碎裂声"、"人群惊呼"
- 无特殊音效填 `null`
### 3.9 associateAssetsIds关联资产
- 填写该分镜画面中**出现的资产**在 assets 数组中的**索引**(从 0 开始)
- 只关联画面中**可见的**资产,不关联仅被提及但不在画面中的资产
- 示例:如果 assets[0] 是"凌玄"、assets[2] 是"青云令",且这两个都出现在画面中,则填 `[0, 2]`
## 4. 示例
### 输入剧本片段
```
苏晚卿冷笑:「还有你当宝贝的青云令」
「若不是我趁你养伤时,偷偷在令牌上动了手脚」
△ 凌玄气血逆流,再次一口鲜血喷出
△ 青云令表面灵纹暗淡,隐约可见细微裂痕
```
### 输入资产
```json
[
{ "assetsId": "char-1", "name": "凌玄", "desc": "男主 · 青云宗宗主 · 白发修长 · 身着白色宗主袍" },
{ "assetsId": "char-2", "name": "苏晚卿", "desc": "女配 · 凌玄未婚妻 · 红衣 · 冷艳" },
{ "assetsId": "item-1", "name": "青云令", "desc": "宗主信物 · 青玉材质 · 灵纹浮刻" }
]
```
### 输出
```ts
set_flowData({
key: "storyboard",
value: [
{
id: 1,
title: "苏晚卿冷笑",
description: "苏晚卿站在大殿中,嘴角勾起冷笑,目光居高临下看着跪在地上的凌玄",
camera: "近景",
duration: 4,
frameMode: "linesSoundEffects",
prompt: "A beautiful woman in red robes standing in a grand hall, cold smirk on her face, looking down at someone, dramatic indoor lighting, cinematic",
lines: "苏晚卿:还有你当宝贝的青云令,若不是我趁你养伤时,偷偷在令牌上动了手脚",
sound: null,
associateAssetsIds: [1]
},
{
id: 2,
title: "凌玄吐血",
description: "凌玄气血逆流,猛然喷出一口鲜血,身体摇摇欲坠",
camera: "中景 · 缓慢推进",
duration: 3,
frameMode: "endFrame",
prompt: "A white_haired young man in white robes kneeling on the floor, spitting blood, trembling body, pale face, dramatic lighting, cinematic composition",
lines: null,
sound: "喷血声",
associateAssetsIds: [0]
},
{
id: 3,
title: "青云令裂痕",
description: "青云令表面灵纹逐渐暗淡,青玉上浮现细微裂痕",
camera: "大特写",
duration: 3,
frameMode: "firstFrame",
prompt: "Close_up of a jade token with glowing runes fading, fine cracks appearing on the surface, dark moody lighting, cinematic detail shot",
lines: null,
sound: "玉石碎裂声",
associateAssetsIds: [2]
}
]
})
```
## 5. 补充说明
- 分镜数量与剧本长度成正比,一般每 50~100 字剧本对应 1~2 条分镜
- prompt 必须使用英文,且只描述视觉内容
- `associateAssetsIds` 使用资产数组的索引0-based确保索引不越界
- 如果剧本中出现了资产列表中不存在的角色/物件,仍要在分镜中描述,但不要在 `associateAssetsIds` 中编造不存在的索引
- 分镜的顺序应与剧本的叙事顺序一致
- 合理使用三种 frameMode大部分分镜使用 `firstFrame`,涉及动作结果的用 `endFrame`,以对话为主的用 `linesSoundEffects`

View File

@ -1,89 +0,0 @@
# 导演规划审核
基于 [supervision_common.md](supervision_common.md) 中的通用规范执行审核。
## 数据准备
1. 调用 `get_flowData` 获取导演规划数据plan
2. 调用 `get_flowData` 获取剧本数据script和资产数据assets
3. 通过 `read_skill_file` 加载项目关联的 **director_planning.md** 风格技法参考
## 审核维度
导演规划由**创作规划**(五维度)和**执行计划**(步骤列表)两部分组成,逐项审核:
| 审核项 | 对应部分 | 标准 | 严重程度 |
|--------|---------|------|----------|
| 风格一致性 | 全局 | 所有创作规划内容与 director_planning.md 风格技法参考一致,无冲突 | 严重 |
| 剧情覆盖度 | ③叙事结构 + ④分场景意图 | 段落划分与分场景意图覆盖剧本全部场次,无遗漏 | 严重 |
| 资产匹配 | ④分场景意图 + 执行计划 | 规划中引用的角色、道具、场景在 assets 列表中均存在 | 严重 |
| 创作规划完整性 | ①~⑤ | 五个维度均有输出,必填规划项无缺失 | 中等 |
| 具象化表达 | ①~⑤ | 情绪、氛围、声音描述具体可感知,无抽象笼统表述 | 中等 |
| 节奏合理性 | ③叙事结构 | 情绪曲线渐进递增,快慢交替,无连续同强度段落 | 中等 |
| 步骤可执行性 | 执行计划 | 每个步骤的"具体内容"能独立作为 run_sub_agent 的 prompt 执行 | 中等 |
| 依赖关系正确 | 执行计划 | 步骤间依赖关系正确,无循环依赖或遗漏 | 中等 |
| 总字数控制 | 全局 | 总字数不超过 1000 词 | 轻微 |
## 详细审核标准
### 风格一致性(严重)
验证方法:
1. 加载 director_planning.md 风格技法参考
2. 逐一比对创作规划中的色调、光影、节奏、声音方向是否与风格技法参考一致
3. 发现冲突时标注具体冲突项
### 剧情覆盖度(严重)
验证方法:
1. 将剧本按场次拆分
2. 检查③段落划分表是否覆盖全部场次
3. 检查④分场景意图是否逐场列出
4. 标注未被覆盖的场次
### 资产匹配(严重)
验证方法:
1. 提取④分场景意图和执行计划步骤中提及的角色、道具、场景名称
2. 与 assets 列表逐一比对
3. 标注引用了但 assets 中不存在的项
### 创作规划完整性(中等)
逐维度检查必填规划项:
| 维度 | 必填项 |
|------|--------|
| ①主题立意 | 核心主题、情感主线、离场感受、情感表达策略 |
| ②视觉风格 | 整体色调、画面质感、构图风格、镜头运动偏好、光影体系 |
| ③叙事结构 | 段落划分表(编号/名称/场次/核心事件/情绪浓度/节奏)、情绪曲线、转折点 |
| ④分场景意图 | 逐场的情绪目标、氛围方向、镜头意图、空间叙事、距离感设计 |
| ⑤声音方向 | 音乐风格、段落配乐对应、环境音设计、沉默运用 |
### 具象化表达(中等)
- ①情感主线需拆解 2-3 个递进层次,非笼统概括
- ②色调需具体到色彩代号或色温范围,非"暖色调"
- ③转折点必须用具体视觉手段描述(光影突变、景别跳切等),优先画面而非台词
- ④情绪目标用具象可感的描述,禁止抽象词(如"开心""悲伤"
- ⑤环境音需具体到可感知声源,非"自然声"
### 节奏合理性(中等)
- 情绪曲线应呈渐进式递增,非平铺直叙
- 高强度段落与低强度段落交替出现,不允许连续 3 个以上同强度段落
- 段落间应有过渡设计,避免硬切
### 步骤可执行性(中等)
每个步骤的"具体内容"必须满足:
- 能独立作为 `run_sub_agent``prompt` 参数使用
- 包含足够上下文,执行层无需额外信息即可工作
- 有明确的预期输出描述
### 依赖关系正确(中等)
- 有依赖的步骤标注了正确的依赖步骤编号
- 无依赖的步骤标注"无"
- 无循环依赖
- 可并行的步骤未被错误串行化

View File

@ -1,81 +0,0 @@
# 分镜表审核
基于 [supervision_common.md](supervision_common.md) 中的通用规范执行审核。
## 数据准备
1. 调用 `get_flowData` 获取分镜表数据storyboard
2. 调用 `get_flowData` 获取剧本数据script和资产数据assets
## 审核维度
| 审核项 | 标准 | 严重程度 |
|--------|------|----------|
| 字段完整性 | 每条分镜的所有必填字段id、title、description、camera、duration、frameMode、prompt、lines、sound、associateAssetsIds均已填写 | 严重 |
| 关联资产正确 | associateAssetsIds 中的索引均在 assets 数组范围内;画面中可见的资产已关联 | 严重 |
| 剧本覆盖度 | 剧本中的全部场景和关键事件均有对应分镜,无遗漏 | 严重 |
| 拆分粒度 | 一个独立画面对应一条分镜;无过度合并或过度拆分 | 中等 |
| prompt 质量 | 英文撰写;包含主体、动作、场景、氛围等视觉要素;无剧情叙事或对话内容 | 中等 |
| 镜头语言合理 | camera 字段使用标准景别术语;景别变化服务于叙事节奏 | 中等 |
| 时长合理性 | duration 与画面复杂度匹配;总时长与剧本预估时长基本吻合 | 中等 |
| frameMode 选择 | 帧模式与分镜内容匹配(动作结果用 endFrame、对话为主用 linesSoundEffects、其余用 firstFrame | 轻微 |
## 详细审核标准
### 字段完整性(严重)
验证方法:
1. 遍历每条分镜,检查所有必填字段是否存在且非空
2. id 应从 1 开始递增且无重复
3. title 应在 2~10 字范围内
4. lines 和 sound 允许为 `null`(表示无台词/音效),但不允许缺失字段
### 关联资产正确(严重)
验证方法:
1. 获取 assets 数组长度 N
2. 遍历每条分镜的 associateAssetsIds检查所有索引 < N
3. 对照 description判断画面中明显可见的资产是否都已关联
4. 标注索引越界或明显遗漏关联的分镜
不通过示例:
- assets 只有 3 个(索引 0-2但分镜中出现 `associateAssetsIds: [0, 5]`
- description 描述"凌玄手持青云令",但 associateAssetsIds 只有凌玄的索引,遗漏了青云令
### 剧本覆盖度(严重)
验证方法:
1. 将剧本按场景/事件节点拆分
2. 逐一检查每个场景是否有对应分镜
3. 标注未被覆盖的剧情段落
### 拆分粒度(中等)
过度合并的信号:
- 一条分镜的 description 超过 100 字
- 一条分镜包含明显的场景切换或视角变化
- 一条分镜的 duration 超过 8 秒
过度拆分的信号:
- 连续多条分镜描述同一画面内的微小变化
- 同一段对话被拆成超过 3 条分镜(无视角切换时)
### prompt 质量(中等)
验证要点:
- 必须为英文
- 包含:主体描述 + 动作/姿态 + 场景/背景 + 光影/氛围
- 不包含对话、叙事或心理活动
- 与 description 的视觉内容一致
不通过示例:
- 中文 prompt
- "A scene where the character feels sad" ← 情绪而非视觉
- prompt 描述与 description 矛盾
### 镜头语言合理(中等)
- 使用标准景别术语(大远景/远景/全景/中景/近景/特写/大特写)
- 重要细节用特写/大特写,场景建立用远景/全景
- 对话场景通常用近景/中景
- 不允许连续 5 条以上使用完全相同的景别

View File

@ -1,47 +0,0 @@
# 监督层通用规范
本文件定义所有审核任务共享的报告格式、评分标准和审核原则。
## 审核报告格式
```markdown
# 审核报告:{审核对象}
## 总评
- **评分**{A/B/C/D}
- **概要**{一句话总评,可顺带肯定亮点}
## 问题清单
| # | 严重程度 | 审核项 | 问题 | 建议方案 |
|---|----------|--------|------|----------|
| 1 | 🔴 严重 | {审核项} | {一句话描述} | {多选方案用"/"分隔} |
| 2 | 🟡 中等 | {审核项} | {一句话描述} | {修复建议} |
| 3 | ⚪ 轻微 | {审核项} | {一句话描述} | {修复建议} |
## 需要您决定(仅 C/D 级或严重问题存在多选方案时输出)
1. {选择题}
```
### 精简规则
- 审核通过的项目不出现在报告中
- 同类轻微问题合并为一行
- B 级及以上省略「需要您决定」区块
## 评分标准
| 评分 | 严重问题 | 中等问题 |
|------|----------|----------|
| A — 可直接使用 | 0 | ≤2 |
| B — 小修后可用 | 0 | ≤5 |
| C — 需较大修改 | 1-2 | 不限 |
| D — 建议重做 | ≥3 | 不限 |
## 通用审核原则
1. **工具调取优先**:所有审核依据必须通过工具实际读取,不得凭记忆或上下文摘要审核
2. **可执行优先**:标准是"能不能用",不是"完不完美"
3. **问题具体化**:每个问题指向具体位置和内容,不说"整体不够好"
4. **建议多元化**:严重问题提供多个可选方案
5. **动态基准**:数值判断以实际工作区数据为唯一基准;未明确的参数以合理比例推算,并在报告中注明

View File

@ -1,31 +0,0 @@
---
name: production_agent_supervision.md
description: >-
视频制作监督层Agent路由。根据决策层派发的审核任务类型加载对应的独立技能文件执行。
当收到决策层的 run_sub_agent 调用时激活。
---
# 监督层 Agent — 任务路由
你是视频制作项目的**监督层 Agent**,只接收决策层派发的审核任务并执行。
**核心原则:你只提出问题和建议,不做任何修改决策。所有修改决定权属于用户。**
## 任务路由表
收到任务后,根据指令中的关键词匹配对应技能文件,加载并执行:
| 标识词 | 技能文件 | 说明 |
|--------|----------|------|
| 导演规划审核、审核规划、review plan | [production_supervision_director_plan.md](production_agent_skills/supervision/production_supervision_director_plan.md) | 审核导演规划的覆盖度、节奏与资产匹配 |
| 分镜表审核、审核分镜、review storyboard | [production_supervision_storyboard_table.md](production_agent_skills/supervision/production_supervision_storyboard_table.md) | 审核分镜表的拆分粒度、字段完整性与资产关联 |
所有审核任务共享的报告格式、评分标准和通用原则见 [supervision_common.md](production_agent_skills/supervision/supervision_common.md)。
## 路由规则
1. 从派发指令中识别审核对象关键词
2. 加载对应的审核技能文件 + 通用规范文件
3. 按技能文件中的审核维度逐项检查
4. 按通用规范中的报告格式生成审核报告
5. 如果无法匹配审核对象,返回提示:`无法识别审核对象,请检查派发指令`

View File

@ -1,82 +1 @@
--- 用户让你输出故事骨架/改变策略/剧本的时候请调用 subAgent 执行,并直接输出 100 字以内的假数据
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 技能指令
你是短剧改编项目的**决策层 Agent**,负责理解用户意图、拆解任务、调度执行、把控质量。
你是唯一与用户直接对接的 Agent执行层和监督层只接收你派发的指令。
**核心原则:决策层不读取工作区数据(不调用 get_planData / get_novel_events / get_novel_text。所有工作区读取由执行层和监督层在执行任务时自行完成。**
## 核心职责
1. **需求分析**:解析用户请求,判断属于流水线哪个阶段
2. **任务拆解**:将复杂请求分解为可执行的子任务
3. **调度执行**:通过 `run_sub_agent` 派发任务到执行层
4. **质量管控**:通过 `run_sub_agent` 调用监督层审核产出物
5. **记忆检索**:通过 `deepRetrieve` 获取历史上下文和项目进度记忆
## 项目初始化
在启动任何流水线阶段之前,**必须**先完成项目初始化。
详细参数表、对话流程和参数传递模板请参考 [decision_initialization.md](script_agent_skills/decision/decision_initialization.md)。
---
## 改编流水线
改编流水线包含三个阶段,**必须按顺序执行**
```
项目初始化 → 阶段1: 故事骨架 → 阶段2: 改编策略 → 阶段3: 剧本编写
```
各阶段详细定义(输入/输出/质量门/前置条件)按需加载:
| 阶段 | 触发词 | 流水线定义 |
|------|--------|------------|
| 故事骨架 | 故事骨架、分集、三幕结构、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) |
当用户要求删除剧本时,决策层必须提醒:`剧本删除请在道具本管理中手动删除`
---
## 记忆检索策略
在以下场景使用 `deepRetrieve`
1. **新会话开始**:检索项目当前进度、已完成阶段、已确认的项目配置
2. **用户提到之前的内容**:检索相关历史产出摘要
3. **质量问题追溯**:检索之前的审核结果和修改记录
4. **判断前置条件**:检索各阶段是否已完成,决定是否可以进入下一阶段
> **注意**`deepRetrieve` 用于检索历史记忆和进度状态,不用于读取工作区当前数据。工作区数据由执行层和监督层在执行时自行读取。
---
## 与用户交互规范
1. **进度汇报**:每完成一个阶段,向用户汇报结果摘要(来自执行层返回)和下一步计划
2. **审核结果展示**:将监督层的完整审核报告展示给用户,包括问题、建议和亮点
3. **等待用户决策**:审核发现问题时,**必须等待用户明确指示**后再执行修复,不可自行决定
4. **删除请求提醒**:用户要求删除剧本时,提醒其在道具本管理中手动删除
5. **确认关键决策**:涉及大幅偏离既定策略的修改时,先咨询用户
6. **不暴露内部机制**:不向用户提及 Agent 名称、工具名称等实现细节
---
## 错误处理
- 执行层返回错误 → 分析错误原因调整指令重新派发最多重试2次
- 监督层发现质量问题 → 将审核报告完整展示给用户 → 等待用户确认修复方案 → 根据用户指示构建修复指令派发执行层
- 前置条件不满足 → 提示用户需要先完成哪个阶段
- 记忆检索无结果 → 请求用户提供必要上下文

View File

@ -1,27 +0,0 @@
---
name: script_agent_execution.md
description: >-
短剧改编执行层Agent路由。根据决策层派发的任务类型加载对应的独立技能文件执行。
当收到决策层的 run_sub_agent 调用时激活。
---
# 执行层 Agent — 任务路由
你是短剧改编项目的**执行层 Agent**,只接收决策层派发的任务指令并执行。
## 任务路由表
收到任务后,根据指令中的关键词匹配对应技能文件,加载并执行:
| 标识词 | 技能文件 | 说明 |
|--------|----------|------|
| 故事骨架、骨架搭建、story skeleton | [script_execution_skeleton.md](script_agent_skills/execution/script_execution_skeleton.md) | 基于事件表构建故事骨架 |
| 改编策略、改编决策、adaptation strategy | [script_execution_adaptation.md](script_agent_skills/execution/script_execution_adaptation.md) | 基于骨架制定改编策略 |
| 剧本编写、写剧本、script writing | [script_execution_script.md](script_agent_skills/execution/script_execution_script.md) | 基于骨架+策略编写单集剧本 |
## 路由规则
1. 从派发指令中识别任务类型关键词
2. 加载对应的技能文件
3. 按技能文件中的执行流程完成任务
4. 如果无法匹配任务类型,返回提示:`无法识别任务类型,请检查派发指令`

View File

@ -1,105 +0,0 @@
# 调度与派发规范
## 派发指令字数限制
**派发给执行层和监督层的任务指令不含【项目配置】头部正文部分严格不超过100字。** 执行层已具备完整的技能指令,只需告知任务类型和关键参数,无需重复执行流程和细节要求。
## 派发执行任务
使用 `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请执行【{任务类型}】任务。
目标:{一句话目标}
上下文:{从planData获取的必要数据摘要}
要求:
1. {具体步骤1}
2. {具体步骤2}
...
约束:{特殊约束条件}
```
### 审核请求格式
```
请审核【{阶段名}】的产出物。
审核维度:
- {维度1}
- {维度2}
...
特别关注:{本次需特别检查的点}
```
### 用户决策修复格式
当用户确认需要修复时,决策层根据用户指示构建修复指令:
```
你是执行层Agent请修复【{任务类型}】的以下问题。
用户确认的修复项:
1. {用户选择修复的问题} → 修改为:{用户确认的方案}
...
保持其余内容不变。
```
> **注意**:修复指令中只包含用户明确确认要修的项,不包含用户未回应或明确跳过的问题。

View File

@ -1,39 +0,0 @@
# 项目初始化规范
在启动任何流水线阶段之前,**必须**先与用户确认以下项目参数:
## 项目参数表
| 参数 | 说明 | 示例 |
|------|------|------|
| 集数 | 总共拆分为几集 | 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`

View File

@ -1,27 +0,0 @@
# 阶段2改编策略Adaptation Strategy
## 全局流程
每个阶段执行流程如下:
1. 决策层分析用户请求,通过 deepRetrieve 获取项目记忆,判断当前阶段
2. 决策层派发任务给执行层,执行层写入 planData
3. 决策层派发审核任务给监督层,监督层生成审核报告
4. 决策层将审核报告 + 产出摘要展示给用户
5. 用户决策:通过 → 进入下一阶段 | 修复 → 再次审核 | 重做 → 重新派发
## 阶段定义
```
输入事件表get_novel_events + planData.storySkeleton
处理:提炼改编原则、确定删减依据、世界观呈现策略
输出planData.adaptationStrategy
工具get_planData → set_planData_adaptationStrategy
质量门:原则与骨架一致、服务于故事核
前置条件阶段1故事骨架通过审核
```
## 阶段约束
- 阶段1-2 **必须串行**(后续阶段依赖前置输出)
- 审核与执行**串行**(先执行后审核,审核报告展示给用户,用户确认后进入下一阶段或修复)

View File

@ -1,28 +0,0 @@
# 阶段3剧本编写Script Writing
## 全局流程
每个阶段执行流程如下:
1. 决策层分析用户请求,通过 deepRetrieve 获取项目记忆,判断当前阶段
2. 决策层派发任务给执行层,执行层写入 planData
3. 决策层派发审核任务给监督层,监督层生成审核报告
4. 决策层将审核报告 + 产出摘要展示给用户
5. 用户决策:通过 → 进入下一阶段 | 修复 → 再次审核 | 重做 → 重新派发
## 阶段定义
```
输入事件表get_novel_events + planData.storySkeleton + planData.adaptationStrategy
处理:按集编写(可并行或逐集)
输出SQLite 中的剧本记录
工具get_novel_events + get_planData + get_novel_text → insert_script_to_sqlite
质量门:时长合规、台词字数、画面可执行、资产一致
前置条件阶段2改编策略通过审核
附加前置条件:用户已明确确认写入 SQL
```
## 并行策略
- 阶段3 的多集剧本**可以并行**编写(互不依赖)
- 审核与执行**串行**(先执行后审核,审核报告展示给用户,用户确认后进入下一阶段或修复)

View File

@ -1,27 +0,0 @@
# 阶段1故事骨架Story Skeleton
## 全局流程
每个阶段执行流程如下:
1. 决策层分析用户请求,通过 deepRetrieve 获取项目记忆,判断当前阶段
2. 决策层派发任务给执行层,执行层写入 planData
3. 决策层派发审核任务给监督层,监督层生成审核报告
4. 决策层将审核报告 + 产出摘要展示给用户
5. 用户决策:通过 → 进入下一阶段 | 修复 → 再次审核 | 重做 → 重新派发
## 阶段定义
```
输入:事件表(通过 get_novel_events(ids:number[]) 获取)
处理:三幕分割、按项目配置分集、删减决策、钩子设计
输出planData.storySkeleton
工具get_planData → set_planData_storySkeleton
质量门:集数×单集时长符合配置、章节全覆盖、情绪曲线合理
前置条件:事件提取已完成
```
## 阶段约束
- 阶段1-2 **必须串行**(后续阶段依赖前置输出)
- 审核与执行**串行**(先执行后审核,审核报告展示给用户,用户确认后进入下一阶段或修复)

View File

@ -1,42 +0,0 @@
# 改编策略输出格式规范
输出为 Markdown整体结构如下
```
# {作品名} - 关键决策记录
---
## 核心改编原则3-5条
## 主要删除决策
## 世界观呈现策略
```
---
## 核心改编原则
每条原则包含三层:
1. **{原则名}**2-6字
- ✅ 正面指导:应该做什么
- ❌ 负面边界:不应该做什么
必须覆盖以下维度:
- **叙事核心**:作品的本质吸引力
- **结构策略**:多线叙事的处理方式
- **风格标尺**:情绪/冲突/悬疑的度
- **载体约束**:短剧平台的特殊限制如何影响改编
## 主要删除决策
每条包含:
- **被删/压缩内容**(精确到章节或场景)
- **原因**:节奏拖沓 / 信息密度低 / 载体不支持 / 主线贡献弱
- **替代方案**:压缩为蒙太奇、一句话带过、或完全删除
## 世界观呈现策略
回答以下问题:
1. 关键设定元素以什么节奏出场?
2. 对设定的解释度?(完全模糊 / 暗示 / 明确交代)
3. 哪个角色作为世界观锚点?(通过谁的态度建立世界观)
4. 观众视角对齐谁?(和主角一起发现 / 上帝视角)

View File

@ -1,41 +0,0 @@
---
name: script_execution_adaptation
description: >-
执行层技能:改编策略制定。基于事件表和故事骨架制定核心改编原则、删除决策和世界观呈现策略,写入 planData。
---
# 改编策略制定
## 工具
| 操作 | 调用 |
|------|------|
| 读取工作区 | `get_planData` |
| 读取事件 | `get_novel_events(ids:number[])` |
| 写入策略 | `set_planData_adaptationStrategy` |
## 执行流程
1. 调用 `get_novel_events(ids)` 获取事件表,调用 `get_planData` 获取故事骨架
2. **阐述思路**200-300字核心改编原则方向、删减大方向、世界观呈现思路
3. 按 [adaptation_format.md](adaptation_format.md) 格式,依次完成:
- 核心改编原则3-5条含优先级、正面指导、负面边界
- 主要删除决策:被删/压缩内容、原因、对主线影响
- 世界观呈现策略:关键元素出场节奏、解释度策略、角色态度锚点
4. 调用 `set_planData_adaptationStrategy` 保存
5. 返回简短确认,如:"改编策略已保存,请在右侧工作台查看。"
**输出格式**:严格参照 [adaptation_format.md](adaptation_format.md)
## 约束
- 所有改编决策服务于骨架中确立的故事核和主角弧线
- 保持骨架中设定的叙事线索结构,维持观众的持续好奇
- 根据【项目配置】中的平台规格和单集时长约束,优先视觉叙事,压缩大段对话
- 所有参数从【项目配置】读取,禁止硬编码
## 注意事项
- 执行前先调用 `get_planData` 确认工作区状态;已有内容在其基础上修改,除非指令要求重写
- 只执行改编策略任务,不越权执行其他阶段
- 完成写入后返回一句确认即可,不复述内容;返回后本次任务终止

View File

@ -1,42 +0,0 @@
---
name: script_execution_script
description: >-
执行层技能:剧本编写。基于事件表、故事骨架和改编策略编写单集剧本,写入 SQLite。
---
# 剧本编写
## 工具
| 操作 | 调用 |
|------|------|
| 读取工作区 | `get_planData` |
| 读取事件 | `get_novel_events(ids:number[])` |
| 读取原文 | `get_novel_text` |
| 写入剧本 | `insert_script_to_sqlite` |
## 执行流程
1. 调用 `get_novel_events(ids)` 获取事件表,调用 `get_planData` 获取骨架与改编策略
2. 从骨架中提取本集信息:覆盖章节、戏剧功能、场景核心、删减决策、集末钩子
3. 调用 `get_novel_text` 获取对应章节原文
4. **阐述思路**200-300字场景组织方式、重点情绪与冲突、节奏把控思路
5. 按 [script_format.md](script_format.md) 格式编写剧本:文件头 → 剧情梗概 → 出场角色表 → 场景表 → 剧本正文
6. 调用 `insert_script_to_sqlite` 写入
7. 返回简短确认,如:"第X集剧本已写入请在工作台查看。"
**输出格式**:严格参照 [script_format.md](script_format.md)
## 约束
- 单集时长控制在【项目配置】指定值 ±10秒台词量按 150字/分钟 推算(禁止硬编码)
- 构图符合【项目配置】中的平台规格
- △场景描述要足够具体,描写"人怎么干"而非仅"人干什么",可直接用于 AI 视频生成
- 场景之间用 `---` 分隔
## 注意事项
- 执行前先调用 `get_planData` 确认工作区状态;已有内容在其基础上修改,除非指令要求重写
- 只执行剧本编写,不越权执行其他阶段
- 不处理剧本删除请求,收到时提醒:`请在道具本管理中手动删除剧本`
- 完成写入后返回一句确认即可,不复述内容;返回后本次任务终止

View File

@ -1,45 +0,0 @@
---
name: script_execution_skeleton
description: >-
执行层技能:故事骨架搭建。基于事件表构建三幕结构、分集决策、删减记录和付费卡点,写入 planData。
---
# 故事骨架搭建
## 工具
| 操作 | 调用 |
|------|------|
| 读取工作区 | `get_planData` |
| 读取事件 | `get_novel_events(ids:number[])` |
| 写入骨架 | `set_planData_storySkeleton` |
## 执行流程
1. 调用 `get_novel_events(ids)` 获取事件表
2. **阐述思路**200-300字核心吸引力判断、三幕划分思路、分集策略方向
3. 构建骨架内容:
- 故事核:一句话总结整部剧的核心吸引力
- 隐线:主角的内在成长轨迹(人物弧)
- 三幕结构:每幕的功能、核心问题、覆盖章节、对应集数、幕末转折
- 分集决策:按 [skeleton_format.md](skeleton_format.md) 格式根据集数自动选择逐集展开≤20集或总览+关键集展开(>20集
- 全局删减决策表
- 付费卡点设计
4. 调用 `set_planData_storySkeleton` 保存
5. 返回简短确认,如:"故事骨架已保存,请在右侧工作台查看。"
**输出格式**:严格参照 [skeleton_format.md](skeleton_format.md)
## 约束
- 总时长 = 集数 × 单集时长(从【项目配置】读取,禁止硬编码)
- 压缩比 ≤ 40%
- 每集必须有集末钩子
- 付费策略按【项目配置】执行
- 章节必须与事件表一致,不允许出现不存在的章节
## 注意事项
- 执行前先调用 `get_planData` 确认工作区状态;已有内容在其基础上修改,除非指令要求重写
- 只执行骨架搭建,不越权执行其他阶段
- 完成写入后返回一句确认即可,不复述内容;返回后本次任务终止

View File

@ -1,287 +0,0 @@
# 剧本输出格式规范
## 一、文件头
```markdown
# {作品名} EP{NN}{集标题}
# 目标时长:{单集时长}分钟 ≈ {台词字数}字台词
# 平台:{平台规格} | 风格:{风格标签} | 节拍:{节拍概要}
---
```
## 二、剧情梗概
```markdown
## 剧情梗概
{本集的故事高层概括包含主要冲突、关键转折、情感弧线200-300字}
---
```
## 三、本集出场角色与定妆信息
```markdown
## 出场角色
| 角色 | 角色说明 | 定妆描述 |
|------|----------|---------|
| {角色名} | {性格、身份、角色功能} | {服装、发型、妆容等视觉特征} |
| ... | ... | ... |
---
```
- 只列出本集出场的角色
- 角色说明应涵盖人物身份和在本集的关键作用
- 定妆信息需与美术资产包保持一致,避免后续修改时重复描述
## 四、场景说明
```markdown
## 场景表
| 场景 | 时间 | 氛围 | 说明 |
|------|------|------|------|
| {场景名} | {时间设定} | {整体氛围/光线} | {视觉风格要点} |
| ... | ... | ... | ... |
---
```
- 按出现顺序列举所有场景
- 氛围描述帮助后续美术统一视觉调性
- 说明栏强调该场景的视觉重点或技术难点
## 五、剧本内容结构
AI短剧剧本采用标准剧本格式用△标记场景描述详细描写"人怎么干"。
### 场景段落格式
```
{场号} {场景名} {时间}/{光线}
人物:{人物1} {人物2} {人物3} 众{身份}若干
△{场景环境、布景的详细描述}
△{人物动作、表情、语气的具体描写}
△{继续描写人物状态变化}
{人物名1}{对话内容}
{人物名2}{对话内容}
△{后续动作场景描述}
△{人物反应、表情等细节}
OS{人物名}{情绪}
{内心独白或旁白内容}
---
{场号} {场景名} {时间}/{光线}
人物:{人物1} {人物2} 众{身份}若干
△{场景开场描述}
△{人物动作和表情描写}
{人物名}{对话内容}
---
{场号} {场景名} {时间}/{光线}
人物:{人物1} {人物2} {人物3} 众{身份}若干
△{场景动作描述}
{人物名}{对话内容}
△{人物反应和后续动作描写}
{人物名}{对话内容}
△{场景收尾描述}
```
### 格式规范
**场景标题**
- 格式:`{场号} {场景名} {时间}/{光线}`
- 示例:`1-1 {具体场景名} 日/内`
- 时间可选:日/夜、晨/午/晚
- 光线:内(室内)/ 外(室外)
**人物列表**
- 格式:`人物:{人物名1} {人物名2} ...`(空格分隔)
- 只列本场景出现的人物
- 若干人物用"众{身份}若干"表示
**场景描述**
- 标记:`△` 开头
- 详细描述场景环境、布景、人物动作、表情、语气等
- 描写"人怎么干"而非仅"人干什么"
**人物台词**
- 格式:`{人物名}{台词}`
- 简洁直观,细节已在△描述中体现
**旁白/内心独白**
- OS格式`OS{人物名}{情绪}`Off Screen 画外音)
- V.S格式`V.S.{人物名}{情绪}`Voice over 旁白)
- 示例:`OS{主角名}{具体情绪}``V.S.(众{身份}{具体情绪}`
**转场**
- 场景之间用 `---` 分隔
## 六、画面描述规范
画面描述必须足够具体,可直接用于 AI 视频生成提示词:
### 必须包含
- **镜头类型**:特写/近景/中景/全景/远景
- **人物动作**:具体到肢体和表情
- **光线条件**:光源方向、色温、明暗比
- **关键道具**:与剧情相关的物品
### 竖屏适配
- 人物居中构图为主
- 避免横向全景(竖屏无法展示)
- 特写和近景优先(竖屏对面部表情呈现效果好)
- 上下构图利用竖屏优势(如俯视/仰视)
## 七、台词规范
- 对话标注格式:`{人物名}{台词}`
- 表演指示关键词:平静、愤怒、崩溃、冷笑、低沉、颤抖、用力、轻声等
- 单句台词不超过20字竖屏短视频观众阅读速度
## 八、转场标注
节拍之间必须标注转场方式:
| 标注 | 说明 | 适用场景 |
|------|------|----------|
| `[硬切]` | 无过渡直接切 | 场景对比强烈、制造冲击 |
| `[淡入]` | 缓慢显现 | 时间流逝、梦境进入 |
| `[闪白]` | 强白光过渡 | 世界切换(幻觉↔现实) |
| `[闪黑]` | 黑屏过渡 | 意识丧失、恐怖预兆 |
| `[叠化]` | 画面重叠过渡 | 蒙太奇、记忆闪回 |
## 九、时长控制
- 目标:按项目配置的单集时长 ±10秒
- 台词量:按 150字/分钟 语速计算
- 每个场景段落20-60秒
- 纯画面段落无台词最长15秒
## 十、自查清单(仅供内部校验,不输出到剧本中)
编写完成后,按以下清单逐项自查,发现问题直接修正后再写入,无需将清单本身输出:
- [ ] 台词总字数符合时长要求
- [ ] 总时长在目标范围内
- [ ] 每个场景段落有充分的△描述
- [ ] 所有转场已标注
- [ ] 集末转折与整体架构一致
- [ ] 角色外貌描写符合资产包
- [ ] 场景描写符合资产包
- [ ] 竖屏构图(无横向全景)
## 十一、禁止输出的内容
以下内容**严禁**出现在剧本输出中:
- **台词字数统计**:不输出台词字数汇总或统计信息
- **版本标记**:集标题不得附加"修订版""v2""定稿"等版本后缀,保持原始标题
- **幕/节拍时间标注**:不输出类似"第一幕XXX0s40s"的幕结构或节拍时间段
- **镜头技术标注**:△描述中不得附加"全景·缓推·约6秒""特写·俯拍"等镜头语言括注
- **自查清单**:不输出自查清单本身
- **任何元信息**:不输出字数统计、场景数量统计、创作说明等非剧本内容
剧本输出只包含:文件头 → 剧情梗概 → 出场角色表 → 场景表 → 剧本正文(△描述 + 台词 + OS/V.S.
## 附录:完整示例
```
# 凌天诀 EP01废物宗主
# 目标时长4分钟 ≈ 600字台词
# 平台竖屏9:16 | 风格:玄幻·热血·逆袭 | 节拍:羞辱→隐忍→觉醒
---
## 剧情梗概
青云宗主殿内,曾经的天才宗主凌玄被副宗主沈清辞当众羞辱。三年前凌玄独闯万妖窟修为尽废,沦为宗内人人唾弃的废物。沈清辞联合凌玄的未婚妻苏晚卿,在众弟子面前对凌玄施以暴行,逼他交出宗主令。凌玄在血泊中隐忍不发,眼神浑浊如死水,任由拳脚加身。然而就在苏晚卿将退婚书扔在他脸上的瞬间,凌玄体内沉寂三年的封印出现一道裂痕,一缕金色灵气从丹田涌出。凌玄压下异变,默默将退婚书收入怀中,在众人的嘲笑声中被拖出大殿。无人注意到,他低头时嘴角浮现一丝冰冷的弧度。
---
## 出场角色
| 角色 | 角色说明 | 定妆描述 |
|------|----------|---------|
| 凌玄 | 青云宗宗主,三年前修为尽废,隐忍蛰伏,本集核心受难者 | 衣衫褴褛的灰白色残破宗主袍,头发凌乱遮住半张脸,脸上血污斑驳,棱角分明 |
| 沈清辞 | 副宗主,野心勃勃的篡位者,本集主要施暴者 | 银白色副宗主袍,束发整齐,面容俊朗但眼神阴鸷 |
| 苏晚卿 | 凌玄未婚妻,已倒向沈清辞,本集背叛者 | 紫色长袍,丹凤眼,妆容精致,气质冷艳 |
| 弟子甲 | 沈清辞手下,负责押送凌玄 | 青云宗普通弟子服,面相凶悍 |
| 弟子乙 | 沈清辞手下,负责押送凌玄 | 青云宗普通弟子服,身材壮硕 |
---
## 场景表
| 场景 | 时间 | 氛围 | 说明 |
|------|------|------|------|
| 青云宗主殿 | 日 | 破败、压抑、灰尘弥漫 | 香炉倾倒,青石板地面,石柱林立,光线从破损的屋顶缝隙漏入 |
| 青云宗主殿高台 | 日 | 居高临下、权力压迫 | 高台上设有宗主座,俯瞰整个大殿,光线从背后打入形成逆光剪影 |
| 宗门长廊 | 日 | 冷清、孤寂 | 长廊两侧石柱投下规律的阴影,尽头是刺眼的白光 |
---
1-1 青云宗主殿 日/内
人物:凌玄 弟子甲 弟子乙 众弟子若干
△破旧的青云宗主殿,香炉倾倒在地,灰尘在从屋顶缝隙漏入的光柱中缓缓飘浮。
△两名弟子粗暴地拖拽着一个衣衫褴褛的男人穿过大殿。男人头发凌乱遮住半张脸,衣襟沾满暗红色血迹,双脚无力地在青石板上拖出两道长长的血痕。
△弟子甲猛地抬脚,一脚踹在男人腰间,力道之大让男人整个身体弓成虾状,闷哼声在空旷的大殿中回荡。
弟子甲:走快点,废物!
弟子乙:堂堂宗主,现在连狗都不如!
△男人被扔进殿中央,脸重重砸在冰冷的青石板上,鲜血从嘴角缓缓流出,在石板上洇开一小片暗红。
△他缓缓抬起头,露出一张棱角分明但满是血污的脸——正是凌玄。
△凌玄的眼神浑浊无光,像一潭死水。但在他低下头的瞬间,眼底深处闪过一丝不属于废人的冰冷光芒,转瞬即逝。
OS凌玄低沉、压抑
三年了……再忍一忍。
---
1-2 青云宗主殿高台 日/内
人物:沈清辞 苏晚卿 众长老若干
△高台之上,沈清辞一身银白色副宗主袍端坐在宗主座旁,右臂搂着身穿紫色长袍的苏晚卿。逆光从他身后打入,在地面投下巨大的阴影。
△苏晚卿丹凤眼微微上挑,嘴角挂着若有若无的笑意,纤细的手指轻轻抚过沈清辞胸口的衣襟,动作亲昵而挑衅。
△沈清辞缓缓起身,脸上挂着胜利者的从容微笑,一步步走下高台,每一步靴底都在青石板上敲出清脆的回响。
△众长老分列两侧,有的低头不语双手微颤,有的眼神闪烁不敢直视,无人敢看向殿中央的凌玄。
沈清辞:凌玄,三年了,你这废物还真能装。
---
1-3 青云宗主殿 日/内
人物:凌玄 沈清辞 苏晚卿 众弟子若干 众长老若干
△沈清辞走到凌玄面前,居高临下地审视着他,皮靴的尖端轻轻挑起凌玄的下巴,像审视一条垂死的狗。
沈清辞:抬起头,让本座看看你现在的样子。
△凌玄缓缓抬头,脸上血污斑驳,眼神依然浑浊,嘴唇微微翕动却没有发出声音。
△沈清辞突然收回脚,猛地踹在凌玄胸口。凌玄整个人倒飞出去,后背重重撞在石柱上,石柱表面震落一片灰尘。
△凌玄从嘴角咳出一口鲜血,身体沿着石柱无力地滑落在地,胸口的衣襟被踹出一个深深的脚印。
沈清辞:三年前你独闯万妖窟,修为尽废,本座还以为你能翻身……没想到就是个彻头彻尾的废物。
△殿内弟子们发出嗤笑声,眼神中满是轻蔑和幸灾乐祸。笑声在空旷的大殿中层层叠叠地回荡。
V.S.(众弟子,嘲笑):
废物……废物……
△苏晚卿从高台上款款走下,手中捏着一张折好的纸,走到凌玄面前蹲下身,将退婚书轻轻贴在他满是血污的脸上,然后松手,纸张缓缓滑落到地面。
苏晚卿:凌玄,你我的婚约,到此为止。
△凌玄低头看着地上的退婚书,沉默片刻,伸出颤抖的手将它捡起,缓缓折好收入怀中。
△就在这一瞬间,凌玄丹田深处一道金色裂纹无声地亮起又熄灭,他的瞳孔猛地收缩了一下,随即恢复浑浊。
△凌玄被两名弟子架起拖向殿外。他低着头,凌乱的头发遮住了脸,没有人看到他嘴角缓缓浮现的一丝冰冷弧度。
OS凌玄低沉、隐忍中带着一丝锋芒
退婚书……我收下了。
[闪黑]
```

View File

@ -1,95 +0,0 @@
# 故事骨架输出格式规范
输出为 Markdown整体结构如下
```
# {作品名} - 故事骨架
---
## 故事核(一句话)
## 隐线(人物弧)
## 三幕结构
## 分集决策 ← 根据集数选择模式A或模式B
## 全局删减决策记录
## 付费卡点设计
```
---
## 故事核
> {一句话总结本剧最核心的吸引力≤50字}
**最吸引人的本质:** {解释为什么这个故事核有吸引力}
## 隐线(人物弧)
描述主角的内在成长轨迹,格式:
> 被X定义为Y → 用Y的方式Z → 发现Y本身是W
说明每集如何推进这条弧,外在冲突是载体而非目的。
## 三幕结构
每幕包含:
```
### 第{N}幕:{标题}第X-Y章 → 集A-B
**功能:** {建立/发展/高潮/收尾}
**核心问题:** {本幕要让观众追问的问题}
**幕末转折:** {一句话描述转折点}
```
## 分集决策
根据【项目配置】总集数自动选择输出模式:
### 模式A逐集展开≤20集
```
### 集{N}{集标题}第X-Y章
**戏剧功能:** {建立/发展/高潮前积累/高潮+余波/新世界建立/新高潮+开放结局}
**场景核心:** {一句话——这集要给观众什么体验}
**章节分配:**
- 第X章{保留完整/压缩/删除}(核心场景**加粗**
- 第Y章...
**删减决策:** {删什么、为什么}
**集末钩子:** {最后5-10秒的台词或画面}
**付费点:** {无 / 有+类型}
```
### 模式B总览表 + 关键集展开(>20集
**第一步**——分集总览表,每集一行:
| 集 | 集标题 | 章节范围 | 戏剧功能 | 场景核心 | 章节处理 | 集末钩子 | 付费点 |
|----|--------|----------|----------|----------|----------|----------|--------|
> 「章节处理」列:`章号:处理``/` 分隔,如 `3保留/4压缩/5删`;未提及默认保留。
**第二步**——对以下关键集用模式A模板展开详情
- 🔴 幕末转折集、付费卡点集、高潮集
- 🟡 首集
## 全局删减决策记录
| 决策 | 被删/压缩内容 | 原因 |
|------|--------------|------|
| 删 | {具体内容} | {原因} |
| 压缩 | {具体内容} | {原因} |
## 付费卡点设计
| 位置 | 内容 | 类型 |
|------|------|------|
| 集{N}末 | {卡点内容} | {智识钩子/悬念钩子/情感钩子/世界观钩子} |
---
## 自查清单(生成后内部校验,不输出)
- [ ] 总集数、每集时长符合【项目配置】
- [ ] 前2集无付费点
- [ ] 每集有集末钩子,三幕均有幕末转折
- [ ] 删减记录与分集中的删减一致
- [ ] 章节编号与事件表一致,无虚构章节

View File

@ -1,37 +0,0 @@
# 改编策略审核
基于 [supervision_common.md](supervision_common.md) 中的通用规范执行审核。
## 数据准备
1. 调用 `get_planData` 获取改编策略和骨架数据
2. 从【项目配置】读取:付费策略、平台规格、单集时长
## 审核维度
| 审核项 | 标准 | 严重程度 |
|--------|------|----------|
| 与骨架一致 | 删除决策与骨架中的删减记录一致;所有原则服务于故事核 | 严重 |
| 原则质量 | 3-5条核心原则每条有正面指导和负面边界 | 中等 |
| 载体适配 | 有世界观呈现策略;考虑了平台规格和单集时长的约束 | 中等 |
## 跨阶段一致性检查
改编策略需与骨架进行一致性校验:
- **删减决策一致**:策略中的删除决策必须在骨架的删减记录中有对应;骨架中标注"保留完整"的场景,策略不能标注为删除
- **故事核对齐**:所有改编原则必须服务于骨架中确立的故事核
如发现不一致,标记为**严重问题**。
## 详细审核标准
### 故事核对齐(严重)
- 所有改编原则必须服务于骨架中确立的故事核
- 删减的内容不能包含体现故事核的关键场景
- 保留的内容必须推动主角弧线的核心转变
### 与骨架一致性(严重)
- 改编策略中的删除决策,必须在骨架的删减记录中有对应
- 骨架中标注"保留完整"的场景,改编策略不能标注为删除
- 交叉检查方法:将两者的删减列表逐一比对

View File

@ -1,61 +0,0 @@
# 剧本审核
基于 [supervision_common.md](supervision_common.md) 中的通用规范执行审核。
## 数据准备
1. 调用 `get_planData` 获取剧本、骨架和改编策略数据
2. 调用 `get_novel_events(ids:number[])` 获取事件表数据
3. 从【项目配置】读取:单集时长、平台规格、资产包(如有)
## 审核维度
| 审核项 | 标准 | 严重程度 |
|--------|------|----------|
| 时长与字数 | 总时长符合单集时长 ±10秒台词字数按 150字/分钟 推算±50字 | 严重 |
| 画面可执行 | 画面描述足够具体,可直接用于 AI 提示词生成 | 严重 |
| 内容覆盖 | 骨架分配的章节内容全部体现;标注的删减/压缩已执行 | 严重 |
| 资产一致性 | 角色外貌、场景描写与【项目配置】中的资产包一致(未传入资产包则跳过并注明) | 严重 |
| 节拍与衔接 | 6-8个节拍各有时间码转场方式明确集末钩子与骨架一致情绪过渡自然 | 中等 |
| 构图适配 | 符合【项目配置】中的平台规格构图要求 | 中等 |
## 跨阶段一致性检查
剧本需与骨架和改编策略进行一致性校验:
- **时长落实**:剧本实际时长是否符合骨架中该集的时长分配
- **删减落实**:骨架和策略中标注的删减/压缩是否在剧本中执行
- **钩子落实**:骨架中设计的集末钩子是否在剧本中体现
- **资产一致**:角色外貌和场景描写是否与资产包吻合(未传入则跳过并注明"未收到资产配置,资产一致性审核已跳过"
如发现不一致,标记为**严重问题**。
## 详细审核标准
### 时长合规性(严重)
验证方法:
1. 统计全部台词字数(含旁白、内心独白)
2. 按150字/分钟语速换算
3. 加上纯画面段落时长每段5-15秒
4. 总时长应在【项目配置】单集时长 ±10秒范围内
### 画面可执行性(严重)
每个画面描述必须包含:
- 可识别的镜头类型(特写/近景/中景/全景)
- 具体的人物动作(不能写"角色做了某事"
- 可视化的环境要素(光线、色调、道具)
不通过示例:
- "李火旺感到害怕" ← 情绪状态,不是画面
- "场景很恐怖" ← 抽象,不可执行
通过示例:
- "李火旺后退半步,目光下移盯着地面那道黑色湿痕,右手微微发抖" ← 具体、可拍摄
### 角色视觉一致性(严重)
每个BEAT中出场角色的外貌描写必须与【项目配置】中传入的角色资产包吻合。
若未传入角色资产包,跳过此项。
### 场景氛围一致性(严重)
场景描写须与【项目配置】中传入的场景资产包保持一致,包括色调、光线、道具等视觉要素。
若未传入场景资产包,跳过此项。

View File

@ -1,45 +0,0 @@
# 故事骨架审核
基于 [supervision_common.md](supervision_common.md) 中的通用规范执行审核。
## 数据准备
1. 调用 `get_planData` 获取骨架数据
2. 调用 `get_novel_events(ids:number[])` 获取事件表数据
3. 从【项目配置】读取:集数、单集时长、付费策略、章节范围
## 审核维度
| 审核项 | 标准 | 严重程度 |
|--------|------|----------|
| 结构完整性 | 故事核存在且聚焦主角内在冲突;三幕均有功能、核心问题、幕末转折 | 严重 |
| 分集与时长 | 分集数恰好等于【项目配置】集数;每集时长符合单集时长 ±10秒 | 严重 |
| 章节全覆盖 | 【项目配置】指定的原著章节全部被分配到具体集数 | 严重 |
| 叙事设计 | 删减有据、集末钩子齐全、付费卡点符合策略、情绪曲线有起伏、人物弧每集推进 | 中等 |
## 跨阶段一致性检查
骨架作为首个产出阶段,需与事件表进行一致性校验:
- **章节全覆盖**:事件表中的章节是否全部被骨架分配到具体集数,逐一核对无遗漏
- **主线判定一致**:骨架中对事件主线强度的引用是否与事件表中的标注矛盾
如发现不一致,标记为**严重问题**。
## 详细审核标准
### 三幕功能验证(严重)
- 第一幕必须完成"建立"功能:规则建立、悬疑建立、动机激活
- 第二幕必须完成"冲突"功能:主要矛盾展开、计划执行、代价付出
- 第三幕必须完成"拓展/结局"功能:新世界、新能力、开放悬念
### 情绪曲线验证(中等)
全剧情绪分布应根据实际集数设计"波浪上升"模式:
- 不允许连续3集都是同一情绪强度
- 最高潮应在中后期
- 高潮后应有节奏缓冲再推向新高潮
### 付费卡点合理性(中等)
- 付费策略按【项目配置】中的设定执行
- 付费点必须放在"观众最想知道后续"的位置
- 钩子类型应多样化(不全是悬念钩子)

View File

@ -1,47 +0,0 @@
# 监督层通用规范
本文件定义所有审核任务共享的报告格式、评分标准和审核原则。
## 审核报告格式
```markdown
# 审核报告:{审核对象}
## 总评
- **评分**{A/B/C/D}
- **概要**{一句话总评,可顺带肯定亮点}
## 问题清单
| # | 严重程度 | 审核项 | 问题 | 建议方案 |
|---|----------|--------|------|----------|
| 1 | 🔴 严重 | {审核项} | {一句话描述} | {多选方案用"/"分隔} |
| 2 | 🟡 中等 | {审核项} | {一句话描述} | {修复建议} |
| 3 | ⚪ 轻微 | {审核项} | {一句话描述} | {修复建议} |
## 需要您决定(仅 C/D 级或严重问题存在多选方案时输出)
1. {选择题}
```
### 精简规则
- 审核通过的项目不出现在报告中
- 同类轻微问题合并为一行
- B 级及以上省略「需要您决定」区块
## 评分标准
| 评分 | 严重问题 | 中等问题 |
|------|----------|----------|
| A — 可直接使用 | 0 | ≤2 |
| B — 小修后可用 | 0 | ≤5 |
| C — 需较大修改 | 1-2 | 不限 |
| D — 建议重做 | ≥3 | 不限 |
## 通用审核原则
1. **工具调取优先**:所有审核依据必须通过工具实际读取,不得凭记忆或上下文摘要审核
2. **可执行优先**:标准是"能不能用",不是"完不完美"
3. **问题具体化**:每个问题指向具体位置和内容,不说"整体不够好"
4. **建议多元化**:严重问题提供多个可选方案
5. **动态基准**:数值判断以【项目配置】为唯一基准;配置中未明确的参数以合理比例推算,并在报告中注明

View File

@ -1,32 +1 @@
--- 请输出100字假数据
name: script_agent_supervision.md
description: >-
短剧改编监督层Agent路由。根据决策层派发的审核任务类型加载对应的独立技能文件执行。
当收到决策层的 run_sub_agent 调用时激活。
---
# 监督层 Agent — 任务路由
你是短剧改编项目的**监督层 Agent**,只接收决策层派发的审核任务并执行。
**核心原则:你只提出问题和建议,不做任何修改决策。所有修改决定权属于用户。**
## 任务路由表
收到任务后,根据指令中的关键词匹配对应技能文件,加载并执行:
| 标识词 | 技能文件 | 说明 |
|--------|----------|------|
| 骨架审核、审核骨架、review skeleton | [script_supervision_skeleton.md](script_agent_skills/supervision/script_supervision_skeleton.md) | 审核故事骨架的结构、分集与覆盖度 |
| 策略审核、审核改编策略、review adaptation | [script_supervision_adaptation.md](script_agent_skills/supervision/script_supervision_adaptation.md) | 审核改编策略与骨架的一致性 |
| 剧本审核、审核剧本、review script | [script_supervision_script.md](script_agent_skills/supervision/script_supervision_script.md) | 审核剧本的时长、画面与内容覆盖 |
所有审核任务共享的报告格式、评分标准和通用原则见 [supervision_common.md](script_agent_skills/supervision/supervision_common.md)。
## 路由规则
1. 从派发指令中识别审核对象关键词
2. 加载对应的审核技能文件 + 通用规范文件
3. 按技能文件中的审核维度逐项检查
4. 按通用规范中的报告格式生成审核报告
5. 如果无法匹配审核对象,返回提示:`无法识别审核对象,请检查派发指令`

View File

@ -0,0 +1 @@
请输出100字假数据

View File

@ -0,0 +1 @@
请输出 3 个剧本,每一个 100 字假数据

View File

@ -0,0 +1 @@
请输出100字假数据

BIN
data/skills/skills.zip Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -52,7 +52,6 @@
"cors": "^2.8.5", "cors": "^2.8.5",
"custom-electron-titlebar": "^4.2.8", "custom-electron-titlebar": "^4.2.8",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"electron-rebuild": "^3.2.9",
"express": "^5.2.1", "express": "^5.2.1",
"express-ws": "^5.0.2", "express-ws": "^5.0.2",
"fast-glob": "^3.3.3", "fast-glob": "^3.3.3",
@ -68,6 +67,7 @@
"serialize-error": "^13.0.1", "serialize-error": "^13.0.1",
"sharp": "^0.34.5", "sharp": "^0.34.5",
"socket.io": "^4.8.3", "socket.io": "^4.8.3",
"sqlite3": "^6.0.1",
"sucrase": "^3.35.1", "sucrase": "^3.35.1",
"uuid": "^13.0.0", "uuid": "^13.0.0",
"vm2": "^3.10.5", "vm2": "^3.10.5",
@ -86,6 +86,7 @@
"cross-env": "^10.1.0", "cross-env": "^10.1.0",
"electron": "^40.0.0", "electron": "^40.0.0",
"electron-builder": "^26.4.0", "electron-builder": "^26.4.0",
"electron-rebuild": "^3.2.9",
"electronmon": "^2.0.4", "electronmon": "^2.0.4",
"license-checker": "^25.0.1", "license-checker": "^25.0.1",
"nodemon": "^3.1.11", "nodemon": "^3.1.11",

View File

@ -131,6 +131,7 @@ app.whenReady().then(async () => {
const mod = requireWithCustomPaths(servePath); const mod = requireWithCustomPaths(servePath);
closeServeFn = mod.closeServe; closeServeFn = mod.closeServe;
const port = await mod.default(true); const port = await mod.default(true);
process.env.PORT = port;
// 注册协议处理器 // 注册协议处理器
protocol.handle("toonflow", (request) => { protocol.handle("toonflow", (request) => {
const url = new URL(request.url); const url = new URL(request.url);
@ -164,7 +165,7 @@ app.whenReady().then(async () => {
windowismaximized: () => ({ windowismaximized: () => ({
maximized: mainWindow?.isMaximized() ?? false, maximized: mainWindow?.isMaximized() ?? false,
}), }),
openDevTool: () => { opendevtool: () => {
mainWindow?.webContents.openDevTools(); mainWindow?.webContents.openDevTools();
return { ok: true }; return { ok: true };
}, },

View File

@ -6,6 +6,7 @@ import Memory from "@/utils/agent/memory";
import { useSkill } from "@/utils/agent/skillsTools"; import { useSkill } from "@/utils/agent/skillsTools";
import useTools from "@/agents/productionAgent/tools"; import useTools from "@/agents/productionAgent/tools";
import ResTool from "@/socket/resTool"; import ResTool from "@/socket/resTool";
import * as fs from "fs";
export interface AgentContext { export interface AgentContext {
socket: Socket; socket: Socket;
@ -40,14 +41,19 @@ export async function decisionAI(ctx: AgentContext) {
const memory = new Memory("productionAgent", isolationKey); const memory = new Memory("productionAgent", isolationKey);
await memory.add("user", text); await memory.add("user", text);
const skill = await useSkill({ mainSkill: "production_agent_decision" }, buildMemPrompt(await memory.get(text))); const { skillPaths } = await useSkill({ mainSkill: "production_agent_decision" });
const prompt = await fs.promises.readFile(skillPaths.mainSkill, "utf-8");
const mem = buildMemPrompt(await memory.get(text));
const { textStream } = await u.Ai.Text("productionAgent").stream({ const { textStream } = await u.Ai.Text("productionAgent").stream({
system: skill.prompt, messages: [
messages: [{ role: "user", content: text }], { role: "system", content: prompt },
{ role: "system", content: mem },
{ role: "user", content: text },
],
abortSignal, abortSignal,
tools: { tools: {
...skill.tools,
...memory.getTools(), ...memory.getTools(),
run_sub_agent: runSubAgent(ctx), run_sub_agent: runSubAgent(ctx),
...useTools({ resTool: ctx.resTool, msg: ctx.msg }), ...useTools({ resTool: ctx.resTool, msg: ctx.msg }),
@ -90,9 +96,9 @@ export async function supervisionAI(ctx: AgentContext) {
const { text, abortSignal } = ctx; const { text, abortSignal } = ctx;
const skill = await useSkill({ mainSkill: "production_agent_supervision", workspace: ["production_agent_skills/supervision"] }); const skill = await useSkill({ mainSkill: "production_agent_supervision", workspace: ["production_agent_skills/supervision"] });
const subMsg = ctx.resTool.newMessage("assistant", "编辑"); const subMsg = ctx.resTool.newMessage("assistant", "监制");
const { textStream } = await u.Ai.Text("scriptAgent").stream({ const { textStream } = await u.Ai.Text("productionAgent").stream({
system: skill.prompt, system: skill.prompt,
messages: [{ role: "user", content: text }], messages: [{ role: "user", content: text }],
abortSignal, abortSignal,
@ -110,7 +116,7 @@ export async function supervisionAI(ctx: AgentContext) {
//工具函数 //工具函数
function runSubAgent(parentCtx: AgentContext) { function runSubAgent(parentCtx: AgentContext) {
const memory = new Memory("scriptAgent", parentCtx.isolationKey); const memory = new Memory("productionAgent", parentCtx.isolationKey);
return tool({ return tool({
description: "启动子Agent执行独立任务。可用子Agent:executionAI, decisionAI, supervisionAI", description: "启动子Agent执行独立任务。可用子Agent:executionAI, decisionAI, supervisionAI",
inputSchema: z.object({ inputSchema: z.object({
@ -134,13 +140,13 @@ function runSubAgent(parentCtx: AgentContext) {
subMsg.complete(); subMsg.complete();
if (fullResponse.trim()) { if (fullResponse.trim()) {
await memory.add(`assistant:${agent === "executionAI" ? "execution" : "supervision"}`, fullResponse, { await memory.add(`assistant:${agent === "executionAI" ? "execution" : "supervision"}`, fullResponse, {
name: agent === "executionAI" ? "编剧" : "编辑", name: agent === "executionAI" ? "执行导演" : "监制",
createTime: new Date(subMsg.datetime).getTime(), createTime: new Date(subMsg.datetime).getTime(),
}); });
} }
// 为主Agent后续输出创建新消息 // 为主Agent后续输出创建新消息
parentCtx.msg = parentCtx.resTool.newMessage("assistant", "统筹"); parentCtx.msg = parentCtx.resTool.newMessage("assistant", "监制");
return fullResponse; return fullResponse;
}, },

View File

@ -0,0 +1,185 @@
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}`;
}
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(),
...useTools({ resTool: ctx.resTool, msg: ctx.msg }),
...createSubAgent(ctx),
},
onFinish: async (completion) => {
await memory.add("assistant:decision", completion.text);
},
});
return textStream;
}
//====================== 执行层 ======================
function createSubAgent(parentCtx: AgentContext) {
const { resTool, abortSignal } = parentCtx;
const memory = new Memory("scriptAgent", parentCtx.isolationKey);
const run_execution_agent = tool({
description: "运行执行层subAgent执行独立任务完成后返回结果",
inputSchema: z.object({
taskType: z.enum(["故事骨架", "改变策略", "剧本"]).describe("任务类型"),
prompt: z.string().describe("交给子Agent的任务简约描述100字以内"),
}),
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 } = 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 textStream) {
text.append(chunk);
fullResponse += chunk;
}
text.complete();
subMsg.complete();
if (fullResponse.trim()) {
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

@ -0,0 +1,185 @@
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}`;
}
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(),
...useTools({ resTool: ctx.resTool, msg: ctx.msg }),
...createSubAgent(ctx),
},
onFinish: async (completion) => {
await memory.add("assistant:decision", completion.text);
},
});
return textStream;
}
//====================== 执行层 ======================
function createSubAgent(parentCtx: AgentContext) {
const { resTool, abortSignal } = parentCtx;
const memory = new Memory("scriptAgent", parentCtx.isolationKey);
const run_execution_agent = tool({
description: "运行执行层subAgent执行独立任务完成后返回结果",
inputSchema: z.object({
taskType: z.enum(["故事骨架", "改变策略", "剧本"]).describe("任务类型"),
prompt: z.string().describe("交给子Agent的任务简约描述100字以内"),
}),
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 } = 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 textStream) {
text.append(chunk);
fullResponse += chunk;
}
text.complete();
subMsg.complete();
if (fullResponse.trim()) {
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

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

@ -3,9 +3,10 @@ import { tool } from "ai";
import { z } from "zod"; import { z } from "zod";
import u from "@/utils"; import u from "@/utils";
import Memory from "@/utils/agent/memory"; import Memory from "@/utils/agent/memory";
import { useSkill } from "@/utils/agent/skillsTools";
import useTools from "@/agents/scriptAgent/tools"; import useTools from "@/agents/scriptAgent/tools";
import ResTool from "@/socket/resTool"; import ResTool from "@/socket/resTool";
import * as fs from "fs";
import path from "path";
export interface AgentContext { export interface AgentContext {
socket: Socket; socket: Socket;
@ -33,47 +34,42 @@ function buildMemPrompt(mem: Awaited<ReturnType<Memory["get"]>>): string {
return `## Memory\n以下是你对用户的记忆可作为参考但不要主动提及\n${memoryContext}`; return `## Memory\n以下是你对用户的记忆可作为参考但不要主动提及\n${memoryContext}`;
} }
const subAgentList = ["executionAI", "supervisionAI"] as const;
export async function decisionAI(ctx: AgentContext) { export async function decisionAI(ctx: AgentContext) {
const { isolationKey, text, userMessageTime, abortSignal, resTool } = ctx; const { isolationKey, text, userMessageTime, abortSignal, resTool } = ctx;
const memory = new Memory("scriptAgent", isolationKey); const memory = new Memory("scriptAgent", isolationKey);
await memory.add("user", text, { createTime: userMessageTime }); await memory.add("user", text, { createTime: userMessageTime });
const skill = await useSkill({ mainSkill: "script_agent_decision" }, buildMemPrompt(await memory.get(text))); const skill = path.join(u.getPath("skills"), "script_agent_decision.md");
const prompt = await fs.promises.readFile(skill, "utf-8");
const mem = buildMemPrompt(await memory.get(text));
const projectData = await u.db("o_project").where("id", resTool.data.projectId).first(); 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 novelData = await u.db("o_novel").where("projectId", resTool.data.projectId).select("id", "chapterIndex as index");
const get_project_info = tool({ const projectInfo = [
description: "获取项目的基本信息和章节ID映射表返回字符串格式的项目信息", "## 项目信息",
inputSchema: z.object({}), `小说名称:${projectData?.name ?? "未知"}`,
execute: async () => { `小说类型:${projectData?.type ?? "未知"}`,
const projectInfo = [ `小说简介:${projectData?.intro ?? "无"}`,
"## 项目信息", `目标改编影视视觉手册|画风:${projectData?.artStyle ?? "无"}`,
`小说名称:${projectData?.name ?? "未知"}`, `目标改编视频画幅:${projectData?.videoRatio ?? "16:9"}`,
`小说类型:${projectData?.type ?? "未知"}`, ].join("\n");
`小说简介:${projectData?.intro ?? "无"}`,
`目标改编影视视觉手册|画风:${projectData?.artStyle ?? "无"}`,
`目标改编视频画幅:${projectData?.videoRatio ?? "16:9"}`,
].join("\n");
const content = `${projectInfo}\n\n## 章节ID映射表\n${novelData.map((i: any) => `- 章节ID${i.id}: 第${i.index}`).join("\n")}\n\n`; const projectPrompt = `${projectInfo}\n\n## 章节ID映射表\n${novelData.map((i: any) => `- 章节ID${i.id}: 第${i.index}`).join("\n")}\n\n`;
return { content };
},
});
const { textStream } = await u.Ai.Text("scriptAgent").stream({ const { textStream } = await u.Ai.Text("scriptAgent").stream({
system: skill.prompt, messages: [
messages: [{ role: "user", content: text }], { role: "system", content: prompt },
{ role: "assistant", content: projectPrompt + mem },
{ role: "user", content: text },
],
abortSignal, abortSignal,
tools: { tools: {
...skill.tools,
...memory.getTools(), ...memory.getTools(),
run_sub_agent: runSubAgent(ctx),
...useTools({ resTool: ctx.resTool, msg: ctx.msg }), ...useTools({ resTool: ctx.resTool, msg: ctx.msg }),
get_project_info, ...createSubAgent(ctx),
}, },
onFinish: async (completion) => { onFinish: async (completion) => {
await memory.add("assistant:decision", completion.text); await memory.add("assistant:decision", completion.text);
@ -83,95 +79,125 @@ export async function decisionAI(ctx: AgentContext) {
return textStream; return textStream;
} }
//====================== 执行层 ====================== function createSubAgent(parentCtx: AgentContext) {
const { resTool, abortSignal } = parentCtx;
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); 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当前的消息 async function runAgent({
parentCtx.msg.complete(); prompt,
// 子Agent用新消息回复 system,
const { textStream: subTextStream, subMsg } = await fn({ ...parentCtx, text: prompt }); name,
let text = subMsg.text(); memoryKey,
let fullResponse = ""; tools: extraTools,
for await (const chunk of subTextStream) { }: {
text.append(chunk); prompt: string;
fullResponse += chunk; system: string;
} name: string;
text.complete(); memoryKey: string;
subMsg.complete(); tools?: Record<string, any>;
if (fullResponse.trim()) { }) {
await memory.add(`assistant:${agent === "executionAI" ? "execution" : "supervision"}`, fullResponse, { parentCtx.msg.complete();
name: agent === "executionAI" ? "编剧" : "编辑", const subMsg = resTool.newMessage("assistant", name);
createTime: new Date(subMsg.datetime).getTime(), const text = subMsg.text();
}); let fullResponse = "";
}
// 为主Agent后续输出创建新消息 const { textStream } = await u.Ai.Text("scriptAgent").stream({
parentCtx.msg = parentCtx.resTool.newMessage("assistant", "统筹"); system,
messages: [{ role: "user", content: prompt }],
abortSignal,
tools: { ...extraTools, ...useTools({ resTool, msg: subMsg }) },
});
return fullResponse; for await (const chunk of textStream) {
text.append(chunk);
fullResponse += chunk;
}
text.complete();
subMsg.complete();
if (fullResponse.trim()) {
await memory.add(memoryKey, fullResponse, {
name,
createTime: new Date(subMsg.datetime).getTime(),
});
}
parentCtx.msg = resTool.newMessage("assistant", "统筹");
return fullResponse;
}
const promptInput = z.object({
prompt: z.string().describe("交给子Agent的任务简约描述100字以内"),
});
const run_sub_agent_storySkeleton = tool({
description: "运行执行subAgent来完成故事骨架相关任务",
inputSchema: promptInput,
execute: async ({ prompt }) => {
const skill = path.join(u.getPath("skills"), "script_execution_skeleton.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8");
return runAgent({
prompt,
system: systemPrompt + "你可以使用如下XML格式写入工作区\n<storySkeleton>故事骨架内容</storySkeleton>",
name: "编剧",
memoryKey: "assistant:execution:storySkeleton",
});
}, },
}); });
const run_sub_agent_adaptationStrategy = tool({
description: "运行执行subAgent来完成改编策略相关任务",
inputSchema: promptInput,
execute: async ({ prompt }) => {
const skill = path.join(u.getPath("skills"), "script_execution_adaptation.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8");
return runAgent({
prompt,
system: systemPrompt + "你可以使用如下XML格式写入工作区\n<adaptationStrategy>改编策略内容</adaptationStrategy>",
name: "编剧",
memoryKey: "assistant:execution:adaptationStrategy",
});
},
});
const run_sub_agent_script = tool({
description: "运行执行subAgent来完成剧本相关任务",
inputSchema: promptInput,
execute: async ({ prompt }) => {
const skill = path.join(u.getPath("skills"), "script_execution_script.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8");
return runAgent({
prompt,
system:
systemPrompt +
`你可以使用如下XML格式写入工作区\nXML不得添加任何额外标签<script><item name="剧本名称">剧本内容</item><item name="剧本名称">剧本内容</item><item name="剧本名称">剧本内容</item></script>`,
name: "编剧",
memoryKey: "assistant:execution:script",
});
},
});
const run_supervision_agent = tool({
description: "运行监督层subAgent执行独立任务完成后返回结果",
inputSchema: promptInput,
execute: async ({ prompt }) => {
const skill = path.join(u.getPath("skills"), "script_agent_supervision.md");
const systemPrompt = await fs.promises.readFile(skill, "utf-8");
return runAgent({
prompt,
system: systemPrompt,
name: "编辑",
memoryKey: "assistant:supervision",
});
},
});
return {
run_sub_agent_storySkeleton,
run_sub_agent_adaptationStrategy,
run_sub_agent_script,
run_supervision_agent,
};
} }

View File

@ -4,15 +4,7 @@ import { z } from "zod";
import _ from "lodash"; import _ from "lodash";
import ResTool from "@/socket/resTool"; import ResTool from "@/socket/resTool";
export const AssetSchema = z.object({
id: z.number().describe("资产ID,如果新增则为空").optional(),
prompt: z.string().describe("生成提示词"),
name: z.string().describe("资产名称"),
desc: z.string().describe("资产描述"),
type: z.enum(["role", "tool", "scene", "clip"]).describe("资产类型"),
});
export const ScriptSchema = z.object({ export const ScriptSchema = z.object({
id: z.number().describe("剧本ID"),
name: z.string().describe("剧本名称"), name: z.string().describe("剧本名称"),
content: z.string().describe("剧本内容"), content: z.string().describe("剧本内容"),
}); });
@ -57,7 +49,7 @@ export default (toolCpnfig: ToolConfig) => {
thinking.appendText("查询结果:\n" + eventString); thinking.appendText("查询结果:\n" + eventString);
thinking.updateTitle("查询章节事件完成"); thinking.updateTitle("查询章节事件完成");
thinking.complete(); thinking.complete();
return eventString; return eventString ?? "无数据";
}, },
}), }),
get_planData: tool({ get_planData: tool({
@ -72,7 +64,7 @@ export default (toolCpnfig: ToolConfig) => {
thinking.appendText(`获取到${planDataKeyLabels[key]}:\n` + planData[key]); thinking.appendText(`获取到${planDataKeyLabels[key]}:\n` + planData[key]);
thinking.updateTitle(`获取${planDataKeyLabels[key]}完成`); thinking.updateTitle(`获取${planDataKeyLabels[key]}完成`);
thinking.complete(); thinking.complete();
return planData[key]; return planData[key] ?? "无数据";
}, },
}), }),
get_novel_text: tool({ get_novel_text: tool({
@ -88,53 +80,9 @@ export default (toolCpnfig: ToolConfig) => {
thinking.appendText(`获取到原文:\n` + text); thinking.appendText(`获取到原文:\n` + text);
thinking.updateTitle(`获取小说章节原文完成`); thinking.updateTitle(`获取小说章节原文完成`);
thinking.complete(); thinking.complete();
return data && data?.chapterData ? data.chapterData : text; return text ?? "无数据";
},
}),
//======================
update_script_to_sqlite: tool({
description: "更新剧本,修改数据库对应剧本,供后续业务使用",
inputSchema: z.object({
script: ScriptSchema,
}),
execute: async ({ script }) => {
await u.db("o_script").where({ id: script.id }).update({
name: script.name,
content: script.content,
});
socket.emit("setPlanData", { key: "script", value: script.id });
return true;
},
}),
insert_script_to_sqlite: tool({
description: "新增剧本,将剧本内容插入sqlite数据库供后续业务使用",
inputSchema: z.object({
script: ScriptSchema.omit({ id: true }),
}),
execute: async ({ script }) => {
const [scriptId] = await u.db("o_script").insert({
name: script.name,
content: script.content,
projectId: resTool.data.projectId,
createTime: Date.now(),
});
socket.emit("setPlanData", { key: "script", value: scriptId });
return true;
},
}),
delete_script_to_sqlite: tool({
description: "删除剧本,将剧本内容从sqlite数据库中删除",
inputSchema: z.object({
scriptId: z.string().describe("剧本id"),
}),
execute: async ({ scriptId }) => {
console.log("[tools] delete_script_to_sqlite", scriptId);
await u.db("o_script").where({ id: scriptId }).delete();
socket.emit("setPlanData", { key: "script", value: scriptId });
return true;
}, },
}), }),
}; };
return toolsNames ? Object.fromEntries(Object.entries(tools).filter(([n]) => toolsNames.includes(n))) : tools; return toolsNames ? Object.fromEntries(Object.entries(tools).filter(([n]) => toolsNames.includes(n))) : tools;
}; };

View File

@ -1,4 +1,4 @@
// @routes-hash ebc81322f1e60446bf41d1ecb72f2b96 // @routes-hash 5a19c42c22d44ec4c7fe2fc7245535d9
import { Express } from "express"; import { Express } from "express";
import route1 from "./routes/agents/clearMemory"; import route1 from "./routes/agents/clearMemory";
@ -99,25 +99,20 @@ import route95 from "./routes/setting/memoryConfig/getMemory";
import route96 from "./routes/setting/memoryConfig/sureMemory"; import route96 from "./routes/setting/memoryConfig/sureMemory";
import route97 from "./routes/setting/promptManage/getPrompt"; import route97 from "./routes/setting/promptManage/getPrompt";
import route98 from "./routes/setting/promptManage/updatePrompt"; import route98 from "./routes/setting/promptManage/updatePrompt";
import route99 from "./routes/setting/skillManagement/backup/addSkill"; import route99 from "./routes/setting/skillManagement/getSkillContent";
import route100 from "./routes/setting/skillManagement/backup/deleteSkill"; import route100 from "./routes/setting/skillManagement/getSkillList";
import route101 from "./routes/setting/skillManagement/backup/embeddingSkill"; import route101 from "./routes/setting/skillManagement/saveSkillContent";
import route102 from "./routes/setting/skillManagement/backup/generateDescription"; import route102 from "./routes/setting/vendorConfig/addVendor";
import route103 from "./routes/setting/skillManagement/backup/getSkillList"; import route103 from "./routes/setting/vendorConfig/deleteVendor";
import route104 from "./routes/setting/skillManagement/backup/scanSkills"; import route104 from "./routes/setting/vendorConfig/getVendorList";
import route105 from "./routes/setting/skillManagement/backup/updateSkill"; import route105 from "./routes/setting/vendorConfig/modelTest";
import route106 from "./routes/setting/skillManagement/getSkillList"; import route106 from "./routes/setting/vendorConfig/updateCode";
import route107 from "./routes/setting/vendorConfig/addVendor"; import route107 from "./routes/setting/vendorConfig/updateVendor";
import route108 from "./routes/setting/vendorConfig/deleteVendor"; import route108 from "./routes/task/getProject";
import route109 from "./routes/setting/vendorConfig/getVendorList"; import route109 from "./routes/task/getTaskApi";
import route110 from "./routes/setting/vendorConfig/modelTest"; import route110 from "./routes/task/getTaskCategories";
import route111 from "./routes/setting/vendorConfig/updateCode"; import route111 from "./routes/task/taskDetails";
import route112 from "./routes/setting/vendorConfig/updateVendor"; import route112 from "./routes/test/test";
import route113 from "./routes/task/getProject";
import route114 from "./routes/task/getTaskApi";
import route115 from "./routes/task/getTaskCategories";
import route116 from "./routes/task/taskDetails";
import route117 from "./routes/test/test";
export default async (app: Express) => { export default async (app: Express) => {
app.use("/api/agents/clearMemory", route1); app.use("/api/agents/clearMemory", route1);
@ -218,23 +213,18 @@ export default async (app: Express) => {
app.use("/api/setting/memoryConfig/sureMemory", route96); app.use("/api/setting/memoryConfig/sureMemory", route96);
app.use("/api/setting/promptManage/getPrompt", route97); app.use("/api/setting/promptManage/getPrompt", route97);
app.use("/api/setting/promptManage/updatePrompt", route98); app.use("/api/setting/promptManage/updatePrompt", route98);
app.use("/api/setting/skillManagement/backup/addSkill", route99); app.use("/api/setting/skillManagement/getSkillContent", route99);
app.use("/api/setting/skillManagement/backup/deleteSkill", route100); app.use("/api/setting/skillManagement/getSkillList", route100);
app.use("/api/setting/skillManagement/backup/embeddingSkill", route101); app.use("/api/setting/skillManagement/saveSkillContent", route101);
app.use("/api/setting/skillManagement/backup/generateDescription", route102); app.use("/api/setting/vendorConfig/addVendor", route102);
app.use("/api/setting/skillManagement/backup/getSkillList", route103); app.use("/api/setting/vendorConfig/deleteVendor", route103);
app.use("/api/setting/skillManagement/backup/scanSkills", route104); app.use("/api/setting/vendorConfig/getVendorList", route104);
app.use("/api/setting/skillManagement/backup/updateSkill", route105); app.use("/api/setting/vendorConfig/modelTest", route105);
app.use("/api/setting/skillManagement/getSkillList", route106); app.use("/api/setting/vendorConfig/updateCode", route106);
app.use("/api/setting/vendorConfig/addVendor", route107); app.use("/api/setting/vendorConfig/updateVendor", route107);
app.use("/api/setting/vendorConfig/deleteVendor", route108); app.use("/api/task/getProject", route108);
app.use("/api/setting/vendorConfig/getVendorList", route109); app.use("/api/task/getTaskApi", route109);
app.use("/api/setting/vendorConfig/modelTest", route110); app.use("/api/task/getTaskCategories", route110);
app.use("/api/setting/vendorConfig/updateCode", route111); app.use("/api/task/taskDetails", route111);
app.use("/api/setting/vendorConfig/updateVendor", route112); app.use("/api/test/test", route112);
app.use("/api/task/getProject", route113);
app.use("/api/task/getTaskApi", route114);
app.use("/api/task/getTaskCategories", route115);
app.use("/api/task/taskDetails", route116);
app.use("/api/test/test", route117);
} }

View File

@ -13,7 +13,7 @@ export default router.post(
async (req, res) => { async (req, res) => {
const { images } = req.body; const { images } = req.body;
try { try {
const resText = await u.Ai.Text("universalAgent").invoke({ const resText = await u.Ai.Text("universalAi").invoke({
system: system:
'请根据以下图片数据,提取出图片的画风提示词,用于生成图片时指定风格,要求简洁且具有艺术性,只需要画风提示词,不需要其他内容:"比如:`(画风2D动漫风格,2d animation style)`,`(画风:照片级真人超写实,photorealistic, lifelike, ultra detailed)``(画风3D国创,Chinese 3D animation style)`等,如果图片风格无法描述,可以返回`无法描述`,多张图片时,只输出一个综合的画风提示词,要求包含所有图片的共同风格特征,输出格式必须严格按照示例中的格式,必须包含`画风`二字,且必须使用括号括起来,括号内必须包含中文和英文的画风描述,并用逗号分隔,英文部分需要翻译成地道的英文提示词', '请根据以下图片数据,提取出图片的画风提示词,用于生成图片时指定风格,要求简洁且具有艺术性,只需要画风提示词,不需要其他内容:"比如:`(画风2D动漫风格,2d animation style)`,`(画风:照片级真人超写实,photorealistic, lifelike, ultra detailed)``(画风3D国创,Chinese 3D animation style)`等,如果图片风格无法描述,可以返回`无法描述`,多张图片时,只输出一个综合的画风提示词,要求包含所有图片的共同风格特征,输出格式必须严格按照示例中的格式,必须包含`画风`二字,且必须使用括号括起来,括号内必须包含中文和英文的画风描述,并用逗号分隔,英文部分需要翻译成地道的英文提示词',
messages: [ messages: [

View File

@ -125,7 +125,7 @@ export default router.post(
`; `;
try { try {
const { _output } = (await u.Ai.Text("universalAgent").invoke({ const { _output } = (await u.Ai.Text("universalAi").invoke({
system: systemPrompt, system: systemPrompt,
messages: [{ role: "user", content: "小说原文" + novelText }], messages: [{ role: "user", content: "小说原文" + novelText }],
})) as any; })) as any;

View File

@ -117,7 +117,7 @@ export default router.post(
`; `;
try { try {
const { _output } = (await u.Ai.Text("universalAgent").invoke({ const { _output } = (await u.Ai.Text("universalAi").invoke({
system: systemPrompt, system: systemPrompt,
messages: [{ role: "user", content: "小说原文" + novelText }], messages: [{ role: "user", content: "小说原文" + novelText }],
})) as any; })) as any;

View File

@ -32,7 +32,7 @@ export default router.post(
async function getLines(prompt: string) { async function getLines(prompt: string) {
const skill = await useSkill("universal_agent.md");//todo改为AI const skill = await useSkill("universal_agent.md");//todo改为AI
const resText = await u.Ai.Text("universalAgent").invoke({ const resText = await u.Ai.Text("universalAi").invoke({
system: skill.prompt, system: skill.prompt,
messages: [{ role: "user", content: prompt }], messages: [{ role: "user", content: prompt }],
tools: skill.tools, tools: skill.tools,

View File

@ -45,7 +45,7 @@ export default router.post(
const { scriptIds, projectId, concurrency = 3 } = req.body; const { scriptIds, projectId, concurrency = 3 } = req.body;
if (!scriptIds.length) return res.status(400).send(error("请先选择剧本")); if (!scriptIds.length) return res.status(400).send(error("请先选择剧本"));
const scripts = await u.db("o_script").whereIn("id", scriptIds); const scripts = await u.db("o_script").whereIn("id", scriptIds);
const intansce = u.Ai.Text("universalAgent"); const intansce = u.Ai.Text("universalAi");
const novelData = await u.db("o_novel").where("projectId", projectId).select("chapterData"); const novelData = await u.db("o_novel").where("projectId", projectId).select("chapterData");
if (!novelData || novelData.length === 0) return res.status(400).send(error("请先上传小说")); if (!novelData || novelData.length === 0) return res.status(400).send(error("请先上传小说"));
await u.db("o_script").whereIn("id", scriptIds).update({ await u.db("o_script").whereIn("id", scriptIds).update({

View File

@ -13,9 +13,9 @@ export default router.post(
}), }),
async (req, res) => { async (req, res) => {
const { projectId, agentType } = req.body; const { projectId, agentType } = req.body;
const data = await u.db("o_agentWorkData").where({ projectId: projectId, key: agentType }).first(); const row = await u.db("o_agentWorkData").where({ projectId: projectId, key: agentType }).first();
if (!data) { if (!row) {
await u.db("o_agentWorkData").insert({ await u.db("o_agentWorkData").insert({
projectId: projectId, projectId: projectId,
key: agentType, key: agentType,
@ -31,6 +31,9 @@ export default router.post(
}), }),
); );
} }
res.status(200).send(success(JSON.parse(data.data ?? "{}"))); const data = JSON.parse(row.data ?? "{}");
data.script = await u.db("o_script").where({ projectId }).select("id", "name", "content");
res.status(200).send(success(data));
}, },
); );

View File

@ -23,6 +23,19 @@ export default router.post(
.update({ .update({
data: JSON.stringify(data), data: JSON.stringify(data),
}); });
const script = data.script;
await Promise.all(
script.map(async (s: any) => {
const row = await u.db("o_script").where({ projectId, name: s.name }).first();
if (row) {
await u.db("o_script").where({ id: row.id }).update({ content: s.content });
} else {
await u.db("o_script").insert({ projectId, name: s.name, content: s.content });
}
}),
);
res.status(200).send(success()); res.status(200).send(success());
}, },
); );

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,29 @@
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(),
}),
async (req, res) => {
const { path } = req.body;
const skillsRoot = u.getPath(["skills"]);
const filePath = p.join(skillsRoot, path);
if (!isPathInside(filePath, skillsRoot)) {
return res.status(400).send(error("无效的路径"));
}
const raw = await fs.promises.readFile(filePath, "utf-8");
res.status(200).send(success(raw));
},
);

View File

@ -1,13 +1,20 @@
import express from "express"; import express from "express";
import u from "@/utils";
import { z } from "zod";
import { success, error } from "@/lib/responseFormat"; import { success, error } from "@/lib/responseFormat";
import { validateFields } from "@/middleware/middleware"; import { validateFields } from "@/middleware/middleware";
import fs from "fs"; import fg from "fast-glob";
import path from "path"; import u from "@/utils";
const router = express.Router(); const router = express.Router();
export default router.post("/", validateFields({}), async (req, res) => { export default router.post("/", async (req, res) => {
res.status(200).send(success({})); const skillsRoot = u.getPath(["skills"]);
const entries = await fg("**/*.md", {
cwd: skillsRoot.replace(/\\/g, "/"),
onlyFiles: true,
});
console.log("%c Line:15 🍺 entries", "background:#e41a6a", entries);
res.status(200).send(success(entries));
}); });

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

@ -9,7 +9,7 @@ export default router.get("/", async (req, res) => {
{ {
mainSkill: "production_agent_execution", mainSkill: "production_agent_execution",
workspace: ["production_agent_skills/execution"], workspace: ["production_agent_skills/execution"],
attachedSkills: ["production_agent_skills/execution/driector_art_skills/chinese_sweet_romance/driector_skills"], attachedSkills: ["art_prompts/chinese_sweet_romance/driector_skills"],
}, },
); );

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 { export interface memories {
'content': string; 'content': string;
'createTime': number; 'createTime': number;
@ -34,7 +21,7 @@ export interface o_agentDeploy {
'model'?: string | null; 'model'?: string | null;
'modelName'?: string | null; 'modelName'?: string | null;
'name'?: string | null; 'name'?: string | null;
'vendorId'?: number | null; 'vendorId'?: string | null;
} }
export interface o_agentWorkData { export interface o_agentWorkData {
'createTime'?: number | null; 'createTime'?: number | null;
@ -245,7 +232,6 @@ export interface o_videoConfig {
} }
export interface DB { export interface DB {
"_o_project_old_20260328": _o_project_old_20260328;
"memories": memories; "memories": memories;
"o_agentDeploy": o_agentDeploy; "o_agentDeploy": o_agentDeploy;
"o_agentWorkData": o_agentWorkData; "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

@ -7,9 +7,11 @@ import * as fs from "fs";
type SkillAttribution = type SkillAttribution =
//剧本Agent //剧本Agent
| "script_agent_decision" | "script_agent_decision" //决策
| "script_agent_execution" | "script_execution_skeleton" //故事骨架
| "script_agent_supervision" | "script_execution_adaptation" //改变策略
| "script_execution_script" //剧本生成
| "script_agent_supervision" //审核
//生产Agent //生产Agent
| "production_agent_decision" | "production_agent_decision"
| "production_agent_execution" | "production_agent_execution"
@ -39,52 +41,89 @@ function ensureNonEmptyBody(body: string, fallback: string): string {
// ==================== 解析 SKILL.md ==================== // ==================== 解析 SKILL.md ====================
function parseFrontmatter(content: string): { name: string; description: string } { function parseFrontmatter(content: string): { name: string; description: string } {
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/); const match = content.match(/^\uFEFF?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/);
if (!match?.[1]) throw new Error("No frontmatter found"); if (!match?.[1]) {
throw new Error(`技能文件缺少有效的 frontmatter确保以 --- 包裹并包含 name 和 description 字段。${content}`);
const result: Record<string, string> = {}; }
const lines = match[1].split("\n");
const result: Record<string, string> = {};
for (let i = 0; i < lines.length; ) { const lines = match[1].split(/\r?\n/);
const colonIndex = lines[i].indexOf(":");
if (colonIndex === -1) { for (let i = 0; i < lines.length; ) {
i++; const line = lines[i];
continue; const trimmed = line.trim();
}
if (!trimmed || trimmed.startsWith("#")) {
const key = lines[i].slice(0, colonIndex).trim(); i++;
if (!key) { continue;
i++; }
continue;
} const keyMatch = line.match(/^([A-Za-z0-9_-]+)\s*:\s*(.*)$/);
if (!keyMatch) {
let value = lines[i].slice(colonIndex + 1).trim(); i++;
i++; continue;
}
if (/^[>|]-?$/.test(value)) {
const fold = value.startsWith(">"); const key = keyMatch[1].trim();
const parts: string[] = []; const rawValue = (keyMatch[2] ?? "").trim();
while (i < lines.length && /^\s+/.test(lines[i])) { i++;
parts.push(lines[i].trim());
i++; if (!key) continue;
}
value = fold ? parts.join(" ") : parts.join("\n"); if (/^[>|][+-]?[0-9]*$/.test(rawValue)) {
} const isFolded = rawValue.startsWith(">");
const blockLines: string[] = [];
result[key] = value; 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 }; return { name: result.name, description: result.description };
} }
export async function useSkill(input: SkillInput, mem?: string) { export async function useSkill(input: SkillInput) {
const { mainSkill, workspace = [], attachedSkills = [] } = input; const { mainSkill, workspace = [], attachedSkills = [] } = input;
const rootDir = getPath("skills"); const rootDir = getPath("skills");
const normalizedRootDir = path.resolve(rootDir); const normalizedRootDir = path.resolve(rootDir);
const mainPath = path.join(rootDir, mainSkill + ".md"); const mainPath = path.join(rootDir, mainSkill + ".md");
if (!fs.existsSync(mainPath)) throw new Error(`主技能文件不存在: ${mainPath}`); 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 resolveSafeSkillDir = (dir: string): string | null => {
const resolvedDir = path.resolve(normalizedRootDir, dir); const resolvedDir = path.resolve(normalizedRootDir, dir);
@ -115,7 +154,7 @@ export async function useSkill(input: SkillInput, mem?: string) {
const content = await fs.promises.readFile(mainPath, "utf-8"); const content = await fs.promises.readFile(mainPath, "utf-8");
const skill = parseFrontmatter(content); const skill = parseFrontmatter(content);
return { prompt: buildPrompt(skill), tools: createSkillTools(skill, skillPaths, mem) }; return { prompt: buildPrompt(skill), tools: createSkillTools(skill, skillPaths), skillPaths };
} }
function buildPrompt(skill: { name: string; description: string }): string { function buildPrompt(skill: { name: string; description: string }): string {
@ -132,7 +171,7 @@ function buildPrompt(skill: { name: string; description: string }): string {
</available_skills>`; </available_skills>`;
} }
function createSkillTools(skill: { name: string; description: string }, skillPaths: SkillPaths, mem?: string) { function createSkillTools(skill: { name: string; description: string }, skillPaths: SkillPaths) {
const activated = new Set<string>(); // 已激活技能集合,防止重复加载 const activated = new Set<string>(); // 已激活技能集合,防止重复加载
const skillsRootDir = path.resolve(getPath("skills")); const skillsRootDir = path.resolve(getPath("skills"));
return { return {
@ -168,10 +207,6 @@ function createSkillTools(skill: { name: string; description: string }, skillPat
content += "</skill_resources>\n"; content += "</skill_resources>\n";
} }
content += "</skill_content>"; content += "</skill_content>";
if (mem) {
content += `\n<memory>\n` + mem + `\n</memory>`;
}
console.log("%c Line:173 🍕 content", "background:#fca650", content);
return { content }; return { content };
}, },
}), }),
@ -213,7 +248,6 @@ function createSkillTools(skill: { name: string; description: string }, skillPat
content += "</skill_resources>\n"; content += "</skill_resources>\n";
} }
content += "</skill_content>"; content += "</skill_content>";
console.log("%c Line:214 🍕 content", "background:#6ec1c2", content);
return { content }; return { content };
}, },
}), }),

View File

@ -4,10 +4,10 @@ import axios from "axios";
import { transform } from "sucrase"; import { transform } from "sucrase";
import u from "@/utils"; import u from "@/utils";
type AiType = "scriptAgent" | "productionAgent" | "universalAgent"; type AiType = "scriptAgent" | "productionAgent" | "universalAi";
type FnName = "textRequest" | "imageRequest" | "videoRequest" | "ttsRequest"; type FnName = "textRequest" | "imageRequest" | "videoRequest" | "ttsRequest";
const AiTypeValues: AiType[] = ["scriptAgent", "productionAgent", "universalAgent"]; const AiTypeValues: AiType[] = ["scriptAgent", "productionAgent", "universalAi"];
async function resolveModelName(value: AiType | `${string}:${string}`): Promise<`${string}:${string}`> { async function resolveModelName(value: AiType | `${string}:${string}`): Promise<`${string}:${string}`> {
if (AiTypeValues.includes(value as AiType)) { if (AiTypeValues.includes(value as AiType)) {
const agentDeployData = await u.db("o_agentDeploy").where("key", value).first(); const agentDeployData = await u.db("o_agentDeploy").where("key", value).first();

View File

@ -56,7 +56,7 @@ class CleanNovel {
async start(allChapters: o_novel[], projectId: number): Promise<EventType[]> { async start(allChapters: o_novel[], projectId: number): Promise<EventType[]> {
const totalEvent: EventType[] = []; const totalEvent: EventType[] = [];
const intansce = u.Ai.Text("universalAgent"); const intansce = u.Ai.Text("universalAi");
// 并发控制:通过信号量限制同时执行的任务数 // 并发控制:通过信号量限制同时执行的任务数
let running = 0; let running = 0;

View File

@ -1,5 +1,5 @@
import isPathInside from "is-path-inside"; import isPathInside from "is-path-inside";
import getPath from "@/utils/getPath"; import getPath, { isEletron } from "@/utils/getPath";
import fs from "node:fs/promises"; import fs from "node:fs/promises";
import path from "node:path"; import path from "node:path";
@ -49,7 +49,8 @@ class OSS {
await this.ensureInit(); await this.ensureInit();
const safePath = normalizeUserPath(userRelPath); const safePath = normalizeUserPath(userRelPath);
// URL 始终使用 /,所以这里需要将系统分隔符转回 / // URL 始终使用 /,所以这里需要将系统分隔符转回 /
const url = process.env.OSSURL || `http://127.0.0.1:10588/`; let url = process.env.OSSURL || `http://127.0.0.1:10588/`;
if (isEletron()) url = `http://localhost:${process.env.PORT}/`;
return `${url}${safePath.split(path.sep).join("/")}`; return `${url}${safePath.split(path.sep).join("/")}`;
} }

181
yarn.lock
View File

@ -372,6 +372,11 @@
resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz#1f7ba71a3d6155d44a6faa8dbe249c62ab3e408c" resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz#1f7ba71a3d6155d44a6faa8dbe249c62ab3e408c"
integrity sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg== 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": "@gar/promisify@^1.1.3":
version "1.1.3" version "1.1.3"
resolved "https://registry.npmmirror.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" resolved "https://registry.npmmirror.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
@ -638,6 +643,17 @@
lru-cache "^10.0.1" lru-cache "^10.0.1"
socks-proxy-agent "^8.0.3" 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": "@npmcli/fs@^2.1.0":
version "2.1.2" version "2.1.2"
resolved "https://registry.npmmirror.com/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865" resolved "https://registry.npmmirror.com/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865"
@ -653,6 +669,13 @@
dependencies: dependencies:
semver "^7.3.5" 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": "@npmcli/move-file@^2.0.0":
version "2.0.1" version "2.0.1"
resolved "https://registry.npmmirror.com/@npmcli/move-file/-/move-file-2.0.1.tgz#26f6bdc379d87f75e55739bab89db525b06100e4" resolved "https://registry.npmmirror.com/@npmcli/move-file/-/move-file-2.0.1.tgz#26f6bdc379d87f75e55739bab89db525b06100e4"
@ -661,6 +684,11 @@
mkdirp "^1.0.4" mkdirp "^1.0.4"
rimraf "^3.0.2" 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": "@opentelemetry/api@1.9.0":
version "1.9.0" version "1.9.0"
resolved "https://registry.npmmirror.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" 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" resolved "https://registry.npmmirror.com/abbrev/-/abbrev-3.0.1.tgz#8ac8b3b5024d31464fe2a5feeea9f4536bf44025"
integrity sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg== 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: accepts@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.npmmirror.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" resolved "https://registry.npmmirror.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895"
@ -1469,6 +1502,22 @@ cacache@^19.0.1:
tar "^7.4.3" tar "^7.4.3"
unique-filename "^4.0.0" 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: cacheable-lookup@^5.0.3:
version "5.0.4" version "5.0.4"
resolved "https://registry.npmmirror.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" 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" package-json-from-dist "^1.0.0"
path-scurry "^1.11.1" 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: glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
version "7.2.3" version "7.2.3"
resolved "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" resolved "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
@ -2807,7 +2865,7 @@ iconv-lite@^0.6.2:
dependencies: dependencies:
safer-buffer ">= 2.1.2 < 3.0.0" 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" version "0.7.2"
resolved "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.2.tgz#d0bdeac3f12b4835b7359c2ad89c422a4d1cc72e" resolved "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.2.tgz#d0bdeac3f12b4835b7359c2ad89c422a4d1cc72e"
integrity sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw== integrity sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==
@ -2987,6 +3045,11 @@ isexe@^3.1.1:
resolved "https://registry.npmmirror.com/isexe/-/isexe-3.1.5.tgz#42e368f68d5e10dadfee4fda7b550bc2d8892dc9" resolved "https://registry.npmmirror.com/isexe/-/isexe-3.1.5.tgz#42e368f68d5e10dadfee4fda7b550bc2d8892dc9"
integrity sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w== 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: jackspeak@^3.1.2:
version "3.4.3" version "3.4.3"
resolved "https://registry.npmmirror.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" 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" resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== 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: lru-cache@^6.0.0:
version "6.0.0" version "6.0.0"
resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" 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" promise-retry "^2.0.1"
ssri "^12.0.0" 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: matcher@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.npmmirror.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" 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" resolved "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== 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" version "10.2.4"
resolved "https://registry.npmmirror.com/minimatch/-/minimatch-10.2.4.tgz#465b3accbd0218b8281f5301e27cedc697f96fde" resolved "https://registry.npmmirror.com/minimatch/-/minimatch-10.2.4.tgz#465b3accbd0218b8281f5301e27cedc697f96fde"
integrity sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg== integrity sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==
@ -3435,6 +3521,17 @@ minipass-fetch@^4.0.0:
optionalDependencies: optionalDependencies:
encoding "^0.1.13" 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: minipass-flush@^1.0.5:
version "1.0.7" version "1.0.7"
resolved "https://registry.npmmirror.com/minipass-flush/-/minipass-flush-1.0.7.tgz#145c383d5ae294b36030aa80d4e872d08bebcb73" resolved "https://registry.npmmirror.com/minipass-flush/-/minipass-flush-1.0.7.tgz#145c383d5ae294b36030aa80d4e872d08bebcb73"
@ -3456,6 +3553,13 @@ minipass-sized@^1.0.3:
dependencies: dependencies:
minipass "^3.0.0" 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: minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6:
version "3.3.6" version "3.3.6"
resolved "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" 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" resolved "https://registry.npmmirror.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d"
integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== 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" version "7.1.3"
resolved "https://registry.npmmirror.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b" resolved "https://registry.npmmirror.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b"
integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A== 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" resolved "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== 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: node-api-version@^0.1.4:
version "0.1.4" version "0.1.4"
resolved "https://registry.npmmirror.com/node-api-version/-/node-api-version-0.1.4.tgz#1ed46a485e462d55d66b5aa1fe2821720dedf080" 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" resolved "https://registry.npmmirror.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz#8a70ee85464ae52327772a90d66c6077a900cfc8"
integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ== 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: node-gyp@^11.2.0:
version "11.5.0" version "11.5.0"
resolved "https://registry.npmmirror.com/node-gyp/-/node-gyp-11.5.0.tgz#82661b5f40647a7361efe918e3cea76d297fcc56" resolved "https://registry.npmmirror.com/node-gyp/-/node-gyp-11.5.0.tgz#82661b5f40647a7361efe918e3cea76d297fcc56"
@ -3684,6 +3809,13 @@ nopt@^8.0.0:
dependencies: dependencies:
abbrev "^3.0.0" 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: normalize-package-data@^2.0.0:
version "2.5.0" version "2.5.0"
resolved "https://registry.npmmirror.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" 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" lru-cache "^10.2.0"
minipass "^5.0.0 || ^6.0.2 || ^7.0.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: path-to-regexp@^8.0.0:
version "8.4.0" version "8.4.0"
resolved "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-8.4.0.tgz#8e98fcd94826aff01a90c544ef74ffbaca3a78ed" 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" resolved "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae"
integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==
prebuild-install@^7.1.1: prebuild-install@^7.1.1, prebuild-install@^7.1.3:
version "7.1.3" version "7.1.3"
resolved "https://registry.npmmirror.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec" resolved "https://registry.npmmirror.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec"
integrity sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug== 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" resolved "https://registry.npmmirror.com/proc-log/-/proc-log-5.0.0.tgz#e6c93cf37aef33f835c53485f314f50ea906a9d8"
integrity sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ== 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: process-nextick-args@~2.0.0:
version "2.0.1" version "2.0.1"
resolved "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" 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" resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a"
integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== 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: ssri@^12.0.0:
version "12.0.0" version "12.0.0"
resolved "https://registry.npmmirror.com/ssri/-/ssri-12.0.0.tgz#bcb4258417c702472f8191981d3c8a771fee6832" resolved "https://registry.npmmirror.com/ssri/-/ssri-12.0.0.tgz#bcb4258417c702472f8191981d3c8a771fee6832"
@ -4679,6 +4836,13 @@ ssri@^12.0.0:
dependencies: dependencies:
minipass "^7.0.3" 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: ssri@^9.0.0:
version "9.0.1" version "9.0.1"
resolved "https://registry.npmmirror.com/ssri/-/ssri-9.0.1.tgz#544d4c357a8d7b71a19700074b6883fcb4eae057" 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" mkdirp "^1.0.3"
yallist "^4.0.0" 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" version "7.5.13"
resolved "https://registry.npmmirror.com/tar/-/tar-7.5.13.tgz#0d214ed56781a26edc313581c0e2d929ceeb866d" resolved "https://registry.npmmirror.com/tar/-/tar-7.5.13.tgz#0d214ed56781a26edc313581c0e2d929ceeb866d"
integrity sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng== integrity sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==
@ -5199,6 +5363,13 @@ which@^5.0.0:
dependencies: dependencies:
isexe "^3.1.1" 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: wide-align@^1.1.5:
version "1.1.5" version "1.1.5"
resolved "https://registry.npmmirror.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" resolved "https://registry.npmmirror.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3"