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.
- package/README.md +146 -8
- package/native/abort.cjs +65 -0
- package/native/activity-journal.cjs +55 -0
- package/native/ai-queue.cjs +64 -0
- package/native/aistudio-build.cjs +21 -13
- package/native/aistudio-client.cjs +40 -20
- package/native/browser-lock.cjs +2 -2
- package/native/chatgpt-client.cjs +49 -31
- package/native/cli.cjs +352 -482
- package/native/client-transport.cjs +168 -0
- package/native/do-executor.cjs +68 -510
- package/native/do-parser.cjs +8 -249
- package/native/doctor.cjs +55 -5
- package/native/endpoint.cjs +174 -0
- package/native/file-transfer.cjs +734 -0
- package/native/gemini-client.cjs +156 -71
- package/native/grok-client.cjs +98 -89
- package/native/host-helpers.cjs +43 -26
- package/native/host-sessions.cjs +287 -0
- package/native/host.cjs +998 -620
- package/native/listener.cjs +20 -0
- package/native/mcp-server.cjs +60 -65
- package/native/network-export.cjs +116 -0
- package/native/network-store.cjs +38 -58
- package/native/perplexity-client.cjs +46 -17
- package/native/playbook-authoring.cjs +44 -0
- package/native/playbook-cli.cjs +157 -0
- package/native/playbook-client.cjs +259 -0
- package/native/playbook-receipts.cjs +109 -0
- package/native/playbook-records.cjs +208 -0
- package/native/playbook-runtime.cjs +177 -0
- package/native/playbooks.cjs +235 -0
- package/native/private-state.cjs +156 -0
- package/native/redaction.cjs +104 -0
- package/native/remote-auth.cjs +279 -0
- package/native/remote-transport.cjs +337 -0
- package/native/request-pending.cjs +148 -0
- package/native/socket-path.cjs +1 -1
- package/native/workflow-definition.cjs +368 -0
- package/native/workflow-runtime.cjs +225 -0
- package/package.json +9 -6
- package/playbooks/page/ops/read.json +22 -0
- package/playbooks/page/playbook.json +7 -0
- package/scripts/install-native-host.cjs +36 -5
- package/skills/README.md +11 -5
- package/skills/deep-x-research/SKILL.md +106 -0
- package/skills/surf/SKILL.md +72 -5
package/native/do-executor.cjs
CHANGED
|
@@ -1,526 +1,84 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
|
194
|
-
}
|
|
195
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
extractStepOutput,
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
};
|