TypeScript研发与代码执行

项目目录

项目简介

OpenCode 是面向开发者的开源编程助手,可在终端中阅读仓库、修改文件和执行命令,提供模型接入、Plan / Build 模式及会话管理,支持长任务压缩和中断后继续处理。

如何写进简历

修码 · 企业研发缺陷修复助手 | 场景化示例

项目描述:面向企业研发人员,接收缺陷单、复现步骤和错误日志,在指定仓库中辅助定位与修复问题。交付修改补丁、修复说明和验证记录,供代码审查与人工验收。

技术栈:TypeScript、OpenCode、Effect、SQLite、Drizzle ORM

主要工作:

  • 分层工具权限控制:基于 Plan / Build 区分缺陷分析与代码修改,在工具筛选之外对文件路径和命令校验权限,结合允许、拒绝及人工确认规则,限制模型越权修改或执行命令。
  • 长任务压缩与上下文重建:利用模型用量与新增消息估算压缩水位,以结构化摘要和近期消息重建请求,保留修复目标、已有决策及未完成项,使长缺陷排查在上下文窗口受限时继续。
  • 持久会话与中断恢复:通过持久输入队列、消息事件与执行标记按会话串行推进任务,重启后处理未结束的工具记录并重建上下文,接续尚未完成的缺陷排查。

