better-dsh 0.2.3-e → 0.2.3-g

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 (270) hide show
  1. package/docs/50_test-reports/2026-09-13-preact-ui-shell/345/256/236/346/265/213/346/212/245/345/221/212.md +1 -1
  2. package/docs/50_test-reports/2026-09-14-4999-skill/346/270/205/345/215/225/344/270/216lsp-gate/345/256/236/346/265/213/346/212/245/345/221/212.md +54 -0
  3. package/docs/specs/agent/spec.md +54 -0
  4. package/docs/specs/ast/spec.md +34 -0
  5. package/docs/specs/compaction-recall/spec.md +46 -0
  6. package/docs/specs/ctx/spec.md +107 -0
  7. package/docs/specs/dsh/spec.md +47 -0
  8. package/docs/specs/dvc/spec.md +87 -0
  9. package/docs/specs/escalation-guidance/spec.md +44 -0
  10. package/docs/specs/fs-scheme-resolution/spec.md +37 -0
  11. package/docs/specs/hash-edit/spec.md +41 -0
  12. package/docs/specs/http-read/spec.md +73 -0
  13. package/docs/specs/kernel-provisioning/spec.md +53 -0
  14. package/docs/specs/lsp/spec.md +121 -0
  15. package/docs/specs/mobile-layout/spec.md +108 -0
  16. package/docs/specs/model-failover/spec.md +20 -0
  17. package/docs/specs/preact-ui-shell/spec.md +22 -0
  18. package/docs/specs/repl-dispatch-resilience/spec.md +21 -0
  19. package/docs/specs/skill/spec.md +58 -0
  20. package/docs/specs/tool-surface/spec.md +222 -0
  21. package/docs/specs/url-schema/spec.md +148 -0
  22. package/docs/specs/web-trust-fence/spec.md +43 -0
  23. package/dsh-docs/AGENTS.md +75 -0
  24. package/dsh-docs/agent-lifecycle.md +84 -0
  25. package/dsh-docs/agent-lifecycle.zh.md +86 -0
  26. package/dsh-docs/api-gateway.md +164 -0
  27. package/dsh-docs/api-gateway.zh.md +164 -0
  28. package/dsh-docs/architecture.md +150 -0
  29. package/dsh-docs/architecture.zh.md +154 -0
  30. package/dsh-docs/capability-seams.md +543 -0
  31. package/dsh-docs/capability-seams.zh.md +545 -0
  32. package/dsh-docs/config-catalog.md +3473 -0
  33. package/dsh-docs/config-catalog.zh.md +3474 -0
  34. package/dsh-docs/cookbook/adding-a-package.md +117 -0
  35. package/dsh-docs/cookbook/adding-a-package.zh.md +119 -0
  36. package/dsh-docs/cookbook/adding-a-remote-api.md +197 -0
  37. package/dsh-docs/cookbook/adding-a-remote-api.zh.md +197 -0
  38. package/dsh-docs/cookbook/adding-a-settings-card.md +102 -0
  39. package/dsh-docs/cookbook/adding-a-settings-card.zh.md +102 -0
  40. package/dsh-docs/cookbook/adding-a-tool.md +101 -0
  41. package/dsh-docs/cookbook/adding-a-tool.zh.md +103 -0
  42. package/dsh-docs/cookbook/adding-a-vendored-package.md +59 -0
  43. package/dsh-docs/cookbook/adding-a-vendored-package.zh.md +59 -0
  44. package/dsh-docs/cookbook/adding-an-llm-adapter.md +43 -0
  45. package/dsh-docs/cookbook/adding-an-llm-adapter.zh.md +43 -0
  46. package/dsh-docs/cookbook/extension-cookbook.md +132 -0
  47. package/dsh-docs/cookbook/extension-cookbook.zh.md +136 -0
  48. package/dsh-docs/cookbook/maintaining-dsh-code-review.md +64 -0
  49. package/dsh-docs/cookbook/maintaining-dsh-code-review.zh.md +64 -0
  50. package/dsh-docs/cookbook/responding-to-pr-review-on-a-stack.md +32 -0
  51. package/dsh-docs/cookbook/responding-to-pr-review-on-a-stack.zh.md +32 -0
  52. package/dsh-docs/cordis-api/context.md +364 -0
  53. package/dsh-docs/cordis-api/context.zh.md +366 -0
  54. package/dsh-docs/cordis-api/events.md +207 -0
  55. package/dsh-docs/cordis-api/events.zh.md +209 -0
  56. package/dsh-docs/cordis-api/fiber.md +375 -0
  57. package/dsh-docs/cordis-api/fiber.zh.md +377 -0
  58. package/dsh-docs/cordis-api/inherited.md +39 -0
  59. package/dsh-docs/cordis-api/registry.md +152 -0
  60. package/dsh-docs/cordis-api/registry.zh.md +154 -0
  61. package/dsh-docs/cordis-api/service.md +102 -0
  62. package/dsh-docs/cordis-api/service.zh.md +104 -0
  63. package/dsh-docs/cordis-primer.md +45 -0
  64. package/dsh-docs/cordis-primer.zh.md +51 -0
  65. package/dsh-docs/cordis-tutorial/01-first-plugin.md +95 -0
  66. package/dsh-docs/cordis-tutorial/01-first-plugin.zh.md +95 -0
  67. package/dsh-docs/cordis-tutorial/02-lifecycle-and-effects.md +98 -0
  68. package/dsh-docs/cordis-tutorial/02-lifecycle-and-effects.zh.md +98 -0
  69. package/dsh-docs/cordis-tutorial/03-services.md +98 -0
  70. package/dsh-docs/cordis-tutorial/03-services.zh.md +98 -0
  71. package/dsh-docs/cordis-tutorial/04-events.md +144 -0
  72. package/dsh-docs/cordis-tutorial/04-events.zh.md +144 -0
  73. package/dsh-docs/cordis-tutorial/05-config.md +84 -0
  74. package/dsh-docs/cordis-tutorial/05-config.zh.md +84 -0
  75. package/dsh-docs/cordis-tutorial/06-composition-and-hmr.md +113 -0
  76. package/dsh-docs/cordis-tutorial/06-composition-and-hmr.zh.md +113 -0
  77. package/dsh-docs/cordis-tutorial/07-into-the-harness.md +108 -0
  78. package/dsh-docs/cordis-tutorial/07-into-the-harness.zh.md +108 -0
  79. package/dsh-docs/cordis-tutorial/index.md +60 -0
  80. package/dsh-docs/cordis-tutorial/index.zh.md +62 -0
  81. package/dsh-docs/deepseek-llm-api-wire-extensions.md +163 -0
  82. package/dsh-docs/deepseek-llm-api-wire-extensions.zh.md +163 -0
  83. package/dsh-docs/defensive-patterns.md +33 -0
  84. package/dsh-docs/defensive-patterns.zh.md +35 -0
  85. package/dsh-docs/development.md +167 -0
  86. package/dsh-docs/development.zh.md +173 -0
  87. package/dsh-docs/event-producer-consumer.md +86 -0
  88. package/dsh-docs/event-producer-consumer.zh.md +88 -0
  89. package/dsh-docs/glossary.md +45 -0
  90. package/dsh-docs/glossary.zh.md +45 -0
  91. package/dsh-docs/graph-atlas.md +22 -0
  92. package/dsh-docs/graph-atlas.zh.md +24 -0
  93. package/dsh-docs/i18n/README.md +60 -0
  94. package/dsh-docs/i18n/README.zh.md +62 -0
  95. package/dsh-docs/i18n/style-samples.md +87 -0
  96. package/dsh-docs/i18n/terminology.md +214 -0
  97. package/dsh-docs/i18n/translation-prompt.md +263 -0
  98. package/dsh-docs/i18n/translation-rules.md +69 -0
  99. package/dsh-docs/i18n/translation-rules.zh.md +69 -0
  100. package/dsh-docs/module-graph.md +1411 -0
  101. package/dsh-docs/module-graph.zh.md +1413 -0
  102. package/dsh-docs/persistence-catalog.md +1075 -0
  103. package/dsh-docs/persistence-catalog.zh.md +1077 -0
  104. package/dsh-docs/postmortem/0001-acp-default-export-drops-inject.md +113 -0
  105. package/dsh-docs/postmortem/0001-acp-default-export-drops-inject.zh.md +113 -0
  106. package/dsh-docs/postmortem/0002-js-expression-disabled-filesystem-tools.md +47 -0
  107. package/dsh-docs/postmortem/0002-js-expression-disabled-filesystem-tools.zh.md +47 -0
  108. package/dsh-docs/postmortem/0003-web-agent-gui-feedback-loop.md +53 -0
  109. package/dsh-docs/postmortem/0003-web-agent-gui-feedback-loop.zh.md +53 -0
  110. package/dsh-docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md +55 -0
  111. package/dsh-docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.zh.md +55 -0
  112. package/dsh-docs/postmortem/README.md +18 -0
  113. package/dsh-docs/postmortem/README.zh.md +18 -0
  114. package/dsh-docs/rescope.md +53 -0
  115. package/dsh-docs/rescope.zh.md +53 -0
  116. package/dsh-docs/subsystems/README.md +61 -0
  117. package/dsh-docs/subsystems/README.zh.md +61 -0
  118. package/dsh-docs/subsystems/agent-team.md +207 -0
  119. package/dsh-docs/subsystems/agent-team.zh.md +207 -0
  120. package/dsh-docs/subsystems/approval.md +170 -0
  121. package/dsh-docs/subsystems/approval.zh.md +170 -0
  122. package/dsh-docs/subsystems/attachment.md +351 -0
  123. package/dsh-docs/subsystems/attachment.zh.md +351 -0
  124. package/dsh-docs/subsystems/client-modules.md +168 -0
  125. package/dsh-docs/subsystems/client-modules.zh.md +168 -0
  126. package/dsh-docs/subsystems/code-runtime.md +195 -0
  127. package/dsh-docs/subsystems/code-runtime.zh.md +195 -0
  128. package/dsh-docs/subsystems/commands.md +219 -0
  129. package/dsh-docs/subsystems/commands.zh.md +219 -0
  130. package/dsh-docs/subsystems/compaction.md +238 -0
  131. package/dsh-docs/subsystems/compaction.zh.md +238 -0
  132. package/dsh-docs/subsystems/conversation.md +258 -0
  133. package/dsh-docs/subsystems/conversation.zh.md +258 -0
  134. package/dsh-docs/subsystems/core.md +1209 -0
  135. package/dsh-docs/subsystems/core.zh.md +1219 -0
  136. package/dsh-docs/subsystems/credentials.md +329 -0
  137. package/dsh-docs/subsystems/credentials.zh.md +329 -0
  138. package/dsh-docs/subsystems/extensions.md +382 -0
  139. package/dsh-docs/subsystems/extensions.zh.md +382 -0
  140. package/dsh-docs/subsystems/feedback.md +266 -0
  141. package/dsh-docs/subsystems/feedback.zh.md +266 -0
  142. package/dsh-docs/subsystems/filesystem.md +505 -0
  143. package/dsh-docs/subsystems/filesystem.zh.md +505 -0
  144. package/dsh-docs/subsystems/goal.md +277 -0
  145. package/dsh-docs/subsystems/goal.zh.md +277 -0
  146. package/dsh-docs/subsystems/invariants.md +88 -0
  147. package/dsh-docs/subsystems/invariants.zh.md +88 -0
  148. package/dsh-docs/subsystems/jobs.md +290 -0
  149. package/dsh-docs/subsystems/jobs.zh.md +290 -0
  150. package/dsh-docs/subsystems/llm-streaming.md +1080 -0
  151. package/dsh-docs/subsystems/llm-streaming.zh.md +1086 -0
  152. package/dsh-docs/subsystems/lsp.md +202 -0
  153. package/dsh-docs/subsystems/lsp.zh.md +202 -0
  154. package/dsh-docs/subsystems/permission-presets.md +131 -0
  155. package/dsh-docs/subsystems/permission-presets.zh.md +131 -0
  156. package/dsh-docs/subsystems/persistence.md +395 -0
  157. package/dsh-docs/subsystems/persistence.zh.md +395 -0
  158. package/dsh-docs/subsystems/plan.md +87 -0
  159. package/dsh-docs/subsystems/plan.zh.md +87 -0
  160. package/dsh-docs/subsystems/sandbox.md +220 -0
  161. package/dsh-docs/subsystems/sandbox.zh.md +220 -0
  162. package/dsh-docs/subsystems/schedule.md +192 -0
  163. package/dsh-docs/subsystems/schedule.zh.md +192 -0
  164. package/dsh-docs/subsystems/scope.md +59 -0
  165. package/dsh-docs/subsystems/scope.zh.md +59 -0
  166. package/dsh-docs/subsystems/session-projection.md +354 -0
  167. package/dsh-docs/subsystems/session-projection.zh.md +354 -0
  168. package/dsh-docs/subsystems/session-query.md +509 -0
  169. package/dsh-docs/subsystems/session-query.zh.md +509 -0
  170. package/dsh-docs/subsystems/session-reference.md +219 -0
  171. package/dsh-docs/subsystems/session-reference.zh.md +219 -0
  172. package/dsh-docs/subsystems/session-telemetry.md +194 -0
  173. package/dsh-docs/subsystems/session-telemetry.zh.md +194 -0
  174. package/dsh-docs/subsystems/session-title.md +204 -0
  175. package/dsh-docs/subsystems/session-title.zh.md +204 -0
  176. package/dsh-docs/subsystems/session.md +1155 -0
  177. package/dsh-docs/subsystems/session.zh.md +1159 -0
  178. package/dsh-docs/subsystems/settings.md +405 -0
  179. package/dsh-docs/subsystems/settings.zh.md +405 -0
  180. package/dsh-docs/subsystems/shell.md +303 -0
  181. package/dsh-docs/subsystems/shell.zh.md +303 -0
  182. package/dsh-docs/subsystems/skills.md +354 -0
  183. package/dsh-docs/subsystems/skills.zh.md +354 -0
  184. package/dsh-docs/subsystems/slots.md +175 -0
  185. package/dsh-docs/subsystems/slots.zh.md +175 -0
  186. package/dsh-docs/subsystems/spill.md +117 -0
  187. package/dsh-docs/subsystems/spill.zh.md +117 -0
  188. package/dsh-docs/subsystems/storage.md +260 -0
  189. package/dsh-docs/subsystems/storage.zh.md +260 -0
  190. package/dsh-docs/subsystems/subagent.md +766 -0
  191. package/dsh-docs/subsystems/subagent.zh.md +770 -0
  192. package/dsh-docs/subsystems/subprocess.md +324 -0
  193. package/dsh-docs/subsystems/subprocess.zh.md +324 -0
  194. package/dsh-docs/subsystems/system-prompt.md +220 -0
  195. package/dsh-docs/subsystems/system-prompt.zh.md +220 -0
  196. package/dsh-docs/subsystems/terminal.md +184 -0
  197. package/dsh-docs/subsystems/terminal.zh.md +184 -0
  198. package/dsh-docs/subsystems/todo.md +32 -0
  199. package/dsh-docs/subsystems/todo.zh.md +32 -0
  200. package/dsh-docs/subsystems/token-meter.md +105 -0
  201. package/dsh-docs/subsystems/token-meter.zh.md +105 -0
  202. package/dsh-docs/subsystems/tools.md +720 -0
  203. package/dsh-docs/subsystems/tools.zh.md +720 -0
  204. package/dsh-docs/subsystems/typert.md +343 -0
  205. package/dsh-docs/subsystems/typert.zh.md +343 -0
  206. package/dsh-docs/subsystems/user-questions.md +178 -0
  207. package/dsh-docs/subsystems/user-questions.zh.md +178 -0
  208. package/dsh-docs/subsystems/web-client.md +95 -0
  209. package/dsh-docs/subsystems/web-client.zh.md +95 -0
  210. package/dsh-docs/subsystems/web-server.md +154 -0
  211. package/dsh-docs/subsystems/web-server.zh.md +154 -0
  212. package/dsh-docs/subsystems/web.md +206 -0
  213. package/dsh-docs/subsystems/web.zh.md +206 -0
  214. package/dsh-docs/subsystems/webhook.md +70 -0
  215. package/dsh-docs/subsystems/webhook.zh.md +70 -0
  216. package/dsh-docs/subsystems/workflow.md +278 -0
  217. package/dsh-docs/subsystems/workflow.zh.md +278 -0
  218. package/dsh-docs/subsystems/workspace.md +321 -0
  219. package/dsh-docs/subsystems/workspace.zh.md +321 -0
  220. package/dsh-docs/testing.md +54 -0
  221. package/dsh-docs/testing.zh.md +54 -0
  222. package/dsh-docs/tool-catalog.md +2225 -0
  223. package/dsh-docs/tool-catalog.zh.md +2233 -0
  224. package/dsh-docs/tool-execution-pipeline.md +62 -0
  225. package/dsh-docs/tool-execution-pipeline.zh.md +64 -0
  226. package/dsh-docs/user/develop/basic/config.md +106 -0
  227. package/dsh-docs/user/develop/basic/config.zh.md +106 -0
  228. package/dsh-docs/user/develop/basic/index.md +144 -0
  229. package/dsh-docs/user/develop/basic/index.zh.md +144 -0
  230. package/dsh-docs/user/develop/basic/publish.md +183 -0
  231. package/dsh-docs/user/develop/basic/publish.zh.md +183 -0
  232. package/dsh-docs/user/develop/basic/tool.md +52 -0
  233. package/dsh-docs/user/develop/basic/tool.zh.md +52 -0
  234. package/dsh-docs/user/develop/framework/events.md +143 -0
  235. package/dsh-docs/user/develop/framework/events.zh.md +143 -0
  236. package/dsh-docs/user/develop/framework/index.md +137 -0
  237. package/dsh-docs/user/develop/framework/index.zh.md +137 -0
  238. package/dsh-docs/user/develop/framework/service.md +148 -0
  239. package/dsh-docs/user/develop/framework/service.zh.md +150 -0
  240. package/dsh-docs/user/develop/practice/dynamic-cordis.md +15 -0
  241. package/dsh-docs/user/develop/practice/dynamic-cordis.zh.md +15 -0
  242. package/dsh-docs/user/develop/practice/index.md +155 -0
  243. package/dsh-docs/user/develop/practice/index.zh.md +155 -0
  244. package/dsh-docs/user/develop/practice/llm-adapter.md +189 -0
  245. package/dsh-docs/user/develop/practice/llm-adapter.zh.md +189 -0
  246. package/dsh-docs/user/guide/github-review.md +102 -0
  247. package/dsh-docs/user/guide/github-review.zh.md +102 -0
  248. package/dsh-docs/user/guide/index.md +30 -0
  249. package/dsh-docs/user/guide/index.zh.md +30 -0
  250. package/dsh-docs/user/guide/mcp-memory.md +101 -0
  251. package/dsh-docs/user/guide/mcp-memory.zh.md +101 -0
  252. package/dsh-docs/user/guide/network-proxy.md +85 -0
  253. package/dsh-docs/user/guide/network-proxy.zh.md +85 -0
  254. package/dsh-docs/user/guide/providers.md +190 -0
  255. package/dsh-docs/user/guide/providers.zh.md +190 -0
  256. package/dsh-docs/user/guide/python-sdk.md +150 -0
  257. package/dsh-docs/user/guide/python-sdk.zh.md +150 -0
  258. package/dsh-docs/user/guide/schedule.md +21 -0
  259. package/dsh-docs/user/guide/schedule.zh.md +21 -0
  260. package/dsh-docs/user/index.md +11 -0
  261. package/dsh-docs/user/index.zh.md +11 -0
  262. package/dsh-docs/web-styling.md +29 -0
  263. package/dsh-docs/web-styling.zh.md +29 -0
  264. package/lib/client/index.js +268 -38
  265. package/lib/fs-aware/sandbox-plugin.js +1 -1
  266. package/lib/index.js +1112 -1261
  267. package/lib/lsp-server-registry-B8DNonhS.js +3 -0
  268. package/lib/lsp-server-registry-BexQagaK.js +943 -0
  269. package/lib/{wrap-DC8O3SYz.js → wrap-JFjcWwZf.js} +42 -16
  270. package/package.json +2 -1
