surf-cli 2.8.0 → 2.10.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 (47) hide show
  1. package/README.md +146 -8
  2. package/native/abort.cjs +65 -0
  3. package/native/activity-journal.cjs +55 -0
  4. package/native/ai-queue.cjs +64 -0
  5. package/native/aistudio-build.cjs +21 -13
  6. package/native/aistudio-client.cjs +40 -20
  7. package/native/browser-lock.cjs +2 -2
  8. package/native/chatgpt-client.cjs +49 -31
  9. package/native/cli.cjs +352 -482
  10. package/native/client-transport.cjs +168 -0
  11. package/native/do-executor.cjs +68 -510
  12. package/native/do-parser.cjs +8 -249
  13. package/native/doctor.cjs +55 -5
  14. package/native/endpoint.cjs +174 -0
  15. package/native/file-transfer.cjs +734 -0
  16. package/native/gemini-client.cjs +156 -71
  17. package/native/grok-client.cjs +98 -89
  18. package/native/host-helpers.cjs +43 -26
  19. package/native/host-sessions.cjs +287 -0
  20. package/native/host.cjs +998 -620
  21. package/native/listener.cjs +20 -0
  22. package/native/mcp-server.cjs +60 -65
  23. package/native/network-export.cjs +116 -0
  24. package/native/network-store.cjs +38 -58
  25. package/native/perplexity-client.cjs +46 -17
  26. package/native/playbook-authoring.cjs +44 -0
  27. package/native/playbook-cli.cjs +157 -0
  28. package/native/playbook-client.cjs +259 -0
  29. package/native/playbook-receipts.cjs +109 -0
  30. package/native/playbook-records.cjs +208 -0
  31. package/native/playbook-runtime.cjs +177 -0
  32. package/native/playbooks.cjs +235 -0
  33. package/native/private-state.cjs +156 -0
  34. package/native/redaction.cjs +104 -0
  35. package/native/remote-auth.cjs +279 -0
  36. package/native/remote-transport.cjs +337 -0
  37. package/native/request-pending.cjs +148 -0
  38. package/native/socket-path.cjs +1 -1
  39. package/native/workflow-definition.cjs +368 -0
  40. package/native/workflow-runtime.cjs +225 -0
  41. package/package.json +9 -6
  42. package/playbooks/page/ops/read.json +22 -0
  43. package/playbooks/page/playbook.json +7 -0
  44. package/scripts/install-native-host.cjs +36 -5
  45. package/skills/README.md +11 -5
  46. package/skills/deep-x-research/SKILL.md +106 -0
  47. package/skills/surf/SKILL.md +72 -5
@@ -1,526 +1,84 @@
1
- /**
2
- * Executor for surf `do` workflow commands
3
- *
4
- * Executes steps sequentially with auto-waits and streaming progress output.
5
- * Supports:
6
- * - Step outputs: capture results with `as` field
7
- * - Loops: `repeat` for fixed iterations, `each` for array iteration
8
- * - Variable substitution: %{varname} syntax
9
- *
10
- * Follows the same socket communication pattern as --script mode in cli.cjs.
11
- */
1
+ const { openClientTransport } = require("./client-transport.cjs");
2
+ const { selectEndpoint } = require("./endpoint.cjs");
3
+ const { prepareRemoteTool, validateLocalToolPaths } = require("./file-transfer.cjs");
4
+ const { resolveRequestDeadlineMs } = require("./host-sessions.cjs");
5
+ const runtime = require("./workflow-runtime.cjs");
12
6
 
