@x1a0f3n9/dsh-session-snapshot 0.1.5-rc.3
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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +175 -0
- package/README.zh.md +175 -0
- package/lib/index.js +2849 -0
- package/lib/types/harness.d.ts +240 -0
- package/lib/types/identity.d.ts +8 -0
- package/lib/types/index.d.ts +27 -0
- package/lib/types/launcher.d.ts +84 -0
- package/lib/types/manifest.d.ts +123 -0
- package/lib/types/normalize.d.ts +144 -0
- package/lib/types/session-files.d.ts +104 -0
- package/lib/types/suite.d.ts +430 -0
- package/lib/types/workspace.d.ts +58 -0
- package/package.json +58 -0
|
@@ -0,0 +1,240 @@
|
|
|
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 @x1a0f3n9/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. Failures identify the child, turn, and deadline even if
|
|
43
|
+
* the first log read is still pending; the underlying failure is retained as cause.
|
|
44
|
+
* `waitForTitleAfterTurnEnd` additionally waits for a later durable title.
|
|
45
|
+
* `waitForEventAfterTurnEnd` waits until a complete record of the given event
|
|
46
|
+
* type follows the latest closed turn — for scenarios whose asserted state
|
|
47
|
+
* (e.g. a goal pause) is appended only after cancellation reaches idle.
|
|
48
|
+
* A standalone `cancel` may also wait for a cwd-relative readiness marker.
|
|
49
|
+
* All wait timeouts default to 10s.
|
|
50
|
+
*/
|
|
51
|
+
export type InputStep = {
|
|
52
|
+
op: 'initialize';
|
|
53
|
+
} | {
|
|
54
|
+
op: 'newSession';
|
|
55
|
+
} | {
|
|
56
|
+
op: 'newSessionExpectError';
|
|
57
|
+
additionalDirectories?: string[];
|
|
58
|
+
} | {
|
|
59
|
+
op: 'prompt';
|
|
60
|
+
text: string;
|
|
61
|
+
} | {
|
|
62
|
+
op: 'promptContent';
|
|
63
|
+
content: AcpContentBlock[];
|
|
64
|
+
} | {
|
|
65
|
+
op: 'promptAndWaitForAgentMessage';
|
|
66
|
+
text: string;
|
|
67
|
+
waitForText: string;
|
|
68
|
+
} | {
|
|
69
|
+
op: 'promptExpectError';
|
|
70
|
+
text: string;
|
|
71
|
+
} | {
|
|
72
|
+
op: 'promptAndCancel';
|
|
73
|
+
text: string;
|
|
74
|
+
waitForFile?: {
|
|
75
|
+
path: string;
|
|
76
|
+
timeoutMs?: number;
|
|
77
|
+
};
|
|
78
|
+
} | {
|
|
79
|
+
op: 'waitForFile';
|
|
80
|
+
path: string;
|
|
81
|
+
timeoutMs?: number;
|
|
82
|
+
} | {
|
|
83
|
+
op: 'waitForTurnStart';
|
|
84
|
+
minimumTurn?: number;
|
|
85
|
+
timeoutMs?: number;
|
|
86
|
+
} | {
|
|
87
|
+
op: 'waitForTurnEnd';
|
|
88
|
+
timeoutMs?: number;
|
|
89
|
+
} | {
|
|
90
|
+
op: 'waitForSubagentTurnEnd';
|
|
91
|
+
child?: number;
|
|
92
|
+
minimumTurn?: number;
|
|
93
|
+
timeoutMs?: number;
|
|
94
|
+
} | {
|
|
95
|
+
op: 'waitForGoalPhase';
|
|
96
|
+
phase: 'active' | 'paused' | 'blocked' | 'complete';
|
|
97
|
+
timeoutMs?: number;
|
|
98
|
+
} | {
|
|
99
|
+
op: 'waitForInboxMessage';
|
|
100
|
+
text: string;
|
|
101
|
+
timeoutMs?: number;
|
|
102
|
+
} | {
|
|
103
|
+
op: 'waitForTitleAfterTurnEnd';
|
|
104
|
+
timeoutMs?: number;
|
|
105
|
+
} | {
|
|
106
|
+
op: 'waitForEventAfterTurnEnd';
|
|
107
|
+
type: string;
|
|
108
|
+
timeoutMs?: number;
|
|
109
|
+
} | {
|
|
110
|
+
op: 'cancel';
|
|
111
|
+
waitForFile?: {
|
|
112
|
+
path: string;
|
|
113
|
+
timeoutMs?: number;
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
/** A scenario's `input.json`: an ordered list of input steps. */
|
|
117
|
+
export interface InputScript {
|
|
118
|
+
steps: InputStep[];
|
|
119
|
+
/**
|
|
120
|
+
* Ordered answers for the agent's `session/request_permission` round-trips,
|
|
121
|
+
* consumed FIFO — the Nth request gets the Nth answer. Each answer selects
|
|
122
|
+
* by option KIND: option ids are agent-issued randoms a committed script
|
|
123
|
+
* cannot know, while kinds are the ACP-stable vocabulary, so the client maps
|
|
124
|
+
* kind → the offered `optionId` at answer time. A request beyond the queue
|
|
125
|
+
* (or with no queue at all) is answered `cancelled` — the stub behavior a
|
|
126
|
+
* scenario without approvals relies on. A scripted kind the request does
|
|
127
|
+
* not offer REJECTS the run: the scenario scripted an impossible selection,
|
|
128
|
+
* and {@link runScenario} throws once the in-flight step settles (the
|
|
129
|
+
* agent itself just sees `cancelled`, so it cannot absorb the bug).
|
|
130
|
+
*/
|
|
131
|
+
permissionAnswers?: PermissionAnswer[];
|
|
132
|
+
}
|
|
133
|
+
/** One scripted answer to a permission request: which offered option kind to select. */
|
|
134
|
+
export interface PermissionAnswer {
|
|
135
|
+
/** The `PermissionOption.kind` to select (`allow_once`, `reject_always`, …). */
|
|
136
|
+
kind: 'allow_once' | 'allow_always' | 'reject_once' | 'reject_always';
|
|
137
|
+
}
|
|
138
|
+
/** One harvested session log plus the identifying facts off its header line. */
|
|
139
|
+
export interface HarvestedLog {
|
|
140
|
+
/** The recorded session id (header `id`). */
|
|
141
|
+
id: string;
|
|
142
|
+
/** Session creation time (header `createdAt`) — the child-ordering key. */
|
|
143
|
+
createdAt: number;
|
|
144
|
+
/** The parent session id, if this log is a subagent child (header `parentSession`). */
|
|
145
|
+
parentSession?: string;
|
|
146
|
+
/** The full `.jsonl` file content. */
|
|
147
|
+
content: string;
|
|
148
|
+
}
|
|
149
|
+
/** The result of running a scenario: raw stdout + the harvested session log(s). */
|
|
150
|
+
export interface RunResult {
|
|
151
|
+
/** Raw stdout bytes (decoded utf8), every newline-delimited JSON-RPC frame. */
|
|
152
|
+
rawStdout: string;
|
|
153
|
+
/** stderr (for diagnostics on failure). */
|
|
154
|
+
stderr: string;
|
|
155
|
+
/** The session id the server issued (undefined if no session was created). */
|
|
156
|
+
sessionId?: string;
|
|
157
|
+
/** The generated cwd the session ran in (the bash workspace). */
|
|
158
|
+
cwd: string;
|
|
159
|
+
/** Filesystem-resolved spellings of {@link cwd} that child processes may report. */
|
|
160
|
+
cwdAliases: string[];
|
|
161
|
+
/** User-visible workspace state after committed and runtime-only setup. */
|
|
162
|
+
initialWorkspace: WorkspaceSnapshotEntry[];
|
|
163
|
+
/** User-visible workspace state after the controlled interface has settled. */
|
|
164
|
+
finalWorkspace: WorkspaceSnapshotEntry[];
|
|
165
|
+
/**
|
|
166
|
+
* Every persisted session log harvested after the run, ordered primary-first:
|
|
167
|
+
* the top-level (parent) session — the one with no `parentSession` — then each
|
|
168
|
+
* subagent child by ascending `createdAt`. A single-session scenario harvests
|
|
169
|
+
* exactly one; a nested-agent scenario harvests the parent plus one per child.
|
|
170
|
+
*/
|
|
171
|
+
sessionLogs: HarvestedLog[];
|
|
172
|
+
}
|
|
173
|
+
/** How to run one scenario: the agent to boot, the mode, and the fixture wiring. */
|
|
174
|
+
export interface RunOptions {
|
|
175
|
+
/** The agent composition to boot. */
|
|
176
|
+
agent: AgentUnderTest;
|
|
177
|
+
/** `replay` (default, keyless) or `record` (real API, harvests the log). */
|
|
178
|
+
mode: 'replay' | 'record';
|
|
179
|
+
/** Scenario-specific deployment environment layered into the subprocess. */
|
|
180
|
+
env?: NodeJS.ProcessEnv;
|
|
181
|
+
/** The recorded session JSONL fixture path (replay reads it; record writes near it). */
|
|
182
|
+
fixtureFile: string;
|
|
183
|
+
/** Optional sidecar override path (replay). */
|
|
184
|
+
overrideFile?: string;
|
|
185
|
+
/**
|
|
186
|
+
* Recorded SUBAGENT child-session fixture paths (replay). A nested-agent
|
|
187
|
+
* scenario ships one per child (`session.1.jsonl`, …); the harness forwards
|
|
188
|
+
* them to `dsh-llm-replay` via `$DSH_SNAPSHOT_CHILD_FILES` so each child
|
|
189
|
+
* session replays from its own recorded script. Empty for single-session
|
|
190
|
+
* scenarios. Ignored in record mode (children are harvested, not replayed).
|
|
191
|
+
*/
|
|
192
|
+
childFiles?: string[];
|
|
193
|
+
/**
|
|
194
|
+
* Optional `<scenario>/workspace/` directory whose contents are copied into
|
|
195
|
+
* the generated cwd BEFORE the run — the standard way to seed files the agent
|
|
196
|
+
* operates on (a file to read, edit, or grep). Absent for scenarios that
|
|
197
|
+
* start from an empty workspace.
|
|
198
|
+
*/
|
|
199
|
+
workspaceDir?: string;
|
|
200
|
+
/**
|
|
201
|
+
* Optional final workspace preparation, run after {@link workspaceDir} is
|
|
202
|
+
* copied and before the agent starts. This is for fixtures that cannot be
|
|
203
|
+
* represented portably in Git (for example, a POSIX-only filename that is
|
|
204
|
+
* invalid on Windows); ordinary seeded files belong in `workspaceDir`.
|
|
205
|
+
*/
|
|
206
|
+
prepareWorkspace?: (cwd: string) => void | Promise<void>;
|
|
207
|
+
/**
|
|
208
|
+
* Parent directory for the generated session cwd. Defaults to
|
|
209
|
+
* `os.tmpdir()`. A scenario that must distinguish its workspace from the
|
|
210
|
+
* sandbox's always-writable temporary roots can place the generated child
|
|
211
|
+
* under `os.homedir()` instead. The harness removes only that generated
|
|
212
|
+
* child, never the supplied parent.
|
|
213
|
+
*/
|
|
214
|
+
workspaceParent?: string;
|
|
215
|
+
/**
|
|
216
|
+
* Alternate live profile patch (absolute), overriding
|
|
217
|
+
* {@link AgentUnderTest.configPath} for this run. Its basename still ends
|
|
218
|
+
* in `cordis.yml` so the launcher can select the replay sibling.
|
|
219
|
+
*/
|
|
220
|
+
configPath?: string;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Derive the stable, fixed-length logical spill prefix; never allocate files here.
|
|
224
|
+
* Windows uses a two-character-shorter root because drive resolution adds its drive prefix.
|
|
225
|
+
* @param fixtureFile - The scenario fixture whose parent directory provides the stable identity.
|
|
226
|
+
* @param platform - the host platform, injectable for unit coverage.
|
|
227
|
+
* @returns the root-relative snapshot spill directory.
|
|
228
|
+
*/
|
|
229
|
+
export declare function snapshotSpillRoot(fixtureFile: string, platform?: NodeJS.Platform): string;
|
|
230
|
+
/**
|
|
231
|
+
* Run a scenario end-to-end against a freshly-spawned subprocess. Owns the
|
|
232
|
+
* child and its generated dirs; always tears them down. Returns the captured stdout
|
|
233
|
+
* and (record mode) the harvested session-log path.
|
|
234
|
+
*
|
|
235
|
+
* @param input The scenario's input script (steps + optional permission answers).
|
|
236
|
+
* @param opts The agent to boot, the mode, and the fixture wiring.
|
|
237
|
+
* @returns The captured stdout/stderr, session id, generated cwd, and harvested logs.
|
|
238
|
+
*/
|
|
239
|
+
export declare function runScenario(input: InputScript, opts: RunOptions): Promise<RunResult>;
|
|
240
|
+
//# 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,27 @@
|
|
|
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 scrubModelRequestBulk} / {@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 @x1a0f3n9/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, normalizeSessionFormatProvenance, normalizeSessionLog, normalizeSessionSnapshot, normalizeSessionSnapshots, normalizeStdout, scrubModelRequestBulk, scrubSessionSnapshot, scrubSystemPrompts, scrubToolSchemas, tokenizeSessionFixtureCwd, type CwdPathMode, type NormalizeContext, type NormalizeOptions, } from './normalize.ts';
|
|
23
|
+
export { parseSnapshotManifest, writesCurrentSessionFixtures, type SnapshotHeaderManifest, type SnapshotInputAttachment, type SnapshotInputManifest, type SnapshotManifest, type SnapshotPermission, type SnapshotPlatform, type SnapshotProfile, type SnapshotRecording, type SnapshotReplayManifest, type SnapshotSessionReference, type SnapshotSessionFormatCoverage, type SnapshotSessionFormatManifest, type SnapshotSessionWriteMode, type SnapshotWorkspaceManifest, } from './manifest.ts';
|
|
24
|
+
export { assertPersistedSessionVersion, assertSessionFixtureVersion, latestPersistedSessionPaths, parsePersistedSessionFilename, parseSessionFixtureName, persistedSessionFilename, sessionFixtureFiles, sessionFixtureName, sessionFixtureNames, sessionHeaderVersion, writerSnapshotName, type PersistedSessionFile, type SessionFixtureFile, } from './session-files.ts';
|
|
25
|
+
export { formatSystemPromptSnapshot, formatToolSchemasSnapshot, fixtureContext, headerChangeCount, defineAcpSnapshotSuite, normalizedHeaders, normalizedSystemPrompts, normalizedToolSchemas, parseSystemPromptSnapshot, parseToolSchemasSnapshot, refreshFixtureReplacements, restorePinnedToolSchemas, stabilizeFixtureMessageIds, stabilizeRefreshLog, systemPromptPrecedesRequests, type Scenario, type SnapshotSuiteOptions, } from './suite.ts';
|
|
26
|
+
export { captureExpectedWorkspaceSnapshot, captureWorkspaceSnapshot, EMPTY_WORKSPACE_MARKER, type CaptureWorkspaceSnapshotOptions, type WorkspaceBinaryFileSnapshot, type WorkspaceEmptyDirectorySnapshot, type WorkspaceSnapshotEntry, type WorkspaceSymlinkSnapshot, type WorkspaceTextFileSnapshot, } from './workspace.ts';
|
|
27
|
+
//# sourceMappingURL=index.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 @x1a0f3n9/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,123 @@
|
|
|
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
|
+
/** Legitimate later `system/message` count (replacements or in-history appends) after the initial system prompt. */
|
|
23
|
+
promptChanges?: number;
|
|
24
|
+
}
|
|
25
|
+
/** Replay facts that cannot be reconstructed from successful model chunks. */
|
|
26
|
+
export interface SnapshotReplayManifest {
|
|
27
|
+
/** A scenario-local `replay.override.json` replaces or patches the recorded model script. */
|
|
28
|
+
override: true;
|
|
29
|
+
}
|
|
30
|
+
/** Host requirements for a scenario's process-level controller. */
|
|
31
|
+
export type SnapshotPlatform = 'posix' | 'pwsh';
|
|
32
|
+
/** Deployment permission preset selected before the scenario starts. */
|
|
33
|
+
export type SnapshotPermission = 'read-only' | 'workspace-write' | 'danger-full-access';
|
|
34
|
+
/** Scenario-local workspace preparation and expected-state metadata. */
|
|
35
|
+
export interface SnapshotWorkspaceManifest {
|
|
36
|
+
/** Named setup needed for state Git cannot represent directly. */
|
|
37
|
+
setup?: string;
|
|
38
|
+
/** Whether `workspace.expected/` owns the complete final world state. */
|
|
39
|
+
final?: true;
|
|
40
|
+
/** Place the generated cwd outside automatically writable temporary roots. */
|
|
41
|
+
parent?: 'outside-temp';
|
|
42
|
+
}
|
|
43
|
+
/** Controller input that cannot enter a session because admission rejects it. */
|
|
44
|
+
export interface SnapshotInputAttachment {
|
|
45
|
+
/** Content-addressed attachment id stored in the session message. */
|
|
46
|
+
id: string;
|
|
47
|
+
/** MIME type supplied by the controlling interface. */
|
|
48
|
+
mediaType: string;
|
|
49
|
+
/** Complete base64 payload needed to reconstruct the input block. */
|
|
50
|
+
data: string;
|
|
51
|
+
}
|
|
52
|
+
/** Controller input bytes or rejected text that the persisted session cannot retain. */
|
|
53
|
+
export interface SnapshotInputManifest {
|
|
54
|
+
/** One-shot task absent from the canonical log only when no user event was accepted. */
|
|
55
|
+
task?: string;
|
|
56
|
+
/** Binary inputs keyed by the content-addressed ids retained in session JSONL. */
|
|
57
|
+
attachments?: SnapshotInputAttachment[];
|
|
58
|
+
}
|
|
59
|
+
/** Optional reference to another scenario's canonical session. */
|
|
60
|
+
export interface SnapshotSessionReference {
|
|
61
|
+
/** Repository-relative POSIX path to the owning scenario's selected parent Session fixture. */
|
|
62
|
+
source: string;
|
|
63
|
+
}
|
|
64
|
+
/** Historical-format behavior one retained scenario permanently exercises. */
|
|
65
|
+
export type SnapshotSessionFormatCoverage = 'multi-hop' | 'packed-row' | 'retry-failure' | 'shipped-profile' | 'adjacent-migration';
|
|
66
|
+
/** Explicit historical generation retained by an owning scenario. */
|
|
67
|
+
export interface SnapshotSessionFormatManifest {
|
|
68
|
+
/** Selected fixture generation; absent manifest metadata tracks the current writer. */
|
|
69
|
+
readonly version: number;
|
|
70
|
+
/** Migration behaviors that require this historical fixture. */
|
|
71
|
+
readonly coverage: readonly SnapshotSessionFormatCoverage[];
|
|
72
|
+
}
|
|
73
|
+
/** Declarative ownership metadata stored beside a recorded session. */
|
|
74
|
+
export interface SnapshotManifest {
|
|
75
|
+
/** Manifest format version. */
|
|
76
|
+
version: 1;
|
|
77
|
+
/** Scenario directory name, repeated for reviewable move and copy diagnostics. */
|
|
78
|
+
scenario?: string;
|
|
79
|
+
/** Shipped profile whose public interface controls the scenario. */
|
|
80
|
+
profile: SnapshotProfile;
|
|
81
|
+
/** Composition id whose sole pin owns its profile patches. */
|
|
82
|
+
composition?: string;
|
|
83
|
+
/** Whether the session is live-recordable or deliberately authored. */
|
|
84
|
+
recording?: SnapshotRecording;
|
|
85
|
+
/** Request-header class and sidecar ownership. */
|
|
86
|
+
header?: SnapshotHeaderManifest;
|
|
87
|
+
/** Exceptional replay metadata absent for ordinary successful recordings. */
|
|
88
|
+
replay?: SnapshotReplayManifest;
|
|
89
|
+
/** Optional host requirement; portable scenarios omit it. */
|
|
90
|
+
platform?: SnapshotPlatform;
|
|
91
|
+
/** Explicit process fallback permission preset. */
|
|
92
|
+
permission?: SnapshotPermission;
|
|
93
|
+
/** Test-only string environment additions needed by the declared composition. */
|
|
94
|
+
environment?: Record<string, string>;
|
|
95
|
+
/** Workspace setup and external final-state ownership. */
|
|
96
|
+
workspace?: SnapshotWorkspaceManifest;
|
|
97
|
+
/** Exceptional controller input absent for ordinary log-driven scenarios. */
|
|
98
|
+
input?: SnapshotInputManifest;
|
|
99
|
+
/** Absent when this directory owns its selected parent fixture; present for a read-only borrower. */
|
|
100
|
+
session?: SnapshotSessionReference;
|
|
101
|
+
/** Historical generation retained by an owner instead of tracking the current writer. */
|
|
102
|
+
sessionFormat?: SnapshotSessionFormatManifest;
|
|
103
|
+
}
|
|
104
|
+
/** Snapshot execution modes that may read or replace committed fixture generations. */
|
|
105
|
+
export type SnapshotSessionWriteMode = 'replay' | 'record' | 'refresh';
|
|
106
|
+
/**
|
|
107
|
+
* Whether one run writes current-writer Session fixtures for this scenario.
|
|
108
|
+
* Explicit historical generations remain immutable replay inputs; record and
|
|
109
|
+
* refresh may still update their non-Session expected outputs.
|
|
110
|
+
*
|
|
111
|
+
* @param manifest - Parsed scenario ownership and retained-generation metadata.
|
|
112
|
+
* @param mode - Snapshot execution mode.
|
|
113
|
+
* @returns True only when a write-capable mode tracks the current writer.
|
|
114
|
+
*/
|
|
115
|
+
export declare function writesCurrentSessionFixtures(manifest: SnapshotManifest, mode: SnapshotSessionWriteMode): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Parse one `snapshot.yml` without admitting JavaScript YAML tags or unknown fields.
|
|
118
|
+
* @param source - complete manifest text.
|
|
119
|
+
* @param path - diagnostic path.
|
|
120
|
+
* @returns validated manifest metadata.
|
|
121
|
+
*/
|
|
122
|
+
export declare function parseSnapshotManifest(source: string, path?: string): SnapshotManifest;
|
|
123
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1,144 @@
|
|
|
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
|
+
* The prompt-text and tool-schema scrubbers stay composable so one scenario per header class can
|
|
5
|
+
* pin prompt and tool-schema sidecars.
|
|
6
|
+
* @module @x1a0f3n9/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; event,
|
|
59
|
+
* historical packed-row, embedded Assistant-stream, goal lifecycle, and
|
|
60
|
+
* catalog child-creation clocks are zeroed; and all volatile strings are
|
|
61
|
+
* scrubbed. Projected inputs remain
|
|
62
|
+
* projected. Packed `data.dt` gaps are normalized even when the projected row
|
|
63
|
+
* omits its `time0` anchor.
|
|
64
|
+
* Output is JSONL in the same shape as the input — one compact record per
|
|
65
|
+
* line.
|
|
66
|
+
*
|
|
67
|
+
* @param rawLog The raw session `.jsonl` content.
|
|
68
|
+
* @param ctx The run's volatile values to scrub.
|
|
69
|
+
* @param options Separator output controls; shared canonical paths are the default.
|
|
70
|
+
* @returns The normalized JSONL log, one record per line.
|
|
71
|
+
*/
|
|
72
|
+
export declare function normalizeSessionLog(rawLog: string, ctx: NormalizeContext, options?: NormalizeOptions): string;
|
|
73
|
+
/**
|
|
74
|
+
* Normalize and project persisted session JSONL for a committed fixture.
|
|
75
|
+
* This composes ordinary log normalization with request-header scrubbing and
|
|
76
|
+
* persistence-envelope projection, then writes the v3 logical event stream as
|
|
77
|
+
* one record per event, independent of persistence flush boundaries. Event order
|
|
78
|
+
* and source-event references are preserved.
|
|
79
|
+
*
|
|
80
|
+
* @param rawLog - persisted or already-projected session JSONL.
|
|
81
|
+
* @param ctx - the run's volatile values to scrub.
|
|
82
|
+
* @param options - separator output controls.
|
|
83
|
+
* @returns normalized committed session snapshot JSONL.
|
|
84
|
+
*/
|
|
85
|
+
export declare function normalizeSessionSnapshot(rawLog: string, ctx: NormalizeContext, options?: NormalizeOptions): string;
|
|
86
|
+
/**
|
|
87
|
+
* Normalize one scenario's primary and child logs with shared typed identity redaction.
|
|
88
|
+
* @param rawLogs - primary-first persisted or projected session JSONL.
|
|
89
|
+
* @param ctx - generated cwd spellings and other volatile run facts.
|
|
90
|
+
* @param options - separator controls; relationship-preserving identity mode is mandatory.
|
|
91
|
+
* @returns normalized session fixtures in input order.
|
|
92
|
+
*/
|
|
93
|
+
export declare function normalizeSessionSnapshots(rawLogs: readonly string[], ctx: NormalizeContext, options?: Omit<NormalizeOptions, 'identityMode'>): string[];
|
|
94
|
+
/**
|
|
95
|
+
* Omit the artifact header generation after official migration for comparison.
|
|
96
|
+
* Delivery and captured-source generations retain their opaque recorded values.
|
|
97
|
+
* @param rawLog - Session records or events as compact JSON lines.
|
|
98
|
+
* @returns the same records with only the Session header version omitted.
|
|
99
|
+
*/
|
|
100
|
+
export declare function normalizeSessionFormatProvenance(rawLog: string): string;
|
|
101
|
+
/**
|
|
102
|
+
* Replace the rendered prompt text of every `system/message` event with the
|
|
103
|
+
* `{{system}}` token. The text block keeps its position and type, so the
|
|
104
|
+
* fixture still shows one system node per prompt version; an empty `content`
|
|
105
|
+
* (no system prompt) stays empty. Request headers and every other line pass
|
|
106
|
+
* through byte-for-byte; the transform is idempotent.
|
|
107
|
+
*
|
|
108
|
+
* @param rawLog The raw session `.jsonl` content.
|
|
109
|
+
* @returns The JSONL with system-prompt text tokenized.
|
|
110
|
+
*/
|
|
111
|
+
export declare function scrubSystemPrompts(rawLog: string): string;
|
|
112
|
+
/**
|
|
113
|
+
* Replace tool schemas in full request-header snapshots with `{{tools}}`
|
|
114
|
+
* tokens while retaining field presence. System-prompt text stays verbatim so
|
|
115
|
+
* pinning fixtures can move only schema bulk into their dedicated JSON
|
|
116
|
+
* sidecar. Lines without a tool payload pass through byte-for-byte; the
|
|
117
|
+
* transform is idempotent.
|
|
118
|
+
*
|
|
119
|
+
* @param rawLog The raw session `.jsonl` content.
|
|
120
|
+
* @returns The JSONL with tool-schema content tokenized.
|
|
121
|
+
*/
|
|
122
|
+
export declare function scrubToolSchemas(rawLog: string): string;
|
|
123
|
+
/**
|
|
124
|
+
* Replace all bulky model-request content in a session JSONL with stable
|
|
125
|
+
* tokens: the `system/message` prompt text handled by
|
|
126
|
+
* {@link scrubSystemPrompts} and the request-header tool schemas handled by
|
|
127
|
+
* {@link scrubToolSchemas}. Field presence, config, and reason are kept.
|
|
128
|
+
* Lines without content to scrub pass through byte-for-byte, and the
|
|
129
|
+
* transform is idempotent.
|
|
130
|
+
*
|
|
131
|
+
* @param rawLog The raw session `.jsonl` content.
|
|
132
|
+
* @returns The JSONL with prompt text and schema bulk tokenized, other lines byte-identical.
|
|
133
|
+
*/
|
|
134
|
+
export declare function scrubModelRequestBulk(rawLog: string): string;
|
|
135
|
+
/**
|
|
136
|
+
* Project a persisted session log while tokenizing prompt text and schema
|
|
137
|
+
* bulk. Each non-empty line is parsed at most once; the session header stays
|
|
138
|
+
* byte-identical. Body records omit their persistence-only envelopes.
|
|
139
|
+
*
|
|
140
|
+
* @param rawLog - persisted or already-projected session JSONL.
|
|
141
|
+
* @returns committed snapshot JSONL with prompt text and tool schemas tokenized.
|
|
142
|
+
*/
|
|
143
|
+
export declare function scrubSessionSnapshot(rawLog: string): string;
|
|
144
|
+
//# sourceMappingURL=normalize.d.ts.map
|