Restore Ask workspace reads and output paging without granting write access, and let programming Subagents reuse their parent's browser page. Allocate browser resources only when used, keep Agent pages aligned with workbar tabs, recover released bindings, and remove unused automatic screenshot work. Preserve inherited Runtime selections during remote usage persistence and manual compression. Keep viable channel model fallbacks, display the actual scheduled-task mode and Harness model source, and avoid rediscovery delays when calling healthy MCP tools. Fix directory/history refresh, case-only renames, rename-aware commit diffs, and FastGPT probe success reporting. Add regression coverage and align feature documentation. The Agent-side connection timeout fix requires deploying an updated Agent; existing Ask read-only and Execute authorization boundaries remain unchanged. Final validation: 3839 tests passed, 66 skipped; typecheck, lint, development build, and diff checks passed. Isolated Electron UI/MCP workflows passed with a loopback model fixture. Current-source Linux x64 Host checks passed with three successful real model requests. Chromium loadURL rejection was not reproduced in the UI; released-binding recovery has automated coverage. Release note: 修复 Ask 读取项目文件、浏览器页面显示与恢复、文件管理及模型配置跟随中的不一致,并减少无用截图和 MCP 等待。定时任务显示实际工作模式,远程会话可正常保存继承 Runtime 的用量;远端连接超时修复需更新 Agent 后生效。
22 KiB
直连模型 Agent 能力技术设计
文档信息
| 项目 | 内容 |
|---|---|
| 状态 | 已实施 |
| 版本 | 0.4 |
| 日期 | 2026-09-09 |
| 关联 PRD | PRD |
| 功能逻辑 | 功能逻辑设计 |
| 相关架构 | 助手工作栏与执行空间、本机工具环境 |
1. 实现基线
当前源码已经实现:
ModelAgentRuntime的多轮工具调用、上下文压缩、取消、用量和有界结果处理。ModelToolProvider的直连模型内置工具、浏览器、Web、知识和自定义 MCP 聚合。workspace_rg、分页workspace_read_text和workspace_apply_patch工作区工具。WorkspaceAccess的本机和远端工作区抽象。- 跨平台子进程树终止辅助函数。
- 面向用户专家协作的
SubagentScheduler和状态事件。专家使用具备已启用本机能力的 直连模型 Runtime,继承父请求 Ask/Execute 及 authorizer,工具事件记入子任务; Ask 保持只读,不把这些本机工具表述为远程 OpenCode 子会话。 - OpenCode、Continue 和 DeepSeek Harness 各自的 Shell/Agent 能力。
- 直连模型
process_execute、subagent_delegate和output_read。 - 不创建顶层 Task/Conversation 的编程 Subagent actor 与活动归并。
2. 总体架构
ModelAgentRuntime
└─ ModelToolProvider
├─ WorkspaceAccess
├─ DirectModelProcessService
│ └─ LocalDirectModelProcessService
├─ DirectModelSubagentService
│ ├─ SubagentScheduler
│ └─ 同一 ModelAgentRuntime 的临时子会话
│ └─ 同一 ModelToolProvider(delegationDepth=1 时移除委派)
├─ Browser / Web / Knowledge
└─ assigned MCP
OpenCode、Continue 和 DeepSeek Harness 的创建路径不接收
DirectModelProcessService 或 DirectModelSubagentService。DeepSeek Harness 即使继续使用
ModelToolProvider 代理 Web/MCP,也必须通过明确的工具视图排除直连模型专属工具。
3. 共享工具目录
BuiltinModelToolSummary 增加 programming 分组和 Runtime 可见性:
type BuiltinModelToolSummary = {
name: string
displayName: string
description: string
access: 'read' | 'write'
group: 'filesystem' | 'browser' | 'web' | 'programming'
}
process_execute 标记为 write,只在 Execute 清单中出现。
subagent_delegate 的实际访问级别由父模式决定:
- Ask 清单中提供只读委派定义。
- Execute 清单中提供继承 Execute 的定义。
callTool不依赖静态access单独授权,必须检查父请求上下文。
工具总数和 schema 总字节继续计入直连模型现有 100 个工具与 512 KiB 上限。
工作区工具契约
工具输入与执行
workspace_rg接受pattern、相对目录、glob、固定字符串、忽略大小写、文件列表模式和 最大结果数。Main 固定追加--no-config,内容搜索读取--json,对模型返回紧凑文本。workspace_read_text接受path、从 1 开始的offset和limit,通过现有WorkspaceAccess.readText字节分页组装带行号结果;offsetBytes提供原始字节续读, 覆盖跨多个页面的超长单行。workspace_apply_patch接受一个*** Begin Patch文本,支持Add File、Update File和Delete File。全部操作先解析并验证;更新文件保留原换行和权限位,单文件原子替换。- Ask 只注册前两个只读工具;Execute 注册全部三个工具。旧的目录列表和整文件覆盖不再进入 新工具清单。
- 输入和目标文件不设固定总量上限。单次工具结果仍遵守直连模型上下文边界;读取返回续读 位置,搜索截断时要求模型缩小路径、glob 或表达式。
ripgrep 打包
固定依赖 @vscode/ripgrep,构建钩子根据 Electron 目标选择对应 Windows、macOS 或 Linux
x64/arm64 平台包,使用 lockfile integrity 校验后提取 rg。安装包只复制当前目标二进制和
MIT 许可证。运行时通过 BundledRuntimePaths.ripgrep 使用绝对路径,并将所在目录放到直连
模型进程 PATH 前部。
4. 进程工具契约
4.1 输入
const processExecuteInputSchema = z.object({
command: z.string().trim().min(1).max(100_000),
cwd: z
.string()
.trim()
.min(1)
.max(4_096)
.optional(),
timeoutMs: z
.number()
.int()
.min(1_000)
.max(10 * 60_000)
.default(120_000)
}).strict()
cwd接受绝对路径或相对工作区路径,省略时使用工作区根目录;按本机路径语义解析..和符号链接,不限制目标位于工作区内。工具 schema 向模型明确这一规则。- 首版不接受
env、stdin、background、pty、shell或任意 executable 参数。 - Shell 选择由执行后端决定,防止模型绕过平台契约启动另一套受管接口。
- 命令字符串仍可以调用当前账号本来有权运行的程序;Execute 不增加命令白名单。
4.2 结果
type ProcessExecuteResult = {
shell: {
kind: 'powershell' | 'bash' | 'sh'
label: string
}
cwd: string
exitCode: number | null
signal?: string
terminationReason?: 'timeout' | 'cancelled'
durationMs: number
stdout: string
stderr: string
stdoutTruncated: boolean
stderrTruncated: boolean
stdoutReference?: PagedOutputReference
stderrReference?: PagedOutputReference
}
cwd 在工作区内返回相对显示值,根目录为 .;工作区外返回规范化绝对路径,准确说明
命令实际执行位置。Shell 在模型结果中只返回稳定标签;绝对可执行路径只进入 Main 诊断状态。
4.3 输出边界
- stdout 和 stderr 各返回最多 96 KiB 的前缀预览,JSON 转义后的内容也计入预览预算。
PagedOutputStore将流式输出完整写入系统临时目录,内存只保留预览;超限后继续写盘。 存储失败明确报错,不把缺失内容标记为完整结果。- 预览省略内容时返回
stdoutReference或stderrReference,包含handle、nextCursor和totalBytes。短输出返回完整文本并删除临时文件,不附带句柄。 - JSON 结果加上元数据后必须低于
ModelToolProvider现有 256 KiB 工具结果上限。 - 输出按 UTF-8 解码,无效序列使用替换字符并保留字节计数。
4.4 输出续读
output_read 接受严格对象 { handle, cursor?, limitBytes? }:cursor 为从 0 开始的 UTF-8
字节位置,省略时从头读取;limitBytes 为 1 至 32,768 的整数,默认 32,768。页面返回
{ handle, content, cursor, nextCursor, totalBytes, eof },模型沿 nextCursor 读取至 eof。
页面在字符边界结束;若请求大小容不下首个完整字符,最多扩展到 4 字节,保证续读前进。
位于 UTF-8 延续字节的输入 cursor、负数、越界和未知字段均拒绝。
Provider 根据 process: 或 subagent: 前缀选择服务,以当前 conversationId 校验所有者,
不接受模型另传 owner。Ask/Execute 均可用,工具总数为此预留一个槽位;不要求本轮能够启动
命令或再次委派。分页结果直接 JSON 序列化,不进行二次文本裁剪,以免 cursor 与内容不一致。
32 KiB 的页面即使全部需要 JSON 转义,也能保留在现有工具结果容量内。
进程输出归属调用会话,Subagent 最终输出归属父会话。子级临时会话释放后,父模型仍能续读 Subagent 结果。会话释放会等待活动调用结算再删除所属文件;服务退出关闭写入句柄并删除临时 目录。输出不写入 SQLite,不跨 Runtime 重启恢复。
5. 平台执行
5.1 Shell 探测
启动直连模型执行服务时解析一次 Shell:
Windows: PATH 中 pwsh → Windows PowerShell powershell.exe
macOS: /bin/bash → /bin/sh
Linux: /bin/bash → /bin/sh
Shell 不可用时保留直连模型对话能力,但不注册 process_execute。设置目录继续展示静态能力
说明,实际 Shell 通过每次请求的工具定义和结果可见;当前不增加独立 Shell 诊断 IPC。
不下载或捆绑新的 Bash/PowerShell。
5.2 启动参数
PowerShell:
<shell> -NoLogo -NoProfile -NonInteractive -Command <command>
Bash:
/bin/bash -c <command>
Sh:
/bin/sh -c <command>
使用 spawn(executable, args, { shell: false }),避免 Node 再增加一层平台 Shell。POSIX 子进程
作为独立进程组启动;Windows 记录根 PID。停止时复用现有
terminateProcessTreeAndWait:Windows 使用 taskkill /T /F,POSIX 终止本次进程组并等待
有界退出。
5.3 环境
进程环境由 Main 构造:
- 继承运行普通项目命令所需的当前用户基础变量。
- 当前使用筛选后的用户 PATH;本机工具环境
实施后可通过现有
toolBinDirectory注入口把受管 bin 放在 PATH 前面。 - 排除模型 Provider Key、GoodBuddy 加密设置、调试变量和无关应用凭据。
- 不修改
process.env,不写系统 PATH 或 Shell Profile。 - 每次调用得到独立环境快照;命令内设置的变量不会进入下一次调用。
6. 执行空间绑定
定义一个小型进程后端接口,不把任意进程能力塞进 WorkspaceAccess:
interface ExecutionSpaceProcessExecutor {
getCapability(): Promise<
| { available: true; shell: ProcessShellSummary }
| { available: false; reason: string }
>
execute(
input: ProcessExecuteInput,
context: {
workspace: WorkspaceAccess
signal: AbortSignal
}
): Promise<ProcessExecuteResult>
dispose(): Promise<void>
}
首版只有 LocalProcessExecutor。ExecutionSpaceDescriptor.kind === 'ssh' 时使用不可用实现,
工具清单不注册进程能力。未来远端实现必须新增 Agent 协议、由 Agent 直接拥有命令进程,并
按远端身份、取消和断线语义验证;不得把 SSH 路径传给本机 executor。
工作目录通过 WorkspaceAccess.getIdentity() 和本机真实路径解析共同确认。路径检查与 spawn
之间若目录被外部替换,spawn 失败并返回准确错误;不为此增加持久路径凭据或恢复协议。
7. Subagent 工具契约
7.1 输入
const subagentDelegateInputSchema = z.object({
task: z.string().trim().min(1).max(100_000)
}).strict()
首版一次调用只创建一个子任务。不同父请求可以通过全局调度器并发,不增加批量委派 schema。
7.2 子请求
DirectModelSubagentService 创建临时子请求:
type DirectModelSubagentContext = {
parentRequestId: string
childRunId: string
projectId?: string
conversationId: string
workMode: 'ask' | 'execute'
}
- 子
conversationId只用于 Runtime 内存和工具会话隔离,完成后立即releaseConversation,不保存为用户 Conversation。 - 子 Prompt 使用明确任务和 Main 可信的编程 Subagent 指令,不复制父会话历史。
- 子级使用父请求模型连接,首版不增加单独模型选择设置。
- 子级复用父请求已授权能力快照和知识范围。
- 子级复用父 Provider;Main 写入的
delegationDepth=1同时在清单和调用边界过滤subagent_delegate。 - Ask 子级使用 Ask 清单;Execute 子级使用 Execute 清单。
- Execute 子级经
ModelSubagentRequestContext继承父browserTabId和浏览器所属browserConversationId。后者只在 Main 内部请求与工具上下文中传递:浏览器操作使用父 Conversation,历史与分页输出仍使用子 Conversation。子级不新建标签页或申请使用租约, 结束时只释放自己的临时会话,父标签页和租约仍由父请求生命周期管理。
7.3 调度和结果
- 复用
SubagentScheduler的取消、并发和队列模式。 - 全局最大并发 3,队列最大 20。
- 单个子任务最长 10 分钟;父请求更早取消时立即停止。
- 文本预览最多 192 KiB,JSON 转义也计入预算;错误最多 2 KiB。省略的正文通过
outputReference续读,字段与生命周期沿用第 4.4 节,完整输出不受预览容量限制。 - 模型用量逐事件归属父请求和
childRunId,不能只算入父模型最后一轮。 - 子级失败和取消返回结构化终态及部分输出,让父模型决定下一步。
7.4 领域持久化
当前 SubagentService 为历史专家流程创建隐藏 Child Task。新编程 Subagent 不复制该行为:
- 运行身份使用内部
childRunId。 - 父消息通过现有
subagent活动块保存终态和结果摘要。 - 若通用 Job/Subjob 表已经实现,则记录为所属父 Task/请求的 Subjob/Run。
- 若通用 Job/Subjob 表尚未实现,首版只保存现有消息活动和模型用量,不先创建另一套持久 子任务表、恢复日志或兼容读取器。
可以提取现有 Scheduler、事件归并和用量转发逻辑,但不能把旧数据库字段当成新的产品模型。
8. ModelToolProvider 接入
ModelToolCallContext 增加不可伪造的运行上下文:
type ModelToolCallContext = {
conversationId: string
requestId: string
runtimeTarget: 'model'
workMode: 'ask' | 'execute'
executionSpaceIdentity: string
delegationDepth: 0 | 1
knowledgeCapabilityToken?: string
}
要求:
listTools根据模式、执行空间能力和深度生成快照。getApproval不为两个工具创建第二套逐次确认;沿用父 Execute 授权和现有通道工具策略。callTool再次检查runtimeTarget、模式、执行空间 identity、深度和请求是否活动。releaseConversation取消并释放该会话拥有的浏览器、Subagent 和进程。dispose等待有界清理,不因模型或子进程不响应而阻塞应用退出。
8.1 Ask 工具循环
普通 Ask 请求进入工具循环,不再依赖知识、联网搜索或 Subagent 是否启用。Runtime 对
workspace_rg、workspace_read_text、output_read 三个明确只读工具免去 Ask authorizer
调用,避免生产 authorizer 的默认拒绝使已列出的只读工具无法执行。Execute 仍沿用既有
authorizer;补丁、进程和其他写入不因此获准。Provider 按清单和调用边界继续校验模式,
浏览器工具不加入 Ask 免审批集合。内部上下文摘要使用 noModelTools,保留无工具问答路径。
8.2 MCP 发现与调用
listTools 发现 MCP 并保存当前绑定快照;已知工具调用只查该快照,不在每次调用前重试
其他失败服务器。未知名称直接失败。失败服务器只在下一次发现入口重试,动态清单刷新失败
时从新快照中移除该服务器工具,下一次发现再刷新;健康连接继续复用。
绑定只在 Provider 内存中存在,dispose 与连接缓存一起清空。MCP 配置保存通过
refreshCapabilities → onRuntimeSettingsChanged → reconfigureRuntimes 替换 Runtime,
SelectedRuntimeManager.reset 使后续请求创建新 Provider;退役 Runtime 按原有会话生命周期
清理。没有新增持久缓存、失败重试日志或配置兼容层。
DeepSeek Harness 的 Main 工具代理必须使用仅包含分配 MCP 和 Web 的 Provider 视图,不能因为
共享 ModelToolProvider 类而自动看到直连模型编程工具。
9. 事件与活动
9.1 进程事件
继续使用现有 Runtime tool 事件:
pending:参数已接收。running:Shell 已成功启动。completed:返回 exit 0 或非零退出结果。failed:启动或契约失败。cancelled:父请求取消。
结果包含 Shell、工作目录、退出码、耗时、输出预览和续读引用,不写应用诊断日志。
通用工具活动仍显示有界摘要,模型通过 output_read 取得的页面记为后续工具活动。
9.2 Subagent 事件
复用现有 subagent 公开事件和消息块,增加 routingMode: 'native' 表示模型主动委派。
expertId 不再作为编程 Subagent 必填身份;共享 schema 应改为执行者 discriminated union,
不得以专家记录代替该执行者:
type SubagentActor =
| { kind: 'expert'; expertId: string; expertName: string }
| { kind: 'direct-model'; label: '编程 Subagent' }
迁移只需让读取器接受旧专家事件和新联合类型,不重写既有消息。
10. 错误处理
| 情况 | 结果 |
|---|---|
| 命令非零退出 | 正常 ProcessExecuteResult |
| 命令不存在 | Shell 非零退出及 stderr |
| Shell 无法启动 | 工具失败 |
| cwd 无效或越界 | 工具调用前失败 |
| 超时/取消 | 终止进程树,返回已有输出和原因 |
| 直连模型瞬时网络错误/请求超时,尚无可见输出 | 500 ms、1 s、2 s 退避,最多自动重试 3 次 |
| 直连模型失败,已有文本或推理输出 | 保留部分输出并失败,不自动重放 |
| 子级模型失败 | 返回 failed、部分输出和错误 |
| 子级工具失败 | 由子模型处理;最终无法完成时返回子级失败 |
| 父请求已终止 | 拒绝新调用,丢弃迟到内容并清理 |
不要用宽泛 catch 抹掉取消、超时、Provider HTTP 状态或进程退出上下文。
模型请求重试由 ModelAgentRuntime 的统一流式请求处理,覆盖普通问答、工具轮次和复用
同一 Runtime 的上下文摘要。重试分类检查错误及有界 cause 链中的 Node/Undici 网络错误
码;每次尝试创建独立超时信号。退避等待监听父请求取消。只有完整模型轮次返回后才执行
工具,因此未产生可见文本或推理内容的重试不会重复执行工具。
模型回复本身不设固定字节上限:流式响应、非流式回复、模型工具参数、请求上下文和工具 结果总量都由请求超时、取消和上下文压缩控制,长回复和大工具结果不会被中断。图像生成 响应仍按可解码图片大小校验。
会话历史在请求校验、Renderer 发送与保存、SQLite 读写和直连模型缓存中均保留完整消息, 不按消息条数、单条文本长度或累计文本量裁剪。上下文大小由现有压缩策略和模型上下文窗口 处理。Continue 接收完整历史,已有有效摘要只替换其覆盖的前缀,剩余消息全部交给原生 Runtime;GoodBuddy 不再额外保留最后 20 条或限制为 128,000 字符。
11. 性能
- Shell 探测按执行服务实例缓存,不在每轮模型调用重复探测。
- stdout/stderr 边读边写入临时文件,内存仅保存有界预览;续读每次只读取一页。
- 子级共享父请求已解析的模型配置和工具服务,不复制 MCP 配置或持久状态。
- 不增加持久 Shell、后台 daemon、工作区快照或恢复状态机。
- 命令执行不使用 PTY,避免普通构建和测试承担终端渲染成本。
12. 测试策略
12.1 单元测试
- 工具可见性矩阵和 Runtime target 过滤。
- Ask 伪造进程调用拒绝。
- Windows PowerShell、POSIX Bash/Sh 选择和参数。
- cwd 默认、相对子目录、绝对路径、工作区外目录、指向外部的符号链接和不存在的目录。
- exit 0、非零退出、spawn 失败、超时、取消和输出截断。
- 输出预览加分页能还原全文,包括 JSON 转义文本、中文、表情和字面替换字符;覆盖一字节 页面前进、跨会话拒绝、活动调用释放、短输出删除及服务退出清理。
- 环境筛选、本机工具 PATH 优先且模型凭据不进入进程。
- 子级模式、模型、项目、执行空间和能力继承。
- 深度 1 过滤、并发 3、队列 20、取消传播和部分输出。
- 编程 Subagent 不创建 Task/Conversation。
- DeepSeek Harness/OpenCode/Continue 不获得本功能工具。
- TLS 建连
ECONNRESET、Undici 连接超时、单次请求超时、三次重试上限和退避取消。 - 已产生文本或推理增量后发生网络错误时不自动重放。
12.2 本机集成测试
- Windows PowerShell 创建临时文件、运行 Node/Python、非零退出和进程树取消。
- macOS/Linux Bash/Sh 执行相同场景。
- 直连模型工具循环执行“修改 → 测试失败 → 修复 → 测试通过”。
- 父模型委派子级修改或验证专用临时项目,并正确综合结果。
- Runtime 切换后工具清单刷新且没有重复 Shell。
- 应用退出后无残留 GoodBuddy 命令进程。
所有测试只在专用临时工作区创建文件,不删除或覆盖用户工作。
12.3 真实模型验证
使用一个最小真实直连文本模型请求:
- Execute 创建一段有缺陷的小程序。
- 调用进程工具运行聚焦测试并观察失败。
- 修改程序并再次运行到通过。
- 委派一个 Subagent 检查实现或补充测试。
- 父模型读取子级结果并给出最终结论。
每个平台一次完整场景即可,不进行批量或高成本调用。记录准确模型调用次数,不记录 API Key。
输出边界场景复用 runtime-e2e.manual.test.ts:真实模型运行一次专用 Node 脚本,输出超过
96 KiB,并在预览之后生成随机中间与结尾标记。模型必须通过生产 output_read 连续读到 EOF,
最终回答包含两个标记。测试限制工具为进程与续读,记录上游请求数和工具调用数;凭据由现有
加密设置解密后仅在内存及测试子进程环境中传递。
12.4 项目验证
源码完成后运行:
npm test
npm run typecheck
npm run lint
npm run build
跨平台进程实现必须由 Windows、macOS、Linux x64 和 Linux arm64 CI 验证。首版不改变 GoodBuddy Agent 或桌面到 Agent 生产路径,因此不要求远端 Host 验证;未来增加远端 executor 时必须按远程主机技术设计执行共享 Linux Host 真机验证。
13. 实施顺序
- Shared 工具摘要、schema 和 Runtime target 过滤。
- 本机 Shell 探测、进程执行、输出边界和进程树取消。
ModelToolProviderExecute 接入及 UI 活动。- 编程 Subagent 服务、过滤 Provider、事件和用量。
- 设置能力目录与状态诊断。
- 三平台真实命令、真实模型和完整项目验证。
每一步都必须保持直连模型正常问答可用;Shell 不可用不能阻止普通对话。