dsh-plugin-dev-kb 1.0.9 → 1.1.0

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.
Files changed (120) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.en.md +6 -6
  3. package/README.md +6 -6
  4. package/kb/INDEX.md +4 -2
  5. package/kb/README.md +4 -4
  6. package/kb/extra/AGENTS.md +1 -1
  7. package/kb/extra/cookbook/adding-a-remote-api.md +197 -0
  8. package/kb/extra/cookbook/adding-a-remote-api.zh.md +197 -0
  9. package/kb/extra/deepseek-llm-api-wire-extensions.md +18 -14
  10. package/kb/extra/deepseek-llm-api-wire-extensions.zh.md +18 -14
  11. package/kb/extra/event-producer-consumer.md +44 -43
  12. package/kb/extra/event-producer-consumer.zh.md +47 -46
  13. package/kb/extra/i18n/style-samples.md +2 -2
  14. package/kb/extra/module-graph.md +444 -991
  15. package/kb/extra/module-graph.zh.md +446 -993
  16. package/kb/extra/subsystems/agent-team.md +4 -3
  17. package/kb/extra/subsystems/agent-team.zh.md +4 -3
  18. package/kb/extra/subsystems/attachment.md +157 -4
  19. package/kb/extra/subsystems/attachment.zh.md +157 -4
  20. package/kb/extra/subsystems/feedback.md +2 -2
  21. package/kb/extra/subsystems/feedback.zh.md +2 -2
  22. package/kb/extra/testing.md +6 -2
  23. package/kb/extra/testing.zh.md +6 -2
  24. package/kb/extra/web-styling.md +4 -0
  25. package/kb/extra/web-styling.zh.md +4 -0
  26. package/kb/meta/search-index.json +135 -111
  27. package/kb/meta/site-pages.txt +184 -182
  28. package/kb/meta/source.json +5 -5
  29. package/kb/meta/topics.md +4 -4
  30. package/kb/site/develop/cordis-tutorial/07-into-the-harness.md +3 -2
  31. package/kb/site/develop/practice/llm-adapter.md +4 -3
  32. package/kb/site/en/develop/cordis-tutorial/07-into-the-harness.md +3 -2
  33. package/kb/site/en/develop/practice/llm-adapter.md +4 -3
  34. package/kb/site/en/guide/network-proxy.md +87 -0
  35. package/kb/site/en/guide/providers.md +70 -17
  36. package/kb/site/en/guide/schedule.md +2 -0
  37. package/kb/site/en/reference/agent-lifecycle.md +5 -3
  38. package/kb/site/en/reference/api-gateway.md +2 -2
  39. package/kb/site/en/reference/capability-seams.md +14 -7
  40. package/kb/site/en/reference/config-catalog.md +157 -230
  41. package/kb/site/en/reference/cookbook/adding-a-package.md +1 -2
  42. package/kb/site/en/reference/cookbook/adding-a-settings-card.md +10 -8
  43. package/kb/site/en/reference/cookbook/extension-cookbook.md +15 -12
  44. package/kb/site/en/reference/index.md +11 -4
  45. package/kb/site/en/reference/persistence-catalog.md +91 -70
  46. package/kb/site/en/reference/subsystems/code-runtime.md +7 -3
  47. package/kb/site/en/reference/subsystems/commands.md +25 -16
  48. package/kb/site/en/reference/subsystems/compaction.md +9 -9
  49. package/kb/site/en/reference/subsystems/conversation.md +11 -4
  50. package/kb/site/en/reference/subsystems/core.md +96 -15
  51. package/kb/site/en/reference/subsystems/credentials.md +5 -4
  52. package/kb/site/en/reference/subsystems/goal.md +1 -1
  53. package/kb/site/en/reference/subsystems/index.md +1 -1
  54. package/kb/site/en/reference/subsystems/llm-streaming.md +24 -3
  55. package/kb/site/en/reference/subsystems/permission-presets.md +5 -5
  56. package/kb/site/en/reference/subsystems/persistence.md +185 -194
  57. package/kb/site/en/reference/subsystems/plan.md +3 -3
  58. package/kb/site/en/reference/subsystems/sandbox.md +2 -0
  59. package/kb/site/en/reference/subsystems/schedule.md +9 -3
  60. package/kb/site/en/reference/subsystems/session-projection.md +50 -24
  61. package/kb/site/en/reference/subsystems/session-query.md +19 -13
  62. package/kb/site/en/reference/subsystems/session-reference.md +25 -1
  63. package/kb/site/en/reference/subsystems/session-telemetry.md +6 -5
  64. package/kb/site/en/reference/subsystems/session-title.md +6 -6
  65. package/kb/site/en/reference/subsystems/session.md +156 -73
  66. package/kb/site/en/reference/subsystems/settings.md +30 -12
  67. package/kb/site/en/reference/subsystems/skills.md +1 -1
  68. package/kb/site/en/reference/subsystems/slots.md +1 -0
  69. package/kb/site/en/reference/subsystems/storage.md +28 -5
  70. package/kb/site/en/reference/subsystems/subagent.md +43 -97
  71. package/kb/site/en/reference/subsystems/system-prompt.md +16 -3
  72. package/kb/site/en/reference/subsystems/token-meter.md +2 -2
  73. package/kb/site/en/reference/subsystems/typert.md +18 -18
  74. package/kb/site/en/reference/subsystems/web-client.md +1 -1
  75. package/kb/site/en/reference/subsystems/workspace.md +8 -8
  76. package/kb/site/en/reference/tool-catalog.md +10 -64
  77. package/kb/site/guide/network-proxy.md +87 -0
  78. package/kb/site/guide/providers.md +70 -17
  79. package/kb/site/guide/schedule.md +2 -0
  80. package/kb/site/reference/agent-lifecycle.md +5 -3
  81. package/kb/site/reference/api-gateway.md +2 -2
  82. package/kb/site/reference/capability-seams.md +14 -7
  83. package/kb/site/reference/config-catalog.md +156 -229
  84. package/kb/site/reference/cookbook/adding-a-package.md +1 -2
  85. package/kb/site/reference/cookbook/adding-a-settings-card.md +10 -8
  86. package/kb/site/reference/cookbook/extension-cookbook.md +15 -12
  87. package/kb/site/reference/index.md +11 -4
  88. package/kb/site/reference/persistence-catalog.md +91 -70
  89. package/kb/site/reference/subsystems/code-runtime.md +7 -3
  90. package/kb/site/reference/subsystems/commands.md +25 -16
  91. package/kb/site/reference/subsystems/compaction.md +9 -9
  92. package/kb/site/reference/subsystems/conversation.md +11 -4
  93. package/kb/site/reference/subsystems/core.md +96 -15
  94. package/kb/site/reference/subsystems/credentials.md +5 -4
  95. package/kb/site/reference/subsystems/goal.md +1 -1
  96. package/kb/site/reference/subsystems/index.md +1 -1
  97. package/kb/site/reference/subsystems/llm-streaming.md +24 -3
  98. package/kb/site/reference/subsystems/permission-presets.md +5 -5
  99. package/kb/site/reference/subsystems/persistence.md +184 -193
  100. package/kb/site/reference/subsystems/plan.md +3 -3
  101. package/kb/site/reference/subsystems/schedule.md +9 -3
  102. package/kb/site/reference/subsystems/session-projection.md +50 -24
  103. package/kb/site/reference/subsystems/session-query.md +19 -13
  104. package/kb/site/reference/subsystems/session-reference.md +25 -1
  105. package/kb/site/reference/subsystems/session-telemetry.md +6 -5
  106. package/kb/site/reference/subsystems/session-title.md +6 -6
  107. package/kb/site/reference/subsystems/session.md +156 -73
  108. package/kb/site/reference/subsystems/settings.md +30 -12
  109. package/kb/site/reference/subsystems/skills.md +1 -1
  110. package/kb/site/reference/subsystems/slots.md +1 -0
  111. package/kb/site/reference/subsystems/storage.md +28 -5
  112. package/kb/site/reference/subsystems/subagent.md +43 -97
  113. package/kb/site/reference/subsystems/system-prompt.md +16 -3
  114. package/kb/site/reference/subsystems/token-meter.md +2 -2
  115. package/kb/site/reference/subsystems/typert.md +18 -18
  116. package/kb/site/reference/subsystems/web-client.md +1 -1
  117. package/kb/site/reference/subsystems/workspace.md +8 -8
  118. package/kb/site/reference/tool-catalog.md +9 -63
  119. package/package.json +2 -2
  120. package/skills/dsh-plugin-dev-kb.md +4 -4
