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 packages/plan/plan-mode/README.md
README.md: d3c2c14fe616e1c9b4e33b716570b084db6474cf
README.zh.md: 6d6878c4b0300a716ad16be60fd86bc79f1514ba
README.md: e3a98115d2d9f14fa0bb46e4d867f6b79cbf269d
README.zh.md: f8481cff12992e83af39498908c5ca2624a4f974

View File

@@ -20,7 +20,7 @@ The TUI consumes the plugin-owned `/plan` command; other front doors may drive t
## Session projection
When the composition mounts `ctx.sessionProjections` ([`@deepseek-ai/dsh-session-projection`](../../session-projection/session-projection/README.md)), this package registers the `plan` projection unit under an injected child. The unit folds two event kinds: a `command/run` record named `plan` sets the wanted target (`off` → inactive, anything else → active), and `plan/mode` commits the logged state and clears it; every other event returns the same state reference. `view` derives `{ active, pending }`, where `pending` is true only while an outstanding selection differs from the logged state — a pure replay quantity, so host restarts, other tabs, and cold reads all recover it from the log alone (the `/plan` handler calls `set()` before any failing path, keeping the logged request and the run plane from forking). The key merges into `SessionProjectionMap` from `src/types.ts` (served to host consumers via `./types` and client aggregates via `./client`); the framework drives the unit and carriers serve the value on the history tail page and the `session/projection` push frame. Compositions without the registry are unaffected.
When the composition mounts `ctx.sessionProjections` ([`@deepseek-ai/dsh-session-projection`](../../session-projection/session-projection/README.md)), this package registers the `plan` projection unit under an injected child. The unit folds two event kinds: a `command/run` record named `plan` with recorded `args` sets the wanted target (`off` → inactive, anything else → active), and `plan/mode` commits the logged state and clears it; every other event returns the same state reference. `view` derives `{ active, pending }`, where `pending` is true only while an outstanding selection differs from the logged state — a pure replay quantity, so host restarts, other tabs, and cold reads all recover it from the log alone (the `/plan` handler calls `set()` before any failing path, keeping the logged request and the run plane from forking). The key merges into `SessionProjectionMap` from `src/types.ts` (served to host consumers via `./types` and client aggregates via `./client`); the framework drives the unit and carriers serve the value on the history tail page and the `session/projection` push frame. Compositions without the registry are unaffected.
## Configuration

View File

@@ -20,7 +20,7 @@ TUI 消费插件拥有的 `/plan` 命令;其他入口可以直接驱动同一
## 会话投影
当组合挂载 `ctx.sessionProjections`[`@deepseek-ai/dsh-session-projection`](../../session-projection/session-projection/README.md))时,本包在注入子插件下注册 `plan` 投影单元。该单元折叠两种事件:名为 `plan``command/run` 记录设置目标值(`off` → 未激活,其余 → 激活),`plan/mode` 提交已记录状态并将其清除;其他任何事件返回同一状态引用。`view` 推导 `{ active, pending }`,其中 `pending` 仅在未兑现的选择不同于已记录状态时为 true——它是纯回放量host 重启、其他标签页与冷读都只凭日志即可恢复(`/plan` 处理器在任何可能失败的路径之前调用 `set()`使已入日志的请求与运行面不可能分叉。key 从 `src/types.ts` merge 进 `SessionProjectionMap`host 消费方经 `./types`、client 聚合经 `./client`);框架驱动单元,载体在历史尾页与 `session/projection` 推送帧上提供该值。未挂注册表的组合不受影响。
当组合挂载 `ctx.sessionProjections`[`@deepseek-ai/dsh-session-projection`](../../session-projection/session-projection/README.md))时,本包在注入子插件下注册 `plan` 投影单元。该单元折叠两种事件:名为 `plan` 且带有已记录 `args``command/run` 记录设置目标值(`off` → 未激活,其余 → 激活),`plan/mode` 提交已记录状态并将其清除;其他任何事件返回同一状态引用。`view` 推导 `{ active, pending }`,其中 `pending` 仅在未兑现的选择不同于已记录状态时为 true——它是纯回放量host 重启、其他标签页与冷读都只凭日志即可恢复(`/plan` 处理器在任何可能失败的路径之前调用 `set()`使已入日志的请求与运行面不可能分叉。key 从 `src/types.ts` merge 进 `SessionProjectionMap`host 消费方经 `./types`、client 聚合经 `./client`);框架驱动单元,载体在历史尾页与 `session/projection` 推送帧上提供该值。未挂注册表的组合不受影响。
## 配置

View File

@@ -234,6 +234,7 @@ export class PlanModeService extends Service {
init: () => ({ active: false, wanted: null }),
apply: (state, event) => {
if (event.type === 'command/run' && event.data.name === 'plan') {
if (event.data.args === undefined) return state
const wanted = event.data.args.trim() !== 'off'
return wanted === state.wanted ? state : { active: state.active, wanted }
}

View File

@@ -1,9 +1,9 @@
/**
* The `plan` projection unit (session-projection RFC's complete example): a
* double-event fold over the session log. `command/run` records named `plan`
* set the wanted target (`off` → false, anything else → true); `plan/mode`
* commits and clears it; `view` derives `{ active, pending }` where pending
* is true only while an outstanding selection differs from the logged state.
* with recorded input set the wanted target (`off` → false, anything else
* → true); `plan/mode` commits and clears it. `view` reports pending only
* while an outstanding selection differs from the logged state.
* Pending is thereby a pure replay quantity — a cold fold answers it without
* the service's in-memory intent. Composition without plan-mode has no `plan`
* key; unloading the fiber removes it (HMR safety).
@@ -88,6 +88,11 @@ describe('plan projection unit', () => {
commandId: CommandId('other-1'), name: 'compact', args: '', source: { kind: 'user' },
})
expect(bench.values().plan).toEqual({ active: true, pending: false })
// A command lifecycle with omitted input carries no plan selection.
bench.session.append('command/run', {
commandId: CommandId('plan-no-input'), name: 'plan', source: { kind: 'user' },
})
expect(bench.values().plan).toEqual({ active: true, pending: false })
runPlanCommand(bench.session, ' off', 1)
expect(bench.values().plan).toEqual({ active: true, pending: true })
commitPlanMode(bench.session, false, 1)