Merge origin/master into xtr/agent-setup-hook
This commit is contained in:
commit
2f7a02022a
243 files changed
+3931
-2186
No files matched your search
@@ -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: c538fac50b0c75e43f761a3b58b385840b83e4ef
|
||||
feedback.zh.md: 4a77f9c0396f3635755c301a80ccaefe398d5ad6
|
||||
feedback.md: 2c6728d97a6a62735e4ef8b97983f08060d4a2c5
|
||||
feedback.zh.md: db9f03cb82685763bc3b424c0f6b29738f59ec99
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
English | [中文](feedback.zh.md)
|
||||
|
||||
[`@deepseek-ai/dsh-message-feedback`](../../packages/feedback/message-feedback) owns editable feedback for individual assistant messages. The canonical Session log stores `feedback/message-put` and `feedback/message-delete`; the immutable Session-level remark remains `feedback/record`. All three are log-only events that never enter model context.
|
||||
[`@deepseek-ai/dsh-message-feedback`](../../packages/feedback/message-feedback) owns editable feedback for individual assistant messages. The canonical Session log stores `feedback/message-put` and `feedback/message-delete`; the immutable Session-level remark remains `feedback/record`, owned by [`@deepseek-ai/dsh-command-feedback`](../../packages/feedback/command-feedback) together with the `FeedbackCategory` taxonomy both kinds of feedback file under. All three are log-only events that never enter model context.
|
||||
|
||||
Source: [`packages/feedback/message-feedback/src/types.ts`](../../packages/feedback/message-feedback/src/types.ts)
|
||||
|
||||
@@ -27,6 +27,8 @@ interface MessageFeedbackItem {
|
||||
readonly rating: MessageFeedbackRating
|
||||
/** Optional explanation, preserved verbatim after validation. */
|
||||
readonly note?: string
|
||||
/** Category the human filed a negative judgment under. */
|
||||
readonly category?: FeedbackCategory
|
||||
/** Equality-only token replaced by every material create or update. */
|
||||
readonly version: MessageFeedbackVersion
|
||||
/** Host-assigned creation time in Unix epoch milliseconds. */
|
||||
@@ -83,6 +85,8 @@ interface MessageFeedbackPutRequest {
|
||||
readonly rating: MessageFeedbackRating
|
||||
/** Optional non-blank explanation. */
|
||||
readonly note?: string
|
||||
/** Optional category; absent keeps the item uncategorized. */
|
||||
readonly category?: FeedbackCategory
|
||||
/** Observed item version, or `null` to require that no item exists. */
|
||||
readonly ifVersion: MessageFeedbackVersion | null
|
||||
}
|
||||
@@ -203,11 +207,76 @@ type MessageFeedbackDeleteResult =
|
||||
| MessageFeedbackRejected<MessageFeedbackSessionNotFound | MessageFeedbackVersionConflict>
|
||||
```
|
||||
|
||||
## Session feedback types
|
||||
|
||||
Source: [`packages/feedback/command-feedback/src/types.ts`](../../packages/feedback/command-feedback/src/types.ts)
|
||||
|
||||
```ts type-equiv
|
||||
/** One of the fixed feedback categories; the ids are durable log vocabulary. */
|
||||
type FeedbackCategory =
|
||||
| 'task-result'
|
||||
| 'instruction-following'
|
||||
| 'product-interaction'
|
||||
| 'service-stability'
|
||||
| 'resource-cost'
|
||||
| 'security-privacy-permission'
|
||||
| 'other'
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* One recorded human remark about a Session. Both members are optional: a
|
||||
* submission with neither still records that the human asked for the
|
||||
* Session to be reviewed, which is what authorizes log delivery.
|
||||
*/
|
||||
interface FeedbackRecord {
|
||||
/** Free-text remark with surrounding whitespace removed; never empty when present. */
|
||||
readonly text?: string
|
||||
/** Category the human filed the remark under. */
|
||||
readonly category?: FeedbackCategory
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Record one Session-level remark through the Host Remote. */
|
||||
interface SessionFeedbackRecordRequest {
|
||||
/** Live Session the remark describes. */
|
||||
readonly sessionId: SessionId
|
||||
/** Free-text remark; blank text is recorded as absent. */
|
||||
readonly text?: string
|
||||
/** Category the human filed the remark under. */
|
||||
readonly category?: FeedbackCategory
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Stable postcondition of a recorded remark. */
|
||||
interface SessionFeedbackRecordValue {
|
||||
/** The remark is appended to the Session log; flushing follows the Session's own schedule. */
|
||||
readonly recorded: true
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** No live Session carries the requested id. */
|
||||
interface SessionFeedbackSessionNotFound {
|
||||
readonly code: 'session-not-found'
|
||||
readonly sessionId: SessionId
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Result returned by the `sessionFeedback.record` operation. */
|
||||
type SessionFeedbackRecordResult =
|
||||
| { readonly ok: true; readonly value: SessionFeedbackRecordValue }
|
||||
| { readonly ok: false; readonly error: SessionFeedbackSessionNotFound }
|
||||
```
|
||||
|
||||
## Data and concurrency
|
||||
|
||||
Current items are folded from canonical feedback events whose payload `sessionId` matches the owning Session. Each item carries a positive or negative rating, an optional note, Host-assigned `createdAt`/`updatedAt` timestamps, and its own opaque version. Versions are compared only for equality and only against the addressed message; callers do not order or synthesize them.
|
||||
Current items are folded from canonical feedback events whose payload `sessionId` matches the owning Session. Each item carries a positive or negative rating, an optional note, an optional category, Host-assigned `createdAt`/`updatedAt` timestamps, and its own opaque version. Versions are compared only for equality and only against the addressed message; callers do not order or synthesize them.
|
||||
|
||||
`put` uses strict optimistic concurrency: every request for an existing item must match its current `ifVersion`, including a no-op. A conflict returns the authoritative current item (or `null`), so a caller can reconcile a lost response or a concurrent edit without another read. Deleting an already absent item succeeds. A per-Session queue serializes reads and mutations; cold mutations hold a persistence write handle across read, comparison, append, and flush. Matching no-ops append no event.
|
||||
`put` uses strict optimistic concurrency: every request for an existing item must match its current `ifVersion`, including a no-op (a put repeating the stored rating, note, and category). A conflict returns the authoritative current item (or `null`), so a caller can reconcile a lost response or a concurrent edit without another read. Deleting an already absent item succeeds. A per-Session queue serializes reads and mutations; cold mutations hold a persistence write handle across read, comparison, append, and flush. Matching no-ops append no event.
|
||||
|
||||
## Target and lifecycle authority
|
||||
|
||||
@@ -217,7 +286,7 @@ Fork seeds can contain parent feedback events, but their payload retains the par
|
||||
|
||||
## Persistence and Remote contract
|
||||
|
||||
Successful message-feedback mutations await canonical persistence: live operations append through the owning Session and require a participating `ctx.sessions.flush` listener; cold operations append and flush through their write handle. Persistence failures propagate rather than reporting success. `maxNoteBytes` is required and bounds note text by UTF-8 bytes; the Web Host composition sets `8192`. The package publishes the Host `messageFeedback.list`, `messageFeedback.put`, and `messageFeedback.delete` unary Remote contract through `TypertRemoteService` and `@Remote`; the generated Cordis API below is the method-level authority.
|
||||
Successful message-feedback mutations await canonical persistence: live operations append through the owning Session and require a participating `ctx.sessions.flush` listener; cold operations append and flush through their write handle. Persistence failures propagate rather than reporting success. `maxNoteBytes` is required and bounds note text by UTF-8 bytes; the Web Host composition sets `8192`. The package publishes the Host `messageFeedback.list`, `messageFeedback.put`, and `messageFeedback.delete` unary Remote contract through `TypertRemoteService` and `@Remote`; `command-feedback` publishes `sessionFeedback.record` the same way for Session-level remarks on live Sessions. The generated Cordis API below is the method-level authority.
|
||||
|
||||
Plugin disposal closes operation admission and drains accepted per-Session queue work.
|
||||
|
||||
@@ -225,12 +294,14 @@ When explicitly enabled, [`session-log-deepseek`](../../packages/session/session
|
||||
|
||||
## Web surface
|
||||
|
||||
[`@deepseek-ai/dsh-client-ui-message-feedback`](../../packages/client/ui-message-feedback) is the browser consumer. `@deepseek-ai/dsh-api-remotes` mounts the generated `messageFeedback` contribution, so the plugin calls `ctx.remote.messageFeedback` and never touches the transport.
|
||||
[`@deepseek-ai/dsh-client-ui-message-feedback`](../../packages/client/ui-message-feedback) is the browser consumer. `@deepseek-ai/dsh-api-remotes` mounts the generated `messageFeedback` and `sessionFeedback` contributions, so the plugin calls `ctx.remote.messageFeedback` and `ctx.remote.sessionFeedback` and never touches the transport.
|
||||
|
||||
The controls are the `feedback` entry (order 10) of the `conversation.chat.assistant-actions` list slot, which `ui-conversation` declares and renders inside the finalized assistant message's IconActions row. `AssistantMessageNode` carries the optional `messageId` from the `assistant/message` event. The field is absent on interruption-frozen partials, and the render site skips the slot when it is absent. The strip renders once per turn, on the closing assistant message: the Host accepts every append-origin step message as a target, but earlier steps of a multi-step turn render tool rows rather than a rateable body, so the UI exposes a narrower set than the Host contract allows.
|
||||
|
||||
One `MessageFeedbackController` per Session backs every message control in that Session: a single `list` read seeds the whole transcript, deferred to first hover or focus rather than fired on mount. Each mutation sends the version that controller last observed as `ifVersion`; a `version-conflict` reply carries the authoritative item, so the controller reconciles from the reply instead of refetching. Mutations serialize per Session so a queued operation compares against the committed version. A `connection/reset` refreshes only Sessions already read.
|
||||
|
||||
Like records the bare positive judgment at once and shows the acknowledgement toast. Dislike opens the Session's feedback dialog, the `feedback-dialog` entry of `conversation.input.overlay`: the shared Modal card with seven category chips and a detail box. Submit puts a negative judgment carrying the chosen category and the trimmed description, or neither. The same dialog opens for the Session from a bare `/feedback` — a decoration `ui-commands` routes as an `action` — and then records through `sessionFeedback.record`; `/feedback <text>` keeps the Host command path. Clicking a recorded rating retracts it.
|
||||
|
||||
## Boundaries and limitations
|
||||
|
||||
- The operation queue is process-local; cold writer exclusion relies on the selected persistence provider.
|
||||
@@ -240,7 +311,8 @@ One `MessageFeedbackController` per Session backs every message control in that
|
||||
- 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 note editor does not pre-check `maxNoteBytes`; an oversized note fails on save 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 -->
|
||||
|
||||
@@ -282,6 +354,24 @@ Session-log service; cold operations never construct a Session or Agent.
|
||||
|
||||
Source: [`packages/feedback/message-feedback/src/index.ts`](../../packages/feedback/message-feedback/src/index.ts)
|
||||
|
||||
<a id="ctxsessionfeedback--sessionfeedbackservice"></a>
|
||||
|
||||
### `ctx.sessionFeedback` — `SessionFeedbackService`
|
||||
|
||||
Host Remote through which a product surface records a Session-level remark.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Record one remark on a live Session.
|
||||
* @param request - target Session plus the optional text and category.
|
||||
* @returns the recorded postcondition, or `session-not-found` when no live
|
||||
* Session carries the id.
|
||||
*/
|
||||
@Remote('record') record(request: SessionFeedbackRecordRequest): Promise<SessionFeedbackRecordResult>
|
||||
```
|
||||
|
||||
Source: [`packages/feedback/command-feedback/src/index.ts`](../../packages/feedback/command-feedback/src/index.ts)
|
||||
|
||||
<a id="feedback-events"></a>
|
||||
|
||||
### `feedback/*` events
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](feedback.md) | 中文
|
||||
|
||||
[`@deepseek-ai/dsh-message-feedback`](../../packages/feedback/message-feedback)拥有针对单条 assistant 消息的可编辑反馈。权威 Session 日志保存 `feedback/message-put` 和 `feedback/message-delete`;不可变的 Session 级备注仍使用 `feedback/record`。三者都是仅写日志的事件,绝不进入模型上下文。
|
||||
[`@deepseek-ai/dsh-message-feedback`](../../packages/feedback/message-feedback)拥有针对单条 assistant 消息的可编辑反馈。权威 Session 日志保存 `feedback/message-put` 和 `feedback/message-delete`;不可变的 Session 级备注仍使用 `feedback/record`,由 [`@deepseek-ai/dsh-command-feedback`](../../packages/feedback/command-feedback) 连同两种反馈共用的 `FeedbackCategory` 分类表一起拥有。三者都是仅写日志的事件,绝不进入模型上下文。
|
||||
|
||||
来源:[`packages/feedback/message-feedback/src/types.ts`](../../packages/feedback/message-feedback/src/types.ts)
|
||||
|
||||
@@ -27,6 +27,8 @@ interface MessageFeedbackItem {
|
||||
readonly rating: MessageFeedbackRating
|
||||
/** Optional explanation, preserved verbatim after validation. */
|
||||
readonly note?: string
|
||||
/** Category the human filed a negative judgment under. */
|
||||
readonly category?: FeedbackCategory
|
||||
/** Equality-only token replaced by every material create or update. */
|
||||
readonly version: MessageFeedbackVersion
|
||||
/** Host-assigned creation time in Unix epoch milliseconds. */
|
||||
@@ -83,6 +85,8 @@ interface MessageFeedbackPutRequest {
|
||||
readonly rating: MessageFeedbackRating
|
||||
/** Optional non-blank explanation. */
|
||||
readonly note?: string
|
||||
/** Optional category; absent keeps the item uncategorized. */
|
||||
readonly category?: FeedbackCategory
|
||||
/** Observed item version, or `null` to require that no item exists. */
|
||||
readonly ifVersion: MessageFeedbackVersion | null
|
||||
}
|
||||
@@ -203,11 +207,76 @@ type MessageFeedbackDeleteResult =
|
||||
| MessageFeedbackRejected<MessageFeedbackSessionNotFound | MessageFeedbackVersionConflict>
|
||||
```
|
||||
|
||||
## Session 反馈类型
|
||||
|
||||
来源:[`packages/feedback/command-feedback/src/types.ts`](../../packages/feedback/command-feedback/src/types.ts)
|
||||
|
||||
```ts type-equiv
|
||||
/** One of the fixed feedback categories; the ids are durable log vocabulary. */
|
||||
type FeedbackCategory =
|
||||
| 'task-result'
|
||||
| 'instruction-following'
|
||||
| 'product-interaction'
|
||||
| 'service-stability'
|
||||
| 'resource-cost'
|
||||
| 'security-privacy-permission'
|
||||
| 'other'
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* One recorded human remark about a Session. Both members are optional: a
|
||||
* submission with neither still records that the human asked for the
|
||||
* Session to be reviewed, which is what authorizes log delivery.
|
||||
*/
|
||||
interface FeedbackRecord {
|
||||
/** Free-text remark with surrounding whitespace removed; never empty when present. */
|
||||
readonly text?: string
|
||||
/** Category the human filed the remark under. */
|
||||
readonly category?: FeedbackCategory
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Record one Session-level remark through the Host Remote. */
|
||||
interface SessionFeedbackRecordRequest {
|
||||
/** Live Session the remark describes. */
|
||||
readonly sessionId: SessionId
|
||||
/** Free-text remark; blank text is recorded as absent. */
|
||||
readonly text?: string
|
||||
/** Category the human filed the remark under. */
|
||||
readonly category?: FeedbackCategory
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Stable postcondition of a recorded remark. */
|
||||
interface SessionFeedbackRecordValue {
|
||||
/** The remark is appended to the Session log; flushing follows the Session's own schedule. */
|
||||
readonly recorded: true
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** No live Session carries the requested id. */
|
||||
interface SessionFeedbackSessionNotFound {
|
||||
readonly code: 'session-not-found'
|
||||
readonly sessionId: SessionId
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Result returned by the `sessionFeedback.record` operation. */
|
||||
type SessionFeedbackRecordResult =
|
||||
| { readonly ok: true; readonly value: SessionFeedbackRecordValue }
|
||||
| { readonly ok: false; readonly error: SessionFeedbackSessionNotFound }
|
||||
```
|
||||
|
||||
## 数据与并发
|
||||
|
||||
当前条目由 payload 中 `sessionId` 与所属 Session 匹配的权威反馈事件归约得到。每个条目携带好评或差评、可选备注、Host 分配的 `createdAt`/`updatedAt` 时间戳及自己的 opaque version。version 只能用于相等比较,且只与目标消息比较;调用方不能排序或自行合成它。
|
||||
当前条目由 payload 中 `sessionId` 与所属 Session 匹配的权威反馈事件归约得到。每个条目携带好评或差评、可选备注、可选分类、Host 分配的 `createdAt`/`updatedAt` 时间戳及自己的 opaque version。version 只能用于相等比较,且只与目标消息比较;调用方不能排序或自行合成它。
|
||||
|
||||
`put` 采用严格乐观并发:已有条目的每次请求都必须匹配当前 `ifVersion`,即使请求不会改变目标值。冲突会返回权威当前条目(不存在时为 `null`),因此调用方无需额外读取,即可协调丢失响应或并发编辑。删除已经不存在的条目同样成功。按 Session 划分的队列串行执行读取与变更;cold 变更在读取、比较、追加和 flush 期间持有持久化写句柄。匹配版本的无变更操作不追加事件。
|
||||
`put` 采用严格乐观并发:已有条目的每次请求都必须匹配当前 `ifVersion`,即使请求不会改变目标值(重复已存评分、备注与分类的 put)。冲突会返回权威当前条目(不存在时为 `null`),因此调用方无需额外读取,即可协调丢失响应或并发编辑。删除已经不存在的条目同样成功。按 Session 划分的队列串行执行读取与变更;cold 变更在读取、比较、追加和 flush 期间持有持久化写句柄。匹配版本的无变更操作不追加事件。
|
||||
|
||||
## 目标与生命周期权威
|
||||
|
||||
@@ -217,7 +286,7 @@ fork 种子可以包含父 Session 的反馈事件,但 payload 保留父级 `s
|
||||
|
||||
## 持久化与 Remote 约定
|
||||
|
||||
成功的消息反馈变更会等待权威持久化完成:live 操作通过所属 Session 追加,并要求有 `ctx.sessions.flush` 监听器参与;cold 操作通过写句柄追加并 flush。持久化故障会原样传播,不会报告成功。`maxNoteBytes` 为必填项,按 UTF-8 字节限制备注文本;Web Host 组合将其设为 `8192`。该包通过 `TypertRemoteService` 与 `@Remote` 发布 Host `messageFeedback.list`、`messageFeedback.put` 和 `messageFeedback.delete` 一元 Remote 约定;下方生成的 Cordis API 是方法级权威。
|
||||
成功的消息反馈变更会等待权威持久化完成:live 操作通过所属 Session 追加,并要求有 `ctx.sessions.flush` 监听器参与;cold 操作通过写句柄追加并 flush。持久化故障会原样传播,不会报告成功。`maxNoteBytes` 为必填项,按 UTF-8 字节限制备注文本;Web Host 组合将其设为 `8192`。该包通过 `TypertRemoteService` 与 `@Remote` 发布 Host `messageFeedback.list`、`messageFeedback.put` 和 `messageFeedback.delete` 一元 Remote 约定;`command-feedback` 以同样方式发布面向 live Session 的 Session 级备注 `sessionFeedback.record`。下方生成的 Cordis API 是方法级权威。
|
||||
|
||||
插件释放会关闭操作接纳,并排空已进入各 Session 队列的工作。
|
||||
|
||||
@@ -225,12 +294,14 @@ fork 种子可以包含父 Session 的反馈事件,但 payload 保留父级 `s
|
||||
|
||||
## Web 界面
|
||||
|
||||
[`@deepseek-ai/dsh-client-ui-message-feedback`](../../packages/client/ui-message-feedback) 是浏览器侧消费方。`@deepseek-ai/dsh-api-remotes` 挂载生成的 `messageFeedback` 贡献,因此该插件调用 `ctx.remote.messageFeedback`,不接触传输层。
|
||||
[`@deepseek-ai/dsh-client-ui-message-feedback`](../../packages/client/ui-message-feedback) 是浏览器侧消费方。`@deepseek-ai/dsh-api-remotes` 挂载生成的 `messageFeedback` 与 `sessionFeedback` 贡献,因此该插件调用 `ctx.remote.messageFeedback` 与 `ctx.remote.sessionFeedback`,不接触传输层。
|
||||
|
||||
控件是 `conversation.chat.assistant-actions` list slot 的 `feedback` 条目(order 10),该 slot 由 `ui-conversation` 声明,并渲染在已定稿助手消息的 IconActions 行内。`AssistantMessageNode` 携带来自 `assistant/message` 事件的可选 `messageId`。被中断冻结的部分输出没有该字段,渲染点在字段缺失时跳过该 slot。该操作栏每个 Turn 渲染一次,位于收尾的助手消息上:Host 接受每条 append-origin 步骤消息作为目标,但多步骤 Turn 中较早的步骤渲染的是工具行而非可评分正文,因此 UI 暴露的范围比 Host 约定允许的更窄。
|
||||
|
||||
每个 Session 一个 `MessageFeedbackController`,支撑该 Session 内所有消息的控件:一次 `list` 读取即填充整段对话,且延迟到首次 hover 或 focus 才发起,而非挂载时触发。每次变更把该 controller 最后观察到的版本作为 `ifVersion` 发送;`version-conflict` 响应携带权威条目,controller 据此对账而不重新拉取。变更按 Session 串行,排队操作与已提交版本比较。`connection/reset` 只刷新已读取过的 Session。
|
||||
|
||||
点赞立即记录不带备注的好评并显示确认 toast。点踩打开该 Session 的反馈弹窗,即 `conversation.input.overlay` 的 `feedback-dialog` 条目:共用的 Modal 卡片,里面是七个分类标签和一个详情框。提交会 put 一条差评,带上所选分类与去除首尾空白的描述,两者也可都不带。不带文本的 `/feedback`(`ui-commands` 以 `action` 路由的一个装饰)为 Session 打开同一个弹窗,随后通过 `sessionFeedback.record` 记录;`/feedback <text>` 仍走宿主命令路径。再次点击已记录的评分会撤回它。
|
||||
|
||||
## 边界与限制
|
||||
|
||||
- 操作队列仅在进程内生效;cold 写入排他性依赖所选持久化提供方。
|
||||
@@ -240,7 +311,8 @@ 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 -->
|
||||
|
||||
@@ -282,6 +354,24 @@ Session-log service; cold operations never construct a Session or Agent.
|
||||
|
||||
Source: [`packages/feedback/message-feedback/src/index.ts`](../../packages/feedback/message-feedback/src/index.ts)
|
||||
|
||||
<a id="ctxsessionfeedback--sessionfeedbackservice"></a>
|
||||
|
||||
### `ctx.sessionFeedback` — `SessionFeedbackService`
|
||||
|
||||
Host Remote through which a product surface records a Session-level remark.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
* Record one remark on a live Session.
|
||||
* @param request - target Session plus the optional text and category.
|
||||
* @returns the recorded postcondition, or `session-not-found` when no live
|
||||
* Session carries the id.
|
||||
*/
|
||||
@Remote('record') record(request: SessionFeedbackRecordRequest): Promise<SessionFeedbackRecordResult>
|
||||
```
|
||||
|
||||
Source: [`packages/feedback/command-feedback/src/index.ts`](../../packages/feedback/command-feedback/src/index.ts)
|
||||
|
||||
<a id="feedback-events"></a>
|
||||
|
||||
### `feedback/*` events
|
||||
|
||||
@@ -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/persistence.md
|
||||
persistence.md: 8ba4e74768050646df4904d4ae1a978781685f35
|
||||
persistence.zh.md: 1821b07df7686a7aa77cfb5c837f903e199ccf94
|
||||
persistence.md: b75ed288e1d034dc361457c8124a9974ecadad92
|
||||
persistence.zh.md: 1e5fa4269c520f2b196b245e4f2af4e8601da8eb
|
||||
@@ -186,7 +186,7 @@ interface SessionHeader {
|
||||
|
||||
## Format refusal — logs a build cannot faithfully read
|
||||
|
||||
A backend refuses a log it cannot faithfully interpret with `SessionFormatUnsupportedError`, distinct from `SessionPersistenceCorruptionError` because nothing is damaged. `stat` and `list` classify the highest canonical generation and translate a supported historical header without reading or mutating its body. Historical `open` calls share one per-session migration preparation before returning current logical values and leave every source path, byte, and inode unchanged. The JSONL provider returns a read handle from that in-memory result without publishing; a write open holds its single-writer claim and file lease while it reuses the preparation, exclusively publishes the final current generation, and only then returns the writable handle. A future highest generation refuses even when an older readable generation remains. Current v2 restoration retains installed extensions and unknown events carrying `ignorable: true`; historical v0/v1 migration refuses an unknown type even when marked ignorable. The message appends the selected raw log path when the backend keeps one artifact per session. An out-of-tree backend must enforce equivalent current-only handle values and direction-aware refusals at its physical-format entry. The [released-format migration decision](../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md) owns the chain and immutable-publication rules.
|
||||
A backend refuses a log it cannot faithfully interpret with `SessionFormatUnsupportedError`, distinct from `SessionPersistenceCorruptionError` because nothing is damaged. `stat` and `list` classify the highest canonical generation and translate a supported historical header without reading or mutating its body. Historical `open` calls share one per-session migration preparation before returning current logical values and leave every source path, byte, and inode unchanged. The JSONL provider returns a read handle from that in-memory result without publishing; a write open holds its single-writer claim and file lease while it reuses the preparation, exclusively publishes the final current generation, and only then returns the writable handle. A future highest generation refuses even when an older readable generation remains. Current-format restoration retains installed extensions and unknown events carrying `ignorable: true`; historical v0/v1/v2 migration refuses an unknown type even when marked ignorable. The message appends the selected raw log path when the backend keeps one artifact per session. An out-of-tree backend must enforce equivalent current-only handle values and direction-aware refusals at its physical-format entry. The [released-format migration decision](../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.md) owns the chain and immutable-publication rules.
|
||||
|
||||
## `CreateSessionOptions` — seeding and metadata
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ interface SessionHeader {
|
||||
|
||||
## 格式拒绝:本构建无法可靠读取的日志
|
||||
|
||||
后端用 `SessionFormatUnsupportedError` 拒绝无法可靠解读的日志,它与 `SessionPersistenceCorruptionError` 区分,因为数据没有损坏。`stat` 与 `list` 会对最高规范 generation 分类,并在不读取或改变正文的前提下转换受支持的历史 header。历史 `open` 会共享每个 Session 唯一的一次 migration preparation,再返回当前逻辑值,并保持每个源路径、字节与 inode 不变。JSONL provider 直接从该内存结果返回读句柄而不发布;写 open 则在持有单写者 claim 与文件 lease 时复用 preparation、排他发布最终 current generation,随后才返回可写句柄。即使仍有较旧的可读 generation,最高的未来 generation 仍会导致拒绝。当前 v2 恢复会保留已安装扩展和带 `ignorable: true` 的未知事件;历史 v0/v1 迁移则会拒绝未知类型,即使它带有 ignorable 标记。后端为每个会话保留独立文件时,消息附上选定的原始日志路径。仓库外后端必须在自己的物理格式入口提供等价的仅当前句柄值与方向感知拒绝。[已发布格式迁移决策](../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md)负责迁移链与不可变发布规则。
|
||||
后端用 `SessionFormatUnsupportedError` 拒绝无法可靠解读的日志,它与 `SessionPersistenceCorruptionError` 区分,因为数据没有损坏。`stat` 与 `list` 会对最高规范 generation 分类,并在不读取或改变正文的前提下转换受支持的历史 header。历史 `open` 会共享每个 Session 唯一的一次 migration preparation,再返回当前逻辑值,并保持每个源路径、字节与 inode 不变。JSONL provider 直接从该内存结果返回读句柄而不发布;写 open 则在持有单写者 claim 与文件 lease 时复用 preparation、排他发布最终 current generation,随后才返回可写句柄。即使仍有较旧的可读 generation,最高的未来 generation 仍会导致拒绝。当前格式恢复会保留已安装扩展和带 `ignorable: true` 的未知事件;历史 v0/v1/v2 迁移则会拒绝未知类型,即使它带有 ignorable 标记。后端为每个会话保留独立文件时,消息附上选定的原始日志路径。仓库外后端必须在自己的物理格式入口提供等价的仅当前句柄值与方向感知拒绝。[已发布格式迁移决策](../../.agents/notes/implemented/architecture/2026-08-31-released-session-format-migrations.zh.md)负责迁移链与不可变发布规则。
|
||||
|
||||
## `CreateSessionOptions`:seed 与元数据
|
||||
|
||||
|
||||
@@ -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/session.md
|
||||
session.md: de0930ea7effcba69bc1f9a4dd405ceda23919d7
|
||||
session.zh.md: ec15dbdec3a8887d8fa87c9698b8ad14699a534b
|
||||
session.md: 50dbbf6474f1b1e756dedd508bb1d8b0ef349832
|
||||
session.zh.md: 9a1b1399e701a1fa2bda2ad59b59c9fbe484ef4a
|
||||
@@ -672,7 +672,7 @@ The hook bridges' `hook/invoked` / `hook/result` pairs (from `@deepseek-ai/dsh-h
|
||||
|
||||
## Durability contract
|
||||
|
||||
What a persistence backend relies on: the durable log persists every event losslessly, and every Assistant attempt is one `assistant/message` or `assistant/attempt` whose embedded compact stream preserves the original timed chunks. `seq` stays contiguous across these settlements and all interleaved events. A backend may choose its own storage framing for an event batch as long as a handle's `read()` returns the exact appended events; current JSONL v2 writes one row per event (see [persistence.md](persistence.md)). All `event.data` must be JSON-serializable; `Session.append` enforces this at the source (throwing on non-serializable data), so a bad event never enters the log and `session.snapshotEvents()` always equals what a backend can persist. Adding an event type that carries non-serializable data, corrupts core execution nesting, or violates its owner's declared relation is a breaking change to the on-disk format.
|
||||
What a persistence backend relies on: the durable log persists every event losslessly, and every Assistant attempt is one `assistant/message` or `assistant/attempt` whose embedded compact stream preserves the original timed chunks. `seq` stays contiguous across these settlements and all interleaved events. A backend may choose its own storage framing for an event batch as long as a handle's `read()` returns the exact appended events; current JSONL writes one row per event (see [persistence.md](persistence.md)). All `event.data` must be JSON-serializable; `Session.append` enforces this at the source (throwing on non-serializable data), so a bad event never enters the log and `session.snapshotEvents()` always equals what a backend can persist. Adding an event type that carries non-serializable data, corrupts core execution nesting, or violates its owner's declared relation is a breaking change to the on-disk format.
|
||||
|
||||
The backends that consume this contract are on [persistence.md](persistence.md).
|
||||
|
||||
|
||||
@@ -676,7 +676,7 @@ interface TurnEndReasonMap {
|
||||
|
||||
## 持久性约定
|
||||
|
||||
持久化后端依赖的约定如下:持久日志无损保存每个事件,每个 Assistant attempt 都是一个 `assistant/message` 或 `assistant/attempt`,其嵌入式紧凑 stream 会保留原始带时间 chunk。`seq` 在这些 settlement 与所有交错事件之间保持连续。后端可以为事件批次选择自己的存储 framing,只要句柄的 `read()` 返回与追加时完全一致的事件即可;当前 JSONL v2 每个事件写一行(见 [persistence.md](persistence.zh.md))。所有 `event.data` 都必须可序列化为 JSON;`Session.append` 会从源头强制这一要求(遇到不可序列化数据时抛出),因此错误事件绝不会进入日志,`session.snapshotEvents()` 始终与后端可持久化的内容一致。新增会携带不可序列化数据、破坏核心执行嵌套或违反事件所有方声明关系的事件类型,都会构成磁盘格式的破坏性变更。
|
||||
持久化后端依赖的约定如下:持久日志无损保存每个事件,每个 Assistant attempt 都是一个 `assistant/message` 或 `assistant/attempt`,其嵌入式紧凑 stream 会保留原始带时间 chunk。`seq` 在这些 settlement 与所有交错事件之间保持连续。后端可以为事件批次选择自己的存储 framing,只要句柄的 `read()` 返回与追加时完全一致的事件即可;当前 JSONL 每个事件写一行(见 [persistence.md](persistence.zh.md))。所有 `event.data` 都必须可序列化为 JSON;`Session.append` 会从源头强制这一要求(遇到不可序列化数据时抛出),因此错误事件绝不会进入日志,`session.snapshotEvents()` 始终与后端可持久化的内容一致。新增会携带不可序列化数据、破坏核心执行嵌套或违反事件所有方声明关系的事件类型,都会构成磁盘格式的破坏性变更。
|
||||
|
||||
消费此约定的后端见 [persistence.md](persistence.zh.md)。
|
||||
|
||||
|
||||
Reference in new issue
Block a user