面试题与回答

  1. Plan Mode 与执行模式的本质区别及工具权限差异是什么?

    展开收起

    核心回答

    OpenCode 的 Plan 和 Build 都可以读取代码、调用模型和使用工具,区别在于任务目标及有效工具权限:Plan 用于分析和形成方案,默认限制普通文件编辑;Build 用于实施,但具体操作仍要通过权限检查。切换模式改变的是 Agent 配置和行为约束,并不会自动把进程变成操作系统级只读沙箱。

    实现上有三层:模式提醒告诉模型当前应做什么;工具注册表过滤被整体禁用的工具;真正读取、编辑或执行命令时,再按动作和资源调用 Permission.assert()。最后这一层决定操作是否允许、需要确认,或直接拒绝。

    Plan 默认限制了什么

    在 OpenCode 2.1.2 中,Plan 插件为 plan Agent 追加普通 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 就认定所有副作用已被隔离。

    参考资料

  2. 上下文压缩在什么条件下触发?

    展开收起

    OpenCode 2.1.2 的会话执行器在每一步模型请求之前检查自动压缩,也支持模型拒绝超长请求后的恢复,以及用户主动提交的压缩请求。三种入口共用压缩服务,但触发条件不同。

    自动压缩依据模型预算

    SessionRunner 先加载当前会话,再调用 compaction.compact({ reason: "auto", ... })。压缩服务用模型的 input 上限计算水位,没有独立输入上限时使用 context 窗口。

    该版未显式设置 buffer 时,默认预留窗口的 15%;窗口至少为 32k 时,预留量还不低于 16k Token。例如窗口为 100k 时,默认水位为 84k。这是一版源码的策略,不能照搬为所有模型的最佳阈值。

    当前大小优先以同一提供商返回的有效用量作基准,再估算此后新增的消息与工具结果;缺少有效用量时,估算系统指令、工具定义和消息。模型窗口未知时不会自动按比例触发,只能依赖手动操作或服务端溢出反馈。刚完成压缩时,执行器先重建请求;原生压缩产生的不透明窗口,也要等新用量反馈后再判断,避免拿旧计数连续压缩。

    溢出恢复与手动压缩

    当请求实际被判断为上下文溢出时,执行器尝试 reason: "overflow" 的压缩;成功后重新加载历史发起请求,并限制同一步的再次溢出恢复。压缩请求自身过大时,还会逐步降低目标大小,无法再缩减则返回失败,不无限重试。

    自动压缩关闭时,普通水位检查和溢出恢复都会跳过压缩;用户的手动请求仍可进入压缩服务。因此,“关闭自动压缩后,超长时一定会自动兜底”并不符合这版实现。

    压缩后怎样继续任务

    支持原生压缩的模型走提供商原生路径;其他模型生成包含目标、要求、决定、工作状态和下一步的摘要,并按 keep 保留近期上下文。该版 keep 默认预算为 15k Token,与触发水位分别配置。

    普通摘要通过消息标识关联保留片段,后续会话历史据此重建请求。这样既能减少反复携带旧过程,又保留近期任务细节;是否丢失约束仍需检查摘要及后续执行,不能仅凭压缩成功状态判断。

  3. Session 中断后重新启动时如何恢复上下文?

    展开收起

    核心回答

    OpenCode 是能够阅读仓库、修改文件和执行命令的编程助手。以该版 TypeScript 会话实现为例,它把用户输入受理、会话执行和模型输入重建分开:先将输入写入持久待处理队列,按会话串行推进步骤;服务重启后检查遗留执行标记,核对未结束的工具记录,再从持久消息重建上下文继续。

    这恢复的是任务的推进依据。它不会接回旧模型请求的进程现场,也不会仅凭会话恢复就撤销或去重所有文件、命令和外部系统的副作用。

    输入先持久受理,再唤醒执行

    Session.prompt 先按输入 ID 核对已有记录,再准备并受理输入,之后才唤醒执行器。resume: false 可以只受理而暂不执行。相同会话、相同输入类型和相同 ID 的重试沿用第一次受理的内容,因此重新发送并不意味着替换原输入。

    待处理输入保存在 session_inbox 中;投递时消费待处理行,并在同一事件事务中插入可见消息。这样,恢复时可以区分“已经受理、尚未投递”和“已经成为会话消息”的输入,而不依赖客户端是否收到了响应。

    重启标记与执行协调各管一件事

    执行器在开始执行时,随开始事件的事务写入持久执行标记。正常完成、失败或用户主动中断会释放标记;服务关闭导致的中断保留标记,进程突然退出也可能留下它。

    受管理服务启动时,恢复扫描识别这些遗留会话,持久增加恢复尝试次数,再安排继续执行;超过预算则记录失败。这个标记用于识别未结束的执行,不是分布式租约。

    同一会话的多次唤醒由进程内协调器合并,同一会话串行推进,不同会话可以并发。重启恢复假定旧进程已经退出,不能据此允许多个进程共享数据库并同时接管同一会话。

    先结清遗留记录,再重建模型输入

    Runner 开始推进时,会把遗留的 streaming/running 工具记录标为中断失败,把遗留的执行中压缩记录标为压缩失败。这段处理让后续模型能看到调用没有取得正常结果,避免历史永远停在“执行中”;它没有证明实际动作未发生,也没有恢复那个动作的执行现场。

    之后每一步重新选择会话上下文并加载持久历史,再进行一次模型流式请求。工具调用及结果进入会话记录,下一步从更新后的历史继续,而不是依赖一个只在内存里保存消息的无限工具循环。

    长历史采用压缩时,本地摘要路径会保留任务目标、要求、决定、工作状态和下一步等内容,并通过 keepID 引用近期消息;历史加载再取回这些近期消息组成文本,与摘要及后续消息一起重建模型输入。原生模型压缩还有模型兼容条件,不能将任意压缩结果直接用于任意模型。

    哪些状态仍要另行核对?

    最关键的情况是:工具已经修改文件或完成外部请求,但进程在结果落库前退出。恢复后即使工具记录被标成中断,动作也可能已经发生。输入 ID 的受理幂等与会话串行,都不能替代业务动作的幂等和结果核对。该版重启恢复明确采用至少一次的执行语义。

    同样,遗留后台 Shell 的取消或完成通知,是对后台任务状态的处理;不能把通知当成任意子进程、外部服务或业务资源已经恢复的证据。文件快照与显式回退也不能撤销任意命令的外部副作用。

    验证时应分别在输入受理后、工具动作完成但结果未写入时、模型输出中途和压缩过程中终止进程,再检查恢复后的输入数量、消息顺序、工具状态及实际文件或业务结果。还要区分用户主动停止与服务退出,确认不会把用户已取消的任务自动重启。