Explorar o código

fix(feedback): keep the category tuple out of types.ts, retire the toast with its entry, and answer the review

creatixchu hai 1 mes
pai
achega
3e53a08f36
Modificáronse 24 ficheiros con 67 adicións e 50 borrados
  1. 2 2
      .agents/notes/implemented/feature/2026-09-08-feedback-dialog-and-categories.i18n.yaml
  2. 1 1
      .agents/notes/implemented/feature/2026-09-08-feedback-dialog-and-categories.md
  3. 1 1
      .agents/notes/implemented/feature/2026-09-08-feedback-dialog-and-categories.zh.md
  4. 2 2
      docs/persistence-catalog.i18n.yaml
  5. 1 1
      docs/persistence-catalog.md
  6. 1 1
      docs/persistence-catalog.zh.md
  7. 2 2
      docs/subsystems/feedback.i18n.yaml
  8. 1 1
      docs/subsystems/feedback.md
  9. 1 1
      docs/subsystems/feedback.zh.md
  10. 2 2
      packages/client/ui-message-feedback/README.i18n.yaml
  11. 1 1
      packages/client/ui-message-feedback/README.md
  12. 1 1
      packages/client/ui-message-feedback/README.zh.md
  13. 4 1
      packages/client/ui-message-feedback/src/client/FeedbackDialog.tsx
  14. 6 2
      packages/client/ui-message-feedback/src/client/controller.ts
  15. 2 2
      packages/client/ui-message-feedback/src/client/surface.ts
  16. 9 1
      packages/client/ui-message-feedback/tests/feedback-dialog.client.spec.tsx
  17. 2 2
      packages/feedback/command-feedback/README.i18n.yaml
  18. 3 1
      packages/feedback/command-feedback/README.md
  19. 3 1
      packages/feedback/command-feedback/README.zh.md
  20. 15 1
      packages/feedback/command-feedback/src/index.ts
  21. 3 17
      packages/feedback/command-feedback/src/types.ts
  22. 1 1
      packages/feedback/message-feedback/src/index.ts
  23. 1 2
      scripts/snapshots/python-sdk-single-exe/advanced/session.v2.jsonl
  24. 2 3
      snapshots/sdk/text-turn/session.v2.jsonl

+ 2 - 2
.agents/notes/implemented/feature/2026-09-08-feedback-dialog-and-categories.i18n.yaml

@@ -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/implemented/feature/2026-09-08-feedback-dialog-and-categories.md
-2026-09-08-feedback-dialog-and-categories.md: dbd417e8df4f1cb2b957f4beb612375b05aab08b
-2026-09-08-feedback-dialog-and-categories.zh.md: 7fe29c905f410c84234ea757b7b4bab9f674249d
+2026-09-08-feedback-dialog-and-categories.md: 02a41f08541cca85dad7cd3c2c984b5e7cd8fd6d
+2026-09-08-feedback-dialog-and-categories.zh.md: 40d83360e52bff569120820b78959a7cc3f4db4f

+ 1 - 1
.agents/notes/implemented/feature/2026-09-08-feedback-dialog-and-categories.md

@@ -30,4 +30,4 @@ The dialog is the shared Modal card at the design's width; the design's checkbox
 
 ## Consequences
 
-Adding a category means adding it to the union, to the Host tuple, to the dialog's chip record, and to the `feedback` dictionaries; the client bundle purity gate forbids a value import from a Host package, so the dialog restates the taxonomy as a `Record<FeedbackCategory, true>` whose key order is the chip order and whose completeness the compiler checks. The frozen released-v2 payload inventory still lists `feedback/record` as `text` only: it governs artifacts migrated from older generations, which cannot carry the new members, while equal-version restoration applies the installed vocabulary. The `message-feedback-layout` web scenario that pinned the popover's geometry is deleted with the popover. The message-feedback and feedback-release web goldens and the feedback subsystem doc changed in the same PR; the `/feedback <text>` command path and its SDK expected outputs are unchanged.
+Adding a category means adding it to the union, to the Host tuple, to the dialog's chip record, and to the `feedback` dictionaries; the client bundle purity gate forbids a value import from a Host package, so the dialog restates the taxonomy as a `Record<FeedbackCategory, true>` whose key order is the chip order and whose completeness the compiler checks. The frozen released-v2 payload inventory still lists `feedback/record` as `text` only: it governs artifacts migrated from older generations, which cannot carry the new members, while equal-version restoration applies the installed vocabulary. The `message-feedback-layout` web scenario that pinned the popover's geometry is deleted with the popover. The message-feedback and feedback-release web goldens and the feedback subsystem doc changed in the same PR; the SDK feedback producer records a categorized Session remark and a categorized Dislike, so both SDK expected outputs carry the new members.