@@ -24,8 +24,8 @@ interface UserMessage extends Message {
24
24
  /**
25
25
  * The merge-extensible, append-only source of truth for an agent interaction.
26
26
  * Message history is derived from this log. Every event is lossless JSON and
27
- * sequence numbers stay contiguous, including raw chunks, so persistence can
28
- * store the canonical log verbatim.
27
+ * sequence numbers stay contiguous. Assistant attempt events embed their exact
28
+ * compact raw streams so persistence stores one durable settlement per attempt.
29
29
  */
30
30
  interface SessionEventMap {
31
31
  /**
@@ -56,8 +56,6 @@ interface SessionEventMap {
56
56
  * project their `content` verbatim; `source` tells them apart.
57
57
  */
58
58
  'user/message': UserMessage
59
- /** Raw stream chunk — token-level replay fidelity. */
60
- 'assistant/chunk': { turn: number; step: number; chunk: StreamChunk }
61
59
  /**
62
60
  * Assembled assistant message for one step (derived history uses this).
63
61
  * Carries the step's `usage` when the adapter reported token accounting, so
@@ -68,7 +66,21 @@ interface SessionEventMap {
68
66
  * marker distinguishes that prefix without re-deriving interruption from turn
69
67
  * boundaries. An aborted turn with no such event streamed no visible content.
70
68
  */
71
- 'assistant/message': { turn: number; step: number; message: AssistantMessage; usage?: TokenUsage; interrupted?: true }
69
+ 'assistant/message': {
70
+ turn: number
71
+ step: number
72
+ message: AssistantMessage
73
+ /** Exact timed model stream, compacted without joining delta boundaries. */
74
+ stream: AssistantStreamRecord[]
75
+ usage?: TokenUsage
76
+ interrupted?: true
77
+ }
78
+ /**
79
+ * One model attempt that committed no surface message. The embedded stream
80
+ * preserves a failed, retried, cancelled, or stream-error attempt that
81
+ * reached settlement without fabricating model-visible history.
82
+ */
83
+ 'assistant/attempt': { turn: number; step: number; stream: AssistantStreamRecord[] }
72
84
  /**
73
85
  * The model requested one tool invocation: `name` with the raw `arguments`
74
86
  * JSON string exactly as the model produced it (unparsed). `callId` pairs the
@@ -112,12 +124,12 @@ interface SessionEventMap {
112
124
  * Marks the end of a constructor seed. Events before it have smaller seq
113
125
  * values and came from the seed (resume, fork, or replay); this lifecycle
114
126
  * produced none of them. This log-only event is the durable projection of
115
- * {@link Session.firstLiveSeq}. Its payload is empty — position and `time`
116
- * carry the meaning.
127
+ * {@link Session.firstLiveSeq}.
117
128
  *
118
- * Locate the LAST one in stored history. A seed already ending in one is not
119
- * re-marked, so reopening an untouched session does not grow its log per
120
- * pickup and the event need not be at the current `firstLiveSeq`.
129
+ * A fresh fork child owns one `{ inherited: true }` marker at its exact
130
+ * inherited-prefix cut, even when that prefix ends in an ancestor marker.
131
+ * The last tagged marker is the current Session's cut; untagged markers keep
132
+ * ordinary restore and replay lifecycle boundaries.
121
133
  *
122
134
  * `Session`'s constructor is the only legitimate writer. The invariant
123
135
  * companion deliberately constrains nothing here, so a plugin appending one
@@ -130,7 +142,7 @@ interface SessionEventMap {
130
142
  * writers — a concurrently live session holds its own boundary elsewhere,
131
143
  * so tolerating concurrent writers needs a signal beyond the log.
132
144
  */
133
- 'session/end-seed': Record<string, never>
145
+ 'session/end-seed': { inherited?: true }
134
146
  }
135
147
  ```
136
148
 
@@ -182,6 +194,28 @@ interface RequestContext {
182
194
 
183
195
  A proper discriminated union over `type` (not independent `type`/`data` unions), so `switch (event.type)` narrows `event.data` without casts. `seq` is the monotonic position in the log (`seq = log.length`); `time` is epoch ms.
184
196
 
197
+ ```ts type-equiv
198
+ /** Sequence number of one existing event in a Session log. */
199
+ type SessionSeq = BrandedNumber<'SessionSeq'>
200
+ ```
201
+
202
+ ```ts type-equiv
203
+ /** A Session log gap, prefix length, or read offset, which may equal the event count. */
204
+ type SessionLogOffset = BrandedNumber<'SessionLogOffset'>
205
+ ```
206
+
207
+ ```ts type-equiv
208
+ /** Inclusive Session event watermark, or `-1` before any event exists. */
209
+ type SessionSeqCursor = SessionSeq | -1
210
+ ```
211
+
212
+ ```ts type-equiv
213
+ /** One existing Session event position, or explicit absence. */
214
+ type OptionalSessionSeq = SessionSeq | null
215
+ ```
216
+
217
+ `SessionSeq(value)` and `SessionLogOffset(value)` admit only non-negative safe integers and reject negative zero. They add compile-time brands without changing the serialized number; arithmetic returns an ordinary `number` that callers must admit again through the constructor for its intended role.
218
+
185
219
  ```ts type-equiv
186
220
  /**
187
221
  * One immutable entry in the session log.
@@ -192,7 +226,7 @@ A proper discriminated union over `type` (not independent `type`/`data` unions),
192
226
  * The {@link sourceEventSeqs} and {@link surfaceOp} fields are conditional:
193
227
  * they only exist on {@link SurfaceEventType} variants (`user/message`,
194
228
  * `assistant/message`, `tool/result`).
195
- * Non-surface events (boundary markers, chunks, usage, errors) never carry
229
+ * Non-surface events (boundary markers, attempts, errors) never carry
196
230
  * surface metadata — the compiler enforces this at `Session.append()`
197
231
  * call sites.
198
232
  */
@@ -200,20 +234,28 @@ type SessionEvent<T extends SessionEventType = SessionEventType> = {
200
234
  [K in SessionEventType]: {
201
235
  type: K
202
236
  /** Monotonic sequence number within the session. */
203
- seq: number
237
+ seq: SessionSeq
204
238
  /** Unix epoch milliseconds. */
205
239
  time: number
206
240
  data: SessionEventMap[K]
241
+ /**
242
+ * Marks an event a reader may safely skip when it does not recognize
243
+ * `type`. Absent means required: a reader meeting an unrecognized type
244
+ * without this marker MUST refuse to reconstruct the session instead of
245
+ * silently dropping the event, because an unrecognized required event may
246
+ * change how the rest of the log is interpreted. A writer sets `true` only
247
+ * on purely informational records whose loss cannot affect reconstruction;
248
+ * defaulting to required means a forgotten marker over-refuses (an
249
+ * inconvenience) rather than silently resuming a gutted session.
250
+ */
251
+ ignorable?: true
207
252
  } & (K extends SurfaceEventType ? {
208
253
  /**
209
- * Seq numbers of earlier events that this event cites as sources
210
- * (e.g. the `assistant/chunk` seqs that built an `assistant/message`,
211
- * or the surface nodes shadowed by a compaction replace node). An
212
- * `assistant/message` may carry a present empty array for a known empty
213
- * provider stream; when the field is absent, the event does not record which
214
- * earlier events produced the message.
254
+ * Seq numbers of earlier events that this event cites as sources, such as
255
+ * the surface nodes shadowed by a compaction replacement. A v2
256
+ * `assistant/message` embeds its provider stream and cannot carry this field.
215
257
  */
216
- sourceEventSeqs?: number[]
258
+ sourceEventSeqs?: SessionSeq[]
217
259
  /** How this event entered the surface; absent for non-surface events. */
218
260
  surfaceOp?: SurfaceOp
219
261
  } : object)
@@ -222,7 +264,7 @@ type SessionEvent<T extends SessionEventType = SessionEventType> = {
222
264
 
223
265
  `SessionEventType = keyof SessionEventMap`. Because `SessionEventMap` is merge-extensible, switches over `SessionEvent` must NOT use `assertNever` — a plugin-added variant is a valid unknown value; handle the known cases and fall through `default`.
224
266
 
225
- For `assistant/message`, a present `sourceEventSeqs: []` is a complete known-empty provider stream, while a legacy or foreign event with no field does not record which earlier events produced the message. The loop writes the field for every successful model call; every other surface event requires a non-empty list when the field is present.
267
+ V2 `assistant/message` embeds its provider stream and cannot carry `sourceEventSeqs`. User and tool surface events may cite a complete non-empty set of unique earlier events when their provenance or replacement operation requires it.
226
268
 
227
269
  ## Surface types
228
270
 
@@ -234,7 +276,8 @@ The three message-producing types (`SurfaceEventType` — `user/message`, `assis
234
276
  /**
235
277
  * The subset of {@link SessionEventType} values whose events produce LLM
236
278
  * messages and are eligible to appear on the ordered surface. Only these
237
- * event types may carry {@link SurfaceOp} and {@link SessionEvent.sourceEventSeqs}.
279
+ * event types may carry {@link SurfaceOp}; user and tool events may also cite
280
+ * earlier sources through {@link SessionEvent.sourceEventSeqs}.
238
281
  */
239
282
  type SurfaceEventType =
240
283
  | 'user/message'
@@ -260,7 +303,7 @@ type SurfaceEventType =
260
303
  */
261
304
  type SurfaceOp =
262
305
  | 'append'
263
- | { op: 'replace'; start: number; end: number }
306
+ | { op: 'replace'; start: SessionSeq; end: SessionSeq }
264
307
  ```
265
308
 
266
309
  `'append'` is the normal tail-append path. `replace` shadows surface entries from `start` through `end` inclusive (both must be valid surface seqs; `start === end` replaces a single entry) and inserts the new event in their place.
@@ -272,21 +315,20 @@ type SurfaceOp =
272
315
  * Surface placement and cited source-event seqs for {@link Session.append}. Required on
273
316
  * message-producing events and forbidden on log-only events.
274
317
  */
275
- interface SurfaceIntent {
318
+ type SurfaceIntent<T extends SurfaceEventType = SurfaceEventType> = {
276
319
  surfaceOp: SurfaceOp
277
- /**
278
- * Complete set of known source-event seqs. `assistant/message` may use a
279
- * present empty array for a known empty provider stream; when the field is
280
- * absent, the event does not record which earlier events produced the message.
281
- * Other surface events require a non-empty set when this field is present.
282
- */
283
- sourceEventSeqs?: number[]
284
- }
320
+ } & (T extends 'assistant/message' ? {
321
+ /** V2 Assistant messages embed their provider stream instead of citing source events. */
322
+ sourceEventSeqs?: never
323
+ } : {
324
+ /** Complete non-empty set of known earlier source-event seqs. */
325
+ sourceEventSeqs?: SessionSeq[]
326
+ })
285
327
  ```
286
328
 
287
329
  Required for `SurfaceEventType` events — every message-producing event must declare how it joins the surface, the sole source of derived model history. A human-facing transcript is the other projection and reads the log's append-origin events instead, because the surface deliberately shadows the ranges a replacement summarizes (`isAppendSurfaceEvent` in [dsh-session](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/README.md)). Non-surface types reject it at compile time.
288
330
 
289
- Only `assistant/message` may carry a present empty `sourceEventSeqs`; when the field is absent, the event does not record which earlier events produced the message, and the provider may still have emitted chunks.
331
+ `assistant/message` cannot carry `sourceEventSeqs`; its `stream` owns exact provider evidence. Other surface events omit the field when they cite no earlier event and use a complete non-empty list when they do.
290
332
 
291
333
  ### `SessionSurface` — the live readonly surface projection
292
334
 
@@ -298,7 +340,7 @@ Only `assistant/message` may carry a present empty `sourceEventSeqs`; when the f
298
340
  /** Readonly live projection of the message-producing session events. */
299
341
  interface SessionSurface {
300
342
  /** Current surface event sequences in model-visible order. */
301
- readonly nodes: readonly number[]
343
+ readonly nodes: readonly SessionSeq[]
302
344
  /** Monotonic count of committed positional replacements. */
303
345
  readonly replaceGeneration: number
304
346
  }
@@ -312,13 +354,13 @@ interface SessionSurface {
312
354
  /** One replacement operation observed while folding a session surface. */
313
355
  interface SurfaceFoldReplacement {
314
356
  /** Seq of the event that replaced the prior surface range. */
315
- seq: number
357
+ seq: SessionSeq
316
358
  /** Declared inclusive start seq of the replaced surface range. */
317
- start: number
359
+ start: SessionSeq
318
360
  /** Declared inclusive end seq of the replaced surface range. */
319
- end: number
361
+ end: SessionSeq
320
362
  /** Actual surface entries removed by the operation, in surface order. */
321
- shadowedSeqs: number[]
363
+ shadowedSeqs: SessionSeq[]
322
364
  }
323
365
  ```
324
366
 
@@ -326,7 +368,7 @@ interface SurfaceFoldReplacement {
326
368
  /** Complete result of replaying the surface operations in a session log. */
327
369
  interface SurfaceFoldResult {
328
370
  /** Current surface event sequences in model-visible order. */
329
- nodes: number[]
371
+ nodes: SessionSeq[]
330
372
  /** Replacement operations in event order. */
331
373
  replacements: SurfaceFoldReplacement[]
332
374
  }
@@ -350,24 +392,27 @@ declare class Session {
350
392
  get surface(): SessionSurface;
351
393
  /**
352
394
  * Detached, deep-frozen creation metadata (format version, cwd, lineage,
353
- * seed boundary). Supplied by the store via `ctx.sessions.create()`. When a
395
+ * and whether fork history exists). Supplied by the store via `ctx.sessions.create()`. When a
354
396
  * `Session` is created without a store-owned header, a minimal header is
355
397
  * synthesized (stamped with the current {@link SESSION_FORMAT_VERSION}) so
356
398
  * `session.header` is always present. Kept out of the event log — it is a
357
399
  * storage concern, not replayable conversation state.
358
400
  */
359
401
  readonly header: SessionHeader;
402
+ /** Number of leading events inherited from this Session's fork parent. */
403
+ readonly inheritedEventCount: SessionLogOffset;
360
404
  /** The session identity, derived from its durable header's single copy. */
361
405
  get id(): SessionId;
362
406
  /**
363
407
  * The first seq appended IN THIS PROCESS: the length of the constructor
364
408
  * seed (0 without one). Events with smaller seq values entered through
365
409
  * construction — replay, fork, or resume — and were never published on the
366
- * `session/event` firehose (constructor seeds do not emit), so consumers
367
- * that replay the log as a publication substitute (telemetry adoption)
368
- * start here. Distinct from `header.seedLength`, the DURABLE fork-lineage
369
- * boundary: a resumed session's constructor seed is its full stored log,
370
- * while its header keeps the original fork value this field is the
410
+ * `session/event` firehose (constructor seeds do not emit). This offset marks
411
+ * the constructor-input boundary for lifecycle ownership and persistence
412
+ * adoption; consumers that need complete canonical history still start at
413
+ * seq 0. Distinct from {@link inheritedEventCount}, the DURABLE
414
+ * fork-lineage cut: a resumed session's constructor seed is its full stored
415
+ * log, while the inherited count keeps the original fork value — this field is the
371
416
  * in-process construction fact.
372
417
  *
373
418
  * Not persisted itself: a seeded session projects it into the log as the
@@ -380,17 +425,23 @@ declare class Session {
380
425
  * When this lifecycle appends the marker, it occupies this seq before the
381
426
  * store attaches and therefore does not publish either. Otherwise this seq
382
427
  * holds an ordinary published write.
383
- */
384
- readonly firstLiveSeq: number;
428
+ */
429
+ readonly firstLiveSeq: SessionLogOffset;
385
430
  /**
386
431
  * Create a detached session by validating and snapshotting borrowed seed
387
432
  * events and storage metadata.
388
433
  * @param id - session identity.
389
434
  * @param seed - optional borrowed replay or fork events.
390
435
  * @param header - optional borrowed storage metadata.
436
+ * @param inheritedEventCount - exact fork-inherited prefix length for a seeded header.
391
437
  * @returns a detached session.
392
438
  */
393
- static create(id: SessionId, seed?: readonly SessionEvent[], header?: SessionHeader): Session;
439
+ static create(
440
+ id: SessionId,
441
+ seed?: readonly SessionEvent[],
442
+ header?: SessionHeader,
443
+ inheritedEventCount?: SessionLogOffset,
444
+ ): Session;
394
445
  /**
395
446
  * Restore a detached session by taking ownership of fresh persistence values.
396
447
  * The storage format, event envelopes, sequence continuity, surface transitions,
@@ -398,18 +449,46 @@ declare class Session {
398
449
  * @param id - restored session identity.
399
450
  * @param seed - fresh detached events whose ownership is transferred.
400
451
  * @param header - fresh detached metadata whose ownership is transferred.
452
+ * @param inheritedEventCount - exact fork-inherited prefix length decoded from storage.
401
453
  * @returns a restored detached session.
402
454
  */
403
- static fromRestore(id: SessionId, seed: readonly SessionEvent[], header: SessionHeader): Session;
455
+ static fromRestore(
456
+ id: SessionId,
457
+ seed: readonly SessionEvent[],
458
+ header: SessionHeader,
459
+ inheritedEventCount: SessionLogOffset,
460
+ ): Session;
461
+ /**
462
+ * Return the immutable event stored at one exact sequence number.
463
+ * @param seq - event sequence number.
464
+ * @returns the accepted event, or undefined when the log does not contain it.
465
+ */
466
+ eventAt(seq: SessionSeq): SessionEvent | undefined;
467
+ /**
468
+ * Materialize an immutable snapshot of a half-open event sequence range.
469
+ * A full current snapshot is reused until the next append; every previously
470
+ * returned snapshot remains stable after later appends.
471
+ * @param fromSeq - non-negative inclusive sequence number; defaults to the log start.
472
+ * @param toSeqExclusive - non-negative exclusive sequence number; defaults to the current end.
473
+ * @returns a frozen array of the selected deeply frozen events.
474
+ */
475
+ snapshotEvents(
476
+ fromSeq: SessionLogOffset = SessionLogOffset(0),
477
+ toSeqExclusive: SessionLogOffset = this.seq,
478
+ ): readonly SessionEvent[];
479
+ /**
480
+ * Return this Session's events after its fork-inherited prefix.
481
+ * @returns a fresh array containing child-owned events in log order.
482
+ */
483
+ ownEvents(): readonly SessionEvent[];
404
484
  /**
405
- * An immutable snapshot of the append-only event log. The snapshot is reused
406
- * until the next append; a previously returned array does not grow later.
407
- * Events and their nested data are deep-frozen at acceptance, so neither a
408
- * cast nor ordinary JavaScript can rewrite durable history.
485
+ * Whether one existing event position is outside the fork-inherited prefix.
486
+ * @param seq - event position in this Session.
487
+ * @returns true when the event belongs to this Session rather than its parent.
409
488
  */
410
- get events(): readonly SessionEvent[];
489
+ isOwnSeq(seq: SessionSeq): boolean;
411
490
  /** The next event's sequence number — always the log length (the `seq = log.length` contiguity contract). */
412
- get seq(): number;
491
+ get seq(): SessionLogOffset;
413
492
  /**
414
493
  * Append one typed event to the log and synchronously notify observers via
415
494
  * the store-owned, module-private publication hooks. The hot path never blocks
@@ -427,7 +506,8 @@ declare class Session {
427
506
  * declare how it joins the surface, the sole source of derived model
428
507
  * history) and
429
508
  * rejected by the compiler for non-surface types like `turn/start` or
430
- * `assistant/chunk`.
509
+ * `assistant/attempt`. Assistant messages embed their exact provider
510
+ * stream and cannot cite top-level source events.
431
511
  * @returns the logged event — its assigned `seq`/`time` plus the SNAPSHOT of
432
512
  * `data` that entered the log, so reading `event.data` back sees the logged
433
513
  * value, never the caller's still-mutable input.
@@ -437,7 +517,7 @@ declare class Session {
437
517
  * Map/Set/Date/class instance), or when the candidate violates the
438
518
  * canonical surface contract (marker shape and eligibility, unique
439
519
  * earlier source-event references, positional replacement validity, and complete
440
- * shadowed-node coverage). One recursive pass reads, validates, and
520
+ * shadowed-node coverage). One iterative pass reads, validates, and
441
521
  * copies each nested value once, so a stateful getter cannot supply one value
442
522
  * to validation and another to storage. The event log is the durable source
443
523
  * of truth, so a bad event fails at the append site rather than later during
@@ -448,13 +528,13 @@ declare class Session {
448
528
  append<T extends SessionEventType>(
449
529
  type: T,
450
530
  data: SessionEventMap[T],
451
- ...opts: T extends SurfaceEventType ? [opts: SurfaceIntent] : []
531
+ ...opts: T extends SurfaceEventType ? [opts: SurfaceIntent<T>] : []
452
532
  ): SessionEvent<T>;
453
533
  /**
454
534
  * The {@link EpochHeader} in force after the log's last header event — the
455
535
  * header the NEXT request will be compared against — or undefined before
456
536
  * the first `request/header` snapshot. The live, incrementally-maintained
457
- * form of `foldRequestHeader(session.events)`: each header event is folded
537
+ * form of `foldRequestHeader(session.snapshotEvents())`: each header event is folded
458
538
  * once, when first seen, so a per-step read costs O(new events).
459
539
  * @returns the folded header, or undefined when no header event exists yet.
460
540
  */
@@ -499,17 +579,17 @@ declare class Session {
499
579
  `Session.deriveMessages()` projects the event log into the `Message[]` the model sees — cached (each surface node projected once, when first seen; a surface rewrite rebuilds) and frozen (a fresh array per call over shared, deep-frozen messages, so mutating logged history through a projection is unrepresentable). `deriveEventMessage(event)` is the per-node pure function the fold applies — public so external reconstructors and the dev invariant project a log prefix with exactly the same rules and cannot disagree with the cache. The projection rules:
500
580
 
501
581
  - `user/message` → a user message carrying exact `content`; an optional envelope remains log-only display metadata.
502
- - `assistant/message` → an assistant message with the provider and model that produced it plus optional adapter-private replay state. Raw `assistant/chunk` events are replay/UI data and are **skipped** in derivation (the assembled message is authoritative). An **empty-content** `assistant/message` is also skipped — a max-tokens step cut off with no content still records an `assistant/message` to hold its usage, provider, and model, but a content-less assistant turn must not enter the provider transcript.
582
+ - `assistant/message` → an assistant message with the provider and model that produced it plus optional adapter-private replay state. Its embedded compact stream is replay, usage, and UI evidence rather than a second message. An **empty-content** `assistant/message` is also skipped — a max-tokens step cut off with no content still records an `assistant/message` to hold its stream, usage, provider, and model, but a content-less assistant turn must not enter the provider transcript.
503
583
  - `tool/result` → a user message carrying a `tool-result` block.
504
584
  - `user/message` (injected context, i.e. non-`user` source) → a user-role message carrying its `content` verbatim at its chronological position; its typed source names the producer and carries any producer-specific data.
505
585
 
506
- Everything else (`turn/*`, `step/*`, plugin-owned `llm/retry`) is structural and does not project into a message. Token accounting reads per-step `assistant/chunk { type: 'usage' }` records and treats `assistant/message.usage` as the committed-step fallback when no usage chunk exists; failed model-request attempts have no assistant message, so their usage chunk is the durable accounting record. Because this unreleased format intentionally has no compatibility promise, seed/load validation rejects request headers and assistant messages that omit provider/model instead of guessing a route for historical data.
586
+ Everything else (`turn/*`, `step/*`, `assistant/attempt`, plugin-owned `llm/retry`) is structural and does not project into a message. Token accounting expands the embedded stream on each `assistant/message` or `assistant/attempt`, while the message's top-level `usage` remains the committed-message authority when present. A failed model-request attempt therefore retains its provider usage without fabricating an assistant message. Current logical validation rejects request headers and assistant messages that omit provider/model instead of guessing a route; supported historical representations are normalized and validated by their adjacent format edge before a current Session exists.
507
587
 
508
588
  ## Live-session fork API
509
589
 
510
590
  `ctx.sessions.create(id, { seed, meta })` is the low-level replay/fork primitive. For ordinary live-session forks, `SessionStore` exposes one policy API:
511
591
 
512
- - `fork(source, boundary?, childSessionId?)` accepts a live `Session` object or live `SessionId`, selects source events through the inclusive `boundary` seq (default: current last event), requires the selected prefix to end outside an open turn, then creates a live child session with deep-cloned seed events plus child metadata (`parentSession`, `seedLength`, and inherited `cwd`).
592
+ - `fork(source, boundary?, childSessionId?)` accepts a live `Session` object or live `SessionId`, selects source events through the inclusive `SessionSeq` boundary (default: current last event), requires the selected prefix to end outside an open turn, then creates a live child session with deep-cloned seed events, `parentSession`, `isSeeded: true`, the exact `inheritedEventCount`, and inherited `cwd`.
513
593
 
514
594
  An explicit `boundary` lets callers fork from any stable between-turn position, including a previous `turn/end` or a later standalone log-only event, even if the source has newer events or an open current turn. The API rejects a prefix that ends inside an open turn instead of clipping silently. Broader execution-relation sanity stays in the existing `dsh-invariants` plugin and persistence repair path rather than being duplicated in `fork()`. `dsh-subagent-fork-in-process` keeps its completed-prefix clipping because tool-time delegation usually starts while the parent turn is open; ordinary session branching should make the requested boundary explicit.
515
595
 
@@ -541,8 +621,10 @@ interface TurnEndReasonMap {
541
621
  /** At least one step reached its output-token ceiling, even if a plugin continued the turn. */
542
622
  'max-tokens': { kind: 'max-tokens' }
543
623
  /**
544
- * A persistence backend closed a crash-orphaned turn on reload. The loop never
545
- * emits this marker, and the events recorded before the crash remain intact.
624
+ * A crash-orphaned turn was closed after the fact: agent-loop resume appends
625
+ * this closer for a stored log whose last turn never ended, and session-query
626
+ * synthesizes it on cold reads. The loop never emits this marker live, and
627
+ * the events recorded before the crash remain intact.
546
628
  */
547
629
  interrupted: { kind: 'interrupted' }
548
630
  }
@@ -558,9 +640,9 @@ The optional `dsh-session/invariant` companion enforces the relations owned by c
558
640
 
559
641
  ## The end-seed boundary: `session/end-seed`
560
642
 
561
- A seeded session — resume, fork, or replay — appends this log-only event immediately after its constructor seed, as its first live write. Events before it have smaller seq values and came from the seed. It is the durable projection of `firstLiveSeq`: that field answers where this lifecycle's writes start for a consumer holding the object, while the event answers the same question for one holding only stored bytes. The payload is empty, so position and `time` carry the whole meaning, and it produces no message. `Session`'s constructor is the only legitimate writer.
643
+ A fresh fork constructor requires its seed to equal the inherited prefix and appends `session/end-seed { inherited: true }` at the exact durable cut. A restore retains that tagged marker and appends an ordinary `session/end-seed {}` only when its complete stored seed does not already end in a marker. Both forms are log-only and produce no message; `Session`'s constructor is the only legitimate writer.
562
644
 
563
- An explicitly supplied empty seed writes `session/end-seed` at seq 0, which distinguishes an empty resumed session from a fresh one. A seed already ending in `session/end-seed` is not re-marked, so reopening an untouched session does not grow its log per pickup. Locate the LAST `session/end-seed` in stored history rather than assuming one exists at `firstLiveSeq`: after a pickup with no work, the event has a smaller seq than the next lifecycle's `firstLiveSeq`.
645
+ For fork lineage, locate the LAST marker whose payload carries `inherited: true`; v2 decoding requires it exactly when `SessionHeader.isSeeded` is true and derives `inheritedEventCount` from its seq. For lifecycle ownership, locate the last `session/end-seed` of either form. Reopening a seed that already ends in any marker does not append another ordinary marker.
564
646
 
565
647
  It exists because seed history and live work are otherwise byte-identical, which defeats any plugin owning a standalone open/close bracket: an unmatched `compaction/start` reads the same whether the writer crashed mid-compaction or is compacting right now. An opening marker before `session/end-seed` came from the constructor seed and belongs to an ended lifecycle, whatever ended it (a crash, a succeeding process, or a fork out of a still-running parent), so its owner may treat it as dead. That covers only brackets *this* session inherited: a concurrently live session holding an open bracket over the same history has its own boundary elsewhere, so tolerating concurrent writers needs a liveness signal beyond the log. Core writes the boundary and reads nothing from it — a bracket's vocabulary stays with its owning plugin, which is why crash repair closes turn/step/tool boundaries and never `compaction/*`.
566
648
 
@@ -576,7 +658,7 @@ The hook bridges' `hook/invoked` / `hook/result` pairs (from `@deepseek-ai/dsh-h
576
658
 
577
659
  ## Durability contract
578
660
 
579
- What a persistence backend relies on: the durable log persists every event losslessly, **including** `assistant/chunk` `seq` must stay contiguous, so chunks cannot be filtered out of the canonical log. A backend may choose its own storage encoding for an event batch as long as `load` returns the exact appended events (the JSONL backend's default packed chunk rows are such an encoding — 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.events` 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.
661
+ 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.
580
662
 
581
663
  The backends that consume this contract are on [persistence.md](./persistence.md).
582
664
 
@@ -614,7 +696,7 @@ resolveAgent(sessionId: SessionId): Promise<ApiSessionAgentResult>
614
696
  * @param signal - optional caller cancellation for persistence reads.
615
697
  * @returns the current attached state or persisted header and event prefix.
616
698
  */
617
- inspect( sessionId: SessionId, signal?: AbortSignal, ): Promise<{ meta: SessionHeader; events: SessionEvent[] }>
699
+ inspect( sessionId: SessionId, signal?: AbortSignal, ): Promise<SessionInspection>
618
700
 
619
701
  /**
620
702
  * Read all visible Session rows without resuming an Agent.
@@ -663,7 +745,7 @@ inspect( sessionId: SessionId, signal?: AbortSignal, ): Promise<{ meta: SessionH
663
745
  * @param request - path after best-effort Session workspace resolution.
664
746
  * @param signal - caller lifetime; abort terminates the native command.
665
747
  * @returns confirmation after the native opener accepts the path.
666
- * @throws TypertRemoteFailure when the request is invalid, cancelled, or the opener fails.
748
+ * @throws RemoteError when the request is invalid, cancelled, or the opener fails.
667
749
  */
668
750
  @Remote('openWorkspacePath') async openWorkspacePath( request: SessionOpenWorkspacePathRequest, signal: AbortSignal, ): Promise<SessionOpenWorkspacePathValue>
669
751
 
@@ -722,7 +804,8 @@ inspect( sessionId: SessionId, signal?: AbortSignal, ): Promise<{ meta: SessionH
722
804
  * Follow one Session log from its opening or resume cursor.
723
805
  * @param request - durable address and last committed sequence already held by the caller.
724
806
  * @param signal - cancellation owned by the Remote stream carrier.
725
- * @returns a complete opening snapshot followed by gap-free event frames.
807
+ * @returns a complete opening snapshot followed by gap-free durable event
808
+ * frames and optional cursorless assistant-stream frames.
726
809
  */
727
810
  @Remote({ mode: 'stream' }) follow(request: SessionFollowRequest, signal: AbortSignal): AsyncIterable<SessionFollowFrame>
728
811
 
@@ -734,7 +817,7 @@ inspect( sessionId: SessionId, signal?: AbortSignal, ): Promise<{ meta: SessionH
734
817
  @Remote({ mode: 'stream' }) control(signal: AbortSignal): AsyncIterable<SessionControlFrame>
735
818
  ```
736
819
 
737
- Types: [SessionHeader](./persistence.md) · [SessionId](./core.md) · [SessionSearchRequest](./session-query.md)
820
+ Types: [SessionId](./core.md) · [SessionInspection](./persistence.md) · [SessionSearchRequest](./session-query.md)
738
821
 
739
822
  Source: [`packages/api/session-controller/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/session-controller/src/index.ts)
740
823
 
@@ -744,7 +827,7 @@ Source: [`packages/api/session-controller/src/index.ts`](https://github.com/deep
744
827
 
745
828
  In-memory session store (`ctx.sessions`).
746
829
 
747
- Persistence is intentionally not implemented here — persistence plugins subscribe to `session/event` and flush on `session/flush` / dispose.
830
+ 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.
748
831
 
749
832
  ```ts cordis-catalog
750
833
  /**
@@ -867,7 +950,7 @@ list(): Session[]
867
950
  * `SessionStore`'s id policy.
868
951
  * @returns The created live child session.
869
952
  */
870
- fork(source: SessionForkSource, boundary?: number, childSessionId?: SessionId): Session
953
+ fork(source: SessionForkSource, boundary?: SessionSeq, childSessionId?: SessionId): Session
871
954
  ```
872
955
 
873
956
  Types: [CreateSessionOptions](./persistence.md) · [PrepareSessionOptions](./persistence.md) · [SessionId](./core.md)