@askdkc/dsh-cli 0.12.3 → 0.13.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 (162) hide show
  1. package/cordis.patch.yml +12 -131
  2. package/lib/types/adapter/channel/session-projection.d.ts.map +1 -1
  3. package/lib/types/adapter/channel/session-projection.js +0 -7
  4. package/lib/types/adapter/ports/channel-view.d.ts +1 -1
  5. package/lib/types/adapter/ports/channel-view.d.ts.map +1 -1
  6. package/lib/types/dsh-adapter/bundled-presets.d.ts +3 -8
  7. package/lib/types/dsh-adapter/bundled-presets.d.ts.map +1 -1
  8. package/lib/types/dsh-adapter/bundled-presets.js +5 -20
  9. package/lib/types/dsh-adapter/channel/binding-events.d.ts.map +1 -1
  10. package/lib/types/dsh-adapter/channel/binding-events.js +2 -9
  11. package/lib/types/dsh-adapter/channel/external-commands.d.ts.map +1 -1
  12. package/lib/types/dsh-adapter/channel/external-commands.js +9 -33
  13. package/lib/types/dsh-adapter/channel/local-actions.d.ts +2 -1
  14. package/lib/types/dsh-adapter/channel/local-actions.d.ts.map +1 -1
  15. package/lib/types/dsh-adapter/channel/local-actions.js +6 -2
  16. package/lib/types/dsh-adapter/channel/model-actions.js +3 -3
  17. package/lib/types/dsh-adapter/channel/model-switch.d.ts.map +1 -1
  18. package/lib/types/dsh-adapter/channel/model-switch.js +0 -1
  19. package/lib/types/dsh-adapter/channel/permissions.d.ts +1 -6
  20. package/lib/types/dsh-adapter/channel/permissions.d.ts.map +1 -1
  21. package/lib/types/dsh-adapter/channel/permissions.js +2 -54
  22. package/lib/types/dsh-adapter/channel/projection.d.ts.map +1 -1
  23. package/lib/types/dsh-adapter/channel/projection.js +15 -70
  24. package/lib/types/dsh-adapter/channel/session-fork.d.ts.map +1 -1
  25. package/lib/types/dsh-adapter/channel/session-fork.js +0 -1
  26. package/lib/types/dsh-adapter/channel/session-metadata.d.ts.map +1 -1
  27. package/lib/types/dsh-adapter/channel/session-metadata.js +1 -4
  28. package/lib/types/dsh-adapter/channel/session-resume.js +2 -2
  29. package/lib/types/dsh-adapter/channel/session-rewind.d.ts.map +1 -1
  30. package/lib/types/dsh-adapter/channel/session-rewind.js +0 -1
  31. package/lib/types/dsh-adapter/channel/session-tree-actions.d.ts.map +1 -1
  32. package/lib/types/dsh-adapter/channel/session-tree-actions.js +1 -2
  33. package/lib/types/dsh-adapter/channel/session-tree.d.ts.map +1 -1
  34. package/lib/types/dsh-adapter/channel/session-tree.js +9 -62
  35. package/lib/types/dsh-adapter/channel/skill-catalog.js +1 -1
  36. package/lib/types/dsh-adapter/channel/subagent-projection.d.ts.map +1 -1
  37. package/lib/types/dsh-adapter/channel/subagent-projection.js +2 -8
  38. package/lib/types/dsh-adapter/channel/transcript.d.ts +0 -16
  39. package/lib/types/dsh-adapter/channel/transcript.d.ts.map +1 -1
  40. package/lib/types/dsh-adapter/channel/transcript.js +0 -43
  41. package/lib/types/dsh-adapter/channel.js +2 -2
  42. package/lib/types/dsh-adapter/compat/index.d.ts +5 -25
  43. package/lib/types/dsh-adapter/compat/index.d.ts.map +1 -1
  44. package/lib/types/dsh-adapter/compat/index.js +5 -25
  45. package/lib/types/dsh-adapter/compat/liveSession.d.ts +12 -23
  46. package/lib/types/dsh-adapter/compat/liveSession.d.ts.map +1 -1
  47. package/lib/types/dsh-adapter/compat/liveSession.js +15 -68
  48. package/lib/types/dsh-adapter/compat/messages.d.ts +1 -3
  49. package/lib/types/dsh-adapter/compat/messages.d.ts.map +1 -1
  50. package/lib/types/dsh-adapter/compat/messages.js +2 -10
  51. package/lib/types/dsh-adapter/compat/persistence.d.ts +2 -3
  52. package/lib/types/dsh-adapter/compat/persistence.d.ts.map +1 -1
  53. package/lib/types/dsh-adapter/compat/persistence.js +9 -14
  54. package/lib/types/dsh-adapter/compat/sessionLog.d.ts +10 -144
  55. package/lib/types/dsh-adapter/compat/sessionLog.d.ts.map +1 -1
  56. package/lib/types/dsh-adapter/compat/sessionLog.js +45 -275
  57. package/lib/types/dsh-adapter/compat/settings.d.ts +4 -10
  58. package/lib/types/dsh-adapter/compat/settings.d.ts.map +1 -1
  59. package/lib/types/dsh-adapter/compat/settings.js +6 -23
  60. package/lib/types/dsh-adapter/compat/shell.d.ts +2 -3
  61. package/lib/types/dsh-adapter/compat/shell.d.ts.map +1 -1
  62. package/lib/types/dsh-adapter/compat/shell.js +2 -7
  63. package/lib/types/dsh-adapter/contract.d.ts +4 -34
  64. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  65. package/lib/types/dsh-adapter/contract.js +8 -83
  66. package/lib/types/dsh-adapter/packaged-presets.d.ts +0 -19
  67. package/lib/types/dsh-adapter/packaged-presets.d.ts.map +1 -1
  68. package/lib/types/dsh-adapter/packaged-presets.js +1 -83
  69. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  70. package/lib/types/dsh-adapter/plugin.js +25 -168
  71. package/lib/types/dsh-adapter/preset-resolution.d.ts +1 -22
  72. package/lib/types/dsh-adapter/preset-resolution.d.ts.map +1 -1
  73. package/lib/types/dsh-adapter/preset-resolution.js +0 -34
  74. package/lib/types/dsh-adapter/presets.d.ts +1 -4
  75. package/lib/types/dsh-adapter/presets.d.ts.map +1 -1
  76. package/lib/types/dsh-adapter/presets.js +3 -6
  77. package/lib/types/dsh-adapter/questions-answerer.d.ts +5 -43
  78. package/lib/types/dsh-adapter/questions-answerer.d.ts.map +1 -1
  79. package/lib/types/dsh-adapter/questions-answerer.js +7 -63
  80. package/lib/types/dsh-adapter/sessionTree.d.ts +0 -19
  81. package/lib/types/dsh-adapter/sessionTree.d.ts.map +1 -1
  82. package/lib/types/dsh-adapter/sessionTree.js +2 -112
  83. package/lib/types/dsh-adapter/sessions/header.d.ts +2 -2
  84. package/lib/types/dsh-adapter/sessions/header.d.ts.map +1 -1
  85. package/lib/types/dsh-adapter/sessions/header.js +2 -8
  86. package/lib/types/dsh-adapter/sessions/list.d.ts +7 -28
  87. package/lib/types/dsh-adapter/sessions/list.d.ts.map +1 -1
  88. package/lib/types/dsh-adapter/sessions/list.js +48 -103
  89. package/lib/types/dsh-adapter/sessions/store.d.ts +1 -2
  90. package/lib/types/dsh-adapter/sessions/store.d.ts.map +1 -1
  91. package/lib/types/dsh-adapter/sessions/store.js +7 -10
  92. package/lib/types/dsh-adapter/sharp.d.ts.map +1 -1
  93. package/lib/types/dsh-adapter/sharp.js +0 -1
  94. package/lib/types/dsh-adapter/subagents.d.ts +1 -2
  95. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
  96. package/lib/types/dsh-adapter/subagents.js +1 -12
  97. package/lib/types/dsh-adapter/trajectory/aggregate.d.ts +1 -1
  98. package/lib/types/dsh-adapter/trajectory/aggregate.js +1 -1
  99. package/lib/types/dsh-adapter/trajectory/guards.d.ts +5 -22
  100. package/lib/types/dsh-adapter/trajectory/guards.d.ts.map +1 -1
  101. package/lib/types/dsh-adapter/trajectory/guards.js +5 -23
  102. package/lib/types/dsh-adapter/trajectory/projection.d.ts +5 -21
  103. package/lib/types/dsh-adapter/trajectory/projection.d.ts.map +1 -1
  104. package/lib/types/dsh-adapter/trajectory/projection.js +4 -38
  105. package/lib/types/dsh-adapter/trajectory/types.d.ts +1 -1
  106. package/lib/types/dsh-adapter/trajectory/types.d.ts.map +1 -1
  107. package/lib/types/i18n.d.ts +2 -22
  108. package/lib/types/i18n.d.ts.map +1 -1
  109. package/lib/types/i18n.js +2 -10
  110. package/lib/types/presetPrefs.d.ts +1 -3
  111. package/lib/types/presetPrefs.d.ts.map +1 -1
  112. package/lib/types/presetPrefs.js +2 -4
  113. package/node_modules/@askdkc/dsh-auth/README.ja.md +28 -9
  114. package/node_modules/@askdkc/dsh-auth/README.md +19 -8
  115. package/node_modules/@askdkc/dsh-auth/dsh-plugin.json +3 -3
  116. package/node_modules/@askdkc/dsh-auth/lib/THIRD_PARTY_NOTICES.txt +168 -0
  117. package/node_modules/@askdkc/dsh-auth/lib/abortable.d.ts +2 -0
  118. package/node_modules/@askdkc/dsh-auth/lib/abortable.js +9 -0
  119. package/node_modules/@askdkc/dsh-auth/lib/auth-contract.d.ts +66 -0
  120. package/node_modules/@askdkc/dsh-auth/lib/auth-contract.js +1 -0
  121. package/node_modules/@askdkc/dsh-auth/lib/command.js +15 -1
  122. package/node_modules/@askdkc/dsh-auth/lib/credentials.d.ts +8 -14
  123. package/node_modules/@askdkc/dsh-auth/lib/credentials.js +1 -1
  124. package/node_modules/@askdkc/dsh-auth/lib/index.d.ts +8 -28
  125. package/node_modules/@askdkc/dsh-auth/lib/index.js +67 -65
  126. package/node_modules/@askdkc/dsh-auth/lib/interaction.d.ts +5 -5
  127. package/node_modules/@askdkc/dsh-auth/lib/opencode-adapter.d.ts +37 -0
  128. package/node_modules/@askdkc/dsh-auth/lib/opencode-adapter.js +197 -0
  129. package/node_modules/@askdkc/dsh-auth/lib/opencode-catalog.d.ts +72 -0
  130. package/node_modules/@askdkc/dsh-auth/lib/opencode-catalog.js +252 -0
  131. package/node_modules/@askdkc/dsh-auth/lib/opencode-codec.d.ts +6 -0
  132. package/node_modules/@askdkc/dsh-auth/lib/opencode-codec.js +93 -0
  133. package/node_modules/@askdkc/dsh-auth/lib/opencode-owned.generated.d.ts +113 -0
  134. package/node_modules/@askdkc/dsh-auth/lib/opencode-owned.generated.js +2866 -0
  135. package/node_modules/@askdkc/dsh-auth/lib/opencode-replay.d.ts +15 -0
  136. package/node_modules/@askdkc/dsh-auth/lib/opencode-replay.js +141 -0
  137. package/node_modules/@askdkc/dsh-auth/lib/opencode-sdk.d.ts +5 -0
  138. package/node_modules/@askdkc/dsh-auth/lib/opencode-sdk.js +43465 -0
  139. package/node_modules/@askdkc/dsh-auth/lib/opencode-sdk.js.map +7 -0
  140. package/node_modules/@askdkc/dsh-auth/lib/opencode-transport.d.ts +5 -0
  141. package/node_modules/@askdkc/dsh-auth/lib/opencode-transport.js +69 -0
  142. package/node_modules/@askdkc/dsh-auth/lib/pi-routes.d.ts +32 -0
  143. package/node_modules/@askdkc/dsh-auth/lib/pi-routes.js +45 -0
  144. package/node_modules/@askdkc/dsh-auth/lib/profiles.d.ts +2 -20
  145. package/node_modules/@askdkc/dsh-auth/lib/profiles.js +6 -41
  146. package/node_modules/@askdkc/dsh-auth/lib/routes.d.ts +20 -0
  147. package/node_modules/@askdkc/dsh-auth/lib/routes.js +13 -0
  148. package/node_modules/@askdkc/dsh-auth/lib/service.d.ts +11 -2
  149. package/node_modules/@askdkc/dsh-auth/lib/service.js +26 -16
  150. package/node_modules/@askdkc/dsh-auth/package.json +26 -15
  151. package/package.json +100 -96
  152. package/lib/types/dsh-adapter/providerGuard.d.ts +0 -75
  153. package/lib/types/dsh-adapter/providerGuard.d.ts.map +0 -1
  154. package/lib/types/dsh-adapter/providerGuard.js +0 -104
  155. package/lib/types/migrationPrefs.d.ts +0 -60
  156. package/lib/types/migrationPrefs.d.ts.map +0 -1
  157. package/lib/types/migrationPrefs.js +0 -97
  158. package/node_modules/@askdkc/dsh-auth/lib/fresh-models.d.ts +0 -59
  159. package/node_modules/@askdkc/dsh-auth/lib/fresh-models.js +0 -246
  160. package/node_modules/@askdkc/dsh-auth/lib/opencode-snapshot.generated.d.ts +0 -321
  161. package/node_modules/@askdkc/dsh-auth/lib/opencode-snapshot.generated.js +0 -528
  162. package/presets/liangshen/.dsh-tui-managed.json +0 -5
