@plaintake/scenario 1.2.1
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.md +68 -0
- package/dist/define.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +753 -0
- package/dist/schema/errors.d.ts +20 -0
- package/dist/schema/event.d.ts +58 -0
- package/dist/schema/index.d.ts +9 -0
- package/dist/schema/json-schema.d.ts +9 -0
- package/dist/schema/manifest.d.ts +46 -0
- package/dist/schema/render-plan.d.ts +454 -0
- package/dist/schema/results.d.ts +335 -0
- package/dist/schema/scenario.d.ts +89 -0
- package/dist/schema/stable-json.d.ts +4 -0
- package/dist/schema/zod-format.d.ts +8 -0
- package/dist/types.d.ts +194 -0
- package/package.json +29 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const ArtifactRefSchema: z.ZodObject<{
|
|
3
|
+
path: z.ZodString;
|
|
4
|
+
sha256: z.ZodString;
|
|
5
|
+
bytes: z.ZodNumber;
|
|
6
|
+
}, z.core.$strip>;
|
|
7
|
+
export declare const ValidationResultSchema: z.ZodObject<{
|
|
8
|
+
scenario: z.ZodOptional<z.ZodObject<{
|
|
9
|
+
id: z.ZodString;
|
|
10
|
+
title: z.ZodString;
|
|
11
|
+
language: z.ZodString;
|
|
12
|
+
handoff: z.ZodDefault<z.ZodEnum<{
|
|
13
|
+
none: "none";
|
|
14
|
+
preflight: "preflight";
|
|
15
|
+
session: "session";
|
|
16
|
+
}>>;
|
|
17
|
+
sha256: z.ZodString;
|
|
18
|
+
}, z.core.$strip>>;
|
|
19
|
+
schema: z.ZodLiteral<"agent-demo.result/v1">;
|
|
20
|
+
kind: z.ZodLiteral<"validate">;
|
|
21
|
+
ok: z.ZodBoolean;
|
|
22
|
+
problems: z.ZodArray<z.ZodString>;
|
|
23
|
+
}, z.core.$strip>;
|
|
24
|
+
export declare const RunCommandResultSchema: z.ZodObject<{
|
|
25
|
+
bundleDir: z.ZodString;
|
|
26
|
+
status: z.ZodEnum<{
|
|
27
|
+
passed: "passed";
|
|
28
|
+
failed: "failed";
|
|
29
|
+
}>;
|
|
30
|
+
cueCount: z.ZodNumber;
|
|
31
|
+
chapterCount: z.ZodNumber;
|
|
32
|
+
narratedCount: z.ZodNumber;
|
|
33
|
+
diagnostics: z.ZodArray<z.ZodObject<{
|
|
34
|
+
code: z.ZodString;
|
|
35
|
+
cueId: z.ZodString;
|
|
36
|
+
detail: z.ZodString;
|
|
37
|
+
}, z.core.$strip>>;
|
|
38
|
+
assertions: z.ZodArray<z.ZodObject<{
|
|
39
|
+
id: z.ZodString;
|
|
40
|
+
status: z.ZodEnum<{
|
|
41
|
+
passed: "passed";
|
|
42
|
+
failed: "failed";
|
|
43
|
+
}>;
|
|
44
|
+
}, z.core.$strip>>;
|
|
45
|
+
outputs: z.ZodArray<z.ZodObject<{
|
|
46
|
+
path: z.ZodString;
|
|
47
|
+
sha256: z.ZodString;
|
|
48
|
+
bytes: z.ZodNumber;
|
|
49
|
+
}, z.core.$strip>>;
|
|
50
|
+
schema: z.ZodLiteral<"agent-demo.result/v1">;
|
|
51
|
+
kind: z.ZodLiteral<"run">;
|
|
52
|
+
ok: z.ZodBoolean;
|
|
53
|
+
problems: z.ZodArray<z.ZodString>;
|
|
54
|
+
}, z.core.$strip>;
|
|
55
|
+
export declare const RenderCommandResultSchema: z.ZodObject<{
|
|
56
|
+
bundleDir: z.ZodString;
|
|
57
|
+
variants: z.ZodEnum<{
|
|
58
|
+
soft: "soft";
|
|
59
|
+
hard: "hard";
|
|
60
|
+
}>;
|
|
61
|
+
streams: z.ZodObject<{
|
|
62
|
+
base: z.ZodOptional<z.ZodObject<{
|
|
63
|
+
container: z.ZodEnum<{
|
|
64
|
+
mp4: "mp4";
|
|
65
|
+
webm: "webm";
|
|
66
|
+
other: "other";
|
|
67
|
+
}>;
|
|
68
|
+
durationMs: z.ZodNumber;
|
|
69
|
+
video: z.ZodObject<{
|
|
70
|
+
codec: z.ZodString;
|
|
71
|
+
width: z.ZodNumber;
|
|
72
|
+
height: z.ZodNumber;
|
|
73
|
+
pixelFormat: z.ZodString;
|
|
74
|
+
fps: z.ZodNumber;
|
|
75
|
+
frames: z.ZodNumber;
|
|
76
|
+
durationMs: z.ZodNumber;
|
|
77
|
+
}, z.core.$strip>;
|
|
78
|
+
subtitles: z.ZodArray<z.ZodObject<{
|
|
79
|
+
codec: z.ZodString;
|
|
80
|
+
language: z.ZodString;
|
|
81
|
+
title: z.ZodString;
|
|
82
|
+
}, z.core.$strip>>;
|
|
83
|
+
audio: z.ZodArray<z.ZodObject<{
|
|
84
|
+
codec: z.ZodString;
|
|
85
|
+
sampleRate: z.ZodNumber;
|
|
86
|
+
channels: z.ZodNumber;
|
|
87
|
+
}, z.core.$strip>>;
|
|
88
|
+
dataStreams: z.ZodNumber;
|
|
89
|
+
chapters: z.ZodArray<z.ZodObject<{
|
|
90
|
+
startMs: z.ZodNumber;
|
|
91
|
+
endMs: z.ZodNumber;
|
|
92
|
+
title: z.ZodString;
|
|
93
|
+
}, z.core.$strip>>;
|
|
94
|
+
}, z.core.$strip>>;
|
|
95
|
+
soft: z.ZodOptional<z.ZodObject<{
|
|
96
|
+
container: z.ZodEnum<{
|
|
97
|
+
mp4: "mp4";
|
|
98
|
+
webm: "webm";
|
|
99
|
+
other: "other";
|
|
100
|
+
}>;
|
|
101
|
+
durationMs: z.ZodNumber;
|
|
102
|
+
video: z.ZodObject<{
|
|
103
|
+
codec: z.ZodString;
|
|
104
|
+
width: z.ZodNumber;
|
|
105
|
+
height: z.ZodNumber;
|
|
106
|
+
pixelFormat: z.ZodString;
|
|
107
|
+
fps: z.ZodNumber;
|
|
108
|
+
frames: z.ZodNumber;
|
|
109
|
+
durationMs: z.ZodNumber;
|
|
110
|
+
}, z.core.$strip>;
|
|
111
|
+
subtitles: z.ZodArray<z.ZodObject<{
|
|
112
|
+
codec: z.ZodString;
|
|
113
|
+
language: z.ZodString;
|
|
114
|
+
title: z.ZodString;
|
|
115
|
+
}, z.core.$strip>>;
|
|
116
|
+
audio: z.ZodArray<z.ZodObject<{
|
|
117
|
+
codec: z.ZodString;
|
|
118
|
+
sampleRate: z.ZodNumber;
|
|
119
|
+
channels: z.ZodNumber;
|
|
120
|
+
}, z.core.$strip>>;
|
|
121
|
+
dataStreams: z.ZodNumber;
|
|
122
|
+
chapters: z.ZodArray<z.ZodObject<{
|
|
123
|
+
startMs: z.ZodNumber;
|
|
124
|
+
endMs: z.ZodNumber;
|
|
125
|
+
title: z.ZodString;
|
|
126
|
+
}, z.core.$strip>>;
|
|
127
|
+
}, z.core.$strip>>;
|
|
128
|
+
hard: z.ZodOptional<z.ZodObject<{
|
|
129
|
+
container: z.ZodEnum<{
|
|
130
|
+
mp4: "mp4";
|
|
131
|
+
webm: "webm";
|
|
132
|
+
other: "other";
|
|
133
|
+
}>;
|
|
134
|
+
durationMs: z.ZodNumber;
|
|
135
|
+
video: z.ZodObject<{
|
|
136
|
+
codec: z.ZodString;
|
|
137
|
+
width: z.ZodNumber;
|
|
138
|
+
height: z.ZodNumber;
|
|
139
|
+
pixelFormat: z.ZodString;
|
|
140
|
+
fps: z.ZodNumber;
|
|
141
|
+
frames: z.ZodNumber;
|
|
142
|
+
durationMs: z.ZodNumber;
|
|
143
|
+
}, z.core.$strip>;
|
|
144
|
+
subtitles: z.ZodArray<z.ZodObject<{
|
|
145
|
+
codec: z.ZodString;
|
|
146
|
+
language: z.ZodString;
|
|
147
|
+
title: z.ZodString;
|
|
148
|
+
}, z.core.$strip>>;
|
|
149
|
+
audio: z.ZodArray<z.ZodObject<{
|
|
150
|
+
codec: z.ZodString;
|
|
151
|
+
sampleRate: z.ZodNumber;
|
|
152
|
+
channels: z.ZodNumber;
|
|
153
|
+
}, z.core.$strip>>;
|
|
154
|
+
dataStreams: z.ZodNumber;
|
|
155
|
+
chapters: z.ZodArray<z.ZodObject<{
|
|
156
|
+
startMs: z.ZodNumber;
|
|
157
|
+
endMs: z.ZodNumber;
|
|
158
|
+
title: z.ZodString;
|
|
159
|
+
}, z.core.$strip>>;
|
|
160
|
+
}, z.core.$strip>>;
|
|
161
|
+
}, z.core.$strip>;
|
|
162
|
+
outputs: z.ZodArray<z.ZodObject<{
|
|
163
|
+
path: z.ZodString;
|
|
164
|
+
sha256: z.ZodString;
|
|
165
|
+
bytes: z.ZodNumber;
|
|
166
|
+
}, z.core.$strip>>;
|
|
167
|
+
schema: z.ZodLiteral<"agent-demo.result/v1">;
|
|
168
|
+
kind: z.ZodLiteral<"render">;
|
|
169
|
+
ok: z.ZodBoolean;
|
|
170
|
+
problems: z.ZodArray<z.ZodString>;
|
|
171
|
+
}, z.core.$strip>;
|
|
172
|
+
export declare const VerificationReportSchema: z.ZodObject<{
|
|
173
|
+
bundleDir: z.ZodString;
|
|
174
|
+
artifactCount: z.ZodNumber;
|
|
175
|
+
schema: z.ZodLiteral<"agent-demo.result/v1">;
|
|
176
|
+
kind: z.ZodLiteral<"verify">;
|
|
177
|
+
ok: z.ZodBoolean;
|
|
178
|
+
problems: z.ZodArray<z.ZodString>;
|
|
179
|
+
}, z.core.$strip>;
|
|
180
|
+
export declare const InspectResultSchema: z.ZodObject<{
|
|
181
|
+
bundleDir: z.ZodString;
|
|
182
|
+
status: z.ZodEnum<{
|
|
183
|
+
passed: "passed";
|
|
184
|
+
failed: "failed";
|
|
185
|
+
}>;
|
|
186
|
+
scenario: z.ZodObject<{
|
|
187
|
+
id: z.ZodString;
|
|
188
|
+
sourceSha256: z.ZodString;
|
|
189
|
+
}, z.core.$strip>;
|
|
190
|
+
toolchain: z.ZodObject<{
|
|
191
|
+
node: z.ZodString;
|
|
192
|
+
playwright: z.ZodString;
|
|
193
|
+
chromiumRevision: z.ZodString;
|
|
194
|
+
ffmpeg: z.ZodString;
|
|
195
|
+
libass: z.ZodString;
|
|
196
|
+
fontSha256: z.ZodString;
|
|
197
|
+
}, z.core.$strip>;
|
|
198
|
+
video: z.ZodObject<{
|
|
199
|
+
durationMs: z.ZodNumber;
|
|
200
|
+
frames: z.ZodNumber;
|
|
201
|
+
width: z.ZodNumber;
|
|
202
|
+
height: z.ZodNumber;
|
|
203
|
+
fps: z.ZodNumber;
|
|
204
|
+
contentDurationMs: z.ZodNumber;
|
|
205
|
+
introDurationMs: z.ZodDefault<z.ZodNumber>;
|
|
206
|
+
outroDurationMs: z.ZodNumber;
|
|
207
|
+
}, z.core.$strip>;
|
|
208
|
+
captions: z.ZodObject<{
|
|
209
|
+
language: z.ZodString;
|
|
210
|
+
cueCount: z.ZodNumber;
|
|
211
|
+
characterCount: z.ZodNumber;
|
|
212
|
+
medianCueMs: z.ZodNumber;
|
|
213
|
+
shortestCueMs: z.ZodNumber;
|
|
214
|
+
longestCueMs: z.ZodNumber;
|
|
215
|
+
}, z.core.$strip>;
|
|
216
|
+
chapters: z.ZodArray<z.ZodObject<{
|
|
217
|
+
startMs: z.ZodNumber;
|
|
218
|
+
title: z.ZodString;
|
|
219
|
+
}, z.core.$strip>>;
|
|
220
|
+
narration: z.ZodOptional<z.ZodObject<{
|
|
221
|
+
clipCount: z.ZodNumber;
|
|
222
|
+
spokenMs: z.ZodNumber;
|
|
223
|
+
suppliedCount: z.ZodNumber;
|
|
224
|
+
voice: z.ZodOptional<z.ZodString>;
|
|
225
|
+
}, z.core.$strip>>;
|
|
226
|
+
outputs: z.ZodArray<z.ZodObject<{
|
|
227
|
+
path: z.ZodString;
|
|
228
|
+
sha256: z.ZodString;
|
|
229
|
+
bytes: z.ZodNumber;
|
|
230
|
+
}, z.core.$strip>>;
|
|
231
|
+
assertions: z.ZodArray<z.ZodObject<{
|
|
232
|
+
id: z.ZodString;
|
|
233
|
+
status: z.ZodEnum<{
|
|
234
|
+
passed: "passed";
|
|
235
|
+
failed: "failed";
|
|
236
|
+
}>;
|
|
237
|
+
}, z.core.$strip>>;
|
|
238
|
+
artifactCount: z.ZodNumber;
|
|
239
|
+
bundleBytes: z.ZodNumber;
|
|
240
|
+
schema: z.ZodLiteral<"agent-demo.result/v1">;
|
|
241
|
+
kind: z.ZodLiteral<"inspect">;
|
|
242
|
+
ok: z.ZodBoolean;
|
|
243
|
+
problems: z.ZodArray<z.ZodString>;
|
|
244
|
+
}, z.core.$strip>;
|
|
245
|
+
export declare const DoctorResultSchema: z.ZodObject<{
|
|
246
|
+
version: z.ZodString;
|
|
247
|
+
ffmpeg: z.ZodString;
|
|
248
|
+
ffprobe: z.ZodString;
|
|
249
|
+
libass: z.ZodString;
|
|
250
|
+
hasLibass: z.ZodBoolean;
|
|
251
|
+
hasX264: z.ZodBoolean;
|
|
252
|
+
hasAac: z.ZodBoolean;
|
|
253
|
+
filters: z.ZodArray<z.ZodString>;
|
|
254
|
+
speech: z.ZodObject<{
|
|
255
|
+
ready: z.ZodBoolean;
|
|
256
|
+
voices: z.ZodArray<z.ZodString>;
|
|
257
|
+
notes: z.ZodArray<z.ZodString>;
|
|
258
|
+
}, z.core.$strip>;
|
|
259
|
+
schema: z.ZodLiteral<"agent-demo.result/v1">;
|
|
260
|
+
kind: z.ZodLiteral<"doctor">;
|
|
261
|
+
ok: z.ZodBoolean;
|
|
262
|
+
problems: z.ZodArray<z.ZodString>;
|
|
263
|
+
}, z.core.$strip>;
|
|
264
|
+
export declare const ActivateResultSchema: z.ZodObject<{
|
|
265
|
+
reason: z.ZodOptional<z.ZodEnum<{
|
|
266
|
+
"empty-key": "empty-key";
|
|
267
|
+
"not-found": "not-found";
|
|
268
|
+
revoked: "revoked";
|
|
269
|
+
offline: "offline";
|
|
270
|
+
"unexpected-response": "unexpected-response";
|
|
271
|
+
"not-configured": "not-configured";
|
|
272
|
+
}>>;
|
|
273
|
+
licence: z.ZodOptional<z.ZodObject<{
|
|
274
|
+
subject: z.ZodString;
|
|
275
|
+
activatedAt: z.ZodString;
|
|
276
|
+
uses: z.ZodNumber;
|
|
277
|
+
}, z.core.$strip>>;
|
|
278
|
+
schema: z.ZodLiteral<"agent-demo.result/v1">;
|
|
279
|
+
kind: z.ZodLiteral<"activate">;
|
|
280
|
+
ok: z.ZodBoolean;
|
|
281
|
+
problems: z.ZodArray<z.ZodString>;
|
|
282
|
+
}, z.core.$strip>;
|
|
283
|
+
/**
|
|
284
|
+
* Mirrors `LicenseStatus` in @plaintake/license: a discriminated union on `tier`, so a
|
|
285
|
+
* caller cannot read `subject` without having established the tier.
|
|
286
|
+
*/
|
|
287
|
+
export declare const LicenceResultSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
288
|
+
tier: z.ZodLiteral<"free">;
|
|
289
|
+
problem: z.ZodOptional<z.ZodString>;
|
|
290
|
+
schema: z.ZodLiteral<"agent-demo.result/v1">;
|
|
291
|
+
kind: z.ZodLiteral<"licence">;
|
|
292
|
+
ok: z.ZodBoolean;
|
|
293
|
+
problems: z.ZodArray<z.ZodString>;
|
|
294
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
295
|
+
tier: z.ZodLiteral<"pro">;
|
|
296
|
+
subject: z.ZodString;
|
|
297
|
+
activatedAt: z.ZodString;
|
|
298
|
+
uses: z.ZodNumber;
|
|
299
|
+
schema: z.ZodLiteral<"agent-demo.result/v1">;
|
|
300
|
+
kind: z.ZodLiteral<"licence">;
|
|
301
|
+
ok: z.ZodBoolean;
|
|
302
|
+
problems: z.ZodArray<z.ZodString>;
|
|
303
|
+
}, z.core.$strip>], "tier">;
|
|
304
|
+
export type ArtifactRef = z.infer<typeof ArtifactRefSchema>;
|
|
305
|
+
export type ValidationResult = z.infer<typeof ValidationResultSchema>;
|
|
306
|
+
export type RunCommandResult = z.infer<typeof RunCommandResultSchema>;
|
|
307
|
+
export type RenderCommandResult = z.infer<typeof RenderCommandResultSchema>;
|
|
308
|
+
export type VerificationReport = z.infer<typeof VerificationReportSchema>;
|
|
309
|
+
export type InspectResult = z.infer<typeof InspectResultSchema>;
|
|
310
|
+
export type DoctorResult = z.infer<typeof DoctorResultSchema>;
|
|
311
|
+
export type ActivateResult = z.infer<typeof ActivateResultSchema>;
|
|
312
|
+
export type LicenceResult = z.infer<typeof LicenceResultSchema>;
|
|
313
|
+
export type DemoResult = ValidationResult | RunCommandResult | RenderCommandResult | VerificationReport | InspectResult | DoctorResult | ActivateResult | LicenceResult;
|
|
314
|
+
/**
|
|
315
|
+
* Renders a filesystem location for display in a result, relative to `root` when it
|
|
316
|
+
* lies inside it.
|
|
317
|
+
*
|
|
318
|
+
* A path outside the root is returned unchanged rather than rejected, because the CLI
|
|
319
|
+
* is deliberately not sandboxed and `--output /tmp/scratch` is a legitimate
|
|
320
|
+
* thing to ask for. The MCP server has the opposite requirement — spec §6, "return
|
|
321
|
+
* relative artifact paths" — and gets it from its sandbox, which resolves every input
|
|
322
|
+
* beneath the workspace root before this is ever called, so the relative branch always
|
|
323
|
+
* wins there. `assertNoAbsolutePaths` enforces that at the MCP boundary.
|
|
324
|
+
*
|
|
325
|
+
* Note this applies to *locations* like `bundleDir`. Paths *within* a bundle stay
|
|
326
|
+
* strictly relative, enforced by the schemas above.
|
|
327
|
+
*/
|
|
328
|
+
export declare function toResultPath(root: string, path: string): string;
|
|
329
|
+
/**
|
|
330
|
+
* Guards the MCP contract: no field anywhere in a result may be an absolute path.
|
|
331
|
+
* Cheap, and it fails loudly rather than quietly leaking the server's filesystem
|
|
332
|
+
* layout to a client that asked for workspace-relative paths.
|
|
333
|
+
*/
|
|
334
|
+
export declare function assertNoAbsolutePaths(result: DemoResult): void;
|
|
335
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* The opening card, declared by the scenario rather than by configuration.
|
|
4
|
+
*
|
|
5
|
+
* The counterpart to the closing credit, and deliberately not the same kind of thing. The
|
|
6
|
+
* outro is *branding* — one card for everything a machine records, owned by the licence and
|
|
7
|
+
* fixed in code on the Free Tier so the credit cannot be quietly removed. An intro is
|
|
8
|
+
* *content*: the hook for this demo and no other, which is why it is authored here beside
|
|
9
|
+
* the steps it introduces and is available on every tier. Only its colours follow branding.
|
|
10
|
+
*
|
|
11
|
+
* `narration` is spoken by the same voice that reads a step's `subtitle`, and is optional:
|
|
12
|
+
* a silent title card is a reasonable thing to want, and a scenario recorded without
|
|
13
|
+
* `--speech` has no voice to read it with either way. `durationMs` is a floor rather than a
|
|
14
|
+
* cut — the recorder lengthens the card when the line needs longer, exactly as `holdMs`
|
|
15
|
+
* gives way to a step's own narration.
|
|
16
|
+
*
|
|
17
|
+
* Two lines, matching the outro: same card, same layout, and a third line would have to
|
|
18
|
+
* shrink the type to fit.
|
|
19
|
+
*/
|
|
20
|
+
export declare const ScenarioIntroSchema: z.ZodObject<{
|
|
21
|
+
lines: z.ZodArray<z.ZodString>;
|
|
22
|
+
narration: z.ZodOptional<z.ZodString>;
|
|
23
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
24
|
+
}, z.core.$strip>;
|
|
25
|
+
export type ScenarioIntro = z.infer<typeof ScenarioIntroSchema>;
|
|
26
|
+
/**
|
|
27
|
+
* Per-scenario camera framing, for the demos whose pages the defaults do not suit.
|
|
28
|
+
*
|
|
29
|
+
* Every field is optional and every default is the constant the camera already used, so a
|
|
30
|
+
* scenario that says nothing is framed exactly as it was before this existed. That is the
|
|
31
|
+
* whole design: the ceiling is not lowered globally, because doing so would silently
|
|
32
|
+
* re-frame every recording anyone has ever made, and the values here were measured against
|
|
33
|
+
* real pages rather than guessed.
|
|
34
|
+
*
|
|
35
|
+
* `maxZoom` is the one that matters. At the 1.6x default the tightest window is 1216x684 —
|
|
36
|
+
* 37% of the frame discarded — which is fine for a compact form and is why one recorded demo
|
|
37
|
+
* lost the whole left third of a results page. A busy layout wants 1.3x or so; text-heavy
|
|
38
|
+
* pages that need the magnification can keep 1.6.
|
|
39
|
+
*
|
|
40
|
+
* `margin` is the breathing room kept around the target when choosing that zoom, as a
|
|
41
|
+
* fraction of the target on each side. Raising it is the gentler way to keep context in
|
|
42
|
+
* frame: it lowers the zoom the target asks for, rather than capping the zoom it gets.
|
|
43
|
+
*
|
|
44
|
+
* `minDwellMs` is the one thing here that changes the recording rather than the framing —
|
|
45
|
+
* see `runtime.ts`, where it is applied.
|
|
46
|
+
*/
|
|
47
|
+
export declare const ScenarioCameraSchema: z.ZodObject<{
|
|
48
|
+
maxZoom: z.ZodOptional<z.ZodNumber>;
|
|
49
|
+
margin: z.ZodOptional<z.ZodNumber>;
|
|
50
|
+
easeMs: z.ZodOptional<z.ZodNumber>;
|
|
51
|
+
minDwellMs: z.ZodOptional<z.ZodNumber>;
|
|
52
|
+
}, z.core.$strip>;
|
|
53
|
+
export type ScenarioCamera = z.infer<typeof ScenarioCameraSchema>;
|
|
54
|
+
export declare const ScenarioMetaSchema: z.ZodObject<{
|
|
55
|
+
schema: z.ZodLiteral<"agent-demo.scenario/v1">;
|
|
56
|
+
id: z.ZodString;
|
|
57
|
+
title: z.ZodString;
|
|
58
|
+
language: z.ZodDefault<z.ZodString>;
|
|
59
|
+
viewport: z.ZodObject<{
|
|
60
|
+
width: z.ZodLiteral<1920>;
|
|
61
|
+
height: z.ZodLiteral<1080>;
|
|
62
|
+
deviceScaleFactor: z.ZodLiteral<1>;
|
|
63
|
+
}, z.core.$strip>;
|
|
64
|
+
locale: z.ZodLiteral<"en-US">;
|
|
65
|
+
timezoneId: z.ZodLiteral<"UTC">;
|
|
66
|
+
colorScheme: z.ZodLiteral<"light">;
|
|
67
|
+
reducedMotion: z.ZodLiteral<"reduce">;
|
|
68
|
+
allowedConsoleErrors: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
69
|
+
handoff: z.ZodDefault<z.ZodEnum<{
|
|
70
|
+
none: "none";
|
|
71
|
+
preflight: "preflight";
|
|
72
|
+
session: "session";
|
|
73
|
+
}>>;
|
|
74
|
+
handoffTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
75
|
+
intro: z.ZodOptional<z.ZodObject<{
|
|
76
|
+
lines: z.ZodArray<z.ZodString>;
|
|
77
|
+
narration: z.ZodOptional<z.ZodString>;
|
|
78
|
+
durationMs: z.ZodOptional<z.ZodNumber>;
|
|
79
|
+
}, z.core.$strip>>;
|
|
80
|
+
camera: z.ZodOptional<z.ZodObject<{
|
|
81
|
+
maxZoom: z.ZodOptional<z.ZodNumber>;
|
|
82
|
+
margin: z.ZodOptional<z.ZodNumber>;
|
|
83
|
+
easeMs: z.ZodOptional<z.ZodNumber>;
|
|
84
|
+
minDwellMs: z.ZodOptional<z.ZodNumber>;
|
|
85
|
+
}, z.core.$strip>>;
|
|
86
|
+
}, z.core.$strip>;
|
|
87
|
+
/** Which phase, if any, hands the browser over. Read by the recorder and the surfaces. */
|
|
88
|
+
export type HandoffMode = z.infer<typeof ScenarioMetaSchema>['handoff'];
|
|
89
|
+
export type ScenarioMeta = z.infer<typeof ScenarioMetaSchema>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Deterministic JSON: sorted keys, 2-space indent, LF, exactly one trailing newline. */
|
|
2
|
+
export declare function stableStringify(value: unknown): string;
|
|
3
|
+
/** Single-line deterministic JSON for NDJSON records. */
|
|
4
|
+
export declare function stableJsonLine(value: unknown): string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ZodError } from 'zod';
|
|
2
|
+
/** So a caller outside this package's Zod boundary never needs its own `zod` dependency. */
|
|
3
|
+
export declare function isZodError(error: unknown): error is ZodError;
|
|
4
|
+
/**
|
|
5
|
+
* One line per issue, `path: message`, instead of `ZodError.message`'s raw JSON array —
|
|
6
|
+
* the same array `validate` and `demo_validate` were both printing verbatim as a "problem".
|
|
7
|
+
*/
|
|
8
|
+
export declare function formatZodError(error: ZodError): string;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import type { ScenarioMeta } from './schema/index.js';
|
|
2
|
+
import type { Locator, Page } from 'playwright-core';
|
|
3
|
+
/**
|
|
4
|
+
* What kind of interaction `run()` performs, declared rather than observed. Like
|
|
5
|
+
* `target`, this is metadata the recorder records and never performs — it tells the
|
|
6
|
+
* cursor track what to draw at the target (`click` a ripple, `type`/`point` a settle).
|
|
7
|
+
* Absent means `point`: the cursor still moves to the target, nothing pulses.
|
|
8
|
+
*/
|
|
9
|
+
export type DemoAction = 'click' | 'type' | 'point';
|
|
10
|
+
export type DemoStep = {
|
|
11
|
+
id: string;
|
|
12
|
+
title: string;
|
|
13
|
+
subtitle?: string;
|
|
14
|
+
/** Locator whose bounding box is recorded on step.start. Never used to perform the action. */
|
|
15
|
+
target?: Locator;
|
|
16
|
+
/** Declared interaction kind, recorded on step.start and consumed by the cursor track. */
|
|
17
|
+
action?: DemoAction;
|
|
18
|
+
/** Extra presentation hold after the action completes, in whole milliseconds. */
|
|
19
|
+
holdMs?: number;
|
|
20
|
+
run: () => Promise<unknown>;
|
|
21
|
+
};
|
|
22
|
+
export type DemoAssertion = {
|
|
23
|
+
id: string;
|
|
24
|
+
title: string;
|
|
25
|
+
run: () => Promise<unknown>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* A CSS selector string, not a Locator, so a mask can be registered
|
|
29
|
+
* before the element it hides exists.
|
|
30
|
+
*/
|
|
31
|
+
export type DemoMask = {
|
|
32
|
+
id: string;
|
|
33
|
+
selector: string;
|
|
34
|
+
reason?: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* A bounded wait for something PlainTake is not driving — a person approving a push
|
|
38
|
+
* notification on their phone, clicking a link in an email, or a background job finishing.
|
|
39
|
+
* Prompts nobody and needs no visible window, so unlike a handoff it works headless and in
|
|
40
|
+
* CI.
|
|
41
|
+
*
|
|
42
|
+
* It exists rather than leaving authors to `await page.waitForSelector(...)` for the three
|
|
43
|
+
* reasons `demo.pause` exists rather than `page.waitForTimeout`: it races the run's abort
|
|
44
|
+
* signal, so Ctrl-C during a five-minute wait does not hang until the wait expires; it puts
|
|
45
|
+
* the wait on the timeline, so a still stretch of video has a recorded explanation; and it
|
|
46
|
+
* gives timeouts one error kind instead of whatever Playwright happened to throw.
|
|
47
|
+
*/
|
|
48
|
+
export type DemoWaitFor = {
|
|
49
|
+
id: string;
|
|
50
|
+
/** Recorded as the event title, and shown to a person in later phases. Never a secret. */
|
|
51
|
+
title: string;
|
|
52
|
+
/**
|
|
53
|
+
* Resolves when the wait is over. Invoked once; a rejection fails the run.
|
|
54
|
+
*
|
|
55
|
+
* Pass `{ timeout: 0 }` to whatever Playwright call is inside, so `timeoutMs` below is the
|
|
56
|
+
* only deadline. Otherwise Playwright's own 30s default fires first and reports the
|
|
57
|
+
* author's locator as the culprit rather than the wait.
|
|
58
|
+
*/
|
|
59
|
+
until: () => Promise<unknown>;
|
|
60
|
+
/** Defaults to 120s, capped at 300s. Out-of-range values are a usage error. */
|
|
61
|
+
timeoutMs?: number;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Hands the real browser window to a person, and waits for them.
|
|
65
|
+
*
|
|
66
|
+
* The counterpart to `waitFor`: that one blocks on a condition and prompts nobody, this one
|
|
67
|
+
* prompts somebody and blocks on them. The person acts *in the browser* — types the one-time
|
|
68
|
+
* code, solves the CAPTCHA, completes the SSO round trip — so no secret ever passes through
|
|
69
|
+
* PlainTake. See `HumanReply`: the only thing that comes back is which of three things
|
|
70
|
+
* happened.
|
|
71
|
+
*
|
|
72
|
+
* Requires `handoff` in the scenario's metadata, and the phase must match. That is checked
|
|
73
|
+
* before Chromium starts, so a scenario that could never work says so immediately.
|
|
74
|
+
*/
|
|
75
|
+
export type PreflightHandoff = {
|
|
76
|
+
id: string;
|
|
77
|
+
/** Shown to the person and recorded as the event title. Never a secret. */
|
|
78
|
+
title: string;
|
|
79
|
+
/** Extra lines at the prompt: what to do, and how to know it worked. */
|
|
80
|
+
detail?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Optional. When present, the handoff can end on its own the moment the condition holds,
|
|
83
|
+
* without the person confirming — the same `{ timeout: 0 }` rule as `DemoWaitFor.until`
|
|
84
|
+
* applies. Without it, the only way out is the person, the deadline, or a cancellation.
|
|
85
|
+
*/
|
|
86
|
+
until?: () => Promise<unknown>;
|
|
87
|
+
/** Defaults to the scenario's `handoffTimeoutMs`, itself 120s and capped at 300s. */
|
|
88
|
+
timeoutMs?: number;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* A handoff that happens on camera. Everything a pre-flight one has, plus the single
|
|
92
|
+
* thing that only means anything once the recording is running.
|
|
93
|
+
*/
|
|
94
|
+
export type DemoHandoff = PreflightHandoff & {
|
|
95
|
+
/**
|
|
96
|
+
* CSS selector, registered before the window is handed over and left registered afterwards,
|
|
97
|
+
* exactly as `demo.mask` does — so a field the *person* fills during the handoff is
|
|
98
|
+
* unreadable from the moment it exists, on this document and on every one after it.
|
|
99
|
+
*
|
|
100
|
+
* It hides what it names and nothing else. Anything already on screen when the handoff
|
|
101
|
+
* begins was already in the frames before it, so mask that with `demo.mask` earlier
|
|
102
|
+
* instead; and a confirmation toast, a URL bar or a desktop notification is beyond reach
|
|
103
|
+
* of any selector. A person cannot proofread what they cannot see.
|
|
104
|
+
*
|
|
105
|
+
* Session-only, and a usage error in pre-flight: nothing is being recorded there, so a mask
|
|
106
|
+
* would hide nothing while silently applying to the whole video that follows.
|
|
107
|
+
*/
|
|
108
|
+
mask?: string;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* What a scenario may do before recording starts.
|
|
112
|
+
*
|
|
113
|
+
* Deliberately narrower than `DemoContext`. `chapter`, `step`, `assert` and `mask` all exist
|
|
114
|
+
* to put something on a video timeline, and during pre-flight there is no video timeline
|
|
115
|
+
* yet. Offering them here and silently dropping their output is exactly the "quietly does
|
|
116
|
+
* nothing" failure the rest of the DSL is built to avoid, so they are absent instead.
|
|
117
|
+
*
|
|
118
|
+
* Both members are here rather than only on `DemoContext` because pre-flight is the *better*
|
|
119
|
+
* place for a handoff: a sign-in done here reaches neither the video nor the trace.
|
|
120
|
+
*
|
|
121
|
+
* `handoff` takes the narrower `PreflightHandoff` for the same reason the four missing
|
|
122
|
+
* methods are missing: `mask` exists to keep something out of the pixels, and there are no
|
|
123
|
+
* pixels yet. Passing one here is a compile error on the spot rather than a mask that
|
|
124
|
+
* quietly does nothing — and the runtime refuses it too, for the object that was not a
|
|
125
|
+
* literal.
|
|
126
|
+
*/
|
|
127
|
+
export interface PreflightContext {
|
|
128
|
+
waitFor(condition: DemoWaitFor): Promise<void>;
|
|
129
|
+
handoff(request: PreflightHandoff): Promise<void>;
|
|
130
|
+
}
|
|
131
|
+
export interface DemoContext extends PreflightContext {
|
|
132
|
+
/** Widened by exactly one optional property, `mask`. See `DemoHandoff`. */
|
|
133
|
+
handoff(request: DemoHandoff): Promise<void>;
|
|
134
|
+
chapter(title: string): Promise<void>;
|
|
135
|
+
step(step: DemoStep): Promise<void>;
|
|
136
|
+
assert(assertion: DemoAssertion): Promise<void>;
|
|
137
|
+
mask(mask: DemoMask): Promise<void>;
|
|
138
|
+
pause(ms: number): Promise<void>;
|
|
139
|
+
}
|
|
140
|
+
export type DemoRunArgs = {
|
|
141
|
+
page: Page;
|
|
142
|
+
demo: DemoContext;
|
|
143
|
+
baseURL: string;
|
|
144
|
+
};
|
|
145
|
+
/** Same property names as `DemoRunArgs`, so moving code between phases changes nothing else. */
|
|
146
|
+
export type PreflightArgs = {
|
|
147
|
+
page: Page;
|
|
148
|
+
demo: PreflightContext;
|
|
149
|
+
baseURL: string;
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Runs before `tracing.start()` as well as before `screencast.start()`, so nothing it
|
|
153
|
+
* does reaches either the trace or the video, and hands the page back on `about:blank`.
|
|
154
|
+
*/
|
|
155
|
+
type PreflightHook = {
|
|
156
|
+
preflight?(args: PreflightArgs): Promise<void>;
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Runs after pre-flight's `about:blank` hand-back and before `runtime.beginSession()`,
|
|
160
|
+
* `tracing.start()` and `screencast.start()`.
|
|
161
|
+
*
|
|
162
|
+
* It exists because the hand-back is load-bearing and must stay: whatever pre-flight ended
|
|
163
|
+
* on is not frame 0 of the video, and the timeline origin is measured under the same starting
|
|
164
|
+
* condition a no-preflight run starts under. But that same hand-back is why a scenario
|
|
165
|
+
* cannot start its video on a loaded page by navigating in pre-flight — the reset throws
|
|
166
|
+
* the navigation away, and the alternative, a `page.goto` as the first step's action, films
|
|
167
|
+
* the load: a client-rendered app whose first paint is blank puts seconds of white between
|
|
168
|
+
* the opening card and the first screen. This phase is the way out. What it navigates to and
|
|
169
|
+
* waits for is the author's to declare, because only the scenario knows which page the video
|
|
170
|
+
* opens on and what "ready" means on it; that it runs off-camera is the recorder's to
|
|
171
|
+
* guarantee, and is the same guarantee pre-flight already rests on.
|
|
172
|
+
*
|
|
173
|
+
* Takes `PreflightArgs` rather than a context of its own: it is still pre-session, so
|
|
174
|
+
* `chapter`, `step`, `assert`, `mask` and `pause` would have no timeline to write to and are
|
|
175
|
+
* absent for the same reason they are absent from pre-flight. A `waitFor` or a `handoff`
|
|
176
|
+
* called here records under the pre-flight phase, which is the honest phase for anything
|
|
177
|
+
* that happens before the session begins.
|
|
178
|
+
*/
|
|
179
|
+
type WarmupHook = {
|
|
180
|
+
warmup?(args: PreflightArgs): Promise<void>;
|
|
181
|
+
};
|
|
182
|
+
export type DemoDefinition = ScenarioMeta & {
|
|
183
|
+
run(args: DemoRunArgs): Promise<void>;
|
|
184
|
+
} & PreflightHook & WarmupHook;
|
|
185
|
+
/**
|
|
186
|
+
* The metadata fields `ScenarioMetaSchema` supplies a `.default()` for, and which an author
|
|
187
|
+
* may therefore leave out. Named rather than spelled inline twice, so adding a defaulted
|
|
188
|
+
* field is one edit here instead of two that can drift apart.
|
|
189
|
+
*/
|
|
190
|
+
type Defaulted = 'language' | 'allowedConsoleErrors' | 'handoff' | 'handoffTimeoutMs';
|
|
191
|
+
export type DemoDefinitionInput = Omit<ScenarioMeta, Defaulted> & Partial<Pick<ScenarioMeta, Defaulted>> & {
|
|
192
|
+
run(args: DemoRunArgs): Promise<void>;
|
|
193
|
+
} & PreflightHook & WarmupHook;
|
|
194
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@plaintake/scenario",
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"description": "Authoring SDK for PlainTake demo scenarios: defineDemo and the scenario DSL types. Install for editor autocomplete; the PlainTake binary ships a runtime fallback.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"LICENSE",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"playwright-core": "1.62.1",
|
|
23
|
+
"zod": "4.4.3"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@plaintake/schema": "0.0.0"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {}
|
|
29
|
+
}
|