# Host 动作、权限与流式持久化 本文描述已经实现的 `0.2.0` Host 行为。源码依据为 DeepSeek Harness `0.2.1-rc.2`,固定提交 [`687f282e8625c6b8d11ff58211a90524be2e4317`](https://github.com/deepseek-ai/deepseek-harness/tree/687f282e8625c6b8d11ff58211a90524be2e4317)。本插件没有修改该仓库,也没有替换 Agent Loop。完整验收记录以 [VALIDATION.md](VALIDATION.md) 为准。 ## 1. 启用边界 默认只启用 P0 文档生成与本地交互。`actions.enabled` 和 `streaming.enabled` 都默认为 `false`。基础 Host 仅要求 `tools`、`systemPrompt`;可选服务通过 Cordis 的 `ctx.inject` 延迟装配,不能因为关闭高级能力而使普通计算器无法加载。 下面是实际支持的插件配置,参数填写在 `intelligent-ui` Host entry 的 `config` 中: ```json { "maxDocumentBytes": 262144, "enabledPlanning": true, "actions": { "enabled": true, "capabilities": [ { "id": "read_file", "toolName": "read", "title": "读取文件", "description": "通过当前 Agent 的文件读取工具读取指定文件", "risk": "read", "resultFields": ["totalLines"] }, { "id": "write_file", "toolName": "write", "title": "写入文件", "description": "通过当前 Agent 的文件写入工具保存内容", "risk": "write", "resultFields": ["operation"] } ] }, "streaming": { "enabled": true } } ``` 管理员配置决定能力目录。模型只能引用 `capabilityId`,不能定义权限、目标工具名称、审批结果、主体身份或幂等记录。工具还必须真实注册且对当前 Agent 的实际作用域可见。`read`、`write` 例子的官方参数分别为 `file_path`,以及 `file_path` 和 `content`;可选的 `offset`、`limit` 属于读取工具。这里的第一版动作参数是扁平标量对象,不能直接传递嵌套工具参数。 `risk: "write"` 强制进入真实 Harness 人工审批服务;即使目标工具本身默认无需审批,也不能省略这一步。目录中的风险分类是管理员承担的信任边界,写入或其他可能产生副作用的工具不能配置成 `read`。目录明确拒绝 `run_code` 和本插件自身的 `intelligent_ui*` 工具,避免把通用执行器或递归调度器直接暴露为 UI 能力。 ## 2. 为什么使用独立 Remote 官方 `CommandInvocation` 只提供命令、Agent、输入、附件与取消信号,没有保留 Gateway 的实际主体身份。命令本身也不会开启一个可记录审批的 Agent turn。因此本实现没有把 `/command` 当作具有独立权限保证的 UI 动作执行器。 正式入口是 `IntelligentUIActions extends TypertRemoteService`,服务名与 Remote namespace 均为 `intelligentUi`。五个方法都用 `@Remote({ resources: { session: ... } })` 声明 Session 资源。插件导出的 `./typert` 由官方加载器安装严格 descriptor;客户端通过 `./remote` 的 `TYPERT_REMOTE` 和官方 `$mount` 接入。Host 不再重复手工注册同一贡献。 | 方法 | 真实签名 | 用途 | | --- | --- | --- | | reserve | `(sessionId, request, signal) → UIActionReceipt` | 登记一个明确点击请求并提交真实 Agent 回合 | | get | `(sessionId, {operationId}, signal) → UIActionReceipt` | 读取一个已登记操作 | | list | `(sessionId, documentRef, signal) → UIActionReceipt[]` | 恢复此界面每个 action 的最近回执 | | cancel | `(sessionId, {operationId}, signal) → UIActionReceipt` | 请求取消排队、审批中或执行中的操作 | | catalog | `(sessionId, signal) → UICapabilityDescriptor[]` | 获取该 Session 实际可见的能力目录 | 请求主体来自 Gateway 创建的 `ctx.invocation.peer.principal`。客户端不能额外传入 `principal`。Gateway 首先执行资源授权;服务也对实际主体、命名方法与 Session 资源进行验证。账本读取还隔离不同主体的操作,owner 可以在已授权的 Session 中查看操作记录。排队后执行时,用记录中的真实主体重新调用权限决策,不能把脱离 Remote 的普通 Context 当成默认授权。 相关官方源码: - [Gateway 调用准备、资源授权与服务 Context](https://github.com/deepseek-ai/deepseek-harness/blob/687f282e8625c6b8d11ff58211a90524be2e4317/packages/api/gateway/src/index.ts) - [RemoteAccess 决策](https://github.com/deepseek-ai/deepseek-harness/blob/687f282e8625c6b8d11ff58211a90524be2e4317/packages/api/remote-access/src/index.ts) - [SessionController 的 inspect、resolveAgent 与 prompt](https://github.com/deepseek-ai/deepseek-harness/blob/687f282e8625c6b8d11ff58211a90524be2e4317/packages/api/session-controller/src/index.ts) - [命令注册与 CommandInvocation](https://github.com/deepseek-ai/deepseek-harness/blob/687f282e8625c6b8d11ff58211a90524be2e4317/packages/interaction/commands/src/index.ts) ## 3. 点击如何变成真实工具调用 浏览器提交 `documentRef`、随机 `requestId`、`actionId` 和完整有效的本地 state。请求总量限 32 KiB,独立于 256 KiB 文档和本地状态预算。Host 从 `SessionController.inspect()` 获取服务端会话事件,按真实 native call 或 PTC subcall 身份找到本插件生成结果,再重新验证文档、状态和表达式。浏览器不提交目标工具、已经求值的工具参数或替代 UI 文档。 静态 UI 使用 `generation: 0, revision: 0`。若同一源 call ID 对应两个不同合法静态文档,Host 拒绝执行,避免旧按钮悄然获得新文档语义。完全相同的重复结果可以恢复。派生会话的继承事件不会获得原会话的操作权限。 Host 在持久化账本中记录冻结后的目标能力、参数、参数 Schema 摘要、真实主体、原始授权请求与操作状态。只有记录成功写入后,才调用官方 `SessionController.prompt({sessionId,requestId,mode:"queue",content}, signal)`。客户端不用再提交一次 prompt。 Agent 得到一个只包含 Host 操作 ID 的调度请求,调用: ```json {"operationId":"uiop_<由Host签发的64位十六进制摘要>"} ``` 目标工具名为 `intelligent_ui_action`。这不是可以猜测或独立调用的通用执行入口:桥接工具只接受当前真实 Agent turn 已消费的、同 Session、同主体对应的待执行操作。它从私有记录重建参数,模型不能通过桥接参数改变文件路径等内容。 真实 `session/event` 中的 `turn/start`、带操作 request ID 的 `user/message`、`turn/end` 用于标记该回合的授权操作。若模型在该回合直接调用替代工具,插件的官方 Tool guard 拒绝它;桥接工具的正确目标和受信任的嵌套调用使用真实 Tool execution token 关联。PTC 的 `run_code` 只保留为官方传输入口;插件不会自行实现其执行沙箱。 桥接随后重新验证当前 Agent、工具可见性、能力配置、工具参数 Schema 和主体权限;原子认领该操作;必要时调用真实 `approval.request()`;审批后再次检查权限和目标身份;最后通过 **`ctx.tools.execute`** 执行目标。它不会直接调用 `ToolDefinition.execute()`。`parent: exec.token`、`rootCallId`、原始 Agent 与组合取消信号都被保留,目标工具原有的权限、审批与中间件仍生效。 工具的 additional contexts 和结束回合请求会按官方语义向外传递。桥接结束后请求结束该动作回合,避免成功回执后再产生无关工具调用。嵌套目标本身不会被本插件伪造成新的 native `tool/call`/`tool/result`;审计由真实桥接调用、原生审批事件和私有账本中的目标信息共同构成。 官方 [ToolsRuntime](https://github.com/deepseek-ai/deepseek-harness/blob/687f282e8625c6b8d11ff58211a90524be2e4317/packages/core/tools/src/index.ts) 是唯一工具执行路径。官方 [ApprovalService](https://github.com/deepseek-ai/deepseek-harness/blob/687f282e8625c6b8d11ff58211a90524be2e4317/packages/interaction/user-approval/src/index.ts) 要求审批发生在 open turn 内;这也是本实现需要真实 Agent 回合,而不在 idle RPC 中执行工具的原因。 ## 4. 状态、取消与恢复 | 状态 | 含义与恢复行为 | | --- | --- | | queued | 已记录,等待 Agent 调度;不是执行成功 | | awaiting_confirmation | 等待真实人类审批;还未调用目标工具 | | running | 目标执行权已认领;取消请求仍需等待实际工作结束 | | succeeded | ToolsRuntime 确认成功,可公开白名单标量结果 | | failed | 没有确认成功;审批或实际执行路径已失败 | | cancelled | 在可判定的取消边界停止,不再自动调度 | | unknown | 无法确认外部结果,可能存在部分副作用;绝不自动重试 | | accepted | Agent callback 已接受排队;不表示后续推理或工具完成 | 公开结果只包含管理员 `resultFields` 选中的顶层标量字段,单字符串和聚合结果都有上限。原始工具异常、完整返回对象、目标内部上下文和私有授权记录不送入 UI。已有工具对自己的普通日志披露仍由 Harness 的原有规则控制。 一个 `requestId` 在同主体、同 Session 下确定一个操作 ID。相同请求重传返回既有回执;相同 ID 配上不同内容会被拒绝;并发认领只能有一个执行者。运行中取消会中止组合信号,但不能把已发生的外部效果说成撤销成功。写工具在进入执行后抛错或取消,结果记为 `unknown`;普通 guard 在进入执行前拒绝则为 `failed`。 Agent callback 采用独立语义:若取消发生在 prompt admission 尚未返回时,状态保留 `queued` 并说明正在等待 admission 结果;确认已接受后为 `accepted`,无法确认时为 `unknown`。不会先显示已取消,再反转成已接受。已经接受的普通 Agent 工作不能通过本操作账本自动撤回。 Host 重启后,执行中的操作记为 `unknown`;尚未开始的工具操作取消;未确认接受的 callback 记为 `unknown`;已终结记录保持。恢复界面只执行 list/get,不重新提交,也不自动把旧 resultMap 写入当前用户 state。插件卸载中止自己的生命周期信号,并等待桥接工作、待登记 Remote 请求与状态写入完成,才关闭账本。 ## 5. 账本载体与容量 账本使用官方 `storageDomain.open(defineDomain(...))`,domain 为 `intelligent_ui_actions`,版本 1,`layout: "single"`。存储位置由所选官方 backend 管理;默认 JSON backend 下对应其 root 中的 `intelligent_ui_actions.json`。插件不自行选择用户目录或另写未受管理的临时记录。 官方域设施在读取时校验 Schema;因此插件每一次写入前也验证、复制并冻结整条记录。单条 insert-if-absent 由插件的串行门提供,状态转换使用官方原子 `table.update`。坏的权威数据使该能力关闭,不能把无法读取的去重记录视为空表。 默认最多 2,048 个全局操作、每 Session 128 个操作;达到上限后拒绝新增,并继续允许读取与相同请求查询。不会为了腾出空间自动删除幂等记录。该布局是**单 Host 所有者**方案,不提供多进程分布式锁,也不声称跨外部系统实现 exactly-once。未来压缩或迁移必须保留完成记录或等价的去重墓碑。 相关官方实现:[StorageDomain](https://github.com/deepseek-ai/deepseek-harness/blob/687f282e8625c6b8d11ff58211a90524be2e4317/packages/storage/storage-domain/src/index.ts)。 ## 6. 已验证的 Host 边界 `tests/host-actions-runtime.test.ts` 使用已安装的真实 Cordis、SessionController、AgentLoop、Gateway、Approval、ToolRuntime、JSONL Session persistence、StorageDomain/JSON backend,以及不联网的确定性 LLM adapter。它覆盖真实主体绑定、冻结参数、替代工具阻挡、重复桥接、审批缺失、审批中收紧权限/Agent scope、写操作未知结果、排队/运行取消、执行中卸载和 callback admission 竞态。callback 时序故障仅通过对实际附件 admission 方法增加等待门来注入,仍由真实 SessionController 完成排队。 `tests/host-actions-gateway.test.ts` 单独验证严格 descriptor 仍保留 decorator 的 Session 资源授权、跨主体隔离、wire 身份伪造拒绝、历史修订查询和卸载。`tests/host-actions-ledger.test.ts` 验证真实磁盘持久化、并发、重启、容量、accessor 拒绝和损坏数据关闭能力。真正网页点击、浏览器人工审批和真实模型调用的结果由整体 [VALIDATION.md](VALIDATION.md) 记录;确定性 adapter 测试本身不被描述为真实模型智能规划评测。