fix(feedback): keep payload in feedback event

This commit is contained in:
Turtle
2026-07-29 21:33:53 +08:00
parent 0ccd3ed463
commit eb6fa86481
42 changed files with 248 additions and 130 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md
2026-07-27-session-projection-and-command-log.md: 6a073c956c27bbfc65cff2d4f44ca12023df0cd5
2026-07-27-session-projection-and-command-log.zh.md: 500f07968db049e4a174ff3b7a075bfe095283db
2026-07-27-session-projection-and-command-log.md: 6ffdae7df9e908356c972f077d66b831f3b6a1ff
2026-07-27-session-projection-and-command-log.zh.md: d2a40fb72245df413c7790932a41ddea3be7902d

View File

@@ -115,11 +115,11 @@ The one existing violation of "no hooks through inject" — `DetailsInjected.use
Two log-only (non-surface, model-invisible) events, mirroring the `tool/call`/`tool/result` pairing:
```ts ignore-check
'command/run': { commandId: string; name: string; args: string; source: CommandSource }
'command/run': { commandId: string; name: string; args?: string; source: CommandSource }
'command/done': { commandId: string; kind: 'success' | 'error'; text?: string }
```
The host command executor (`packages/ui/commands`) appends `command/run` before invoking the handler and `command/done` at settlement — direct standalone appends on the receiving agent's session, in the same shape as every other plugin-owned log-only event after the [synthetic-turn removal](../../implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md): no turn wraps them (turns describe model-loop executions only), persistence drains them at ordinary checkpoints, and the commands package's own invariant companion enforces the run/done pairing. The payload is structured — `name` and `args` are the parser's own split (`parseCommand`'s name and rawInput), so a consumer (a projection unit folding its own command records, a rich command card) never re-parses a line. `text` is the handler's verbatim outcome — factual data of the same nature as `tool/result.content`, not presentation (how it is laid out remains client-computed at render time, satisfying the "presentation never enters the log" red line). Domains that want the model to know the outcome keep doing what they do today (plan's narration, goal's inject) — that is a domain decision, unchanged.
The host command executor (`packages/ui/commands`) appends `command/run` before invoking the handler and `command/done` at settlement — direct standalone appends on the receiving agent's session, in the same shape as every other plugin-owned log-only event after the [synthetic-turn removal](../../implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md): no turn wraps them (turns describe model-loop executions only), persistence drains them at ordinary checkpoints, and the commands package's own invariant companion enforces the run/done pairing. The payload is structured — `name` and, by default, `args` are the parser's own split (`parseCommand`'s name and rawInput), so a consumer (a projection unit folding its own command records, a rich command card) never re-parses a line. A definition sets `recordInput: false` when its authoritative domain event owns the payload; `command/run` then omits `args` rather than duplicating it. `text` is the handler's verbatim outcome — factual data of the same nature as `tool/result.content`, not presentation (how it is laid out remains client-computed at render time, satisfying the "presentation never enters the log" red line). Domains that want the model to know the outcome keep doing what they do today (plan's narration, goal's inject) — that is a domain decision, unchanged.
Because committed events broadcast on the mux stream, refresh persistence, multi-tab sync, and fork/resume recovery all come for free. The `command.execute` RPC degrades to admission — `{ matched, commandId? }`: whether the line resolved, and the minted pairing id when it did, so the issuing client can correlate its request with the flow node the lifecycle events produce. The one-shot notice channel (`runDetached` → `noticeFor`) is retired.

View File

@@ -115,11 +115,11 @@ type UseProjection = {
两个仅日志(非 surface、模型不可见)事件,镜像 `tool/call`/`tool/result` 的配对:
```ts ignore-check
'command/run': { commandId: string; name: string; args: string; source: CommandSource }
'command/run': { commandId: string; name: string; args?: string; source: CommandSource }
'command/done': { commandId: string; kind: 'success' | 'error'; text?: string }
```
host 侧命令执行器(`packages/ui/commands`)在调用处理器前追加 `command/run`,在结算时追加 `command/done`——在接收 agent 的会话上直接独立追加,与[合成轮次移除](../../implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md)之后所有插件自有 log-only 事件同一形状:没有轮次包裹它们(轮次只描述模型循环执行),持久化在常规检查点排空它们,run/done 配对由 commands 包自己的 invariant 伴生插件把守。载荷是结构化的——`name` 与 `args` 就是解析器自己的切分(`parseCommand` 的 name 与 rawInput),因此消费方(折叠自己命令记录的投影单元、富命令卡片)永远无需重新解析行文本。`text` 是处理器的原样结果——与 `tool/result.content` 同一性质的事实数据,不是呈现(版式如何编排仍由客户端在渲染时计算,满足「呈现永不入日志」这条红线)。想让模型知道结果的领域继续做它们今天在做的事(plan 的旁白、goal 的注入)——那是领域自己的决定,保持不变。
host 侧命令执行器(`packages/ui/commands`)在调用处理器前追加 `command/run`,在结算时追加 `command/done`——在接收 agent 的会话上直接独立追加,与[合成轮次移除](../../implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md)之后所有插件自有 log-only 事件同一形状:没有轮次包裹它们(轮次只描述模型循环执行),持久化在常规检查点排空它们,run/done 配对由 commands 包自己的 invariant 伴生插件把守。载荷是结构化的——`name` 以及默认携带的 `args` 来自解析器自己的切分(`parseCommand` 的 name 与 rawInput),因此消费方(折叠自己命令记录的投影单元、富命令卡片)永远无需重新解析行文本。当载荷由权威领域事件持有时,命令定义会设置 `recordInput: false`;此时 `command/run` 省略 `args`,而不是重复该载荷。`text` 是处理器的原样结果——与 `tool/result.content` 同一性质的事实数据,不是呈现(版式如何编排仍由客户端在渲染时计算,满足「呈现永不入日志」这条红线)。想让模型知道结果的领域继续做它们今天在做的事(plan 的旁白、goal 的注入)——那是领域自己的决定,保持不变。
由于已提交事件会在 mux 流上广播,刷新后仍在、多标签页同步、fork/恢复后可还原这三件事随之全部自动获得。`command.execute` RPC 退化为准入判定——`{ matched, commandId? }`:该行是否匹配命中,以及命中时新铸的配对 id,发起命令的客户端据此把自己的请求与生命周期事件产出的 flow 节点关联起来。一次性通知通道(`runDetached` → `noticeFor`)就此下线。