OpenCode
anomalyco/opencode项目目录
项目简介
OpenCode 是面向开发者的开源编程助手,可在终端中阅读仓库、修改文件和执行命令,提供模型接入、Plan / Build 模式及会话管理,支持长任务压缩和中断后继续处理。
如何写进简历
修码 · 企业研发缺陷修复助手 | 场景化示例
项目描述:面向企业研发人员,接收缺陷单、复现步骤和错误日志,在指定仓库中辅助定位与修复问题。交付修改补丁、修复说明和验证记录,供代码审查与人工验收。
技术栈:TypeScript、OpenCode、Effect、SQLite、Drizzle ORM
主要工作:
- 分层工具权限控制:基于
Plan/Build区分缺陷分析与代码修改,在工具筛选之外对文件路径和命令校验权限,结合允许、拒绝及人工确认规则,限制模型越权修改或执行命令。 - 长任务压缩与上下文重建:利用模型用量与新增消息估算压缩水位,以结构化摘要和近期消息重建请求,保留修复目标、已有决策及未完成项,使长缺陷排查在上下文窗口受限时继续。
- 持久会话与中断恢复:通过持久输入队列、消息事件与执行标记按会话串行推进任务,重启后处理未结束的工具记录并重建上下文,接续尚未完成的缺陷排查。
面试题与回答
Plan Mode 与执行模式的本质区别及工具权限差异是什么?
展开收起
核心回答
OpenCode的Plan和Build都可以读取代码、调用模型和使用工具,区别在于任务目标及有效工具权限:Plan用于分析和形成方案,默认限制普通文件编辑;Build用于实施,但具体操作仍要通过权限检查。切换模式改变的是 Agent 配置和行为约束,并不会自动把进程变成操作系统级只读沙箱。实现上有三层:模式提醒告诉模型当前应做什么;工具注册表过滤被整体禁用的工具;真正读取、编辑或执行命令时,再按动作和资源调用
Permission.assert()。最后这一层决定操作是否允许、需要确认,或直接拒绝。Plan 默认限制了什么
在
OpenCode 2.1.2中,Plan 插件为planAgent 追加普通edit拒绝规则,并为用户主目录下的.opencode/plan计划目录保留编辑例外。提示词同时要求默认在对话中讨论计划,只有用户明确要求时才保存计划文件;实现任务应由用户切换 Agent 后继续。Plan 配置与提醒。例如用户要求“先分析重构方案”,模型可以读取调用链、整理涉及文件;按默认 Plan 编辑规则,直接改普通源码文件会被拒绝,而计划目录是明确例外。切换到
Build后,默认 Agent 可以按配置执行工具,仍需检查当前会话和目标资源的权限。Build 定义。这里的
edit限制不能推成“任何途径都无法写文件”:Shell 命令、插件和外部工具有各自的动作权限,最终还取决于合并后的配置。若要求严格只读,需要同时收紧这些入口;文件系统或进程隔离承担的又是另一层职责。为什么隐藏工具还不够
Tool.snapshot()会根据权限过滤被整体禁用的工具,但可见性无法表达某次调用的全部条件。同一个编辑工具可能只允许操作计划目录,因此工具仍可见,普通源码路径却不允许写。工具可见性过滤。实际授权以动作和资源匹配规则。规则求值采用最后匹配项;权限服务合并 Agent 与会话规则后,先检查明确拒绝,再处理保存的允许规则及需要确认的请求。
deny返回拒绝,allow继续,ask则建立待处理请求并等待决定。这样模型即使提出了一个可见工具的调用,也不能只凭工具名取得任意资源的访问权。规则匹配、权限求值与确认。权限检查怎样落到真实操作
- 读取文件:读取工具先调用
FileAccess.authorizeRead(),解析目标路径,必要时检查工作目录外访问,再以read动作和目标资源调用Permission.assert(),通过后才交给读取器。读取入口、路径与读取授权。 - 编辑文件:编辑工具解析匹配位置、准备差异预览,再用
edit动作对目标路径申请权限;通过后还会检查匹配是否唯一等编辑条件,才进入写入。权限允许与补丁可正确应用是两个判断。编辑授权。 - 执行命令:Shell 工具扫描命令和涉及目录,检查外部目录访问,并按命令资源申请权限;确认后还会重新核对工作目录,再启动进程。因此允许
read不等于允许执行所有命令,禁止edit也不会自动禁止所有可能写文件的命令。Shell 执行前检查。
模式切换与方案取舍
切换 Agent 时,Plan 插件注入进入或退出提醒,并在后续上下文中补齐缺失提醒,避免压缩后模型仍受旧模式提示影响。它负责行为连贯性,具体动作仍由权限服务判断。模式提醒维护。
这种设计能复用同一套模型与工具,在分析和实施之间调整能力范围,也支持路径级例外。代价是需要逐个工具落实授权,并分别验证直接编辑、Shell 写入、外部目录和模式切换;不能只看到界面显示 Plan 就认定所有副作用已被隔离。
参考资料
- 国内:JavaGuide:Harness Engineering:参考模型提出动作、运行系统负责工具和约束的职责划分,解释权限为何不能仅由提示词保证。
- 国内:zero2Agent:Agent 架构与规划设计:参考计划与实施共享任务信息、切换后继续处理既定目标的组织;具体默认权限以本文源码为准。
- 国内:WeThinkIn:Agent Harness 工程:参考任务允许动作与运行环境分别定义,解释权限模式和执行环境不是同一层能力。
- 国内:WeThinkIn:工具权限、沙箱和人工审批:参考工具策略、用户确认和沙箱分别解决的问题,说明应用授权与操作系统隔离的区别。
- 国外:Claude Code:Choose a permission mode:以另一产品的 Plan 权限及命令审批作为对照,说明具体能力由产品版本与配置决定。
- 国外:Anthropic:Building effective agents:参考模型按反馈选择工具的运行方式,解释规划能力与运行时允许动作可以分别设计。
- 读取文件:读取工具先调用
上下文压缩在什么条件下触发?
展开收起
OpenCode 2.1.2的会话执行器在每一步模型请求之前检查自动压缩,也支持模型拒绝超长请求后的恢复,以及用户主动提交的压缩请求。三种入口共用压缩服务,但触发条件不同。自动压缩依据模型预算
SessionRunner先加载当前会话,再调用compaction.compact({ reason: "auto", ... })。压缩服务用模型的input上限计算水位,没有独立输入上限时使用context窗口。该版未显式设置
buffer时,默认预留窗口的15%;窗口至少为32k时,预留量还不低于16k Token。例如窗口为100k时,默认水位为84k。这是一版源码的策略,不能照搬为所有模型的最佳阈值。当前大小优先以同一提供商返回的有效用量作基准,再估算此后新增的消息与工具结果;缺少有效用量时,估算系统指令、工具定义和消息。模型窗口未知时不会自动按比例触发,只能依赖手动操作或服务端溢出反馈。刚完成压缩时,执行器先重建请求;原生压缩产生的不透明窗口,也要等新用量反馈后再判断,避免拿旧计数连续压缩。
溢出恢复与手动压缩
当请求实际被判断为上下文溢出时,执行器尝试
reason: "overflow"的压缩;成功后重新加载历史发起请求,并限制同一步的再次溢出恢复。压缩请求自身过大时,还会逐步降低目标大小,无法再缩减则返回失败,不无限重试。自动压缩关闭时,普通水位检查和溢出恢复都会跳过压缩;用户的手动请求仍可进入压缩服务。因此,“关闭自动压缩后,超长时一定会自动兜底”并不符合这版实现。
压缩后怎样继续任务
支持原生压缩的模型走提供商原生路径;其他模型生成包含目标、要求、决定、工作状态和下一步的摘要,并按
keep保留近期上下文。该版keep默认预算为15k Token,与触发水位分别配置。普通摘要通过消息标识关联保留片段,后续会话历史据此重建请求。这样既能减少反复携带旧过程,又保留近期任务细节;是否丢失约束仍需检查摘要及后续执行,不能仅凭压缩成功状态判断。
Session 中断后重新启动时如何恢复上下文?
展开收起
核心回答
OpenCode是能够阅读仓库、修改文件和执行命令的编程助手。以该版TypeScript会话实现为例,它把用户输入受理、会话执行和模型输入重建分开:先将输入写入持久待处理队列,按会话串行推进步骤;服务重启后检查遗留执行标记,核对未结束的工具记录,再从持久消息重建上下文继续。这恢复的是任务的推进依据。它不会接回旧模型请求的进程现场,也不会仅凭会话恢复就撤销或去重所有文件、命令和外部系统的副作用。
输入先持久受理,再唤醒执行
Session.prompt先按输入 ID 核对已有记录,再准备并受理输入,之后才唤醒执行器。resume: false可以只受理而暂不执行。相同会话、相同输入类型和相同 ID 的重试沿用第一次受理的内容,因此重新发送并不意味着替换原输入。待处理输入保存在
session_inbox中;投递时消费待处理行,并在同一事件事务中插入可见消息。这样,恢复时可以区分“已经受理、尚未投递”和“已经成为会话消息”的输入,而不依赖客户端是否收到了响应。重启标记与执行协调各管一件事
执行器在开始执行时,随开始事件的事务写入持久执行标记。正常完成、失败或用户主动中断会释放标记;服务关闭导致的中断保留标记,进程突然退出也可能留下它。
受管理服务启动时,恢复扫描识别这些遗留会话,持久增加恢复尝试次数,再安排继续执行;超过预算则记录失败。这个标记用于识别未结束的执行,不是分布式租约。
同一会话的多次唤醒由进程内协调器合并,同一会话串行推进,不同会话可以并发。重启恢复假定旧进程已经退出,不能据此允许多个进程共享数据库并同时接管同一会话。
先结清遗留记录,再重建模型输入
Runner 开始推进时,会把遗留的
streaming/running工具记录标为中断失败,把遗留的执行中压缩记录标为压缩失败。这段处理让后续模型能看到调用没有取得正常结果,避免历史永远停在“执行中”;它没有证明实际动作未发生,也没有恢复那个动作的执行现场。之后每一步重新选择会话上下文并加载持久历史,再进行一次模型流式请求。工具调用及结果进入会话记录,下一步从更新后的历史继续,而不是依赖一个只在内存里保存消息的无限工具循环。
长历史采用压缩时,本地摘要路径会保留任务目标、要求、决定、工作状态和下一步等内容,并通过
keepID引用近期消息;历史加载再取回这些近期消息组成文本,与摘要及后续消息一起重建模型输入。原生模型压缩还有模型兼容条件,不能将任意压缩结果直接用于任意模型。哪些状态仍要另行核对?
最关键的情况是:工具已经修改文件或完成外部请求,但进程在结果落库前退出。恢复后即使工具记录被标成中断,动作也可能已经发生。输入 ID 的受理幂等与会话串行,都不能替代业务动作的幂等和结果核对。该版重启恢复明确采用至少一次的执行语义。
同样,遗留后台 Shell 的取消或完成通知,是对后台任务状态的处理;不能把通知当成任意子进程、外部服务或业务资源已经恢复的证据。文件快照与显式回退也不能撤销任意命令的外部副作用。
验证时应分别在输入受理后、工具动作完成但结果未写入时、模型输出中途和压缩过程中终止进程,再检查恢复后的输入数量、消息顺序、工具状态及实际文件或业务结果。还要区分用户主动停止与服务退出,确认不会把用户已取消的任务自动重启。