13
- const net = require("net");
14
- const { SOCKET_PATH, formatSocketError } = require("./socket-path.cjs");
15
-
16
- // Maximum iterations for loops (safety cap)
17
- const MAX_LOOP_ITERATIONS = 100;
18
-
19
- // Commands that trigger auto-wait after execution
20
- // Note: 'type' is intentionally excluded - typing doesn't trigger navigation or DOM changes
21
- const AUTO_WAIT_COMMANDS = [
22
- 'go', 'navigate', 'click', 'key', 'form.fill', 'submit',
23
- 'tab.switch', 'tab.new', 'back', 'forward'
24
- ];
25
-
26
- // Auto-wait strategies per command type
27
- const AUTO_WAIT_MAP = {
28
- 'navigate': 'wait.load',
29
- 'go': 'wait.load',
30
- 'click': 'wait.dom',
31
- 'key': 'wait.dom',
32
- 'form.fill': 'wait.dom',
33
- 'submit': 'wait.load', // Form submission typically triggers navigation
34
- 'tab.switch': 'wait.load',
35
- 'tab.new': 'wait.load',
36
- 'back': 'wait.load',
37
- 'forward': 'wait.load',
38
- };
39
-
40
- /**
41
- * Check if a command should trigger an auto-wait
42
- * @param {string} cmd - Command name
43
- * @returns {boolean}
44
- */
45
- function shouldAutoWait(cmd) {
46
- return AUTO_WAIT_COMMANDS.some(c => cmd === c || cmd.startsWith(c + '.'));
47
- }
48
-
49
- /**
50
- * Get the appropriate auto-wait command for a given command
51
- * @param {string} cmd - Command name
52
- * @returns {string|null} - Wait command to execute, or null
53
- */
54
- function getAutoWaitCommand(cmd) {
55
- // Check exact match first
56
- if (AUTO_WAIT_MAP[cmd] !== undefined) return AUTO_WAIT_MAP[cmd];
57
-
58
- // Check prefix match
59
- for (const [prefix, waitCmd] of Object.entries(AUTO_WAIT_MAP)) {
60
- if (cmd.startsWith(prefix + '.')) return waitCmd;
61
- }
62
-
63
- return null;
64
- }
65
-
66
- /**
67
- * Send a single tool request over socket
68
- * @param {string} toolName - Tool/command name
69
- * @param {object} toolArgs - Tool arguments
70
- * @param {object} context - Execution context (tabId, windowId)
71
- * @returns {Promise<object>} - Response from host
72
- */
73
7
  function sendDoRequest(toolName, toolArgs, context = {}) {
74
- return new Promise((resolve, reject) => {
75
- const sock = net.createConnection(SOCKET_PATH, () => {
76
- const req = {
77
- type: "tool_request",
78
- method: "execute_tool",
79
- params: { tool: toolName, args: toolArgs },
80
- id: "do-" + Date.now() + "-" + Math.random(),
81
- };
82
- if (context.tabId) req.tabId = context.tabId;
83
- if (context.windowId) req.windowId = context.windowId;
84
- sock.write(JSON.stringify(req) + "\n");
85
- });
86
-
87
- let buf = "";
88
- sock.on("data", (d) => {
89
- buf += d.toString();
90
- const lines = buf.split("\n");
91
- buf = lines.pop();
92
- for (const line of lines) {
93
- if (!line.trim()) continue;
94
- try {
95
- const resp = JSON.parse(line);
96
- sock.end();
97
- resolve(resp);
98
- } catch {
99
- sock.end();
100
- reject(new Error("Invalid JSON response"));
101
- }
102
- }
103
- });
104
-
105
- sock.on("error", (e) => {
106
- reject(new Error(formatSocketError(e)));
107
- });
108
-
109
- const timeoutId = setTimeout(() => {
110
- sock.destroy();
111
- reject(new Error("Request timeout"));
112
- }, 30000);
113
-
114
- sock.on("close", () => clearTimeout(timeoutId));
115
- });
116
- }
117
-
118
- /**
119
- * Resolve a variable reference or perform string substitution
120
- * @param {*} template - Value to resolve (may contain %{var} references)
121
- * @param {object} vars - Variables map
122
- * @returns {*} - Resolved value
123
- */
124
- function resolveVar(template, vars) {
125
- if (typeof template !== 'string') return template;
126
-
127
- // Check if it's a simple variable reference like %{urls}
128
- const match = template.match(/^%\{(\w+)\}$/);
129
- if (match) {
130
- const value = vars[match[1]];
131
- return value !== undefined ? value : template;
132
- }
133
-
134
- // Otherwise do string substitution
135
- return template.replace(/%\{(\w+)\}/g, (_, name) => {
136
- const val = vars[name];
137
- if (val === undefined) return `%{${name}}`;
138
- // Convert objects/arrays to string for interpolation
139
- if (typeof val === 'object') return JSON.stringify(val);
140
- return String(val);
141
- });
142
- }
143
-
144
- /**
145
- * Substitute variables in arguments using %{varname} syntax
146
- * @param {object} args - Arguments object
147
- * @param {object} vars - Variables map
148
- * @returns {object} - Arguments with variables substituted
149
- */
150
- function substituteVars(args, vars) {
151
- if (!args || typeof args !== 'object') return args;
152
-
153
- // Handle arrays specially to preserve array type
154
- if (Array.isArray(args)) {
155
- return args.map(item => {
156
- if (typeof item === 'string') {
157
- return resolveVar(item, vars);
158
- } else if (typeof item === 'object' && item !== null) {
159
- return substituteVars(item, vars);
160
- } else {
161
- return item;
162
- }
163
- });
164
- }
165
-
166
- // Handle plain objects
167
- const result = {};
168
- for (const [key, val] of Object.entries(args)) {
169
- if (typeof val === 'string') {
170
- result[key] = resolveVar(val, vars);
171
- } else if (Array.isArray(val)) {
172
- result[key] = substituteVars(val, vars);
173
- } else if (typeof val === 'object' && val !== null) {
174
- result[key] = substituteVars(val, vars);
175
- } else {
176
- result[key] = val;
177
- }
178
- }
179
- return result;
180
- }
181
-
182
- /**
183
- * Extract usable output from a step response for the `as` capture
184
- * @param {object} resp - Response from sendDoRequest
185
- * @returns {*} - Extracted value
186
- */
187
- function extractStepOutput(resp) {
188
- // MCP format: resp.result.content[0].text
189
- if (resp.result?.content?.[0]?.text) {
190
- const text = resp.result.content[0].text;
191
- // Try to parse as JSON, otherwise return raw text
8
+ const request = {
9
+ type: "tool_request",
10
+ method: "execute_tool",
11
+ params: { tool: toolName, args: toolArgs },
12
+ id: `do-${Date.now()}-${Math.random()}`,
13
+ };
14
+ if (context.tabId) request.tabId = context.tabId;
15
+ if (context.windowId) request.windowId = context.windowId;
16
+ const timeoutMs = context.timeoutMs || resolveRequestDeadlineMs(toolName, toolArgs);
17
+ const endpoint = context.endpoint || selectEndpoint([]).endpoint;
18
+ const prepared = endpoint.kind === "remote"
19
+ ? prepareRemoteTool(toolName, toolArgs)
20
+ : { args: validateLocalToolPaths(toolName, toolArgs), uploads: [], downloads: [] };
21
+ request.params.args = prepared.args;
22
+ if (context.transport) return context.transport.request(request, timeoutMs, prepared);
23
+ return (async () => {
24
+ const transport = await openClientTransport(endpoint);
192
25
  try {
193
- return JSON.parse(text);
194
- } catch {
195
- return text;
26
+ return await transport.request(request, timeoutMs, prepared);
27
+ } finally {
28
+ await transport.close();
196
29
  }
197
- }
198
-
199
- // Direct value (some tools return this)
200
- if (resp.value !== undefined) return resp.value;
201
-
202
- // Direct result object
203
- if (resp.result !== undefined) return resp.result;
204
-
205
- // Fallback to the whole response
206
- return resp;
30
+ })();
207
31
  }