+ 1 - 1
.agents/notes/implemented/feature/2026-09-08-feedback-dialog-and-categories.zh.md

@@ -30,4 +30,4 @@ Web 客户端有两条互不相连的反馈路径,且都没有可见结果。`
 
 ## 后果
 
-新增分类意味着把它加进联合类型、宿主元组、弹窗的标签记录和 `feedback` 词典;客户端打包纯度门禁止从宿主包做值导入,因此弹窗以 `Record<FeedbackCategory, true>` 重述分类表,键的顺序就是标签顺序,完整性由编译器检查。冻结的已发布 v2 载荷清单仍把 `feedback/record` 列为仅有 `text`:它管辖从旧代际迁移来的产物,那些产物不可能携带新成员,而同版本恢复应用的是已安装词汇。固定浮层几何的 `message-feedback-layout` Web 场景随浮层一起删除。message-feedback 与 feedback-release 的 Web 期望输出和反馈子系统文档在同一个 PR 中更新;`/feedback <text>` 命令路径及其 SDK 期望输出不变。
+新增分类意味着把它加进联合类型、宿主元组、弹窗的标签记录和 `feedback` 词典;客户端打包纯度门禁止从宿主包做值导入,因此弹窗以 `Record<FeedbackCategory, true>` 重述分类表,键的顺序就是标签顺序,完整性由编译器检查。冻结的已发布 v2 载荷清单仍把 `feedback/record` 列为仅有 `text`:它管辖从旧代际迁移来的产物,那些产物不可能携带新成员,而同版本恢复应用的是已安装词汇。固定浮层几何的 `message-feedback-layout` Web 场景随浮层一起删除。message-feedback 与 feedback-release 的 Web 期望输出和反馈子系统文档在同一个 PR 中更新;SDK 的反馈生产方会记录一条带分类的 Session 备注和一条带分类的差评,因此两个 SDK 期望输出都携带新成员。

+ 2 - 2
docs/persistence-catalog.i18n.yaml

@@ -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 docs/persistence-catalog.md
-persistence-catalog.md: c83e98e2722430b29967dcb0cbc7052834c73ecc
-persistence-catalog.zh.md: 187c39797d94e6c38ca38833281fb3650fe6463e
+persistence-catalog.md: bf70879f80f959c865eca17efbdbce3ef78fda2e
+persistence-catalog.zh.md: 1332dfdc8778dde651eeedefab2f87b09d67c06e

+ 1 - 1
docs/persistence-catalog.md

@@ -447,7 +447,7 @@ Source: [`packages/feedback/message-feedback/src/types.ts:56`](../packages/feedb
 'feedback/record': FeedbackRecord
 ```
 
-Source: [`packages/feedback/command-feedback/src/types.ts:54`](../packages/feedback/command-feedback/src/types.ts)
+Source: [`packages/feedback/command-feedback/src/types.ts:40`](../packages/feedback/command-feedback/src/types.ts)
 
 ### `goal/*`
 

+ 1 - 1
docs/persistence-catalog.zh.md

