fix(web): address review findings on producer-declared context forms

- `catalogHistory` validated its durable read. `agent.session.events` is a
  JSONL/SQLite seed on resume or fork, and seed validation guarantees only a
  source object with a non-empty `kind`; a `skill-catalog` record with missing
  or wrongly shaped `entries` threw inside the step listener, failing every
  later turn of that session. It is now skipped as an unrecognizable record,
  the posture the replaced content digest had, with a regression test over six
  malformed shapes.
- The headless keyless smoke still filtered catalogs by the old plugin source,
  so the `built-bin-smoke` gate would not have found the catalog message.
- Entries record the published description unescaped. The pseudo-XML escaping
  belongs to the `<available_skills>` frame and is applied when rendering it,
  so a description containing `<` no longer reaches the card as `&lt;`.
  `escapeText` is injective, so republish semantics and the model-facing text
  are unchanged.
- Adjacent text blocks join with no separator, matching how provider adapters
  flatten them; the body no longer shows a line break the model never saw.
- Provenance fields are bounded like the text: an unknown producer may record
  an arbitrarily large value.
- Both readers are all-or-nothing, and the row's form marker reports what
  rendered rather than what was declared, so a partly unreadable record cannot
  present a confident but incomplete account.
- The catalog body consumes `update` as a replacement notice; the digest
  canonicalizes per entry as JSON, since every separator character is itself
  legal in a description.
- `core.md` documents the form axis with a `ContextForm` type-equiv block, and
  both projections assert the wiring they duplicate.
This commit is contained in:
creatixchu
2026-08-05 14:55:21 +08:00
parent e6293785bc
commit ebe6ca20ad
17 changed files with 408 additions and 79 deletions

View File

@@ -173,6 +173,32 @@ interface MessageSourceMap {
}
```
溯源与形态是相互独立的两根轴。`kind` 回答「由谁产生」;生产方可选混入的 `form` 回答「这是何种形态的信息」,因此多个生产方可以共用一种呈现,一个生产方在一次会话中也可以发出多种形态。该词汇表是语义的,逐个取值增长;未声明或无法识别的取值是有文档的默认,按不透明内容呈现:
```ts type-equiv
/**
* What SHAPE of information a producer-supplied context carries, declared by
* the producer beside its provenance.
*
* `MessageSource.kind` answers *who produced this*; `form` answers *what kind
* of thing it is*, and the two axes are deliberately independent — several
* producers share one form (three snapshot producers today), and one producer
* may emit more than one form over a session.
*
* The vocabulary is SEMANTIC, never visual: a value states that the content is
* a file's instructions or a catalog of available items, and a consumer decides
* what that looks like. Colors, icons, ordering, and collapse defaults are the
* consumer's business and must not enter this union. It grows one value at a
* time as producers gain the structured fields their form needs; an absent or
* unknown value is the documented default, presented as opaque content.
*/
type ContextForm =
/** Instructions read out of workspace files the model is expected to follow. */
| 'instructions'
/** A catalog of items available in this session, republished as it changes. */
| 'catalog'
```
## 流式输出
适配器发出原始**分片**协议;循环记录分片(回放保真度),同时将同一批分片送入 `BlockAssembler` 以重建块和消息。`StreamChunk` 是基于 `type` 的封闭判别联合——`block-start`、`text-delta`、`reasoning-delta`、`tool-call-delta`、`block-end`、`usage`、`finish`。