.agents/design/core/ai/sandbox/skill-entrypoint.md
Agent Skill 需要支持初始化脚本,但入口脚本不能绑定到 sandbox 创建时机。运行态 sandbox 会复用,selected skill 版本会变化,应用配置里的 sandbox 层脚本也可能变化。更准确的模型是:每次进入运行态 useSandbox() 时,把当前 sandbox reconcile 到本轮需要的状态。
本方案覆盖运行态 sandbox bootstrap、sandbox entrypoint 与 Agent skill runtime,不改 projects/code-sandbox,不使用 sandbox provider 的容器 entrypoint。
sandboxEntrypoint,不写入 sandbox 工作区。./projects/<versionId>/entrypoint.sh。SKILL.md 扫描注入 prompt 前执行。versionId 已成功执行时跳过。entrypoint.sh。./entrypoint.sh。配置字段:
sandboxEntrypoint: string
运行时只在服务端通过 sandbox.execute() 临时传给 shell 执行。有效条件为:
sandboxEntrypoint.trim().length > 0
普通运行态把 selected skill 版本包部署到:
./projects/<versionId>/
只识别该目录根部的:
./projects/<versionId>/entrypoint.sh
不识别:
./entrypoint.sh
./projects/<versionId>/<skill-name>/entrypoint.sh
./projects/<versionId>/**/entrypoint.sh
包内 SKILL.md 仍可位于子目录,扫描边界是本轮 selected version 目录。
entrypoint.sh 只按版本目录根部判断,不要求同目录存在 SKILL.md。
普通运行态按 selected skill 的 currentVersionId 同步 ./projects:
selected skillIds
-> 查询 skill.currentVersionId
-> 查询 version.storageKey
-> expectedVersionDirs = ./projects/<currentVersionId>
-> 缺失的 versionDir 才下载、解压
-> 已存在的 versionDir 跳过下载、解压
-> 清理 ./projects 下不在 expectedVersionDirs 的旧 version 目录
-> 对 expectedVersionDirs 检查 entrypoint 成功状态,未成功执行过时执行
-> 只扫描本轮 selected versionDirs 下的 SKILL.md,用于 prompt 注入
部署目录判断:
./projects/<versionId> 为一级目录,且 versionId 是合法版本 ID
=> 认为该版本包已经部署过
versionDir 不存在
=> 使用 version.storageKey 下载 zip 并部署
部署使用临时目录解压,成功后再整体替换目标 versionDir,避免半解压目录直接暴露为可用版本:
./projects/.tmp-<versionId>-<random> 解压临时目录
mv ./projects/.tmp-<versionId>-<random> ./projects/<versionId>
storageKey 只作为下载地址,不进入状态 key。正式发布、导入、复制、创建路径里,包对象 ID 都来自新生成的 versionId;现有版本更新接口只修改 versionName,没有合法路径在同一个 versionId 下替换 storageKey。
状态写在 sandbox 用户 HOME,而不是 skill root:
~/.fastgpt/agent-skill-entrypoints/state.json
HOME 解析沿用 ide-agent 逻辑:
$HOME。$HOME 不存在时执行 sh -c "echo ~"。最小状态:
{
"sandboxEntrypointHash": "sha256:...",
"skillEntrypoints": ["<versionId>"]
}
字段含义:
sandboxEntrypointHash:当前 sandbox 已成功执行过的 sandbox 层脚本 hash。skillEntrypoints:当前 sandbox 已成功执行过版本包根 entrypoint.sh 的 versionId 集合。状态只用于本 sandbox 内的 entrypoint 执行去重,不描述 DB 版本或发布状态。
有 selected version 时会按本轮 version 集合 reconcile skillEntrypoints:未选中的 versionId 会被移除。状态目录不可用时仍执行脚本但不记录成功状态;状态文件缺失或损坏时会按空状态重建。
状态读写和普通运行态 skill 目录 reconcile 由服务端 Redis per-sandbox lease 保护,不再向 sandbox 文件系统写入 .lock 或 .runtime.lock。lease 使用 3 分钟 TTL,heartbeat 间隔为 TTL 的 1/6(默认 30 秒),续期和释放都必须校验 token。获取 lease 失败、Redis 获取异常或执行期间续期确认 lease 丢失时,不无锁执行临界区;服务端将错误转换为“虚拟机正在初始化,请稍后重试”的业务错误,由 Agent dispatch 兜底返回本轮错误响应。
没有代码侧 bootstrap 回调
=> 跳过
存在代码侧 bootstrap 回调
=> 在 Redis lease 内、用户文件和 skill 包注入前执行回调
=> 回调内部自行决定是否写文件、执行命令、检查版本或跳过
bootstrap 回调不暴露为用户配置,只允许代码侧注入。框架只保证执行时机、sandbox 上下文和 Redis lease 保护;回调内部逻辑、幂等状态、错误处理和是否执行 shell 都由调用方自行维护。它用于配置镜像源、写平台内置文件等基础环境准备,执行时机早于用户文件注入、skill 包部署和用户 sandbox entrypoint。
没有配置脚本
=> 跳过
状态可读,且 state.sandboxEntrypointHash 等于 hash(script.trim())
=> 跳过
状态不可读,或 state.sandboxEntrypointHash 不等于 hash(script.trim())
=> 执行脚本
=> 成功后尽力写入 state.sandboxEntrypointHash
=> 失败/超时只记录截断日志,继续主流程
状态放在 sandbox HOME 中,而不是 DB 中。DB 只能表示当前发布配置是什么,不能证明某个具体 sandbox 里已经存在对应副作用。sandbox 被重建、HOME 被清理或实例切换时,HOME 状态会自然丢失,下次会重新执行。
./projects/<versionId>/entrypoint.sh 不存在
=> 跳过
entrypoint.sh 存在
=> state.skillEntrypoints 包含 versionId 时跳过
=> 状态不可读或不包含 versionId 时执行
正常发布模型下,同一个 versionId 的脚本内容不会变化,因此 skill 层不记录脚本 hash。失败或超时不写入 versionId,下轮同一 sandbox 会再次尝试。
useSandbox() 普通运行态顺序:
1. 启动或连接 sandbox
2. 执行平台侧 sandbox bootstrap
3. 并行注入用户输入文件、部署 selected skill version、读取 pwd
4. 等待用户文件和 skill 包部署都完成
5. 执行 sandbox 层 entrypoint
6. 执行本轮需要运行的 skill version entrypoint
7. 扫描本轮 selected versionDirs 下的 SKILL.md
8. 返回 sandboxClient、currentWorkingDirectory、skillInfos
平台 bootstrap 放在最前面,因为镜像源和平台内置文件可能会被后续用户文件注入、sandbox entrypoint 或 skill entrypoint 消费。sandbox 层 entrypoint 放在 skill entrypoint 前面,因为它更像用户配置的基础环境初始化,skill entrypoint 可以依赖它产生的环境。
edit-debug 不接入自动执行流程。
ToolCall/SimpleApp 没有 selected skill 包部署流程,只在启用 sandbox 时执行 sandbox 层 sandboxEntrypoint。它同样发生在用户文件注入完成之后、LLM loop 开始之前。
shellQuote()。cd <versionDir> && /bin/bash entrypoint.sh
cd <runtime workDirectory>。1..600 秒。./projects/<versionId>。versionId 再次运行时不重新下载、不重新解压。currentVersionId 时,只部署变化的版本。storageKey 只在缺失版本需要下载时使用。./projects/<versionId>/entrypoint.sh。entrypoint.sh。SKILL.md。