@@ -1,68 +1,6 @@
1
- /**
2
- * Session-log compatibility helpers.
3
- *
4
- * Title lookup tolerates event types unknown to the current harness. Offline
5
- * rename and delete support the `/resume` picker when no live Agent owns the
6
- * selected persisted session. The resume seam registers vouched-for legacy
7
- * event types into every reachable KNOWN_SESSION_EVENT_TYPES copy so the
8
- * strict read path stops rejecting whole sessions over them (issue #153).
9
- *
10
- * Registration background: plugins like dsh-working-activity (< the publish
11
- * cut) appended `activity/status` through `session.append`, but rc.6's
12
- * append exposes no `ignorable` flag and the type is absent from
13
- * KNOWN_SESSION_EVENT_TYPES — so resume's seed validation rejects the WHOLE
14
- * session ("unknown to this harness and not marked ignorable"). Upstream's
15
- * catalog header defers a registration surface "until such a consumer
16
- * exists"; the working-activity plugin became that consumer at #119 and
17
- * registers its type at load. Logs written BEFORE that cut, resumed in a
18
- * process where the plugin's registration never ran (plugin unmounted, or
19
- * a bare cordis.yml), still hit the rejection — this module is the consumer
20
- * for exactly that residue.
21
- *
22
- * Why registration and NOT rewriting the log to mark events `ignorable`
23
- * (the #107 approach, restored then replaced after review): the store is
24
- * shared with dsh web (#24) and possibly a second TUI instance (#153), and
25
- * a whole-file tmp+rename swap (a) loses frames an already-open appender
26
- * lands on the replaced inode, (b) drops the backend's 0600 artifact mode,
27
- * (c) re-encodes frames without the writer's checksum flag, and (d) dies on
28
- * torn tails the backend itself can recover. Registration touches nothing
29
- * on disk and degrades to exactly the pre-patch behavior when no copy
30
- * resolves.
31
- *
32
- * Whitelist discipline: ONLY `activity/status` — the type the plugin
33
- * provably wrote as ephemeral UI frames. Anything else unknown stays
34
- * unknown: upstream's fail-closed ("likely written by a newer harness") is
35
- * a feature — silently skipping a REQUIRED future event would reconstruct
36
- * a wrong session. Retires the day upstream's shared catalog adopts the
37
- * type or ships a real registration API (the add() calls become no-ops).
38
- *
39
- * The second half of this module is the bounded, tolerant, read-only log
40
- * reader behind the session tree (channel.buildSessionTree): 64 KiB chunked
41
- * I/O, lazy per-frame zstd decode over an RFC 8878 structural frame walk (a
42
- * torn final frame — crash mid-flush — is dropped as uncommitted, never
43
- * fatal), packed-row expansion, an event budget plus a scanned-envelope
44
- * budget so ignorable-heavy logs cannot multiply the cost of opening the
45
- * panel, and an inherited-prefix skip so a fork's event budget pays only for
46
- * its OWN events. It never writes; both stock encodings are read.
47
- *
48
- * @module dsh-cli/compat/sessionLog
49
- */
50
1
  import type { SessionEvent } from '@deepseek-ai/dsh-session';
