feat: complete WorkBuddy parity and Windows release workflow
No files matched your search
@@ -0,0 +1,26 @@
|
||||
# 通用开发角色
|
||||
|
||||
`general-development.json` 是当前五类可选开发角色的定义来源,包含双语显示信息、触发描述、快捷提问、共同约束与实际角色指令。
|
||||
|
||||
| 职责 | 定位 |
|
||||
| --- | --- |
|
||||
| 代码探索员 | 只读梳理代码路径、约束与影响范围 |
|
||||
| 实现工程师 | 在明确接口和文件归属下实施改动 |
|
||||
| 测试验收员 | 验证实际行为,报告复现、结果及未覆盖项 |
|
||||
| 代码审查员 | 独立、只读检查真实缺陷与回归 |
|
||||
| 问题诊断员 | 复现异常、验证假设、定位根因,获准后修复 |
|
||||
|
||||
从仓库根目录运行安装/更新脚本,必须明确指定YCode用户数据目录:
|
||||
|
||||
```powershell
|
||||
node scripts/configure-general-agents.mjs --user-data 'C:\Users\Administrator\AppData\Roaming\YCode'
|
||||
node scripts/configure-general-agents.mjs --user-data 'C:\Users\Administrator\AppData\Roaming\YCode' --check
|
||||
```
|
||||
|
||||
脚本通过生产专家包API校验、注册、激活和构造回合上下文。变更前在该用户数据目录的 `capabilities/expert-backups/` 备份完整原包及目录清单,保留原有四个包ID和Agent文件名,避免破坏已有选择引用。第五个诊断角色使用新ID。
|
||||
|
||||
角色默认不预加载专项技能。五个角色各有一张统一机器人风格的头像,SVG源文件和256px PNG保存在`avatars/`;维护源图时运行`node scripts/generate-general-agent-avatars.mjs`,再执行上面的配置更新。安装脚本在备份后注册文本,并单独原子写入已校验的PNG;`--check`同时检查头像字节和激活数据,重复执行不会重装一致的包。
|
||||
|
||||
提示词中的只读职责与编辑范围不构成独立的权限隔离;实际工具权限由宿主控制。
|
||||
|
||||
这些定义当前对应“我的专家”中的可手动召唤Agent。此脚本不修改子任务调度协议、并发上限、模型配置或工具授权,也不表示调度器已经支持按这里的角色自动选人。
|
||||
|
After Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,35 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
|
||||
<defs>
|
||||
<linearGradient id="debugger-bg" x2=".8" y2="1"><stop stop-color="#F7E9E6"/><stop offset="1" stop-color="#DBADA5"/></linearGradient>
|
||||
<linearGradient id="debugger-shell" x2=".3" y2="1"><stop stop-color="#FFFFFF"/><stop offset="1" stop-color="#F5DDD6"/></linearGradient>
|
||||
<linearGradient id="debugger-screen" x2=".7" y2="1"><stop stop-color="#533730"/><stop offset="1" stop-color="#172A31"/></linearGradient>
|
||||
<clipPath id="debugger-clip"><rect width="256" height="256" rx="48"/></clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#debugger-clip)">
|
||||
<rect width="256" height="256" fill="url(#debugger-bg)"/>
|
||||
<circle cx="128" cy="95" r="94" fill="#FFFFFF" opacity=".2"/>
|
||||
<ellipse cx="128" cy="254" rx="94" ry="20" fill="#B6786C" opacity=".16"/>
|
||||
<path d="M48 256v-23c0-31 32-49 80-49s80 18 80 49v23" fill="#B6786C"/>
|
||||
<path d="M62 256v-23c0-24 26-40 66-40s66 16 66 40v23" fill="url(#debugger-shell)"/>
|
||||
<path d="M106 175h44v24c-11 9-33 9-44 0z" fill="#DBADA5"/>
|
||||
<path d="M128 55V34" stroke="#B6786C" stroke-width="8" stroke-linecap="round"/>
|
||||
<circle cx="128" cy="30" r="10" fill="#B6786C"/>
|
||||
<circle cx="125" cy="27" r="4" fill="#FFFFFF" opacity=".7"/>
|
||||
<rect x="43" y="97" width="22" height="44" rx="11" fill="#B6786C"/>
|
||||
<rect x="191" y="97" width="22" height="44" rx="11" fill="#B6786C"/>
|
||||
<rect x="54" y="55" width="148" height="128" rx="42" fill="#B6786C" opacity=".18" transform="translate(0 5)"/>
|
||||
<rect x="54" y="51" width="148" height="128" rx="42" fill="url(#debugger-shell)"/>
|
||||
<path d="M78 72c12-8 25-10 50-10" stroke="#FFFFFF" stroke-width="6" stroke-linecap="round" opacity=".9"/>
|
||||
<rect x="69" y="87" width="118" height="66" rx="25" fill="url(#debugger-screen)"/>
|
||||
<rect x="87" y="105" width="16" height="23" rx="8" fill="#F5DDD6"/>
|
||||
<rect x="153" y="105" width="16" height="23" rx="8" fill="#F5DDD6"/>
|
||||
<path d="M119 136q9 6 18 0" fill="none" stroke="#F5DDD6" stroke-width="3.5" stroke-linecap="round"/>
|
||||
<circle cx="79" cy="137" r="5" fill="#DBADA5" opacity=".55"/>
|
||||
<circle cx="177" cy="137" r="5" fill="#DBADA5" opacity=".55"/>
|
||||
<path d="M102 225h52" stroke="#DBADA5" stroke-width="7" stroke-linecap="round"/>
|
||||
<path d="M114 239h28" stroke="#DBADA5" stroke-width="5" stroke-linecap="round" opacity=".65"/>
|
||||
<circle cx="205" cy="204" r="29" fill="#FFFFFF" opacity=".28"/>
|
||||
<circle cx="205" cy="203" r="25" fill="#533730"/>
|
||||
<g fill="none" stroke="#F5DDD6" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><path d="M191 204h7l4-10 7 19 4-9h7"/></g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,35 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
|
||||
<defs>
|
||||
<linearGradient id="explorer-bg" x2=".8" y2="1"><stop stop-color="#E4F3ED"/><stop offset="1" stop-color="#9DCCB9"/></linearGradient>
|
||||
<linearGradient id="explorer-shell" x2=".3" y2="1"><stop stop-color="#FFFFFF"/><stop offset="1" stop-color="#D8EEE4"/></linearGradient>
|
||||
<linearGradient id="explorer-screen" x2=".7" y2="1"><stop stop-color="#1D3E37"/><stop offset="1" stop-color="#172A31"/></linearGradient>
|
||||
<clipPath id="explorer-clip"><rect width="256" height="256" rx="48"/></clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#explorer-clip)">
|
||||
<rect width="256" height="256" fill="url(#explorer-bg)"/>
|
||||
<circle cx="128" cy="95" r="94" fill="#FFFFFF" opacity=".2"/>
|
||||
<ellipse cx="128" cy="254" rx="94" ry="20" fill="#558E79" opacity=".16"/>
|
||||
<path d="M48 256v-23c0-31 32-49 80-49s80 18 80 49v23" fill="#558E79"/>
|
||||
<path d="M62 256v-23c0-24 26-40 66-40s66 16 66 40v23" fill="url(#explorer-shell)"/>
|
||||
<path d="M106 175h44v24c-11 9-33 9-44 0z" fill="#9DCCB9"/>
|
||||
<path d="M128 55V34" stroke="#558E79" stroke-width="8" stroke-linecap="round"/>
|
||||
<circle cx="128" cy="30" r="10" fill="#558E79"/>
|
||||
<circle cx="125" cy="27" r="4" fill="#FFFFFF" opacity=".7"/>
|
||||
<rect x="43" y="97" width="22" height="44" rx="11" fill="#558E79"/>
|
||||
<rect x="191" y="97" width="22" height="44" rx="11" fill="#558E79"/>
|
||||
<rect x="54" y="55" width="148" height="128" rx="42" fill="#558E79" opacity=".18" transform="translate(0 5)"/>
|
||||
<rect x="54" y="51" width="148" height="128" rx="42" fill="url(#explorer-shell)"/>
|
||||
<path d="M78 72c12-8 25-10 50-10" stroke="#FFFFFF" stroke-width="6" stroke-linecap="round" opacity=".9"/>
|
||||
<rect x="69" y="87" width="118" height="66" rx="25" fill="url(#explorer-screen)"/>
|
||||
<rect x="87" y="105" width="16" height="23" rx="8" fill="#D8EEE4"/>
|
||||
<rect x="153" y="105" width="16" height="23" rx="8" fill="#D8EEE4"/>
|
||||
<path d="M119 136q9 6 18 0" fill="none" stroke="#D8EEE4" stroke-width="3.5" stroke-linecap="round"/>
|
||||
<circle cx="79" cy="137" r="5" fill="#9DCCB9" opacity=".55"/>
|
||||
<circle cx="177" cy="137" r="5" fill="#9DCCB9" opacity=".55"/>
|
||||
<path d="M102 225h52" stroke="#9DCCB9" stroke-width="7" stroke-linecap="round"/>
|
||||
<path d="M114 239h28" stroke="#9DCCB9" stroke-width="5" stroke-linecap="round" opacity=".65"/>
|
||||
<circle cx="205" cy="204" r="29" fill="#FFFFFF" opacity=".28"/>
|
||||
<circle cx="205" cy="203" r="25" fill="#1D3E37"/>
|
||||
<g fill="none" stroke="#D8EEE4" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="204" cy="202" r="8"/><path d="m210 208 8 8"/></g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 34 KiB |
@@ -0,0 +1,35 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
|
||||
<defs>
|
||||
<linearGradient id="implementer-bg" x2=".8" y2="1"><stop stop-color="#E7EFFB"/><stop offset="1" stop-color="#A6BCE2"/></linearGradient>
|
||||
<linearGradient id="implementer-shell" x2=".3" y2="1"><stop stop-color="#FFFFFF"/><stop offset="1" stop-color="#E0EBFA"/></linearGradient>
|
||||
<linearGradient id="implementer-screen" x2=".7" y2="1"><stop stop-color="#253D60"/><stop offset="1" stop-color="#172A31"/></linearGradient>
|
||||
<clipPath id="implementer-clip"><rect width="256" height="256" rx="48"/></clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#implementer-clip)">
|
||||
<rect width="256" height="256" fill="url(#implementer-bg)"/>
|
||||
<circle cx="128" cy="95" r="94" fill="#FFFFFF" opacity=".2"/>
|
||||
<ellipse cx="128" cy="254" rx="94" ry="20" fill="#668CC3" opacity=".16"/>
|
||||
<path d="M48 256v-23c0-31 32-49 80-49s80 18 80 49v23" fill="#668CC3"/>
|
||||
<path d="M62 256v-23c0-24 26-40 66-40s66 16 66 40v23" fill="url(#implementer-shell)"/>
|
||||
<path d="M106 175h44v24c-11 9-33 9-44 0z" fill="#A6BCE2"/>
|
||||
<path d="M128 55V34" stroke="#668CC3" stroke-width="8" stroke-linecap="round"/>
|
||||
<circle cx="128" cy="30" r="10" fill="#668CC3"/>
|
||||
<circle cx="125" cy="27" r="4" fill="#FFFFFF" opacity=".7"/>
|
||||
<rect x="43" y="97" width="22" height="44" rx="11" fill="#668CC3"/>
|
||||
<rect x="191" y="97" width="22" height="44" rx="11" fill="#668CC3"/>
|
||||
<rect x="54" y="55" width="148" height="128" rx="42" fill="#668CC3" opacity=".18" transform="translate(0 5)"/>
|
||||
<rect x="54" y="51" width="148" height="128" rx="42" fill="url(#implementer-shell)"/>
|
||||
<path d="M78 72c12-8 25-10 50-10" stroke="#FFFFFF" stroke-width="6" stroke-linecap="round" opacity=".9"/>
|
||||
<rect x="69" y="87" width="118" height="66" rx="25" fill="url(#implementer-screen)"/>
|
||||
<rect x="87" y="105" width="16" height="23" rx="8" fill="#E0EBFA"/>
|
||||
<rect x="153" y="105" width="16" height="23" rx="8" fill="#E0EBFA"/>
|
||||
<path d="M119 136q9 6 18 0" fill="none" stroke="#E0EBFA" stroke-width="3.5" stroke-linecap="round"/>
|
||||
<circle cx="79" cy="137" r="5" fill="#A6BCE2" opacity=".55"/>
|
||||
<circle cx="177" cy="137" r="5" fill="#A6BCE2" opacity=".55"/>
|
||||
<path d="M102 225h52" stroke="#A6BCE2" stroke-width="7" stroke-linecap="round"/>
|
||||
<path d="M114 239h28" stroke="#A6BCE2" stroke-width="5" stroke-linecap="round" opacity=".65"/>
|
||||
<circle cx="205" cy="204" r="29" fill="#FFFFFF" opacity=".28"/>
|
||||
<circle cx="205" cy="203" r="25" fill="#253D60"/>
|
||||
<g fill="none" stroke="#E0EBFA" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><path d="m199 195-7 8 7 8m13-16 7 8-7 8m-7-19-3 22"/></g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,35 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
|
||||
<defs>
|
||||
<linearGradient id="reviewer-bg" x2=".8" y2="1"><stop stop-color="#EDEAF7"/><stop offset="1" stop-color="#B9AADC"/></linearGradient>
|
||||
<linearGradient id="reviewer-shell" x2=".3" y2="1"><stop stop-color="#FFFFFF"/><stop offset="1" stop-color="#E9E1F6"/></linearGradient>
|
||||
<linearGradient id="reviewer-screen" x2=".7" y2="1"><stop stop-color="#3D3258"/><stop offset="1" stop-color="#172A31"/></linearGradient>
|
||||
<clipPath id="reviewer-clip"><rect width="256" height="256" rx="48"/></clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#reviewer-clip)">
|
||||
<rect width="256" height="256" fill="url(#reviewer-bg)"/>
|
||||
<circle cx="128" cy="95" r="94" fill="#FFFFFF" opacity=".2"/>
|
||||
<ellipse cx="128" cy="254" rx="94" ry="20" fill="#8770B5" opacity=".16"/>
|
||||
<path d="M48 256v-23c0-31 32-49 80-49s80 18 80 49v23" fill="#8770B5"/>
|
||||
<path d="M62 256v-23c0-24 26-40 66-40s66 16 66 40v23" fill="url(#reviewer-shell)"/>
|
||||
<path d="M106 175h44v24c-11 9-33 9-44 0z" fill="#B9AADC"/>
|
||||
<path d="M128 55V34" stroke="#8770B5" stroke-width="8" stroke-linecap="round"/>
|
||||
<circle cx="128" cy="30" r="10" fill="#8770B5"/>
|
||||
<circle cx="125" cy="27" r="4" fill="#FFFFFF" opacity=".7"/>
|
||||
<rect x="43" y="97" width="22" height="44" rx="11" fill="#8770B5"/>
|
||||
<rect x="191" y="97" width="22" height="44" rx="11" fill="#8770B5"/>
|
||||
<rect x="54" y="55" width="148" height="128" rx="42" fill="#8770B5" opacity=".18" transform="translate(0 5)"/>
|
||||
<rect x="54" y="51" width="148" height="128" rx="42" fill="url(#reviewer-shell)"/>
|
||||
<path d="M78 72c12-8 25-10 50-10" stroke="#FFFFFF" stroke-width="6" stroke-linecap="round" opacity=".9"/>
|
||||
<rect x="69" y="87" width="118" height="66" rx="25" fill="url(#reviewer-screen)"/>
|
||||
<rect x="87" y="105" width="16" height="23" rx="8" fill="#E9E1F6"/>
|
||||
<rect x="153" y="105" width="16" height="23" rx="8" fill="#E9E1F6"/>
|
||||
<path d="M119 136q9 6 18 0" fill="none" stroke="#E9E1F6" stroke-width="3.5" stroke-linecap="round"/>
|
||||
<circle cx="79" cy="137" r="5" fill="#B9AADC" opacity=".55"/>
|
||||
<circle cx="177" cy="137" r="5" fill="#B9AADC" opacity=".55"/>
|
||||
<path d="M102 225h52" stroke="#B9AADC" stroke-width="7" stroke-linecap="round"/>
|
||||
<path d="M114 239h28" stroke="#B9AADC" stroke-width="5" stroke-linecap="round" opacity=".65"/>
|
||||
<circle cx="205" cy="204" r="29" fill="#FFFFFF" opacity=".28"/>
|
||||
<circle cx="205" cy="203" r="25" fill="#3D3258"/>
|
||||
<g fill="none" stroke="#E9E1F6" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><path d="m205 190 13 5v9c0 8-8 13-13 15-5-2-13-7-13-15v-9z"/><path d="m199 203 4 4 8-9"/></g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,35 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
|
||||
<defs>
|
||||
<linearGradient id="verifier-bg" x2=".8" y2="1"><stop stop-color="#FAF0DE"/><stop offset="1" stop-color="#E6C384"/></linearGradient>
|
||||
<linearGradient id="verifier-shell" x2=".3" y2="1"><stop stop-color="#FFFFFF"/><stop offset="1" stop-color="#F8E9CB"/></linearGradient>
|
||||
<linearGradient id="verifier-screen" x2=".7" y2="1"><stop stop-color="#55412B"/><stop offset="1" stop-color="#172A31"/></linearGradient>
|
||||
<clipPath id="verifier-clip"><rect width="256" height="256" rx="48"/></clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#verifier-clip)">
|
||||
<rect width="256" height="256" fill="url(#verifier-bg)"/>
|
||||
<circle cx="128" cy="95" r="94" fill="#FFFFFF" opacity=".2"/>
|
||||
<ellipse cx="128" cy="254" rx="94" ry="20" fill="#B98B42" opacity=".16"/>
|
||||
<path d="M48 256v-23c0-31 32-49 80-49s80 18 80 49v23" fill="#B98B42"/>
|
||||
<path d="M62 256v-23c0-24 26-40 66-40s66 16 66 40v23" fill="url(#verifier-shell)"/>
|
||||
<path d="M106 175h44v24c-11 9-33 9-44 0z" fill="#E6C384"/>
|
||||
<path d="M128 55V34" stroke="#B98B42" stroke-width="8" stroke-linecap="round"/>
|
||||
<circle cx="128" cy="30" r="10" fill="#B98B42"/>
|
||||
<circle cx="125" cy="27" r="4" fill="#FFFFFF" opacity=".7"/>
|
||||
<rect x="43" y="97" width="22" height="44" rx="11" fill="#B98B42"/>
|
||||
<rect x="191" y="97" width="22" height="44" rx="11" fill="#B98B42"/>
|
||||
<rect x="54" y="55" width="148" height="128" rx="42" fill="#B98B42" opacity=".18" transform="translate(0 5)"/>
|
||||
<rect x="54" y="51" width="148" height="128" rx="42" fill="url(#verifier-shell)"/>
|
||||
<path d="M78 72c12-8 25-10 50-10" stroke="#FFFFFF" stroke-width="6" stroke-linecap="round" opacity=".9"/>
|
||||
<rect x="69" y="87" width="118" height="66" rx="25" fill="url(#verifier-screen)"/>
|
||||
<rect x="87" y="105" width="16" height="23" rx="8" fill="#F8E9CB"/>
|
||||
<rect x="153" y="105" width="16" height="23" rx="8" fill="#F8E9CB"/>
|
||||
<path d="M119 136q9 6 18 0" fill="none" stroke="#F8E9CB" stroke-width="3.5" stroke-linecap="round"/>
|
||||
<circle cx="79" cy="137" r="5" fill="#E6C384" opacity=".55"/>
|
||||
<circle cx="177" cy="137" r="5" fill="#E6C384" opacity=".55"/>
|
||||
<path d="M102 225h52" stroke="#E6C384" stroke-width="7" stroke-linecap="round"/>
|
||||
<path d="M114 239h28" stroke="#E6C384" stroke-width="5" stroke-linecap="round" opacity=".65"/>
|
||||
<circle cx="205" cy="204" r="29" fill="#FFFFFF" opacity=".28"/>
|
||||
<circle cx="205" cy="203" r="25" fill="#55412B"/>
|
||||
<g fill="none" stroke="#F8E9CB" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><path d="m194 203 8 8 15-17"/></g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"version": 2,
|
||||
"commonInstructions": [
|
||||
"你是 YCode 的通用开发角色。根据当前仓库、技术栈和用户目标工作,不预设编程语言、框架、行业、项目目录或外部服务。",
|
||||
"先阅读适用的项目约定,使用真实文件、运行记录和明确输入作为依据;区分已确认事实、推测、已实施和未验证内容。",
|
||||
"作为委派成员时,只处理主智能体明确下发的任务,遵守允许修改的文件范围,不扩大目标,不创建嵌套子任务;整体需求、架构取舍、冲突处理与最终验收由主智能体负责。",
|
||||
"只使用宿主实际提供的工具和权限;角色说明不授予额外能力。需要但没有命令、浏览器、联网或其他工具时,明确报告主智能体继续处理,不虚构操作或结果。",
|
||||
"专业技能仅在任务确有需要且实际可用时按需读取,不默认加载语言、文档格式、设计系统或算法专项技能。",
|
||||
"报告保持简洁,包含检查或修改的文件、关键结论与证据、实际验证及结果、遗留限制和需要主智能体处理的事项。"
|
||||
],
|
||||
"agents": [
|
||||
{
|
||||
"role": "explorer",
|
||||
"avatar": "./avatars/explorer.png",
|
||||
"id": "senior-project-manager",
|
||||
"agentName": "senior-project-manager",
|
||||
"displayName": { "zh": "探索", "en": "Explorer" },
|
||||
"profession": { "zh": "代码探索员", "en": "Code Explorer" },
|
||||
"description": "Read-only codebase exploration: locate entry points, trace calls and state transitions, and identify constraints and affected files before implementation.",
|
||||
"displayDescription": { "zh": "负责梳理任意代码库的结构、调用链、关键约束与改动影响,基于文件证据说明结论,默认只读。", "en": "Maps code structure, execution paths, constraints and impact with file evidence. Read-only by default." },
|
||||
"tags": [{ "zh": "代码定位", "en": "Code Mapping" }, { "zh": "调用链", "en": "Call Paths" }, { "zh": "影响范围", "en": "Impact Analysis" }],
|
||||
"quickPrompts": [
|
||||
{ "zh": "请梳理当前需求涉及的代码路径、关键约束和改动影响,给出文件依据,先不修改代码。", "en": "Map the code paths, constraints and impact of the current requirement with file references, without editing code." },
|
||||
{ "zh": "请定位当前功能从入口到执行结果的完整调用链与状态变化。", "en": "Trace the current feature from entry point to result, including state transitions." },
|
||||
{ "zh": "请检查已有实现、可复用模块和测试入口,列出尚不明确的事项。", "en": "Identify existing implementations, reusable modules, test entry points and unresolved questions." }
|
||||
],
|
||||
"instructions": [
|
||||
"职责是搜集和解释代码事实,为后续实现提供有依据的地图。接手仓库、定位功能或评估影响时调用。",
|
||||
"默认只读,不编辑代码、配置、文档或测试,不执行会改变仓库状态的操作。",
|
||||
"优先定向搜索和必要文件读取,追踪真实入口、调用关系、数据与状态流、错误处理及测试入口;避免逐文件漫游或堆砌整段源码。",
|
||||
"输出关键文件与符号、执行路径、约束和影响范围;未知项说明缺少什么证据。方案取舍交给主智能体,不擅自开始实现。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"role": "implementer",
|
||||
"avatar": "./avatars/implementer.png",
|
||||
"id": "cpp-super-expert",
|
||||
"agentName": "cpp-super-expert",
|
||||
"displayName": { "zh": "实现", "en": "Implementer" },
|
||||
"profession": { "zh": "实现工程师", "en": "Implementation Engineer" },
|
||||
"description": "Implement a clearly scoped feature or refactor after requirements and interfaces are agreed; modify only assigned files and validate changed behavior.",
|
||||
"displayDescription": { "zh": "负责按明确需求完成跨语言、跨框架的代码实现与局部重构,遵守文件边界,交付可验证的完整改动。", "en": "Implements scoped features and refactors across languages and frameworks, with explicit file ownership and validation." },
|
||||
"tags": [{ "zh": "功能实现", "en": "Implementation" }, { "zh": "局部重构", "en": "Scoped Refactoring" }, { "zh": "行为验证", "en": "Validation" }],
|
||||
"quickPrompts": [
|
||||
{ "zh": "请按当前需求实现最小完整改动,遵守项目约定,验证相关行为并说明结果。", "en": "Implement the smallest complete change for the current requirement, follow project conventions and validate affected behavior." },
|
||||
{ "zh": "请在约定的文件范围内完成这个模块,并保持已确认的接口和兼容行为。", "en": "Implement this module within the assigned files while preserving agreed interfaces and compatibility." },
|
||||
{ "zh": "请对指定代码做有明确目标的局部重构,并验证行为没有意外改变。", "en": "Perform a targeted refactor of the specified code and verify behavior has not changed unexpectedly." }
|
||||
],
|
||||
"instructions": [
|
||||
"职责是交付已明确目标的实现。需求、接口或文件归属存在阻断性歧义时,先将具体问题报告主智能体;可以继续不依赖该决定的工作。",
|
||||
"只修改获准的文件,保留已有用户改动。跨模块接口、公共契约、依赖升级和范围扩展由主智能体决定。",
|
||||
"优先复用现有结构和组件;改动应覆盖必要的错误处理与兼容路径,避免无关重构。",
|
||||
"运行与改动相称的验证;必要时补充能发现真实行为回归的测试,不为低影响改动堆砌重复断言。",
|
||||
"交付文件清单、行为变化、验证命令和实际结果;未运行的检查与仍待集成的部分明确标注。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"role": "verifier",
|
||||
"avatar": "./avatars/verifier.png",
|
||||
"id": "ui-designer",
|
||||
"agentName": "ui-designer",
|
||||
"displayName": { "zh": "验收", "en": "Verifier" },
|
||||
"profession": { "zh": "测试验收员", "en": "Test and Acceptance Engineer" },
|
||||
"description": "Validate implemented behavior using relevant tests and reproducible acceptance steps; report failures, actual results and coverage gaps independently from implementation.",
|
||||
"displayDescription": { "zh": "负责围绕当前改动执行适当测试和实际行为验收,覆盖关键路径与边界条件,清楚报告结果及未覆盖项。", "en": "Checks implemented behavior with focused tests, reproducible acceptance steps and explicit coverage limits." },
|
||||
"tags": [{ "zh": "测试执行", "en": "Test Execution" }, { "zh": "行为验收", "en": "Acceptance" }, { "zh": "边界回归", "en": "Regression" }],
|
||||
"quickPrompts": [
|
||||
{ "zh": "请围绕本次改动执行适当测试与实际行为验收,给出复现步骤、运行结果和未覆盖项。", "en": "Validate this change with appropriate tests and behavior checks; report steps, actual results and untested areas." },
|
||||
{ "zh": "请检查关键路径、边界条件和失败恢复,整理能够复现的问题。", "en": "Check critical paths, edge cases and failure recovery, and document reproducible issues." },
|
||||
{ "zh": "请核对现有测试是否覆盖本次行为变化,只补充确有必要的回归验证。", "en": "Check whether existing tests cover the behavior change and add only necessary regression coverage." }
|
||||
],
|
||||
"instructions": [
|
||||
"职责是独立验证实际行为是否满足验收条件,不以实现者的完成声明代替证据。",
|
||||
"先选择与改动有关的已有测试和实际操作路径,覆盖正常、异常、取消与恢复等适用状态;根据具体任务决定是否需要界面、接口或持久化检查。",
|
||||
"只有获准时才补充或修改指定测试文件;发现产品缺陷时记录问题并交给实现者或主智能体,不通过放宽断言、跳过失败或改业务代码制造通过。",
|
||||
"记录真实命令、退出状态、关键输出与复现步骤;构建通过、mock通过和真实端到端验收分别报告。",
|
||||
"工具不可用、依赖缺失或条件不具备时,报告阻碍和尚未验收的范围;不将计划执行写成已经执行。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"role": "reviewer",
|
||||
"avatar": "./avatars/reviewer.png",
|
||||
"id": "senior-algorithm-expert",
|
||||
"agentName": "senior-algorithm-engineer",
|
||||
"displayName": { "zh": "审查", "en": "Reviewer" },
|
||||
"profession": { "zh": "代码审查员", "en": "Code Reviewer" },
|
||||
"description": "Independently review a change for concrete correctness bugs, regressions, concurrency risks and missing tests; return evidence-based findings without editing files.",
|
||||
"displayDescription": { "zh": "负责独立审查代码正确性、回归风险、边界处理和测试缺口,提供可复现的问题与文件依据,默认只读。", "en": "Independently reviews correctness, regressions, edge cases and test gaps with reproducible findings. Read-only by default." },
|
||||
"tags": [{ "zh": "正确性", "en": "Correctness" }, { "zh": "回归风险", "en": "Regression Risks" }, { "zh": "独立审查", "en": "Independent Review" }],
|
||||
"quickPrompts": [
|
||||
{ "zh": "请独立审查当前改动,优先检查正确性、回归、边界和测试缺口,给出问题及文件依据,先不修改代码。", "en": "Independently review the current change for correctness, regressions, edge cases and test gaps, with file evidence and no edits." },
|
||||
{ "zh": "请沿真实调用路径检查这次修改是否引入状态、并发或资源管理问题。", "en": "Trace actual execution paths for state, concurrency and resource-lifetime problems introduced by this change." },
|
||||
{ "zh": "请复核这些审查发现,确认触发条件和影响,剔除没有依据的推测。", "en": "Verify these review findings, confirm triggers and impact, and discard unsupported speculation." }
|
||||
],
|
||||
"instructions": [
|
||||
"职责是独立发现有依据的缺陷。默认只读,不编辑产品代码、配置、文档或测试,不代替实现者修复。",
|
||||
"先理解改动目标与差异,再沿相关调用路径核对兼容性、边界处理、状态一致性、资源生命周期和测试覆盖。涉及权限、数据或性能的改动按实际影响检查。",
|
||||
"每个发现给出严重程度、文件位置、具体触发条件、可复现路径或证据和实际影响;区分确认缺陷与待验证风险。",
|
||||
"优先报告正确性和回归问题,避免纯风格偏好、无关重构建议与假设性告警。没有确认问题时如实说明审查范围和剩余验证缺口。",
|
||||
"修复是否采用及如何集成由主智能体决定,不扩大审查目标或改写已有任务。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"role": "debugger",
|
||||
"avatar": "./avatars/debugger.png",
|
||||
"id": "development-debugger",
|
||||
"agentName": "development-debugger",
|
||||
"displayName": { "zh": "诊断", "en": "Debugger" },
|
||||
"profession": { "zh": "问题诊断员", "en": "Root Cause Investigator" },
|
||||
"description": "Investigate a concrete error, failed test, hang or unexpected behavior; reproduce it, test hypotheses and establish the root cause before a minimal fix.",
|
||||
"displayDescription": { "zh": "负责复现异常、分析日志和状态变化并定位根因,区分事实与假设,给出最小修复方案和可执行验证步骤。", "en": "Reproduces failures, tests hypotheses and identifies root causes before proposing a minimal fix and verification." },
|
||||
"tags": [{ "zh": "问题复现", "en": "Reproduction" }, { "zh": "根因定位", "en": "Root Cause" }, { "zh": "修复验证", "en": "Fix Verification" }],
|
||||
"quickPrompts": [
|
||||
{ "zh": "请调查当前问题,先复现并用证据定位根因,再提出最小修复与验证方案。", "en": "Investigate this issue, reproduce it and establish the root cause with evidence before proposing a minimal fix and validation." },
|
||||
{ "zh": "请分析这段报错、日志或失败记录,区分直接现象、根因和仍需验证的假设。", "en": "Analyze this error, log or failure record; distinguish symptoms, root causes and hypotheses requiring verification." },
|
||||
{ "zh": "请定位这个偶发异常的触发条件与状态变化,给出能够验证判断的复现方法。", "en": "Identify the triggers and state transitions behind this intermittent failure and provide a reproducible validation method." }
|
||||
],
|
||||
"instructions": [
|
||||
"职责是调查已经出现的具体异常,与代码探索的结构梳理和代码审查的改动检查区分。先定位根因,再考虑修复。",
|
||||
"收集报错、堆栈、日志、环境和最近改动,建立最短可行复现步骤;每个假设说明支持证据及可证伪的检查方法。",
|
||||
"跟踪真实数据与状态变化,避免凭现象直接修改代码或只掩盖错误。未复现时明确已排除什么、还缺少什么。",
|
||||
"默认只做诊断和修复建议;只有主智能体明确授权修复且指定文件范围时才编辑,采用最小改动并验证原问题与相关回归。",
|
||||
"交付复现条件、根因证据、修复建议或实际改动、验证结果及未解决事项。"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 132 KiB |
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 models.dev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"source": "https://models.dev/api.json",
|
||||
"repository": "https://github.com/anomalyco/models.dev",
|
||||
"repositoryRevisionAtFetch": "8deeec23540f4c0e2c473be7f08130366e662c78",
|
||||
"fetchedAt": "2026-10-17T22:29:14.523Z",
|
||||
"sha256": "5980b554323ea4f118b06b960dde1eeb193f5bfa4722d6127fb3966d77195a1c",
|
||||
"bytes": 5796868,
|
||||
"providers": 232,
|
||||
"models": 9041
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"deepinfra":{"id":"deepinfra","env":["DEEPINFRA_API_KEY"],"npm":"@ai-sdk/deepinfra","name":"Deep Infra","doc":"https://deepinfra.com/models","models":{"anthropic/claude-fable-5":{"id":"anthropic/claude-fable-5","name":"Claude Fable 5","description":"Claude model for creative writing, analysis, and controlled agent workflows","family":"claude-fable","attachment":true,"reasoning":true,"reasoning_options":[{"type":"effort","values":["low","medium","high","xhigh","max"]}],"tool_call":true,"structured_output":true,"temperature":false,"knowledge":"2026-01-31","release_date":"2026-06-09","last_updated":"2026-06-09","modalities":{"input":["text","image","pdf"],"output":["text"]},"open_weights":false,"limit":{"context":1000000,"output":128000},"cost":{"input":10,"output":50},"canonical_model_id":"anthropic/claude-fable-5"},"tencent/Hy3":{"id":"tencent/Hy3","name":"Hy3","description":"Tencent Hy reasoning model for coding, instruction following, and agent tasks","family":"Hy","attachment":false,"reasoning":true,"reasoning_options":[],"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-07-06","last_updated":"2026-07-06","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"limit":{"context":262144,"input":192000,"output":128000},"status":"deprecated","cost":{"input":0.13,"output":0.53,"cache_read":0.033},"canonical_model_id":"tencent/hy3"},"tencent/Hy4-preview":{"id":"tencent/Hy4-preview","name":"Hy4 preview","description":"A next-generation productivity model with significantly enhanced Agent and complex task execution capabilities.","family":"Hy","attachment":false,"reasoning":true,"reasoning_options":[{"type":"effort","values":["none","high"]}],"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-08-28","last_updated":"2026-08-28","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"limit":{"context":1048576,"output":64000},"cost":{"input":0.834,"output":2.501,"cache_read":0.042},"canonical_model_id":"tencent/hy4-preview"},"meta-llama/Llama-3.3-70B-Instruct-Turbo":{"id":"meta-llama/Llama-3.3-70B-Instruct-Turbo","name":"Llama 3.3 70B Turbo","description":"Compact Llama instruction model for fast chat and local deployment","family":"llama","attachment":false,"reasoning":false,"tool_call":true,"structured_output":true,"release_date":"2024-12-06","last_updated":"2024-12-06","modalities":{"input":["text"],"output":["text"]},"open_weights":true,"limit":{"context":131072,"output":16384},"cost":{"input":0.1,"output":0.32}},"meta-llama/Llama-4-Scout-17B-16E-Instruct":{"id":"meta-llama/Llama-4-Scout-17B-16E-Instruct","name":"Llama 4 Scout 17B","description":"Open multimodal Llama model for long-context analysis and efficient agents","family":"llama","attachment":true,"reasoning":false,"tool_call":true,"structured_output":true,"release_date":"2025-04-05","last_updated":"2025-04-05","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"limit":{"context":327680,"output":16384},"cost":{"input":0.1,"output":0.3}},"meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8":{"id":"meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8","name":"Llama 4 Maverick 17B FP8","description":"Open multimodal Llama model for strong reasoning and fast responses","family":"llama","attachment":true,"reasoning":false,"tool_call":false,"structured_output":true,"release_date":"2025-04-05","last_updated":"2025-04-05","modalities":{"input":["text","image"],"output":["text"]},"open_weights":true,"limit":{"context":1048576,"output":16384},"status":"deprecated","cost":{"input":0.2,"output":0.8}},"XiaomiMiMo/MiMo-V2.6-Pro":{"id":"XiaomiMiMo/MiMo-V2.6-Pro","name":"MiMo-V2.6-Pro","description":"Stronger MiMo Pro tier for multimodal reasoning and coding-agent execution","family":"mimo","attachment":true,"reasoning":true,"reasoning_options":[{"type":"toggle"}],"tool_call":true,"structured_output":true,"temperature":true,"release_date":"2026-09-22","last_updated":"2026-09-22","modalities":{"input":["text","image","audio","video"],"output":["text"]},"open_weights":true,"limit":{"context":1048576,"output":131072},"cost":{"input":0.43,"output":0.87,"cache_read":0.0036},"canonical_model_id":"xiaomi/mimo-v2.6-pro"},"XiaomiMiMo/MiMo-V2.5-Pro":{"id":"XiaomiMiMo/MiMo-V2.5-Pro","name":"MiMo-V2.5-Pro","description":"Stronger MiMo Pro tier for multimodal reasoning and coding-agent execution","family":"mimo","attachment":true,"reasoning":true,"reasoning_options":[{"type":"toggle"}],"tool_call":true,"interleaved":{"field":"reasoning_content"},"structured_output":true,"temperature":true,"knowledge":"2024-12","release_date":"2026-04-22","last_updated":"2026-04-22","modalities":{"input":["text","audio"],"output":["text"]},"open_weights":true,"limit":{"context":1048576,"output":16384},"status":"deprecated","cost":{"input":1,"output":3,"cache_read":0.2},"canonical_model_id":"xiaomi/mimo-v2.5-pro"},"XiaomiMiMo/MiMo-V2.6-Flash":{"id":"XiaomiMiMo/MiMo-V2.6-Flash","name":"MLine truncated
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"format": 1,
|
||||
"kernel": "omp",
|
||||
"upstream": "oh-my-pi",
|
||||
"version": "18.6.3",
|
||||
"platform": "win32-x64",
|
||||
"releaseUrl": "https://github.com/can1357/oh-my-pi/releases/tag/v18.6.3",
|
||||
"publishedAt": "2026-10-06T13:17:27Z",
|
||||
"binary": {
|
||||
"name": "omp.exe",
|
||||
"bytes": 235715072,
|
||||
"sha256": "453e8ecd17f36e0b7faba2abc761206fe72d16b97fbacbe1281831ad9fa86482"
|
||||
},
|
||||
"license": {
|
||||
"name": "LICENSE",
|
||||
"bytes": 1144,
|
||||
"sha256": "16c45f9d667442781f03fa198914cc39abcaa48ec5ed8f644643e554ca2fbf63"
|
||||
},
|
||||
"thirdPartyNotices": {
|
||||
"name": "THIRD-PARTY-NOTICES.txt",
|
||||
"bytes": 1076437,
|
||||
"sha256": "d0c2e7c05bb4d755044b13fa560be58d01ab7c980b87892400a979397e569a8b"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"format": 1,
|
||||
"provider": "git-for-windows",
|
||||
"version": "2.56.0.windows.2",
|
||||
"platform": "win32-x64",
|
||||
"runtimeManifestSha256": "17cbe59d1fdd9230e528be9ec6402b870637d5e7148215f278bbaff01b92ed01",
|
||||
"publishedAt": "2026-10-05T21:42:10Z",
|
||||
"releaseUrl": "https://github.com/git-for-windows/git/releases/tag/v2.56.0.windows.2",
|
||||
"archive": {
|
||||
"name": "MinGit-2.56.0.2-64-bit.zip",
|
||||
"url": "https://github.com/git-for-windows/git/releases/download/v2.56.0.windows.2/MinGit-2.56.0.2-64-bit.zip",
|
||||
"bytes": 39806486,
|
||||
"sha256": "da35e72aa21c005a5a0d298cfbae110bc1609a815730ea0dde84b01a1b3cd3be"
|
||||
},
|
||||
"sources": {
|
||||
"git": "https://github.com/git-for-windows/git/tree/v2.56.0.windows.2",
|
||||
"gitSourceArchive": "https://github.com/git-for-windows/git/archive/refs/tags/v2.56.0.windows.2.tar.gz",
|
||||
"windowsBuild": "https://github.com/git-for-windows/build-extra",
|
||||
"dependencyBuildRecipes": "https://github.com/git-for-windows/MINGW-packages"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
'use strict'
|
||||
const fs = require('node:fs')
|
||||
const { randomUUID, createHash } = require('node:crypto')
|
||||
const prefix = '\x1eYCODE_WORKSPACE_READ_V1 '
|
||||
let selected = false
|
||||
function error(code, message) { return Object.assign(new Error(message), { code }) }
|
||||
function frame(operation, args, id) { return '\n' + prefix + JSON.stringify({ version: 1, requestId: id, operation, args }) + '\n' }
|
||||
function send(operation, args, id) {
|
||||
const bytes = Buffer.from(frame(operation, args, id))
|
||||
let offset = 0
|
||||
while (offset < bytes.length) offset += fs.writeSync(1, bytes, offset, bytes.length - offset)
|
||||
}
|
||||
function unpack(line, id) {
|
||||
let response
|
||||
try { response = JSON.parse(line) } catch { throw error('EPROTO', 'Invalid file bridge reply') }
|
||||
if (response.version !== 1 || response.requestId !== id || typeof response.ok !== 'boolean') throw error('EPROTO', 'Unexpected file bridge reply')
|
||||
if (!response.ok) throw error(response.error?.code || 'EACCES', response.error?.message || 'Workspace read denied')
|
||||
return response.value
|
||||
}
|
||||
function claim() {
|
||||
if (selected) throw error('EBUSY', 'Choose one bridge client and mode per process; stdin belongs to that client')
|
||||
selected = true
|
||||
}
|
||||
function decode(part, initial, offset) {
|
||||
if (!part || part.snapshotId !== initial.snapshotId || part.sha256 !== initial.sha256 || part.size !== initial.size || part.offset !== offset || typeof part.data !== 'string') throw error('EPROTO', 'Inconsistent read snapshot')
|
||||
const bytes = Buffer.from(part.data, 'base64')
|
||||
if (bytes.length > 16384 || !bytes.length && offset < initial.size || offset + bytes.length > initial.size || part.eof !== (offset + bytes.length === initial.size)) throw error('EPROTO', 'Invalid read chunk')
|
||||
return bytes
|
||||
}
|
||||
function complete(chunks, initial, encoding) {
|
||||
const bytes = Buffer.concat(chunks)
|
||||
if (bytes.length !== initial.size || createHash('sha256').update(bytes).digest('hex') !== initial.sha256) throw error('EPROTO', 'Read snapshot digest mismatch')
|
||||
return encoding ? bytes.toString(encoding) : bytes
|
||||
}
|
||||
function connectSync() {
|
||||
claim()
|
||||
let buffered = Buffer.alloc(0), closed = false
|
||||
function call(operation, args, id = randomUUID()) {
|
||||
if (closed) throw error('EPIPE', 'File bridge client closed')
|
||||
send(operation, args, id)
|
||||
for (;;) {
|
||||
const end = buffered.indexOf(10)
|
||||
if (end >= 0) {
|
||||
const line = buffered.subarray(0, end).toString('utf8'); buffered = buffered.subarray(end + 1)
|
||||
return unpack(line, id)
|
||||
}
|
||||
if (buffered.length > 32768) throw error('EPROTO', 'File bridge reply exceeds limit')
|
||||
const bytes = Buffer.alloc(32768), length = fs.readSync(0, bytes, 0, bytes.length, null)
|
||||
if (!length) { closed = true; throw error('EPIPE', 'File bridge disconnected') }
|
||||
buffered = Buffer.concat([buffered, bytes.subarray(0, length)])
|
||||
}
|
||||
}
|
||||
return {
|
||||
call,
|
||||
readFileSync(path, encoding) {
|
||||
const initial = call('workspace.readFile', { path }), chunks = []
|
||||
try {
|
||||
let part = initial, offset = 0
|
||||
for (;;) {
|
||||
const bytes = decode(part, initial, offset); chunks.push(bytes); offset += bytes.length
|
||||
if (part.eof) return complete(chunks, initial, encoding)
|
||||
part = call('workspace.readChunk', { snapshotId: initial.snapshotId, offset })
|
||||
}
|
||||
} finally { call('workspace.closeRead', { snapshotId: initial.snapshotId }) }
|
||||
},
|
||||
statSync: path => call('workspace.stat', { path }),
|
||||
readdirSync: path => call('workspace.readdir', { path }).entries,
|
||||
querySync: requestId => call('operation.query', { requestId }),
|
||||
close() { closed = true }
|
||||
}
|
||||
}
|
||||
function connect() {
|
||||
claim()
|
||||
const waiting = new Map(), queue = []
|
||||
let buffered = Buffer.alloc(0), closed = false, failure
|
||||
function shutdown(reason) {
|
||||
if (closed) return
|
||||
closed = true; failure = reason
|
||||
process.stdin.off('data', receive); process.stdin.off('end', ended); process.stdin.off('error', failed); process.stdin.pause()
|
||||
process.stdin.destroy()
|
||||
for (const item of [...waiting.values(), ...queue]) item.reject(reason)
|
||||
waiting.clear(); queue.length = 0
|
||||
}
|
||||
function flush() {
|
||||
while (!closed && waiting.size < 8 && queue.length) {
|
||||
const item = queue.shift(); waiting.set(item.id, item)
|
||||
try { send(item.operation, item.args, item.id) } catch (reason) { shutdown(reason) }
|
||||
}
|
||||
if (waiting.size) { process.stdin.ref?.(); process.stdin.resume() }
|
||||
else { process.stdin.pause(); process.stdin.unref?.() }
|
||||
}
|
||||
function receive(bytes) {
|
||||
buffered = Buffer.concat([buffered, bytes])
|
||||
for (;;) {
|
||||
const end = buffered.indexOf(10)
|
||||
if (end < 0) { if (buffered.length > 32768) shutdown(error('EPROTO', 'File bridge reply exceeds limit')); return }
|
||||
const line = buffered.subarray(0, end).toString('utf8'); buffered = buffered.subarray(end + 1)
|
||||
let id
|
||||
try { id = JSON.parse(line).requestId } catch { shutdown(error('EPROTO', 'Invalid file bridge reply')); return }
|
||||
const item = waiting.get(id)
|
||||
if (!item) { shutdown(error('EPROTO', 'Unexpected file bridge reply')); return }
|
||||
waiting.delete(id)
|
||||
try { item.resolve(unpack(line, id)) } catch (reason) { item.reject(reason) }
|
||||
flush()
|
||||
}
|
||||
}
|
||||
const ended = () => shutdown(error('EPIPE', 'File bridge disconnected'))
|
||||
const failed = reason => shutdown(reason)
|
||||
process.stdin.on('data', receive); process.stdin.on('end', ended); process.stdin.on('error', failed); process.stdin.pause()
|
||||
function call(operation, args, id = randomUUID()) {
|
||||
if (closed) return Promise.reject(failure || error('EPIPE', 'File bridge client closed'))
|
||||
if (queue.length + waiting.size >= 32) return Promise.reject(error('EBUSY', 'File bridge client queue full'))
|
||||
return new Promise((resolve, reject) => { queue.push({ id, operation, args, resolve, reject }); flush() })
|
||||
}
|
||||
return {
|
||||
call,
|
||||
async readFile(path, encoding) {
|
||||
const initial = await call('workspace.readFile', { path }), chunks = []
|
||||
try {
|
||||
let part = initial, offset = 0
|
||||
for (;;) {
|
||||
const bytes = decode(part, initial, offset); chunks.push(bytes); offset += bytes.length
|
||||
if (part.eof) return complete(chunks, initial, encoding)
|
||||
part = await call('workspace.readChunk', { snapshotId: initial.snapshotId, offset })
|
||||
}
|
||||
} finally { await call('workspace.closeRead', { snapshotId: initial.snapshotId }) }
|
||||
},
|
||||
stat: path => call('workspace.stat', { path }),
|
||||
readdir: async path => (await call('workspace.readdir', { path })).entries,
|
||||
query: requestId => call('operation.query', { requestId }),
|
||||
close() { shutdown(error('EPIPE', 'File bridge client closed')) }
|
||||
}
|
||||
}
|
||||
module.exports = { connect, connectSync }
|
||||
@@ -0,0 +1,70 @@
|
||||
'use strict'
|
||||
// Explicit preload for synchronous subprocess capture in Windows AppContainers.
|
||||
// Every subprocess still uses Node's own spawn and inherits the existing token/Job.
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const cp = require('node:child_process')
|
||||
const { syncBuiltinESMExports } = require('node:module')
|
||||
const originals = { spawnSync: cp.spawnSync, execFileSync: cp.execFileSync, execSync: cp.execSync }
|
||||
const maximum = 16 * 1024 * 1024
|
||||
function captured(method, file, args, options, spawn) {
|
||||
if (options.stdio !== undefined && options.stdio !== 'pipe') return method()
|
||||
const limit = options.maxBuffer === undefined ? 1024 * 1024 : options.maxBuffer
|
||||
if (!Number.isSafeInteger(limit) || limit < 0 || limit > maximum) throw new RangeError('Sandbox process adapter maxBuffer must be between 0 and 16 MiB')
|
||||
const input = options.input === undefined ? Buffer.alloc(0) : typeof options.input === 'string'
|
||||
? Buffer.from(options.input, options.encoding && options.encoding !== 'buffer' ? options.encoding : 'utf8')
|
||||
: ArrayBuffer.isView(options.input) ? Buffer.from(options.input.buffer, options.input.byteOffset, options.input.byteLength) : null
|
||||
if (!input) throw new TypeError('Sandbox process adapter input must be a string, Buffer or ArrayBuffer view')
|
||||
if (input.length > maximum) throw new RangeError('Sandbox process adapter input exceeds 16 MiB')
|
||||
const directory = fs.mkdtempSync(path.join(process.env.TEMP, 'ycode-process-'))
|
||||
const files = ['stdin', 'stdout', 'stderr'].map(name => path.join(directory, name)), handles = []
|
||||
const encode = bytes => options.encoding && options.encoding !== 'buffer' ? bytes.toString(options.encoding) : bytes
|
||||
let answer, failure
|
||||
try {
|
||||
fs.writeFileSync(files[0], input, { flag: 'wx', mode: 0o600 })
|
||||
handles.push(fs.openSync(files[0], 'r'))
|
||||
handles.push(fs.openSync(files[1], 'wx+'))
|
||||
handles.push(fs.openSync(files[2], 'wx+'))
|
||||
const forwarded = { ...options, stdio: handles }; delete forwarded.input
|
||||
try { answer = method(forwarded) } catch (error) { failure = error }
|
||||
const read = index => {
|
||||
const buffer = Buffer.alloc(Math.min(fs.fstatSync(handles[index]).size, limit + 1))
|
||||
let length = 0
|
||||
while (length < buffer.length) { const count = fs.readSync(handles[index], buffer, length, buffer.length - length, length); if (!count) break; length += count }
|
||||
return { bytes: buffer.subarray(0, Math.min(length, limit)), overflow: length > limit }
|
||||
}
|
||||
const out = read(1), err = read(2), stdout = encode(out.bytes), stderr = encode(err.bytes)
|
||||
if (out.overflow || err.overflow) failure = Object.assign(new Error('Sandbox subprocess output exceeds maxBuffer'), { code: 'ENOBUFS', syscall: 'spawnSync', path: file, spawnargs: args })
|
||||
if (spawn) {
|
||||
if (failure && !answer) throw failure
|
||||
return { ...answer, stdout, stderr, output: [null, stdout, stderr], ...(failure ? { error: failure } : {}) }
|
||||
}
|
||||
if (failure) {
|
||||
failure.stdout = stdout; failure.stderr = stderr; failure.output = [null, stdout, stderr]
|
||||
throw failure
|
||||
}
|
||||
return stdout
|
||||
} finally {
|
||||
for (const handle of handles) try { fs.closeSync(handle) } catch {}
|
||||
for (const file of files) try { fs.unlinkSync(file) } catch {}
|
||||
try { fs.rmdirSync(directory) } catch {}
|
||||
}
|
||||
}
|
||||
function argumentsOf(args, options) {
|
||||
return Array.isArray(args) ? { args, options: options || {} } : { args: [], options: args == null ? options || {} : args }
|
||||
}
|
||||
function spawnSync(file, args, options) {
|
||||
const normalized = argumentsOf(args, options)
|
||||
return captured(forwarded => originals.spawnSync(file, normalized.args, forwarded || normalized.options), file, normalized.args, normalized.options, true)
|
||||
}
|
||||
function execFileSync(file, args, options) {
|
||||
const normalized = argumentsOf(args, options)
|
||||
return captured(forwarded => originals.execFileSync(file, normalized.args, forwarded || normalized.options), file, normalized.args, normalized.options, false)
|
||||
}
|
||||
function execSync(command, options = {}) {
|
||||
return captured(forwarded => originals.execSync(command, forwarded || options), command, [], options, false)
|
||||
}
|
||||
if (process.env.YCODE_SANDBOX !== 'appcontainer') throw new Error('The subprocess adapter is for the isolated YCode Node runtime')
|
||||
Object.assign(cp, { spawnSync, execFileSync, execSync })
|
||||
syncBuiltinESMExports()
|
||||
module.exports = { spawnSync, execFileSync, execSync }
|
||||
@@ -0,0 +1,23 @@
|
||||
"""Keep private Windows temporary directories accessible to this AppContainer.
|
||||
|
||||
Python 3.13+ gives mode 0o700 an explicit user/admin DACL on Windows. That
|
||||
omits the AppContainer SID. Inside our stage, inherit the native private parent
|
||||
DACL instead. Kernel permissions and the Job remain the security boundary.
|
||||
"""
|
||||
import os
|
||||
|
||||
if os.name == "nt" and os.environ.get("YCODE_SANDBOX") == "appcontainer":
|
||||
_mkdir = os.mkdir
|
||||
_stage = os.path.normcase(os.path.abspath(os.environ["YCODE_SANDBOX_ROOT"]))
|
||||
|
||||
def _private_mkdir(path, mode=0o777, *, dir_fd=None):
|
||||
if mode == 0o700 and dir_fd is None:
|
||||
target = os.path.normcase(os.path.abspath(os.fsdecode(path)))
|
||||
try:
|
||||
if os.path.commonpath((_stage, target)) == _stage:
|
||||
mode = 0o777
|
||||
except ValueError:
|
||||
pass
|
||||
return _mkdir(path, mode, dir_fd=dir_fd)
|
||||
|
||||
os.mkdir = _private_mkdir
|
||||
|
After Width: | Height: | Size: 2.4 KiB |