@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.
- package/LICENSE +21 -0
- package/dist/__tests__/dsl-validation.test.d.ts +2 -0
- package/dist/__tests__/dsl-validation.test.d.ts.map +1 -0
- package/dist/__tests__/dsl-validation.test.js +203 -0
- package/dist/__tests__/pack-versioning.test.d.ts +2 -0
- package/dist/__tests__/pack-versioning.test.d.ts.map +1 -0
- package/dist/__tests__/pack-versioning.test.js +165 -0
- package/dist/__tests__/validator.test.d.ts +2 -0
- package/dist/__tests__/validator.test.d.ts.map +1 -0
- package/dist/__tests__/validator.test.js +149 -0
- package/dist/authResilience.d.ts +146 -0
- package/dist/authResilience.d.ts.map +1 -0
- package/dist/authResilience.js +378 -0
- package/dist/browserLauncher.d.ts +74 -0
- package/dist/browserLauncher.d.ts.map +1 -0
- package/dist/browserLauncher.js +159 -0
- package/dist/browserPersistence.d.ts +49 -0
- package/dist/browserPersistence.d.ts.map +1 -0
- package/dist/browserPersistence.js +143 -0
- package/dist/context.d.ts +10 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +30 -0
- package/dist/dsl/builders.d.ts +340 -0
- package/dist/dsl/builders.d.ts.map +1 -0
- package/dist/dsl/builders.js +416 -0
- package/dist/dsl/conditions.d.ts +33 -0
- package/dist/dsl/conditions.d.ts.map +1 -0
- package/dist/dsl/conditions.js +169 -0
- package/dist/dsl/interpreter.d.ts +24 -0
- package/dist/dsl/interpreter.d.ts.map +1 -0
- package/dist/dsl/interpreter.js +491 -0
- package/dist/dsl/stepHandlers.d.ts +32 -0
- package/dist/dsl/stepHandlers.d.ts.map +1 -0
- package/dist/dsl/stepHandlers.js +787 -0
- package/dist/dsl/target.d.ts +28 -0
- package/dist/dsl/target.d.ts.map +1 -0
- package/dist/dsl/target.js +110 -0
- package/dist/dsl/templating.d.ts +21 -0
- package/dist/dsl/templating.d.ts.map +1 -0
- package/dist/dsl/templating.js +73 -0
- package/dist/dsl/types.d.ts +695 -0
- package/dist/dsl/types.d.ts.map +1 -0
- package/dist/dsl/types.js +7 -0
- package/dist/dsl/validation.d.ts +15 -0
- package/dist/dsl/validation.d.ts.map +1 -0
- package/dist/dsl/validation.js +974 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/jsonPackValidator.d.ts +11 -0
- package/dist/jsonPackValidator.d.ts.map +1 -0
- package/dist/jsonPackValidator.js +61 -0
- package/dist/loader.d.ts +35 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +107 -0
- package/dist/networkCapture.d.ts +107 -0
- package/dist/networkCapture.d.ts.map +1 -0
- package/dist/networkCapture.js +390 -0
- package/dist/packUtils.d.ts +36 -0
- package/dist/packUtils.d.ts.map +1 -0
- package/dist/packUtils.js +97 -0
- package/dist/packVersioning.d.ts +25 -0
- package/dist/packVersioning.d.ts.map +1 -0
- package/dist/packVersioning.js +137 -0
- package/dist/runner.d.ts +62 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +170 -0
- package/dist/types.d.ts +336 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/validator.d.ts +20 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +68 -0
- package/package.json +49 -0
package/dist/runner.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { TaskPack, RunResult } from './types.js';
|
|
2
|
+
import type { Logger } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Options for running a task pack
|
|
5
|
+
*/
|
|
6
|
+
export interface RunTaskPackOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Directory to store run artifacts and logs
|
|
9
|
+
*/
|
|
10
|
+
runDir: string;
|
|
11
|
+
/**
|
|
12
|
+
* Logger instance for structured logging
|
|
13
|
+
*/
|
|
14
|
+
logger: Logger;
|
|
15
|
+
/**
|
|
16
|
+
* Whether to run browser in headless mode (default: true)
|
|
17
|
+
*/
|
|
18
|
+
headless?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Session ID for "once" step caching (session scope)
|
|
21
|
+
*/
|
|
22
|
+
sessionId?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Profile ID for "once" step caching (profile scope)
|
|
25
|
+
*/
|
|
26
|
+
profileId?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Directory for profile cache storage (typically the pack directory)
|
|
29
|
+
*/
|
|
30
|
+
cacheDir?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Pack directory path (used for loading secrets)
|
|
33
|
+
*/
|
|
34
|
+
packPath?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Pre-loaded secrets (if not provided, will be loaded from packPath)
|
|
37
|
+
*/
|
|
38
|
+
secrets?: Record<string, string>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Result of running a task pack with paths
|
|
42
|
+
*/
|
|
43
|
+
export interface RunTaskPackResult extends RunResult {
|
|
44
|
+
/**
|
|
45
|
+
* Path to the run directory
|
|
46
|
+
*/
|
|
47
|
+
runDir: string;
|
|
48
|
+
/**
|
|
49
|
+
* Path to the events JSONL file
|
|
50
|
+
*/
|
|
51
|
+
eventsPath: string;
|
|
52
|
+
/**
|
|
53
|
+
* Path to the artifacts directory
|
|
54
|
+
*/
|
|
55
|
+
artifactsDir: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Runs a task pack with Playwright
|
|
59
|
+
* This is a reusable function that can be used by both CLI and MCP server
|
|
60
|
+
*/
|
|
61
|
+
export declare function runTaskPack(taskPack: TaskPack, inputs: Record<string, unknown>, options: RunTaskPackOptions): Promise<RunTaskPackResult>;
|
|
62
|
+
//# sourceMappingURL=runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAc,MAAM,YAAY,CAAC;AAElE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAGzC;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,iBAAkB,SAAQ,SAAS;IAClD;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,iBAAiB,CAAC,CA6L5B"}
|
package/dist/runner.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { InputValidator, RunContextFactory, runFlow, attachNetworkCapture, TaskPackLoader } from './index.js';
|
|
4
|
+
import { launchBrowser } from './browserLauncher.js';
|
|
5
|
+
/**
|
|
6
|
+
* Runs a task pack with Playwright
|
|
7
|
+
* This is a reusable function that can be used by both CLI and MCP server
|
|
8
|
+
*/
|
|
9
|
+
export async function runTaskPack(taskPack, inputs, options) {
|
|
10
|
+
const { runDir, logger, headless: requestedHeadless = true, packPath, secrets: providedSecrets } = options;
|
|
11
|
+
const artifactsDir = join(runDir, 'artifacts');
|
|
12
|
+
const eventsPath = join(runDir, 'events.jsonl');
|
|
13
|
+
// Load secrets from pack directory if not provided and packPath is given
|
|
14
|
+
const secrets = providedSecrets ?? (packPath ? TaskPackLoader.loadSecrets(packPath) : {});
|
|
15
|
+
// Ensure directories exist
|
|
16
|
+
mkdirSync(runDir, { recursive: true });
|
|
17
|
+
mkdirSync(artifactsDir, { recursive: true });
|
|
18
|
+
// Auto-detect if we can run headful (check for DISPLAY)
|
|
19
|
+
// If headful was requested but no DISPLAY is available, fall back to headless
|
|
20
|
+
const hasDisplay = !!process.env.DISPLAY;
|
|
21
|
+
const headless = requestedHeadless || !hasDisplay;
|
|
22
|
+
if (!requestedHeadless && !hasDisplay) {
|
|
23
|
+
console.error('[Warning] Headful mode requested but no DISPLAY environment variable found. ' +
|
|
24
|
+
'Falling back to headless mode. Set DISPLAY or use xvfb-run to enable headful mode.');
|
|
25
|
+
}
|
|
26
|
+
const startTime = Date.now();
|
|
27
|
+
let browserSession = null;
|
|
28
|
+
let page = null;
|
|
29
|
+
let runContext = null;
|
|
30
|
+
try {
|
|
31
|
+
// Apply defaults and validate inputs
|
|
32
|
+
const inputsWithDefaults = InputValidator.applyDefaults(inputs, taskPack.inputs);
|
|
33
|
+
InputValidator.validate(inputsWithDefaults, taskPack.inputs);
|
|
34
|
+
// Log run start
|
|
35
|
+
logger.log({
|
|
36
|
+
type: 'run_started',
|
|
37
|
+
data: {
|
|
38
|
+
packId: taskPack.metadata.id,
|
|
39
|
+
packVersion: taskPack.metadata.version,
|
|
40
|
+
inputs: inputsWithDefaults,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
// Launch browser with unified launcher
|
|
44
|
+
browserSession = await launchBrowser({
|
|
45
|
+
browserSettings: taskPack.browser,
|
|
46
|
+
headless,
|
|
47
|
+
sessionId: options.sessionId,
|
|
48
|
+
packPath: options.packPath ?? options.cacheDir,
|
|
49
|
+
});
|
|
50
|
+
page = browserSession.page;
|
|
51
|
+
// Attach network capture (rolling buffer, redacted for logs; full headers in-memory for replay only)
|
|
52
|
+
const networkCapture = attachNetworkCapture(page);
|
|
53
|
+
// Create run context
|
|
54
|
+
// Note: browserSession.browser may be null for persistent contexts or Camoufox
|
|
55
|
+
// We pass a proxy that satisfies the Browser type for the RunContext
|
|
56
|
+
const browserProxy = browserSession.browser ?? {
|
|
57
|
+
close: async () => browserSession?.close(),
|
|
58
|
+
contexts: () => [browserSession?.context],
|
|
59
|
+
isConnected: () => true,
|
|
60
|
+
newContext: async () => browserSession?.context,
|
|
61
|
+
newPage: async () => browserSession?.page,
|
|
62
|
+
version: () => 'unknown',
|
|
63
|
+
};
|
|
64
|
+
runContext = RunContextFactory.create(page, browserProxy, logger, artifactsDir, networkCapture);
|
|
65
|
+
// Execute declarative DSL flow
|
|
66
|
+
const flowResult = await runFlow(runContext, taskPack.flow, {
|
|
67
|
+
inputs: inputsWithDefaults,
|
|
68
|
+
auth: taskPack.auth,
|
|
69
|
+
sessionId: options.sessionId,
|
|
70
|
+
profileId: options.profileId,
|
|
71
|
+
cacheDir: options.cacheDir,
|
|
72
|
+
secrets,
|
|
73
|
+
});
|
|
74
|
+
// Filter collectibles to only include those defined in the pack
|
|
75
|
+
// This prevents intermediate variables from polluting the output
|
|
76
|
+
const definedCollectibleNames = new Set((taskPack.collectibles || []).map(c => c.name));
|
|
77
|
+
const filteredCollectibles = {};
|
|
78
|
+
for (const [key, value] of Object.entries(flowResult.collectibles)) {
|
|
79
|
+
if (definedCollectibleNames.has(key)) {
|
|
80
|
+
filteredCollectibles[key] = value;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Convert RunFlowResult to RunResult format
|
|
84
|
+
const result = {
|
|
85
|
+
collectibles: filteredCollectibles,
|
|
86
|
+
meta: {
|
|
87
|
+
url: flowResult.meta.url,
|
|
88
|
+
durationMs: flowResult.meta.durationMs,
|
|
89
|
+
notes: `Executed ${flowResult.meta.stepsExecuted}/${flowResult.meta.stepsTotal} steps`,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
// Propagate diagnostic hints if present
|
|
93
|
+
if (flowResult._hints && flowResult._hints.length > 0) {
|
|
94
|
+
result._hints = flowResult._hints;
|
|
95
|
+
}
|
|
96
|
+
const durationMs = Date.now() - startTime;
|
|
97
|
+
// Log run finish
|
|
98
|
+
logger.log({
|
|
99
|
+
type: 'run_finished',
|
|
100
|
+
data: {
|
|
101
|
+
success: true,
|
|
102
|
+
durationMs,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
...result,
|
|
107
|
+
runDir,
|
|
108
|
+
eventsPath,
|
|
109
|
+
artifactsDir,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
const durationMs = Date.now() - startTime;
|
|
114
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
115
|
+
// Log error
|
|
116
|
+
logger.log({
|
|
117
|
+
type: 'error',
|
|
118
|
+
data: {
|
|
119
|
+
error: errorMessage,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
// Save artifacts on error
|
|
123
|
+
if (page) {
|
|
124
|
+
try {
|
|
125
|
+
if (runContext) {
|
|
126
|
+
await runContext.artifacts.saveScreenshot('error');
|
|
127
|
+
const html = await page.content();
|
|
128
|
+
await runContext.artifacts.saveHTML('error', html);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
// Fallback: save artifacts directly if runContext wasn't created
|
|
132
|
+
const screenshotPath = join(artifactsDir, 'error.png');
|
|
133
|
+
await page.screenshot({ path: screenshotPath, fullPage: true });
|
|
134
|
+
const html = await page.content();
|
|
135
|
+
const htmlPath = join(artifactsDir, 'error.html');
|
|
136
|
+
writeFileSync(htmlPath, html, 'utf-8');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (artifactError) {
|
|
140
|
+
// Ignore artifact save errors
|
|
141
|
+
console.error('Failed to save artifacts:', artifactError);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Log run finish with failure
|
|
145
|
+
logger.log({
|
|
146
|
+
type: 'run_finished',
|
|
147
|
+
data: {
|
|
148
|
+
success: false,
|
|
149
|
+
durationMs,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
// Return partial result with paths even on error
|
|
153
|
+
return {
|
|
154
|
+
collectibles: {},
|
|
155
|
+
meta: {
|
|
156
|
+
durationMs,
|
|
157
|
+
notes: `Error: ${errorMessage}`,
|
|
158
|
+
},
|
|
159
|
+
runDir,
|
|
160
|
+
eventsPath,
|
|
161
|
+
artifactsDir,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
// Cleanup using unified browser session close
|
|
166
|
+
if (browserSession) {
|
|
167
|
+
await browserSession.close();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import type { Browser, Page } from 'playwright';
|
|
2
|
+
import type { DslStep } from './dsl/types.js';
|
|
3
|
+
import type { NetworkCaptureApi } from './networkCapture.js';
|
|
4
|
+
/**
|
|
5
|
+
* Primitive types supported in input/collectible schemas
|
|
6
|
+
*/
|
|
7
|
+
export type PrimitiveType = 'string' | 'number' | 'boolean';
|
|
8
|
+
/**
|
|
9
|
+
* Secret definition for task pack secrets
|
|
10
|
+
*/
|
|
11
|
+
export interface SecretDefinition {
|
|
12
|
+
name: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
required?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Browser engine options
|
|
18
|
+
*/
|
|
19
|
+
export type BrowserEngine = 'chromium' | 'camoufox';
|
|
20
|
+
/**
|
|
21
|
+
* Browser persistence mode
|
|
22
|
+
* - 'none': Ephemeral, no data persisted (default)
|
|
23
|
+
* - 'session': Data persisted in temp directory, cleared after 30min inactivity
|
|
24
|
+
* - 'profile': Data persisted in pack directory, permanent
|
|
25
|
+
*/
|
|
26
|
+
export type BrowserPersistence = 'none' | 'session' | 'profile';
|
|
27
|
+
/**
|
|
28
|
+
* Browser configuration for task packs
|
|
29
|
+
*/
|
|
30
|
+
export interface BrowserSettings {
|
|
31
|
+
/**
|
|
32
|
+
* Browser engine to use (default: 'camoufox')
|
|
33
|
+
*/
|
|
34
|
+
engine?: BrowserEngine;
|
|
35
|
+
/**
|
|
36
|
+
* Persistence mode for browser data (cookies, localStorage, etc.)
|
|
37
|
+
* - 'none': Fresh browser each run (default)
|
|
38
|
+
* - 'session': Persist in temp dir, cleared after inactivity
|
|
39
|
+
* - 'profile': Persist in pack's .browser-profile/ directory
|
|
40
|
+
*/
|
|
41
|
+
persistence?: BrowserPersistence;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Field definition in a schema
|
|
45
|
+
*/
|
|
46
|
+
export interface FieldDefinition {
|
|
47
|
+
type: PrimitiveType;
|
|
48
|
+
required?: boolean;
|
|
49
|
+
description?: string;
|
|
50
|
+
/** Default value for this field when not provided */
|
|
51
|
+
default?: unknown;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Input schema definition
|
|
55
|
+
*/
|
|
56
|
+
export type InputSchema = Record<string, FieldDefinition>;
|
|
57
|
+
/**
|
|
58
|
+
* Collectible definition
|
|
59
|
+
*/
|
|
60
|
+
export interface CollectibleDefinition {
|
|
61
|
+
name: string;
|
|
62
|
+
type: PrimitiveType;
|
|
63
|
+
description?: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Task Pack metadata
|
|
67
|
+
*/
|
|
68
|
+
export interface TaskPackMetadata {
|
|
69
|
+
id: string;
|
|
70
|
+
name: string;
|
|
71
|
+
version: string;
|
|
72
|
+
description?: string;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Task Pack manifest (taskpack.json)
|
|
76
|
+
*
|
|
77
|
+
* Only json-dsl format is supported:
|
|
78
|
+
* - taskpack.json: metadata with kind: "json-dsl"
|
|
79
|
+
* - flow.json: inputs, collectibles, and flow steps
|
|
80
|
+
*/
|
|
81
|
+
export interface TaskPackManifest extends TaskPackMetadata {
|
|
82
|
+
/**
|
|
83
|
+
* Pack kind: must be "json-dsl"
|
|
84
|
+
*/
|
|
85
|
+
kind: 'json-dsl';
|
|
86
|
+
/**
|
|
87
|
+
* Auth configuration for resilience and recovery
|
|
88
|
+
*/
|
|
89
|
+
auth?: AuthConfig;
|
|
90
|
+
/**
|
|
91
|
+
* Secret definitions (names and descriptions for secrets stored in .secrets.json)
|
|
92
|
+
*/
|
|
93
|
+
secrets?: SecretDefinition[];
|
|
94
|
+
/**
|
|
95
|
+
* Browser configuration
|
|
96
|
+
*/
|
|
97
|
+
browser?: BrowserSettings;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Run context provided to task pack execution
|
|
101
|
+
*/
|
|
102
|
+
export interface RunContext {
|
|
103
|
+
page: Page;
|
|
104
|
+
browser: Browser;
|
|
105
|
+
logger: Logger;
|
|
106
|
+
artifacts: ArtifactManager;
|
|
107
|
+
/** Present when flow runs with network capture (e.g. runner); required for network_find/network_replay */
|
|
108
|
+
networkCapture?: NetworkCaptureApi;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Run result returned by task pack
|
|
112
|
+
*/
|
|
113
|
+
export interface RunResult {
|
|
114
|
+
collectibles: Record<string, unknown>;
|
|
115
|
+
meta: {
|
|
116
|
+
url?: string;
|
|
117
|
+
durationMs: number;
|
|
118
|
+
notes?: string;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Diagnostic hints from JSONPath operations (e.g., unsupported syntax, empty results).
|
|
122
|
+
* These help AI agents understand why data extraction may have failed.
|
|
123
|
+
*/
|
|
124
|
+
_hints?: string[];
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Logger interface for structured logging
|
|
128
|
+
*/
|
|
129
|
+
export interface Logger {
|
|
130
|
+
log(event: LogEvent): void;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Auth policy configuration for reactive auth failure detection and recovery
|
|
134
|
+
*/
|
|
135
|
+
export interface AuthPolicy {
|
|
136
|
+
/**
|
|
137
|
+
* Enable auth failure detection and recovery (default: true)
|
|
138
|
+
*/
|
|
139
|
+
enabled?: boolean;
|
|
140
|
+
/**
|
|
141
|
+
* HTTP status codes that indicate auth failure (default: [401, 403])
|
|
142
|
+
*/
|
|
143
|
+
statusCodes?: number[];
|
|
144
|
+
/**
|
|
145
|
+
* URL patterns (substring match) that trigger auth failure detection
|
|
146
|
+
* If provided, only responses matching these patterns will trigger recovery
|
|
147
|
+
*/
|
|
148
|
+
urlIncludes?: string[];
|
|
149
|
+
/**
|
|
150
|
+
* URL regex patterns for auth failure detection
|
|
151
|
+
* If provided, only responses matching these patterns will trigger recovery
|
|
152
|
+
*/
|
|
153
|
+
urlRegex?: string;
|
|
154
|
+
/**
|
|
155
|
+
* Optional: URL patterns that indicate login page (for navigation-based detection)
|
|
156
|
+
*/
|
|
157
|
+
loginUrlIncludes?: string[];
|
|
158
|
+
/**
|
|
159
|
+
* Maximum number of recovery attempts per run (default: 1)
|
|
160
|
+
*/
|
|
161
|
+
maxRecoveriesPerRun?: number;
|
|
162
|
+
/**
|
|
163
|
+
* Maximum number of times to retry a failed step after recovery (default: 1)
|
|
164
|
+
*/
|
|
165
|
+
maxStepRetryAfterRecovery?: number;
|
|
166
|
+
/**
|
|
167
|
+
* Cooldown delay in milliseconds before retrying after recovery (default: 0)
|
|
168
|
+
*/
|
|
169
|
+
cooldownMs?: number;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Auth guard configuration for proactive auth checks (OFF by default)
|
|
173
|
+
*/
|
|
174
|
+
export interface AuthGuard {
|
|
175
|
+
/**
|
|
176
|
+
* Enable auth guard (default: false)
|
|
177
|
+
*/
|
|
178
|
+
enabled?: boolean;
|
|
179
|
+
/**
|
|
180
|
+
* Guard strategy: check for visible selector or URL pattern
|
|
181
|
+
*/
|
|
182
|
+
strategy?: {
|
|
183
|
+
/**
|
|
184
|
+
* Assert that a selector is visible (preferred, no extra navigation)
|
|
185
|
+
*/
|
|
186
|
+
visibleSelector?: string;
|
|
187
|
+
/**
|
|
188
|
+
* OR check URL pattern after initial navigation (if already on a page)
|
|
189
|
+
*/
|
|
190
|
+
urlIncludes?: string;
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Auth configuration for task packs
|
|
195
|
+
*/
|
|
196
|
+
export interface AuthConfig {
|
|
197
|
+
/**
|
|
198
|
+
* Auth policy for reactive failure detection and recovery
|
|
199
|
+
*/
|
|
200
|
+
authPolicy?: AuthPolicy;
|
|
201
|
+
/**
|
|
202
|
+
* Auth guard for proactive checks (OFF by default)
|
|
203
|
+
*/
|
|
204
|
+
authGuard?: AuthGuard;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Log event types
|
|
208
|
+
*/
|
|
209
|
+
export type LogEvent = {
|
|
210
|
+
type: 'run_started';
|
|
211
|
+
data: {
|
|
212
|
+
packId: string;
|
|
213
|
+
packVersion: string;
|
|
214
|
+
inputs: unknown;
|
|
215
|
+
};
|
|
216
|
+
} | {
|
|
217
|
+
type: 'step_started';
|
|
218
|
+
data: {
|
|
219
|
+
stepId: string;
|
|
220
|
+
type: string;
|
|
221
|
+
label?: string;
|
|
222
|
+
params?: unknown;
|
|
223
|
+
};
|
|
224
|
+
} | {
|
|
225
|
+
type: 'step_finished';
|
|
226
|
+
data: {
|
|
227
|
+
stepId: string;
|
|
228
|
+
type: string;
|
|
229
|
+
label?: string;
|
|
230
|
+
durationMs: number;
|
|
231
|
+
};
|
|
232
|
+
} | {
|
|
233
|
+
type: 'step_skipped';
|
|
234
|
+
data: {
|
|
235
|
+
stepId: string;
|
|
236
|
+
type: string;
|
|
237
|
+
reason: 'once_already_executed' | 'condition_met';
|
|
238
|
+
restoredVars?: string[];
|
|
239
|
+
restoredCollectibles?: string[];
|
|
240
|
+
condition?: string;
|
|
241
|
+
};
|
|
242
|
+
} | {
|
|
243
|
+
type: 'auth_failure_detected';
|
|
244
|
+
data: {
|
|
245
|
+
url: string;
|
|
246
|
+
status: number;
|
|
247
|
+
stepId?: string;
|
|
248
|
+
};
|
|
249
|
+
} | {
|
|
250
|
+
type: 'auth_recovery_started';
|
|
251
|
+
data: {
|
|
252
|
+
recoveryAttempt: number;
|
|
253
|
+
maxRecoveries: number;
|
|
254
|
+
};
|
|
255
|
+
} | {
|
|
256
|
+
type: 'auth_recovery_finished';
|
|
257
|
+
data: {
|
|
258
|
+
recoveryAttempt: number;
|
|
259
|
+
success: boolean;
|
|
260
|
+
};
|
|
261
|
+
} | {
|
|
262
|
+
type: 'auth_recovery_exhausted';
|
|
263
|
+
data: {
|
|
264
|
+
url: string;
|
|
265
|
+
status: number;
|
|
266
|
+
maxRecoveries: number;
|
|
267
|
+
};
|
|
268
|
+
} | {
|
|
269
|
+
type: 'run_finished';
|
|
270
|
+
data: {
|
|
271
|
+
success: boolean;
|
|
272
|
+
durationMs: number;
|
|
273
|
+
};
|
|
274
|
+
} | {
|
|
275
|
+
type: 'error';
|
|
276
|
+
data: {
|
|
277
|
+
error: string;
|
|
278
|
+
stepId?: string;
|
|
279
|
+
type?: string;
|
|
280
|
+
label?: string;
|
|
281
|
+
};
|
|
282
|
+
};
|
|
283
|
+
/**
|
|
284
|
+
* Artifact manager for saving screenshots and HTML snapshots
|
|
285
|
+
*/
|
|
286
|
+
export interface ArtifactManager {
|
|
287
|
+
saveScreenshot(name: string): Promise<string>;
|
|
288
|
+
saveHTML(name: string, html: string): Promise<string>;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Task Pack module interface
|
|
292
|
+
*
|
|
293
|
+
* Supports two execution styles:
|
|
294
|
+
* - Declarative: provide `flow` array of DSL steps
|
|
295
|
+
* - Imperative: provide `run` function
|
|
296
|
+
*
|
|
297
|
+
* If both are provided, `flow` takes precedence.
|
|
298
|
+
*/
|
|
299
|
+
export interface TaskPack {
|
|
300
|
+
metadata: TaskPackMetadata;
|
|
301
|
+
inputs: InputSchema;
|
|
302
|
+
collectibles: CollectibleDefinition[];
|
|
303
|
+
/**
|
|
304
|
+
* Declarative flow of DSL steps
|
|
305
|
+
*/
|
|
306
|
+
flow: DslStep[];
|
|
307
|
+
/**
|
|
308
|
+
* Auth configuration for resilience and recovery
|
|
309
|
+
*/
|
|
310
|
+
auth?: AuthConfig;
|
|
311
|
+
/**
|
|
312
|
+
* Browser configuration
|
|
313
|
+
*/
|
|
314
|
+
browser?: BrowserSettings;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* A saved version snapshot of a flow
|
|
318
|
+
*/
|
|
319
|
+
export interface FlowVersion {
|
|
320
|
+
number: number;
|
|
321
|
+
/** Version string from taskpack.json metadata.version at snapshot time */
|
|
322
|
+
version: string;
|
|
323
|
+
/** ISO 8601 timestamp */
|
|
324
|
+
timestamp: string;
|
|
325
|
+
label?: string;
|
|
326
|
+
source: 'agent' | 'cli' | 'dashboard';
|
|
327
|
+
conversationId?: string;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Manifest tracking all saved versions for a pack
|
|
331
|
+
*/
|
|
332
|
+
export interface VersionManifest {
|
|
333
|
+
versions: FlowVersion[];
|
|
334
|
+
maxVersions: number;
|
|
335
|
+
}
|
|
336
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { InputSchema } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Validates inputs against a schema
|
|
4
|
+
*/
|
|
5
|
+
export declare class InputValidator {
|
|
6
|
+
/**
|
|
7
|
+
* Apply default values from schema to inputs.
|
|
8
|
+
* Returns a new object with defaults applied for missing fields.
|
|
9
|
+
*/
|
|
10
|
+
static applyDefaults(inputs: Record<string, unknown>, schema: InputSchema): Record<string, unknown>;
|
|
11
|
+
/**
|
|
12
|
+
* Validate inputs against schema
|
|
13
|
+
*/
|
|
14
|
+
static validate(inputs: Record<string, unknown>, schema: InputSchema): void;
|
|
15
|
+
/**
|
|
16
|
+
* Validate a single value against a type
|
|
17
|
+
*/
|
|
18
|
+
private static validateType;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,YAAY,CAAC;AAE7D;;GAEG;AACH,qBAAa,cAAc;IACzB;;;OAGG;IACH,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAUnG;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI;IA6B3E;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,YAAY;CAoB5B"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates inputs against a schema
|
|
3
|
+
*/
|
|
4
|
+
export class InputValidator {
|
|
5
|
+
/**
|
|
6
|
+
* Apply default values from schema to inputs.
|
|
7
|
+
* Returns a new object with defaults applied for missing fields.
|
|
8
|
+
*/
|
|
9
|
+
static applyDefaults(inputs, schema) {
|
|
10
|
+
const result = { ...inputs };
|
|
11
|
+
for (const [fieldName, fieldDef] of Object.entries(schema)) {
|
|
12
|
+
if (!(fieldName in result) && fieldDef.default !== undefined) {
|
|
13
|
+
result[fieldName] = fieldDef.default;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Validate inputs against schema
|
|
20
|
+
*/
|
|
21
|
+
static validate(inputs, schema) {
|
|
22
|
+
const errors = [];
|
|
23
|
+
// Check required fields
|
|
24
|
+
for (const [fieldName, fieldDef] of Object.entries(schema)) {
|
|
25
|
+
if (fieldDef.required && !(fieldName in inputs)) {
|
|
26
|
+
errors.push(`Missing required field: ${fieldName}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Check field types
|
|
30
|
+
for (const [fieldName, value] of Object.entries(inputs)) {
|
|
31
|
+
if (!(fieldName in schema)) {
|
|
32
|
+
errors.push(`Unknown field: ${fieldName}`);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const fieldDef = schema[fieldName];
|
|
36
|
+
const typeError = this.validateType(fieldName, value, fieldDef.type);
|
|
37
|
+
if (typeError) {
|
|
38
|
+
errors.push(typeError);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (errors.length > 0) {
|
|
42
|
+
throw new Error(`Input validation failed:\n${errors.join('\n')}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Validate a single value against a type
|
|
47
|
+
*/
|
|
48
|
+
static validateType(fieldName, value, expectedType) {
|
|
49
|
+
switch (expectedType) {
|
|
50
|
+
case 'string':
|
|
51
|
+
if (typeof value !== 'string') {
|
|
52
|
+
return `Field ${fieldName} must be a string, got ${typeof value}`;
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
case 'number':
|
|
56
|
+
if (typeof value !== 'number') {
|
|
57
|
+
return `Field ${fieldName} must be a number, got ${typeof value}`;
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
case 'boolean':
|
|
61
|
+
if (typeof value !== 'boolean') {
|
|
62
|
+
return `Field ${fieldName} must be a boolean, got ${typeof value}`;
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|