首页/新闻资讯/正文详情

Sandcastle 编码规范实战:从 Effect 类型安全、沙箱路径到可测试接口的完整工程指南

发布时间:2026/9/26 22:13:58 来源:云帆数科 栏目:资讯中心
Sandcastle 编码规范实战:从 Effect 类型安全、沙箱路径到可测试接口的完整工程指南
【免费下载链接】sandcastleOrchestrate sandboxed coding agents in TypeScript with sandcastle.run()项目地址https://gitcode.com/gh_mirrors/sandcastl/sandcastle点击查看免费下载Sandcastle 是一个用 TypeScript 编排沙箱化编码 Agent 的开源项目核心入口是sandcastle.run()。本文基于仓库根目录下.sandcastle/CODING_STANDARDS.md的工程规范结合 src/errors.ts、src/Orchestrator.ts、src/sandboxes/docker.ts、src/cli.ts 等真实实现逐条讲解项目在 Effect 类型安全、沙箱 Provider 隔离、流式输出、跨平台路径、交互式 CLI 与测试设计上的硬性要求。读完你不仅能理解这些规范「为什么存在」还能直接套用到自己的 Agent 编排或沙箱工具项目中。一、Effect 优先用类型安全替代裸 Promise规范第一条也是全局性最强的尽可能使用 Effect 原语如FileSystem而非 Promise。理由是 Effect 提供两样裸 Promise 给不了的东西——依赖注入DI和类型安全错误。但 Effect 不应泄漏到用户面 API 中。这意味着典型的代码形态是「外层 Promise、内层 Effect」即使外层函数是 Promise比如用户面 API内层函数也应立即委托给 Effect。规范给出的反例信号是单个函数内出现多个.runPromise调用就是一个需要重构的红旗应改成类似这样的结构const outerFunc async () { const inner Effect.gen(function* () { // 在这里做事 }).pipe(Effect.runPromise); };在仓库里这一模式体现得十分明显例如 src/Orchestrator.ts 的orchestrate是一个返回Effect.EffectOrchestrateResult, SandboxError, SandboxFactory | Display | AgentStreamEmitter的 Effect内部通过yield*从SandboxFactory、Display、AgentStreamEmitter三个服务中取依赖而 src/sandboxes/docker.ts 中startContainer这类内部操作也统一用Effect.runPromise收敛同时构造期如resolveUserMounts、processFileMountParents在docker()工厂里同步完成校验尽量把错误提前到构造阶段。二、失败通道必须是 Tagged Error绝不裸抛new Error()Effect 的runPromise只能捕获Error实例因此规范明确绝不在 Effect 闭包内使用new Error()例如Effect.fail(new Error(...))或Effect.tryPromise的catch回调。裸Error会把推断的错误通道拓宽成通用的Error/unknown抹掉 Effect 提供的类型安全错误追踪让下游的Effect.catchTag/Effect.catchAll失去做窄范围恢复所需的判别标志。返回 Effect 的函数同样如此失败通道必须是 tagged-error 联合类型而不是裸Error。// BAD: 裸 Error 拓宽失败通道 Effect.tryPromise({ try: () doThing(), catch: (e) new Error(Failed: ${e instanceof Error ? e.message : String(e)}), }); // GOOD: tagged error —— 失败通道保持窄且可判别 Effect.tryPromise({ try: () doThing(), catch: (e) new WorktreeError({ message: Failed: ${e instanceof Error ? e.message : String(e)}, }), });仓库的证据集中在 src/errors.ts全部错误都是Data.TaggedError(...)子类包括ExecError、ExecHostError、CopyError、DockerError、PodmanError、SyncError、WorktreeError、PromptError、AgentError、AgentIdleTimeoutError、PromptExpansionTimeoutError等近 20 个最终汇聚成一个SandboxError联合类型src/errors.ts。这些错误大多携带结构化载荷例如PromptExpansionTimeoutError同时带timeoutMs配置的截止时间与elapsedMs实际运行耗时让下游编排器能区分「真正的超时」与「几乎立即中止」WorktreeTimeoutError带operation: create | prune判别。catchTag正是依靠这些 tag 才能精确恢复。反例也真实存在src/sandboxExec.ts的execHost/execOk仍以throw new Error(...)的方式抛错这正说明这些工具位于 Effect 边界之外属于规范要求逐步收敛的遗留形态。三、Changeset先探索再落笔允许一提交多变更集写 changeset 之前先探索其他潜在相关的 changeset避免重复劳动。如果一次提交触及多个用户面行为允许一个 commit 携带多个 changeset。这是一条团队协作层面的规范核心是「变更集是用户面行为的记录不是提交的计数单位」。四、沙箱 Provider 之间禁止共享集成代码编写沙箱 Provider 时不要在不同的 Provider 之间共享 Provider 专属代码。每个 Provider例如 Vercel 和 Daytona接入的是不同 SDK属于不同关注点即使今天两个 Provider 看起来相似它们也会各自演化分叉因此集成逻辑绝不共享。唯一的例外是纯的、与 Provider 无关的工具函数不引用任何 Provider SDK、不引用任何 Provider 配置、行为纯粹由输入决定例如一个有界字符串尾部缓冲区。这类函数可以放进共享模块。判别的测试方法很直白读代码时如果说不清它是给哪个 Provider 用的它才是工具函数而不是共享抽象。仓库里现成的正面案例是 src/boundedTail.ts 的BoundedTail类与MAX_TAIL_CHARS常量它只做「按总字符数有界地保留滚动尾部」不引用任何 SDK 或配置因此被 src/sandboxes/docker.ts 和 src/sandboxes/podman.ts 等多个 Provider 共用。其设计本身也有讲究默认maxChars 64 * 102464 KiB理由是「高于任何 Agent 完成信号或结构化输出载荷又远低于 V8 的字符串长度上限」单个超长条目会被截断为其自身尾部防止无换行的巨型 blob 一次 push 就撑爆预算src/boundedTail.ts。配套的 src/boundedTail.test.ts 用 10,000 次 push 验证预算不增长、单条超长文本只保留尾部等行为。五、exec的onLine必须实时回调带onLine回调的exec必须在输出到达时实时调用onLine而不是等命令执行完再一次性分割。原因很具体编排器用onLine回调重置空闲超时——如果执行期间行不发射超时就会提前触发或挂死无法被检测到。因此必须使用底层 SDK 的流式 / WebSocket API绝不「先执行完再分割」。这条契约被固化在 Provider 接口的 JSDoc 里无论是BindMountSandboxHandle还是IsolatedSandboxHandle的exec都注明「实现必须通过onLine支持逐行流式输出这是 Sandcastle 向用户提供实时反馈并强制空闲超时的方式一个等进程退出后才调用onLine的缓冲/批量实现不满足该契约」src/SandboxProvider.ts。实现侧src/sandboxes/docker.ts 用spawn(docker, [exec, ...])拉起子进程再通过node:readline的createInterface逐行读取 stdout 并回调onLine同时用BoundedTail保留有界尾部作为返回的stdout/stderr。而消费侧src/Orchestrator.ts 的invokeAgent正是依赖该回调每收到一行就解析流事件、累积输出、在每一行之后调用resetTimer()重置空闲计时器并检查完成信号completionSignal是否已出现在累积输出中。空闲计时器默认 10 分钟src/Orchestrator.ts超时则Deferred.fail(new AgentIdleTimeoutError(...))。若onLine不是实时触发这个整套机制都会失效——这就是规范把流式列为硬性要求的原因。六、公共 API 必须有 JSDoc任何公共面向的属性或函数都应有 JSDoc 注释解释其用途。仓库对此执行得很彻底SandboxProvider.ts中每个接口字段都有文档连deprecated的AnySandboxProvider都标注了迁移方向src/SandboxProvider.tsdocker()的每个选项如selinuxLabel、network、groups、devices、cpus都解释了对应的底层docker run参数与取值语义src/sandboxes/docker.ts。七、Node 内置模块禁止懒加载导入 Node 内置模块时不要用懒import()风格直接用普通import即可。这是避免动态导入带来的模块解析不确定性、提高静态分析可追踪性的常规做法。八、沙箱内路径永远用posix.join主机路径用平台感知join注定要进入沙箱容器的路径传给copyFileIn/copyFileOut/exec或作为沙箱 cwd / projects dir 存储永远是 Linux 路径必须用node:path的posix.join而不是裸的平台感知join。原因Windows 主机上后者会产生\分隔符而docker cp/podman cp会静默拒绝这类路径——运行看起来继续但数据丢了。而主机侧路径tmpdir()下的内容、hostRepoDir、主机 projects dir应继续用平台感知的join。仓库中的实践Codex、Pi 等 Agent 的沙箱会话目录都硬编码为posix.join(/home/agent, .codex, sessions)、posix.join(/home/agent, .pi, agent, sessions)src/AgentProvider.tsSessionStore的沙箱 JSONL 路径同样用posix.joinsrc/SessionStore.ts而写回主机的目标路径则用平台感知join(process.env.HOME ?? ~, ...)src/AgentProvider.ts。专门的 src/SessionStore.windowsPath.test.ts 和createSandbox-windowsMounts.test.ts系列测试就是为了钉住这类跨平台差异。九、路径比较前先统一分隔符比较两个路径、startsWith、Set.has、.includes等时先把两边的分隔符都规范化例如p.replace(/\\/g, /)。原因git在所有平台都报告正斜杠而node:path.join/normalize在 Windows 上产生\因此把 git 来源的路径与 join 来源的路径做原始比较在 Windows 上会静默失败。这在 Linux/macOS CI 上是不可见的两处都产出/所以除非刻意混用两种分隔符风格否则测试不会暴露它。当结果要返回给下游join/ fs 使用时要重新应用平台原生的normalize让调用方拿到一致的分隔符——只有比较本身需要正斜杠。仓库证据WorktreeManager.ts的normalizePath (p: string): string p.replace(/\\/g, /)src/WorktreeManager.tsmountUtils.ts在解析 git 目录路径与挂载路径时同样先做replace(/\\/g, /)src/mountUtils.ts。src/WorktreeManager.windowsPath.test.ts 的注释直接点明了动机Windows 上git worktree list --porcelain报正斜杠而node:path.join产反斜杠测试通过人为混用C:/repo/...与C:\\repo\\...两种表示来钉住分隔符鲁棒的比较逻辑——这正是 Linux CI 复现不出来的场景。十、可选参数要极度审慎传给函数的可选参数应被极其仔细地审查它们是巨大的 bug 来源因遗漏而致。正确性优先于向后兼容。这条规范直接呼应了仓库里大量「宁可让类型系统逼着你显式传参」的设计例如OrchestrateOptions里idleTimeoutSeconds、completionTimeoutSeconds都有默认值并有 JSDoc 标注默认600 秒 / 60 秒timeouts覆盖项「未设置的键保持默认」src/Orchestrator.ts尽量把「可选」收敛为「有明确默认」。十一、sandcastle init的每个交互提示必须配对非交互 CLI 标志sandcastle init流程中的每个交互提示都必须配对一个能解析同一选择的非交互 CLI 标志两者是绑定的只加新提示而不加对应标志 → 破坏脚本化 / CI 安装无 TTY 时提示会卡住或崩溃只加标志而不接进提示路径 → 交互体验不一致当 stdin 不是 TTY 且某个本会触发的提示缺少对应标志时要快速失败报一条指名缺失标志的消息而不是让提示库崩溃新提示 新标志必须在同一次变更里落地。仓库实现cli.ts用process.stdin.isTTY true判定是否交互src/cli.tsfailIfNonInteractive(flag)在非 TTY 且缺标志时报${flag} is required in non-interactive mode (no TTY detected).src/cli.ts。Agent、sandbox provider、issue tracker、template 等选择全部遵循「CLI 标志 交互 select 非交互快速失败」的顺序src/cli.tsconfirm 类提示如--create-label、--install-template-deps、--build-image也走同一套三态逻辑src/cli.ts。src/cli.test.ts 专门验证了「全量标志集在非 TTY 环境非交互安装」与「缺--agent/ 缺--create-label时带清晰消息快速失败」两条路径。十二、测试覆盖不要用internal属性改用 Effect 配置层需要为测试提供函数或模块行为覆盖时不要用internal属性例如给类型加_idleWarningIntervalMs、_hostProjectsDir、_sandboxProjectsDir这类下划线前缀的「仅测试用」字段// BAD type Example { /** internal Test-only override for the idle warning interval in milliseconds. Default: 60000 (1 minute). */ readonly _idleWarningIntervalMs?: number; /** internal Override for the host projects directory (for testing). */ readonly _hostProjectsDir?: string; /** internal Override for the sandbox projects directory (for testing). */ readonly _sandboxProjectsDir?: string; };正确做法是为那个函数创建基于 Effect 的配置层在测试和生产中分别以不同配置实例化。且不要让这一层变为可选——可选项会给所有层级增加更多间接层。这里有一个值得注意的对照Orchestrator.ts的OrchestrateOptions目前仍保留着_idleWarningIntervalMs?: number标注为internal Test-only overridesrc/Orchestrator.ts——它正是这份规范点名的反例形态。从源码结构看这份标准书写的正是「把这类字段迁移到 Effect 服务配置层」的演进方向读者在阅读该文件时不应把它当作推荐写法。十三、测试通过公共接口验证行为而非实现细节核心原则测试通过公共接口验证行为而不是实现细节。代码可以完全重写只要行为没变测试就不该坏。好测试长什么样好测试是集成风格的通过公共 API 走真实代码路径描述系统「做什么」what而不是「怎么做」how// GOOD: 通过公共接口测试可观察行为 test(createUser makes user retrievable, async () { const user await createUser({ name: Alice }); const retrieved await getUser(user.id); expect(retrieved.name).toBe(Alice); });好测试的特征测试用户/调用方在乎的行为、只用公共 API、能扛住内部重构、每个测试一个逻辑断言。坏测试的红旗// BAD: mock 内部协作者测试的是 HOW 而不是 WHAT test(checkout calls paymentService.process, async () { const mockPayment jest.mock(paymentService); await checkout(cart, payment); expect(mockPayment.process).toHaveBeenCalledWith(cart.total); }); // BAD: 绕过接口通过数据库验证 test(createUser saves to database, async () { await createUser({ name: Alice }); const row await db.query(SELECT * FROM users WHERE name ?, [Alice]); expect(row).toBeDefined(); });红旗清单mock 内部协作者自己的类/模块测试私有方法断言内部调用的次数/顺序无行为变化的重构导致测试坏掉测试名描述 HOW 而非 WHAT绕过接口通过外部手段如查数据库验证。Mock 只发生在系统边界只在系统边界处 mock外部 API支付、邮件等、时间/随机性、不方便用真实实例时的文件系统或数据库。永远不要 mock 自己的类/模块或内部协作者——如果某物不 mock 内部就难以测试就重新设计接口。在边界处优先用 SDK 风格接口而非通用 fetcher每个函数可独立 mock 成单一返回形状测试设置里没有条件逻辑。仓库在这方面是执行得相当一致的boundedTail.test.ts只通过BoundedTail的公共push/toString验证行为Orchestrator、SandboxFactory、WorktreeManager的测试均通过公共接口触发真实路径而不是对内部模块打桩。TDD 工作流纵向切片不要先把所有测试写完、再写全部实现——那会产生验证「想象中行为」、对真实变化不敏感的测试。正确姿势是「一个测试、一个实现、重复循环」RED→GREEN: test1→impl1 RED→GREEN: test2→impl2 RED→GREEN: test3→impl3每个测试回应你从上一轮循环中学到的东西。RED 状态下绝不重构——先到 GREEN 再说。十四、接口设计深模块与可测试性深模块优于浅模块优先深模块小接口、深实现——少数方法加简单参数背后隐藏复杂逻辑。避免浅模块大接口、很多方法、只是透传给薄实现。设计时自问方法数能减吗参数能简化吗能把更多复杂度藏进内部吗仓库里典型的深模块是BoundedTail只有push和toString两个公共方法却封装了滚动淘汰、超长截断、长度计数一致性计数封装在类内调用方无法使之失步等全部复杂度src/boundedTail.ts。SandboxProvider的句柄接口同样是小而深的exec/interactiveExec/copyFileIn/copyFileOut/close几个方法背后是完整的容器生命周期与流式输出管理。为可测试性设计接受依赖而非创建依赖——外部依赖由外部传入而不是在内部构造返回结果而非产生副作用——返回值的函数比改状态的函数更容易测试小表面积——方法越少要写的测试越少参数越少测试设置越简单。这三条与「mock 只在系统边界」「深模块」互为表里共同构成 Sandcastle 的测试友好架构。想深入观摩落地效果可以对照阅读 src/Orchestrator.test.ts、src/SandboxFactory.test.ts 与 src/WorktreeManager.test.ts以及验证 Windows 路径行为的 src/WorktreeManager.windowsPath.test.ts 和 src/SessionStore.windowsPath.test.ts。结语一份规范一套可追溯的工程证据.sandcastle/CODING_STANDARDS.md并不是悬浮在空中的口号而是仓库里每一条实现都能反查的工程契约Effect 的 tagged error 在errors.ts落地为近 20 个可判别错误流式onLine契约写进了SandboxProvider的接口文档并由Orchestrator的空闲超时机制强制执行posix.join与路径分隔符规范由专门的 Windows 路径测试钉死init的「提示 ↔ 标志」绑定由cli.ts的三态解析和cli.test.ts的非 TTY 用例守护。无论你是要贡献 Sandcastle 本身还是想为自研的 Agent 编排 / 沙箱工具制定同类标准这份文档与上述源码都是可以直接引用的范本。赞分享【免费下载链接】sandcastleOrchestrate sandboxed coding agents in TypeScript with sandcastle.run()项目地址https://gitcode.com/gh_mirrors/sandcastl/sandcastle点击查看免费下载相关推荐Sandcastle 实战指南用 TypeScript 在沙箱中编排 AI 编码 AgentSandcastle 实战指南用 TypeScript 在沙箱中编排 AI 编码 Agent Sandcastle 是一个面向 TypeScript 的 AITwig 沙箱Sandbox安全指南从 SecurityPolicy 到 Sandbox 类的完整实践Twig 沙箱Sandbox安全指南从 SecurityPolicy 到 Sandbox 类的完整实践 Twig 默认把模板源码当作可信代码处理只有显式后端Sunshine 排障手册串流黑屏、无声、掉帧按 5 个高频故障顺序自查Sunshine 排障手册串流黑屏、无声、掉帧按 5 个高频故障顺序自查 Sunshine 是一款自托管的游戏串流服务器game stream host音视频后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