@@ -0,0 +1,1155 @@
1
+ # Sessions
2
+
3
+ English | [中文](session.zh.md)
4
+
5
+ The in-memory, event-sourced model of [dsh-session](../../packages/core/session). A `Session` is an **append-only log** of typed `SessionEvent`s — the single source of truth for an agent's whole interaction history. The LLM message history is *derived* from the log, never stored separately; replay is re-derivation from the same events. How the log is made **durable** (the persistence seam, backends, crash recovery) is the sibling concern on [persistence.md](persistence.md).
6
+
7
+ Source: [`packages/core/session/src/types.ts`](../../packages/core/session/src/types.ts)
8
+
9
+ ## `SessionEventMap` — the event vocabulary
10
+
11
+ The append-only event types. Merge-extensible: a plugin declares extra event types via declaration merging — e.g. the [compaction seam](compaction.md) adds `compaction/start` / `compaction/summary` / `compaction/end`, and `@deepseek-ai/dsh-hook-protocol` adds log-only `hook/invoked` / `hook/result` records for a hook bridge. Like `compaction/*`, these are NOT `SurfaceEventType`s (no `surfaceOp`). The generated [persistence log event catalog](../persistence-catalog.md) enumerates every member — core and merged — with its payload, surface badge, and declaration site.
12
+
13
+ ```ts type-equiv
14
+ /** A user-role specialization of the one shared message representation. */
15
+ interface UserMessage extends Message {
16
+ readonly role: 'user'
17
+ }
18
+ ```
19
+
20
+ ```ts type-equiv
21
+ /**
22
+ * The merge-extensible, append-only source of truth for an agent interaction.
23
+ * Message history is derived from this log. Every event is lossless JSON and
24
+ * sequence numbers stay contiguous. Assistant attempt events embed their exact
25
+ * compact raw streams so persistence stores one durable settlement per attempt.
26
+ */
27
+ interface SessionEventMap {
28
+ /**
29
+ * Opens turn `turn` before the loop claims queued input or runs pre-step.
30
+ * Rejection, empty input, cancellation, or failure may close it with no
31
+ * step; otherwise the following identified `user/message` event or batch
32
+ * records the messages entering the step.
33
+ */
34
+ 'turn/start': { turn: number }
35
+ /**
36
+ * Closes turn `turn` with the {@link TurnEndReason} that ended it. A turn
37
+ * with no entered step has no `step/start` or `step/end`. The loop does not await a
38
+ * flush at turn boundaries: `dsh-session-checkpoint-policy` owns the
39
+ * per-request durability checkpoint, and consumers that read storage after
40
+ * `whenIdle()` flush themselves. Success commits the turn; rejection is
41
+ * reported live and does not prevent later work.
42
+ */
43
+ 'turn/end': { turn: number; reason: TurnEndReason }
44
+ /** Opens step `step` of turn `turn` — one model call plus the tool executions it requested. */
45
+ 'step/start': { turn: number; step: number }
46
+ /** Closes step `step` of turn `turn`. */
47
+ 'step/end': { turn: number; step: number }
48
+ /**
49
+ * A user-role message on the model-visible surface: a direct human prompt
50
+ * (the queued message claimed for this turn), a synthetic `agent.inject()`
51
+ * context (file-change notices, subdir AGENTS.md, skill content, cron
52
+ * notifications, …), or an entered goal continuation round. All three
53
+ * project their `content` verbatim; `source` tells them apart.
54
+ */
55
+ 'user/message': UserMessage
56
+ /**
57
+ * Assembled assistant message for one step (derived history uses this).
58
+ * Carries the step's `usage` when the adapter reported token accounting, so
59
+ * the model output and its accounting travel together (there is no separate
60
+ * usage record). `usage` is absent when the adapter reported none. A turn
61
+ * cancelled mid-stream finalizes its delivered text/reasoning prefix as this
62
+ * event with `interrupted: true`; undispatched tool calls are absent. The
63
+ * marker distinguishes that prefix without re-deriving interruption from turn
64
+ * boundaries. An aborted turn with no such event streamed no visible content.
65
+ */
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[] }
81
+ /**
82
+ * The model requested one tool invocation: `name` with the raw `arguments`
83
+ * JSON string exactly as the model produced it (unparsed). `callId` pairs the
84
+ * call with its `tool/result`.
85
+ */
86
+ 'tool/call': { turn: number; step: number; callId: ToolCallId; name: string; arguments: string }
87
+ /**
88
+ * A completed tool call's model-facing result, optional internal failure
89
+ * identity, and optional tool-private `meta` presentation payload. `meta` is
90
+ * opaque to the core (the producing tool owns its shape and reads it back in
91
+ * `presentResult`) but MUST be JSON-serializable: `Session.append`
92
+ * runtime-validates all event data with `isJsonValue`, so a non-serializable
93
+ * `meta` is rejected at the source, and the durable log reproduces the
94
+ * identical card on replay. Absent
95
+ * unless the tool attaches one (e.g. `dsh-tool-fs` carries its result-time
96
+ * contextual diff here).
97
+ */
98
+ 'tool/result': {
99
+ turn: number
100
+ step: number
101
+ message: ToolResultMessage
102
+ error?: { name: string; code: string }
103
+ meta?: JsonValue
104
+ }
105
+ /**
106
+ * Full header for the next request, appended inside its step before dispatch.
107
+ * It is log-only; the latest snapshot reconstructs the request header.
108
+ */
109
+ 'request/header': {
110
+ header: EpochHeader
111
+ reason: RequestHeaderReason
112
+ /** A changed header also begins a distinct model-message series. */
113
+ startsSeries?: true
114
+ }
115
+ /**
116
+ * Route metadata for the next request, logged only when the route or capacity
117
+ * changes. It does not participate in request reconstruction or header equality.
118
+ */
119
+ 'request/context': RequestContext
120
+ /**
121
+ * Marks the end of a constructor seed. Events before it have smaller seq
122
+ * values and came from the seed (resume, fork, or replay); this lifecycle
123
+ * produced none of them. This log-only event is the durable projection of
124
+ * {@link Session.firstLiveSeq}.
125
+ *
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.
130
+ *
131
+ * `Session`'s constructor is the only legitimate writer. The invariant
132
+ * companion deliberately constrains nothing here, so a plugin appending one
133
+ * would silently classify every live bracket before it as seed history.
134
+ *
135
+ * An owner of a standalone open/close bracket (`compaction/start` …
136
+ * `compaction/end`) reads it because seed history and live work are otherwise
137
+ * byte-identical: an unmatched opening marker before this event belongs to
138
+ * an ended lifecycle, whatever ended it. NOT a liveness signal about other
139
+ * writers — a concurrently live session holds its own boundary elsewhere,
140
+ * so tolerating concurrent writers needs a signal beyond the log.
141
+ */
142
+ 'session/end-seed': { inherited?: true }
143
+ }
144
+ ```
145
+
146
+ `UserMessage` is the identified, frozen user-role value shared by ordinary prompts, injected context, steering, and live inbox events. Event wrappers add only event-local position or outcome facts; the loop adds only driver-owned routing state while an item remains pending.
147
+
148
+ <a id="the-request-header-event-requestheader"></a>
149
+
150
+ ### The request header event: `request/header`
151
+
152
+ The request envelope — the `EpochHeader` (call config + markers for adapter-supplied defaults + rendered system prompt + assembled tool schemas) — is logged session state, so every conversation request is a pure function of the log (the reconstructability Agent Note). A full `request/header` snapshot with reason `'initial'` or `'resume'` records each loop-instance boundary; a changed request appends a snapshot with reason `'change'`; and an unchanged envelope beginning an explicitly declared message series or following a surface replacement appends a snapshot with reason `'series'`. A changed snapshot carries `startsSeries: true` when that request also begins a series. Ordinary append-only later Turns, further Steps, and retries in the same model-message series inherit the latest snapshot. `foldRequestHeader(events)` reconstructs the header by selecting the latest snapshot. The event is not a `SurfaceEventType`: it produces no LLM message.
153
+
154
+ ```ts type-equiv
155
+ /**
156
+ * Logged request state outside derived history: call config, system prompt, and
157
+ * tools. The latest full `request/header` snapshot reconstructs it; canonical
158
+ * empty optional fields are absent.
159
+ */
160
+ interface EpochHeader {
161
+ /** The conversation's call configuration (provider, model, reasoning effort, and sampling scalars). */
162
+ config: LlmCallConfig
163
+ /** Effective config fields materialized from the exact adapter rather than proposed by a caller. */
164
+ adapterDefaults?: LlmCallConfigAdapterDefaults
165
+ /** Rendered system prompt text; absent for a system-less request. */
166
+ system?: string
167
+ /** Assembled tool schemas; absent for a tool-less request. */
168
+ tools?: ToolSchema[]
169
+ }
170
+ ```
171
+
172
+ Canonical form represents an empty system prompt or tool list as an absent field, matching how requests are built. Legacy v0 logs containing the legacy `request/header-delta` event or its full-snapshot `fallback` reason are rejected at seed, append, and persistence-load boundaries rather than replayed incompletely.
173
+
174
+ ### The route capacity event: `request/context`
175
+
176
+ The context metadata of the route a request resolved to is separate logged state, appended beside `request/header` inside the same step and only when the provider, model, or capacity differs from the previous record. It stays outside `EpochHeader` because that type is the reconstruction contract compared field-wise by `headerEquals`: capacity describes a route, not a request input, so folding it in would let a capacity change register as a request-envelope `change` and would pull adapter metadata into the loop's reconstruction invariant. Like `request/header`, it is not a `SurfaceEventType` and produces no LLM message. `session.requestContext()` folds the latest record incrementally. A route whose adapter advertises no capacity is recorded with `contextWindow` absent, so the new record clears an older route's capacity.
177
+
178
+ ```ts type-equiv
179
+ /** Registration-bound metadata for one resolved model route. */
180
+ interface RequestContext {
181
+ /** Registered provider route the metadata belongs to. */
182
+ provider: string
183
+ /** Provider-owned model id the metadata belongs to. */
184
+ model: string
185
+ /** Maximum combined request and response context in tokens, when advertised. */
186
+ contextWindow?: number
187
+ }
188
+ ```
189
+
190
+ ## `SessionEvent<T>` — one log entry
191
+
192
+ 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.
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)` 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.
215
+
216
+ ```ts type-equiv
217
+ /**
218
+ * One immutable entry in the session log.
219
+ *
220
+ * A proper discriminated union over `type` (not independent `type`/`data`
221
+ * unions), so `switch (event.type)` narrows `event.data` without casts.
222
+ *
223
+ * The {@link sourceEventSeqs} and {@link surfaceOp} fields are conditional:
224
+ * they only exist on {@link SurfaceEventType} variants (`user/message`,
225
+ * `assistant/message`, `tool/result`).
226
+ * Non-surface events (boundary markers, attempts, errors) never carry
227
+ * surface metadata — the compiler enforces this at `Session.append()`
228
+ * call sites.
229
+ */
230
+ type SessionEvent<T extends SessionEventType = SessionEventType> = {
231
+ [K in SessionEventType]: {
232
+ type: K
233
+ /** Monotonic sequence number within the session. */
234
+ seq: SessionSeq
235
+ /** Unix epoch milliseconds. */
236
+ time: number
237
+ data: SessionEventMap[K]
238
+ /**
239
+ * Marks an event a reader may safely skip when it does not recognize
240
+ * `type`. Absent means required: a reader meeting an unrecognized type
241
+ * without this marker MUST refuse to reconstruct the session instead of
242
+ * silently dropping the event, because an unrecognized required event may
243
+ * change how the rest of the log is interpreted. A writer sets `true` only
244
+ * on purely informational records whose loss cannot affect reconstruction;
245
+ * defaulting to required means a forgotten marker over-refuses (an
246
+ * inconvenience) rather than silently resuming a gutted session.
247
+ */
248
+ ignorable?: true
249
+ } & (K extends SurfaceEventType ? {
250
+ /**
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.
254
+ */
255
+ sourceEventSeqs?: SessionSeq[]
256
+ /** How this event entered the surface; absent for non-surface events. */
257
+ surfaceOp?: SurfaceOp
258
+ } : object)
259
+ }[T]
260
+ ```
261
+
262
+ `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`.
263
+
264
+ 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.
265
+
266
+ ## Surface types
267
+
268
+ The three message-producing types (`SurfaceEventType` — `user/message`, `assistant/message`, `tool/result`) carry surface metadata declaring how they join the ordered derived surface. See the [session surface Agent Note](../../.agents/notes/implemented/architecture/2026-06-18-session-surface.md).
269
+
270
+ ### `SurfaceEventType` — the message-producing subset of event types
271
+
272
+ ```ts type-equiv
273
+ /**
274
+ * The subset of {@link SessionEventType} values whose events produce LLM
275
+ * messages and are eligible to appear on the ordered surface. Only these
276
+ * event types may carry {@link SurfaceOp}; user and tool events may also cite
277
+ * earlier sources through {@link SessionEvent.sourceEventSeqs}.
278
+ */
279
+ type SurfaceEventType =
280
+ | 'user/message'
281
+ | 'assistant/message'
282
+ | 'tool/result'
283
+ ```
284
+
285
+ ### `SurfaceOp` — how an event entered the surface
286
+
287
+ ```ts type-equiv
288
+ /**
289
+ * How a session event entered the ordered surface. Only valid on
290
+ * {@link SurfaceEventType} events.
291
+ *
292
+ * - `'append'`: added to the tail — normal path for user/assistant/tool
293
+ * messages.
294
+ * - `{ op: 'replace', start, end }`: replaces surface nodes from `start`
295
+ * (inclusive) through `end` (inclusive) with this node. Both must exist as
296
+ * surface nodes in the current surface. `start === end` replaces a single
297
+ * node. The node's {@link SessionEvent.sourceEventSeqs} must include every
298
+ * shadowed surface node. Used by compaction; any surface-replacing producer
299
+ * may use it.
300
+ */
301
+ type SurfaceOp =
302
+ | 'append'
303
+ | { op: 'replace'; start: SessionSeq; end: SessionSeq }
304
+ ```
305
+
306
+ `'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.
307
+
308
+ ### `SurfaceIntent` — the parameter to `session.append()`
309
+
310
+ ```ts type-equiv
311
+ /**
312
+ * Surface placement and cited source-event seqs for {@link Session.append}. Required on
313
+ * message-producing events and forbidden on log-only events.
314
+ */
315
+ type SurfaceIntent<T extends SurfaceEventType = SurfaceEventType> = {
316
+ surfaceOp: SurfaceOp
317
+ } & (T extends 'assistant/message' ? {
318
+ /** V2 Assistant messages embed their provider stream instead of citing source events. */
319
+ sourceEventSeqs?: never
320
+ } : {
321
+ /** Complete non-empty set of known earlier source-event seqs. */
322
+ sourceEventSeqs?: SessionSeq[]
323
+ })
324
+ ```
325
+
326
+ 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](../../packages/core/session/README.md)). Non-surface types reject it at compile time.
327
+
328
+ `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.
329
+
330
+ ### `SessionSurface` — the live readonly surface projection
331
+
332
+ `Session.surface` returns the session's stable `SessionSurface` view. The same incremental manager validates append candidates before commit and advances this projection from committed events; callers can observe membership and replacement generation but cannot invoke validation.
333
+
334
+ `SurfaceManager(log, baseSeq?)` can instead fold a contiguous loaded window whose first event has the absolute sequence `baseSeq`. Every event remains contiguous in that absolute sequence space, and a replacement that crosses the window head fails because its declared range is absent.
335
+
336
+ ```ts type-equiv
337
+ /** Readonly live projection of the message-producing session events. */
338
+ interface SessionSurface {
339
+ /** Current surface event sequences in model-visible order. */
340
+ readonly nodes: readonly SessionSeq[]
341
+ /** Monotonic count of committed positional replacements. */
342
+ readonly replaceGeneration: number
343
+ }
344
+ ```
345
+
346
+ ### `SurfaceFoldReplacement` and `SurfaceFoldResult` — a complete surface replay
347
+
348
+ `foldSurface(events)` returns detached current event sequences together with the actual sequences shadowed by each declared replacement range. The live manager uses the same transitions without retaining replacement history. Its `replaceGeneration` increments for each committed replacement so incremental consumers can distinguish pure tail growth from a rewrite.
349
+
350
+ ```ts type-equiv
351
+ /** One replacement operation observed while folding a session surface. */
352
+ interface SurfaceFoldReplacement {
353
+ /** Seq of the event that replaced the prior surface range. */
354
+ seq: SessionSeq
355
+ /** Declared inclusive start seq of the replaced surface range. */
356
+ start: SessionSeq
357
+ /** Declared inclusive end seq of the replaced surface range. */
358
+ end: SessionSeq
359
+ /** Actual surface entries removed by the operation, in surface order. */
360
+ shadowedSeqs: SessionSeq[]
361
+ }
362
+ ```
363
+
364
+ ```ts type-equiv
365
+ /** Complete result of replaying the surface operations in a session log. */
366
+ interface SurfaceFoldResult {
367
+ /** Current surface event sequences in model-visible order. */
368
+ nodes: SessionSeq[]
369
+ /** Replacement operations in event order. */
370
+ replacements: SurfaceFoldReplacement[]
371
+ }
372
+ ```
373
+
374
+ ## `Session` public API
375
+
376
+ The body-stripped declaration keeps the plain class's detached factory, state accessors, append method, and history projections synchronized with source. Store operations remain in the generated [`ctx.sessions` section](#ctxsessions--sessionstore).
377
+
378
+ ```ts public-api
379
+ /**
380
+ * An event-sourced session: an append-only log of {@link SessionEvent}s.
381
+ *
382
+ * Plain class (not a Service) — create live instances via
383
+ * `ctx.sessions.create()` and detached instances via {@link create}.
384
+ * Seeding with an existing event log replays/forks a session.
385
+ * @typert object
386
+ */
387
+ declare class Session {
388
+ /** The ordered surface over this session's event log. */
389
+ get surface(): SessionSurface;
390
+ /**
391
+ * Detached, deep-frozen creation metadata (format version, cwd, lineage,
392
+ * and whether fork history exists). Supplied by the store via `ctx.sessions.create()`. When a
393
+ * `Session` is created without a store-owned header, a minimal header is
394
+ * synthesized (stamped with the current {@link SESSION_FORMAT_VERSION}) so
395
+ * `session.header` is always present. Kept out of the event log — it is a
396
+ * storage concern, not replayable conversation state.
397
+ */
398
+ readonly header: SessionHeader;
399
+ /** Number of leading events inherited from this Session's fork parent. */
400
+ readonly inheritedEventCount: SessionLogOffset;
401
+ /** The session identity, derived from its durable header's single copy. */
402
+ get id(): SessionId;
403
+ /**
404
+ * The first seq appended IN THIS PROCESS: the length of the constructor
405
+ * seed (0 without one). Events with smaller seq values entered through
406
+ * construction — replay, fork, or resume — and were never published on the
407
+ * `session/event` firehose (constructor seeds do not emit). This offset marks
408
+ * the constructor-input boundary for lifecycle ownership and persistence
409
+ * adoption; consumers that need complete canonical history still start at
410
+ * seq 0. Distinct from {@link inheritedEventCount}, the DURABLE
411
+ * fork-lineage cut: a resumed session's constructor seed is its full stored
412
+ * log, while the inherited count keeps the original fork value — this field is the
413
+ * in-process construction fact.
414
+ *
415
+ * Not persisted itself: a seeded session projects it into the log as the
416
+ * `session/end-seed` event, which is what a consumer reading STORED history
417
+ * reads. Locate the LAST such event, not necessarily one at this seq — a
418
+ * seed already ending in one is not re-marked, so reopening an untouched
419
+ * session leaves that event at a smaller seq than `firstLiveSeq`. Prefer
420
+ * this field in-process: it is exact before the marker reaches storage.
421
+ *
422
+ * When this lifecycle appends the marker, it occupies this seq before the
423
+ * store attaches and therefore does not publish either. Otherwise this seq
424
+ * holds an ordinary published write.
425
+ */
426
+ readonly firstLiveSeq: SessionLogOffset;
427
+ /**
428
+ * Create a detached session by validating and snapshotting borrowed seed
429
+ * events and storage metadata.
430
+ * @param id - session identity.
431
+ * @param seed - optional borrowed replay or fork events.
432
+ * @param header - optional borrowed storage metadata.
433
+ * @param inheritedEventCount - exact fork-inherited prefix length for a seeded header.
434
+ * @returns a detached session.
435
+ */
436
+ static create(
437
+ id: SessionId,
438
+ seed?: readonly SessionEvent[],
439
+ header?: SessionHeader,
440
+ inheritedEventCount?: SessionLogOffset,
441
+ ): Session;
442
+ /**
443
+ * Restore a detached session by taking ownership of fresh persistence values.
444
+ * The storage format, event envelopes, sequence continuity, surface transitions,
445
+ * and header fields are validated before the restored objects are frozen.
446
+ * @param id - restored session identity.
447
+ * @param seed - fresh detached events whose ownership is transferred.
448
+ * @param header - fresh detached metadata whose ownership is transferred.
449
+ * @param inheritedEventCount - exact fork-inherited prefix length decoded from storage.
450
+ * @returns a restored detached session.
451
+ */
452
+ static fromRestore(
453
+ id: SessionId,
454
+ seed: readonly SessionEvent[],
455
+ header: SessionHeader,
456
+ inheritedEventCount: SessionLogOffset,
457
+ ): Session;
458
+ /**
459
+ * Return the immutable event stored at one exact sequence number.
460
+ * @param seq - event sequence number.
461
+ * @returns the accepted event, or undefined when the log does not contain it.
462
+ */
463
+ eventAt(seq: SessionSeq): SessionEvent | undefined;
464
+ /**
465
+ * Materialize an immutable snapshot of a half-open event sequence range.
466
+ * A full current snapshot is reused until the next append; every previously
467
+ * returned snapshot remains stable after later appends.
468
+ * @param fromSeq - non-negative inclusive sequence number; defaults to the log start.
469
+ * @param toSeqExclusive - non-negative exclusive sequence number; defaults to the current end.
470
+ * @returns a frozen array of the selected deeply frozen events.
471
+ */
472
+ snapshotEvents(
473
+ fromSeq: SessionLogOffset = SessionLogOffset(0),
474
+ toSeqExclusive: SessionLogOffset = this.seq,
475
+ ): readonly SessionEvent[];
476
+ /**
477
+ * Return this Session's events after its fork-inherited prefix.
478
+ * @returns a fresh array containing child-owned events in log order.
479
+ */
480
+ ownEvents(): readonly SessionEvent[];
481
+ /**
482
+ * Whether one existing event position is outside the fork-inherited prefix.
483
+ * @param seq - event position in this Session.
484
+ * @returns true when the event belongs to this Session rather than its parent.
485
+ */
486
+ isOwnSeq(seq: SessionSeq): boolean;
487
+ /** The next event's sequence number — always the log length (the `seq = log.length` contiguity contract). */
488
+ get seq(): SessionLogOffset;
489
+ /**
490
+ * Append one typed event to the log and synchronously notify observers via
491
+ * the store-owned, module-private publication hooks. The hot path never blocks
492
+ * on I/O — persistence plugins buffer asynchronously. Once the event enters
493
+ * the log, the append is committed: observer failures are logged and
494
+ * contained per listener, so they do not change the return value or prevent
495
+ * later listeners from observing the same accepted event.
496
+ *
497
+ * @param type - The event type (key of {@link SessionEventMap}).
498
+ * @param data - The event payload; must be JSON-serializable.
499
+ * @param opts - Surface metadata: `surfaceOp` controls how the event enters
500
+ * the ordered surface; `sourceEventSeqs` lists the seq numbers of earlier
501
+ * events this one derives from. REQUIRED for
502
+ * {@link SurfaceEventType} events (every message-producing event must
503
+ * declare how it joins the surface, the sole source of derived model
504
+ * history) and
505
+ * rejected by the compiler for non-surface types like `turn/start` or
506
+ * `assistant/attempt`. Assistant messages embed their exact provider
507
+ * stream and cannot cite top-level source events.
508
+ * @returns the logged event — its assigned `seq`/`time` plus the SNAPSHOT of
509
+ * `data` that entered the log, so reading `event.data` back sees the logged
510
+ * value, never the caller's still-mutable input.
511
+ * @throws if `data` or surface metadata is not losslessly JSON-serializable
512
+ * (BigInt, function, symbol, undefined, negative zero, non-finite number,
513
+ * circular reference, sparse array, or an exotic object such as
514
+ * Map/Set/Date/class instance), or when the candidate violates the
515
+ * canonical surface contract (marker shape and eligibility, unique
516
+ * earlier source-event references, positional replacement validity, and complete
517
+ * shadowed-node coverage). One iterative pass reads, validates, and
518
+ * copies each nested value once, so a stateful getter cannot supply one value
519
+ * to validation and another to storage. The event log is the durable source
520
+ * of truth, so a bad event fails at the append site rather than later during
521
+ * a backend flush. A synchronous internal dispatch validation failure or an
522
+ * append reentered while this acceptance/publication boundary is open also
523
+ * rejects before the log changes.
524
+ */
525
+ append<T extends SessionEventType>(
526
+ type: T,
527
+ data: SessionEventMap[T],
528
+ ...opts: T extends SurfaceEventType ? [opts: SurfaceIntent<T>] : []
529
+ ): SessionEvent<T>;
530
+ /**
531
+ * The {@link EpochHeader} in force after the log's last header event — the
532
+ * header the NEXT request will be compared against — or undefined before
533
+ * the first `request/header` snapshot. The live, incrementally-maintained
534
+ * form of `foldRequestHeader(session.snapshotEvents())`: each header event is folded
535
+ * once, when first seen, so a per-step read costs O(new events).
536
+ * @returns the folded header, or undefined when no header event exists yet.
537
+ */
538
+ requestHeader(): EpochHeader | undefined;
539
+ /**
540
+ * Return the latest resolved route metadata, or `undefined` before the first
541
+ * `request/context` event. Each event is folded once.
542
+ * @returns the latest immutable route metadata.
543
+ */
544
+ requestContext(): RequestContext | undefined;
545
+ /**
546
+ * Derive the LLM message history by walking the ordered sequences of
547
+ * message-producing events maintained by `surfaceOp` markers. The
548
+ * surface is the single source of derived history: every message-producing
549
+ * append records its `surfaceOp`, so a raw event with no marker (a chunk, a
550
+ * turn boundary) is correctly absent, and a compaction `replace` deletes the
551
+ * shadowed nodes from the derivation. The projection rules are
552
+ * {@link deriveEventMessage}, folded per node.
553
+ *
554
+ * CACHED: each surface node is projected exactly once, when first seen — a
555
+ * call costs O(new nodes), and a surface rewrite (a `replace`;
556
+ * {@link SessionSurface.replaceGeneration}) rebuilds. The returned array is
557
+ * a fresh snapshot per call (later appends never grow an array a caller
558
+ * already holds); the `Message` objects in it are SHARED and **deep-frozen**.
559
+ * Their content reuses the already frozen durable event data, so the cache
560
+ * needs no second deep clone and consumers still cannot mutate the log.
561
+ * @returns a fresh array of the shared, frozen derived history.
562
+ */
563
+ deriveMessages(): Message[];
564
+ /**
565
+ * Instance face of the pure per-node `deriveEventMessage` export from
566
+ * `surface.ts`.
567
+ * @param event - the event to project.
568
+ * @returns the derived message, or null when the event produces none.
569
+ */
570
+ deriveEventMessage(event: SessionEvent): Message | null;
571
+ }
572
+ ```
573
+
574
+ ## Derived history: `deriveMessages()` and `deriveEventMessage()`
575
+
576
+ `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:
577
+
578
+ - `user/message` → a user message carrying exact `content`; an optional envelope remains log-only display metadata.
579
+ - `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.
580
+ - `tool/result` → a user message carrying a `tool-result` block.
581
+ - `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.
582
+
583
+ 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.
584
+
585
+ ## Live-session fork API
586
+
587
+ `ctx.sessions.create(id, { seed, meta })` is the low-level replay/fork primitive. For ordinary live-session forks, `SessionStore` exposes one policy API:
588
+
589
+ - `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`.
590
+
591
+ 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.
592
+
593
+ ## Why a turn ended: `TurnEndReasonMap`
594
+
595
+ `turn/start` has no trigger field. The entered `user/message` batch records what entered each step, `llm/retry` records request recovery, and idle injection remains pending until a waking delivery reaches a later pre-step. Live turns retain the typed [`AgentCancelCause`](core.md#the-agent-handle) that stopped the driver; persistence uses the additional `{ kind: 'legacy' }` cause only when importing a supported coarse cancellation record that did not store its caller.
596
+
597
+ ```ts type-equiv
598
+ /** Durable cancellation cause, including imports whose original coarse record carried no cause. */
599
+ type TurnEndCancelCause = AgentCancelCause | { readonly kind: 'legacy' }
600
+ ```
601
+
602
+ ```ts type-equiv
603
+ /**
604
+ * Why a turn ended. Merge-extensible sum type.
605
+ */
606
+ interface TurnEndReasonMap {
607
+ completed: { kind: 'completed' }
608
+ /** A cancellation request interrupted the live turn. */
609
+ aborted: { kind: 'aborted'; reason: TurnEndCancelCause }
610
+
611
+ blocked: { kind: 'blocked' }
612
+ /**
613
+ * The turn failed. `error` is always a structured failure: the `LlmError`
614
+ * facts verbatim, or `{ message: errorChain(error), code: 'UNKNOWN' }`
615
+ * flattened from any other error.
616
+ */
617
+ error: { kind: 'error'; error: LlmFailure }
618
+ /** At least one step reached its output-token ceiling, even if a plugin continued the turn. */
619
+ 'max-tokens': { kind: 'max-tokens' }
620
+ /**
621
+ * A crash-orphaned turn was closed after the fact: agent-loop resume appends
622
+ * this closer for a stored log whose last turn never ended, and session-query
623
+ * synthesizes it on cold reads. The loop never emits this marker live, and
624
+ * the events recorded before the crash remain intact.
625
+ */
626
+ interrupted: { kind: 'interrupted' }
627
+ }
628
+ ```
629
+
630
+ `max-tokens` mirrors the model-call `FinishReason` of the same name: any `max-tokens` step in a turn makes the whole turn end `max-tokens` rather than `completed` (the cut-short fact wins over a later continuation), so a consumer can tell a clean stop from a truncated one. Cancellation and errors remain distinct outcomes. `interrupted` is the one reason no loop emits—it is synthesized by crash recovery (see [persistence.md](persistence.md)). The map is merge-extensible.
631
+
632
+ ## Execution enclosure and standalone events
633
+
634
+ A turn encloses one model-loop execution, not the whole session log. AgentLoop records injected `user/message` events only from entering pre-step batches inside a turn; plugin-owned log-only events may still appear between `turn/end` and the next `turn/start`, consuming event seqs without incrementing turn numbers. Persistence admits every contiguous accepted event into a bounded durable batch, while crash repair closes only a genuinely open trailing turn. A producer that needs an immediate durability barrier explicitly awaits `ctx.sessions.flush(session)`.
635
+
636
+ The optional `dsh-session/invariant` companion enforces the relations owned by core: turn and step numbering, execution-event enclosure, and same-step tool call/result pairing. Merge-extensible event relations belong to the plugin that declares them, so core does not reject an unknown event merely because no turn is open. See [the standalone-event decision](../../.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md).
637
+
638
+ ## The end-seed boundary: `session/end-seed`
639
+
640
+ 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.
641
+
642
+ 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.
643
+
644
+ 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/*`.
645
+
646
+ Consumers that order Sessions by human activity exclude this boundary: picking a Session up is not work, so ordering by the log tail would float every opened Session to the top.
647
+
648
+ ## Plugin-contributed log-only events
649
+
650
+ A plugin may declaration-merge extra `SessionEventMap` types. These are **log-only**: NOT `SurfaceEventType`s (they carry no `surfaceOp` and contribute nothing to derived history). Their owner decides whether they belong to an open execution turn or may stand between turns, and enforces any relation in its own invariant companion. The generated [persistence log event catalog](../persistence-catalog.md) enumerates every core and plugin-contributed event; the compaction seam's `compaction/*` semantics are discussed on [compaction.md](compaction.md).
651
+
652
+ When several events in one plugin-owned family assemble into one Web Client Conversation Node, every start, update, result, resource, or interruption event in that family carries or independently derives the same stable business id. This requirement applies to correlated Node families, not to every Session event; it lets the client group each event without guessing from adjacency or scanning history. See the [Conversation subsystem](conversation.md).
653
+
654
+ The hook bridges' `hook/invoked` / `hook/result` pairs (from `@deepseek-ai/dsh-hook-protocol`) correlate by `handlerId`. `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, and `Stop` fire inside the loop's open turn, so their `hook/*` records are turn-enclosed by construction. `SessionStart` gets no `hook/*` record because it runs before turn 1; its context remains pending in the inbox until a waking delivery opens a turn (see [the hook-bridges Agent Note](../../.agents/notes/implemented/feature/2026-06-30-hook-bridges.md)).
655
+
656
+ ## Durability contract
657
+
658
+ 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.
659
+
660
+ The backends that consume this contract are on [persistence.md](persistence.md).
661
+
662
+ ## Remote catalog and workspace opening
663
+
664
+ `ModelCatalog` is the Host-generation model directory returned by `session/modelCatalog`: it carries the deployment default, routable provider ids, successful provider groups, and isolated provider failures. It is not derived from one Session and remains separate from Session projections.
665
+
666
+ `SessionOpenWorkspacePathRequest` carries an absolute or workspace-resolved `path`. `SessionOpenWorkspacePathValue` confirms that the Host accepted the native handoff. A Session-aware Client resolves relative paths against its current Session cwd when known; the controller hands the path to the opener unchanged and reports invalid requests, cancellation, and opener failures through the Session Remote error vocabulary.
667
+
668
+ <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
669
+
670
+ <a id="cordis-surface"></a>
671
+
672
+ ## Cordis API
673
+
674
+ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
675
+
676
+ <a id="ctxsessioncontroller--sessioncontroller"></a>
677
+
678
+ ### `ctx.sessionController` — `SessionController`
679
+
680
+ Host service backing the generated `ctx.remote.session` namespace.
681
+
682
+ ```ts cordis-catalog
683
+ /**
684
+ * Resolve or resume one ordinary Session for another Host API domain.
685
+ * @param sessionId - Session identity whose Agent owns the operation.
686
+ * @returns the live Agent or the stable Session-domain failure.
687
+ */
688
+ resolveAgent(sessionId: SessionId): Promise<ApiSessionAgentResult>
689
+
690
+ /**
691
+ * Inspect one attached or persisted Session without activating its Agent.
692
+ * @param sessionId - durable Session identity.
693
+ * @param signal - optional caller cancellation for persistence reads.
694
+ * @returns the current attached state or persisted header and event prefix.
695
+ */
696
+ inspect( sessionId: SessionId, signal?: AbortSignal, ): Promise<SessionInspection>
697
+
698
+ /**
699
+ * Read all visible Session rows without resuming an Agent.
700
+ * @param _request - reserved empty list request.
701
+ * @param signal - cancellation for persistence reads.
702
+ * @returns visible Session summaries ordered by activity.
703
+ */
704
+ @Remote('list') async list(_request: SessionListRequest, signal: AbortSignal): Promise<SessionListValue>
705
+
706
+ /**
707
+ * Search visible Session content without resuming an Agent.
708
+ * @param request - literal message-content query.
709
+ * @param signal - cancellation for list and search reads.
710
+ * @returns authorized bounded Session search results.
711
+ */
712
+ @Remote('search') search(request: SessionSearchRequest, signal: AbortSignal): Promise<SessionSearchValue>
713
+
714
+ /**
715
+ * Create or idempotently adopt one ordinary Session.
716
+ * @param request - requested identity, location, and Agent preset.
717
+ * @returns the Session identity and resolved preset when configured.
718
+ */
719
+ @Remote('create') create(request: SessionCreateRequest): Promise<SessionCreateValue>
720
+
721
+ /**
722
+ * Select one Session-local model after explicitly resuming the Session.
723
+ * @param request - Session identity and requested model selection.
724
+ * @returns the normalized selection installed for the Session.
725
+ */
726
+ @Remote('selectModel') selectModel(request: SessionSelectModelRequest): Promise<SessionSelectModelValue>
727
+
728
+ /**
729
+ * Describe every currently routable model for Host-generation selectors.
730
+ * @returns provider-grouped models, the deployment default, and isolated provider failures.
731
+ */
732
+ @Remote('modelCatalog') modelCatalog(): Promise<ModelCatalog>
733
+
734
+ /**
735
+ * Report whether this deployment can hand a Session workspace path to a native desktop.
736
+ * @returns true when the matching open operation is available.
737
+ */
738
+ @Remote canOpenWorkspacePath(): boolean
739
+
740
+ /**
741
+ * Open one path prepared by a Session-aware caller on the Host desktop.
742
+ * @param request - path after best-effort Session workspace resolution.
743
+ * @param signal - caller lifetime; abort terminates the native command.
744
+ * @returns confirmation after the native opener accepts the path.
745
+ * @throws RemoteError when the request is invalid, cancelled, or the opener fails.
746
+ */
747
+ @Remote('openWorkspacePath') async openWorkspacePath( request: SessionOpenWorkspacePathRequest, signal: AbortSignal, ): Promise<SessionOpenWorkspacePathValue>
748
+
749
+ /**
750
+ * Rename one Session after explicitly resuming it.
751
+ * @param request - Session identity and proposed title.
752
+ * @returns the accepted title and durable event sequence.
753
+ */
754
+ @Remote('rename') rename(request: SessionRenameRequest): Promise<SessionRenameValue>
755
+
756
+ /**
757
+ * Fork one cold-readable completed-turn prefix into a new Session.
758
+ * @param request - source Session and optional event anchor.
759
+ * @returns the new Session identity.
760
+ */
761
+ @Remote('fork') fork(request: SessionForkRequest): Promise<SessionForkValue>
762
+
763
+ /**
764
+ * Admit one prompt after explicitly resuming its Session.
765
+ * @param request - Session identity, prompt content, source metadata, and delivery mode.
766
+ * @param signal - caller cancellation before prompt admission begins.
767
+ * @returns acknowledgement that the Agent accepted the prompt.
768
+ */
769
+ @Remote('prompt') prompt(request: SessionPromptRequest, signal: AbortSignal): Promise<SessionPromptValue>
770
+
771
+ /**
772
+ * Read one image proven reachable from the addressed Session log.
773
+ * @param request - Session and attachment identities used for authorization.
774
+ * @returns the durable attachment reference and base64-encoded bytes.
775
+ */
776
+ @Remote('attachment') attachment(request: SessionAttachmentRequest): Promise<SessionAttachmentValue>
777
+
778
+ /**
779
+ * Mutate one still-pending queue occurrence on a live Agent.
780
+ * @param request - Session, queue item, and requested mutation.
781
+ * @returns acknowledgement that the queue mutation was applied.
782
+ */
783
+ @Remote('updateQueue') updateQueue(request: SessionUpdateQueueRequest): SessionUpdateQueueValue
784
+
785
+ /**
786
+ * Cancel one active Agent turn without dropping its pending inbox.
787
+ * @param request - Session whose active Agent turn is cancelled.
788
+ * @returns acknowledgement that cancellation was requested.
789
+ */
790
+ @Remote('cancel') cancel(request: SessionCancelRequest): SessionCancelValue
791
+
792
+ /**
793
+ * Read one cold-safe, message-aligned Session history page.
794
+ * @param request - durable address, backward cursor, and page budget.
795
+ * @param signal - cancellation for persistence reads.
796
+ * @returns one chronological page.
797
+ */
798
+ @Remote('page') page(request: SessionPageRequest, signal: AbortSignal): Promise<SessionPage>
799
+
800
+ /**
801
+ * Follow one Session log from its opening or resume cursor.
802
+ * @param request - durable address and last committed sequence already held by the caller.
803
+ * @param signal - cancellation owned by the Remote stream carrier.
804
+ * @returns a complete opening snapshot followed by gap-free durable event
805
+ * frames and optional cursorless assistant-stream frames.
806
+ */
807
+ @Remote({ mode: 'stream' }) follow(request: SessionFollowRequest, signal: AbortSignal): AsyncIterable<SessionFollowFrame>
808
+
809
+ /**
810
+ * Stream a complete live-control baseline followed by replacement frames.
811
+ * @param signal - cancellation owned by the Remote stream carrier.
812
+ * @returns one complete baseline followed by live replacement frames.
813
+ */
814
+ @Remote({ mode: 'stream' }) control(signal: AbortSignal): AsyncIterable<SessionControlFrame>
815
+ ```
816
+
817
+ Types: [SessionId](core.md) · [SessionInspection](persistence.md) · [SessionSearchRequest](session-query.md)
818
+
819
+ Source: [`packages/api/session-controller/src/index.ts`](../../packages/api/session-controller/src/index.ts)
820
+
821
+ <a id="ctxsessions--sessionstore"></a>
822
+
823
+ ### `ctx.sessions` — `SessionStore`
824
+
825
+ In-memory session store (`ctx.sessions`).
826
+
827
+ 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.
828
+
829
+ ```ts cordis-catalog
830
+ /**
831
+ * Create a session owned by the calling fiber: disposing that fiber stops
832
+ * event notification and removes the session from the store. `options.seed`
833
+ * populates the session with a copy of those events (replay/fork);
834
+ * `options.meta` attaches creation metadata (validated absolute `cwd`, seed
835
+ * and parent lineage, and delegation depth) as the immutable
836
+ * {@link SessionHeader} (the store fills `version`/`id`/`createdAt`).
837
+ *
838
+ * For an agent whose session must be torn down IN ORDER with its loop (so the
839
+ * loop's final events are published before the store attachment ends), do NOT use this
840
+ * — fold the session lifecycle into the agent's own effect via
841
+ * {@link prepare} + {@link enter} + {@link announce} (see
842
+ * `dsh-agent-loop`'s creation transaction).
843
+ *
844
+ * @param id - the session id; omitted, the store mints `session-<n>`.
845
+ * @param options - seed events and/or creation metadata for the header.
846
+ * @returns the live session, already entered and announced.
847
+ * @throws if a session with `id` already exists, metadata is not a plain
848
+ * lossless-JSON record with valid scalar fields, or `meta.cwd` is a
849
+ * non-absolute path (storage backends key directories off it).
850
+ */
851
+ create(id?: SessionId, options?: CreateSessionOptions): Session
852
+
853
+ /**
854
+ * Build a session WITHOUT entering it into the store — validate the id/cwd and
855
+ * construct the {@link Session} (with its immutable {@link SessionHeader}).
856
+ * Pairs with {@link enter} + {@link announce}: a caller that owns a composite
857
+ * `ctx.effect` (the agent factory) folds the session lifecycle into that ONE
858
+ * effect so a fiber unload tears the session + agent down as a single ORDERED
859
+ * chain rather than as racing sibling effects — which would remove the publication hooks
860
+ * before the driver's closing events commit, dropping them.
861
+ *
862
+ * @param id - the session id; omitted, the store mints `session-<n>`.
863
+ * @param options - seed events and/or creation metadata for the header. With
864
+ * `seedSource: 'persistence'`, metadata and events must be fresh detached
865
+ * graphs whose ownership transfers to this call: they are validated and
866
+ * frozen in place through {@link Session.fromRestore}, so the caller must
867
+ * retain no mutable aliases.
868
+ * @returns the constructed session, NOT yet in the store.
869
+ * @throws if a session with `id` already exists, metadata is not a plain
870
+ * lossless-JSON record with valid scalar fields, or `meta.cwd` is a
871
+ * non-absolute path.
872
+ */
873
+ prepare(id?: SessionId, options?: PrepareSessionOptions): Session
874
+
875
+ /**
876
+ * Enter a {@link prepare}d session into the store: install the module-private
877
+ * append publication hooks and add it to the store. Returns the DETACH
878
+ * disposer (hooks + store removal). Does NOT emit `session/created` —
879
+ * the caller yields this disposer inside its effect and THEN calls
880
+ * {@link announce}, so a throwing `session/created` listener rolls the attach
881
+ * back instead of leaking it.
882
+ *
883
+ * Re-checks the id for a duplicate: `prepare` and `enter` are public
884
+ * cross-package primitives and a caller may interleave arbitrary work (or
885
+ * another create) between them, so a stale prepared session must NOT overwrite
886
+ * a live store entry of the same id — its detach disposer would later delete
887
+ * the REAL session. The {@link create} convenience and the agent factory call
888
+ * the two back-to-back so they never trip this, but the public API cannot
889
+ * assume that.
890
+ *
891
+ * @param session - a {@link prepare}d session not yet in the store.
892
+ * @returns the detach disposer (publication hooks + store removal). When called from
893
+ * a synchronous `session/created` listener, removal and disposal wait until
894
+ * that creation dispatch unwinds.
895
+ * @throws if a session with this id is already in the store.
896
+ */
897
+ enter(session: Session): () => void
898
+
899
+ /** Emit `session/created` exactly once for an {@link enter}ed session (with
900
+ * the carrier {@link enter} captured). Separate from {@link enter} so the
901
+ * caller can yield the detach disposer first (rollback safety — see
902
+ * {@link enter}).
903
+ * @param session - the entered session to announce to listeners.
904
+ * @throws if the session is not live or its announcement already began,
905
+ * including a reentrant call from a creation listener. */
906
+ announce(session: Session): void
907
+
908
+ /**
909
+ * Dispatch the awaited `session/flush` durability checkpoint for `session`,
910
+ * with the carrier captured at {@link enter}. THE flush entry point: the
911
+ * store owns the carrier, so callers (the checkpoint policy's per-request
912
+ * barrier, goal-round-driver's idle checkpoint, teardown drains, and consumers
913
+ * that flush themselves before reading storage) must come through here
914
+ * rather than dispatch a raw `ctx.parallel('session/flush', …)` — one owner,
915
+ * one spelling, and the scoped-dispatch invariant can pin it.
916
+ * @param session - the session whose buffered events must reach durable storage.
917
+ * @returns whether at least one durability listener participated, after every
918
+ * listener has settled successfully.
919
+ * @throws the first registered listener failure after every listener settles.
920
+ */
921
+ async flush(session: Session): Promise<boolean>
922
+
923
+ /**
924
+ * Look up a live session.
925
+ * @param id - the session id to look up.
926
+ * @returns the session, or undefined when no live session has that id.
927
+ */
928
+ get(id: SessionId): Session | undefined
929
+
930
+ /**
931
+ * All live sessions, in creation order.
932
+ * @returns a fresh array; mutating it does not affect the store.
933
+ */
934
+ list(): Session[]
935
+
936
+ /**
937
+ * Create a live child session from a stable prefix of a live source.
938
+ * `boundary` is an inclusive source event seq; omitted means the source's
939
+ * current last event. The selected slice may end with a between-turn event
940
+ * but must not end inside an open turn.
941
+ *
942
+ * @param source - Live source session object or id.
943
+ * @param boundary - Inclusive source event seq to fork through; omitted means
944
+ * the source's current last event, and omitted on an empty source forks an
945
+ * empty child.
946
+ * @param childSessionId - Optional child session id; omitted delegates to
947
+ * `SessionStore`'s id policy.
948
+ * @returns The created live child session.
949
+ */
950
+ fork(source: SessionForkSource, boundary?: SessionSeq, childSessionId?: SessionId): Session
951
+ ```
952
+
953
+ Types: [CreateSessionOptions](persistence.md) · [PrepareSessionOptions](persistence.md) · [SessionId](core.md)
954
+
955
+ Source: [`packages/core/session/src/index.ts`](../../packages/core/session/src/index.ts)
956
+
957
+ <a id="api-session-events"></a>
958
+
959
+ ### `api-session/*` events
960
+
961
+ <a id="api-sessionactivity--emit"></a>
962
+
963
+ #### `api-session/activity` — emit
964
+
965
+ One user-authored durable message advanced Session list activity.
966
+
967
+ ```ts cordis-catalog
968
+ /**
969
+ * One user-authored durable message advanced Session list activity.
970
+ * @mode emit
971
+ * @param sessionId - addressed Session identity.
972
+ * @param updatedAt - durable message time used for list ordering.
973
+ */
974
+ 'api-session/activity'(sessionId: SessionId, updatedAt: number): void
975
+ ```
976
+
977
+ Types: [SessionId](core.md)
978
+
979
+ Source: [`packages/api/session-controller/src/types.ts`](../../packages/api/session-controller/src/types.ts)
980
+
981
+ <a id="api-sessionadded--emit"></a>
982
+
983
+ #### `api-session/added` — emit
984
+
985
+ A Session became visible to Session list consumers.
986
+
987
+ ```ts cordis-catalog
988
+ /**
989
+ * A Session became visible to Session list consumers.
990
+ * @mode emit
991
+ * @param summary - initial list row for the Session.
992
+ */
993
+ 'api-session/added'(summary: SessionSummary): void
994
+ ```
995
+
996
+ Source: [`packages/api/session-controller/src/types.ts`](../../packages/api/session-controller/src/types.ts)
997
+
998
+ <a id="api-sessionerror--emit"></a>
999
+
1000
+ #### `api-session/error` — emit
1001
+
1002
+ One Agent failed outside a durable turn position.
1003
+
1004
+ ```ts cordis-catalog
1005
+ /**
1006
+ * One Agent failed outside a durable turn position.
1007
+ * @mode emit
1008
+ * @param sessionId - Agent and Session identity.
1009
+ * @param message - user-safe failure chain.
1010
+ */
1011
+ 'api-session/error'(sessionId: SessionId, message: string): void
1012
+ ```
1013
+
1014
+ Types: [SessionId](core.md)
1015
+
1016
+ Source: [`packages/api/session-controller/src/types.ts`](../../packages/api/session-controller/src/types.ts)
1017
+
1018
+ <a id="api-sessionremoved--emit"></a>
1019
+
1020
+ #### `api-session/removed` — emit
1021
+
1022
+ A Session left the live Host registry.
1023
+
1024
+ ```ts cordis-catalog
1025
+ /**
1026
+ * A Session left the live Host registry.
1027
+ * @mode emit
1028
+ * @param sessionId - removed Session identity.
1029
+ */
1030
+ 'api-session/removed'(sessionId: SessionId): void
1031
+ ```
1032
+
1033
+ Types: [SessionId](core.md)
1034
+
1035
+ Source: [`packages/api/session-controller/src/types.ts`](../../packages/api/session-controller/src/types.ts)
1036
+
1037
+ <a id="api-sessionstatus--emit"></a>
1038
+
1039
+ #### `api-session/status` — emit
1040
+
1041
+ One Agent changed running state.
1042
+
1043
+ ```ts cordis-catalog
1044
+ /**
1045
+ * One Agent changed running state.
1046
+ * @mode emit
1047
+ * @param sessionId - Agent and Session identity.
1048
+ * @param running - whether the Agent is running.
1049
+ */
1050
+ 'api-session/status'(sessionId: SessionId, running: boolean): void
1051
+ ```
1052
+
1053
+ Types: [SessionId](core.md)
1054
+
1055
+ Source: [`packages/api/session-controller/src/types.ts`](../../packages/api/session-controller/src/types.ts)
1056
+
1057
+ <a id="session-events"></a>
1058
+
1059
+ ### `session/*` events
1060
+
1061
+ <a id="sessioncreated--emit"></a>
1062
+
1063
+ #### `session/created` — emit
1064
+
1065
+ Creation announcement during session publication. A synchronous throw vetoes and rolls back with a paired disposal; detach requested during dispatch is deferred. A returned-promise rejection is logged but cannot retroactively veto this synchronous boundary. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only sessions entered through that agent's context.
1066
+
1067
+ ```ts cordis-catalog
1068
+ /**
1069
+ * Creation announcement during session publication. A synchronous throw vetoes and rolls
1070
+ * back with a paired disposal; detach requested during dispatch is deferred.
1071
+ * A returned-promise rejection is logged but cannot retroactively veto this
1072
+ * synchronous boundary.
1073
+ * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners
1074
+ * receive only sessions entered through that agent's context.
1075
+ * @param session - the session just entered and announced.
1076
+ * @dshScopeScan unsupported
1077
+ * @mode emit
1078
+ */
1079
+ 'session/created'(this: Scoped<Session>, session: Session): void
1080
+ ```
1081
+
1082
+ Types: [Scoped](scope.md)
1083
+
1084
+ Source: [`packages/core/session/src/index.ts`](../../packages/core/session/src/index.ts)
1085
+
1086
+ <a id="sessiondisposed--emit"></a>
1087
+
1088
+ #### `session/disposed` — emit
1089
+
1090
+ Emitted once when an announced session leaves the store, including publication rollback, but never for an entry whose creation announcement did not begin. Listener failures are logged and contained. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the owner scope.
1091
+
1092
+ ```ts cordis-catalog
1093
+ /**
1094
+ * Emitted once when an announced session leaves the store, including
1095
+ * publication rollback, but never for an entry whose creation announcement
1096
+ * did not begin. Listener failures are logged and contained.
1097
+ * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the owner scope.
1098
+ * @param session - the session that is no longer live in the store.
1099
+ * @dshScopeScan unsupported
1100
+ * @mode emit
1101
+ */
1102
+ 'session/disposed'(this: Scoped<Session>, session: Session): void
1103
+ ```
1104
+
1105
+ Types: [Scoped](scope.md)
1106
+
1107
+ Source: [`packages/core/session/src/index.ts`](../../packages/core/session/src/index.ts)
1108
+
1109
+ <a id="sessionevent--emit"></a>
1110
+
1111
+ #### `session/event` — emit
1112
+
1113
+ Post-commit, fire-and-forget append feed. The listener snapshot resolves before the log push, but callbacks run after it; observer failures are logged and contained without making the committed append fail. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only events from sessions entered through that agent's context.
1114
+
1115
+ ```ts cordis-catalog
1116
+ /**
1117
+ * Post-commit, fire-and-forget append feed. The listener snapshot resolves
1118
+ * before the log push, but callbacks run after it; observer failures are
1119
+ * logged and contained without making the committed append fail.
1120
+ * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners
1121
+ * receive only events from sessions entered through that agent's context.
1122
+ * @param session - the session whose log grew.
1123
+ * @param event - the appended event, exactly as recorded.
1124
+ * @dshScopeScan unsupported
1125
+ * @mode emit
1126
+ */
1127
+ 'session/event'(this: Scoped<Session>, session: Session, event: SessionEvent): void
1128
+ ```
1129
+
1130
+ Types: [Scoped](scope.md)
1131
+
1132
+ Source: [`packages/core/session/src/index.ts`](../../packages/core/session/src/index.ts)
1133
+
1134
+ <a id="sessionflush--parallel"></a>
1135
+
1136
+ #### `session/flush` — parallel
1137
+
1138
+ Awaited parallel durability checkpoint: every listener runs and the caller awaits all of them, with no waterfall veto. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the session's owner scope.
1139
+
1140
+ ```ts cordis-catalog
1141
+ /**
1142
+ * Awaited parallel durability checkpoint: every listener runs and the
1143
+ * caller awaits all of them, with no waterfall veto. Scope-filtered dispatch
1144
+ * (`@deepseek-ai/dsh-scope`) reuses the session's owner scope.
1145
+ * @param session - the session whose buffered events must reach durable storage.
1146
+ * @dshScopeScan unsupported
1147
+ * @mode parallel
1148
+ */
1149
+ 'session/flush'(this: Scoped<Session>, session: Session): Promise<void> | void
1150
+ ```
1151
+
1152
+ Types: [Scoped](scope.md)
1153
+
1154
+ Source: [`packages/core/session/src/index.ts`](../../packages/core/session/src/index.ts)
1155
+ <!-- END GENERATED cordis-surface -->