208
32
 
209
- /**
210
- * Execute a single tool step (non-loop)
211
- * @param {object} step - Step to execute { cmd, args, as? }
212
- * @param {object} vars - Variables map (mutated if step has `as`)
213
- * @param {object} context - Execution context
214
- * @param {object} options - Execution options
215
- * @returns {Promise<object>} - Result { success, error?, output? }
216
- */
217
- async function executeSingleStep(step, vars, context, options) {
218
- const { autoWait = true, stepDelay = 100 } = options;
219
-
220
- // Substitute variables in args
221
- const resolvedArgs = substituteVars(step.args || {}, vars);
222
-
223
- try {
224
- const resp = await sendDoRequest(step.cmd, resolvedArgs, context);
225
-
226
- if (resp.error) {
227
- const errText = resp.error.content?.[0]?.text || JSON.stringify(resp.error);
228
- return { success: false, error: errText };
229
- }
230
-
231
- // Capture output if step has `as` field
232
- if (step.as) {
233
- const output = extractStepOutput(resp);
234
- vars[step.as] = output;
235
- }
236
-
237
- // Command-specific auto-wait
238
- if (autoWait) {
239
- const waitCmd = getAutoWaitCommand(step.cmd);
240
- if (waitCmd) {
241
- const waitArgs = waitCmd === 'wait.load'
242
- ? { timeout: 10000 }
243
- : { stable: 100, timeout: 5000 };
244
- try {
245
- await sendDoRequest(waitCmd, waitArgs, context);
246
- } catch {
247
- // Ignore auto-wait failures silently
248
- }
249
- }
250
- }
251
-
252
- // Delay between steps
253
- if (stepDelay > 0) {
254
- await new Promise(r => setTimeout(r, stepDelay));
255
- }
256
-
257
- return { success: true, output: step.as ? vars[step.as] : undefined };
258
- } catch (err) {
259
- return { success: false, error: err.message };
260
- }
33
+ function summarizeArgs(step) {
34
+ return Object.entries(step.args || {})
35
+ .map(([key, value]) => typeof value === "string" && value.length > 40 ? `${key}="${value.slice(0, 37)}..."` : `${key}=${JSON.stringify(value)}`)
36
+ .join(" ");
261
37
  }
262
38
 
263
- /**
264
- * Execute a single step, handling loops recursively
265
- * @param {object} step - Step to execute (may be a loop or regular step)
266
- * @param {object} vars - Variables map
267
- * @param {object} context - Execution context
268
- * @param {object} options - Execution options
269
- * @param {function} onProgress - Progress callback for streaming output
270
- * @returns {Promise<object>} - Result { success, error?, stepsExecuted }
271
- */
272
- async function executeStep(step, vars, context, options, onProgress) {
273
- const { onError = 'stop' } = options;
274
-
275
- // Handle `repeat` loop
276
- if (step.repeat !== undefined) {
277
- // Resolve repeat count (may be a variable)
278
- let max = resolveVar(step.repeat, vars);
279
- if (typeof max === 'string') max = parseInt(max, 10);
280
- if (typeof max !== 'number' || isNaN(max)) max = 1;
281
-
282
- // Safety cap
283
- max = Math.min(max, MAX_LOOP_ITERATIONS);
284
-
285
- if (!Array.isArray(step.steps) || step.steps.length === 0) {
286
- return { success: false, error: 'repeat: steps array required', stepsExecuted: 0 };
287
- }
288
-
289
- let totalExecuted = 0;
290
-
291
- for (let i = 0; i < max; i++) {
292
- // Create loop-scoped variables
293
- const loopVars = { ...vars, _index: i, _iteration: i + 1 };
294
-
295
- // Execute nested steps
296
- for (const nestedStep of step.steps) {
297
- const result = await executeStep(nestedStep, loopVars, context, options, onProgress);
298
- totalExecuted += result.stepsExecuted || 1;
299
-
300
- if (!result.success && onError === 'stop') {
301
- return { success: false, error: result.error, stepsExecuted: totalExecuted };
302
- }
303
- }
304
-
305
- // Copy captured variables back to parent scope (only from regular steps, not loops)
306
- for (const nestedStep of step.steps) {
307
- // Skip loop steps - their 'as' is the loop variable, not an output capture
308
- const isNestedLoop = nestedStep.repeat !== undefined || nestedStep.each !== undefined;
309
- if (!isNestedLoop && nestedStep.as && loopVars[nestedStep.as] !== undefined) {
310
- vars[nestedStep.as] = loopVars[nestedStep.as];
311
- }
312
- }
313
-
314
- // Check `until` condition
315
- if (step.until) {
316
- const untilResult = await executeSingleStep(step.until, loopVars, context, options);
317
- totalExecuted++;
318
-
319
- // Exit loop if until condition is truthy
320
- const exitValue = untilResult.output;
321
- if (exitValue === true || exitValue === 'true' || exitValue) {
322
- break;
323
- }
324
- }
325
- }
326
-
327
- return { success: true, stepsExecuted: totalExecuted };
328
- }
329
-
330
- // Handle `each` loop
331
- if (step.each !== undefined) {
332
- const items = resolveVar(step.each, vars);
333
-
334
- if (!Array.isArray(items)) {
335
- return {
336
- success: false,
337
- error: `each: expected array, got ${typeof items}${items === undefined ? ' (undefined)' : ''}`,
338
- stepsExecuted: 0
339
- };
340
- }
341
-
342
- if (!Array.isArray(step.steps) || step.steps.length === 0) {
343
- return { success: false, error: 'each: steps array required', stepsExecuted: 0 };
344
- }
345
-
346
- // Safety cap
347
- const maxItems = Math.min(items.length, MAX_LOOP_ITERATIONS);
348
- const itemVar = step.as || 'item';
349
- let totalExecuted = 0;
350
-
351
- for (let i = 0; i < maxItems; i++) {
352
- // Create loop-scoped variables
353
- const loopVars = { ...vars, [itemVar]: items[i], _index: i, _iteration: i + 1 };
354
-
355
- // Execute nested steps
356
- for (const nestedStep of step.steps) {
357
- const result = await executeStep(nestedStep, loopVars, context, options, onProgress);
358
- totalExecuted += result.stepsExecuted || 1;
359
-
360
- if (!result.success && onError === 'stop') {
361
- return { success: false, error: result.error, stepsExecuted: totalExecuted };
362
- }
363
- }
364
-
365
- // Copy captured variables back to parent scope (only from regular steps, not loops)
366
- for (const nestedStep of step.steps) {
367
- // Skip loop steps - their 'as' is the loop variable, not an output capture
368
- const isNestedLoop = nestedStep.repeat !== undefined || nestedStep.each !== undefined;
369
- if (!isNestedLoop && nestedStep.as && loopVars[nestedStep.as] !== undefined) {
370
- vars[nestedStep.as] = loopVars[nestedStep.as];
371
- }
372
- }
373
- }
374
-
375
- return { success: true, stepsExecuted: totalExecuted };
39
+ function printProgress(event) {
40
+ const stepNum = `[${event.index + 1}/${event.total}]`;
41
+ if (event.type === "loop") {
42
+ if (event.phase === "start") {
43
+ const step = event.step;
44
+ const loopType = step.repeat !== undefined ? `repeat ${step.repeat}` : `each ${step.each}`;
45
+ console.log(`${stepNum} Loop: ${loopType} (${step.steps?.length || 0} nested steps)`);
46
+ } else if (event.phase === "ok") console.log(` Loop completed: ${event.stepsExecuted} steps (${event.ms}ms)`);
47
+ return;
376
48
  }
377
-
378
- // Regular step (non-loop)
379
- if (onProgress) {
380
- onProgress(step, 'start');
49
+ if (event.phase === "start") {
50
+ const args = summarizeArgs(event.step);
51
+ process.stdout.write(`${stepNum} ${event.step.cmd}${args ? ` ${args}` : ""} ... `);
52
+ } else if (event.phase === "ok") console.log(`OK (${event.ms}ms)`);
53
+ else {
54
+ console.log("FAIL");
55
+ console.log(` Error: ${event.error}`);
381
56
  }
382
-
383
- const result = await executeSingleStep(step, vars, context, options);
384
-
385
- if (onProgress) {
386
- onProgress(step, result.success ? 'ok' : 'fail', result.error);
387
- }
388
-
389
- return { ...result, stepsExecuted: 1 };
390
57
  }
391
58
 
392
- /**
393
- * Execute all workflow steps sequentially
394
- * @param {Array<object>} steps - Steps to execute
395
- * @param {object} options - Execution options
396
- * @returns {Promise<object>} - Execution result
397
- */
398
59
  async function executeDoSteps(steps, options = {}) {
399
- const {
400
- onError = 'stop',
401
- autoWait = true,
402
- stepDelay = 100,
403
- context = {},
404
- quiet = false, // For --json mode, suppress streaming output
405
- vars: initialVars = {},
406
- } = options;
407
-
408
- const results = [];
409
- const vars = { ...initialVars, ...(context.vars || {}) };
410
- const total = steps.length;
411
- let failed = 0;
412
- let stepsExecuted = 0;
413
- const startTotal = Date.now();
414
-
415
- for (let i = 0; i < total; i++) {
416
- const step = steps[i];
417
- const startTime = Date.now();
418
-
419
- // Check if this is a loop step
420
- const isLoop = step.repeat !== undefined || step.each !== undefined;
421
-
422
- if (isLoop) {
423
- // Loops handle their own progress output
424
- if (!quiet) {
425
- const loopType = step.repeat !== undefined ? `repeat ${step.repeat}` : `each ${step.each}`;
426
- console.log(`[${i + 1}/${total}] Loop: ${loopType} (${step.steps?.length || 0} nested steps)`);
427
- }
428
-
429
- const result = await executeStep(step, vars, context, { onError, autoWait, stepDelay }, null);
430
- const ms = Date.now() - startTime;
431
-
432
- stepsExecuted += result.stepsExecuted || 0;
433
-
434
- if (!result.success) {
435
- results.push({ step: i + 1, type: 'loop', status: 'error', error: result.error, ms });
436
- failed++;
437
-
438
- if (onError === 'stop') {
439
- return {
440
- status: 'failed',
441
- completedSteps: stepsExecuted,
442
- totalSteps: total,
443
- results,
444
- error: result.error,
445
- totalMs: Date.now() - startTotal,
446
- vars
447
- };
448
- }
449
- } else {
450
- results.push({ step: i + 1, type: 'loop', status: 'ok', stepsExecuted: result.stepsExecuted, ms });
451
- if (!quiet) {
452
- console.log(` Loop completed: ${result.stepsExecuted} steps (${ms}ms)`);
453
- }
454
- }
455
- } else {
456
- // Regular step
457
- const stepNum = `[${i + 1}/${total}]`;
458
- const argSummary = Object.entries(step.args || {})
459
- .map(([k, v]) => typeof v === "string" && v.length > 40
460
- ? `${k}="${v.slice(0, 37)}..."`
461
- : `${k}=${JSON.stringify(v)}`)
462
- .join(" ");
463
- const desc = argSummary ? `${step.cmd} ${argSummary}` : step.cmd;
464
-
465
- if (!quiet) {
466
- process.stdout.write(`${stepNum} ${desc} ... `);
467
- }
468
-
469
- const result = await executeSingleStep(step, vars, context, { onError, autoWait, stepDelay });
470
- const ms = Date.now() - startTime;
471
- stepsExecuted++;
472
-
473
- if (!result.success) {
474
- if (!quiet) {
475
- console.log('FAIL');
476
- console.log(` Error: ${result.error}`);
477
- }
478
-
479
- results.push({ step: i + 1, cmd: step.cmd, status: 'error', error: result.error, ms });
480
- failed++;
481
-
482
- if (onError === 'stop') {
483
- return {
484
- status: 'failed',
485
- completedSteps: stepsExecuted - 1,
486
- totalSteps: total,
487
- results,
488
- error: result.error,
489
- totalMs: Date.now() - startTotal,
490
- vars
491
- };
492
- }
493
- } else {
494
- if (!quiet) {
495
- console.log(`OK (${ms}ms)`);
496
- }
497
- results.push({ step: i + 1, cmd: step.cmd, status: 'ok', ms });
498
- }
499
- }
500
- }
501
-
502
- return {
503
- status: failed > 0 ? 'partial' : 'completed',
504
- completedSteps: stepsExecuted,
505
- totalSteps: total,
506
- results,
507
- failed,
508
- totalMs: Date.now() - startTotal,
509
- vars
510
- };
60
+ const context = options.context || {};
61
+ return runtime.executeWorkflow(steps, {
62
+ ...options,
63
+ executeTool: options.executeTool || ((tool, args) => sendDoRequest(tool, args, context)),
64
+ onProgress: options.quiet ? options.onProgress : (event) => {
65
+ printProgress(event);
66
+ options.onProgress?.(event);
67
+ },
68
+ });
511
69
  }
512
70
 
513
- module.exports = {
514
- executeDoSteps,
515
- sendDoRequest,
516
- shouldAutoWait,
517
- getAutoWaitCommand,
518
- substituteVars,
519
- resolveVar,
520
- extractStepOutput,
521
- executeStep,
522
- executeSingleStep,
523
- AUTO_WAIT_COMMANDS,
524
- AUTO_WAIT_MAP,
525
- MAX_LOOP_ITERATIONS
71
+ module.exports = {
72
+ AUTO_WAIT_COMMANDS: runtime.AUTO_WAIT_COMMANDS,
73
+ AUTO_WAIT_MAP: runtime.AUTO_WAIT_MAP,
74
+ MAX_LOOP_ITERATIONS: runtime.MAX_LOOP_ITERATIONS,
75
+ executeSingleStep: runtime.executeSingleStep,
76
+ executeStep: runtime.executeStep,
77
+ executeDoSteps,
78
+ extractStepOutput: runtime.extractStepOutput,
79
+ getAutoWaitCommand: runtime.getAutoWaitCommand,
80
+ resolveVar: runtime.resolveVar,
81
+ sendDoRequest,
82
+ shouldAutoWait: runtime.shouldAutoWait,
83
+ substituteVars: runtime.substituteVars,
526
84
  };