@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.
- package/cordis.patch.yml +12 -131
- package/lib/types/adapter/channel/session-projection.d.ts.map +1 -1
- package/lib/types/adapter/channel/session-projection.js +0 -7
- package/lib/types/adapter/ports/channel-view.d.ts +1 -1
- package/lib/types/adapter/ports/channel-view.d.ts.map +1 -1
- package/lib/types/dsh-adapter/bundled-presets.d.ts +3 -8
- package/lib/types/dsh-adapter/bundled-presets.d.ts.map +1 -1
- package/lib/types/dsh-adapter/bundled-presets.js +5 -20
- package/lib/types/dsh-adapter/channel/binding-events.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/binding-events.js +2 -9
- package/lib/types/dsh-adapter/channel/external-commands.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/external-commands.js +9 -33
- package/lib/types/dsh-adapter/channel/local-actions.d.ts +2 -1
- package/lib/types/dsh-adapter/channel/local-actions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/local-actions.js +6 -2
- package/lib/types/dsh-adapter/channel/model-actions.js +3 -3
- package/lib/types/dsh-adapter/channel/model-switch.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/model-switch.js +0 -1
- package/lib/types/dsh-adapter/channel/permissions.d.ts +1 -6
- package/lib/types/dsh-adapter/channel/permissions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/permissions.js +2 -54
- package/lib/types/dsh-adapter/channel/projection.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/projection.js +15 -70
- package/lib/types/dsh-adapter/channel/session-fork.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-fork.js +0 -1
- package/lib/types/dsh-adapter/channel/session-metadata.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-metadata.js +1 -4
- package/lib/types/dsh-adapter/channel/session-resume.js +2 -2
- package/lib/types/dsh-adapter/channel/session-rewind.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-rewind.js +0 -1
- package/lib/types/dsh-adapter/channel/session-tree-actions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-tree-actions.js +1 -2
- package/lib/types/dsh-adapter/channel/session-tree.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/session-tree.js +9 -62
- package/lib/types/dsh-adapter/channel/skill-catalog.js +1 -1
- package/lib/types/dsh-adapter/channel/subagent-projection.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/subagent-projection.js +2 -8
- package/lib/types/dsh-adapter/channel/transcript.d.ts +0 -16
- package/lib/types/dsh-adapter/channel/transcript.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel/transcript.js +0 -43
- package/lib/types/dsh-adapter/channel.js +2 -2
- package/lib/types/dsh-adapter/compat/index.d.ts +5 -25
- package/lib/types/dsh-adapter/compat/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/index.js +5 -25
- package/lib/types/dsh-adapter/compat/liveSession.d.ts +12 -23
- package/lib/types/dsh-adapter/compat/liveSession.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/liveSession.js +15 -68
- package/lib/types/dsh-adapter/compat/messages.d.ts +1 -3
- package/lib/types/dsh-adapter/compat/messages.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/messages.js +2 -10
- package/lib/types/dsh-adapter/compat/persistence.d.ts +2 -3
- package/lib/types/dsh-adapter/compat/persistence.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/persistence.js +9 -14
- package/lib/types/dsh-adapter/compat/sessionLog.d.ts +10 -144
- package/lib/types/dsh-adapter/compat/sessionLog.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/sessionLog.js +45 -275
- package/lib/types/dsh-adapter/compat/settings.d.ts +4 -10
- package/lib/types/dsh-adapter/compat/settings.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/settings.js +6 -23
- package/lib/types/dsh-adapter/compat/shell.d.ts +2 -3
- package/lib/types/dsh-adapter/compat/shell.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/shell.js +2 -7
- package/lib/types/dsh-adapter/contract.d.ts +4 -34
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
- package/lib/types/dsh-adapter/contract.js +8 -83
- package/lib/types/dsh-adapter/packaged-presets.d.ts +0 -19
- package/lib/types/dsh-adapter/packaged-presets.d.ts.map +1 -1
- package/lib/types/dsh-adapter/packaged-presets.js +1 -83
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +25 -168
- package/lib/types/dsh-adapter/preset-resolution.d.ts +1 -22
- package/lib/types/dsh-adapter/preset-resolution.d.ts.map +1 -1
- package/lib/types/dsh-adapter/preset-resolution.js +0 -34
- package/lib/types/dsh-adapter/presets.d.ts +1 -4
- package/lib/types/dsh-adapter/presets.d.ts.map +1 -1
- package/lib/types/dsh-adapter/presets.js +3 -6
- package/lib/types/dsh-adapter/questions-answerer.d.ts +5 -43
- package/lib/types/dsh-adapter/questions-answerer.d.ts.map +1 -1
- package/lib/types/dsh-adapter/questions-answerer.js +7 -63
- package/lib/types/dsh-adapter/sessionTree.d.ts +0 -19
- package/lib/types/dsh-adapter/sessionTree.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sessionTree.js +2 -112
- package/lib/types/dsh-adapter/sessions/header.d.ts +2 -2
- package/lib/types/dsh-adapter/sessions/header.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sessions/header.js +2 -8
- package/lib/types/dsh-adapter/sessions/list.d.ts +7 -28
- package/lib/types/dsh-adapter/sessions/list.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sessions/list.js +48 -103
- package/lib/types/dsh-adapter/sessions/store.d.ts +1 -2
- package/lib/types/dsh-adapter/sessions/store.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sessions/store.js +7 -10
- package/lib/types/dsh-adapter/sharp.d.ts.map +1 -1
- package/lib/types/dsh-adapter/sharp.js +0 -1
- package/lib/types/dsh-adapter/subagents.d.ts +1 -2
- package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
- package/lib/types/dsh-adapter/subagents.js +1 -12
- package/lib/types/dsh-adapter/trajectory/aggregate.d.ts +1 -1
- package/lib/types/dsh-adapter/trajectory/aggregate.js +1 -1
- package/lib/types/dsh-adapter/trajectory/guards.d.ts +5 -22
- package/lib/types/dsh-adapter/trajectory/guards.d.ts.map +1 -1
- package/lib/types/dsh-adapter/trajectory/guards.js +5 -23
- package/lib/types/dsh-adapter/trajectory/projection.d.ts +5 -21
- package/lib/types/dsh-adapter/trajectory/projection.d.ts.map +1 -1
- package/lib/types/dsh-adapter/trajectory/projection.js +4 -38
- package/lib/types/dsh-adapter/trajectory/types.d.ts +1 -1
- package/lib/types/dsh-adapter/trajectory/types.d.ts.map +1 -1
- package/lib/types/i18n.d.ts +2 -22
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +2 -10
- package/lib/types/presetPrefs.d.ts +1 -3
- package/lib/types/presetPrefs.d.ts.map +1 -1
- package/lib/types/presetPrefs.js +2 -4
- package/node_modules/@askdkc/dsh-auth/README.ja.md +28 -9
- package/node_modules/@askdkc/dsh-auth/README.md +19 -8
- package/node_modules/@askdkc/dsh-auth/dsh-plugin.json +3 -3
- package/node_modules/@askdkc/dsh-auth/lib/THIRD_PARTY_NOTICES.txt +168 -0
- package/node_modules/@askdkc/dsh-auth/lib/abortable.d.ts +2 -0
- package/node_modules/@askdkc/dsh-auth/lib/abortable.js +9 -0
- package/node_modules/@askdkc/dsh-auth/lib/auth-contract.d.ts +66 -0
- package/node_modules/@askdkc/dsh-auth/lib/auth-contract.js +1 -0
- package/node_modules/@askdkc/dsh-auth/lib/command.js +15 -1
- package/node_modules/@askdkc/dsh-auth/lib/credentials.d.ts +8 -14
- package/node_modules/@askdkc/dsh-auth/lib/credentials.js +1 -1
- package/node_modules/@askdkc/dsh-auth/lib/index.d.ts +8 -28
- package/node_modules/@askdkc/dsh-auth/lib/index.js +67 -65
- package/node_modules/@askdkc/dsh-auth/lib/interaction.d.ts +5 -5
- package/node_modules/@askdkc/dsh-auth/lib/opencode-adapter.d.ts +37 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-adapter.js +197 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-catalog.d.ts +72 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-catalog.js +252 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-codec.d.ts +6 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-codec.js +93 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-owned.generated.d.ts +113 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-owned.generated.js +2866 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-replay.d.ts +15 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-replay.js +141 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-sdk.d.ts +5 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-sdk.js +43465 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-sdk.js.map +7 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-transport.d.ts +5 -0
- package/node_modules/@askdkc/dsh-auth/lib/opencode-transport.js +69 -0
- package/node_modules/@askdkc/dsh-auth/lib/pi-routes.d.ts +32 -0
- package/node_modules/@askdkc/dsh-auth/lib/pi-routes.js +45 -0
- package/node_modules/@askdkc/dsh-auth/lib/profiles.d.ts +2 -20
- package/node_modules/@askdkc/dsh-auth/lib/profiles.js +6 -41
- package/node_modules/@askdkc/dsh-auth/lib/routes.d.ts +20 -0
- package/node_modules/@askdkc/dsh-auth/lib/routes.js +13 -0
- package/node_modules/@askdkc/dsh-auth/lib/service.d.ts +11 -2
- package/node_modules/@askdkc/dsh-auth/lib/service.js +26 -16
- package/node_modules/@askdkc/dsh-auth/package.json +26 -15
- package/package.json +100 -96
- package/lib/types/dsh-adapter/providerGuard.d.ts +0 -75
- package/lib/types/dsh-adapter/providerGuard.d.ts.map +0 -1
- package/lib/types/dsh-adapter/providerGuard.js +0 -104
- package/lib/types/migrationPrefs.d.ts +0 -60
- package/lib/types/migrationPrefs.d.ts.map +0 -1
- package/lib/types/migrationPrefs.js +0 -97
- package/node_modules/@askdkc/dsh-auth/lib/fresh-models.d.ts +0 -59
- package/node_modules/@askdkc/dsh-auth/lib/fresh-models.js +0 -246
- package/node_modules/@askdkc/dsh-auth/lib/opencode-snapshot.generated.d.ts +0 -321
- package/node_modules/@askdkc/dsh-auth/lib/opencode-snapshot.generated.js +0 -528
- 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
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
244
|
-
* KNOWN_SESSION_EVENT_TYPES and
|
|
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":"
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
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
|
-
/**
|
|
168
|
-
function
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
886
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1015
|
-
* KNOWN_SESSION_EVENT_TYPES and
|
|
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.
|