Replace the restricted workspace_rg parameter mapping with native args and optional cwd. Preserve rg output and exit codes, and reuse process cancellation and output_read pagination so large search results remain accessible. Ask remains read-only within the workspace; Execute uses current-account permissions. Validation: focused regression 169 passed, 1 skipped; typecheck and lint passed. Real DeepSeek Ask and Execute pagination scenarios passed with six counted requests in the successful run; an earlier failed fixture run has no retained request count. Full-suite validation is not green: the recorded run had unrelated failures and the latest run timed out without a summary. The remote Agent does not use this local execution path. Release note: 直连模型可使用 ripgrep 原生参数搜索文件并保留原生结果,大输出可通过 output_read 完整续读。Ask 保持工作区只读,Execute 使用当前账号权限。
534 lines
25 KiB
Markdown
534 lines
25 KiB
Markdown
# 直连模型 Agent 能力技术设计
|
||
|
||
## 文档信息
|
||
|
||
| 项目 | 内容 |
|
||
| --- | --- |
|
||
| 状态 | 已实施 |
|
||
| 版本 | 0.4 |
|
||
| 日期 | 2026-09-09 |
|
||
| 关联 PRD | [PRD](./prd.md) |
|
||
| 功能逻辑 | [功能逻辑设计](./logic-design.md) |
|
||
| 相关架构 | [助手工作栏与执行空间](../assistant-workbar/prd.md)、[本机工具环境](../local-tool-environment/technical-design.md) |
|
||
|
||
## 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. 总体架构
|
||
|
||
直连模型在 Main 内执行 HTTP,本身没有独立模型进程。跨 Runtime 资源调研中的直连
|
||
部分仅检查历史缓存、会话引用和工具资源释放,不为它新增进程池;分析与验证范围
|
||
见 [Runtime 资源技术设计 §7.2](../assistant-workbar/runtime-process-reuse-technical-design.md#72-直连模型及历史详情)。
|
||
以下当前功能不因此改变,进程工具产生的子进程仍按各自会话生命周期管理。
|
||
|
||
```text
|
||
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 可见性:
|
||
|
||
```ts
|
||
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` 接受 `{ args: string[], cwd?: string }`,直接启动随包 rg,不经过 Shell。
|
||
默认参数为 `--no-config --color=never --line-number`,随后传入原生 args;cwd 默认工作区。
|
||
保留原生 stdout、stderr 和退出码,不强制 JSON、不裁剪匹配行。完整输出复用进程服务的
|
||
临时文件与 `output_read`;搜索不另设时限,取消和会话释放终止进程树。
|
||
- `workspace_read_text` 接受 `path`、从 1 开始的 `offset` 和 `limit`,通过现有
|
||
`WorkspaceAccess.readText` 字节分页组装带行号结果;`offsetBytes` 提供原始字节续读,
|
||
覆盖跨多个页面的超长单行。
|
||
- `workspace_apply_patch` 接受一个 `*** Begin Patch` 文本,支持 `Add File`、`Update File`
|
||
和 `Delete File`。全部操作先解析并验证;更新文件保留原换行和权限位,单文件原子替换。
|
||
- Ask 只注册前两个只读工具;Execute 注册全部三个工具。旧的目录列表和整文件覆盖不再进入
|
||
新工具清单。
|
||
- 输入和目标文件不设固定总量上限。单次工具结果仍遵守直连模型上下文边界;读取返回续读
|
||
位置,搜索预览省略的内容通过 `output_read` 续读。
|
||
|
||
### 读取与搜索失败恢复
|
||
|
||
`workspace_read_text` 和 `workspace_rg` 的输入校验及预期工作区错误由 `ModelToolProvider`
|
||
转为 `RecoverableModelToolError`,保留原始 cause。范围包括 schema 校验失败、绝对路径或
|
||
越界路径、目标不存在或类型不符、无效 UTF-8、无效读取范围,以及 `ENOENT`、`ENOTDIR`、
|
||
`EISDIR`、`EACCES`、`EPERM`、`ELOOP`、`ENAMETOOLONG` 等已列明的文件系统错误。
|
||
Runtime 向模型返回 `{ ok: false, recoverable: true, error, nextAction }` 的 JSON 文本工具结果,
|
||
工具活动记为 `recoverable`,继续工具循环,由模型修正路径、分页参数、glob 或搜索表达式;
|
||
提示不得原样重复失败参数。取消、远端断线、失效工作区、缺失 rg 可执行文件及未分类的内部
|
||
错误继续向外传播,不转为可恢复结果。
|
||
|
||
`workspace_read_text` 仍只接受工作区内相对路径。搜索在 Ask 中检查 cwd、搜索路径、模式文件
|
||
和忽略文件位于工作区内,并阻止 `--pre`、`--hostname-bin`、`--follow/-L`、`--search-zip/-z`,
|
||
避免只读调用启动外部命令或跟随链接越界。Execute 原样接受 rg 参数及账号可访问的路径,
|
||
不增加逐工具授权。两种模式均禁用外部 rg 配置,不提升系统权限。
|
||
|
||
ripgrep 结果按退出状态处理:
|
||
|
||
- exit 1 保持正常无匹配结果。
|
||
- exit 2 返回原生 stderr 和已有 stdout,表示错误或搜索覆盖不完整;模型可修改参数继续。
|
||
无效正则同样返回 exit 2;字面搜索使用 `-F`。
|
||
|
||
该契约属于桌面 `ModelAgentRuntime → ModelToolProvider` 内置工具路径,复用浏览器标签页
|
||
关闭和元素引用失效已有的可恢复错误机制;浏览器分别提示 `browser_navigate` 或
|
||
`browser_snapshot`,路由规则见[浏览器技术设计](../assistant-workbar/browser-tabs-technical-design.md#61-兼容阶段)。
|
||
`workspace_read_text` 若使用 `RemoteWorkspaceAccess`,仍经同一 Provider 分类远端
|
||
`data.code` 中的预期文件系统和路径/文本错误。Agent daemon 的 RPC 错误序列化保留
|
||
`ENOENT` 等大写系统错误码,避免远端文件不存在被误判为不可恢复错误;远端原生工具
|
||
处理不变。内置 rg 仍仅支持本机工作区,托管 SSH 当前仍使用远端 OpenCode 原生工具,
|
||
不因共享读取抽象而开放远端直连模型,见[远程 Runtime 边界](../remote-host/technical-design.md#runtime)。
|
||
|
||
### ripgrep 打包
|
||
|
||
固定依赖 `@vscode/ripgrep`,构建钩子根据 Electron 目标选择对应 Windows、macOS 或 Linux
|
||
x64/arm64 平台包,使用 lockfile integrity 校验后提取 `rg`。安装包只复制当前目标二进制和
|
||
MIT 许可证。运行时通过 `BundledRuntimePaths.ripgrep` 使用绝对路径,并将所在目录放到直连
|
||
模型进程 PATH 前部。
|
||
|
||
## 4. 进程工具契约
|
||
|
||
### 4.1 输入
|
||
|
||
```ts
|
||
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 结果
|
||
|
||
```ts
|
||
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 根据 `rg:`、`process:` 或 `subagent:` 前缀选择服务,以当前 `conversationId` 校验所有者,
|
||
不接受模型另传 owner。Ask/Execute 均可用,工具总数为此预留一个槽位;不要求本轮能够启动
|
||
命令或再次委派。分页结果直接 JSON 序列化,不进行二次文本裁剪,以免 cursor 与内容不一致。
|
||
32 KiB 的页面即使全部需要 JSON 转义,也能保留在现有工具结果容量内。
|
||
|
||
进程输出归属调用会话,Subagent 最终输出归属父会话。子级临时会话释放后,父模型仍能续读
|
||
Subagent 结果。会话释放会等待活动调用结算再删除所属文件;服务退出关闭写入句柄并删除临时
|
||
目录。输出不写入 SQLite,不跨 Runtime 重启恢复。
|
||
|
||
## 5. 平台执行
|
||
|
||
### 5.1 Shell 探测
|
||
|
||
启动直连模型执行服务时解析一次 Shell:
|
||
|
||
```text
|
||
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 启动参数
|
||
|
||
```text
|
||
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;[本机工具环境](../local-tool-environment/technical-design.md)
|
||
实施后可通过现有 `toolBinDirectory` 注入口把受管 bin 放在 PATH 前面。
|
||
- 排除模型 Provider Key、GoodBuddy 加密设置、调试变量和无关应用凭据。
|
||
- 不修改 `process.env`,不写系统 PATH 或 Shell Profile。
|
||
- 每次调用得到独立环境快照;命令内设置的变量不会进入下一次调用。
|
||
|
||
## 6. 执行空间绑定
|
||
|
||
定义一个小型进程后端接口,不把任意进程能力塞进 `WorkspaceAccess`:
|
||
|
||
```ts
|
||
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 输入
|
||
|
||
```ts
|
||
const subagentDelegateInputSchema = z.object({
|
||
task: z.string().trim().min(1).max(100_000)
|
||
}).strict()
|
||
```
|
||
|
||
首版一次调用只创建一个子任务。不同父请求可以通过全局调度器并发,不增加批量委派 schema。
|
||
|
||
### 7.2 子请求
|
||
|
||
`DirectModelSubagentService` 创建临时子请求:
|
||
|
||
```ts
|
||
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` 增加不可伪造的运行上下文:
|
||
|
||
```ts
|
||
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,
|
||
不得以专家记录代替该执行者:
|
||
|
||
```ts
|
||
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 真实模型验证
|
||
|
||
使用一个最小真实直连文本模型请求:
|
||
|
||
1. Execute 创建一段有缺陷的小程序。
|
||
2. 调用进程工具运行聚焦测试并观察失败。
|
||
3. 修改程序并再次运行到通过。
|
||
4. 委派一个 Subagent 检查实现或补充测试。
|
||
5. 父模型读取子级结果并给出最终结论。
|
||
|
||
每个平台一次完整场景即可,不进行批量或高成本调用。记录准确模型调用次数,不记录 API Key。
|
||
|
||
输出边界场景复用 `runtime-e2e.manual.test.ts`:真实模型运行一次专用 Node 脚本,输出超过
|
||
96 KiB,并在预览之后生成随机中间与结尾标记。模型必须通过生产 `output_read` 连续读到 EOF,
|
||
最终回答包含两个标记。测试限制工具为进程与续读,记录上游请求数和工具调用数;凭据由现有
|
||
加密设置解密后仅在内存及测试子进程环境中传递。
|
||
|
||
### 12.4 项目验证
|
||
|
||
源码完成后运行:
|
||
|
||
```text
|
||
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. 实施顺序
|
||
|
||
1. Shared 工具摘要、schema 和 Runtime target 过滤。
|
||
2. 本机 Shell 探测、进程执行、输出边界和进程树取消。
|
||
3. `ModelToolProvider` Execute 接入及 UI 活动。
|
||
4. 编程 Subagent 服务、过滤 Provider、事件和用量。
|
||
5. 设置能力目录与状态诊断。
|
||
6. 三平台真实命令、真实模型和完整项目验证。
|
||
|
||
每一步都必须保持直连模型正常问答可用;Shell 不可用不能阻止普通对话。
|