dsh-plugin-guide 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.es.md +5 -1
- package/README.hi.md +5 -1
- package/README.md +5 -1
- package/README.pt.md +5 -1
- package/README.zh.md +5 -1
- package/dist/dsh-plugin-dev.js +3 -3
- package/guide/plugin-dev-guide.md +2 -2
- package/guide/quick-reference.es.md +2 -2
- package/guide/quick-reference.hi.md +1 -1
- package/guide/quick-reference.md +2 -2
- package/guide/quick-reference.pt.md +2 -2
- package/guide/quick-reference.zh-CN.md +2 -2
- package/package.json +3 -3
- package/references/official-docs/AGENTS.md +3 -3
- package/references/official-docs/SNAPSHOT.md +4 -4
- package/references/official-docs/THIRD_PARTY_NOTICES.md +4 -6
- package/references/official-docs/docs/agent-lifecycle.i18n.yaml +2 -2
- package/references/official-docs/docs/agent-lifecycle.md +5 -3
- package/references/official-docs/docs/agent-lifecycle.zh.md +5 -3
- package/references/official-docs/docs/architecture.i18n.yaml +2 -2
- package/references/official-docs/docs/architecture.md +9 -4
- package/references/official-docs/docs/architecture.zh.md +9 -4
- package/references/official-docs/docs/capability-seams.i18n.yaml +2 -2
- package/references/official-docs/docs/capability-seams.md +8 -1
- package/references/official-docs/docs/capability-seams.zh.md +8 -1
- package/references/official-docs/docs/config-catalog.i18n.yaml +2 -2
- package/references/official-docs/docs/config-catalog.md +104 -68
- package/references/official-docs/docs/config-catalog.zh.md +101 -65
- package/references/official-docs/docs/cookbook/adding-a-package.i18n.yaml +2 -2
- package/references/official-docs/docs/cookbook/adding-a-package.md +1 -1
- package/references/official-docs/docs/cookbook/adding-a-package.zh.md +1 -1
- package/references/official-docs/docs/cookbook/extension-cookbook.i18n.yaml +2 -2
- package/references/official-docs/docs/cookbook/extension-cookbook.md +12 -10
- package/references/official-docs/docs/cookbook/extension-cookbook.zh.md +12 -10
- package/references/official-docs/docs/deepseek-llm-api-wire-extensions.i18n.yaml +2 -2
- package/references/official-docs/docs/deepseek-llm-api-wire-extensions.md +16 -12
- package/references/official-docs/docs/deepseek-llm-api-wire-extensions.zh.md +16 -12
- package/references/official-docs/docs/event-producer-consumer.i18n.yaml +2 -2
- package/references/official-docs/docs/event-producer-consumer.md +30 -29
- package/references/official-docs/docs/event-producer-consumer.zh.md +35 -34
- package/references/official-docs/docs/module-graph.i18n.yaml +2 -2
- package/references/official-docs/docs/module-graph.md +214 -360
- package/references/official-docs/docs/module-graph.zh.md +214 -360
- package/references/official-docs/docs/persistence-catalog.i18n.yaml +2 -2
- package/references/official-docs/docs/persistence-catalog.md +74 -64
- package/references/official-docs/docs/persistence-catalog.zh.md +72 -62
- package/references/official-docs/docs/subsystems/agent-team.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/agent-team.md +4 -3
- package/references/official-docs/docs/subsystems/agent-team.zh.md +4 -3
- package/references/official-docs/docs/subsystems/attachment.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/attachment.md +157 -4
- package/references/official-docs/docs/subsystems/attachment.zh.md +157 -4
- package/references/official-docs/docs/subsystems/code-runtime.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/code-runtime.md +7 -3
- package/references/official-docs/docs/subsystems/code-runtime.zh.md +7 -3
- package/references/official-docs/docs/subsystems/commands.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/commands.md +25 -16
- package/references/official-docs/docs/subsystems/commands.zh.md +25 -16
- package/references/official-docs/docs/subsystems/compaction.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/compaction.md +9 -9
- package/references/official-docs/docs/subsystems/compaction.zh.md +9 -9
- package/references/official-docs/docs/subsystems/conversation.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/conversation.md +4 -4
- package/references/official-docs/docs/subsystems/conversation.zh.md +4 -4
- package/references/official-docs/docs/subsystems/core.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/core.md +66 -4
- package/references/official-docs/docs/subsystems/core.zh.md +66 -4
- package/references/official-docs/docs/subsystems/feedback.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/feedback.md +2 -2
- package/references/official-docs/docs/subsystems/feedback.zh.md +2 -2
- package/references/official-docs/docs/subsystems/llm-streaming.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/llm-streaming.md +23 -2
- package/references/official-docs/docs/subsystems/llm-streaming.zh.md +23 -2
- package/references/official-docs/docs/subsystems/persistence.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/persistence.md +183 -191
- package/references/official-docs/docs/subsystems/persistence.zh.md +183 -191
- package/references/official-docs/docs/subsystems/schedule.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/schedule.md +1 -1
- package/references/official-docs/docs/subsystems/schedule.zh.md +1 -1
- package/references/official-docs/docs/subsystems/session-projection.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/session-projection.md +40 -17
- package/references/official-docs/docs/subsystems/session-projection.zh.md +40 -17
- package/references/official-docs/docs/subsystems/session-query.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/session-query.md +19 -13
- package/references/official-docs/docs/subsystems/session-query.zh.md +19 -13
- package/references/official-docs/docs/subsystems/session-reference.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/session-reference.md +25 -1
- package/references/official-docs/docs/subsystems/session-reference.zh.md +25 -1
- package/references/official-docs/docs/subsystems/session-telemetry.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/session-telemetry.md +6 -5
- package/references/official-docs/docs/subsystems/session-telemetry.zh.md +6 -5
- package/references/official-docs/docs/subsystems/session-title.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/session-title.md +5 -5
- package/references/official-docs/docs/subsystems/session-title.zh.md +5 -5
- package/references/official-docs/docs/subsystems/session.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/session.md +143 -71
- package/references/official-docs/docs/subsystems/session.zh.md +143 -71
- package/references/official-docs/docs/subsystems/slots.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/slots.md +1 -0
- package/references/official-docs/docs/subsystems/slots.zh.md +1 -0
- package/references/official-docs/docs/subsystems/storage.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/storage.md +28 -5
- package/references/official-docs/docs/subsystems/storage.zh.md +28 -5
- package/references/official-docs/docs/subsystems/subagent.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/subagent.md +32 -88
- package/references/official-docs/docs/subsystems/subagent.zh.md +31 -87
- package/references/official-docs/docs/subsystems/token-meter.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/token-meter.md +2 -2
- package/references/official-docs/docs/subsystems/token-meter.zh.md +2 -2
- package/references/official-docs/docs/subsystems/web-client.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/web-client.md +1 -1
- package/references/official-docs/docs/subsystems/web-client.zh.md +1 -1
- package/references/official-docs/docs/subsystems/workspace.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/workspace.md +8 -8
- package/references/official-docs/docs/subsystems/workspace.zh.md +8 -8
- package/references/official-docs/docs/testing.i18n.yaml +2 -2
- package/references/official-docs/docs/testing.md +2 -2
- package/references/official-docs/docs/testing.zh.md +2 -2
- package/references/official-docs/docs/tool-catalog.i18n.yaml +2 -2
- package/references/official-docs/docs/tool-catalog.md +9 -63
- package/references/official-docs/docs/tool-catalog.zh.md +8 -62
- package/references/official-docs/docs/user/guide/network-proxy.i18n.yaml +6 -0
- package/references/official-docs/docs/user/guide/network-proxy.md +85 -0
- package/references/official-docs/docs/user/guide/network-proxy.zh.md +85 -0
- package/references/official-docs/docs/user/guide/providers.i18n.yaml +2 -2
- package/references/official-docs/docs/user/guide/providers.md +70 -17
- package/references/official-docs/docs/user/guide/providers.zh.md +70 -17
- package/references/official-docs/docs/web-styling.i18n.yaml +2 -2
- package/references/official-docs/docs/web-styling.md +4 -0
- package/references/official-docs/docs/web-styling.zh.md +4 -0
- package/references/official-docs/packages/AGENTS.md +2 -2
- package/references/official-docs/website-docs.ts +8 -0
- package/templates/js/README.es.md +1 -1
- package/templates/js/README.hi.md +1 -1
- package/templates/js/README.md +1 -1
- package/templates/js/README.pt.md +1 -1
- package/templates/js/README.zh.md +1 -1
- package/templates/ts/README.es.md +1 -1
- package/templates/ts/README.hi.md +1 -1
- package/templates/ts/README.md +1 -1
- package/templates/ts/README.pt.md +1 -1
- package/templates/ts/README.zh.md +1 -1
|
@@ -21,8 +21,8 @@ interface UserMessage extends Message {
|
|
|
21
21
|
/**
|
|
22
22
|
* The merge-extensible, append-only source of truth for an agent interaction.
|
|
23
23
|
* Message history is derived from this log. Every event is lossless JSON and
|
|
24
|
-
* sequence numbers stay contiguous
|
|
25
|
-
*
|
|
24
|
+
* sequence numbers stay contiguous. Assistant attempt events embed their exact
|
|
25
|
+
* compact raw streams so persistence stores one durable settlement per attempt.
|
|
26
26
|
*/
|
|
27
27
|
interface SessionEventMap {
|
|
28
28
|
/**
|
|
@@ -53,8 +53,6 @@ interface SessionEventMap {
|
|
|
53
53
|
* project their `content` verbatim; `source` tells them apart.
|
|
54
54
|
*/
|
|
55
55
|
'user/message': UserMessage
|
|
56
|
-
/** Raw stream chunk — token-level replay fidelity. */
|
|
57
|
-
'assistant/chunk': { turn: number; step: number; chunk: StreamChunk }
|
|
58
56
|
/**
|
|
59
57
|
* Assembled assistant message for one step (derived history uses this).
|
|
60
58
|
* Carries the step's `usage` when the adapter reported token accounting, so
|
|
@@ -65,7 +63,21 @@ interface SessionEventMap {
|
|
|
65
63
|
* marker distinguishes that prefix without re-deriving interruption from turn
|
|
66
64
|
* boundaries. An aborted turn with no such event streamed no visible content.
|
|
67
65
|
*/
|
|
68
|
-
'assistant/message': {
|
|
66
|
+
'assistant/message': {
|
|
67
|
+
turn: number
|
|
68
|
+
step: number
|
|
69
|
+
message: AssistantMessage
|
|
70
|
+
/** Exact timed model stream, compacted without joining delta boundaries. */
|
|
71
|
+
stream: AssistantStreamRecord[]
|
|
72
|
+
usage?: TokenUsage
|
|
73
|
+
interrupted?: true
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* One model attempt that committed no surface message. The embedded stream
|
|
77
|
+
* preserves a failed, retried, cancelled, or stream-error attempt that
|
|
78
|
+
* reached settlement without fabricating model-visible history.
|
|
79
|
+
*/
|
|
80
|
+
'assistant/attempt': { turn: number; step: number; stream: AssistantStreamRecord[] }
|
|
69
81
|
/**
|
|
70
82
|
* The model requested one tool invocation: `name` with the raw `arguments`
|
|
71
83
|
* JSON string exactly as the model produced it (unparsed). `callId` pairs the
|
|
@@ -109,12 +121,12 @@ interface SessionEventMap {
|
|
|
109
121
|
* Marks the end of a constructor seed. Events before it have smaller seq
|
|
110
122
|
* values and came from the seed (resume, fork, or replay); this lifecycle
|
|
111
123
|
* produced none of them. This log-only event is the durable projection of
|
|
112
|
-
* {@link Session.firstLiveSeq}.
|
|
113
|
-
* carry the meaning.
|
|
124
|
+
* {@link Session.firstLiveSeq}.
|
|
114
125
|
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
126
|
+
* A fresh fork child owns one `{ inherited: true }` marker at its exact
|
|
127
|
+
* inherited-prefix cut, even when that prefix ends in an ancestor marker.
|
|
128
|
+
* The last tagged marker is the current Session's cut; untagged markers keep
|
|
129
|
+
* ordinary restore and replay lifecycle boundaries.
|
|
118
130
|
*
|
|
119
131
|
* `Session`'s constructor is the only legitimate writer. The invariant
|
|
120
132
|
* companion deliberately constrains nothing here, so a plugin appending one
|
|
@@ -127,7 +139,7 @@ interface SessionEventMap {
|
|
|
127
139
|
* writers — a concurrently live session holds its own boundary elsewhere,
|
|
128
140
|
* so tolerating concurrent writers needs a signal beyond the log.
|
|
129
141
|
*/
|
|
130
|
-
'session/end-seed':
|
|
142
|
+
'session/end-seed': { inherited?: true }
|
|
131
143
|
}
|
|
132
144
|
```
|
|
133
145
|
|
|
@@ -179,6 +191,28 @@ interface RequestContext {
|
|
|
179
191
|
|
|
180
192
|
基于 `type` 的真正可辨识联合(而非独立的 `type`/`data` 联合),因此 `switch (event.type)` 能直接收窄 `event.data`,无需类型断言。`seq` 是日志中的单调递增位置(`seq = log.length`);`time` 为 epoch 毫秒。
|
|
181
193
|
|
|
194
|
+
```ts type-equiv
|
|
195
|
+
/** Sequence number of one existing event in a Session log. */
|
|
196
|
+
type SessionSeq = BrandedNumber<'SessionSeq'>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
```ts type-equiv
|
|
200
|
+
/** A Session log gap, prefix length, or read offset, which may equal the event count. */
|
|
201
|
+
type SessionLogOffset = BrandedNumber<'SessionLogOffset'>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
```ts type-equiv
|
|
205
|
+
/** Inclusive Session event watermark, or `-1` before any event exists. */
|
|
206
|
+
type SessionSeqCursor = SessionSeq | -1
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```ts type-equiv
|
|
210
|
+
/** One existing Session event position, or explicit absence. */
|
|
211
|
+
type OptionalSessionSeq = SessionSeq | null
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`SessionSeq(value)` 与 `SessionLogOffset(value)` 只接纳非负安全整数,并拒绝负零。它们仅添加编译期品牌,不改变序列化后的数值;算术会返回普通 `number`,调用方必须按结果的预期角色通过对应构造器重新接纳。
|
|
215
|
+
|
|
182
216
|
```ts type-equiv
|
|
183
217
|
/**
|
|
184
218
|
* One immutable entry in the session log.
|
|
@@ -189,7 +223,7 @@ interface RequestContext {
|
|
|
189
223
|
* The {@link sourceEventSeqs} and {@link surfaceOp} fields are conditional:
|
|
190
224
|
* they only exist on {@link SurfaceEventType} variants (`user/message`,
|
|
191
225
|
* `assistant/message`, `tool/result`).
|
|
192
|
-
* Non-surface events (boundary markers,
|
|
226
|
+
* Non-surface events (boundary markers, attempts, errors) never carry
|
|
193
227
|
* surface metadata — the compiler enforces this at `Session.append()`
|
|
194
228
|
* call sites.
|
|
195
229
|
*/
|
|
@@ -197,7 +231,7 @@ type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
|
|
197
231
|
[K in SessionEventType]: {
|
|
198
232
|
type: K
|
|
199
233
|
/** Monotonic sequence number within the session. */
|
|
200
|
-
seq:
|
|
234
|
+
seq: SessionSeq
|
|
201
235
|
/** Unix epoch milliseconds. */
|
|
202
236
|
time: number
|
|
203
237
|
data: SessionEventMap[K]
|
|
@@ -214,14 +248,11 @@ type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
|
|
214
248
|
ignorable?: true
|
|
215
249
|
} & (K extends SurfaceEventType ? {
|
|
216
250
|
/**
|
|
217
|
-
* Seq numbers of earlier events that this event cites as sources
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* `assistant/message` may carry a present empty array for a known empty
|
|
221
|
-
* provider stream; when the field is absent, the event does not record which
|
|
222
|
-
* earlier events produced the message.
|
|
251
|
+
* Seq numbers of earlier events that this event cites as sources, such as
|
|
252
|
+
* the surface nodes shadowed by a compaction replacement. A v2
|
|
253
|
+
* `assistant/message` embeds its provider stream and cannot carry this field.
|
|
223
254
|
*/
|
|
224
|
-
sourceEventSeqs?:
|
|
255
|
+
sourceEventSeqs?: SessionSeq[]
|
|
225
256
|
/** How this event entered the surface; absent for non-surface events. */
|
|
226
257
|
surfaceOp?: SurfaceOp
|
|
227
258
|
} : object)
|
|
@@ -230,7 +261,7 @@ type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
|
|
230
261
|
|
|
231
262
|
`SessionEventType = keyof SessionEventMap`。由于 `SessionEventMap` 可通过合并扩展,对 `SessionEvent` 的 switch 语句禁止使用 `assertNever`:插件添加的变体是合法的未知值;处理已知 case 后在 `default` 中放行。
|
|
232
263
|
|
|
233
|
-
|
|
264
|
+
V2 `assistant/message` 嵌入 provider stream,不能携带 `sourceEventSeqs`。User 与 tool surface event 可以在 provenance 或 replacement operation 需要时引用完整且非空的唯一较早 event 集合。
|
|
234
265
|
|
|
235
266
|
<a id="surface-types"></a>
|
|
236
267
|
|
|
@@ -244,7 +275,8 @@ type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
|
|
244
275
|
/**
|
|
245
276
|
* The subset of {@link SessionEventType} values whose events produce LLM
|
|
246
277
|
* messages and are eligible to appear on the ordered surface. Only these
|
|
247
|
-
* event types may carry {@link SurfaceOp} and
|
|
278
|
+
* event types may carry {@link SurfaceOp}; user and tool events may also cite
|
|
279
|
+
* earlier sources through {@link SessionEvent.sourceEventSeqs}.
|
|
248
280
|
*/
|
|
249
281
|
type SurfaceEventType =
|
|
250
282
|
| 'user/message'
|
|
@@ -270,7 +302,7 @@ type SurfaceEventType =
|
|
|
270
302
|
*/
|
|
271
303
|
type SurfaceOp =
|
|
272
304
|
| 'append'
|
|
273
|
-
| { op: 'replace'; start:
|
|
305
|
+
| { op: 'replace'; start: SessionSeq; end: SessionSeq }
|
|
274
306
|
```
|
|
275
307
|
|
|
276
308
|
`'append'` 是常规的尾部追加路径。`replace` 会遮蔽从 `start` 到 `end`(含两端)的 surface 条目(两者都必须是有效的 surface seq;`start === end` 时仅替换单个条目),并在原位置插入新事件。
|
|
@@ -282,21 +314,20 @@ type SurfaceOp =
|
|
|
282
314
|
* Surface placement and cited source-event seqs for {@link Session.append}. Required on
|
|
283
315
|
* message-producing events and forbidden on log-only events.
|
|
284
316
|
*/
|
|
285
|
-
|
|
317
|
+
type SurfaceIntent<T extends SurfaceEventType = SurfaceEventType> = {
|
|
286
318
|
surfaceOp: SurfaceOp
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
319
|
+
} & (T extends 'assistant/message' ? {
|
|
320
|
+
/** V2 Assistant messages embed their provider stream instead of citing source events. */
|
|
321
|
+
sourceEventSeqs?: never
|
|
322
|
+
} : {
|
|
323
|
+
/** Complete non-empty set of known earlier source-event seqs. */
|
|
324
|
+
sourceEventSeqs?: SessionSeq[]
|
|
325
|
+
})
|
|
295
326
|
```
|
|
296
327
|
|
|
297
328
|
对 `SurfaceEventType` 事件必填:每个产生消息的事件都必须声明它如何加入 surface(派生模型历史的唯一来源)。面向人类的 transcript(文本记录)是另一个投影,读取的是日志中追加来源的事件,因为 surface 会有意遮蔽替换所概括的范围(见 [dsh-session](../../packages/core/session/README.zh.md) 的 `isAppendSurfaceEvent`)。非 surface 类型在编译期拒绝此参数。
|
|
298
329
|
|
|
299
|
-
|
|
330
|
+
`assistant/message` 不能携带 `sourceEventSeqs`;它的 `stream` 拥有精确 provider 证据。其他 surface event 不引用较早 event 时省略该字段,需要引用时使用完整非空 list。
|
|
300
331
|
|
|
301
332
|
### `SessionSurface`:实时只读 surface 投影
|
|
302
333
|
|
|
@@ -308,7 +339,7 @@ interface SurfaceIntent {
|
|
|
308
339
|
/** Readonly live projection of the message-producing session events. */
|
|
309
340
|
interface SessionSurface {
|
|
310
341
|
/** Current surface event sequences in model-visible order. */
|
|
311
|
-
readonly nodes: readonly
|
|
342
|
+
readonly nodes: readonly SessionSeq[]
|
|
312
343
|
/** Monotonic count of committed positional replacements. */
|
|
313
344
|
readonly replaceGeneration: number
|
|
314
345
|
}
|
|
@@ -322,13 +353,13 @@ interface SessionSurface {
|
|
|
322
353
|
/** One replacement operation observed while folding a session surface. */
|
|
323
354
|
interface SurfaceFoldReplacement {
|
|
324
355
|
/** Seq of the event that replaced the prior surface range. */
|
|
325
|
-
seq:
|
|
356
|
+
seq: SessionSeq
|
|
326
357
|
/** Declared inclusive start seq of the replaced surface range. */
|
|
327
|
-
start:
|
|
358
|
+
start: SessionSeq
|
|
328
359
|
/** Declared inclusive end seq of the replaced surface range. */
|
|
329
|
-
end:
|
|
360
|
+
end: SessionSeq
|
|
330
361
|
/** Actual surface entries removed by the operation, in surface order. */
|
|
331
|
-
shadowedSeqs:
|
|
362
|
+
shadowedSeqs: SessionSeq[]
|
|
332
363
|
}
|
|
333
364
|
```
|
|
334
365
|
|
|
@@ -336,7 +367,7 @@ interface SurfaceFoldReplacement {
|
|
|
336
367
|
/** Complete result of replaying the surface operations in a session log. */
|
|
337
368
|
interface SurfaceFoldResult {
|
|
338
369
|
/** Current surface event sequences in model-visible order. */
|
|
339
|
-
nodes:
|
|
370
|
+
nodes: SessionSeq[]
|
|
340
371
|
/** Replacement operations in event order. */
|
|
341
372
|
replacements: SurfaceFoldReplacement[]
|
|
342
373
|
}
|
|
@@ -360,24 +391,27 @@ declare class Session {
|
|
|
360
391
|
get surface(): SessionSurface;
|
|
361
392
|
/**
|
|
362
393
|
* Detached, deep-frozen creation metadata (format version, cwd, lineage,
|
|
363
|
-
*
|
|
394
|
+
* and whether fork history exists). Supplied by the store via `ctx.sessions.create()`. When a
|
|
364
395
|
* `Session` is created without a store-owned header, a minimal header is
|
|
365
396
|
* synthesized (stamped with the current {@link SESSION_FORMAT_VERSION}) so
|
|
366
397
|
* `session.header` is always present. Kept out of the event log — it is a
|
|
367
398
|
* storage concern, not replayable conversation state.
|
|
368
399
|
*/
|
|
369
400
|
readonly header: SessionHeader;
|
|
401
|
+
/** Number of leading events inherited from this Session's fork parent. */
|
|
402
|
+
readonly inheritedEventCount: SessionLogOffset;
|
|
370
403
|
/** The session identity, derived from its durable header's single copy. */
|
|
371
404
|
get id(): SessionId;
|
|
372
405
|
/**
|
|
373
406
|
* The first seq appended IN THIS PROCESS: the length of the constructor
|
|
374
407
|
* seed (0 without one). Events with smaller seq values entered through
|
|
375
408
|
* construction — replay, fork, or resume — and were never published on the
|
|
376
|
-
* `session/event` firehose (constructor seeds do not emit)
|
|
377
|
-
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
409
|
+
* `session/event` firehose (constructor seeds do not emit). This offset marks
|
|
410
|
+
* the constructor-input boundary for lifecycle ownership and persistence
|
|
411
|
+
* adoption; consumers that need complete canonical history still start at
|
|
412
|
+
* seq 0. Distinct from {@link inheritedEventCount}, the DURABLE
|
|
413
|
+
* fork-lineage cut: a resumed session's constructor seed is its full stored
|
|
414
|
+
* log, while the inherited count keeps the original fork value — this field is the
|
|
381
415
|
* in-process construction fact.
|
|
382
416
|
*
|
|
383
417
|
* Not persisted itself: a seeded session projects it into the log as the
|
|
@@ -390,17 +424,23 @@ declare class Session {
|
|
|
390
424
|
* When this lifecycle appends the marker, it occupies this seq before the
|
|
391
425
|
* store attaches and therefore does not publish either. Otherwise this seq
|
|
392
426
|
* holds an ordinary published write.
|
|
393
|
-
|
|
394
|
-
readonly firstLiveSeq:
|
|
427
|
+
*/
|
|
428
|
+
readonly firstLiveSeq: SessionLogOffset;
|
|
395
429
|
/**
|
|
396
430
|
* Create a detached session by validating and snapshotting borrowed seed
|
|
397
431
|
* events and storage metadata.
|
|
398
432
|
* @param id - session identity.
|
|
399
433
|
* @param seed - optional borrowed replay or fork events.
|
|
400
434
|
* @param header - optional borrowed storage metadata.
|
|
435
|
+
* @param inheritedEventCount - exact fork-inherited prefix length for a seeded header.
|
|
401
436
|
* @returns a detached session.
|
|
402
437
|
*/
|
|
403
|
-
static create(
|
|
438
|
+
static create(
|
|
439
|
+
id: SessionId,
|
|
440
|
+
seed?: readonly SessionEvent[],
|
|
441
|
+
header?: SessionHeader,
|
|
442
|
+
inheritedEventCount?: SessionLogOffset,
|
|
443
|
+
): Session;
|
|
404
444
|
/**
|
|
405
445
|
* Restore a detached session by taking ownership of fresh persistence values.
|
|
406
446
|
* The storage format, event envelopes, sequence continuity, surface transitions,
|
|
@@ -408,18 +448,46 @@ declare class Session {
|
|
|
408
448
|
* @param id - restored session identity.
|
|
409
449
|
* @param seed - fresh detached events whose ownership is transferred.
|
|
410
450
|
* @param header - fresh detached metadata whose ownership is transferred.
|
|
451
|
+
* @param inheritedEventCount - exact fork-inherited prefix length decoded from storage.
|
|
411
452
|
* @returns a restored detached session.
|
|
412
453
|
*/
|
|
413
|
-
static fromRestore(
|
|
454
|
+
static fromRestore(
|
|
455
|
+
id: SessionId,
|
|
456
|
+
seed: readonly SessionEvent[],
|
|
457
|
+
header: SessionHeader,
|
|
458
|
+
inheritedEventCount: SessionLogOffset,
|
|
459
|
+
): Session;
|
|
460
|
+
/**
|
|
461
|
+
* Return the immutable event stored at one exact sequence number.
|
|
462
|
+
* @param seq - event sequence number.
|
|
463
|
+
* @returns the accepted event, or undefined when the log does not contain it.
|
|
464
|
+
*/
|
|
465
|
+
eventAt(seq: SessionSeq): SessionEvent | undefined;
|
|
466
|
+
/**
|
|
467
|
+
* Materialize an immutable snapshot of a half-open event sequence range.
|
|
468
|
+
* A full current snapshot is reused until the next append; every previously
|
|
469
|
+
* returned snapshot remains stable after later appends.
|
|
470
|
+
* @param fromSeq - non-negative inclusive sequence number; defaults to the log start.
|
|
471
|
+
* @param toSeqExclusive - non-negative exclusive sequence number; defaults to the current end.
|
|
472
|
+
* @returns a frozen array of the selected deeply frozen events.
|
|
473
|
+
*/
|
|
474
|
+
snapshotEvents(
|
|
475
|
+
fromSeq: SessionLogOffset = SessionLogOffset(0),
|
|
476
|
+
toSeqExclusive: SessionLogOffset = this.seq,
|
|
477
|
+
): readonly SessionEvent[];
|
|
478
|
+
/**
|
|
479
|
+
* Return this Session's events after its fork-inherited prefix.
|
|
480
|
+
* @returns a fresh array containing child-owned events in log order.
|
|
481
|
+
*/
|
|
482
|
+
ownEvents(): readonly SessionEvent[];
|
|
414
483
|
/**
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
* cast nor ordinary JavaScript can rewrite durable history.
|
|
484
|
+
* Whether one existing event position is outside the fork-inherited prefix.
|
|
485
|
+
* @param seq - event position in this Session.
|
|
486
|
+
* @returns true when the event belongs to this Session rather than its parent.
|
|
419
487
|
*/
|
|
420
|
-
|
|
488
|
+
isOwnSeq(seq: SessionSeq): boolean;
|
|
421
489
|
/** The next event's sequence number — always the log length (the `seq = log.length` contiguity contract). */
|
|
422
|
-
get seq():
|
|
490
|
+
get seq(): SessionLogOffset;
|
|
423
491
|
/**
|
|
424
492
|
* Append one typed event to the log and synchronously notify observers via
|
|
425
493
|
* the store-owned, module-private publication hooks. The hot path never blocks
|
|
@@ -437,7 +505,8 @@ declare class Session {
|
|
|
437
505
|
* declare how it joins the surface, the sole source of derived model
|
|
438
506
|
* history) and
|
|
439
507
|
* rejected by the compiler for non-surface types like `turn/start` or
|
|
440
|
-
* `assistant/
|
|
508
|
+
* `assistant/attempt`. Assistant messages embed their exact provider
|
|
509
|
+
* stream and cannot cite top-level source events.
|
|
441
510
|
* @returns the logged event — its assigned `seq`/`time` plus the SNAPSHOT of
|
|
442
511
|
* `data` that entered the log, so reading `event.data` back sees the logged
|
|
443
512
|
* value, never the caller's still-mutable input.
|
|
@@ -458,13 +527,13 @@ declare class Session {
|
|
|
458
527
|
append<T extends SessionEventType>(
|
|
459
528
|
type: T,
|
|
460
529
|
data: SessionEventMap[T],
|
|
461
|
-
...opts: T extends SurfaceEventType ? [opts: SurfaceIntent] : []
|
|
530
|
+
...opts: T extends SurfaceEventType ? [opts: SurfaceIntent<T>] : []
|
|
462
531
|
): SessionEvent<T>;
|
|
463
532
|
/**
|
|
464
533
|
* The {@link EpochHeader} in force after the log's last header event — the
|
|
465
534
|
* header the NEXT request will be compared against — or undefined before
|
|
466
535
|
* the first `request/header` snapshot. The live, incrementally-maintained
|
|
467
|
-
* form of `foldRequestHeader(session.
|
|
536
|
+
* form of `foldRequestHeader(session.snapshotEvents())`: each header event is folded
|
|
468
537
|
* once, when first seen, so a per-step read costs O(new events).
|
|
469
538
|
* @returns the folded header, or undefined when no header event exists yet.
|
|
470
539
|
*/
|
|
@@ -509,17 +578,17 @@ declare class Session {
|
|
|
509
578
|
`Session.deriveMessages()` 将事件日志投影为模型看到的 `Message[]`。它是缓存的(每个 surface 节点在首次出现时投影一次;surface 重写触发重建)且冻结的(每次调用返回一个新数组,引用共享的深冻结消息,因此通过投影修改已记录的历史在类型上不可表达)。`deriveEventMessage(event)` 是折叠所应用的逐节点纯函数,公开暴露以便外部重建器和开发不变式检查能以完全相同的规则投影日志前缀,不会与缓存产生分歧。投影规则:
|
|
510
579
|
|
|
511
580
|
- `user/message` → 一条携带确切 `content` 的 user 消息;可选 envelope 仅作为日志中的展示元数据保留。
|
|
512
|
-
- `assistant/message` → 一条 assistant
|
|
581
|
+
- `assistant/message` → 一条 assistant 消息,包含生成它的提供方和模型,以及可选的适配器私有回放状态。其嵌入式紧凑 stream 是回放、usage 与 UI 证据,而不是第二条 message。**内容为空的** `assistant/message` 也会跳过:因 max-tokens 而截断且无内容的步骤仍会记录一条 `assistant/message` 来保存 stream、usage、提供方和模型,但无内容的 assistant 轮次不得进入提供方 transcript(文本记录)。
|
|
513
582
|
- `tool/result` → 一条携带 `tool-result` 块的 user 消息。
|
|
514
583
|
- `user/message`(注入上下文,即非 `user` 来源)→ 按时间顺序在相应位置生成一条 user-role 消息,并原样承载其 `content`;其类型化 source 标明生产方,并携带所有生产方专用数据。
|
|
515
584
|
|
|
516
|
-
其余所有事件(`turn/*`、`step
|
|
585
|
+
其余所有事件(`turn/*`、`step/*`、`assistant/attempt`、插件所属的 `llm/retry`)均为结构信息,不会投影为消息。token 记账会展开每个 `assistant/message` 或 `assistant/attempt` 的嵌入式 stream,message 顶层 `usage` 存在时仍是已提交 message 的权威。失败的模型请求 attempt 因此可以保留提供方 usage,而无需虚构 assistant message。当前逻辑校验会拒绝没有提供方/模型的 request header 和 assistant 消息,而不会猜测路由;受支持的历史表示会在当前 Session 存在前,由其相邻格式迁移边归一化并校验。
|
|
517
586
|
|
|
518
587
|
## 活跃会话 fork API
|
|
519
588
|
|
|
520
589
|
`ctx.sessions.create(id, { seed, meta })` 是底层的回放/fork 原语。对于普通的活跃会话 fork,`SessionStore` 暴露一个策略 API:
|
|
521
590
|
|
|
522
|
-
- `fork(source, boundary?, childSessionId?)` 接受一个活跃的 `Session` 对象或活跃的 `SessionId`,选取到 `
|
|
591
|
+
- `fork(source, boundary?, childSessionId?)` 接受一个活跃的 `Session` 对象或活跃的 `SessionId`,选取到 `SessionSeq` boundary(含)为止的源事件(默认为当前最后一个事件),要求所选前缀结束时没有开放轮次,然后创建一个活跃的子会话,包含深克隆的 seed event、`parentSession`、`isSeeded: true`、精确 `inheritedEventCount` 及继承的 `cwd`。
|
|
523
592
|
|
|
524
593
|
显式 `boundary` 允许调用者从任意稳定的轮次间位置 fork,包括之前的 `turn/end` 或更晚的独立纯日志事件,即使源会话有更新的事件或正在进行的轮次。API 拒绝结束于开放轮次内的前缀,而不是静默截断。更广泛的执行关系健全性检查留在既有的 `dsh-invariants` 插件和持久化修复路径中,不在 `fork()` 中重复。`dsh-subagent-fork-in-process` 保留其已完成前缀截断逻辑,因为工具调用时的委托通常在父轮次仍然打开时启动;普通的会话分支应显式指定请求的 boundary。
|
|
525
594
|
|
|
@@ -553,8 +622,10 @@ interface TurnEndReasonMap {
|
|
|
553
622
|
/** At least one step reached its output-token ceiling, even if a plugin continued the turn. */
|
|
554
623
|
'max-tokens': { kind: 'max-tokens' }
|
|
555
624
|
/**
|
|
556
|
-
* A
|
|
557
|
-
*
|
|
625
|
+
* A crash-orphaned turn was closed after the fact: agent-loop resume appends
|
|
626
|
+
* this closer for a stored log whose last turn never ended, and session-query
|
|
627
|
+
* synthesizes it on cold reads. The loop never emits this marker live, and
|
|
628
|
+
* the events recorded before the crash remain intact.
|
|
558
629
|
*/
|
|
559
630
|
interrupted: { kind: 'interrupted' }
|
|
560
631
|
}
|
|
@@ -570,9 +641,9 @@ interface TurnEndReasonMap {
|
|
|
570
641
|
|
|
571
642
|
## 种子结束边界:`session/end-seed`
|
|
572
643
|
|
|
573
|
-
|
|
644
|
+
新 fork constructor 要求 seed 等于 inherited prefix,并在精确持久 cut 追加 `session/end-seed { inherited: true }`。restore 会保留该 tagged marker,并且只在完整 stored seed 尚未以 marker 结尾时追加普通 `session/end-seed {}`。两种形式都只进入 log 且不产生 message;`Session` constructor 是唯一合法 writer。
|
|
574
645
|
|
|
575
|
-
|
|
646
|
+
对于 fork lineage,定位 payload 携带 `inherited: true` 的最后一个 marker;v2 decoding 只在 `SessionHeader.isSeeded` 为 true 时要求该 marker,并从其 seq 推导 `inheritedEventCount`。对于 lifecycle ownership,定位任一形式的最后一个 `session/end-seed`。重新打开已经以任一 marker 结尾的 seed 时,不会再追加普通 marker。
|
|
576
647
|
|
|
577
648
|
它之所以必要,是因为种子历史与实时工作在字节层面完全相同,这会让任何拥有独立开/闭括号的插件失效:一个未配对的 `compaction/start`,无论写入方是在压缩中途崩溃、还是此刻正在压缩,读起来都一样。在 `session/end-seed` 之前的开启标记来自构造种子,并且属于一个已结束的生命周期,无论结束原因为何(崩溃、进程接替,或从仍在运行的父会话 fork 出来),因此其所有方可以视之为已死。这只覆盖*本*会话继承的括号:另一个并发存活的会话可能在同一段历史上持有开放括号,而它自己的边界在别处,因此容忍并发写入方还需要日志之外的存活信号。核心写入该边界但不从中读取任何内容——括号的词汇表仍归其所属插件,这也正是崩溃修复只关闭轮次/步骤/工具边界而从不处理 `compaction/*` 的原因。
|
|
578
649
|
|
|
@@ -588,7 +659,7 @@ interface TurnEndReasonMap {
|
|
|
588
659
|
|
|
589
660
|
## 持久性约定
|
|
590
661
|
|
|
591
|
-
|
|
662
|
+
持久化后端依赖的约定如下:持久日志无损保存每个事件,每个 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()` 始终与后端可持久化的内容一致。新增会携带不可序列化数据、破坏核心执行嵌套或违反事件所有方声明关系的事件类型,都会构成磁盘格式的破坏性变更。
|
|
592
663
|
|
|
593
664
|
消费此约定的后端见 [persistence.md](persistence.zh.md)。
|
|
594
665
|
|
|
@@ -626,7 +697,7 @@ resolveAgent(sessionId: SessionId): Promise<ApiSessionAgentResult>
|
|
|
626
697
|
* @param signal - optional caller cancellation for persistence reads.
|
|
627
698
|
* @returns the current attached state or persisted header and event prefix.
|
|
628
699
|
*/
|
|
629
|
-
inspect( sessionId: SessionId, signal?: AbortSignal, ): Promise<
|
|
700
|
+
inspect( sessionId: SessionId, signal?: AbortSignal, ): Promise<SessionInspection>
|
|
630
701
|
|
|
631
702
|
/**
|
|
632
703
|
* Read all visible Session rows without resuming an Agent.
|
|
@@ -734,7 +805,8 @@ inspect( sessionId: SessionId, signal?: AbortSignal, ): Promise<{ meta: SessionH
|
|
|
734
805
|
* Follow one Session log from its opening or resume cursor.
|
|
735
806
|
* @param request - durable address and last committed sequence already held by the caller.
|
|
736
807
|
* @param signal - cancellation owned by the Remote stream carrier.
|
|
737
|
-
* @returns a complete opening snapshot followed by gap-free event
|
|
808
|
+
* @returns a complete opening snapshot followed by gap-free durable event
|
|
809
|
+
* frames and optional cursorless assistant-stream frames.
|
|
738
810
|
*/
|
|
739
811
|
@Remote({ mode: 'stream' }) follow(request: SessionFollowRequest, signal: AbortSignal): AsyncIterable<SessionFollowFrame>
|
|
740
812
|
|
|
@@ -746,7 +818,7 @@ inspect( sessionId: SessionId, signal?: AbortSignal, ): Promise<{ meta: SessionH
|
|
|
746
818
|
@Remote({ mode: 'stream' }) control(signal: AbortSignal): AsyncIterable<SessionControlFrame>
|
|
747
819
|
```
|
|
748
820
|
|
|
749
|
-
Types: [
|
|
821
|
+
Types: [SessionId](core.zh.md) · [SessionInspection](persistence.zh.md) · [SessionSearchRequest](session-query.zh.md)
|
|
750
822
|
|
|
751
823
|
Source: [`packages/api/session-controller/src/index.ts`](../../packages/api/session-controller/src/index.ts)
|
|
752
824
|
|
|
@@ -756,7 +828,7 @@ Source: [`packages/api/session-controller/src/index.ts`](../../packages/api/sess
|
|
|
756
828
|
|
|
757
829
|
In-memory session store (`ctx.sessions`).
|
|
758
830
|
|
|
759
|
-
Persistence is intentionally not implemented here —
|
|
831
|
+
Persistence is intentionally not implemented here — the agent lifecycle attaches a session-log writer to each published session's write handle; a session published outside that lifecycle persists nothing.
|
|
760
832
|
|
|
761
833
|
```ts cordis-catalog
|
|
762
834
|
/**
|
|
@@ -879,7 +951,7 @@ list(): Session[]
|
|
|
879
951
|
* `SessionStore`'s id policy.
|
|
880
952
|
* @returns The created live child session.
|
|
881
953
|
*/
|
|
882
|
-
fork(source: SessionForkSource, boundary?:
|
|
954
|
+
fork(source: SessionForkSource, boundary?: SessionSeq, childSessionId?: SessionId): Session
|
|
883
955
|
```
|
|
884
956
|
|
|
885
957
|
Types: [CreateSessionOptions](persistence.zh.md) · [PrepareSessionOptions](persistence.zh.md) · [SessionId](core.zh.md)
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write docs/subsystems/slots.md
|
|
5
|
-
slots.md:
|
|
6
|
-
slots.zh.md:
|
|
5
|
+
slots.md: a37374ff11f9d460a2566a7a30f8318e69cbfa0b
|
|
6
|
+
slots.zh.md: 09329b1f299f4912490f888efa3146ec0ce11e28
|
|
@@ -135,6 +135,7 @@ root
|
|
|
135
135
|
│ │ │ ├─ conversation.chat.commandview
|
|
136
136
|
│ │ │ ├─ conversation.chat.turnTail
|
|
137
137
|
│ │ │ └─ tool.call.toolview
|
|
138
|
+
│ │ │ ├─ tool.call.images
|
|
138
139
|
│ │ │ └─ tool.view.cordis
|
|
139
140
|
│ │ ├─ conversation.message.images
|
|
140
141
|
│ │ └─ conversation.trajectory.images
|
|
@@ -135,6 +135,7 @@ root
|
|
|
135
135
|
│ │ │ ├─ conversation.chat.commandview
|
|
136
136
|
│ │ │ ├─ conversation.chat.turnTail
|
|
137
137
|
│ │ │ └─ tool.call.toolview
|
|
138
|
+
│ │ │ ├─ tool.call.images
|
|
138
139
|
│ │ │ └─ tool.view.cordis
|
|
139
140
|
│ │ ├─ conversation.message.images
|
|
140
141
|
│ │ └─ conversation.trajectory.images
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write docs/subsystems/storage.md
|
|
5
|
-
storage.md:
|
|
6
|
-
storage.zh.md:
|
|
5
|
+
storage.md: 03b0fa1c674064994039d37c4fec906dc45794b3
|
|
6
|
+
storage.zh.md: ded8783fbfe7f0d7d8a846c7a66d780b48de1445
|
|
@@ -44,7 +44,7 @@ interface StorageBackend {
|
|
|
44
44
|
}
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
A backend owns one medium (a file-tree root, a database file) and exposes optional operation groups; `kv` is the only shipped group. `KvFacet.open(descriptor)` opens one named unit — `KvUnitDescriptor` carries the name, format version, table names, and whether a global singleton slot exists — and returns a `KvUnit` with `loadAll`, `putRecord`, `deleteRecord`, `setGlobal`, and `close`. Unit and table names must match `UNIT_NAME_RE` (safe as a file name and as a SQL identifier segment); record keys are arbitrary strings that never reach file paths. A unit does not serialize concurrent writes — ordering belongs to the caller — but each single call is atomic on the medium and durable once resolved. A medium stamped with a different version rejects `version-mismatch`;
|
|
47
|
+
A backend owns one medium (a file-tree root, a database file) and exposes optional operation groups; `kv` is the only shipped group. `KvFacet.open(descriptor)` opens one named unit — `KvUnitDescriptor` carries the name, current format version, optional compatible record versions, table names, and whether a global singleton slot exists — and returns a `KvUnit` with `loadAll`, `putRecord`, `deleteRecord`, `setGlobal`, and `close`. Unit and table names must match `UNIT_NAME_RE` (safe as a file name and as a SQL identifier segment); record keys are arbitrary strings that never reach file paths. A unit does not serialize concurrent writes — ordering belongs to the caller — but each single call is atomic on the medium and durable once resolved. A `single` medium stamped with a different version rejects `version-mismatch`; a `per-record` document stamped outside the accepted set reads as absent. A medium that cannot be parsed as the unit rejects `malformed-medium`. [`backend.ts`](../../packages/storage/storage/src/backend.ts) is the normative clause-by-clause contract, and the shared conformance suite in [`tests/contract.ts`](../../packages/storage/storage/tests/contract.ts) checks every clause against each backend. The [json backend](../../packages/storage/storage-json/README.md) republishes one whole human-readable file per unit atomically; the [sqlite backend](../../packages/storage/storage-sqlite/README.md) stores one document per row in one database for frequently updated data.
|
|
48
48
|
|
|
49
49
|
## Declaring a domain
|
|
50
50
|
|
|
@@ -55,16 +55,36 @@ A domain is declared once by its owning package as a spec object — the single
|
|
|
55
55
|
interface DomainSpec {
|
|
56
56
|
/** Domain name; must match `UNIT_NAME_RE` (doubles as the backend unit name). */
|
|
57
57
|
readonly name: string
|
|
58
|
-
/**
|
|
58
|
+
/** Current domain format version; reads enforce it according to the selected layout. */
|
|
59
59
|
readonly version: number
|
|
60
60
|
/**
|
|
61
61
|
* Medium layout for the backend unit: `single` (the default) stores the
|
|
62
62
|
* whole unit as one document; `per-record` stores each record as its own
|
|
63
63
|
* document, for units whose records are large, sparse, or individually
|
|
64
|
-
* disposable — the projection cache — and scopes version
|
|
65
|
-
* (
|
|
64
|
+
* disposable — the projection cache — and scopes version checks per record
|
|
65
|
+
* (an unaccepted record document is discarded, never migrated).
|
|
66
66
|
*/
|
|
67
67
|
readonly layout?: 'single' | 'per-record'
|
|
68
|
+
/**
|
|
69
|
+
* Older domain versions whose stored records the current record schemas
|
|
70
|
+
* also accept (the declaring owner vouches for that, typically by
|
|
71
|
+
* declaring the fields older records lack as optional). `per-record` backends
|
|
72
|
+
* read documents stamped with a listed version instead of discarding them,
|
|
73
|
+
* and accept a legacy whole-unit file so stamped for the one-time
|
|
74
|
+
* bootstrap; writes always stamp {@link version}.
|
|
75
|
+
*/
|
|
76
|
+
readonly compatibleVersions?: readonly number[]
|
|
77
|
+
/**
|
|
78
|
+
* What `open` does with a stored table record that fails its zod schema.
|
|
79
|
+
* Absent (the default), the whole open rejects with `invalid-record` —
|
|
80
|
+
* right for authoritative data. `'backup-and-skip'` is for domains whose
|
|
81
|
+
* records are disposable derived data: the backend moves the record's
|
|
82
|
+
* document aside (`KvUnit.backupRecord`), the failure is logged with
|
|
83
|
+
* its cause, and the open continues with the record absent. A backend
|
|
84
|
+
* without `backupRecord` (no per-record document to move) falls back
|
|
85
|
+
* to the rejecting default. The global slot always rejects.
|
|
86
|
+
*/
|
|
87
|
+
readonly invalidRecords?: 'backup-and-skip'
|
|
68
88
|
/** Optional global singleton slot. */
|
|
69
89
|
readonly global?: DomainGlobalSpec<unknown>
|
|
70
90
|
/** Table declarations keyed by table name; each name must match `UNIT_NAME_RE`. */
|
|
@@ -180,7 +200,10 @@ The mounted domain facility. Opens declared domains over routed backends; one fa
|
|
|
180
200
|
* (`facet-unsupported`); open the unit projected from the spec (backend
|
|
181
201
|
* `version-mismatch`/`malformed-medium` pass through); load and validate
|
|
182
202
|
* every stored record against the spec's zod schemas (`invalid-record`
|
|
183
|
-
* with the offending table and key
|
|
203
|
+
* with the offending table and key — unless the spec declares
|
|
204
|
+
* `invalidRecords: 'backup-and-skip'` and the unit can move documents aside, in
|
|
205
|
+
* which case the failing record is backed up, logged, and skipped);
|
|
206
|
+
* construct the domain.
|
|
184
207
|
*
|
|
185
208
|
* Lifecycle: the CALLER owns the returned handle and closes it via
|
|
186
209
|
* `Domain.close()` (typically as its own `ctx.effect` disposer) — the
|