@showrun/core 0.1.0 → 0.1.1-b
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/dist/__tests__/config.test.d.ts +2 -0
- package/dist/__tests__/config.test.d.ts.map +1 -0
- package/dist/__tests__/config.test.js +164 -0
- package/dist/__tests__/httpReplay.test.d.ts +2 -0
- package/dist/__tests__/httpReplay.test.d.ts.map +1 -0
- package/dist/__tests__/httpReplay.test.js +306 -0
- package/dist/__tests__/requestSnapshot.test.d.ts +2 -0
- package/dist/__tests__/requestSnapshot.test.d.ts.map +1 -0
- package/dist/__tests__/requestSnapshot.test.js +323 -0
- package/dist/browserLauncher.d.ts.map +1 -1
- package/dist/browserLauncher.js +7 -1
- package/dist/config.d.ts +96 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +268 -0
- package/dist/dsl/interpreter.d.ts +9 -0
- package/dist/dsl/interpreter.d.ts.map +1 -1
- package/dist/dsl/interpreter.js +24 -5
- package/dist/dsl/stepHandlers.d.ts +7 -0
- package/dist/dsl/stepHandlers.d.ts.map +1 -1
- package/dist/dsl/stepHandlers.js +141 -5
- package/dist/dsl/templating.d.ts.map +1 -1
- package/dist/dsl/templating.js +11 -0
- package/dist/dsl/types.d.ts +16 -4
- package/dist/dsl/types.d.ts.map +1 -1
- package/dist/dsl/validation.d.ts.map +1 -1
- package/dist/dsl/validation.js +29 -14
- package/dist/httpReplay.d.ts +43 -0
- package/dist/httpReplay.d.ts.map +1 -0
- package/dist/httpReplay.js +102 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/jsonPackValidator.d.ts.map +1 -1
- package/dist/jsonPackValidator.js +12 -3
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +4 -0
- package/dist/networkCapture.d.ts +10 -4
- package/dist/networkCapture.d.ts.map +1 -1
- package/dist/networkCapture.js +20 -12
- package/dist/requestSnapshot.d.ts +91 -0
- package/dist/requestSnapshot.d.ts.map +1 -0
- package/dist/requestSnapshot.js +200 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +209 -13
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage/keys.d.ts +12 -0
- package/dist/storage/keys.d.ts.map +1 -0
- package/dist/storage/keys.js +39 -0
- package/dist/storage/types.d.ts +112 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +7 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/config.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System-wide configuration for ShowRun.
|
|
3
|
+
*
|
|
4
|
+
* Layered config discovery:
|
|
5
|
+
* Built-in defaults < global config.json < project config.json < .env < real env vars
|
|
6
|
+
*
|
|
7
|
+
* Config directory search order (lowest → highest priority):
|
|
8
|
+
* Linux/macOS: $XDG_CONFIG_HOME/showrun/ → ~/.showrun/ → ancestor .showrun/ → cwd/.showrun/
|
|
9
|
+
* Windows: %APPDATA%\showrun\ → ancestor .showrun\ → cwd\.showrun\
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, copyFileSync } from 'fs';
|
|
12
|
+
import { resolve, join, parse as parsePath } from 'path';
|
|
13
|
+
import { platform, homedir } from 'os';
|
|
14
|
+
import { cwd } from 'process';
|
|
15
|
+
import { ensureDir, readJsonFile } from './packUtils.js';
|
|
16
|
+
// ── Deep merge helper ──────────────────────────────────────────────────────
|
|
17
|
+
/**
|
|
18
|
+
* Recursively merge `override` into `base`, returning a new object.
|
|
19
|
+
* - Primitives and arrays in override replace base values.
|
|
20
|
+
* - Null/undefined values in override are skipped.
|
|
21
|
+
* - Nested plain objects are merged recursively.
|
|
22
|
+
*/
|
|
23
|
+
export function deepMerge(base, override) {
|
|
24
|
+
if (typeof base !== 'object' || base === null ||
|
|
25
|
+
typeof override !== 'object' || override === null ||
|
|
26
|
+
Array.isArray(base) || Array.isArray(override)) {
|
|
27
|
+
return override;
|
|
28
|
+
}
|
|
29
|
+
const result = { ...base };
|
|
30
|
+
const src = override;
|
|
31
|
+
for (const key of Object.keys(src)) {
|
|
32
|
+
const overrideVal = src[key];
|
|
33
|
+
if (overrideVal === null || overrideVal === undefined)
|
|
34
|
+
continue;
|
|
35
|
+
const baseVal = result[key];
|
|
36
|
+
if (typeof baseVal === 'object' && baseVal !== null && !Array.isArray(baseVal) &&
|
|
37
|
+
typeof overrideVal === 'object' && overrideVal !== null && !Array.isArray(overrideVal)) {
|
|
38
|
+
result[key] = deepMerge(baseVal, overrideVal);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
result[key] = overrideVal;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
// ── Directory discovery ────────────────────────────────────────────────────
|
|
47
|
+
/**
|
|
48
|
+
* Walk from `startDir` up toward the filesystem root, collecting every
|
|
49
|
+
* `.showrun/` directory encountered. Returns in bottom-up order (closest
|
|
50
|
+
* ancestor first) which is the *highest* priority order.
|
|
51
|
+
*/
|
|
52
|
+
function walkUpForShowrunDirs(startDir) {
|
|
53
|
+
const dirs = [];
|
|
54
|
+
let dir = resolve(startDir);
|
|
55
|
+
const root = parsePath(dir).root;
|
|
56
|
+
// Skip cwd itself — handled separately
|
|
57
|
+
dir = resolve(dir, '..');
|
|
58
|
+
while (dir !== root && dir.length > root.length) {
|
|
59
|
+
const candidate = join(dir, '.showrun');
|
|
60
|
+
if (existsSync(candidate)) {
|
|
61
|
+
dirs.push(candidate);
|
|
62
|
+
}
|
|
63
|
+
dir = resolve(dir, '..');
|
|
64
|
+
}
|
|
65
|
+
// Reverse so that farthest ancestor is first (lowest priority)
|
|
66
|
+
return dirs.reverse();
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Returns an ordered list of config directories to search, lowest priority first.
|
|
70
|
+
*/
|
|
71
|
+
export function discoverConfigDirs() {
|
|
72
|
+
const dirs = [];
|
|
73
|
+
const os = platform();
|
|
74
|
+
const home = homedir();
|
|
75
|
+
const currentDir = cwd();
|
|
76
|
+
if (os === 'win32') {
|
|
77
|
+
// Windows: %APPDATA%\showrun
|
|
78
|
+
const appData = process.env.APPDATA;
|
|
79
|
+
if (appData) {
|
|
80
|
+
dirs.push(join(appData, 'showrun'));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// Linux/macOS: XDG_CONFIG_HOME/showrun (default ~/.config/showrun)
|
|
85
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || join(home, '.config');
|
|
86
|
+
dirs.push(join(xdgConfig, 'showrun'));
|
|
87
|
+
// ~/.showrun
|
|
88
|
+
dirs.push(join(home, '.showrun'));
|
|
89
|
+
}
|
|
90
|
+
// Ancestor .showrun/ directories (farthest ancestor first = lowest priority)
|
|
91
|
+
dirs.push(...walkUpForShowrunDirs(currentDir));
|
|
92
|
+
// cwd/.showrun (highest priority)
|
|
93
|
+
dirs.push(join(currentDir, '.showrun'));
|
|
94
|
+
return dirs;
|
|
95
|
+
}
|
|
96
|
+
// ── Config loading ─────────────────────────────────────────────────────────
|
|
97
|
+
const DEFAULT_CONFIG = {};
|
|
98
|
+
/**
|
|
99
|
+
* Load all `config.json` files from discovered directories, merge them, and
|
|
100
|
+
* return the result along with metadata about which files were loaded.
|
|
101
|
+
*/
|
|
102
|
+
export function loadConfig() {
|
|
103
|
+
const searchedDirs = discoverConfigDirs();
|
|
104
|
+
let merged = { ...DEFAULT_CONFIG };
|
|
105
|
+
const loadedFiles = [];
|
|
106
|
+
for (const dir of searchedDirs) {
|
|
107
|
+
const configPath = join(dir, 'config.json');
|
|
108
|
+
if (existsSync(configPath)) {
|
|
109
|
+
try {
|
|
110
|
+
const fileConfig = readJsonFile(configPath);
|
|
111
|
+
merged = deepMerge(merged, fileConfig);
|
|
112
|
+
loadedFiles.push(configPath);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
console.warn(`[Config] Failed to load ${configPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { config: merged, loadedFiles, searchedDirs };
|
|
120
|
+
}
|
|
121
|
+
// ── Env var mapping ────────────────────────────────────────────────────────
|
|
122
|
+
/** Map of config path → env var name */
|
|
123
|
+
const CONFIG_TO_ENV = [
|
|
124
|
+
{ path: ['llm', 'provider'], envVar: 'LLM_PROVIDER' },
|
|
125
|
+
{ path: ['llm', 'anthropic', 'apiKey'], envVar: 'ANTHROPIC_API_KEY' },
|
|
126
|
+
{ path: ['llm', 'anthropic', 'model'], envVar: 'ANTHROPIC_MODEL' },
|
|
127
|
+
{ path: ['llm', 'anthropic', 'baseUrl'], envVar: 'ANTHROPIC_BASE_URL' },
|
|
128
|
+
{ path: ['llm', 'openai', 'apiKey'], envVar: 'OPENAI_API_KEY' },
|
|
129
|
+
{ path: ['llm', 'openai', 'model'], envVar: 'OPENAI_MODEL' },
|
|
130
|
+
{ path: ['llm', 'openai', 'baseUrl'], envVar: 'OPENAI_BASE_URL' },
|
|
131
|
+
{ path: ['agent', 'maxBrowserRounds'], envVar: 'MAX_BROWSER_ROUNDS' },
|
|
132
|
+
{ path: ['agent', 'debug'], envVar: 'SHOWRUN_DEBUG' },
|
|
133
|
+
{ path: ['agent', 'transcriptLogging'], envVar: 'SHOWRUN_TRANSCRIPT_LOGGING' },
|
|
134
|
+
{ path: ['prompts', 'teachChatSystemPrompt'], envVar: 'TEACH_CHAT_SYSTEM_PROMPT' },
|
|
135
|
+
{ path: ['prompts', 'explorationAgentPromptPath'], envVar: 'EXPLORATION_AGENT_PROMPT_PATH' },
|
|
136
|
+
{ path: ['techniques', 'vectorStore', 'provider'], envVar: 'VECTORSTORE_PROVIDER' },
|
|
137
|
+
{ path: ['techniques', 'vectorStore', 'url'], envVar: 'WEAVIATE_URL' },
|
|
138
|
+
{ path: ['techniques', 'vectorStore', 'apiKey'], envVar: 'WEAVIATE_API_KEY' },
|
|
139
|
+
{ path: ['techniques', 'vectorStore', 'vectorizer'], envVar: 'WEAVIATE_VECTORIZER' },
|
|
140
|
+
{ path: ['techniques', 'embedding', 'apiKey'], envVar: 'EMBEDDING_API_KEY' },
|
|
141
|
+
{ path: ['techniques', 'embedding', 'model'], envVar: 'EMBEDDING_MODEL' },
|
|
142
|
+
{ path: ['techniques', 'embedding', 'baseUrl'], envVar: 'EMBEDDING_BASE_URL' },
|
|
143
|
+
{ path: ['techniques', 'collectionName'], envVar: 'TECHNIQUES_COLLECTION' },
|
|
144
|
+
];
|
|
145
|
+
function getNestedValue(obj, path) {
|
|
146
|
+
let current = obj;
|
|
147
|
+
for (const key of path) {
|
|
148
|
+
if (current === null || current === undefined || typeof current !== 'object')
|
|
149
|
+
return undefined;
|
|
150
|
+
current = current[key];
|
|
151
|
+
}
|
|
152
|
+
return current;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Apply config values to `process.env`, only setting vars that are not already present.
|
|
156
|
+
*/
|
|
157
|
+
export function applyConfigToEnv(config) {
|
|
158
|
+
for (const { path, envVar } of CONFIG_TO_ENV) {
|
|
159
|
+
if (process.env[envVar])
|
|
160
|
+
continue; // real env / dotenv takes precedence
|
|
161
|
+
const value = getNestedValue(config, path);
|
|
162
|
+
if (value !== undefined && value !== null) {
|
|
163
|
+
process.env[envVar] = String(value);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// ── File resolution ────────────────────────────────────────────────────────
|
|
168
|
+
/**
|
|
169
|
+
* Resolve a filename by searching local paths first (cwd, then ancestors),
|
|
170
|
+
* then config directories (highest priority first).
|
|
171
|
+
* Local files always win over config dir copies.
|
|
172
|
+
* Returns the first existing path, or null.
|
|
173
|
+
*/
|
|
174
|
+
export function resolveFilePath(filename) {
|
|
175
|
+
// 1. Search cwd first — local files take priority
|
|
176
|
+
const cwdPath = resolve(cwd(), filename);
|
|
177
|
+
if (existsSync(cwdPath))
|
|
178
|
+
return cwdPath;
|
|
179
|
+
// 2. Walk up from cwd looking for the file directly (not inside .showrun)
|
|
180
|
+
let dir = resolve(cwd(), '..');
|
|
181
|
+
const root = parsePath(dir).root;
|
|
182
|
+
while (dir !== root && dir.length > root.length) {
|
|
183
|
+
const candidate = resolve(dir, filename);
|
|
184
|
+
if (existsSync(candidate))
|
|
185
|
+
return candidate;
|
|
186
|
+
dir = resolve(dir, '..');
|
|
187
|
+
}
|
|
188
|
+
// 3. Fall back to config dirs from highest priority (last) to lowest (first)
|
|
189
|
+
const configDirs = discoverConfigDirs();
|
|
190
|
+
for (let i = configDirs.length - 1; i >= 0; i--) {
|
|
191
|
+
const candidate = join(configDirs[i], filename);
|
|
192
|
+
if (existsSync(candidate))
|
|
193
|
+
return candidate;
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
// ── System prompt helper ───────────────────────────────────────────────────
|
|
198
|
+
/**
|
|
199
|
+
* Get the global config directory path for the current platform.
|
|
200
|
+
*/
|
|
201
|
+
export function getGlobalConfigDir() {
|
|
202
|
+
const os = platform();
|
|
203
|
+
if (os === 'win32') {
|
|
204
|
+
const appData = process.env.APPDATA;
|
|
205
|
+
if (appData)
|
|
206
|
+
return join(appData, 'showrun');
|
|
207
|
+
return join(homedir(), 'AppData', 'Roaming', 'showrun');
|
|
208
|
+
}
|
|
209
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || join(homedir(), '.config');
|
|
210
|
+
return join(xdgConfig, 'showrun');
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Ensure a system prompt file exists in a config directory.
|
|
214
|
+
* If the prompt was found outside config dirs (e.g. repo root), copy it
|
|
215
|
+
* into the global config dir so it's available when running from any directory.
|
|
216
|
+
*/
|
|
217
|
+
export function ensureSystemPromptInConfigDir(filename, sourcePath) {
|
|
218
|
+
const configDirs = discoverConfigDirs();
|
|
219
|
+
// Check if the file already exists in any config dir
|
|
220
|
+
for (const dir of configDirs) {
|
|
221
|
+
const candidate = join(dir, filename);
|
|
222
|
+
if (existsSync(candidate))
|
|
223
|
+
return candidate;
|
|
224
|
+
}
|
|
225
|
+
// Not in any config dir — copy it to the global config dir
|
|
226
|
+
const globalDir = getGlobalConfigDir();
|
|
227
|
+
const destPath = join(globalDir, filename);
|
|
228
|
+
ensureDir(globalDir);
|
|
229
|
+
copyFileSync(sourcePath, destPath);
|
|
230
|
+
console.log(`[Config] Created config directory at ${globalDir}`);
|
|
231
|
+
console.log(`[Config] Copied ${filename} to ${destPath}`);
|
|
232
|
+
return destPath;
|
|
233
|
+
}
|
|
234
|
+
// ── Main entry point ───────────────────────────────────────────────────────
|
|
235
|
+
/**
|
|
236
|
+
* Load config, merge, and apply to process.env.
|
|
237
|
+
* This is the single call sites should use (e.g. CLI bootstrap).
|
|
238
|
+
*/
|
|
239
|
+
export function initConfig() {
|
|
240
|
+
const result = loadConfig();
|
|
241
|
+
applyConfigToEnv(result.config);
|
|
242
|
+
if (result.loadedFiles.length > 0) {
|
|
243
|
+
console.log(`[Config] Loaded: ${result.loadedFiles.join(', ')}`);
|
|
244
|
+
}
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
// ── Default config template ────────────────────────────────────────────────
|
|
248
|
+
export const DEFAULT_CONFIG_TEMPLATE = {
|
|
249
|
+
llm: {
|
|
250
|
+
provider: 'anthropic',
|
|
251
|
+
anthropic: { apiKey: '', model: '', baseUrl: '' },
|
|
252
|
+
openai: { apiKey: '', model: '', baseUrl: '' },
|
|
253
|
+
},
|
|
254
|
+
agent: {
|
|
255
|
+
maxBrowserRounds: 0,
|
|
256
|
+
debug: false,
|
|
257
|
+
transcriptLogging: false,
|
|
258
|
+
},
|
|
259
|
+
prompts: {
|
|
260
|
+
teachChatSystemPrompt: '',
|
|
261
|
+
explorationAgentPromptPath: '',
|
|
262
|
+
},
|
|
263
|
+
techniques: {
|
|
264
|
+
vectorStore: { provider: 'weaviate', url: '', apiKey: '', vectorizer: '' },
|
|
265
|
+
embedding: { apiKey: '', model: '', baseUrl: '' },
|
|
266
|
+
collectionName: '',
|
|
267
|
+
},
|
|
268
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { RunContext, AuthConfig } from '../types.js';
|
|
2
2
|
import type { DslStep, RunFlowOptions, RunFlowResult } from './types.js';
|
|
3
|
+
import type { SnapshotFile } from '../requestSnapshot.js';
|
|
3
4
|
/**
|
|
4
5
|
* Extended options for running a flow with auth resilience
|
|
5
6
|
*/
|
|
@@ -16,6 +17,14 @@ export interface RunFlowOptionsWithAuth extends RunFlowOptions {
|
|
|
16
17
|
* Secrets for template resolution ({{secret.NAME}})
|
|
17
18
|
*/
|
|
18
19
|
secrets?: Record<string, string>;
|
|
20
|
+
/**
|
|
21
|
+
* If true, run in HTTP-only mode (skip browser steps, replay from snapshots)
|
|
22
|
+
*/
|
|
23
|
+
httpMode?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Request snapshots for HTTP-first execution
|
|
26
|
+
*/
|
|
27
|
+
snapshots?: SnapshotFile;
|
|
19
28
|
}
|
|
20
29
|
/**
|
|
21
30
|
* Runs a flow of DSL steps sequentially with auth resilience support
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interpreter.d.ts","sourceRoot":"","sources":["../../src/dsl/interpreter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAa,UAAU,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"interpreter.d.ts","sourceRoot":"","sources":["../../src/dsl/interpreter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAa,UAAU,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAgBzE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AA+C1D;;GAEG;AACH,MAAM,WAAW,sBAAuB,SAAQ,cAAc;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,SAAS,CAAC,EAAE,YAAY,CAAC;CAC1B;AAyBD;;GAEG;AACH,wBAAsB,OAAO,CAC3B,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,OAAO,EAAE,EAChB,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,aAAa,CAAC,CAycxB"}
|
package/dist/dsl/interpreter.js
CHANGED
|
@@ -72,6 +72,8 @@ export async function runFlow(ctx, steps, options) {
|
|
|
72
72
|
const profileId = options?.profileId;
|
|
73
73
|
const cacheDir = options?.cacheDir;
|
|
74
74
|
const secrets = options?.secrets ?? {};
|
|
75
|
+
const httpMode = options?.httpMode ?? false;
|
|
76
|
+
const snapshots = options?.snapshots;
|
|
75
77
|
// Get secret values for redaction (only values >= 3 chars)
|
|
76
78
|
const secretValues = Object.values(secrets).filter((v) => v && v.length >= 3);
|
|
77
79
|
// Validate flow before execution
|
|
@@ -98,6 +100,9 @@ export async function runFlow(ctx, steps, options) {
|
|
|
98
100
|
inputs,
|
|
99
101
|
networkCapture: ctx.networkCapture,
|
|
100
102
|
authMonitor: authMonitor ?? undefined,
|
|
103
|
+
httpMode,
|
|
104
|
+
snapshots: snapshots ?? undefined,
|
|
105
|
+
secrets,
|
|
101
106
|
};
|
|
102
107
|
if (authConfig?.authPolicy) {
|
|
103
108
|
authMonitor = new AuthFailureMonitor(authConfig.authPolicy);
|
|
@@ -174,8 +179,13 @@ export async function runFlow(ctx, steps, options) {
|
|
|
174
179
|
}
|
|
175
180
|
}
|
|
176
181
|
catch (conditionError) {
|
|
177
|
-
|
|
178
|
-
console.warn(`[interpreter]
|
|
182
|
+
const msg = `skip_if evaluation error for step "${step.id}": ${conditionError instanceof Error ? conditionError.message : String(conditionError)}`;
|
|
183
|
+
console.warn(`[interpreter] ${msg}`);
|
|
184
|
+
ctx.logger.log({ type: 'error', data: { error: msg, stepId: step.id, type: step.type } });
|
|
185
|
+
// Push to hints so the agent can see the error
|
|
186
|
+
if (!vars['__jmespath_hints'])
|
|
187
|
+
vars['__jmespath_hints'] = [];
|
|
188
|
+
vars['__jmespath_hints'].push(msg);
|
|
179
189
|
}
|
|
180
190
|
}
|
|
181
191
|
// Resolve templates in step params before execution
|
|
@@ -384,8 +394,8 @@ export async function runFlow(ctx, steps, options) {
|
|
|
384
394
|
stepsExecuted++;
|
|
385
395
|
continue;
|
|
386
396
|
}
|
|
387
|
-
// Capture artifacts on error if available
|
|
388
|
-
if (ctx.artifacts) {
|
|
397
|
+
// Capture artifacts on error if available (skip in HTTP mode — no browser)
|
|
398
|
+
if (ctx.artifacts && !httpMode) {
|
|
389
399
|
try {
|
|
390
400
|
await ctx.artifacts.saveScreenshot(`error-${step.id}`);
|
|
391
401
|
const html = await ctx.page.content();
|
|
@@ -396,12 +406,20 @@ export async function runFlow(ctx, steps, options) {
|
|
|
396
406
|
console.error('Failed to save artifacts:', artifactError);
|
|
397
407
|
}
|
|
398
408
|
}
|
|
409
|
+
// Attach partial results to the error for upstream consumers
|
|
410
|
+
if (error instanceof Error) {
|
|
411
|
+
error.partialResult = {
|
|
412
|
+
collectibles: { ...collectibles },
|
|
413
|
+
stepsExecuted,
|
|
414
|
+
failedStepId: step.id,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
399
417
|
// Stop on error (default behavior)
|
|
400
418
|
throw error;
|
|
401
419
|
}
|
|
402
420
|
}
|
|
403
421
|
const durationMs = Date.now() - startTime;
|
|
404
|
-
const finalUrl = ctx.page.url();
|
|
422
|
+
const finalUrl = httpMode ? undefined : ctx.page.url();
|
|
405
423
|
// Collect JMESPath hints from vars (stored by step handlers)
|
|
406
424
|
const jmespathHints = vars['__jmespath_hints'] || [];
|
|
407
425
|
const singleHint = vars['__jmespath_hint'];
|
|
@@ -418,6 +436,7 @@ export async function runFlow(ctx, steps, options) {
|
|
|
418
436
|
stepsExecuted,
|
|
419
437
|
stepsTotal: steps.length,
|
|
420
438
|
},
|
|
439
|
+
_vars: { ...vars },
|
|
421
440
|
};
|
|
422
441
|
// Only include _hints if there are any
|
|
423
442
|
if (uniqueHints.length > 0) {
|
|
@@ -2,6 +2,7 @@ import type { Page, BrowserContext, Frame } from 'playwright';
|
|
|
2
2
|
import type { DslStep } from './types.js';
|
|
3
3
|
import type { NetworkCaptureApi } from '../networkCapture.js';
|
|
4
4
|
import type { AuthFailureMonitor } from '../authResilience.js';
|
|
5
|
+
import type { SnapshotFile } from '../requestSnapshot.js';
|
|
5
6
|
/**
|
|
6
7
|
* Step execution context
|
|
7
8
|
*/
|
|
@@ -24,6 +25,12 @@ export interface StepContext {
|
|
|
24
25
|
previousTabIndex?: number;
|
|
25
26
|
/** Task pack directory path (for resolving relative file paths) */
|
|
26
27
|
packDir?: string;
|
|
28
|
+
/** If true, running in HTTP-only mode (no browser) */
|
|
29
|
+
httpMode?: boolean;
|
|
30
|
+
/** Request snapshots for HTTP-first execution */
|
|
31
|
+
snapshots?: SnapshotFile;
|
|
32
|
+
/** Secrets for template resolution in HTTP mode */
|
|
33
|
+
secrets?: Record<string, string>;
|
|
27
34
|
}
|
|
28
35
|
/**
|
|
29
36
|
* Executes a single DSL step
|
|
@@ -1 +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;
|
|
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;AAE/D,OAAO,KAAK,EAAE,YAAY,EAAmB,MAAM,uBAAuB,CAAC;AAI3E;;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;IACjB,sDAAsD;IACtD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iDAAiD;IACjD,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAmiCD;;GAEG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,IAAI,CAAC,CA4Ef"}
|
package/dist/dsl/stepHandlers.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { resolveTemplate } from './templating.js';
|
|
2
2
|
import { resolveTargetWithFallback, selectorToTarget } from './target.js';
|
|
3
3
|
import { search as jmesSearch } from '@jmespath-community/jmespath';
|
|
4
|
+
import { replayFromSnapshot } from '../httpReplay.js';
|
|
5
|
+
import { validateResponse } from '../requestSnapshot.js';
|
|
4
6
|
/**
|
|
5
7
|
* Executes a navigate step
|
|
6
8
|
*/
|
|
@@ -284,18 +286,24 @@ function getByPath(obj, path) {
|
|
|
284
286
|
}
|
|
285
287
|
try {
|
|
286
288
|
const result = jmesSearch(obj, trimmed);
|
|
287
|
-
// Check for null/undefined results and provide diagnostic hint
|
|
289
|
+
// Check for null/undefined results and provide diagnostic hint with data structure
|
|
288
290
|
if (result === null || result === undefined) {
|
|
291
|
+
const topKeys = typeof obj === 'object' && obj !== null
|
|
292
|
+
? Object.keys(obj).slice(0, 10).join(', ')
|
|
293
|
+
: typeof obj;
|
|
289
294
|
return {
|
|
290
295
|
value: result,
|
|
291
|
-
hint: `JMESPath '${trimmed}' matched nothing (returned ${result}).
|
|
296
|
+
hint: `JMESPath '${trimmed}' matched nothing (returned ${result}). Actual top-level keys: [${topKeys}]. Try a simpler path like 'data' or 'keys(@)' to inspect the structure.`,
|
|
292
297
|
};
|
|
293
298
|
}
|
|
294
299
|
// Check for empty array results
|
|
295
300
|
if (Array.isArray(result) && result.length === 0) {
|
|
301
|
+
const topKeys = typeof obj === 'object' && obj !== null
|
|
302
|
+
? Object.keys(obj).slice(0, 10).join(', ')
|
|
303
|
+
: typeof obj;
|
|
296
304
|
return {
|
|
297
305
|
value: result,
|
|
298
|
-
hint: `JMESPath '${trimmed}' returned an empty array. The path may be correct but no items matched.`,
|
|
306
|
+
hint: `JMESPath '${trimmed}' returned an empty array. Actual top-level keys: [${topKeys}]. The path may be correct but no items matched.`,
|
|
299
307
|
};
|
|
300
308
|
}
|
|
301
309
|
return { value: result };
|
|
@@ -399,8 +407,13 @@ async function executeNetworkReplay(ctx, step) {
|
|
|
399
407
|
setQuery: step.params.overrides.setQuery,
|
|
400
408
|
setHeaders: step.params.overrides.setHeaders,
|
|
401
409
|
body: step.params.overrides.body,
|
|
402
|
-
urlReplace
|
|
403
|
-
|
|
410
|
+
// Normalize urlReplace/bodyReplace to array (DSL type accepts single or array)
|
|
411
|
+
urlReplace: step.params.overrides.urlReplace
|
|
412
|
+
? (Array.isArray(step.params.overrides.urlReplace) ? step.params.overrides.urlReplace : [step.params.overrides.urlReplace])
|
|
413
|
+
: undefined,
|
|
414
|
+
bodyReplace: step.params.overrides.bodyReplace
|
|
415
|
+
? (Array.isArray(step.params.overrides.bodyReplace) ? step.params.overrides.bodyReplace : [step.params.overrides.bodyReplace])
|
|
416
|
+
: undefined,
|
|
404
417
|
}
|
|
405
418
|
: undefined;
|
|
406
419
|
let result;
|
|
@@ -717,10 +730,133 @@ async function executeSwitchTab(ctx, step) {
|
|
|
717
730
|
ctx.vars['__newPage'] = targetPage;
|
|
718
731
|
await targetPage.bringToFront();
|
|
719
732
|
}
|
|
733
|
+
/** Step types that are skipped silently in HTTP mode (setup/trigger steps). */
|
|
734
|
+
const HTTP_MODE_SKIP_STEPS = new Set([
|
|
735
|
+
'navigate', 'click', 'fill', 'select_option', 'press_key',
|
|
736
|
+
'upload_file', 'wait_for', 'assert', 'frame', 'new_tab',
|
|
737
|
+
'switch_tab', 'network_find',
|
|
738
|
+
]);
|
|
739
|
+
/**
|
|
740
|
+
* Merge flow-level step overrides with snapshot-level overrides.
|
|
741
|
+
* Step overrides take precedence for scalar fields (url, body).
|
|
742
|
+
* Array fields (bodyReplace, urlReplace) are concatenated: snapshot first, then step.
|
|
743
|
+
* Object fields (setQuery, setHeaders) are merged: step values override snapshot values.
|
|
744
|
+
*/
|
|
745
|
+
function mergeStepOverridesIntoSnapshot(snapshot, stepOverrides) {
|
|
746
|
+
if (!stepOverrides)
|
|
747
|
+
return snapshot;
|
|
748
|
+
// Normalize step overrides arrays
|
|
749
|
+
const stepBodyReplace = stepOverrides.bodyReplace
|
|
750
|
+
? (Array.isArray(stepOverrides.bodyReplace) ? stepOverrides.bodyReplace : [stepOverrides.bodyReplace])
|
|
751
|
+
: [];
|
|
752
|
+
const stepUrlReplace = stepOverrides.urlReplace
|
|
753
|
+
? (Array.isArray(stepOverrides.urlReplace) ? stepOverrides.urlReplace : [stepOverrides.urlReplace])
|
|
754
|
+
: [];
|
|
755
|
+
const snapshotOv = snapshot.overrides;
|
|
756
|
+
const bodyReplaceArr = [...(snapshotOv?.bodyReplace ?? []), ...stepBodyReplace];
|
|
757
|
+
const urlReplaceArr = [...(snapshotOv?.urlReplace ?? []), ...stepUrlReplace];
|
|
758
|
+
// Coerce step setQuery values to strings (step type allows string | number, snapshot expects string)
|
|
759
|
+
const stepSetQuery = stepOverrides.setQuery
|
|
760
|
+
? Object.fromEntries(Object.entries(stepOverrides.setQuery).map(([k, v]) => [k, String(v)]))
|
|
761
|
+
: undefined;
|
|
762
|
+
const setQueryMerged = (snapshotOv?.setQuery || stepSetQuery)
|
|
763
|
+
? { ...snapshotOv?.setQuery, ...stepSetQuery }
|
|
764
|
+
: undefined;
|
|
765
|
+
const setHeadersMerged = (snapshotOv?.setHeaders || stepOverrides.setHeaders)
|
|
766
|
+
? { ...snapshotOv?.setHeaders, ...stepOverrides.setHeaders }
|
|
767
|
+
: undefined;
|
|
768
|
+
const merged = {
|
|
769
|
+
// Scalar overrides: step takes precedence
|
|
770
|
+
url: stepOverrides.url ?? snapshotOv?.url,
|
|
771
|
+
body: stepOverrides.body ?? snapshotOv?.body,
|
|
772
|
+
setQuery: setQueryMerged && Object.keys(setQueryMerged).length > 0 ? setQueryMerged : undefined,
|
|
773
|
+
setHeaders: setHeadersMerged && Object.keys(setHeadersMerged).length > 0 ? setHeadersMerged : undefined,
|
|
774
|
+
bodyReplace: bodyReplaceArr.length > 0 ? bodyReplaceArr : undefined,
|
|
775
|
+
urlReplace: urlReplaceArr.length > 0 ? urlReplaceArr : undefined,
|
|
776
|
+
};
|
|
777
|
+
const hasOverrides = merged.url || merged.body || merged.setQuery || merged.setHeaders || merged.bodyReplace || merged.urlReplace;
|
|
778
|
+
return { ...snapshot, overrides: hasOverrides ? merged : undefined };
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Execute a network_replay step in HTTP-only mode using snapshot data.
|
|
782
|
+
*/
|
|
783
|
+
async function executeNetworkReplayHttp(ctx, step) {
|
|
784
|
+
if (!ctx.snapshots) {
|
|
785
|
+
throw new Error('network_replay in HTTP mode requires snapshots');
|
|
786
|
+
}
|
|
787
|
+
const snapshot = ctx.snapshots.snapshots[step.id];
|
|
788
|
+
if (!snapshot) {
|
|
789
|
+
throw new Error(`No snapshot found for step "${step.id}"`);
|
|
790
|
+
}
|
|
791
|
+
// Merge flow-level overrides (from step.params) with snapshot-level overrides.
|
|
792
|
+
// This ensures bodyReplace/urlReplace from the flow are applied in HTTP-only mode.
|
|
793
|
+
const mergedSnapshot = mergeStepOverridesIntoSnapshot(snapshot, step.params.overrides);
|
|
794
|
+
const result = await replayFromSnapshot(mergedSnapshot, ctx.inputs, ctx.vars, {
|
|
795
|
+
secrets: ctx.secrets,
|
|
796
|
+
});
|
|
797
|
+
// Validate the response — throw to trigger browser fallback if stale
|
|
798
|
+
const validation = validateResponse(snapshot, result);
|
|
799
|
+
if (!validation.valid) {
|
|
800
|
+
throw new Error(`Snapshot stale for step "${step.id}": ${validation.reason}`);
|
|
801
|
+
}
|
|
802
|
+
if (step.params.saveAs) {
|
|
803
|
+
ctx.vars[step.params.saveAs] = {
|
|
804
|
+
status: result.status,
|
|
805
|
+
contentType: result.contentType,
|
|
806
|
+
body: result.body,
|
|
807
|
+
bodySize: result.bodySize,
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
// Use 'path' with fallback to deprecated 'jsonPath'
|
|
811
|
+
const pathExpr = step.params.response.path || step.params.response.jsonPath;
|
|
812
|
+
let outValue;
|
|
813
|
+
if (step.params.response.as === 'json') {
|
|
814
|
+
try {
|
|
815
|
+
outValue = JSON.parse(result.body);
|
|
816
|
+
}
|
|
817
|
+
catch {
|
|
818
|
+
throw new Error(`network_replay (HTTP mode): response body is not valid JSON (status ${result.status})`);
|
|
819
|
+
}
|
|
820
|
+
if (pathExpr) {
|
|
821
|
+
const pathResult = getByPath(outValue, pathExpr);
|
|
822
|
+
outValue = pathResult.value;
|
|
823
|
+
if (pathResult.hint) {
|
|
824
|
+
ctx.vars['__jmespath_hint'] = pathResult.hint;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
else {
|
|
829
|
+
if (pathExpr) {
|
|
830
|
+
const pathResult = getByPath(JSON.parse(result.body), pathExpr);
|
|
831
|
+
outValue = pathResult.value;
|
|
832
|
+
if (pathResult.hint) {
|
|
833
|
+
ctx.vars['__jmespath_hint'] = pathResult.hint;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
else {
|
|
837
|
+
outValue = result.body;
|
|
838
|
+
}
|
|
839
|
+
if (typeof outValue === 'object' && outValue !== null) {
|
|
840
|
+
outValue = JSON.stringify(outValue);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
ctx.collectibles[step.params.out] = outValue;
|
|
844
|
+
}
|
|
720
845
|
/**
|
|
721
846
|
* Executes a single DSL step
|
|
722
847
|
*/
|
|
723
848
|
export async function executeStep(ctx, step) {
|
|
849
|
+
// In HTTP mode, skip DOM/setup steps and use snapshot replay for network_replay
|
|
850
|
+
if (ctx.httpMode) {
|
|
851
|
+
if (HTTP_MODE_SKIP_STEPS.has(step.type)) {
|
|
852
|
+
return; // silently skip
|
|
853
|
+
}
|
|
854
|
+
if (step.type === 'network_replay') {
|
|
855
|
+
await executeNetworkReplayHttp(ctx, step);
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
// network_extract, set_var, sleep execute normally below
|
|
859
|
+
}
|
|
724
860
|
switch (step.type) {
|
|
725
861
|
case 'navigate':
|
|
726
862
|
await executeNavigate(ctx, step);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templating.d.ts","sourceRoot":"","sources":["../../src/dsl/templating.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"templating.d.ts","sourceRoot":"","sources":["../../src/dsl/templating.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAuClD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,MAAM,CAWlF;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,eAAe,GAAG,CAAC,CAkBvE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEhD"}
|
package/dist/dsl/templating.js
CHANGED
|
@@ -10,6 +10,17 @@ const env = new nunjucks.Environment(null, {
|
|
|
10
10
|
autoescape: false,
|
|
11
11
|
throwOnUndefined: false, // Allow undefined values to render as empty string
|
|
12
12
|
});
|
|
13
|
+
/**
|
|
14
|
+
* pctEncode filter: like urlencode but also encodes characters that
|
|
15
|
+
* encodeURIComponent leaves unescaped (parentheses, !, ', *, ~).
|
|
16
|
+
* Useful in URLs where those chars have structural meaning (e.g. LinkedIn Sales Navigator query syntax).
|
|
17
|
+
* Usage: {{inputs.company_name | pctEncode}}
|
|
18
|
+
*/
|
|
19
|
+
env.addFilter('pctEncode', (val) => {
|
|
20
|
+
if (val == null)
|
|
21
|
+
return '';
|
|
22
|
+
return encodeURIComponent(String(val)).replace(/[!'()*~]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase());
|
|
23
|
+
});
|
|
13
24
|
/**
|
|
14
25
|
* TOTP filter: generates a 6-digit TOTP code from a base32 secret.
|
|
15
26
|
* Usage: {{secret.TOTP_KEY | totp}}
|
package/dist/dsl/types.d.ts
CHANGED
|
@@ -433,16 +433,22 @@ export interface NetworkReplayStep extends BaseDslStep {
|
|
|
433
433
|
setQuery?: Record<string, string | number>;
|
|
434
434
|
setHeaders?: Record<string, string>;
|
|
435
435
|
body?: string;
|
|
436
|
-
/** Regex find/replace on captured URL; replace can use $1, $2. Supports {{vars.xxx}}/{{inputs.xxx}} (resolved before replace). */
|
|
436
|
+
/** Regex find/replace on captured URL; replace can use $1, $2. Supports {{vars.xxx}}/{{inputs.xxx}} (resolved before replace). Accepts single object or array. */
|
|
437
437
|
urlReplace?: {
|
|
438
438
|
find: string;
|
|
439
439
|
replace: string;
|
|
440
|
-
}
|
|
441
|
-
|
|
440
|
+
} | Array<{
|
|
441
|
+
find: string;
|
|
442
|
+
replace: string;
|
|
443
|
+
}>;
|
|
444
|
+
/** Regex find/replace on captured body; replace can use $1, $2. Supports {{vars.xxx}}/{{inputs.xxx}} (resolved before replace). Accepts single object or array. */
|
|
442
445
|
bodyReplace?: {
|
|
443
446
|
find: string;
|
|
444
447
|
replace: string;
|
|
445
|
-
}
|
|
448
|
+
} | Array<{
|
|
449
|
+
find: string;
|
|
450
|
+
replace: string;
|
|
451
|
+
}>;
|
|
446
452
|
};
|
|
447
453
|
auth: 'browser_context';
|
|
448
454
|
out: string;
|
|
@@ -691,5 +697,11 @@ export interface RunFlowResult {
|
|
|
691
697
|
* These help AI agents understand why data extraction may have failed.
|
|
692
698
|
*/
|
|
693
699
|
_hints?: string[];
|
|
700
|
+
/**
|
|
701
|
+
* Internal: resolved vars after flow execution. Used by snapshot capture
|
|
702
|
+
* to look up network entries by their runtime-resolved request IDs.
|
|
703
|
+
* Not part of the public API.
|
|
704
|
+
*/
|
|
705
|
+
_vars?: Record<string, unknown>;
|
|
694
706
|
}
|
|
695
707
|
//# sourceMappingURL=types.d.ts.map
|