imprint-mcp 0.6.0 → 0.6.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.
- package/README.md +2 -0
- package/package.json +4 -2
- package/prompts/audit-agent.md +4 -2
- package/prompts/auth-compile-agent.md +149 -158
- package/prompts/build-planning.md +14 -9
- package/prompts/compile-agent.md +46 -23
- package/prompts/live-verifier-agent.md +101 -0
- package/prompts/playbook-compilation.md +8 -1
- package/prompts/tool-candidate-detection.md +9 -29
- package/src/cli.ts +83 -1
- package/src/imprint/audit.ts +219 -20
- package/src/imprint/auth-compile-agent.ts +215 -155
- package/src/imprint/auth-compile-tools.ts +300 -118
- package/src/imprint/auth-verifier.ts +167 -180
- package/src/imprint/backend-cache.ts +18 -11
- package/src/imprint/backend-ladder.ts +225 -177
- package/src/imprint/build-plan.ts +3 -8
- package/src/imprint/cdp-browser-fetch.ts +930 -83
- package/src/imprint/claude-cli-compile.ts +46 -20
- package/src/imprint/codex-cli-compile.ts +436 -139
- package/src/imprint/compile-agent-types.ts +15 -10
- package/src/imprint/compile-agent.ts +88 -28
- package/src/imprint/compile-tools.ts +228 -39
- package/src/imprint/compile-verification.ts +187 -0
- package/src/imprint/compile.ts +71 -2
- package/src/imprint/credential-store.ts +57 -44
- package/src/imprint/emit.ts +3 -1
- package/src/imprint/freeform-redact.ts +27 -6
- package/src/imprint/live-verifier.ts +1665 -0
- package/src/imprint/llm.ts +22 -3
- package/src/imprint/login.ts +107 -25
- package/src/imprint/mcp-compile-server.ts +447 -114
- package/src/imprint/mcp-live-verifier-server.ts +359 -0
- package/src/imprint/mcp-server.ts +116 -35
- package/src/imprint/probe-backends.ts +88 -7
- package/src/imprint/recording-request.ts +36 -0
- package/src/imprint/request-capture.ts +40 -17
- package/src/imprint/runtime.ts +490 -337
- package/src/imprint/teach-state.ts +36 -2
- package/src/imprint/teach.ts +302 -146
- package/src/imprint/tool-candidates.ts +8 -10
- package/src/imprint/tool-loader.ts +12 -4
- package/src/imprint/types.ts +137 -68
|
@@ -18,7 +18,6 @@ import { createLog } from './log.ts';
|
|
|
18
18
|
import { compactRequestContexts, requestContextDigest } from './request-context.ts';
|
|
19
19
|
import { isTelemetryRequest } from './telemetry.ts';
|
|
20
20
|
import { setSpanAttributes, traced } from './tracing.ts';
|
|
21
|
-
import { TwoFactorTypeSchema } from './types.ts';
|
|
22
21
|
import type { CapturedRequest, Session } from './types.ts';
|
|
23
22
|
|
|
24
23
|
const PROMPTS_DIR = pathJoin(import.meta.dir, '..', '..', 'prompts');
|
|
@@ -97,14 +96,8 @@ export const SharedCompileContextSchema = z.object({
|
|
|
97
96
|
credentialNames: z.array(z.string()).default([]),
|
|
98
97
|
tokenExtractionNotes: z.string().default(''),
|
|
99
98
|
sharedHelperNotes: z.string().default(''),
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
twoFactorRequestSeqs: z.array(z.number().int().nonnegative()).default([]),
|
|
103
|
-
authCompletionSeqs: z.array(z.number().int().nonnegative()).default([]),
|
|
104
|
-
/** OTP only: names of initiate-response fields the completion request reads
|
|
105
|
-
* (chained as ${state.X}); listed structurally from the recording. */
|
|
106
|
-
twoFactorContext: z.array(z.string()).default([]),
|
|
107
|
-
twoFactorNotes: z.string().default(''),
|
|
99
|
+
authRequestSeqs: z.array(z.number().int().nonnegative()).default([]),
|
|
100
|
+
authNotes: z.string().default(''),
|
|
108
101
|
});
|
|
109
102
|
export type SharedCompileContext = z.infer<typeof SharedCompileContextSchema>;
|
|
110
103
|
|
|
@@ -115,7 +108,11 @@ export type SharedCompileContext = z.infer<typeof SharedCompileContextSchema>;
|
|
|
115
108
|
* replaying the login inline (which hammers the site at compile time). */
|
|
116
109
|
export function sharedContextHasAuth(ctx: SharedCompileContext | undefined): boolean {
|
|
117
110
|
if (!ctx) return false;
|
|
118
|
-
return
|
|
111
|
+
return (
|
|
112
|
+
ctx.authRequestSeqs.length > 0 ||
|
|
113
|
+
ctx.loginRequestSeqs.length > 0 ||
|
|
114
|
+
ctx.credentialNames.length > 0
|
|
115
|
+
);
|
|
119
116
|
}
|
|
120
117
|
|
|
121
118
|
export const ToolCandidateSchema = z.object({
|
|
@@ -338,6 +335,7 @@ export function buildSharedCompileContext(
|
|
|
338
335
|
return {
|
|
339
336
|
...detection.sharedContext,
|
|
340
337
|
loginRequestSeqs: [...new Set(detection.sharedContext.loginRequestSeqs)].sort((a, b) => a - b),
|
|
338
|
+
authRequestSeqs: [...new Set(detection.sharedContext.authRequestSeqs)].sort((a, b) => a - b),
|
|
341
339
|
};
|
|
342
340
|
}
|
|
343
341
|
|
|
@@ -161,9 +161,12 @@ export function toCamelCase(snake: string): string {
|
|
|
161
161
|
.join('');
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
/** Zod validator from workflow parameters
|
|
165
|
-
*
|
|
166
|
-
export function buildZodValidator(
|
|
164
|
+
/** Zod validator from workflow parameters. Auth actions select their own
|
|
165
|
+
* required subset at runtime, so the MCP transport accepts omitted fields. */
|
|
166
|
+
export function buildZodValidator(
|
|
167
|
+
parameters: WorkflowParameter[],
|
|
168
|
+
options: { allOptional?: boolean } = {},
|
|
169
|
+
): z.ZodObject<z.ZodRawShape> {
|
|
167
170
|
const shape: z.ZodRawShape = {};
|
|
168
171
|
for (const p of parameters) {
|
|
169
172
|
let field: z.ZodType;
|
|
@@ -178,8 +181,13 @@ export function buildZodValidator(parameters: WorkflowParameter[]): z.ZodObject<
|
|
|
178
181
|
field = z.boolean();
|
|
179
182
|
break;
|
|
180
183
|
}
|
|
184
|
+
if (p.choices?.length) {
|
|
185
|
+
field = field.refine((value) => p.choices?.includes(value), {
|
|
186
|
+
message: `Expected one of: ${p.choices.join(', ')}`,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
181
189
|
field = field.describe(p.description);
|
|
182
|
-
if (p.default !== undefined) field = field.optional();
|
|
190
|
+
if (p.default !== undefined || options.allOptional) field = field.optional();
|
|
183
191
|
shape[p.name] = field;
|
|
184
192
|
}
|
|
185
193
|
return z.object(shape);
|
package/src/imprint/types.ts
CHANGED
|
@@ -111,6 +111,8 @@ const WorkflowParameterSchema = z.object({
|
|
|
111
111
|
name: z.string(),
|
|
112
112
|
type: z.enum(['string', 'number', 'boolean']),
|
|
113
113
|
description: z.string(),
|
|
114
|
+
/** Optional finite choices to surface as JSON Schema enum values. */
|
|
115
|
+
choices: z.array(z.union([z.string(), z.number(), z.boolean()])).optional(),
|
|
114
116
|
/** Optional with this default if set. */
|
|
115
117
|
default: z.union([z.string(), z.number(), z.boolean()]).optional(),
|
|
116
118
|
/** Whether a `param:<name>` integration test verified this parameter's effect
|
|
@@ -152,6 +154,13 @@ const CaptureCommonSchema = z.object({
|
|
|
152
154
|
capability: StateCapabilitySchema.optional().default('ordinary_http'),
|
|
153
155
|
});
|
|
154
156
|
|
|
157
|
+
const CaptureEqualsSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
|
|
158
|
+
const RequestCaptureCommonSchema = CaptureCommonSchema.extend({
|
|
159
|
+
/** Optional exact scalar match. This also makes an explicitly matched empty
|
|
160
|
+
* string or null a valid capture value. */
|
|
161
|
+
equals: CaptureEqualsSchema.optional(),
|
|
162
|
+
});
|
|
163
|
+
|
|
155
164
|
const CookieCaptureSchema = CaptureCommonSchema.extend({
|
|
156
165
|
source: z.literal('cookie'),
|
|
157
166
|
cookie: z.string(),
|
|
@@ -162,22 +171,22 @@ const CookieCaptureSchema = CaptureCommonSchema.extend({
|
|
|
162
171
|
allowHttpOnlyProjection: z.boolean().optional().default(false),
|
|
163
172
|
});
|
|
164
173
|
|
|
165
|
-
const RequestCaptureSchema = z.discriminatedUnion('source', [
|
|
166
|
-
|
|
174
|
+
export const RequestCaptureSchema = z.discriminatedUnion('source', [
|
|
175
|
+
RequestCaptureCommonSchema.extend({
|
|
167
176
|
source: z.literal('json'),
|
|
168
177
|
path: z.string(),
|
|
169
178
|
}),
|
|
170
|
-
|
|
179
|
+
RequestCaptureCommonSchema.extend({
|
|
171
180
|
source: z.literal('response_header'),
|
|
172
181
|
header: z.string(),
|
|
173
182
|
mode: z.enum(['first', 'last', 'all']).optional().default('last'),
|
|
174
183
|
}),
|
|
175
|
-
|
|
184
|
+
RequestCaptureCommonSchema.extend({
|
|
176
185
|
source: z.literal('text_regex'),
|
|
177
186
|
pattern: z.string(),
|
|
178
187
|
group: z.number().int().nonnegative().optional().default(1),
|
|
179
188
|
}),
|
|
180
|
-
CookieCaptureSchema,
|
|
189
|
+
CookieCaptureSchema.extend({ equals: CaptureEqualsSchema.optional() }),
|
|
181
190
|
]);
|
|
182
191
|
export type RequestCapture = z.infer<typeof RequestCaptureSchema>;
|
|
183
192
|
|
|
@@ -226,11 +235,59 @@ export type BootstrapCapture = z.infer<typeof BootstrapCaptureSchema>;
|
|
|
226
235
|
|
|
227
236
|
const WorkflowRequestSchema = z.object({
|
|
228
237
|
method: z.string(),
|
|
238
|
+
/** Sequence number of the captured request that grounds this workflow request.
|
|
239
|
+
* Auth login uses this to resolve persisted captures only against their
|
|
240
|
+
* producing recorded response. */
|
|
241
|
+
recordingRequestSeq: z.number().int().nonnegative().optional(),
|
|
229
242
|
/** Template; ${param.X} substitutes a parameter, ${response[N].path} an
|
|
230
243
|
* earlier extracted value. */
|
|
231
244
|
url: z.string(),
|
|
232
245
|
headers: z.record(z.string()),
|
|
233
246
|
body: z.string().optional(),
|
|
247
|
+
/** Execute this GET or URL-encoded POST as a top-level browser navigation instead of fetch().
|
|
248
|
+
* This lets the recorded page run its own JavaScript and mint coupled browser
|
|
249
|
+
* state, and lets form POSTs retain document-navigation semantics, without
|
|
250
|
+
* teaching Imprint site- or framework-specific behavior. */
|
|
251
|
+
mode: z.enum(['fetch', 'navigate']).optional(),
|
|
252
|
+
/** Bounded completion criteria for mode="navigate". Predicates are ANDed.
|
|
253
|
+
* With no predicate, navigation completes at the selected lifecycle event. */
|
|
254
|
+
navigation: z
|
|
255
|
+
.object({
|
|
256
|
+
waitUntil: z.enum(['domcontentloaded', 'load']).optional(),
|
|
257
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
258
|
+
pollIntervalMs: z.number().int().positive().optional(),
|
|
259
|
+
urlIncludes: z.string().min(1).optional(),
|
|
260
|
+
/** Wait until the rendered document contains this CSS selector before
|
|
261
|
+
* snapshotting outerHTML. Useful for pages whose meaningful results
|
|
262
|
+
* hydrate after the load event. */
|
|
263
|
+
selector: z.string().min(1).optional(),
|
|
264
|
+
/** Optional browser interactions performed after the initial navigation
|
|
265
|
+
* predicate is satisfied. This is a generic escape hatch for workflows
|
|
266
|
+
* whose next request is assembled from private in-page state rather than
|
|
267
|
+
* values serialized into HTML. */
|
|
268
|
+
actions: z
|
|
269
|
+
.array(
|
|
270
|
+
z
|
|
271
|
+
.object({
|
|
272
|
+
action: z.literal('click'),
|
|
273
|
+
selector: z.string().min(1),
|
|
274
|
+
})
|
|
275
|
+
.strict(),
|
|
276
|
+
)
|
|
277
|
+
.min(1)
|
|
278
|
+
.optional(),
|
|
279
|
+
/** After navigation actions run, wait for this rendered selector before
|
|
280
|
+
* snapshotting the final document. */
|
|
281
|
+
resultSelector: z.string().min(1).optional(),
|
|
282
|
+
cookie: z
|
|
283
|
+
.object({
|
|
284
|
+
name: z.string().min(1),
|
|
285
|
+
domain: z.string().min(1).optional(),
|
|
286
|
+
path: z.string().min(1).optional(),
|
|
287
|
+
})
|
|
288
|
+
.optional(),
|
|
289
|
+
})
|
|
290
|
+
.optional(),
|
|
234
291
|
/** Names → jsonpath expressions; later requests reference via ${response[N].name}. */
|
|
235
292
|
extract: z.record(z.string()).optional(),
|
|
236
293
|
captures: z.array(RequestCaptureSchema).optional(),
|
|
@@ -244,44 +301,56 @@ const WorkflowRequestSchema = z.object({
|
|
|
244
301
|
});
|
|
245
302
|
export type WorkflowRequest = z.infer<typeof WorkflowRequestSchema>;
|
|
246
303
|
|
|
247
|
-
/** The two *structural* 2FA cases the runtime actually branches on, plus `none`.
|
|
248
|
-
* A code that arrives out-of-band (sms / email / authenticator app) and is typed
|
|
249
|
-
* back into a second request is one case (`otp`); an approval the user taps
|
|
250
|
-
* elsewhere while we poll is the other (`push`). The delivery *channel* never
|
|
251
|
-
* changes how we replay it, so we don't model it. */
|
|
252
|
-
export const TwoFactorTypeSchema = z.enum(['otp', 'push', 'none']).default('none');
|
|
253
|
-
|
|
254
304
|
const AuthConfigSchema = z
|
|
255
305
|
.object({
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
306
|
+
/** Recording-grounded auth program. Action names, boundaries, evidence,
|
|
307
|
+
* retry behavior, and completion are chosen by the compile agent. */
|
|
308
|
+
entry: z.string().min(1),
|
|
309
|
+
actions: z.record(
|
|
310
|
+
z.string().min(1),
|
|
311
|
+
z
|
|
312
|
+
.object({
|
|
313
|
+
parameters: z.array(z.string().min(1)).optional().default([]),
|
|
314
|
+
steps: z
|
|
315
|
+
.array(
|
|
316
|
+
z
|
|
317
|
+
.object({
|
|
318
|
+
request: z.number().int().nonnegative(),
|
|
319
|
+
onError: z.enum(['fail', 'continue', 'retry']).optional().default('fail'),
|
|
320
|
+
repeat: z
|
|
321
|
+
.object({
|
|
322
|
+
until: RequestCaptureSchema,
|
|
323
|
+
intervalMs: z.number().int().nonnegative(),
|
|
324
|
+
maxAttempts: z.number().int().positive(),
|
|
325
|
+
})
|
|
326
|
+
.strict()
|
|
327
|
+
.optional(),
|
|
328
|
+
})
|
|
329
|
+
.strict(),
|
|
330
|
+
)
|
|
331
|
+
.min(1),
|
|
332
|
+
outcome: z.discriminatedUnion('type', [
|
|
333
|
+
z
|
|
334
|
+
.object({
|
|
335
|
+
type: z.literal('pause'),
|
|
336
|
+
next: z.string().min(1),
|
|
337
|
+
evidence: z.array(z.string().min(1)).optional().default([]),
|
|
338
|
+
carry: z.array(z.string().min(1)).optional().default([]),
|
|
339
|
+
message: z.string().min(1),
|
|
340
|
+
})
|
|
341
|
+
.strict(),
|
|
342
|
+
z
|
|
343
|
+
.object({
|
|
344
|
+
type: z.literal('success'),
|
|
345
|
+
evidence: z.array(z.string().min(1)).optional().default([]),
|
|
346
|
+
})
|
|
347
|
+
.strict(),
|
|
348
|
+
]),
|
|
349
|
+
})
|
|
350
|
+
.strict(),
|
|
351
|
+
),
|
|
352
|
+
/** Captured state names to store as durable credentials after success. */
|
|
353
|
+
persist: z.array(z.string().min(1)).optional().default([]),
|
|
285
354
|
/** Opt-in: when the recorded login carries its session through a CROSS-ORIGIN
|
|
286
355
|
* `Set-Cookie` (e.g. a `functions.*`/`global.*` host sets a cookie that a
|
|
287
356
|
* later leg depends on), set this so cdp-replay writes those cross-origin
|
|
@@ -289,15 +358,8 @@ const AuthConfigSchema = z
|
|
|
289
358
|
* when the recording actually shows cross-origin cookie chaining; otherwise
|
|
290
359
|
* the browser's normal same-origin jar is left untouched. */
|
|
291
360
|
crossOriginCookieReinjection: z.boolean().default(false),
|
|
292
|
-
/** Durable session tokens to persist after a SUCCESSFUL login completion so
|
|
293
|
-
* DATA tools can reuse them without re-running auth (they re-auth only on
|
|
294
|
-
* expiry / AUTH_EXPIRED). Cookies persist automatically; declare here only
|
|
295
|
-
* the NON-cookie material a data request needs — a bearer / access / CSRF
|
|
296
|
-
* token the completion response returns in its body or a header. Each
|
|
297
|
-
* capture's resolved value is stored as a durable credential secret
|
|
298
|
-
* (`${credential.NAME}`). Grounded in the recording; channel/site-agnostic. */
|
|
299
|
-
sessionCapture: z.array(RequestCaptureSchema).default([]),
|
|
300
361
|
})
|
|
362
|
+
.strict()
|
|
301
363
|
.optional();
|
|
302
364
|
export type AuthConfig = z.infer<typeof AuthConfigSchema>;
|
|
303
365
|
|
|
@@ -331,14 +393,30 @@ export const WorkflowSchema = z.object({
|
|
|
331
393
|
*
|
|
332
394
|
* Return value:
|
|
333
395
|
* - `string` — the transformed URL (backward-compatible).
|
|
334
|
-
* - `{ url
|
|
396
|
+
* - `{ url?: string; body?: string; headers?: Record<string, string>; navigation?: object; skip?: boolean }` —
|
|
335
397
|
* URL plus optional body and header overrides for complex body formats
|
|
336
398
|
* (JSPB, nested JSON-in-form) where placeholder substitution alone
|
|
337
|
-
* cannot handle the encoding.
|
|
399
|
+
* cannot handle the encoding. `navigation` may supply dynamic browser
|
|
400
|
+
* navigation actions derived from resolved parameters. `skip: true` skips this request, for
|
|
401
|
+
* conditional follow-up requests such as pagination/detail calls.
|
|
338
402
|
*
|
|
339
403
|
* The optional 4th arg `params` carries the resolved workflow parameters
|
|
340
404
|
* so the transform can construct request bodies programmatically. */
|
|
341
405
|
requestTransformModule: z.string().optional(),
|
|
406
|
+
/** Agent-authored capability limitations retained with the generated tool.
|
|
407
|
+
* A compiler may deliberately omit a secondary feature or parameter when the
|
|
408
|
+
* recording and live evidence cannot support it without guessing. Runtime
|
|
409
|
+
* execution does not interpret these notes; MCP descriptions and teach
|
|
410
|
+
* summaries surface them to callers and operators. */
|
|
411
|
+
limitations: z
|
|
412
|
+
.array(
|
|
413
|
+
z.object({
|
|
414
|
+
feature: z.string().min(1),
|
|
415
|
+
reason: z.string().min(1),
|
|
416
|
+
omittedParameters: z.array(z.string().min(1)).optional(),
|
|
417
|
+
}),
|
|
418
|
+
)
|
|
419
|
+
.optional(),
|
|
342
420
|
/** Did this tool's integration test produce live data at compile time?
|
|
343
421
|
*
|
|
344
422
|
* - `liveVerified: true` (default when present) — the integration test
|
|
@@ -383,12 +461,12 @@ export interface StateMissingItem {
|
|
|
383
461
|
|
|
384
462
|
/** Discriminated union returned by every generated tool. */
|
|
385
463
|
export type ToolResult<T = unknown> =
|
|
386
|
-
| { ok: true; data: T
|
|
464
|
+
| { ok: true; data: T }
|
|
387
465
|
| {
|
|
388
466
|
ok: false;
|
|
389
467
|
error:
|
|
390
468
|
| 'AUTH_EXPIRED' // 401 — run `imprint login`
|
|
391
|
-
| '
|
|
469
|
+
| 'ACTION_REQUIRED' // declared auth action completed; another action/user input is needed
|
|
392
470
|
| 'FORBIDDEN' // 403 — bot detection, geo, ToS, capability mismatch
|
|
393
471
|
| 'NETWORK' // fetch threw / timed out
|
|
394
472
|
| 'RATE_LIMITED' // 429
|
|
@@ -402,17 +480,12 @@ export type ToolResult<T = unknown> =
|
|
|
402
480
|
* (absent for transport/STATE_MISSING failures). Surfaced so the auth
|
|
403
481
|
* compile agent sees the concrete code, not just a prose message. */
|
|
404
482
|
status?: number;
|
|
405
|
-
/** Truncated response body of the failing request (first ~500 chars).
|
|
406
|
-
* Lets the compile agent inspect the server's actual error payload
|
|
407
|
-
* without re-running. Distinct from `loginResponsePreview`, which is the
|
|
408
|
-
* *initiate* response preview on the AWAITING_2FA path. */
|
|
483
|
+
/** Truncated response body of the failing request (first ~500 chars). */
|
|
409
484
|
responseBodyPreview?: string;
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
twoFactorContext?: Record<string, unknown>;
|
|
415
|
-
loginResponsePreview?: string;
|
|
485
|
+
/** Recording-derived state and generic action progress. Echo this object
|
|
486
|
+
* unchanged on the next auth call. */
|
|
487
|
+
continuation?: Record<string, unknown>;
|
|
488
|
+
nextAction?: string;
|
|
416
489
|
};
|
|
417
490
|
|
|
418
491
|
// ─── Cron config (input to `imprint cron`) ───────────────────────────────────
|
|
@@ -610,11 +683,7 @@ const PlaybookResultSchema = z.discriminatedUnion('source', [
|
|
|
610
683
|
]);
|
|
611
684
|
export type PlaybookResult = z.infer<typeof PlaybookResultSchema>;
|
|
612
685
|
|
|
613
|
-
/** A named value extracted from a captured XHR response
|
|
614
|
-
* success marker. Used by a login playbook to carry a 2FA-chain token (e.g. a
|
|
615
|
-
* single-use `SecurityCode` minted in the browser during the OTP-send step)
|
|
616
|
-
* out of the playbook run so a later stateless `submit_otp` can include it.
|
|
617
|
-
* The `name` should match an `authConfig.twoFactorContext` entry. */
|
|
686
|
+
/** A named value extracted from a playbook's captured XHR response. */
|
|
618
687
|
const PlaybookCaptureSchema = z.object({
|
|
619
688
|
name: z.string(),
|
|
620
689
|
/** Regex matched against the captured response URL. */
|