@@ -449,7 +449,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
 'feedback/record': FeedbackRecord
 ```
 
-来源:[`packages/feedback/command-feedback/src/types.ts:54`](../packages/feedback/command-feedback/src/types.ts)
+来源:[`packages/feedback/command-feedback/src/types.ts:40`](../packages/feedback/command-feedback/src/types.ts)
 
 ### `goal/*`
 

+ 2 - 2
docs/subsystems/feedback.i18n.yaml

@@ -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 docs/subsystems/feedback.md
-feedback.md: b3dec08a88a5c8373b06ac62cbbe23e18c6da12a
-feedback.zh.md: 4e38f6d951904abe253bfd160dc5d26c252e5ab0
+feedback.md: 2c6728d97a6a62735e4ef8b97983f08060d4a2c5
+feedback.zh.md: db9f03cb82685763bc3b424c0f6b29738f59ec99

+ 1 - 1
docs/subsystems/feedback.md

@@ -311,7 +311,7 @@ Like records the bare positive judgment at once and shows the acknowledgement to
 - The Host contract records no authenticated actor or audit identity and therefore assumes a trusted caller boundary.
 - The Web controls appear in the chat view only. The trajectory and waterfall views render no feedback entry even though their assistant nodes carry the same `messageId`.
 - The Web controller does not consume feedback log events, so a second tab's rating becomes visible on reconnect or on the next conflict reply rather than immediately.
-- The dialog does not pre-check `maxNoteBytes`; an oversized description fails on submit with `note-too-large` rather than while typing.
+- The dialog does not pre-check `maxNoteBytes`; an oversized description for a message fails on submit with `note-too-large` rather than while typing. A Session remark has no size bound, as the `/feedback` command never had one.
 - `sessionFeedback.record` serves live Sessions only and answers `session-not-found` otherwise; the dialog reports that failure when its Session retires while it is open.
 
 <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->

+ 1 - 1
docs/subsystems/feedback.zh.md

@@ -311,7 +311,7 @@ fork 种子可以包含父 Session 的反馈事件,但 payload 保留父级 `s
 - Host 约定不记录已认证的 actor 或审计身份,因此假设调用方边界可信。
 - Web 控件只出现在对话视图。trajectory 与 waterfall 视图不渲染反馈条目,尽管它们的助手节点携带相同的 `messageId`。
 - Web 控制器不消费反馈日志事件,因此另一个标签页的评分要等到重连或下一次冲突响应才可见,不会立即出现。
-- 弹窗不预先校验 `maxNoteBytes`;超长描述在提交时以 `note-too-large` 失败,而不是在输入过程中。
+- 弹窗不预先校验 `maxNoteBytes`;针对消息的超长描述在提交时以 `note-too-large` 失败,而不是在输入过程中。Session 级备注没有大小上限,`/feedback` 命令从来也没有。
 - `sessionFeedback.record` 只服务 live Session,否则回答 `session-not-found`;弹窗打开期间 Session 退役时,弹窗会报告该失败。
 
 <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->

+ 2 - 2
packages/client/ui-message-feedback/README.i18n.yaml

@@ -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/client/ui-message-feedback/README.md
-README.md: 81a37bd8a62c49a17a8c70918ff3de1fd5fc4eec
-README.zh.md: c7015c62bba808459c348b3fb85c7cae433830d6
+README.md: 8ffcf87a59ec95fe783a0379423b93d4bf965a3e
+README.zh.md: a29448eaaddf0f089d8fc12c1315732fd13fc304

+ 1 - 1
packages/client/ui-message-feedback/README.md

@@ -76,7 +76,7 @@ None; feedback mutations leave the model-visible history unchanged.
 
 These limits define the current feedback surface. They are current package constraints, not a general rating comparison or a task backlog.
 
-- **Note size is a Host policy** — the deployment configures `maxNoteBytes` (8192 in the Web bundle) and the Host rejects an oversized note with `note-too-large`. The dialog does not pre-check the limit, so an oversized description fails on submit rather than while typing.
+- **Note size is a Host policy** — the deployment configures `maxNoteBytes` (8192 in the Web bundle) and the Host rejects an oversized note with `note-too-large`. The dialog does not pre-check the limit, so an oversized description for a message fails on submit rather than while typing; a Session remark has no bound.
 - **No note on a Like** — only the Dislike dialog collects a category and description; a Like records the bare judgment.
 - **No cross-tab push** — a second tab's rating becomes visible on reconnect or on the next conflict reply, not immediately; the controller does not consume feedback log events.
 - **Chat view only** — the trajectory and waterfall views render no feedback controls even though their assistant nodes carry the same `messageId`.

+ 1 - 1
packages/client/ui-message-feedback/README.zh.md

@@ -76,7 +76,7 @@ kind: "package-reference"
 
 这些限制界定了当前反馈界面。它们是当前包约束,不是通用评分对比或任务积压。
 
-- **备注大小是宿主策略**——部署方配置 `maxNoteBytes`(Web bundle 中为 8192),超长备注由宿主以 `note-too-large` 拒绝。弹窗不预先校验该上限,因此超长描述在提交时才失败,而不是在输入过程中。
+- **备注大小是宿主策略**——部署方配置 `maxNoteBytes`(Web bundle 中为 8192),超长备注由宿主以 `note-too-large` 拒绝。弹窗不预先校验该上限,因此针对消息的超长描述在提交时才失败,而不是在输入过程中;Session 级备注没有上限。
 - **点赞不带备注**——只有点踩弹窗收集分类与描述;点赞只记录判断本身。
 - **无跨标签页推送**——另一个标签页的评分要等到重连或下一次冲突响应才可见,不会立即出现;控制器不消费反馈日志事件。
 - **仅限对话视图**——trajectory 与 waterfall 视图不渲染反馈控件,尽管它们的助手节点也带有相同的 `messageId`。

+ 4 - 1
packages/client/ui-message-feedback/src/client/FeedbackDialog.tsx

@@ -7,7 +7,7 @@
  * @module @deepseek-ai/dsh-client-ui-message-feedback/client/FeedbackDialog
  */
 