51
- /**
52
- * Legacy third-party session-event types the TUI vouches for as ephemeral
53
- * UI frames — safe for the strict read path to accept and skip. Exported
54
- * for the regression verifier; grow it only with proof the type was always
55
- * inert (never load-bearing for session reconstruction).
56
- *
57
- * 0.1.5 note: the vouch is not only for OLD logs. `session/color` is written
58
- * by the TUI itself on every host generation (session-metadata.ts), and
59
- * 0.1.5's `validateStoredEvents` refuses unknown non-ignorable types at load
60
- * — without this registration a single color pick makes that session
61
- * permanently unresumable (probed: SessionFormatUnsupportedError without the
62
- * vouch, clean load with it). The type stays inert for reconstruction either
63
- * way: it only re-tints the row.
64
- */
65
- export declare const LEGACY_SESSION_EVENT_TYPES: readonly string[];
2
+ /** Informational event vocabulary written by this TUI on the current host. */
3
+ export declare const TUI_SESSION_EVENT_TYPES: readonly string[];
66
4
  /**
67
5
  * Session-log storage roots, in priority order, mirroring the persistence
68
6
  * backend's `root` resolution: cordis.patch.yml sets `DSH_TUI_SESSION_ROOT ?? dshHomePath(
@@ -89,19 +27,7 @@ interface SessionLogFile {
89
27
  /** true for session.jsonl.zstd (default), false for compression:"none". */
90
28
  readonly compressed: boolean;
91
29
  }
92
- /**
93
- * Resolve a `persistence.locate()` hint to a readable log. The hint may name
94
- * the physical artifact directly or the backend's LOGICAL name for it (the
95
- * jsonl backend's raw-artifact filename carries no encoding suffix), so the
96
- * compressed twin is probed as well. Since 0.1.5 the hint names the CURRENT
97
- * generation (`session.vN.jsonl[.zstd]`) without touching the filesystem —
98
- * for a session never opened since its format was superseded that file does
99
- * not exist yet, and the authoritative artifact is an older-generation
100
- * sibling in the same directory. Encoding is sniffed from content, never
101
- * inferred from the extension.
102
- * @param hint - Absolute path from SessionPersistence.locate().
103
- * @returns The readable log, or undefined when nothing materialized there.
104
- */
30
+ /** Resolve the exact backend artifact or its compressed twin. */
105
31
  export declare function resolveLocatedPath(hint: string): SessionLogFile | undefined;
106
32
  /** Outcome of reading an EXISTING log through the bounded reader. */