nohup在后台常驻运行php脚本
nohup在后台常驻运行php脚本

nohup是什么nohup是Linux和Unix系统中的一个命令,其作用是在终端退出时,让进程在后台继续运行。它的全称为“no hang up”,意为“不挂起”。nohup命令可以让你在退出终端或关闭SSH连接后继续运行命令。nohup语法规则nohup命令的基本语法如下&… · 2026/9/26 22:13:52

SketchUp Pro 2021直接使用版全攻略:安装避坑与优化配置
SketchUp Pro 2021直接使用版全攻略:安装避坑与优化配置

简介:SketchUp Pro 2021 v21.0.339 Win64 直接使用版资源包,面向需要快速启用草图大师进行三维建模的用户,尤其适合零基础学习者、室内/建筑/景观设计初学者,以及想跳过繁琐激活、直接上手实践的人群。压缩包共21个文件&#xff0… · 2026/9/26 22:13:52

西安知名网站开发的公司3招搞定性能优化
西安知名网站开发的公司3招搞定性能优化

西安知名网站开发的公司3招搞定性能优化 改个需求建站公司拖一周,这种体验太常见了。你刚提个色块微调,对方说排期要五天,其实他们在做无意义的代码重构或等待某个“神秘”的审批。更坑的是,等网站终于上线,打开速度像蜗牛,首屏加载超过5秒,用户全跑… · 2026/9/26 22:13:38