-import { useCallback, useLayoutEffect, useRef, useState } from 'react'
+import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
 import { Button, IconCheckOutline16, Modal, Toast } from '@deepseek-ai/dsh-client-ui-primitives'
 import type { FeedbackCategory } from '@deepseek-ai/dsh-command-feedback/types'
 import type { FeedbackDialogProps } from './slots.ts'
@@ -50,6 +50,9 @@ export function FeedbackDialog({ useDialog, edit, submit, dismiss, dismissToast,
   }, [])
   const toast = state.toast
   const onToastDone = useCallback(() => { dismissToast(toast) }, [dismissToast, toast])
+  // A toast retires with the entry that showed it: the Toast's own timer dies
+  // on unmount, and the Session's controller must not replay it on return.
+  useEffect(() => () => { dismissToast(toast) }, [dismissToast, toast])
   const failure = state.failure === null ? null : t(FAILURE_COPY[state.failure] ?? 'error.generic')
 
   return (

+ 6 - 2
packages/client/ui-message-feedback/src/client/controller.ts

@@ -62,8 +62,12 @@ const DISPOSED: MessageFeedbackActionFailure = Object.freeze({
   error: Object.freeze({ code: 'disposed', message: 'feedback controller is disposed' }),
 })
 
-/** Human-readable text for one business failure code. */
-function describe(code: string): string {
+/**
+ * Human-readable text for one business failure code.
+ * @param code - the Host's business failure code.
+ * @returns the developer-facing description carried in the failure branch.
+ */
+export function describe(code: string): string {
   switch (code) {
     case 'session-not-found': return 'this session is no longer persisted'
     case 'target-not-found': return 'this message is not a persisted assistant message'

+ 2 - 2
packages/client/ui-message-feedback/src/client/surface.ts

@@ -9,7 +9,7 @@
 import type { Context as ClientContext } from '@deepseek-ai/cordis'
 import type { SessionId } from '@deepseek-ai/dsh-session/types'
 import type { FeedbackRecord } from '@deepseek-ai/dsh-command-feedback/types'
-import { MessageFeedbackController, type MessageFeedbackActionResult } from './controller.ts'
+import { MessageFeedbackController, describe, type MessageFeedbackActionResult } from './controller.ts'
 import { FeedbackDialogController } from './dialog.ts'
 
 /** The per-session pair behind every entry of one Session. */
@@ -33,7 +33,7 @@ export class FeedbackSurface {
     const carried = await this.ctx.remote.sessionFeedback.record({ sessionId: this.sessionId, ...entry })
     if (!carried.ok) return { ok: false, error: { code: carried.error.code, message: carried.error.message } }
     if (carried.value.ok) return { ok: true }
-    return { ok: false, error: { code: carried.value.error.code, message: 'this session is no longer live' } }
+    return { ok: false, error: { code: carried.value.error.code, message: describe(carried.value.error.code) } }
   }
 
   /** Drop both controllers when the owning fiber unloads. */

+ 9 - 1
packages/client/ui-message-feedback/tests/feedback-dialog.client.spec.tsx

@@ -12,7 +12,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest'
 import { act, cleanup, fireEvent, render } from '@testing-library/react'
 import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime'
 import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts'
-import { FEEDBACK_CATEGORIES } from '@deepseek-ai/dsh-command-feedback/types'
+import { FEEDBACK_CATEGORIES } from '@deepseek-ai/dsh-command-feedback'
 import { FeedbackDialog } from '../src/client/FeedbackDialog.tsx'
 import type { FeedbackDialogState } from '../src/client/dialog.ts'
 import { zh } from '../src/client/locales.ts'
@@ -107,6 +107,14 @@ describe('FeedbackDialog', () => {
     expect(other.getByRole('status').textContent).toBe(zh['error.generic'])
   })
 
+  it('retires the toast when the entry unmounts, so a Session switch does not replay it', () => {
+    const ui = mount({ target: null, toast: 3 })
+
+    ui.unmount()
+
+    expect(ui.dismissToast).toHaveBeenCalledWith(3)
+  })
+
   it('shows the acknowledgement toast and retires it after the fade', () => {
     vi.useFakeTimers()
     try {

+ 2 - 2
packages/feedback/command-feedback/README.i18n.yaml

@@ -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/feedback/command-feedback/README.md
-README.md: ed920f1551734513929425c8a2c415c1e86cb487
-README.zh.md: d6d67e2825c00454a611073e3f59af6323d8170b
+README.md: ad310f15b5b5b49fe879e063bd7d3c7c5518ebb8
+README.zh.md: d0a4ab64a0f41165c291d974684cf2c7b241e7a4

+ 3 - 1
packages/feedback/command-feedback/README.md

@@ -57,9 +57,11 @@ The ids are durable log vocabulary shared with per-message feedback; each surfac
 
 ### Recording feedback from your own UI
 
-Feedback does not have to come from the slash command or the dialog: any UI, hook, or host integration can record a remark directly through `recordFeedback` or the `sessionFeedback` Remote, with the same guarantees and without a model turn. A custom app that wants the slash command mounts the command registry plus this plugin:
+Feedback does not have to come from the slash command or the dialog: any UI, hook, or host integration can record a remark directly through `recordFeedback` or the `sessionFeedback` Remote, with the same guarantees and without a model turn. A custom app that wants the slash command mounts the Session store, the command registry, and this plugin; the Session store is what the `sessionFeedback` Remote resolves live Sessions from:
 
 ```yaml
+- id: session
+  name: '@deepseek-ai/dsh-session'
 - id: commands
   name: '@deepseek-ai/dsh-commands'
 - id: command-feedback

+ 3 - 1
packages/feedback/command-feedback/README.zh.md

@@ -57,9 +57,11 @@ kind: "package-reference"
 
 ### 从自己的 UI 记录反馈
 
-反馈不一定来自斜杠命令或弹窗:任何 UI、钩子或 host 集成都可以通过 `recordFeedback` 或 `sessionFeedback` Remote 直接记录评价,享有同样的保证且无需模型轮次。想要斜杠命令的自定义应用,把命令注册表与本插件组合在一起即可:
+反馈不一定来自斜杠命令或弹窗:任何 UI、钩子或 host 集成都可以通过 `recordFeedback` 或 `sessionFeedback` Remote 直接记录评价,享有同样的保证且无需模型轮次。想要斜杠命令的自定义应用,把 Session 存储、命令注册表与本插件组合在一起即可;`sessionFeedback` Remote 从 Session 存储中解析 live Session:
 
 ```yaml
+- id: session
+  name: '@deepseek-ai/dsh-session'
 - id: commands
   name: '@deepseek-ai/dsh-commands'
 - id: command-feedback

+ 15 - 1
packages/feedback/command-feedback/src/index.ts

@@ -14,14 +14,28 @@ import type { Session } from '@deepseek-ai/dsh-session'
 import { getOrCreateAnonymousUserId } from '@deepseek-ai/dsh-anonymous-user-id'
 import { TypertRemoteService, Remote } from '@deepseek-ai/dsh-typert-protocol'
 import type {
+  FeedbackCategory,
   FeedbackRecord,
   SessionFeedbackRecordRequest,
   SessionFeedbackRecordResult,
 } from './types.ts'
 
-export { FEEDBACK_CATEGORIES } from './types.ts'
 export type * from './types.ts'
 
+/**
+ * Every feedback category in the order product surfaces present them; each
+ * surface owns its localized labels.
+ */
+export const FEEDBACK_CATEGORIES = [
+  'task-result',
+  'instruction-following',
+  'product-interaction',
+  'service-stability',
+  'resource-cost',
+  'security-privacy-permission',
+  'other',
+] as const satisfies readonly FeedbackCategory[]
+
 export const name = 'command-feedback'
 export const inject = ['commands']
 

+ 3 - 17
packages/feedback/command-feedback/src/types.ts

@@ -1,9 +1,9 @@
 /**
  * Public vocabulary of Session-level feedback: the fixed category taxonomy,
  * the `feedback/record` event payload, and the `sessionFeedback.record`
- * Remote request and result types. Host packages (message-feedback), the
- * generated Remote client, and tests import the tuple; a browser plugin
- * imports types only, because its bundle may not carry Host values.
+ * Remote request and result types. Types only; the runtime category tuple is
+ * exported by the package entry, and a browser plugin imports types alone
+ * because its bundle may not carry Host values.
  * @module @deepseek-ai/dsh-command-feedback/types
  */
 
@@ -19,20 +19,6 @@ export type FeedbackCategory =
   | 'security-privacy-permission'
   | 'other'
 
-/**
- * Every feedback category in the order product surfaces present them; each
- * surface owns its localized labels.
- */
-export const FEEDBACK_CATEGORIES = [
-  'task-result',
-  'instruction-following',
-  'product-interaction',
-  'service-stability',
-  'resource-cost',
-  'security-privacy-permission',
-  'other',
-] as const satisfies readonly FeedbackCategory[]
-
 /**
  * One recorded human remark about a Session. Both members are optional: a
  * submission with neither still records that the human asked for the

+ 1 - 1
packages/feedback/message-feedback/src/index.ts

@@ -9,7 +9,7 @@ import { isDeepStrictEqual } from 'node:util'
 import { Context, Service } from '@deepseek-ai/cordis'
 import s from '@deepseek-ai/schemastery'
 import { z } from 'zod'
-import { FEEDBACK_CATEGORIES } from '@deepseek-ai/dsh-command-feedback/types'
+import { FEEDBACK_CATEGORIES } from '@deepseek-ai/dsh-command-feedback'
 import { SessionSeq } from '@deepseek-ai/dsh-session/types'
 import { deriveEventMessage, isAppendSurfaceEvent } from '@deepseek-ai/dsh-session/surface'
 import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session/types'

+ 1 - 2
scripts/snapshots/python-sdk-single-exe/advanced/session.v2.jsonl

@@ -61,8 +61,7 @@
 {"type":"command/run","data":{"commandId":"{{command:1}}","name":"feedback","source":{"kind":"user"}}}
 {"type":"feedback/record","data":{"text":"The session needs a clearer explanation."}}
 {"type":"command/done","data":{"commandId":"{{command:1}}","kind":"success","text":"Feedback recorded for session {{parent}}\nAnonymous user: {{anonymous-user}}."}}
-{"type":"feedback/record","data":{"category":"other"}}
-{"type":"feedback/message-put","data":{"sessionId":"{{parent}}","item":{"messageId":"{{message:1}}","rating":"negative","note":"Explain the result.","category":"task-result","version":"{{feedback-version:1}}","createdAt":0,"updatedAt":0}}}
+{"type":"feedback/message-put","data":{"sessionId":"{{parent}}","item":{"messageId":"{{message:1}}","rating":"negative","note":"Explain the result.","version":"{{feedback-version:1}}","createdAt":0,"updatedAt":0}}}
 {"type":"feedback/message-put","data":{"sessionId":"{{parent}}","item":{"messageId":"{{message:1}}","rating":"positive","note":"The explanation is clear now.","version":"{{feedback-version:2}}","createdAt":0,"updatedAt":0}}}
 {"type":"feedback/message-delete","data":{"sessionId":"{{parent}}","messageId":"{{message:1}}"}}
 {"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}}

+ 2 - 3
snapshots/sdk/text-turn/session.v2.jsonl

@@ -12,13 +12,12 @@
 {"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
 {"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
 {"type":"session-log-deepseek/delivery-accepted","data":{"sessionId":"{{session:1}}","sessionFormatVersion":2,"throughSeq":11}}
-{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly \"SDK snapshot OK\". Let me do that."},{"type":"text","text":"SDK snapshot OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:3}}"},"usage":{"inputTokens":1769,"outputTokens":24,"cacheReadTokens":0,"reasoningTokens":19},"stream":[{"type":"chunk","time":1788871683462,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1788871683462,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," reply"," with"," exactly"," \"","SD","K"," snapshot"," OK","\"."," Let"," me"," do"," that","."]},{"type":"chunk","time":1788871683462,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1788871683462,"index":1,"dt":[0,0,0],"texts":["SD","K"," snapshot"," OK"]},{"type":"chunk","time":1788871683462,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to reply with exactly \"SDK snapshot OK\". Let me do that."}}},{"type":"chunk","time":1788871683462,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"SDK snapshot OK"}}},{"type":"chunk","time":1788871683462,"chunk":{"type":"usage","usage":{"inputTokens":1769,"outputTokens":24,"cacheReadTokens":0,"reasoningTokens":19}}},{"type":"chunk","time":1788871683462,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
+{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly \"SDK snapshot OK\". Let me do that."},{"type":"text","text":"SDK snapshot OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:3}}"},"usage":{"inputTokens":1769,"outputTokens":24,"cacheReadTokens":0,"reasoningTokens":19},"stream":[{"type":"chunk","time":1788603540672,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1788603540672,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," reply"," with"," exactly"," \"","SD","K"," snapshot"," OK","\"."," Let"," me"," do"," that","."]},{"type":"chunk","time":1788603540672,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1788603540672,"index":1,"dt":[0,0,0],"texts":["SD","K"," snapshot"," OK"]},{"type":"chunk","time":1788603540672,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to reply with exactly \"SDK snapshot OK\". Let me do that."}}},{"type":"chunk","time":1788603540672,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"SDK snapshot OK"}}},{"type":"chunk","time":1788603540672,"chunk":{"type":"usage","usage":{"inputTokens":1769,"outputTokens":24,"cacheReadTokens":0,"reasoningTokens":19}}},{"type":"chunk","time":1788603540672,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
 {"type":"step/end","data":{"turn":1,"step":1}}
 {"type":"command/run","data":{"commandId":"{{command:1}}","name":"feedback","source":{"kind":"user"}}}
 {"type":"feedback/record","data":{"text":"The session needs a clearer explanation."}}
 {"type":"command/done","data":{"commandId":"{{command:1}}","kind":"success","text":"Feedback recorded for session {{session:1}}\nAnonymous user: {{id:1}}."}}
-{"type":"feedback/record","data":{"category":"other"}}
-{"type":"feedback/message-put","data":{"sessionId":"{{session:1}}","item":{"messageId":"{{message:3}}","rating":"negative","note":"Explain the result.","category":"task-result","version":"{{id:2}}","createdAt":0,"updatedAt":0}}}
+{"type":"feedback/message-put","data":{"sessionId":"{{session:1}}","item":{"messageId":"{{message:3}}","rating":"negative","note":"Explain the result.","version":"{{id:2}}","createdAt":0,"updatedAt":0}}}
 {"type":"feedback/message-put","data":{"sessionId":"{{session:1}}","item":{"messageId":"{{message:3}}","rating":"positive","note":"The explanation is clear now.","version":"{{id:3}}","createdAt":0,"updatedAt":0}}}
 {"type":"feedback/message-delete","data":{"sessionId":"{{session:1}}","messageId":"{{message:3}}"}}
 {"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}}