107
33
  export interface SessionLogRead {
@@ -150,8 +76,7 @@ export declare function readSessionEventsFromFile(path: string, maxEvents?: numb
150
76
  * cost-bounded. Built for the session tree (buildSessionTree), whose browse
151
77
  * must satisfy three constraints the strict backend read cannot:
152
78
  *
153
- * - READ-ONLY: the strict backend read (`persistence.inspect` on pre-0.1.5
154
- * hosts, the validated `handle.read` path since) rejects logs carrying
79
+ * - READ-ONLY: the strict handle.read path rejects logs carrying
155
80
  * third-party event types (working-activity's `activity/status`), and
156
81
  * the resume seam's answer (in-process type registration, issue #153)
157
82
  * must not be inverted here: opening a picker never rewrites a history
@@ -169,79 +94,20 @@ export declare function readSessionEventsFromFile(path: string, maxEvents?: numb
169
94
  * is the atomic unit (see logRecords) — the budget bounds work ACROSS
170
95
  * frames, never inside one giant flush.
171
96
  *
172
- * Packed rows are expanded with the backend's own `decodeStorageRecord`:
173
- * the packChunks writer folds each run of same-block delta chunks into ONE
174
- * `text-chunks`/`reasoning-chunks`/`tool-call-chunks` line (the default —
175
- * the finding that motivated this reader's third revision), so a line is a
176
- * storage record, not an event, and seq-less rows must not be dropped as if
177
- * they were headers. The budget counts EXPANDED events, matching the tree's
178
- * per-event cost. Malformed packed rows throw inside decodeStorageRecord —
179
- * corrupt storage degrades to `undefined`, never to silently dropped runs.
180
- *
181
- * Torn-tail semantics mirror the backend's own reader: a plain log's
182
- * unterminated final line (crash mid-append) is uncommitted data and
183
- * ignored, while an unterminated record INSIDE a complete zstd frame is
184
- * corruption and fails the read.
185
- *
186
- * Envelopes with `ignorable: true` are skipped (the read path's own skip
187
- * signal) and the seq-less header row is metadata, not an event. Skipped
188
- * envelopes still count against the SCAN budget (maxScanned): skipping is
189
- * not free — the I/O, decompress and JSON.parse for them have already been
190
- * paid — so an ignorable-heavy log cannot bypass the cost bound by keeping
191
- * its collectible count low. Never throws; returns undefined ONLY when the
192
- * log is absent — an existing-but-undecodable log returns `failed: true`
193
- * so the caller degrades to a placeholder instead of escalating to an
194
- * unbounded strict re-read.
195
- * @param sessionId - Session whose log should be read.
196
- * @param maxEvents - Stop before collecting more than this many events
197
- * (default: unbounded). 0 collects nothing and reports complete: false
198
- * whenever the log holds any collectible event.
199
- * @param maxScanned - Stop after INSPECTING this many envelopes, collected
200
- * or skipped (default: 4× maxEvents + 4096; unbounded when maxEvents is).
201
- * @param skipBelowSeq - Inherited-prefix cutoff (session-tree dedup):
202
- * seq'd events below it are skipped without spending the event budget —
203
- * they still count against maxScanned. `session/title` events are always
204
- * collected (branch-head labels need them; they never become entries).
205
- * @returns The read outcome — events, completeness, real scan cost, and a
206
- * `failed` marker for safety-cap/corruption bailouts.
97
+ * Current event envelopes are read with bounded scan and allocation costs.
207
98
  */
208
99
  export declare function readSessionEventsFromLog(sessionId: string, maxEvents?: number, maxScanned?: number, skipBelowSeq?: number): SessionLogRead | undefined;
209
- /**
210
- * Read the exact inherited-prefix length from a log. Pre-V2 physical headers
211
- * carry it as `seedLength` on the first line; V2/V3 headers only have
212
- * `isSeeded`, and the cut is the seq of the `session/end-seed` marker event
213
- * stamped with `inherited: true` — scanned for, bounded, only when the
214
- * header says the session is seeded. Logical list headers never carry the
215
- * cut on any generation. Never guesses from snapshot length, and an
216
- * unseeded or marker-less log reports undefined (no cut to report).
217
- */
100
+ /** Read the exact current inherited cut from a bounded end-seed marker scan. */
218
101
  export declare function readPhysicalHeaderSeedLength(path: string): number | undefined;
219
102
  /** Locate a session log by id and read its inherited-prefix cut. */
220
103
  export declare function readPhysicalHeaderSeedLengthForSession(sessionId: string): number | undefined;
221
- /**
222
- * Register every {@link LEGACY_SESSION_EVENT_TYPES} type as known in EVERY
223
- * reachable KNOWN_SESSION_EVENT_TYPES copy, ahead of the strict read path
224
- * (`agents.resume` seed validation, `persistence.load`). Idempotent; never
225
- * throws.
226
- *
227
- * Why a walk instead of a single import: a runtime can load dsh-session
228
- * more than once (CLI tree vs profile tree, version overlap during
229
- * upgrades, pnpm peer-context splits), and the strict validator — which
230
- * lives in the dsh-session-persistence package — consults only ITS OWN
231
- * tree's copy. Registering through one import leaves the other trees'
232
- * copies untouched. So from EACH base anchor (this module = the dsh-tui
233
- * tree, the process entry point = the launcher/CLI tree) the walk
234
- * registers the tree's own dsh-session AND steps one edge further:
235
- * resolve the validator package from that same tree, then register the
236
- * dsh-session copy the validator's entry resolves. A branch that cannot
237
- * be resolved simply is not there; resolved module paths are deduped.
238
- */
239
- export declare function ensureLegacySessionEventTypes(): void;
104
+ /** Register current TUI-owned types in the profile and host validator copies. */
105
+ export declare function registerTuiSessionEventTypes(): void;
240
106
  /**
241
107
  * Read a session's display title from its persisted log, tolerantly.
242
108
  *
243
- * Why not `persistence.load()`: the backend validates every event against
244
- * KNOWN_SESSION_EVENT_TYPES and throws the WHOLE load when a third-party
109
+ * Why not a strict persistence read: the backend validates every event against
110
+ * KNOWN_SESSION_EVENT_TYPES and rejects the entire read when a third-party
245
111
  * plugin wrote an unmarked unknown type. A picker label is
246
112
  * read-only UI state: decoding frames directly here keeps titles working
247
113
  * for logs the strict path refuses, now and for future plugin event types.
@@ -1 +1 @@
1
- {"version":3,"file":"sessionLog.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/compat/sessionLog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAiB5D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,0BAA0B,EAAE,SAAS,MAAM,EAAyC,CAAA;AAKjG;;;;;;;GAOG;AACH,wBAAgB,aAAa,IAAI,MAAM,EAAE,CASxC;AAsED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAiBxE;AAqOD,sDAAsD;AACtD,UAAU,cAAc;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;CAC7B;AA+SD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAa3E;AAED,qEAAqE;AACrE,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,CAAA;IACxC,+EAA+E;IAC/E,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B;4DACwD;IACxD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,uEAAuE;IACvE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB;;;;;;yDAMqD;IACrD,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAC1B;AAoED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,SAAS,GAAE,MAAiC,EAC5C,UAAU,GAAE,MAAqC,EACjD,YAAY,GAAE,MAAU,GACvB,cAAc,GAAG,SAAS,CAI5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,MAAM,EACjB,SAAS,GAAE,MAAiC,EAC5C,UAAU,GAAE,MAAqC,EACjD,YAAY,GAAE,MAAU,GACvB,cAAc,GAAG,SAAS,CAI5B;AA4CD;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAqC7E;AAED,oEAAoE;AACpE,wBAAgB,sCAAsC,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI5F;AA+BD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,6BAA6B,IAAI,IAAI,CAkCpD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,GAChB;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAyBzD;AAyBD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG;IAC5C,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CACzB,CAEA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,aAAa,CAyB/F;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,CAwB7E"}
1
+ {"version":3,"file":"sessionLog.d.ts","sourceRoot":"","sources":["../../../../src/dsh-adapter/compat/sessionLog.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAiB5D,8EAA8E;AAC9E,eAAO,MAAM,uBAAuB,EAAE,SAAS,MAAM,EAAsB,CAAA;AAK3E;;;;;;;GAOG;AACH,wBAAgB,aAAa,IAAI,MAAM,EAAE,CASxC;AAsED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAiBxE;AA0CD,sDAAsD;AACtD,UAAU,cAAc;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAA;CAC7B;AA+SD,iEAAiE;AACjE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAO3E;AAED,qEAAqE;AACrE,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,CAAA;IACxC,+EAA+E;IAC/E,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B;4DACwD;IACxD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,uEAAuE;IACvE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB;;;;;;yDAMqD;IACrD,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAC1B;AAqED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,SAAS,GAAE,MAAiC,EAC5C,UAAU,GAAE,MAAqC,EACjD,YAAY,GAAE,MAAU,GACvB,cAAc,GAAG,SAAS,CAI5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,MAAM,EACjB,SAAS,GAAE,MAAiC,EAC5C,UAAU,GAAE,MAAqC,EACjD,YAAY,GAAE,MAAU,GACvB,cAAc,GAAG,SAAS,CAI5B;AAmCD,gFAAgF;AAChF,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAoC7E;AAED,oEAAoE;AACpE,wBAAgB,sCAAsC,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI5F;AAqCD,iFAAiF;AACjF,wBAAgB,4BAA4B,IAAI,IAAI,CAkCnD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,GAChB;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAyBzD;AAyBD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG;IAC5C,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CACzB,CAEA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,aAAa,CAyB/F;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,CAwB7E"}
@@ -1,23 +1,16 @@
1
+ /**
2
+ * Bounded reads and offline metadata edits for current session logs.
3
+ * TUI-owned informational event types are registered before strict reads.
4
+ * Unsupported older storage formats are left untouched and reported as failed reads.
5
+ */
6
+ import { SESSION_FORMAT_VERSION } from '@deepseek-ai/dsh-session';
1
7
  import { createRequire } from 'node:module';
2
8
  import { appendFileSync, closeSync, existsSync, openSync, readdirSync, readFileSync, readSync, realpathSync, rmSync, } from 'node:fs';
3
9
  import { dirname, join, sep } from 'node:path';
4
10
  import { zstdCompressSync, zstdDecompressSync } from 'node:zlib';
5
11
  import { homeDir } from '../../utils/paths.js';
6
- /**
7
- * Legacy third-party session-event types the TUI vouches for as ephemeral
8
- * UI frames — safe for the strict read path to accept and skip. Exported
9
- * for the regression verifier; grow it only with proof the type was always
10
- * inert (never load-bearing for session reconstruction).
11
- *
12
- * 0.1.5 note: the vouch is not only for OLD logs. `session/color` is written
13
- * by the TUI itself on every host generation (session-metadata.ts), and
14
- * 0.1.5's `validateStoredEvents` refuses unknown non-ignorable types at load
15
- * — without this registration a single color pick makes that session
16
- * permanently unresumable (probed: SessionFormatUnsupportedError without the
17
- * vouch, clean load with it). The type stays inert for reconstruction either
18
- * way: it only re-tints the row.
19
- */
20
- export const LEGACY_SESSION_EVENT_TYPES = ['activity/status', 'session/color'];
12
+ /** Informational event vocabulary written by this TUI on the current host. */
13
+ export const TUI_SESSION_EVENT_TYPES = ['session/color'];
21
14
  /** Zstd frame magic number, little-endian (0xFD2FB528). */
22
15
  const ZSTD_MAGIC = 0xfd2fb528;
23
16
  /**
@@ -54,7 +47,7 @@ function isSafeSessionId(sessionId) {
54
47
  * dsh-session-format's `CANONICAL_LOG_FILENAME` (that package is not a
55
48
  * blessed import, so the rule is re-derived here): version zero keeps the
56
49
  * original `session.jsonl`; every later generation carries a lowercase
57
- * numeric `.vN` component (0.1.5 writes `session.v3.jsonl`). Noncanonical,
50
+ * numeric `.vN` component. Noncanonical,
58
51
  * uppercase, leading-zero, and `.v0` names never identify committed
59
52
  * generations. A `.zstd` suffix marks the compressed encoding.
60
53
  */
@@ -130,165 +123,27 @@ export function findSessionLogFile(sessionId) {
130
123
  }
131
124
  return undefined;
132
125
  }
133
- let cachedDecoder;
134
- function decodeStorageRecord(value) {
135
- if (cachedDecoder === undefined) {
136
- cachedDecoder = loadUpstreamDecoder() ?? decodeStorageRecordLocal;
137
- }
138
- return cachedDecoder(value);
139
- }
140
- function loadUpstreamDecoder() {
141
- try {
142
- const req = createRequire(import.meta.url);
143
- const mod = req('@deepseek-ai/dsh-session');
144
- return typeof mod.decodeStorageRecord === 'function'
145
- ? mod.decodeStorageRecord
146
- : undefined;
147
- }
148
- catch {
149
- // No resolvable dsh-session copy from this tree — the local decoder is
150
- // fully self-contained, so reads still work.
151
- return undefined;
152
- }
153
- }
154
126
  /* ------------------------------------------------------------------------- *\
155
- * Local port of the removed dsh-session chunk-rows decoder (0.1.2-rc.1 and
156
- * earlier, lib/types/chunk-rows.js). Storage rows are a durable-encoding
157
- * vocabulary, NOT session events: the packChunks writer folded each run of
158
- * same-block delta chunks into ONE `text-chunks`/`reasoning-chunks`/
159
- * `tool-call-chunks` line. Row-tagged values validate and expand (a malformed
160
- * row throws — it is corrupt storage, and treating it as an event would
161
- * silently drop a whole run); every other value passes through as a single
162
- * event, unvalidated.
127
+ * Bounded, tolerant, read-only log reader behind the session tree
128
+ * (channel.buildSessionTree): 64 KiB chunked I/O, lazy per-frame zstd decode
129
+ * over an RFC 8878 structural frame walk (a torn final frame — crash
130
+ * mid-flush — is dropped as uncommitted, never fatal), event envelopes,
131
+ * an event budget plus a scanned-envelope budget so ignorable-heavy logs
132
+ * cannot multiply the cost of opening the panel, and an inherited-prefix
133
+ * skip so a fork's event budget pays only for its OWN events. It never
134
+ * writes; both stock encodings are read.
163
135
  \* ------------------------------------------------------------------------- */
164
136
  function isRecordValue(value) {
165
137
  return typeof value === 'object' && value !== null;
166
138
  }
167
- /** Exact-key check: `value` has every key in `keys` and nothing else. */
168
- function hasExactKeys(value, keys) {
169
- return Object.keys(value).length === keys.length && keys.every(k => Object.hasOwn(value, k));
170
- }
171
- /** The uniform malformed-row diagnostic. */
172
- function malformedRow(tag, why) {
173
- throw new Error(`malformed ${tag} storage row: ${why}`);
174
- }
175
- /** Validate the shared run-data fields and the payload/dt arity; returns the member payload. */
176
- function validateRunData(tag, data, payloadKey) {
177
- if (typeof data.turn !== 'number' || typeof data.step !== 'number' || typeof data.index !== 'number') {
178
- malformedRow(tag, 'turn/step/index must be numbers');
179
- }
180
- const payload = data[payloadKey];
181
- if (!Array.isArray(payload) || payload.length === 0 || payload.some(entry => typeof entry !== 'string')) {
182
- malformedRow(tag, `${payloadKey} must be a non-empty string array`);
183
- }
184
- const dt = data.dt;
185
- if (!Array.isArray(dt) || dt.some(gap => !Number.isSafeInteger(gap))) {
186
- malformedRow(tag, 'dt must be an array of safe integers');
187
- }
188
- if (dt.length !== payload.length - 1) {
189
- malformedRow(tag, `dt length ${dt.length} does not match ${payload.length} members`);
190
- }
191
- return payload;
192
- }
193
- /** Validate a row-tagged parsed value's envelope and data, throwing on any malformation. */
194
- function validateChunkRow(value, tag) {
195
- if (!hasExactKeys(value, ['type', 'seq0', 'time0', 'data'])) {
196
- malformedRow(tag, 'envelope must be exactly {type, seq0, time0, data}');
197
- }
198
- if (!Number.isSafeInteger(value['seq0']) || value['seq0'] < 0) {
199
- malformedRow(tag, 'seq0 must be a non-negative safe integer');
200
- }
201
- if (!Number.isSafeInteger(value['time0'])) {
202
- malformedRow(tag, 'time0 must be a safe integer');
203
- }
204
- const data = value['data'];
205
- if (!isRecordValue(data))
206
- malformedRow(tag, 'data must be an object');
207
- const row = data;
208
- let payload;
209
- if (tag === 'tool-call-chunks') {
210
- const withName = hasExactKeys(row, ['turn', 'step', 'index', 'id', 'name', 'dt', 'args']);
211
- if (!withName && !hasExactKeys(row, ['turn', 'step', 'index', 'id', 'dt', 'args'])) {
212
- malformedRow(tag, 'data must be exactly {turn, step, index, id, name?, dt, args}');
213
- }
214
- if (typeof row['id'] !== 'string' || (withName && typeof row['name'] !== 'string')) {
215
- malformedRow(tag, 'id (and name when present) must be strings');
216
- }
217
- payload = validateRunData(tag, row, 'args');
218
- }
219
- else {
220
- if (!hasExactKeys(row, ['turn', 'step', 'index', 'dt', 'texts'])) {
221
- malformedRow(tag, 'data must be exactly {turn, step, index, dt, texts}');
222
- }
223
- payload = validateRunData(tag, row, 'texts');
224
- }
225
- // Reconstruction bounds. The encoder only packs runs whose member seqs and
226
- // times are all safe integers, so a running value that leaves safe range is
227
- // outside any encoder's image: float arithmetic would round it to a
228
- // different number than exact arithmetic, a silent corruption. Within safe
229
- // range every step is exact, so the first departure is always caught.
230
- const seq0 = value['seq0'];
231
- if (!Number.isSafeInteger(seq0 + payload.length - 1)) {
232
- malformedRow(tag, 'member seqs must stay safe integers');
233
- }
234
- let time = value['time0'];
235
- for (const gap of row['dt']) {
236
- time += gap;
237
- if (!Number.isSafeInteger(time)) {
238
- malformedRow(tag, 'member times must stay safe integers');
239
- }
240
- }
241
- return { type: tag, seq0, time0: value['time0'], data: row };
242
- }
243
- /** Expand a validated row back into its exact original events, in order. */
244
- function expandChunkRow(row) {
245
- const members = row.type === 'tool-call-chunks' ? row.data.args : row.data.texts;
246
- const events = [];
247
- let time = row.time0;
248
- for (let k = 0; k < members.length; k++) {
249
- if (k > 0)
250
- time += row.data.dt[k - 1];
251
- let chunk;
252
- switch (row.type) {
253
- case 'text-chunks':
254
- chunk = { type: 'text-delta', index: row.data.index, text: members[k] };
255
- break;
256
- case 'reasoning-chunks':
257
- chunk = { type: 'reasoning-delta', index: row.data.index, text: members[k] };
258
- break;
259
- default:
260
- chunk = {
261
- type: 'tool-call-delta',
262
- index: row.data.index,
263
- id: row.data.id,
264
- ...(row.data.name !== undefined ? { name: row.data.name } : {}),
265
- argumentsDelta: members[k],
266
- };
267
- break;
268
- }
269
- // assistant/chunk is gone from the 0.1.5 SessionEvent union; the events
270
- // are reconstructed structurally for the legacy-log replay path.
271
- events.push({
272
- type: 'assistant/chunk',
273
- seq: row.seq0 + k,
274
- time,
275
- data: { turn: row.data.turn, step: row.data.step, chunk },
276
- });
277
- }
278
- return events;
279
- }
280
- /**
281
- * Structural twin of the removed upstream `decodeStorageRecord`: packed
282
- * chunk rows expand, everything else passes through as one event.
283
- */
284
- function decodeStorageRecordLocal(value) {
139
+ /** Current storage keeps complete event envelopes, never top-level packed chunks. */
140
+ function decodeStorageRecord(value) {
285
141
  if (!isRecordValue(value))
286
- return [value];
287
- const tag = value['type'];
288
- if (tag !== 'text-chunks' && tag !== 'reasoning-chunks' && tag !== 'tool-call-chunks') {
289
- return [value];
142
+ throw new Error('session log record is not an object');
143
+ if (['text-chunks', 'reasoning-chunks', 'tool-call-chunks', 'assistant/chunk'].includes(String(value['type']))) {
144
+ throw new Error('unsupported legacy session log; convert it with the current DSH format catalog');
290
145
  }
291
- return expandChunkRow(validateChunkRow(value, tag));
146
+ return [value];
292
147
  }
293
148
  /** I/O slice for streamed log reads. */
294
149
  const READ_CHUNK = 64 * 1024;
@@ -509,7 +364,7 @@ function* zstdFrames(chunks) {
509
364
  * streaming UTF-8 decoder keeps multi-byte characters intact across read
510
365
  * chunks. An unterminated FINAL line is a torn write (a crash mid-append):
511
366
  * the backend's own reader ignores uncommitted records, and so does this
512
- * one — the tree must never show events persistence.load() would not
367
+ * one — the tree must never show events a strict persistence read would not
513
368
  * acknowledge.
514
369
  * @param chunks - Raw file bytes, in order.
515
370
  * @yields Each non-empty newline-terminated line's text, in log order.
@@ -620,19 +475,7 @@ function sniffLogFile(path) {
620
475
  }
621
476
  }
622
477
  }
623
- /**
624
- * Resolve a `persistence.locate()` hint to a readable log. The hint may name
625
- * the physical artifact directly or the backend's LOGICAL name for it (the
626
- * jsonl backend's raw-artifact filename carries no encoding suffix), so the
627
- * compressed twin is probed as well. Since 0.1.5 the hint names the CURRENT
628
- * generation (`session.vN.jsonl[.zstd]`) without touching the filesystem —
629
- * for a session never opened since its format was superseded that file does
630
- * not exist yet, and the authoritative artifact is an older-generation
631
- * sibling in the same directory. Encoding is sniffed from content, never
632
- * inferred from the extension.
633
- * @param hint - Absolute path from SessionPersistence.locate().
634
- * @returns The readable log, or undefined when nothing materialized there.
635
- */
478
+ /** Resolve the exact backend artifact or its compressed twin. */
636
479
  export function resolveLocatedPath(hint) {
637
480
  const direct = existsSync(hint) ? sniffLogFile(hint) : undefined;
638
481
  if (direct !== undefined)
@@ -640,15 +483,7 @@ export function resolveLocatedPath(hint) {
640
483
  const twin = `${hint}.zstd`;
641
484
  if (existsSync(twin))
642
485
  return { path: twin, compressed: true };
643
- // Generation fallback: only when the hint itself is a canonical
644
- // generation name — a foreign backend's logical name must not steer a
645
- // directory scan. The scan stays inside the backend's own session
646
- // directory, so locate's authority over root and workspace key is kept.
647
- const base = hint.slice(hint.lastIndexOf(sep) + 1);
648
- const withoutSuffix = base.endsWith('.zstd') ? base.slice(0, -'.zstd'.length) : base;
649
- if (!GENERATION_LOG_NAME.test(withoutSuffix))
650
- return undefined;
651
- return selectGenerationLog(dirname(hint), false);
486
+ return undefined;
652
487
  }
653
488
  /**
654
489
  * Shared bounded read loop over a located log. See readSessionEventsFromLog
@@ -672,6 +507,8 @@ function readEvents(file, maxEvents, maxScanned, skipBelowSeq) {
672
507
  for (const record of logRecords(fd, file.compressed)) {
673
508
  if (scanned === 0 && isRecordValue(record) && record['type'] === 'session' && Number.isSafeInteger(record['version'])) {
674
509
  formatVersion = record['version'];
510
+ if (formatVersion !== SESSION_FORMAT_VERSION)
511
+ throw new Error('unsupported session log version');
675
512
  }
676
513
  for (const event of decodeStorageRecord(record)) {
677
514
  // The SCAN budget bounds the real cost drivers — I/O, decompression,
@@ -753,8 +590,7 @@ export function readSessionEventsFromFile(path, maxEvents = Number.POSITIVE_INFI
753
590
  * cost-bounded. Built for the session tree (buildSessionTree), whose browse
754
591
  * must satisfy three constraints the strict backend read cannot:
755
592
  *
756
- * - READ-ONLY: the strict backend read (`persistence.inspect` on pre-0.1.5
757
- * hosts, the validated `handle.read` path since) rejects logs carrying
593
+ * - READ-ONLY: the strict handle.read path rejects logs carrying
758
594
  * third-party event types (working-activity's `activity/status`), and
759
595
  * the resume seam's answer (in-process type registration, issue #153)
760
596
  * must not be inverted here: opening a picker never rewrites a history
@@ -772,41 +608,7 @@ export function readSessionEventsFromFile(path, maxEvents = Number.POSITIVE_INFI
772
608
  * is the atomic unit (see logRecords) — the budget bounds work ACROSS
773
609
  * frames, never inside one giant flush.
774
610
  *
775
- * Packed rows are expanded with the backend's own `decodeStorageRecord`:
776
- * the packChunks writer folds each run of same-block delta chunks into ONE
777
- * `text-chunks`/`reasoning-chunks`/`tool-call-chunks` line (the default —
778
- * the finding that motivated this reader's third revision), so a line is a
779
- * storage record, not an event, and seq-less rows must not be dropped as if
780
- * they were headers. The budget counts EXPANDED events, matching the tree's
781
- * per-event cost. Malformed packed rows throw inside decodeStorageRecord —
782
- * corrupt storage degrades to `undefined`, never to silently dropped runs.
783
- *
784
- * Torn-tail semantics mirror the backend's own reader: a plain log's
785
- * unterminated final line (crash mid-append) is uncommitted data and
786
- * ignored, while an unterminated record INSIDE a complete zstd frame is
787
- * corruption and fails the read.
788
- *
789
- * Envelopes with `ignorable: true` are skipped (the read path's own skip
790
- * signal) and the seq-less header row is metadata, not an event. Skipped
791
- * envelopes still count against the SCAN budget (maxScanned): skipping is
792
- * not free — the I/O, decompress and JSON.parse for them have already been
793
- * paid — so an ignorable-heavy log cannot bypass the cost bound by keeping
794
- * its collectible count low. Never throws; returns undefined ONLY when the
795
- * log is absent — an existing-but-undecodable log returns `failed: true`
796
- * so the caller degrades to a placeholder instead of escalating to an
797
- * unbounded strict re-read.
798
- * @param sessionId - Session whose log should be read.
799
- * @param maxEvents - Stop before collecting more than this many events
800
- * (default: unbounded). 0 collects nothing and reports complete: false
801
- * whenever the log holds any collectible event.
802
- * @param maxScanned - Stop after INSPECTING this many envelopes, collected
803
- * or skipped (default: 4× maxEvents + 4096; unbounded when maxEvents is).
804
- * @param skipBelowSeq - Inherited-prefix cutoff (session-tree dedup):
805
- * seq'd events below it are skipped without spending the event budget —
806
- * they still count against maxScanned. `session/title` events are always
807
- * collected (branch-head labels need them; they never become entries).
808
- * @returns The read outcome — events, completeness, real scan cost, and a
809
- * `failed` marker for safety-cap/corruption bailouts.
611
+ * Current event envelopes are read with bounded scan and allocation costs.
810
612
  */
811
613
  export function readSessionEventsFromLog(sessionId, maxEvents = Number.POSITIVE_INFINITY, maxScanned = defaultMaxScanned(maxEvents), skipBelowSeq = 0) {
812
614
  const file = findSessionLogFileAnyEncoding(sessionId);
@@ -814,18 +616,7 @@ export function readSessionEventsFromLog(sessionId, maxEvents = Number.POSITIVE_
814
616
  return undefined;
815
617
  return readEvents(file, maxEvents, maxScanned, skipBelowSeq);
816
618
  }
817
- function seedLengthOfPhysicalHeader(record) {
818
- if (record === null || typeof record !== 'object' || Array.isArray(record))
819
- return undefined;
820
- const rec = record;
821
- if (rec['type'] !== 'session')
822
- return undefined;
823
- const seed = rec['seedLength'];
824
- if (typeof seed === 'number' && Number.isSafeInteger(seed) && seed >= 0 && !Object.is(seed, -0))
825
- return seed;
826
- return undefined;
827
- }
828
- /** The header's `isSeeded` flag (V2/V3 headers carry it instead of `seedLength`). */
619
+ /** The current header's isSeeded flag. */
829
620
  function isSeededPhysicalHeader(record) {
830
621
  if (record === null || typeof record !== 'object' || Array.isArray(record))
831
622
  return false;
@@ -859,15 +650,7 @@ function inheritedMarkerCut(record) {
859
650
  * (the tree detaches the parent edge) instead of an unbounded read.
860
651
  */
861
652
  const INHERITED_CUT_SCAN_LIMIT = 2_000_000;
862
- /**
863
- * Read the exact inherited-prefix length from a log. Pre-V2 physical headers
864
- * carry it as `seedLength` on the first line; V2/V3 headers only have
865
- * `isSeeded`, and the cut is the seq of the `session/end-seed` marker event
866
- * stamped with `inherited: true` — scanned for, bounded, only when the
867
- * header says the session is seeded. Logical list headers never carry the
868
- * cut on any generation. Never guesses from snapshot length, and an
869
- * unseeded or marker-less log reports undefined (no cut to report).
870
- */
653
+ /** Read the exact current inherited cut from a bounded end-seed marker scan. */
871
654
  export function readPhysicalHeaderSeedLength(path) {
872
655
  const file = resolveLocatedPath(path);
873
656
  if (file === undefined)
@@ -882,9 +665,8 @@ export function readPhysicalHeaderSeedLength(path) {
882
665
  for (const record of logRecords(fd, file.compressed)) {
883
666
  if (first) {
884
667
  first = false;
885
- const seed = seedLengthOfPhysicalHeader(record);
886
- if (seed !== undefined)
887
- return seed;
668
+ if (!isRecordValue(record) || record['version'] !== SESSION_FORMAT_VERSION)
669
+ return undefined;
888
670
  if (!isSeededPhysicalHeader(record))
889
671
  return undefined;
890
672
  seeded = true;
@@ -937,7 +719,7 @@ function decodeEvents(buf) {
937
719
  }
938
720
  if (offsets.length === 0)
939
721
  throw new Error('no zstd frame found');
940
- return offsets.flatMap((start, i) => {
722
+ const events = offsets.flatMap((start, i) => {
941
723
  const end = i + 1 < offsets.length ? offsets[i + 1] : buf.length;
942
724
  const text = zstdDecompressSync(buf.subarray(start, end)).toString('utf8');
943
725
  return text
@@ -951,26 +733,14 @@ function decodeEvents(buf) {
951
733
  return parsed;
952
734
  });
953
735
  });
736
+ const header = events[0];
737
+ if (header?.['type'] === 'session' && header['version'] !== SESSION_FORMAT_VERSION) {
738
+ throw new Error('unsupported session log version');
739
+ }
740
+ return events;
954
741
  }
955
- /**
956
- * Register every {@link LEGACY_SESSION_EVENT_TYPES} type as known in EVERY
957
- * reachable KNOWN_SESSION_EVENT_TYPES copy, ahead of the strict read path
958
- * (`agents.resume` seed validation, `persistence.load`). Idempotent; never
959
- * throws.
960
- *
961
- * Why a walk instead of a single import: a runtime can load dsh-session
962
- * more than once (CLI tree vs profile tree, version overlap during
963
- * upgrades, pnpm peer-context splits), and the strict validator — which
964
- * lives in the dsh-session-persistence package — consults only ITS OWN
965
- * tree's copy. Registering through one import leaves the other trees'
966
- * copies untouched. So from EACH base anchor (this module = the dsh-tui
967
- * tree, the process entry point = the launcher/CLI tree) the walk
968
- * registers the tree's own dsh-session AND steps one edge further:
969
- * resolve the validator package from that same tree, then register the
970
- * dsh-session copy the validator's entry resolves. A branch that cannot
971
- * be resolved simply is not there; resolved module paths are deduped.
972
- */
973
- export function ensureLegacySessionEventTypes() {
742
+ /** Register current TUI-owned types in the profile and host validator copies. */
743
+ export function registerTuiSessionEventTypes() {
974
744
  const roots = [import.meta.url, process.argv[1]].filter((anchor) => typeof anchor === 'string' && anchor.length > 0);
975
745
  const visitedAnchors = new Set();
976
746
  const registeredCopies = new Set();
@@ -990,7 +760,7 @@ export function ensureLegacySessionEventTypes() {
990
760
  if (!registeredCopies.has(copy)) {
991
761
  registeredCopies.add(copy);
992
762
  const mod = req(copy);
993
- for (const type of LEGACY_SESSION_EVENT_TYPES) {
763
+ for (const type of TUI_SESSION_EVENT_TYPES) {
994
764
  mod.KNOWN_SESSION_EVENT_TYPES?.add(type);
995
765
  }
996
766
  }
@@ -1011,8 +781,8 @@ export function ensureLegacySessionEventTypes() {
1011
781
  /**
1012
782
  * Read a session's display title from its persisted log, tolerantly.
1013
783
  *
1014
- * Why not `persistence.load()`: the backend validates every event against
1015
- * KNOWN_SESSION_EVENT_TYPES and throws the WHOLE load when a third-party
784
+ * Why not a strict persistence read: the backend validates every event against
785
+ * KNOWN_SESSION_EVENT_TYPES and rejects the entire read when a third-party
1016
786
  * plugin wrote an unmarked unknown type. A picker label is
1017
787
  * read-only UI state: decoding frames directly here keeps titles working
1018
788
  * for logs the strict path refuses, now and for future plugin event types.