从select到epoll:IO多路复用演进与高并发实战
从select到epoll:IO多路复用演进与高并发实战

1. 从一次线上故障说起:阻塞模型为什么撑不住高并发我第一次被IO多路复用逼到认真研究,是因为一个网关项目在并发冲到800左右的时候开始频繁超时。当时排查了一周,调线程池、改socket超时参数,全部按下葫芦浮起瓢。后来把阻塞acce… · 2026/9/26 22:51:48

深度拆解 rdma_conn_param:从字段含义到配置实战
深度拆解 rdma_conn_param:从字段含义到配置实战

写RDMA应用的开发者,几乎没有人没遇见过struct rdma_conn_param。但说实话,很长一段时间里我自己对这个结构体的理解也停留在“填个private_data,其它抄默认值”的层面,直到有一次给一个分布式存储项目调连接参数,线上… · 2026/9/26 22:51:48

5G NR时频域资源网格图全解析:从子载波到资源块
5G NR时频域资源网格图全解析:从子载波到资源块

搞5G无线通信这一行,不管是做算法、协议栈、网优测试,还是刚入门的学生,最后都要回到同一张图上——5G NR的时频域资源网格图。这张图不仅定义了信号在时间和频率上的位置,也决定了终端能不能正确地收发数据。我第一次看到这张图的… · 2026/9/26 22:51:33

