@showrun/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/dist/__tests__/dsl-validation.test.d.ts +2 -0
  3. package/dist/__tests__/dsl-validation.test.d.ts.map +1 -0
  4. package/dist/__tests__/dsl-validation.test.js +203 -0
  5. package/dist/__tests__/pack-versioning.test.d.ts +2 -0
  6. package/dist/__tests__/pack-versioning.test.d.ts.map +1 -0
  7. package/dist/__tests__/pack-versioning.test.js +165 -0
  8. package/dist/__tests__/validator.test.d.ts +2 -0
  9. package/dist/__tests__/validator.test.d.ts.map +1 -0
  10. package/dist/__tests__/validator.test.js +149 -0
  11. package/dist/authResilience.d.ts +146 -0
  12. package/dist/authResilience.d.ts.map +1 -0
  13. package/dist/authResilience.js +378 -0
  14. package/dist/browserLauncher.d.ts +74 -0
  15. package/dist/browserLauncher.d.ts.map +1 -0
  16. package/dist/browserLauncher.js +159 -0
  17. package/dist/browserPersistence.d.ts +49 -0
  18. package/dist/browserPersistence.d.ts.map +1 -0
  19. package/dist/browserPersistence.js +143 -0
  20. package/dist/context.d.ts +10 -0
  21. package/dist/context.d.ts.map +1 -0
  22. package/dist/context.js +30 -0
  23. package/dist/dsl/builders.d.ts +340 -0
  24. package/dist/dsl/builders.d.ts.map +1 -0
  25. package/dist/dsl/builders.js +416 -0
  26. package/dist/dsl/conditions.d.ts +33 -0
  27. package/dist/dsl/conditions.d.ts.map +1 -0
  28. package/dist/dsl/conditions.js +169 -0
  29. package/dist/dsl/interpreter.d.ts +24 -0
  30. package/dist/dsl/interpreter.d.ts.map +1 -0
  31. package/dist/dsl/interpreter.js +491 -0
  32. package/dist/dsl/stepHandlers.d.ts +32 -0
  33. package/dist/dsl/stepHandlers.d.ts.map +1 -0
  34. package/dist/dsl/stepHandlers.js +787 -0
  35. package/dist/dsl/target.d.ts +28 -0
  36. package/dist/dsl/target.d.ts.map +1 -0
  37. package/dist/dsl/target.js +110 -0
  38. package/dist/dsl/templating.d.ts +21 -0
  39. package/dist/dsl/templating.d.ts.map +1 -0
  40. package/dist/dsl/templating.js +73 -0
  41. package/dist/dsl/types.d.ts +695 -0
  42. package/dist/dsl/types.d.ts.map +1 -0
  43. package/dist/dsl/types.js +7 -0
  44. package/dist/dsl/validation.d.ts +15 -0
  45. package/dist/dsl/validation.d.ts.map +1 -0
  46. package/dist/dsl/validation.js +974 -0
  47. package/dist/index.d.ts +20 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +20 -0
  50. package/dist/jsonPackValidator.d.ts +11 -0
  51. package/dist/jsonPackValidator.d.ts.map +1 -0
  52. package/dist/jsonPackValidator.js +61 -0
  53. package/dist/loader.d.ts +35 -0
  54. package/dist/loader.d.ts.map +1 -0
  55. package/dist/loader.js +107 -0
  56. package/dist/networkCapture.d.ts +107 -0
  57. package/dist/networkCapture.d.ts.map +1 -0
  58. package/dist/networkCapture.js +390 -0
  59. package/dist/packUtils.d.ts +36 -0
  60. package/dist/packUtils.d.ts.map +1 -0
  61. package/dist/packUtils.js +97 -0
  62. package/dist/packVersioning.d.ts +25 -0
  63. package/dist/packVersioning.d.ts.map +1 -0
  64. package/dist/packVersioning.js +137 -0
  65. package/dist/runner.d.ts +62 -0
  66. package/dist/runner.d.ts.map +1 -0
  67. package/dist/runner.js +170 -0
  68. package/dist/types.d.ts +336 -0
  69. package/dist/types.d.ts.map +1 -0
  70. package/dist/types.js +1 -0
  71. package/dist/validator.d.ts +20 -0
  72. package/dist/validator.d.ts.map +1 -0
  73. package/dist/validator.js +68 -0
  74. package/package.json +49 -0
