Register a global `/feedback` command so a user can record a remark about the session without spending a model turn. `/feedback <text>` acknowledges; empty or whitespace-only input returns a usage error. The plugin appends no session event of its own. `dsh-commands` already writes a `command/run` / `command/done` pair for every dispatched command, carrying the verbatim text and the settled outcome, and both records are log-only and non-surface. The feedback is therefore durably in the session log and invisible to the model without this package touching the log format. Text is never parsed, so `/feedback /plan felt slow` records that literal content. Nothing consumes the records; capture is deliberately inert. New group `packages/feedback/` — no existing group owns feedback capture. Its row raises the packages/README.md word ceiling by 10, which had no headroom; one redundant sentence there was removed to offset most of the cost.
6.0 KiB
Agent Note: /feedback 命令
Status: implemented
English | 中文
问题
用户在会话中途发现问题时,没有地方记下这个观察。告诉模型会浪费一个轮次、改变用户原本进行的对话,并把这条评论埋进派生历史,使后续读者无法找到它。写到会话之外则会丢失让它有意义的上下文:属于哪个会话、处于哪个时点、针对哪项工作。
采集接口必须能在用户产生不满的那一刻使用,因此任何需要用户离开 TUI 的方案都不可行;它还不能扰动正在进行的运行:不消耗模型 token、不产生工作轮次、不改变用户正在等待的请求。
决策
位于 packages/feedback/command-feedback/ 的 @deepseek-ai/dsh-command-feedback 通过 ctx.commands 注册一个全局 feedback 命令。/feedback <text> 给出确认;空输入或仅含空白的输入返回直接用法错误。处理器是同步的,只注入 commands,且没有任何配置。
该插件不追加属于自己的会话事件。dsh-commands 已经为每个已分发命令写入一对 command/run / command/done,携带命令名、原样未解析的后缀、调用来源以及结算结果。这些记录仅写入日志且非 surface,因此反馈会进入会话日志并对模型保持不可见,而本包无需向日志格式贡献任何内容。这些追加会启动持久化的常规即时排空;没有任何环节强制 flush,因此确认文本报告的是条目已记录在日志中,而非已经落盘。
采集刻意不产生后续动作:本仓库中没有任何代码读回这些记录。
为何不设专用的 session/feedback 事件
早先的实现声明过该事件,后来将其移除,因为它重复了注册表已经写入的记录:两者会携带相同文本、相隔极短时间先后追加,而消费方还得判断以哪一条为准。依据命令名筛选 command/run 记录已足以找到反馈,同时让本包完全不涉及会话事件格式——没有 SessionEventMap 合并、没有不变式关系、没有持久化目录条目。
代价是被记录的文本为原始后缀,包含其前导分隔空白;且反馈仅凭命令名与其他命令相区分。两者都属于尚不存在的消费方在读取时需要处理的问题,目前都不足以支撑再增加一条持久记录。
为何模型永不看到它
反馈是关于会话的,而不是会话的输入。将其作为 user 消息注入会改变下一次模型请求,与「记录不得扰动运行」的要求相冲突,也会让该评论成为它所评论的那段对话的一部分。command/run 与 command/done 不属于 SurfaceEventType,因此即便出错也无法获得 surfaceOp 或进入派生历史。
原样文本
不做任何解析。/feedback /plan felt slow 记录的就是该字面文本;开头的 /plan 是内容,而非嵌套命令。处理器仅为判断是否提供了文本而修剪。若采用 /goal 那样的控制词语法,对应的字面反馈将无法表达,这与采集接口的目的正好相反。
一个新的分组
packages/feedback/ 是新分组,因为现有分组都不拥有此职责:goal/ 负责目标状态,session-title/ 负责标题,core/ 是产品主干。该分组目前只有一个包;未来的消费方应加入该分组,而不是迫使这个包不断膨胀。
考虑过的替代方案
声明专用的 session/feedback 仅日志事件。 先实现后移除。它让反馈拥有一等的可查询类型和预先修剪的文本,但重复了注册表的记录,向已冻结的日志格式新增了一个 SessionEventMap 成员与持久化目录条目,并使同一行为产生两条记录而没有取舍规则。
通过 agent.inject() 将反馈作为 user 消息注入。 无需新增事件类型,并复用 /goal 变更所走的路径。已否决:它会让反馈对模型可见,从而进入下一次请求、改变正被评论的那次运行并消耗 token——与「不得扰动」要求的三个方面全部冲突。
让 /feedback 成为真正的空操作,什么都不记录。 这是对「什么都不做」最字面的理解。已否决:这会使命令失去意义——明确的要求是让这条评论进入会话日志。
在现有包中注册该命令,例如 packages/ui/commands。可省去新分组及其双语 README。已否决:ctx.commands 是注册表,而不是任意命令实现的归属地;且请求者明确要求独立的包。
从文本中解析结构(类别前缀、严重程度标记)。已否决,属于投机设计:目前没有消费方使用该结构,而任何控制词语法都会让对应的字面反馈无法记录。原样文本是未来消费方可以收窄的最宽接口;而已被解析的接口无法事后放宽。
改为提供面向模型的工具。 已否决:反馈是人类的直接观察。经由模型会消耗一个轮次、让模型改写用户的原话,并使记录取决于模型是否选择调用该工具。
后果
TUI 无条件挂载该命令:没有配置,也不依赖 goal 栈。无头 CLI、ACP 和 JSON-RPC 应用不消费 ctx.commands,因此 /feedback 在那里不可用。
本包现已小到其全部契约就是命令定义加一个校验分支。它不拥有任何会话事件,因此无需不变式关系,也不可能影响回放、fork 或崩溃恢复。
延期事项:没有消费方;没有结构化字段;不支持修改或撤回,因为日志仅追加且本包不新增 tombstone;被记录的文本未修剪,需由消费方在读取时处理;且没有显式持久化屏障,因此紧临崩溃前记录的条目可能与其他未 flush 的尾部一同丢失。
本次变更不附带 snapshot。AGENTS.md 要求面向产品用户的可见行为变更通过可运行示例附带无密钥 snapshot;此项按请求者的明确指示跳过。包测试连同一个基于真实 cordis.yml 的 Loader 组合测试即为全部证据,此外还有在组装后 TUI 中的交互验证。