feat: complete WorkBuddy parity and Windows release workflow

This commit is contained in:
xiaohan.yuan committed 2026-10-18 12:45:21 +08:00
1 parent 143516b3a8
commit d5512df549
434 files changed
+54977 -1854

No files matched your search

+26
View File
@@ -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。此脚本不修改子任务调度协议、并发上限、模型配置或工具授权,也不表示调度器已经支持按这里的角色自动选人。
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

+35
View File
@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

+35
View File
@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

+35
View File
@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

+35
View File
@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

+35
View File
@@ -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

+127
View File
@@ -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": [
"职责是调查已经出现的具体异常,与代码探索的结构梳理和代码审查的改动检查区分。先定位根因,再考虑修复。",
"收集报错、堆栈、日志、环境和最近改动,建立最短可行复现步骤;每个假设说明支持证据及可证伪的检查方法。",
"跟踪真实数据与状态变化,避免凭现象直接修改代码或只掩盖错误。未复现时明确已排除什么、还缺少什么。",
"默认只做诊断和修复建议;只有主智能体明确授权修复且指定文件范围时才编辑,采用最小改动并验证原问题与相关回归。",
"交付复现条件、根因证据、修复建议或实际改动、验证结果及未解决事项。"
]
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

+21
View File
@@ -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.
+10
View File
@@ -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
}
+1
View File
@@ -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
+24
View File
@@ -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"
}
}
+21
View File
@@ -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"
}
}
+134
View File
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB