agent-scenario-loop 0.1.5 → 0.1.6
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/runner/profile-mobile.js +108 -13
- package/package.json +1 -1
|
@@ -20,7 +20,7 @@ exports.runProfileCli = runProfileCli;
|
|
|
20
20
|
exports.runProfileMobile = runProfileMobile;
|
|
21
21
|
exports.hashScenarioContract = hashScenarioContract;
|
|
22
22
|
exports.usage = usage;
|
|
23
|
-
const {
|
|
23
|
+
const { spawn } = require('node:child_process');
|
|
24
24
|
const fs = require('node:fs');
|
|
25
25
|
const fsp = require('node:fs/promises');
|
|
26
26
|
const path = require('node:path');
|
|
@@ -35,6 +35,7 @@ const { writeUsage } = require('./cli');
|
|
|
35
35
|
const CAPTURE_EVIDENCE_KINDS = new Set(['screenshot', 'uiTree', 'video']);
|
|
36
36
|
const PROVIDER_EVIDENCE_KINDS = new Set(['accessibility', 'logs', 'profiler']);
|
|
37
37
|
const SIGNAL_EVIDENCE_KINDS = new Set(['js', 'memory', 'network']);
|
|
38
|
+
const DEFAULT_PROVIDER_COMMAND_TIMEOUT_MS = 180_000;
|
|
38
39
|
/**
|
|
39
40
|
* Prints CLI usage to stderr.
|
|
40
41
|
*
|
|
@@ -188,23 +189,83 @@ async function hashFileSha256(filePath) {
|
|
|
188
189
|
return crypto.createHash('sha256').update(content).digest('hex');
|
|
189
190
|
}
|
|
190
191
|
/**
|
|
191
|
-
*
|
|
192
|
+
* Resolves the timeout applied to provider commands.
|
|
192
193
|
*
|
|
193
|
-
* @
|
|
194
|
+
* @returns {number}
|
|
195
|
+
*/
|
|
196
|
+
function resolveProviderCommandTimeoutMs() {
|
|
197
|
+
return readPositiveInteger(process.env.ASL_PROVIDER_COMMAND_TIMEOUT_MS, DEFAULT_PROVIDER_COMMAND_TIMEOUT_MS);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Runs one provider command without a shell, streaming output to raw files.
|
|
201
|
+
*
|
|
202
|
+
* @param {{command: string, args: string[], cwd?: string, env?: Record<string, string>, stderrPath: string, stdoutPath: string, timeoutMs: number}} options
|
|
194
203
|
* @returns {Promise<ProviderCommandResult>}
|
|
195
204
|
*/
|
|
196
|
-
function execProviderCommand({ args, command, cwd, env, }) {
|
|
205
|
+
function execProviderCommand({ args, command, cwd, env, stderrPath, stdoutPath, timeoutMs, }) {
|
|
197
206
|
return new Promise((resolve) => {
|
|
198
|
-
|
|
207
|
+
const child = spawn(command, args, {
|
|
199
208
|
...(cwd ? { cwd } : {}),
|
|
200
209
|
env: env ? { ...process.env, ...env } : process.env,
|
|
201
|
-
|
|
210
|
+
shell: false,
|
|
211
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
212
|
+
});
|
|
213
|
+
const stdoutChunks = [];
|
|
214
|
+
const stderrChunks = [];
|
|
215
|
+
let timedOut = false;
|
|
216
|
+
let settled = false;
|
|
217
|
+
const timeout = setTimeout(() => {
|
|
218
|
+
timedOut = true;
|
|
219
|
+
child.kill('SIGTERM');
|
|
220
|
+
setTimeout(() => {
|
|
221
|
+
if (!settled) {
|
|
222
|
+
child.kill('SIGKILL');
|
|
223
|
+
}
|
|
224
|
+
}, 1000).unref();
|
|
225
|
+
}, timeoutMs);
|
|
226
|
+
timeout.unref();
|
|
227
|
+
child.stdout.on('data', (chunk) => {
|
|
228
|
+
stdoutChunks.push(chunk);
|
|
229
|
+
fs.appendFileSync(stdoutPath, chunk);
|
|
230
|
+
});
|
|
231
|
+
child.stderr.on('data', (chunk) => {
|
|
232
|
+
stderrChunks.push(chunk);
|
|
233
|
+
fs.appendFileSync(stderrPath, chunk);
|
|
234
|
+
});
|
|
235
|
+
child.on('error', (error) => {
|
|
236
|
+
if (settled) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
clearTimeout(timeout);
|
|
240
|
+
settled = true;
|
|
241
|
+
const stderr = error.message;
|
|
242
|
+
fs.appendFileSync(stderrPath, `${stderr}\n`, 'utf8');
|
|
202
243
|
resolve({
|
|
203
244
|
args,
|
|
204
245
|
command,
|
|
205
|
-
exitCode:
|
|
246
|
+
exitCode: 1,
|
|
247
|
+
signal: null,
|
|
248
|
+
stderr,
|
|
249
|
+
stdout: Buffer.concat(stdoutChunks).toString('utf8'),
|
|
250
|
+
timedOut,
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
child.on('close', (exitCode, signal) => {
|
|
254
|
+
if (settled) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
clearTimeout(timeout);
|
|
258
|
+
settled = true;
|
|
259
|
+
const stdout = Buffer.concat(stdoutChunks).toString('utf8');
|
|
260
|
+
const stderr = Buffer.concat(stderrChunks).toString('utf8');
|
|
261
|
+
resolve({
|
|
262
|
+
args,
|
|
263
|
+
command,
|
|
264
|
+
exitCode: typeof exitCode === 'number' ? exitCode : timedOut ? 124 : 1,
|
|
265
|
+
signal,
|
|
206
266
|
stderr,
|
|
207
267
|
stdout,
|
|
268
|
+
timedOut,
|
|
208
269
|
});
|
|
209
270
|
});
|
|
210
271
|
});
|
|
@@ -396,32 +457,66 @@ async function executeProviderCommands({ args, layout, platform, runDir, runId,
|
|
|
396
457
|
? resolveProviderPath({ context, manifestDir, value: providerCommand.cwd })
|
|
397
458
|
: manifestDir;
|
|
398
459
|
const resolvedEnv = Object.fromEntries(Object.entries(providerCommand.env ?? {}).map(([key, value]) => [key, applyProviderPlaceholders(value, context)]));
|
|
460
|
+
const commandRecordFileName = `${providerId}-${providerCommand.id}.json`;
|
|
461
|
+
const stdoutFileName = `${providerId}-${providerCommand.id}.stdout.txt`;
|
|
462
|
+
const stderrFileName = `${providerId}-${providerCommand.id}.stderr.txt`;
|
|
463
|
+
const commandRecordPath = path.join(commandRecordDir, commandRecordFileName);
|
|
464
|
+
const stdoutPath = path.join(commandRecordDir, stdoutFileName);
|
|
465
|
+
const stderrPath = path.join(commandRecordDir, stderrFileName);
|
|
466
|
+
const timeoutMs = resolveProviderCommandTimeoutMs();
|
|
467
|
+
const startedAt = new Date().toISOString();
|
|
468
|
+
await fsp.writeFile(stdoutPath, '', 'utf8');
|
|
469
|
+
await fsp.writeFile(stderrPath, '', 'utf8');
|
|
470
|
+
await fsp.writeFile(commandRecordPath, `${JSON.stringify({
|
|
471
|
+
args: resolvedArgs,
|
|
472
|
+
command: resolvedCommand,
|
|
473
|
+
phase: providerCommand.phase,
|
|
474
|
+
providerId,
|
|
475
|
+
startedAt,
|
|
476
|
+
status: 'started',
|
|
477
|
+
stderrPath: `raw/provider-commands/${stderrFileName}`,
|
|
478
|
+
stdoutPath: `raw/provider-commands/${stdoutFileName}`,
|
|
479
|
+
timeoutMs,
|
|
480
|
+
}, null, 2)}\n`, 'utf8');
|
|
399
481
|
const commandResult = await execProviderCommand({
|
|
400
482
|
args: resolvedArgs,
|
|
401
483
|
command: resolvedCommand,
|
|
402
484
|
cwd: resolvedCwd,
|
|
403
485
|
env: resolvedEnv,
|
|
486
|
+
stderrPath,
|
|
487
|
+
stdoutPath,
|
|
488
|
+
timeoutMs,
|
|
404
489
|
});
|
|
405
|
-
const commandRecordFileName = `${providerId}-${providerCommand.id}.json`;
|
|
406
|
-
const commandRecordPath = path.join(commandRecordDir, commandRecordFileName);
|
|
407
490
|
await fsp.writeFile(commandRecordPath, `${JSON.stringify({
|
|
408
491
|
args: commandResult.args,
|
|
409
492
|
command: commandResult.command,
|
|
493
|
+
endedAt: new Date().toISOString(),
|
|
410
494
|
exitCode: commandResult.exitCode,
|
|
411
495
|
phase: providerCommand.phase,
|
|
412
496
|
providerId,
|
|
497
|
+
signal: commandResult.signal,
|
|
413
498
|
stderr: commandResult.stderr,
|
|
499
|
+
stderrPath: `raw/provider-commands/${stderrFileName}`,
|
|
500
|
+
status: commandResult.timedOut ? 'timed_out' : commandResult.exitCode === 0 ? 'completed' : 'failed',
|
|
414
501
|
stdout: commandResult.stdout,
|
|
502
|
+
stdoutPath: `raw/provider-commands/${stdoutFileName}`,
|
|
503
|
+
timedOut: commandResult.timedOut,
|
|
504
|
+
timeoutMs,
|
|
415
505
|
}, null, 2)}\n`, 'utf8');
|
|
416
506
|
if (commandResult.exitCode !== 0) {
|
|
507
|
+
const timedOut = commandResult.timedOut;
|
|
417
508
|
failures.push({
|
|
418
509
|
commandId: providerCommand.id,
|
|
419
|
-
code: 'provider_command_failed',
|
|
510
|
+
code: timedOut ? 'provider_liveness_timeout' : 'provider_command_failed',
|
|
420
511
|
exitCode: commandResult.exitCode,
|
|
421
|
-
message:
|
|
512
|
+
message: timedOut
|
|
513
|
+
? `Evidence provider command ${providerId}/${providerCommand.id} did not finish before the ${timeoutMs}ms timeout.`
|
|
514
|
+
: `Evidence provider command ${providerId}/${providerCommand.id} failed with exit code ${commandResult.exitCode}.`,
|
|
422
515
|
name: 'evidence_provider_command_completed',
|
|
423
|
-
nextAction:
|
|
424
|
-
|
|
516
|
+
nextAction: timedOut
|
|
517
|
+
? `Inspect raw/provider-commands/${commandRecordFileName}, raw/provider-commands/${stdoutFileName}, and raw/provider-commands/${stderrFileName}; fix the provider liveness issue or increase ASL_PROVIDER_COMMAND_TIMEOUT_MS only if the provider is making progress.`
|
|
518
|
+
: `Inspect raw/provider-commands/${commandRecordFileName}, fix the provider command or its environment, then rerun the profile.`,
|
|
519
|
+
nextActionCode: timedOut ? 'fix_provider_liveness' : 'fix_provider_command',
|
|
425
520
|
phase: providerCommand.phase,
|
|
426
521
|
providerId,
|
|
427
522
|
rawPath: `raw/provider-commands/${commandRecordFileName}`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-scenario-loop",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Scenario orchestration and evidence collection for agent-driven software development. Bring your own runner. Keep your scenarios. Keep your evidence.",
|
|
6
6
|
"license": "MIT",
|