Spring AI MCP实战:从原理到源码,构建安全高效的AI工具调用体系
Spring AI MCP实战:从原理到源码,构建安全高效的AI工具调用体系

干这一行最怕的就是信息差。早几年做大模型应用,最折磨人的不是模型能力不够,而是怎么把各种外部系统“安全、高效、规范“地接进来——写插件、调API、做鉴权、处理数据结构。每接一个工具,都得从零开始写一套胶水代码,还得祈祷对… · 2026/9/26 22:51:33

Windows 本地安装 Redis7 全攻略:移植版与 WSL2 选型及配置
Windows 本地安装 Redis7 全攻略:移植版与 WSL2 选型及配置

1. 为什么要在 Windows 上折腾 Redis7很多人第一次接触 Redis 都是在 Linux 服务器上,apt install redis或者yum install redis一行命令就完事了。但现实工作中,大量开发同学的日常主力机就是 Windows,本地调试、写单元测试、跑小型 Demo 的时… · 2026/9/26 22:51:15

WorkBuddy + Flask + SQLite:轻量级独立站快速搭建与日更实践
WorkBuddy + Flask + SQLite:轻量级独立站快速搭建与日更实践

1. 为什么我选择 WorkBuddy Flask SQLite 这套组合1.1 从零建站这件事,工具选型决定了后面三个月的幸福感做独立站这件事,我前前后后折腾过不少方案。最早用 WordPress,插件装了一堆,主题换了七八套,结果页面加载速度… · 2026/9/26 22:51:15

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码