@@ -0,0 +1,491 @@
1
+ import { validateFlow } from './validation.js';
2
+ import { executeStep } from './stepHandlers.js';
3
+ import { resolveTemplates } from './templating.js';
4
+ import { OnceCache, AuthFailureMonitor, AuthGuardChecker, setupBrowserAuthMonitoring, shouldSkipStep, getOnceSteps, } from '../authResilience.js';
5
+ import { evaluateCondition, conditionToString } from './conditions.js';
6
+ /**
7
+ * Capture the delta of vars and collectibles produced by a step,
8
+ * along with any network entries referenced by new vars (for network_find caching)
9
+ */
10
+ function captureStepOutputs(varsBefore, varsAfter, collectiblesBefore, collectiblesAfter, networkCapture) {
11
+ const newVars = {};
12
+ const newCollectibles = {};
13
+ const networkEntries = [];
14
+ // Find vars that were added or modified
15
+ for (const [key, value] of Object.entries(varsAfter)) {
16
+ if (!(key in varsBefore) || varsBefore[key] !== value) {
17
+ newVars[key] = value;
18
+ // If the value looks like a request ID (string starting with 'req-'),
19
+ // export the network entry for caching
20
+ if (networkCapture && typeof value === 'string' && value.startsWith('req-')) {
21
+ const entry = networkCapture.exportEntry(value);
22
+ if (entry) {
23
+ networkEntries.push(entry);
24
+ }
25
+ }
26
+ }
27
+ }
28
+ // Find collectibles that were added or modified
29
+ for (const [key, value] of Object.entries(collectiblesAfter)) {
30
+ if (!(key in collectiblesBefore) || collectiblesBefore[key] !== value) {
31
+ newCollectibles[key] = value;
32
+ }
33
+ }
34
+ return {
35
+ vars: newVars,
36
+ collectibles: newCollectibles,
37
+ networkEntries: networkEntries.length > 0 ? networkEntries : undefined,
38
+ };
39
+ }
40
+ /**
41
+ * Redacts secret values from an object before logging.
42
+ * Replaces any occurrence of secret values with [REDACTED].
43
+ */
44
+ function redactSecrets(obj, secretValues) {
45
+ if (secretValues.length === 0)
46
+ return obj;
47
+ let str = JSON.stringify(obj);
48
+ for (const value of secretValues) {
49
+ // Only redact secrets that are at least 3 characters long
50
+ if (value && value.length >= 3) {
51
+ // Escape special regex characters in the secret value
52
+ const escaped = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
53
+ str = str.replace(new RegExp(escaped, 'g'), '[REDACTED]');
54
+ }
55
+ }
56
+ try {
57
+ return JSON.parse(str);
58
+ }
59
+ catch {
60
+ return obj;
61
+ }
62
+ }
63
+ /**
64
+ * Runs a flow of DSL steps sequentially with auth resilience support
65
+ */
66
+ export async function runFlow(ctx, steps, options) {
67
+ const startTime = Date.now();
68
+ const stopOnError = options?.stopOnError ?? true;
69
+ const inputs = options?.inputs ?? {};
70
+ const authConfig = options?.auth;
71
+ const sessionId = options?.sessionId;
72
+ const profileId = options?.profileId;
73
+ const cacheDir = options?.cacheDir;
74
+ const secrets = options?.secrets ?? {};
75
+ // Get secret values for redaction (only values >= 3 chars)
76
+ const secretValues = Object.values(secrets).filter((v) => v && v.length >= 3);
77
+ // Validate flow before execution
78
+ validateFlow(steps);
79
+ const collectibles = {};
80
+ const vars = {};
81
+ // Initialize variable context (including secrets for templating)
82
+ const variableContext = {
83
+ inputs,
84
+ vars,
85
+ secrets,
86
+ };
87
+ // Initialize auth resilience components: load persisted "once" cache when sessionId/profileId provided
88
+ const onceCache = sessionId || profileId
89
+ ? OnceCache.fromDisk(sessionId, profileId, cacheDir)
90
+ : new OnceCache();
91
+ let authMonitor = null;
92
+ let authGuard = null;
93
+ let authRecoveriesUsed = 0;
94
+ const stepContext = {
95
+ page: ctx.page,
96
+ collectibles,
97
+ vars,
98
+ inputs,
99
+ networkCapture: ctx.networkCapture,
100
+ authMonitor: authMonitor ?? undefined,
101
+ };
102
+ if (authConfig?.authPolicy) {
103
+ authMonitor = new AuthFailureMonitor(authConfig.authPolicy);
104
+ if (authMonitor.isEnabled()) {
105
+ setupBrowserAuthMonitoring(ctx.page, authMonitor, ctx.logger);
106
+ }
107
+ }
108
+ if (authConfig?.authGuard) {
109
+ authGuard = new AuthGuardChecker(authConfig.authGuard);
110
+ // Run auth guard check if enabled (before main steps)
111
+ if (authGuard.isEnabled()) {
112
+ const authValid = await authGuard.checkAuth(ctx.page);
113
+ if (!authValid) {
114
+ // Auth guard failed - run setup steps (once steps) before main flow
115
+ const onceSteps = getOnceSteps(steps);
116
+ if (onceSteps.length > 0) {
117
+ await executeStepsWithRecovery(ctx, stepContext, variableContext, onceSteps, onceCache, authMonitor, sessionId, profileId, stopOnError, authRecoveriesUsed);
118
+ }
119
+ }
120
+ }
121
+ }
122
+ let stepsExecuted = 0;
123
+ try {
124
+ // Execute steps sequentially
125
+ for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) {
126
+ const step = steps[stepIndex];
127
+ const stepStartTime = Date.now();
128
+ const stepLabel = step.label || step.id;
129
+ // Check if step should be skipped due to "once" cache
130
+ if (shouldSkipStep(step, onceCache, sessionId, profileId)) {
131
+ // Determine scope and restore cached outputs
132
+ const scope = step.once === 'session' && sessionId ? 'session' : 'profile';
133
+ const cachedOutputs = onceCache.getOutputs(step.id, scope);
134
+ // Restore cached vars and collectibles
135
+ if (cachedOutputs) {
136
+ Object.assign(vars, cachedOutputs.vars);
137
+ Object.assign(collectibles, cachedOutputs.collectibles);
138
+ // Restore network entries into the capture buffer
139
+ if (cachedOutputs.networkEntries && ctx.networkCapture) {
140
+ for (const entry of cachedOutputs.networkEntries) {
141
+ ctx.networkCapture.importEntry(entry);
142
+ }
143
+ }
144
+ }
145
+ ctx.logger.log({
146
+ type: 'step_skipped',
147
+ data: {
148
+ stepId: step.id,
149
+ type: step.type,
150
+ reason: 'once_already_executed',
151
+ restoredVars: cachedOutputs ? Object.keys(cachedOutputs.vars) : [],
152
+ restoredCollectibles: cachedOutputs ? Object.keys(cachedOutputs.collectibles) : [],
153
+ },
154
+ });
155
+ stepsExecuted++;
156
+ continue;
157
+ }
158
+ // Check if step should be skipped due to skip_if condition
159
+ if (step.skip_if) {
160
+ try {
161
+ const shouldSkip = await evaluateCondition({ page: ctx.page, vars }, step.skip_if);
162
+ if (shouldSkip) {
163
+ ctx.logger.log({
164
+ type: 'step_skipped',
165
+ data: {
166
+ stepId: step.id,
167
+ type: step.type,
168
+ reason: 'condition_met',
169
+ condition: conditionToString(step.skip_if),
170
+ },
171
+ });
172
+ stepsExecuted++;
173
+ continue;
174
+ }
175
+ }
176
+ catch (conditionError) {
177
+ // Log condition evaluation error but continue with step execution
178
+ console.warn(`[interpreter] Error evaluating skip_if for step ${step.id}:`, conditionError);
179
+ }
180
+ }
181
+ // Resolve templates in step params before execution
182
+ const resolvedStep = {
183
+ ...step,
184
+ params: resolveTemplates(step.params, variableContext),
185
+ };
186
+ // Log step start with resolved params (redact secrets for safe logging)
187
+ const logParams = redactSecrets(JSON.parse(JSON.stringify(resolvedStep.params)), secretValues);
188
+ ctx.logger.log({
189
+ type: 'step_started',
190
+ data: {
191
+ stepId: step.id,
192
+ type: step.type,
193
+ label: stepLabel,
194
+ params: logParams,
195
+ },
196
+ });
197
+ try {
198
+ // Snapshot state before step execution (for capturing outputs)
199
+ const varsBefore = resolvedStep.once ? { ...vars } : {};
200
+ const collectiblesBefore = resolvedStep.once ? { ...collectibles } : {};
201
+ // Apply step-level timeout if specified
202
+ const timeoutMs = resolvedStep.timeoutMs;
203
+ let stepPromise = executeStep(stepContext, resolvedStep);
204
+ if (timeoutMs) {
205
+ const timeoutPromise = new Promise((_, reject) => {
206
+ setTimeout(() => {
207
+ reject(new Error(`Step timeout after ${timeoutMs}ms`));
208
+ }, timeoutMs);
209
+ });
210
+ stepPromise = Promise.race([stepPromise, timeoutPromise]);
211
+ }
212
+ await stepPromise;
213
+ stepsExecuted++;
214
+ // Mark step as executed if it has "once" flag, with captured outputs
215
+ if (resolvedStep.once) {
216
+ const scope = resolvedStep.once === 'session' && sessionId ? 'session' : 'profile';
217
+ const stepOutputs = captureStepOutputs(varsBefore, vars, collectiblesBefore, collectibles, ctx.networkCapture);
218
+ onceCache.markExecuted(step.id, scope, stepOutputs);
219
+ }
220
+ // Log step finish
221
+ const stepDuration = Date.now() - stepStartTime;
222
+ ctx.logger.log({
223
+ type: 'step_finished',
224
+ data: {
225
+ stepId: step.id,
226
+ type: step.type,
227
+ label: stepLabel,
228
+ durationMs: stepDuration,
229
+ },
230
+ });
231
+ }
232
+ catch (error) {
233
+ const errorMessage = error instanceof Error ? error.message : String(error);
234
+ // Check for auth failure and attempt recovery
235
+ if (authMonitor?.isEnabled()) {
236
+ const stepFailures = authMonitor.getFailuresForStep(step.id);
237
+ if (stepFailures.length > 0) {
238
+ // Auth failure detected for this step - attempt recovery
239
+ const latestFailure = stepFailures[stepFailures.length - 1];
240
+ authRecoveriesUsed++;
241
+ const maxRecoveries = authMonitor.getMaxRecoveries();
242
+ if (authRecoveriesUsed > maxRecoveries) {
243
+ // Recovery exhausted
244
+ ctx.logger.log({
245
+ type: 'auth_recovery_exhausted',
246
+ data: {
247
+ url: latestFailure.url,
248
+ status: latestFailure.status,
249
+ maxRecoveries,
250
+ },
251
+ });
252
+ throw new Error(`Auth recovery exhausted after ${maxRecoveries} attempt(s). Last failure: ${latestFailure.status} at ${latestFailure.url}`);
253
+ }
254
+ // Attempt recovery
255
+ ctx.logger.log({
256
+ type: 'auth_recovery_started',
257
+ data: {
258
+ recoveryAttempt: authRecoveriesUsed,
259
+ maxRecoveries,
260
+ },
261
+ });
262
+ // Clear once cache based on pack scope (sessionId/profileId)
263
+ // If sessionId is provided, clear session cache; otherwise clear profile cache
264
+ // Clear both if both are provided (to be safe)
265
+ if (sessionId && profileId) {
266
+ onceCache.clearAll();
267
+ }
268
+ else if (sessionId) {
269
+ onceCache.clear('session');
270
+ }
271
+ else if (profileId) {
272
+ onceCache.clear('profile');
273
+ }
274
+ else {
275
+ // No scope provided - clear both to be safe
276
+ onceCache.clearAll();
277
+ }
278
+ // Rerun once steps (setup)
279
+ const onceSteps = getOnceSteps(steps);
280
+ if (onceSteps.length > 0) {
281
+ await executeStepsWithRecovery(ctx, stepContext, variableContext, onceSteps, onceCache, authMonitor, sessionId, profileId, stopOnError, authRecoveriesUsed);
282
+ }
283
+ // Clear the failure record for this step (to allow retry)
284
+ authMonitor.clearFailuresForStep(step.id);
285
+ // Wait for cooldown if configured
286
+ const cooldownMs = authMonitor.getCooldownMs();
287
+ if (cooldownMs > 0) {
288
+ await new Promise((resolve) => setTimeout(resolve, cooldownMs));
289
+ }
290
+ // Retry the failed step (up to maxStepRetryAfterRecovery times)
291
+ const maxRetries = authMonitor.getMaxStepRetries();
292
+ const timeoutMs = resolvedStep.timeoutMs; // Capture timeoutMs for retry loop
293
+ let retrySuccess = false;
294
+ for (let retryAttempt = 0; retryAttempt < maxRetries; retryAttempt++) {
295
+ try {
296
+ // Snapshot state before retry (for capturing outputs)
297
+ const retryVarsBefore = resolvedStep.once ? { ...vars } : {};
298
+ const retryCollectiblesBefore = resolvedStep.once ? { ...collectibles } : {};
299
+ // Update current step ID for monitoring
300
+ stepContext.currentStepId = step.id;
301
+ setupBrowserAuthMonitoring(ctx.page, authMonitor, ctx.logger, step.id);
302
+ let retryPromise = executeStep(stepContext, resolvedStep);
303
+ if (timeoutMs) {
304
+ const timeoutPromise = new Promise((_, reject) => {
305
+ setTimeout(() => {
306
+ reject(new Error(`Step timeout after ${timeoutMs}ms`));
307
+ }, timeoutMs);
308
+ });
309
+ retryPromise = Promise.race([retryPromise, timeoutPromise]);
310
+ }
311
+ await retryPromise;
312
+ retrySuccess = true;
313
+ stepsExecuted++;
314
+ // Mark step as executed if it has "once" flag, with captured outputs
315
+ if (resolvedStep.once) {
316
+ const scope = resolvedStep.once === 'session' && sessionId ? 'session' : 'profile';
317
+ const stepOutputs = captureStepOutputs(retryVarsBefore, vars, retryCollectiblesBefore, collectibles, ctx.networkCapture);
318
+ onceCache.markExecuted(step.id, scope, stepOutputs);
319
+ }
320
+ // Log successful retry
321
+ const stepDuration = Date.now() - stepStartTime;
322
+ ctx.logger.log({
323
+ type: 'step_finished',
324
+ data: {
325
+ stepId: step.id,
326
+ type: step.type,
327
+ label: stepLabel,
328
+ durationMs: stepDuration,
329
+ },
330
+ });
331
+ ctx.logger.log({
332
+ type: 'auth_recovery_finished',
333
+ data: {
334
+ recoveryAttempt: authRecoveriesUsed,
335
+ success: true,
336
+ },
337
+ });
338
+ break; // Success - exit retry loop
339
+ }
340
+ catch (retryError) {
341
+ // Retry failed - check if we have more attempts
342
+ if (retryAttempt === maxRetries - 1) {
343
+ // All retries exhausted
344
+ ctx.logger.log({
345
+ type: 'auth_recovery_finished',
346
+ data: {
347
+ recoveryAttempt: authRecoveriesUsed,
348
+ success: false,
349
+ },
350
+ });
351
+ throw retryError;
352
+ }
353
+ // Wait before next retry
354
+ if (cooldownMs > 0) {
355
+ await new Promise((resolve) => setTimeout(resolve, cooldownMs));
356
+ }
357
+ }
358
+ }
359
+ if (retrySuccess) {
360
+ continue; // Step succeeded after recovery - continue to next step
361
+ }
362
+ }
363
+ }
364
+ // Log error
365
+ ctx.logger.log({
366
+ type: 'error',
367
+ data: {
368
+ error: errorMessage,
369
+ stepId: step.id,
370
+ type: step.type,
371
+ label: stepLabel,
372
+ },
373
+ });
374
+ // Handle optional steps
375
+ if (resolvedStep.optional) {
376
+ // Optional step failed - log and continue
377
+ stepsExecuted++;
378
+ continue;
379
+ }
380
+ // Handle per-step error behavior
381
+ const errorBehavior = resolvedStep.onError || (stopOnError ? 'stop' : 'continue');
382
+ if (errorBehavior === 'continue') {
383
+ // Continue to next step
384
+ stepsExecuted++;
385
+ continue;
386
+ }
387
+ // Capture artifacts on error if available
388
+ if (ctx.artifacts) {
389
+ try {
390
+ await ctx.artifacts.saveScreenshot(`error-${step.id}`);
391
+ const html = await ctx.page.content();
392
+ await ctx.artifacts.saveHTML(`error-${step.id}`, html);
393
+ }
394
+ catch (artifactError) {
395
+ // Ignore artifact save errors, but log them
396
+ console.error('Failed to save artifacts:', artifactError);
397
+ }
398
+ }
399
+ // Stop on error (default behavior)
400
+ throw error;
401
+ }
402
+ }
403
+ const durationMs = Date.now() - startTime;
404
+ const finalUrl = ctx.page.url();
405
+ // Collect JMESPath hints from vars (stored by step handlers)
406
+ const jmespathHints = vars['__jmespath_hints'] || [];
407
+ const singleHint = vars['__jmespath_hint'];
408
+ if (singleHint && !jmespathHints.includes(singleHint)) {
409
+ jmespathHints.push(singleHint);
410
+ }
411
+ // Deduplicate hints
412
+ const uniqueHints = [...new Set(jmespathHints)];
413
+ const result = {
414
+ collectibles,
415
+ meta: {
416
+ url: finalUrl,
417
+ durationMs,
418
+ stepsExecuted,
419
+ stepsTotal: steps.length,
420
+ },
421
+ };
422
+ // Only include _hints if there are any
423
+ if (uniqueHints.length > 0) {
424
+ result._hints = uniqueHints;
425
+ }
426
+ return result;
427
+ }
428
+ catch (error) {
429
+ throw error;
430
+ }
431
+ finally {
432
+ if (sessionId || profileId) {
433
+ onceCache.persist(sessionId, profileId, cacheDir);
434
+ }
435
+ }
436
+ }
437
+ /**
438
+ * Helper to execute steps with recovery support (for rerunning once steps)
439
+ */
440
+ async function executeStepsWithRecovery(ctx, stepContext, variableContext, steps, onceCache, authMonitor, sessionId, profileId, stopOnError, authRecoveriesUsed) {
441
+ const { vars } = variableContext;
442
+ const collectibles = stepContext.collectibles;
443
+ const networkCapture = stepContext.networkCapture;
444
+ for (const step of steps) {
445
+ // Skip if already executed (shouldn't happen during recovery, but be safe)
446
+ if (shouldSkipStep(step, onceCache, sessionId, profileId)) {
447
+ // Restore cached outputs when skipping during recovery
448
+ const scope = step.once === 'session' && sessionId ? 'session' : 'profile';
449
+ const cachedOutputs = onceCache.getOutputs(step.id, scope);
450
+ if (cachedOutputs) {
451
+ Object.assign(vars, cachedOutputs.vars);
452
+ Object.assign(collectibles, cachedOutputs.collectibles);
453
+ // Restore network entries into the capture buffer
454
+ if (cachedOutputs.networkEntries && networkCapture) {
455
+ for (const entry of cachedOutputs.networkEntries) {
456
+ networkCapture.importEntry(entry);
457
+ }
458
+ }
459
+ }
460
+ continue;
461
+ }
462
+ const resolvedStep = {
463
+ ...step,
464
+ params: resolveTemplates(step.params, variableContext),
465
+ };
466
+ try {
467
+ // Snapshot state before step execution (for capturing outputs)
468
+ const varsBefore = resolvedStep.once ? { ...vars } : {};
469
+ const collectiblesBefore = resolvedStep.once ? { ...collectibles } : {};
470
+ // Update current step ID and monitoring for current step
471
+ stepContext.currentStepId = step.id;
472
+ if (authMonitor?.isEnabled()) {
473
+ setupBrowserAuthMonitoring(ctx.page, authMonitor, ctx.logger, step.id);
474
+ }
475
+ await executeStep(stepContext, resolvedStep);
476
+ // Mark as executed with captured outputs
477
+ if (resolvedStep.once) {
478
+ const scope = resolvedStep.once === 'session' && sessionId ? 'session' : 'profile';
479
+ const stepOutputs = captureStepOutputs(varsBefore, vars, collectiblesBefore, collectibles, networkCapture);
480
+ onceCache.markExecuted(step.id, scope, stepOutputs);
481
+ }
482
+ }
483
+ catch (error) {
484
+ // If recovery fails during setup rerun, throw (don't retry recovery)
485
+ if (stopOnError) {
486
+ throw error;
487
+ }
488
+ // Otherwise continue
489
+ }
490
+ }
491
+ }
@@ -0,0 +1,32 @@
1
+ import type { Page, BrowserContext, Frame } from 'playwright';
2
+ import type { DslStep } from './types.js';
3
+ import type { NetworkCaptureApi } from '../networkCapture.js';
4
+ import type { AuthFailureMonitor } from '../authResilience.js';
5
+ /**
6
+ * Step execution context
7
+ */
8
+ export interface StepContext {
9
+ page: Page;
10
+ collectibles: Record<string, unknown>;
11
+ vars: Record<string, unknown>;
12
+ inputs: Record<string, unknown>;
13
+ /** Required for network_find and network_replay */
14
+ networkCapture?: NetworkCaptureApi;
15
+ /** Optional auth failure monitor for detecting auth failures in network_replay */
16
+ authMonitor?: AuthFailureMonitor;
17
+ /** Current step ID for auth failure tracking */
18
+ currentStepId?: string;
19
+ /** Browser context for multi-tab operations */
20
+ browserContext?: BrowserContext;
21
+ /** Current frame context (for iframe operations) */
22
+ currentFrame?: Frame;
23
+ /** Previous tab index (for switch_tab with 'previous') */
24
+ previousTabIndex?: number;
25
+ /** Task pack directory path (for resolving relative file paths) */
26
+ packDir?: string;
27
+ }
28
+ /**
29
+ * Executes a single DSL step
30
+ */
31
+ export declare function executeStep(ctx: StepContext, step: DslStep): Promise<void>;
32
+ //# sourceMappingURL=stepHandlers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stepHandlers.d.ts","sourceRoot":"","sources":["../../src/dsl/stepHandlers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,KAAK,EACV,OAAO,EAqBR,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,iBAAiB,EAA4C,MAAM,sBAAsB,CAAC;AAGxG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG/D;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,IAAI,CAAC;IACX,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,mDAAmD;IACnD,cAAc,CAAC,EAAE,iBAAiB,CAAC;IACnC,kFAAkF;IAClF,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,gDAAgD;IAChD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,+CAA+C;IAC/C,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,oDAAoD;IACpD,YAAY,CAAC,EAAE,KAAK,CAAC;IACrB,0DAA0D;IAC1D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAw5BD;;GAEG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,IAAI,CAAC,CAgEf"}