@deepseek-ai/dsh-session-snapshot 0.1.2-alpha.2

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.
@@ -0,0 +1,23 @@
1
+ //#region lib/types/invariant.js
2
+ /**
3
+ * Package-owned invariant companion for `@deepseek-ai/dsh-session-snapshot`.
4
+ * @module @deepseek-ai/dsh-session-snapshot/invariant
5
+ */
6
+ const PACKAGE_NAME = "@deepseek-ai/dsh-session-snapshot";
7
+ /** Cordis companion plugin name. */
8
+ const name = "session-snapshot-invariant";
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ["invariants"];
11
+ /**
12
+ * No runtime invariant: this test-support package owns no production event stream or mutable data;
13
+ * consuming test suites exercise its behavior.
14
+ */
15
+ const install = () => {};
16
+ /**
17
+ * Register this package's invariant companion.
18
+ * @param ctx - Cordis context carrying the invariant service.
19
+ * @returns the installed registration's disposer after setup succeeds.
20
+ */
21
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
22
+ //#endregion
23
+ export { apply, inject, name };
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Shared subprocess harness for ACP snapshot suites. A library module driven by
3
+ * the suite factory in ./suite.ts (and directly by harness-level specs); each
4
+ * profile adapter names its own agent-under-test paths.
5
+ *
6
+ * It boots the REAL agent bin subprocess via the cordis Loader (so the
7
+ * export-shape bug class stays guarded — see docs/postmortem/0001), drives it
8
+ * over real ACP JSON-RPC stdio with a deterministic input script, tees raw
9
+ * stdout (for the expected-output and purity checks) into an SDK client app,
10
+ * and — in record mode — harvests the persisted session JSONL after a graceful
11
+ * shutdown flush. The pure normalizers in ./normalize.ts turn the captured
12
+ * stdout frames and the session-log events into stable, snapshot-able text.
13
+ *
14
+ * See .agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md.
15
+ *
16
+ * @module @deepseek-ai/dsh-session-snapshot/harness
17
+ */
18
+ import { type ContentBlock as AcpContentBlock } from '@agentclientprotocol/sdk';
19
+ import { type AgentUnderTest } from './launcher.ts';
20
+ import { type WorkspaceSnapshotEntry } from './workspace.ts';
21
+ export type { AgentUnderTest } from './launcher.ts';
22
+ /**
23
+ * One step of a scenario's deterministic input script (`input.json`). The
24
+ * harness interprets these in order. `newSession` captures the server-issued
25
+ * (random) session id into a `{{sessionId}}` variable that later steps
26
+ * reference, since a committed file cannot know the id in advance.
27
+ *
28
+ * `promptAndCancel` starts a prompt without awaiting completion, waits for a
29
+ * readiness condition, then cancels and awaits completion. Its optional
30
+ * `waitForFile` observes a cwd-relative marker; otherwise it waits for the
31
+ * durable turn start. The standalone `waitForFile` holds the next script step
32
+ * behind the same marker.
33
+ * `promptAndWaitForAgentMessage` arms an exact text-chunk waiter before sending
34
+ * the prompt, then keeps the application live until that later update arrives.
35
+ * `waitForTurnStart` waits for an open durable turn, optionally at or beyond a
36
+ * specified turn number. `waitForTurnEnd` holds the subprocess open until the
37
+ * selected session's latest complete raw-JSONL turn boundary is `turn/end`.
38
+ * `waitForGoalPhase` waits for the latest durable goal snapshot to reach one phase.
39
+ * `waitForInboxMessage` waits for inserted inbox text containing a scenario marker.
40
+ * `waitForSubagentTurnEnd` waits until one background child has persisted a
41
+ * closed model-work turn after its own descriptor; child progress has no ACP
42
+ * update to wait on.
43
+ * `waitForTitleAfterTurnEnd` additionally waits for a later durable title.
44
+ * `waitForEventAfterTurnEnd` waits until a complete record of the given event
45
+ * type follows the latest closed turn — for scenarios whose asserted state
46
+ * (e.g. a goal pause) is appended only after cancellation reaches idle.
47
+ * A standalone `cancel` may also wait for a cwd-relative readiness marker.
48
+ * All wait timeouts default to 10s.
49
+ */
50
+ export type InputStep = {
51
+ op: 'initialize';
52
+ } | {
53
+ op: 'newSession';
54
+ } | {
55
+ op: 'newSessionExpectError';
56
+ additionalDirectories?: string[];
57
+ } | {
58
+ op: 'prompt';
59
+ text: string;
60
+ } | {
61
+ op: 'promptContent';
62
+ content: AcpContentBlock[];
63
+ } | {
64
+ op: 'promptAndWaitForAgentMessage';
65
+ text: string;
66
+ waitForText: string;
67
+ } | {
68
+ op: 'promptExpectError';
69
+ text: string;
70
+ } | {
71
+ op: 'promptAndCancel';
72
+ text: string;
73
+ waitForFile?: {
74
+ path: string;
75
+ timeoutMs?: number;
76
+ };
77
+ } | {
78
+ op: 'waitForFile';
79
+ path: string;
80
+ timeoutMs?: number;
81
+ } | {
82
+ op: 'waitForTurnStart';
83
+ minimumTurn?: number;
84
+ timeoutMs?: number;
85
+ } | {
86
+ op: 'waitForTurnEnd';
87
+ timeoutMs?: number;
88
+ } | {
89
+ op: 'waitForSubagentTurnEnd';
90
+ child?: number;
91
+ minimumTurn?: number;
92
+ timeoutMs?: number;
93
+ } | {
94
+ op: 'waitForGoalPhase';
95
+ phase: 'active' | 'paused' | 'blocked' | 'complete';
96
+ timeoutMs?: number;
97
+ } | {
98
+ op: 'waitForInboxMessage';
99
+ text: string;
100
+ timeoutMs?: number;
101
+ } | {
102
+ op: 'waitForTitleAfterTurnEnd';
103
+ timeoutMs?: number;
104
+ } | {
105
+ op: 'waitForEventAfterTurnEnd';
106
+ type: string;
107
+ timeoutMs?: number;
108
+ } | {
109
+ op: 'cancel';
110
+ waitForFile?: {
111
+ path: string;
112
+ timeoutMs?: number;
113
+ };
114
+ };
115
+ /** A scenario's `input.json`: an ordered list of input steps. */
116
+ export interface InputScript {
117
+ steps: InputStep[];
118
+ /**
119
+ * Ordered answers for the agent's `session/request_permission` round-trips,
120
+ * consumed FIFO — the Nth request gets the Nth answer. Each answer selects
121
+ * by option KIND: option ids are agent-issued randoms a committed script
122
+ * cannot know, while kinds are the ACP-stable vocabulary, so the client maps
123
+ * kind → the offered `optionId` at answer time. A request beyond the queue
124
+ * (or with no queue at all) is answered `cancelled` — the stub behavior a
125
+ * scenario without approvals relies on. A scripted kind the request does
126
+ * not offer REJECTS the run: the scenario scripted an impossible selection,
127
+ * and {@link runScenario} throws once the in-flight step settles (the
128
+ * agent itself just sees `cancelled`, so it cannot absorb the bug).
129
+ */
130
+ permissionAnswers?: PermissionAnswer[];
131
+ }
132
+ /** One scripted answer to a permission request: which offered option kind to select. */
133
+ export interface PermissionAnswer {
134
+ /** The `PermissionOption.kind` to select (`allow_once`, `reject_always`, …). */
135
+ kind: 'allow_once' | 'allow_always' | 'reject_once' | 'reject_always';
136
+ }
137
+ /** One harvested session log plus the identifying facts off its header line. */
138
+ export interface HarvestedLog {
139
+ /** The recorded session id (header `id`). */
140
+ id: string;
141
+ /** Session creation time (header `createdAt`) — the child-ordering key. */
142
+ createdAt: number;
143
+ /** The parent session id, if this log is a subagent child (header `parentSession`). */
144
+ parentSession?: string;
145
+ /** The full `.jsonl` file content. */
146
+ content: string;
147
+ }
148
+ /** The result of running a scenario: raw stdout + the harvested session log(s). */
149
+ export interface RunResult {
150
+ /** Raw stdout bytes (decoded utf8), every newline-delimited JSON-RPC frame. */
151
+ rawStdout: string;
152
+ /** stderr (for diagnostics on failure). */
153
+ stderr: string;
154
+ /** The session id the server issued (undefined if no session was created). */
155
+ sessionId?: string;
156
+ /** The generated cwd the session ran in (the bash workspace). */
157
+ cwd: string;
158
+ /** Filesystem-resolved spellings of {@link cwd} that child processes may report. */
159
+ cwdAliases: string[];
160
+ /** User-visible workspace state after committed and runtime-only setup. */
161
+ initialWorkspace: WorkspaceSnapshotEntry[];
162
+ /** User-visible workspace state after the controlled interface has settled. */
163
+ finalWorkspace: WorkspaceSnapshotEntry[];
164
+ /**
165
+ * Every persisted session log harvested after the run, ordered primary-first:
166
+ * the top-level (parent) session — the one with no `parentSession` — then each
167
+ * subagent child by ascending `createdAt`. A single-session scenario harvests
168
+ * exactly one; a nested-agent scenario harvests the parent plus one per child.
169
+ */
170
+ sessionLogs: HarvestedLog[];
171
+ }
172
+ /** How to run one scenario: the agent to boot, the mode, and the fixture wiring. */
173
+ export interface RunOptions {
174
+ /** The agent composition to boot. */
175
+ agent: AgentUnderTest;
176
+ /** `replay` (default, keyless) or `record` (real API, harvests the log). */
177
+ mode: 'replay' | 'record';
178
+ /** Scenario-specific deployment environment layered into the subprocess. */
179
+ env?: NodeJS.ProcessEnv;
180
+ /** The recorded session JSONL fixture path (replay reads it; record writes near it). */
181
+ fixtureFile: string;
182
+ /** Optional sidecar override path (replay). */
183
+ overrideFile?: string;
184
+ /**
185
+ * Recorded SUBAGENT child-session fixture paths (replay). A nested-agent
186
+ * scenario ships one per child (`session.1.jsonl`, …); the harness forwards
187
+ * them to `dsh-llm-replay` via `$DSH_SNAPSHOT_CHILD_FILES` so each child
188
+ * session replays from its own recorded script. Empty for single-session
189
+ * scenarios. Ignored in record mode (children are harvested, not replayed).
190
+ */
191
+ childFiles?: string[];
192
+ /**
193
+ * Optional `<scenario>/workspace/` directory whose contents are copied into
194
+ * the generated cwd BEFORE the run — the standard way to seed files the agent
195
+ * operates on (a file to read, edit, or grep). Absent for scenarios that
196
+ * start from an empty workspace.
197
+ */
198
+ workspaceDir?: string;
199
+ /**
200
+ * Optional final workspace preparation, run after {@link workspaceDir} is
201
+ * copied and before the agent starts. This is for fixtures that cannot be
202
+ * represented portably in Git (for example, a POSIX-only filename that is
203
+ * invalid on Windows); ordinary seeded files belong in `workspaceDir`.
204
+ */
205
+ prepareWorkspace?: (cwd: string) => void | Promise<void>;
206
+ /**
207
+ * Parent directory for the generated session cwd. Defaults to
208
+ * `os.tmpdir()`. A scenario that must distinguish its workspace from the
209
+ * sandbox's always-writable temporary roots can place the generated child
210
+ * under `os.homedir()` instead. The harness removes only that generated
211
+ * child, never the supplied parent.
212
+ */
213
+ workspaceParent?: string;
214
+ /**
215
+ * Alternate live profile patch (absolute), overriding
216
+ * {@link AgentUnderTest.configPath} for this run. Its basename still ends
217
+ * in `cordis.yml` so the launcher can select the replay sibling.
218
+ */
219
+ configPath?: string;
220
+ }
221
+ /**
222
+ * Derive one stable, fixed-length spill root owned by this scenario.
223
+ * Windows uses a two-character-shorter root because drive resolution adds its drive prefix.
224
+ * @param fixtureFile - The scenario fixture whose parent directory provides the stable identity.
225
+ * @param platform - the host platform, injectable for unit coverage.
226
+ * @returns the root-relative snapshot spill directory.
227
+ */
228
+ export declare function snapshotSpillRoot(fixtureFile: string, platform?: NodeJS.Platform): string;
229
+ /**
230
+ * Run a scenario end-to-end against a freshly-spawned subprocess. Owns the
231
+ * child and its generated dirs; always tears them down. Returns the captured stdout
232
+ * and (record mode) the harvested session-log path.
233
+ *
234
+ * @param input The scenario's input script (steps + optional permission answers).
235
+ * @param opts The agent to boot, the mode, and the fixture wiring.
236
+ * @returns The captured stdout/stderr, session id, generated cwd, and harvested logs.
237
+ */
238
+ export declare function runScenario(input: InputScript, opts: RunOptions): Promise<RunResult>;
239
+ //# sourceMappingURL=harness.d.ts.map
@@ -0,0 +1,8 @@
1
+ /** Relationship-preserving identity redaction for committed session snapshots. */
2
+ /**
3
+ * Replace volatile opaque ids while preserving equality relationships across a parent and its child logs.
4
+ * @param logs - one scenario's primary-first session JSONL fixtures.
5
+ * @returns compact JSONL with typed first-seen identity tokens.
6
+ */
7
+ export declare function redactSessionSnapshotIds(logs: readonly string[]): string[];
8
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Session-log snapshot support behind the keyless snapshot tier
3
+ * (`pnpm run test:snapshot`). The current ACP adapter has four layers: the
4
+ * shared subprocess/client launcher ({@link launchAcpTestAgent}), the scripted
5
+ * scenario harness ({@link runScenario}), the pure expected-output normalizers
6
+ * ({@link normalizeStdout} / {@link normalizeSessionLog} /
7
+ * {@link scrubRequestHeaders} / {@link scrubSystemPrompts}), and the suite
8
+ * factory ({@link defineAcpSnapshotSuite}) that registers a scenario table as a
9
+ * full describe/it tree. Transport-neutral normalizers and fixture invariants
10
+ * remain reusable by other profile adapters. Ordinary ACP e2e tests can use the launcher directly;
11
+ * the ACP corpus adapter supplies only its {@link AgentUnderTest} paths,
12
+ * snapshots directory, and {@link Scenario} table.
13
+ *
14
+ * NOTE: ./suite.ts imports vitest, so this package is importable only inside a
15
+ * vitest run — a support-tier constraint stated in the README.
16
+ *
17
+ * @module @deepseek-ai/dsh-session-snapshot
18
+ */
19
+ export { redactSessionSnapshotIds, } from './identity.ts';
20
+ export { runScenario, snapshotSpillRoot, type HarvestedLog, type InputScript, type InputStep, type PermissionAnswer, type RunOptions, type RunResult, } from './harness.ts';
21
+ export { launchAcpTestAgent, materializeProfilePatch, type AcpTestLaunchOptions, type AgentUnderTest, type LaunchedAcpTestAgent, } from './launcher.ts';
22
+ export { extractSnapshotSpillPaths, normalizeSessionLog, normalizeSessionSnapshot, normalizeSessionSnapshots, normalizeStdout, scrubRequestHeaders, scrubSessionSnapshot, scrubSystemPrompts, scrubToolSchemas, tokenizeSessionFixtureCwd, type CwdPathMode, type NormalizeContext, type NormalizeOptions, } from './normalize.ts';
23
+ export { parseSnapshotManifest, type SnapshotHeaderManifest, type SnapshotInputAttachment, type SnapshotInputManifest, type SnapshotManifest, type SnapshotPermission, type SnapshotPlatform, type SnapshotProfile, type SnapshotRecording, type SnapshotReplayManifest, type SnapshotSessionReference, type SnapshotWorkspaceManifest, } from './manifest.ts';
24
+ export { formatSystemPromptSnapshot, formatToolSchemasSnapshot, fixtureContext, headerChangeCount, defineAcpSnapshotSuite, normalizedHeaders, normalizedSystemPrompts, normalizedToolSchemas, parseToolSchemasSnapshot, refreshFixtureReplacements, restorePinnedToolSchemas, sessionFixtureNames, stabilizeFixtureMessageIds, stabilizeRefreshLog, type Scenario, type SnapshotSuiteOptions, } from './suite.ts';
25
+ export { captureExpectedWorkspaceSnapshot, captureWorkspaceSnapshot, EMPTY_WORKSPACE_MARKER, type CaptureWorkspaceSnapshotOptions, type WorkspaceBinaryFileSnapshot, type WorkspaceEmptyDirectorySnapshot, type WorkspaceSnapshotEntry, type WorkspaceSymlinkSnapshot, type WorkspaceTextFileSnapshot, } from './workspace.ts';
26
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `@deepseek-ai/dsh-session-snapshot`.
3
+ * @module @deepseek-ai/dsh-session-snapshot/invariant
4
+ */
5
+ import type { Context } from '@deepseek-ai/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "session-snapshot-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
16
+ //# sourceMappingURL=invariant.d.ts.map
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Shared launcher for ACP tests that drive an agent subprocess over JSON-RPC
3
+ * stdio. It owns source-or-built launch resolution, workspace environment,
4
+ * stdout tee, SDK client, update collection, permission fallback, and process
5
+ * shutdown so e2e and snapshot suites do not each reconstruct that boundary.
6
+ *
7
+ * @module @deepseek-ai/dsh-session-snapshot/launcher
8
+ */
9
+ import { type ChildProcessWithoutNullStreams } from 'node:child_process';
10
+ import { type CancelNotification, type CloseSessionRequest, type CloseSessionResponse, type InitializeRequest, type InitializeResponse, type ListSessionsRequest, type ListSessionsResponse, type NewSessionRequest, type NewSessionResponse, type PromptRequest, type PromptResponse, type RequestPermissionRequest, type RequestPermissionResponse, type ResumeSessionRequest, type ResumeSessionResponse, type SetSessionConfigOptionRequest, type SetSessionConfigOptionResponse, type SessionNotification } from '@agentclientprotocol/sdk';
11
+ /** The source/built entry, profile patch, and workspace tsconfig an ACP test boots. */
12
+ export interface AgentUnderTest {
13
+ /** The source bin entry; product suites use `apps/cli/src/bin.ts`. */
14
+ binScript: string;
15
+ /** Explicit built-mode entry for fixtures whose source path is not under `src/`. */
16
+ libBinScript?: string | undefined;
17
+ /** Base config or profile patch loaded by the bin. */
18
+ configPath: string;
19
+ /** Named dsh profile; omitted only for test-only fake bins with their own config grammar. */
20
+ profile?: string;
21
+ /** The repo tsconfig whose paths resolve unbuilt workspace imports. */
22
+ tsconfigPath: string;
23
+ }
24
+ /** Options for one ACP test subprocess. */
25
+ export interface AcpTestLaunchOptions {
26
+ /** The agent composition to boot. */
27
+ agent: AgentUnderTest;
28
+ /** Process cwd and default session-home root. */
29
+ cwd: string;
30
+ /** Alternate leaf config for this launch. */
31
+ configPath?: string;
32
+ /** Extra environment values layered over the parent environment. */
33
+ env?: NodeJS.ProcessEnv;
34
+ /** Permission handler; omitted requests fail closed as `cancelled`. */
35
+ requestPermission?: (params: RequestPermissionRequest) => Promise<RequestPermissionResponse>;
36
+ }
37
+ /** Stable ACP methods used by the subprocess test harness. */
38
+ export interface AcpTestClient {
39
+ readonly closed: Promise<void>;
40
+ initialize: (params: InitializeRequest) => Promise<InitializeResponse>;
41
+ newSession: (params: NewSessionRequest) => Promise<NewSessionResponse>;
42
+ listSessions: (params: ListSessionsRequest) => Promise<ListSessionsResponse>;
43
+ resumeSession: (params: ResumeSessionRequest) => Promise<ResumeSessionResponse>;
44
+ closeSession: (params: CloseSessionRequest) => Promise<CloseSessionResponse>;
45
+ setSessionConfigOption: (params: SetSessionConfigOptionRequest) => Promise<SetSessionConfigOptionResponse>;
46
+ prompt: (params: PromptRequest) => Promise<PromptResponse>;
47
+ cancel: (params: CancelNotification) => Promise<void>;
48
+ }
49
+ /** A running ACP test process and its captured client-side outputs. */
50
+ export interface LaunchedAcpTestAgent {
51
+ /** The child process, exposed for process-level assertions. */
52
+ child: ChildProcessWithoutNullStreams;
53
+ /** Resolve when the OS spawns the child; reject with its asynchronous spawn failure. */
54
+ spawned: Promise<void>;
55
+ /** The SDK connection backed by the child's stdio. */
56
+ client: AcpTestClient;
57
+ /** Session updates in receive order. */
58
+ updates: SessionNotification['update'][];
59
+ /** Decode all stdout bytes captured so far. */
60
+ rawStdout(): string;
61
+ /** Decode all stderr chunks captured so far. */
62
+ stderr(): string;
63
+ /** Resolve when a future session update matches the predicate. */
64
+ waitForUpdate(match: (update: SessionNotification['update']) => boolean): Promise<SessionNotification['update']>;
65
+ /** Close the process and drain its streams and callbacks; rejects promptly if fallback termination is refused. */
66
+ close(signal?: NodeJS.Signals): Promise<void>;
67
+ }
68
+ /**
69
+ * Boot an ACP agent subprocess and connect an SDK client to its stdio.
70
+ *
71
+ * @param options Agent paths, cwd, environment, and optional permission handler.
72
+ * @returns The running process, connected client, captures, and shutdown handle.
73
+ */
74
+ export declare function launchAcpTestAgent(options: AcpTestLaunchOptions): LaunchedAcpTestAgent;
75
+ /**
76
+ * Copy one authored patch into the launch cwd with relative plugin names made absolute.
77
+ * @param source - authored profile patch path.
78
+ * @param cwd - isolated process cwd whose profile fallback receives package links.
79
+ * @param targetDir - existing directory that owns the materialized patch.
80
+ * @param index - stable patch ordinal used in the output filename.
81
+ * @returns absolute materialized patch path.
82
+ */
83
+ export declare function materializeProfilePatch(source: string, cwd: string, targetDir: string, index: number): string;
84
+ //# sourceMappingURL=launcher.d.ts.map
@@ -0,0 +1,98 @@
1
+ /** Parse and validate one recorded-session snapshot manifest. */
2
+ /** Public `dsh` profile used to control a recorded-session scenario. */
3
+ export type SnapshotProfile = 'headless' | 'sdk' | 'acp' | 'web';
4
+ /** How a canonical session may be regenerated. */
5
+ export type SnapshotRecording = 'live' | 'authored';
6
+ /** Request-header ownership metadata for one composition. */
7
+ export interface SnapshotHeaderManifest {
8
+ /** Stable class name shared only by byte-identical request headers. */
9
+ class: string;
10
+ /** Whether this scenario owns the class's tokenized header sequence. */
11
+ pin?: true;
12
+ /** Scenario that owns the readable system-prompt sidecar. */
13
+ systemPromptSource?: string;
14
+ /** Scenario that owns the readable tool-schema sidecar. */
15
+ toolSchemasSource?: string;
16
+ /** Child fixture indexes that own distinct system-prompt sidecars. */
17
+ childSystemPrompts?: number[];
18
+ /** Child fixture indexes that own distinct tool-schema sidecars. */
19
+ childToolSchemas?: number[];
20
+ /** Legitimate changed-header count after the initial request header. */
21
+ changes?: number;
22
+ }
23
+ /** Replay facts that cannot be reconstructed from successful model chunks. */
24
+ export interface SnapshotReplayManifest {
25
+ /** A scenario-local `replay.override.json` replaces or patches the recorded model script. */
26
+ override: true;
27
+ }
28
+ /** Host requirements for a scenario's process-level controller. */
29
+ export type SnapshotPlatform = 'posix' | 'pwsh';
30
+ /** Deployment permission preset selected before the scenario starts. */
31
+ export type SnapshotPermission = 'read-only' | 'workspace-write' | 'danger-full-access';
32
+ /** Scenario-local workspace preparation and expected-state metadata. */
33
+ export interface SnapshotWorkspaceManifest {
34
+ /** Named setup needed for state Git cannot represent directly. */
35
+ setup?: string;
36
+ /** Whether `workspace.expected/` owns the complete final world state. */
37
+ final?: true;
38
+ /** Place the generated cwd under the user's home instead of a temporary root. */
39
+ parent?: 'home';
40
+ }
41
+ /** Controller input that cannot enter a session because admission rejects it. */
42
+ export interface SnapshotInputAttachment {
43
+ /** Content-addressed attachment id stored in the session message. */
44
+ id: string;
45
+ /** MIME type supplied by the controlling interface. */
46
+ mediaType: string;
47
+ /** Complete base64 payload needed to reconstruct the input block. */
48
+ data: string;
49
+ }
50
+ /** Controller input bytes or rejected text that the persisted session cannot retain. */
51
+ export interface SnapshotInputManifest {
52
+ /** One-shot task absent from the canonical log only when no user event was accepted. */
53
+ task?: string;
54
+ /** Binary inputs keyed by the content-addressed ids retained in session JSONL. */
55
+ attachments?: SnapshotInputAttachment[];
56
+ }
57
+ /** Optional reference to another scenario's canonical session. */
58
+ export interface SnapshotSessionReference {
59
+ /** Repository-relative POSIX path from this scenario directory to the owning `session.jsonl`. */
60
+ source: string;
61
+ }
62
+ /** Declarative ownership metadata stored beside a recorded session. */
63
+ export interface SnapshotManifest {
64
+ /** Manifest format version. */
65
+ version: 1;
66
+ /** Scenario directory name, repeated for reviewable move and copy diagnostics. */
67
+ scenario?: string;
68
+ /** Shipped profile whose public interface controls the scenario. */
69
+ profile: SnapshotProfile;
70
+ /** Composition id whose sole pin owns its profile patches. */
71
+ composition?: string;
72
+ /** Whether the session is live-recordable or deliberately authored. */
73
+ recording?: SnapshotRecording;
74
+ /** Request-header class and sidecar ownership. */
75
+ header?: SnapshotHeaderManifest;
76
+ /** Exceptional replay metadata absent for ordinary successful recordings. */
77
+ replay?: SnapshotReplayManifest;
78
+ /** Optional host requirement; portable scenarios omit it. */
79
+ platform?: SnapshotPlatform;
80
+ /** Explicit process fallback permission preset. */
81
+ permission?: SnapshotPermission;
82
+ /** Test-only string environment additions needed by the declared composition. */
83
+ environment?: Record<string, string>;
84
+ /** Workspace setup and external final-state ownership. */
85
+ workspace?: SnapshotWorkspaceManifest;
86
+ /** Exceptional controller input absent for ordinary log-driven scenarios. */
87
+ input?: SnapshotInputManifest;
88
+ /** Absent when this directory owns `session.jsonl`; present for a read-only borrower. */
89
+ session?: SnapshotSessionReference;
90
+ }
91
+ /**
92
+ * Parse one `snapshot.yml` without admitting JavaScript YAML tags or unknown fields.
93
+ * @param source - complete manifest text.
94
+ * @param path - diagnostic path.
95
+ * @returns validated manifest metadata.
96
+ */
97
+ export declare function parseSnapshotManifest(source: string, path?: string): SnapshotManifest;
98
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Pure ACP transcript and session-log normalizers. They scrub session ids, run cwd, RPC ids,
3
+ * timestamps, goal lifecycle clocks, and hook duration while preserving semantic payload values.
4
+ * Request-header scrubbers stay composable so one scenario per header class can pin prompt and
5
+ * tool-schema sidecars.
6
+ * @module @deepseek-ai/dsh-session-snapshot/normalize
7
+ */
8
+ /**
9
+ * Extract every snapshot-mode spill path from a session log, keyed by spill
10
+ * filename. Used by refresh write-back to keep spill paths stable across runs.
11
+ * @param content - the raw session log text to scan.
12
+ * @returns spill filename → the full matched spill path, last match wins per name.
13
+ */
14
+ export declare function extractSnapshotSpillPaths(content: string): Map<string, string>;
15
+ /** Inputs the normalizers need to recognize a run's volatile values. */
16
+ export interface NormalizeContext {
17
+ /** The session id(s) the run issued — replaced with `{{sessionId}}`. */
18
+ sessionIds: string[];
19
+ /** The generated cwd the run used — replaced with `{{cwd}}`. */
20
+ cwd: string;
21
+ /** Other filesystem spellings of the same cwd (for example Windows short and long paths). */
22
+ cwdAliases?: readonly string[];
23
+ }
24
+ /** How cwd-rooted path separators are represented after the cwd is tokenized. */
25
+ export type CwdPathMode = 'canonical' | 'native';
26
+ /** Optional controls shared by stdout and session-log normalization. */
27
+ export interface NormalizeOptions {
28
+ /** Use `/` for shared goldens, or preserve captured separators for a platform-specific golden. */
29
+ cwdPathMode?: CwdPathMode;
30
+ /** Keep already-redacted typed ids and arbitrary UUID-like prose unchanged. */
31
+ identityMode?: 'legacy' | 'preserve';
32
+ }
33
+ /**
34
+ * Store one generated workspace as `{{cwd}}` while retaining every other
35
+ * session value. The caller opts in only for workspaces created under a
36
+ * platform temporary root; explicitly relocated workspaces keep their real
37
+ * path.
38
+ *
39
+ * @param rawLog The raw or refresh-stabilized session JSONL fixture.
40
+ * @returns Compact JSONL whose known cwd spellings become `{{cwd}}`.
41
+ * @throws If a non-empty line is invalid JSON or the session cwd has no basename.
42
+ */
43
+ export declare function tokenizeSessionFixtureCwd(rawLog: string): string;
44
+ /**
45
+ * Normalize a raw stdout transcript (newline-delimited JSON-RPC frames) into a stable expected output
46
+ * in the same shape as the wire: one compact JSON frame per line (NDJSON), with the JSON-RPC
47
+ * `id` rewritten to a per-transcript sequence (1, 2, 3, …) and all volatile strings scrubbed.
48
+ * Invalid JSON throws, doubling as a protocol-stdout purity check.
49
+ *
50
+ * @param rawStdout The captured stdout bytes, decoded utf8.
51
+ * @param ctx The run's volatile values to scrub.
52
+ * @param options Separator output controls; shared canonical paths are the default.
53
+ * @returns The normalized NDJSON transcript, one frame per line.
54
+ */
55
+ export declare function normalizeStdout(rawStdout: string, ctx: NormalizeContext, options?: NormalizeOptions): string;
56
+ /**
57
+ * Normalize a session JSONL log into a stable expected output: the header line's
58
+ * volatile fields (`createdAt`, `id`, `cwd`) are zeroed/scrubbed, ordinary
59
+ * event `time`, packed-row `time0`, and goal-change lifecycle clock values are
60
+ * zeroed, and all volatile strings are scrubbed. Projected inputs remain
61
+ * projected. Packed `data.dt` gaps are normalized even when the projected row
62
+ * omits its `time0` anchor.
63
+ * Output is JSONL in the same shape as the input — one compact record per
64
+ * line.
65
+ *
66
+ * @param rawLog The raw session `.jsonl` content.
67
+ * @param ctx The run's volatile values to scrub.
68
+ * @param options Separator output controls; shared canonical paths are the default.
69
+ * @returns The normalized JSONL log, one record per line.
70
+ */
71
+ export declare function normalizeSessionLog(rawLog: string, ctx: NormalizeContext, options?: NormalizeOptions): string;
72
+ /**
73
+ * Normalize and project persisted session JSONL for a committed fixture.
74
+ * This composes ordinary log normalization with request-header scrubbing and
75
+ * persistence-envelope projection, then packs the logical event stream into a
76
+ * canonical layout independent of persistence flush boundaries.
77
+ *
78
+ * @param rawLog - persisted or already-projected session JSONL.
79
+ * @param ctx - the run's volatile values to scrub.
80
+ * @param options - separator output controls.
81
+ * @returns normalized committed session snapshot JSONL.
82
+ */
83
+ export declare function normalizeSessionSnapshot(rawLog: string, ctx: NormalizeContext, options?: NormalizeOptions): string;
84
+ /**
85
+ * Normalize one scenario's primary and child logs with shared typed identity redaction.
86
+ * @param rawLogs - primary-first persisted or projected session JSONL.
87
+ * @param ctx - generated cwd spellings and other volatile run facts.
88
+ * @param options - separator controls; relationship-preserving identity mode is mandatory.
89
+ * @returns normalized session fixtures in input order.
90
+ */
91
+ export declare function normalizeSessionSnapshots(rawLogs: readonly string[], ctx: NormalizeContext, options?: Omit<NormalizeOptions, 'identityMode'>): string[];
92
+ /**
93
+ * Replace system-prompt content in request headers with `{{system}}` tokens
94
+ * while retaining field presence.
95
+ * Other header content stays verbatim, so a header-pinning fixture can keep
96
+ * its complete tool schemas while every JSONL fixture omits the prompt text.
97
+ * Lines without a system payload pass through byte-for-byte; the transform is
98
+ * idempotent.
99
+ *
100
+ * @param rawLog The raw session `.jsonl` content.
101
+ * @returns The JSONL with system-prompt content tokenized.
102
+ */
103
+ export declare function scrubSystemPrompts(rawLog: string): string;
104
+ /**
105
+ * Replace tool schemas in full request-header snapshots with `{{tools}}`
106
+ * tokens while retaining field presence. System prompts and session-prefix
107
+ * messages stay verbatim so pinning fixtures can move only schema bulk into
108
+ * their dedicated JSON sidecar. Lines without a tool payload pass through
109
+ * byte-for-byte; the transform is idempotent.
110
+ *
111
+ * @param rawLog The raw session `.jsonl` content.
112
+ * @returns The JSONL with tool-schema content tokenized.
113
+ */
114
+ export declare function scrubToolSchemas(rawLog: string): string;
115
+ /**
116
+ * Replace all bulky request-header content in a session JSONL with stable
117
+ * tokens. This includes the system-prompt fields handled by
118
+ * {@link scrubSystemPrompts}, tool schemas, and session-prefix messages. It
119
+ * keeps prefix message counts, field presence, config, and reason. Lines
120
+ * without content to scrub pass through byte-for-byte, and the transform is
121
+ * idempotent.
122
+ *
123
+ * @param rawLog The raw session `.jsonl` content.
124
+ * @returns The JSONL with all header bulk tokenized, other lines byte-identical.
125
+ */
126
+ export declare function scrubRequestHeaders(rawLog: string): string;
127
+ /**
128
+ * Project a persisted session log while tokenizing all request-header bulk.
129
+ * Each non-empty line is parsed at most once; the session header stays
130
+ * byte-identical. Body records omit their persistence-only envelopes, and
131
+ * request-header payloads are tokenized.
132
+ *
133
+ * @param rawLog - persisted or already-projected session JSONL.
134
+ * @returns committed snapshot JSONL with request headers tokenized.
135
+ */
136
+ export declare function scrubSessionSnapshot(rawLog: string): string;
137
+ //# sourceMappingURL=normalize.d.ts.map