人类协作平面
packages/interaction/ 承载人类与运行中 agent 通过其协作的服务与插件——提问、审批、权限预设与斜杠命令。这些都是产品包:真实的人可驱动的接口。它们通过既有 agent 与会话契约集成,而非改动循环;交互式应用提供具体适配器,自动化则使用 acp/。
| 包 | 角色 | ctx 键 |
|---|---|---|
@deepseek-ai/dsh-commands | 人类命令注册表 + 面向交互适配器的分派 | ctx.commands |
@deepseek-ai/dsh-user-approval | 一次性审批决策 | ctx.approval |
@deepseek-ai/dsh-permission-presets | 用户可见的权限预设捆绑 | ctx.permissionPresets |
@deepseek-ai/dsh-user-questions | 中立的用户提问/回答接缝 | ctx.userQuestions |
@deepseek-ai/dsh-tool-ask-user | 向模型暴露问题 | 注册于 ctx.tools |
子系统参考位于 docs/subsystems/:commands.md、user-questions.md、approval.md、permission-presets.md。
命令平面
人类命令(human command)是带斜杠前缀的指令,由人类面向适配器经 ctx.commands 解释并执行,而不成为模型消息。术语表划出清晰的三方界线:人类命令区别于面向模型的工具,也区别于经 ctx.shell 的 shell 命令执行。命令平面(command plane)是由 UI 适配器与命令插件拥有的发现、解析、分派、取消与结果渲染。命令输出是 UI 状态,除非处理器另行变更一个持久领域。
服务契约(packages/interaction/commands/src/index.ts)
ctx.commands.register(definition)—— 注册一个小写命令名、描述、可选非结构化输入提示、可选recordInput策略,以及一个可中止的处理器。普通上下文注册是全局的;挂载在agent.ctx之下的插件声明自己的commands注入,并创建精确的 agent 作用域定义,遮蔽同名全局定义。同一层内重名会失败。list(agent)/find(agent, name)—— 作用域遮蔽后不可变、按名排序的发现;注册/移除会通知每个commands/change观察者,使实时适配器刷新。execute(agent, line, signal)—— 只解析并运行已知命令,返回已回定的CommandExecution(对非法语法/未知名返回undefined)。
parseCommand() 识别字节零处的斜杠、包含字母/数字/_/- 的小写名,以及行尾或空白;它把名后的一切字节作为 rawInput 返回。
命令输出是 UI 状态
一次回定命令的生命周期记录为仅日志对 command/run(处理器前,带新铸的 commandId、解析器的结构化名、签发 CommandSource,以及除 recordInput 为 false 外的 args)与 command/done(回定时,带结果种类与逐字文本)。两者都是对接收 agent 会话的直接独立追加——没有轮次包裹它们。成功结果可通过 sourceEventSeq 命名一个较早的非命令权威领域事件(例如 /compact 命名它的 compaction/summary)。处理器返回 success 或 error 加可选 UI 文本;结果由适配器直接渲染,永不进入模型历史。注册表从不隐式向 agent 提交 rawInput。
取消
本修订没有 command-cancel 包。取消是协作式的:分派让处理器与命令的中止信号竞速,已注册的处理器应尊重该信号。被中止的处理器以 kind: 'error' 的形式回定为 command/done。不合作的处理器可以在调用方停止等待后继续其自身的外部副作用——这是已文档化的限制。像 /compact、/goal 这样的命令产生插件会把 UI 的取消信号转发给其底层接缝(compactNow/compactRegion 信号、set 反转)。
/命令与工具的区别
斜杠命令在 UI 命令平面执行而无需模型轮次;它不不出现在工具目录,耗用零模型 token,其输入输出也不提交给模型。面向模型的工具(例如 ask_user_question、get_goal)可由模型在步骤内调用,其参数/结果留在历史中。有些插件两者兼有——/goal(人类命令)与 get_goal/create_goal/update_goal(模型工具)驱动同一个目标领域。
向用户提问:问题接缝
@deepseek-ai/dsh-user-questions 拥有 ctx.userQuestions,这是面向模型的工具或权限插件在需要暂停工作并向人类请求决策时使用的中立服务。它是 Service Definition;消费工具依赖它,UI/Web 宿主运行时提供 provider。
API:
registerProvider(provider)—— 每个上下文一个活跃 provider;一个都没有时ask()抛NO_PROVIDER(失败关闭)、再来一个则DUPLICATE_PROVIDER。ask(request): Promise<AskUserQuestionAnswer>—— 请求{ questions: [{ id, question, detail?, header?, options?, multiSelect?, intent? }], agent?, signal? }。
关键类型:
| 类型 | 形态 |
|---|---|
AskUserQuestionOption | { label, description? } |
AskUserQuestionIntent | { kind: 'plan-review', approve } —— 带标签的呈现意图 |
AskUserQuestionAnswer | { answers: [{ id, selected, custom? }] } |
UserQuestionProvider | 带 ask(request) 的 UI 实现 |
UserQuestionError | 码:EMPTY_QUESTIONS、BAD_INTENT、NO_PROVIDER、DUPLICATE_PROVIDER、ASK_ABORTED、CALLER_NOT_LIVE、DELEGATED_CALLER、…… |
对单选项问题,custom 覆盖 selected;对多选项则补充。提供 agent 时,ask() 经 AgentRegistry 校验精确活跃的运行时根;被另一 agent 拥有的活跃子节点会被拒(DELEGATED_CALLER),哪怕其持久血统深度为零。intent 只改呈现——尊重它的 UI 会返回与通用 UI 相同的标签。当 approve 未命名该问题自身任何选项、或意图落在一个没有 detail 的问题上时,ask() 拒绝 BAD_INTENT。
ask_user_question(面向模型)
@deepseek-ai/dsh-tool-ask-user 是消费者:它把模型参数翻译成 AskUserQuestionRequest 并返回人类答案。该工具调用 ctx.userQuestions.ask() 并返回规范值 { answers: [{ id, selected, custom? }] }。参数(完整问题)留在 assistant 工具调用历史中;人类回答后,下一步骤会看到紧凑 JSON 结果。等待人类不耗 token。待决问题会阻塞工具调用直到回答;取消搭乘轮次的 exec.signal(无 timeout-policy)。该工具从不渲染 UI;Native 渲染器保留紧凑 JSON 形态。
端到端提问流程
model calls ask_user_question
└─> dsh-tool-ask-user -> ctx.userQuestions.ask(request)
└─> 活跃 provider(Web UI)渲染问题
└─> 人类回答
└─> ask() 以 AskUserQuestionAnswer 回定
└─> 紧凑 JSON 工具结果 -> 恢复循环审批:一次性门禁
@deepseek-ai/dsh-user-approval 是通道中立、一次性的审批接缝。ctx.approval.request(req) 返回 allowed-once、rejected、cancelled 或 unavailable;缺失或失败的应答者失败关闭,且授权只适用于所请求的动作。
每个请求必须属于一个打开的 agent 轮次。该服务追加配对的 approval/asked 与 approval/decided 审计记录(均为仅日志——模型只看到请求方消费者最终的工具结果)。被中止的请求回定为 cancelled;提交前审计追加失败则拒绝,而不是返回未记录的决定。
应答者是 approval/request 瀑布监听器:为自有 agent 返回一个结果,或调用 next() 委派。agent 作用域监听器只接收该 agent 的请求;每个部署请组合一个终端应答者。ACP 自动化桥为其拥有的会话提供一次性机器决定。ApprovalPolicy 是 'ask' 或 'never';有效值是最后一个 approval/policy 事件并回退到配置,setApprovalPolicy() 是写路径。'never' 在交互式分派前拒绝。
工具管线把 ask 决策路由经该接缝,缺时失败关闭;沙箱 bash 工具也用它做升级重试。完整的权限模型请交叉参考 /zh/security/permissions。
审批策略运行时上下文贡献:
“ask” 之下:
Approval policy: ask. Operations that require approval may ask through the configured answerers;
without an available answerer, the request fails closed.
“never” 之下:
Approval prompts are disabled in this session: actions that require approval are rejected
automatically — do not request sandbox escalation (do not set `sandbox_permissions`).权限预设(ctx.permissionPresets)
@deepseek-ai/dsh-permission-presets 把每个具名预设与 sandbox/mode + approval/policy 捆绑。默认:workspace-write(workspace-write + ask)与 danger-full-access(danger-full-access + never)。set(session, name) 把变更的选择记录到仅日志的 permission/preset 事件,然后只在某个旋钮的有效值改变时才调用其 setter。current(events) 返回仍匹配的记录选择、首个匹配表项,或 custom。custom 仅派生——调用方可切离一个不匹配的组合,但无法瞄准或持久化它。该服务拥有 permission Settings 命名空间,其 defaultPreset 适用于未来会话(创建时把选择钉进会话)。两个可选子件提供产品表面:一个 permissions 会话投影单元与一个 /permission 命令。
UI 侧(简要)
packages/client/ui-user-questions 是问题接缝的 Web provider;packages/client/ui-input-trigger 在 Web UI 中提供斜杠命令输入捕获,把命令行经 ctx.commands.execute() 分派。packages/client/ui-commands 及其下模块渲染命令输出。这些客户端包消费接缝的规范形态(AskUserQuestionAnswer、CommandExecution),从不定制它们。
命令生命周期时间线
命令平面的事件序列值得单独隔离,因为每个微步骤都有不同的拥有者与持久化故事:
UI 捕获 "/compact" ui-input-trigger
│
▼
ctx.commands.execute(agent, line, signal)
│ parseCommand() → { name, rawInput }
│ find(agent, name) → 已注册处理器(或 undefined)
▼
追加 command/run { commandId, name, source, args? } ← 仅日志
│
▼
运行处理器(与中止信号竞速;UI 可取消)
│
▼
追加 command/done { commandId, kind, text, sourceEventSeq? } ← 仅日志
│
▼
适配器渲染 CommandResult 文本(UI 状态,绝非模型历史)
│
└ 若处理器变更了一个持久领域(goals / plan / compaction),
该领域自己的事件拥有权威记录关键边界:command/run 先于处理器并铸下 commandId;command/done 以结果种类回定;成功结果可通过 sourceEventSeq 命名一个较早的权威领域事件(例如 /compact → 它的 compaction/summary)。二者都不进入模型历史。若处理器显式通过接收 Agent 安排 agent 工作(像 /plan <消息> 那样),则那个生产者拥有由此产生的消息契约。
模型体验摘要
- 命令:发现、执行与直接输出耗零模型 token;输入/输出不提交。只有显式导航 agent 的命令生产者(如
/plan <消息>)才增加 token,与普通 agent 输入相当。 - 提问:待决的
ask_user_question在等待期间不耗 token;完整参数留在历史中,答案以紧凑 JSON 返回,循环随即恢复。 - 审批:
approval/asked/approval/decided审计仅日志;模型只看到请求方消费者allowed-once/rejected/cancelled/unavailable的结果。ask/never策略在首次请求与有效变化时贡献一条精简运行时上下文消息。
三个平面都让模型历史保持干净:人类命令文本、问题 UI 与审批审计都是 UI/审计状态,而每个权威决策都存在于它所变更的持久领域中。
进一步阅读
- 权限与审批 — 一次性审批门禁背后的完整权限模型。
- 目标与目标轮次 — 作为命令/工具消费者的
/goal命令与目标工具。 - 计划模式 —
/plan与plan-review问题意图。 docs/glossary.md— human command 与 command plane 的定义。packages/interaction/commands/src/index.ts—parseCommand()文法与command/run/command/done生命周期。docs/subsystems/commands.md、user-questions.md、approval.md— 子系统参考。