agent-scenario-loop 0.1.2 → 0.1.4

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 (87) hide show
  1. package/README.md +9 -9
  2. package/app/profile-session.ts +352 -12
  3. package/dist/core/agent-summary.d.ts +3 -2
  4. package/dist/core/agent-summary.js +44 -2
  5. package/dist/core/artifact-contract.d.ts +28 -8
  6. package/dist/core/artifact-contract.js +676 -26
  7. package/dist/core/comparison.d.ts +57 -3
  8. package/dist/core/comparison.js +113 -1
  9. package/dist/core/planner.d.ts +32 -1
  10. package/dist/core/planner.js +144 -0
  11. package/dist/core/run-index.d.ts +4 -0
  12. package/dist/core/run-index.js +55 -1
  13. package/dist/core/schema-validator.d.ts +2 -0
  14. package/dist/core/schema-validator.js +2 -0
  15. package/dist/runner/android-adb-driver.d.ts +7 -2
  16. package/dist/runner/android-adb-driver.js +7 -1
  17. package/dist/runner/android-adb.d.ts +40 -5
  18. package/dist/runner/android-adb.js +1046 -664
  19. package/dist/runner/compare-latest.d.ts +8 -4
  20. package/dist/runner/compare-latest.js +24 -5
  21. package/dist/runner/example-android-live.d.ts +10 -1
  22. package/dist/runner/example-android-live.js +55 -0
  23. package/dist/runner/example-ios-live.d.ts +10 -1
  24. package/dist/runner/example-ios-live.js +55 -0
  25. package/dist/runner/ios-simctl.d.ts +6 -0
  26. package/dist/runner/ios-simctl.js +7 -0
  27. package/dist/runner/live-comparison.d.ts +2 -2
  28. package/dist/runner/live-comparison.js +2 -1
  29. package/dist/runner/live-proof-summary.d.ts +5 -4
  30. package/dist/runner/live-proof-summary.js +12 -2
  31. package/dist/runner/live-proof.d.ts +3 -2
  32. package/dist/runner/live-proof.js +9 -2
  33. package/dist/runner/profile-android.d.ts +16 -1
  34. package/dist/runner/profile-android.js +364 -26
  35. package/dist/runner/profile-ios.d.ts +13 -2
  36. package/dist/runner/profile-ios.js +341 -19
  37. package/dist/runner/profile-mobile.d.ts +39 -3
  38. package/dist/runner/profile-mobile.js +1054 -42
  39. package/dist/runner/validate-project.js +3 -0
  40. package/dist/scripts/consumer-rehearsal.d.ts +119 -0
  41. package/dist/scripts/consumer-rehearsal.js +757 -0
  42. package/dist/scripts/downstream-local-package-gate.d.ts +2 -0
  43. package/dist/scripts/downstream-local-package-gate.js +264 -0
  44. package/dist/scripts/package-smoke.d.ts +96 -0
  45. package/dist/scripts/package-smoke.js +2282 -0
  46. package/dist/scripts/release-readiness.d.ts +2 -0
  47. package/dist/scripts/release-readiness.js +520 -0
  48. package/docs/adapters.md +7 -1
  49. package/docs/api.md +2 -2
  50. package/docs/architecture.md +90 -0
  51. package/docs/authoring.md +39 -3
  52. package/docs/concepts.md +3 -24
  53. package/docs/consumer-rehearsal.md +31 -1
  54. package/docs/contracts.md +45 -101
  55. package/docs/external-adapter-protocol.md +219 -0
  56. package/docs/live-proofs.md +86 -3
  57. package/docs/principles.md +9 -15
  58. package/examples/mobile-app/README.md +12 -0
  59. package/examples/mobile-app/runner-manifests/evidence-provider.json +3 -3
  60. package/examples/mobile-app/runner-manifests/primary-runner.json +1 -0
  61. package/examples/mobile-app/scripts/asl-capture-profiler-provider.mjs +25 -0
  62. package/examples/runners/README.md +4 -3
  63. package/examples/runners/adb-android.json +1 -0
  64. package/examples/runners/agent-device-android.json +1 -0
  65. package/examples/runners/agent-device-ios.json +1 -0
  66. package/examples/runners/argent-android.json +1 -0
  67. package/examples/runners/argent-ios.json +1 -0
  68. package/examples/runners/axe-accessibility-provider.json +2 -2
  69. package/examples/runners/script-accessibility-provider.json +2 -2
  70. package/examples/runners/script-memory-provider.json +2 -2
  71. package/examples/runners/script-network-provider.json +2 -2
  72. package/examples/runners/script-profiler-provider.json +2 -2
  73. package/examples/runners/xcodebuildmcp-ios.json +1 -0
  74. package/package.json +12 -3
  75. package/schemas/causal-run.schema.json +85 -2
  76. package/schemas/comparison.schema.json +130 -2
  77. package/schemas/external-adapter-message.schema.json +693 -0
  78. package/schemas/health.schema.json +72 -0
  79. package/schemas/live-proof-set.schema.json +1 -1
  80. package/schemas/live-proof.schema.json +14 -6
  81. package/schemas/manifest.schema.json +515 -4
  82. package/schemas/profiler.schema.json +243 -0
  83. package/schemas/runner-capabilities.schema.json +28 -2
  84. package/schemas/scenario.schema.json +34 -2
  85. package/templates/evidence-provider.json +3 -3
  86. package/templates/primary-runner.json +1 -0
  87. package/templates/scripts/asl-capture-profiler-provider.mjs +20 -0
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,264 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const assert = require('node:assert/strict');
5
+ const fs = require('node:fs');
6
+ const os = require('node:os');
7
+ const path = require('node:path');
8
+ const { execFileSync } = require('node:child_process');
9
+ const DEFAULT_TIMEOUT_MS = 300_000;
10
+ const RESTORE_FILES = ['package.json', 'pnpm-lock.yaml'];
11
+ /**
12
+ * Returns the per-command timeout for downstream release gates.
13
+ *
14
+ * @param {NodeJS.ProcessEnv} env
15
+ * @returns {number}
16
+ */
17
+ function resolveTimeoutMs(env) {
18
+ const timeoutMs = Number.parseInt(env.ASL_DOWNSTREAM_GATE_TIMEOUT_MS ?? '', 10);
19
+ return Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_TIMEOUT_MS;
20
+ }
21
+ /**
22
+ * Runs one command with inherited output so downstream gates preserve evidence.
23
+ *
24
+ * @param {string} command
25
+ * @param {string[]} args
26
+ * @param {RunOptions} options
27
+ * @returns {string}
28
+ */
29
+ function run(command, args, options) {
30
+ return execFileSync(command, args, {
31
+ cwd: options.cwd,
32
+ encoding: 'utf8',
33
+ env: options.env,
34
+ stdio: ['ignore', 'pipe', 'inherit'],
35
+ timeout: resolveTimeoutMs(options.env),
36
+ });
37
+ }
38
+ /**
39
+ * Runs one command while streaming its stdout and stderr.
40
+ *
41
+ * @param {string[]} argv
42
+ * @param {RunOptions} options
43
+ * @returns {void}
44
+ */
45
+ function runGateCommand(argv, options) {
46
+ assert.ok(argv.length > 0, 'downstream command arrays must not be empty');
47
+ execFileSync(argv[0], argv.slice(1), {
48
+ cwd: options.cwd,
49
+ env: options.env,
50
+ stdio: 'inherit',
51
+ timeout: resolveTimeoutMs(options.env),
52
+ });
53
+ }
54
+ /**
55
+ * Parses CLI options.
56
+ *
57
+ * @param {string[]} argv
58
+ * @returns {CliOptions}
59
+ */
60
+ function parseArgs(argv) {
61
+ const commands = [];
62
+ let appRoot = process.env.ASL_DOWNSTREAM_APP_ROOT ?? '';
63
+ let expectedBranch = process.env.ASL_DOWNSTREAM_EXPECTED_BRANCH;
64
+ let keepInstall = process.env.ASL_DOWNSTREAM_KEEP_INSTALL === '1';
65
+ let packDir = process.env.ASL_DOWNSTREAM_PACK_DIR;
66
+ for (let index = 0; index < argv.length; index += 1) {
67
+ const arg = argv[index];
68
+ if (arg === '--') {
69
+ continue;
70
+ }
71
+ else if (arg === '--app-root') {
72
+ appRoot = argv[index + 1] ?? '';
73
+ index += 1;
74
+ }
75
+ else if (arg === '--expected-branch') {
76
+ expectedBranch = argv[index + 1] ?? '';
77
+ index += 1;
78
+ }
79
+ else if (arg === '--command-json') {
80
+ const parsed = JSON.parse(argv[index + 1] ?? 'null');
81
+ assert.ok(Array.isArray(parsed) && parsed.every((entry) => typeof entry === 'string'), '--command-json must be a JSON string array');
82
+ commands.push(parsed);
83
+ index += 1;
84
+ }
85
+ else if (arg === '--commands-json') {
86
+ const parsed = JSON.parse(argv[index + 1] ?? 'null');
87
+ assert.ok(Array.isArray(parsed) &&
88
+ parsed.every((entry) => Array.isArray(entry) && entry.every((part) => typeof part === 'string')), '--commands-json must be a JSON array of string arrays');
89
+ commands.push(...parsed);
90
+ index += 1;
91
+ }
92
+ else if (arg === '--keep-install') {
93
+ keepInstall = true;
94
+ }
95
+ else if (arg === '--pack-dir') {
96
+ packDir = argv[index + 1] ?? '';
97
+ index += 1;
98
+ }
99
+ else if (arg === '--help') {
100
+ printUsage();
101
+ process.exit(0);
102
+ }
103
+ else {
104
+ throw new Error(`Unknown argument: ${arg}`);
105
+ }
106
+ }
107
+ if (process.env.ASL_DOWNSTREAM_COMMANDS_JSON) {
108
+ const parsed = JSON.parse(process.env.ASL_DOWNSTREAM_COMMANDS_JSON);
109
+ assert.ok(Array.isArray(parsed) &&
110
+ parsed.every((entry) => Array.isArray(entry) && entry.every((part) => typeof part === 'string')), 'ASL_DOWNSTREAM_COMMANDS_JSON must be a JSON array of string arrays');
111
+ commands.push(...parsed);
112
+ }
113
+ assert.ok(appRoot, 'Provide --app-root or ASL_DOWNSTREAM_APP_ROOT.');
114
+ assert.ok(commands.length > 0, 'Provide at least one --command-json, --commands-json, or ASL_DOWNSTREAM_COMMANDS_JSON entry.');
115
+ return {
116
+ appRoot: path.resolve(appRoot),
117
+ commands,
118
+ ...(expectedBranch ? { expectedBranch } : {}),
119
+ keepInstall,
120
+ ...(packDir ? { packDir: path.resolve(packDir) } : {}),
121
+ };
122
+ }
123
+ function printUsage() {
124
+ process.stdout.write([
125
+ 'Usage: pnpm downstream:local-package -- --app-root <path> --command-json \'["pnpm","run","asl:validate"]\'',
126
+ '',
127
+ 'Options:',
128
+ ' --app-root <path> Downstream app worktree to validate.',
129
+ ' --expected-branch <branch> Fail if the downstream worktree is on another branch.',
130
+ ' --command-json <json-array> Command argv to run after installing the local tarball. Repeatable.',
131
+ ' --commands-json <json-array> JSON array of command argv arrays.',
132
+ ' --keep-install Leave package.json/pnpm-lock.yaml as modified validation state.',
133
+ ' --pack-dir <path> Directory for npm pack output. Defaults to a temp directory.',
134
+ '',
135
+ ].join('\n'));
136
+ }
137
+ /**
138
+ * Packs this repository and returns the tarball path.
139
+ *
140
+ * @param {{env: NodeJS.ProcessEnv; packageRoot: string; packDir: string}} options
141
+ * @returns {string}
142
+ */
143
+ function packLocalPackage({ env, packageRoot, packDir, }) {
144
+ fs.mkdirSync(packDir, { recursive: true });
145
+ const npmStateDir = path.join(packDir, '.npm');
146
+ fs.mkdirSync(npmStateDir, { recursive: true });
147
+ const packOutput = run('npm', ['pack', '--pack-destination', packDir], {
148
+ cwd: packageRoot,
149
+ env: {
150
+ ...env,
151
+ npm_config_cache: path.join(npmStateDir, 'cache'),
152
+ npm_config_logs_dir: path.join(npmStateDir, 'logs'),
153
+ },
154
+ });
155
+ const tarballName = packOutput.trim().split(/\n/u).pop();
156
+ assert.ok(tarballName, 'npm pack did not print a tarball name');
157
+ const tarballPath = path.join(packDir, tarballName);
158
+ assert.equal(fs.existsSync(tarballPath), true, `missing packed tarball: ${tarballPath}`);
159
+ return tarballPath;
160
+ }
161
+ /**
162
+ * Reads the current git branch for a worktree.
163
+ *
164
+ * @param {string} appRoot
165
+ * @param {NodeJS.ProcessEnv} env
166
+ * @returns {string}
167
+ */
168
+ function readGitBranch(appRoot, env) {
169
+ return run('git', ['branch', '--show-current'], { cwd: appRoot, env }).trim();
170
+ }
171
+ /**
172
+ * Fails when dependency files already have local changes.
173
+ *
174
+ * @param {string} appRoot
175
+ * @param {NodeJS.ProcessEnv} env
176
+ * @returns {void}
177
+ */
178
+ function assertDependencyFilesClean(appRoot, env) {
179
+ const status = run('git', ['status', '--porcelain', '--', ...RESTORE_FILES], {
180
+ cwd: appRoot,
181
+ env,
182
+ }).trim();
183
+ assert.equal(status, '', `downstream dependency files are already dirty; preserve or commit them before running this gate:\n${status}`);
184
+ }
185
+ /**
186
+ * Captures dependency files so the temporary tarball install can be restored.
187
+ *
188
+ * @param {string} appRoot
189
+ * @returns {Map<string, string | null>}
190
+ */
191
+ function snapshotRestoreFiles(appRoot) {
192
+ const snapshots = new Map();
193
+ for (const relativePath of RESTORE_FILES) {
194
+ const filePath = path.join(appRoot, relativePath);
195
+ snapshots.set(relativePath, fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : null);
196
+ }
197
+ return snapshots;
198
+ }
199
+ /**
200
+ * Restores dependency files after a temporary tarball install.
201
+ *
202
+ * @param {string} appRoot
203
+ * @param {Map<string, string | null>} snapshots
204
+ * @returns {void}
205
+ */
206
+ function restoreFiles(appRoot, snapshots) {
207
+ for (const [relativePath, content] of snapshots.entries()) {
208
+ const filePath = path.join(appRoot, relativePath);
209
+ if (content === null) {
210
+ fs.rmSync(filePath, { force: true });
211
+ }
212
+ else {
213
+ fs.writeFileSync(filePath, content, 'utf8');
214
+ }
215
+ }
216
+ }
217
+ /**
218
+ * Runs the downstream local-package gate.
219
+ *
220
+ * @returns {void}
221
+ */
222
+ function main() {
223
+ const repoRoot = path.resolve(__dirname, '..', '..');
224
+ const options = parseArgs(process.argv.slice(2));
225
+ const env = {
226
+ ...process.env,
227
+ npm_config_audit: 'false',
228
+ npm_config_fund: 'false',
229
+ npm_config_update_notifier: 'false',
230
+ };
231
+ assert.equal(fs.existsSync(path.join(options.appRoot, 'package.json')), true, `missing downstream package.json at ${options.appRoot}`);
232
+ if (options.expectedBranch) {
233
+ assert.equal(readGitBranch(options.appRoot, env), options.expectedBranch);
234
+ }
235
+ assertDependencyFilesClean(options.appRoot, env);
236
+ const packageJson = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
237
+ const packDir = options.packDir ?? fs.mkdtempSync(path.join(os.tmpdir(), 'asl-downstream-pack-'));
238
+ const tarballPath = packLocalPackage({ env, packageRoot: repoRoot, packDir });
239
+ const snapshots = snapshotRestoreFiles(options.appRoot);
240
+ try {
241
+ runGateCommand(['pnpm', 'add', tarballPath], { cwd: options.appRoot, env });
242
+ const installedPackageJsonPath = path.join(options.appRoot, 'node_modules', 'agent-scenario-loop', 'package.json');
243
+ assert.equal(fs.existsSync(installedPackageJsonPath), true, 'local package install did not create node_modules/agent-scenario-loop/package.json');
244
+ const installedPackageJson = JSON.parse(fs.readFileSync(installedPackageJsonPath, 'utf8'));
245
+ assert.equal(installedPackageJson.name, 'agent-scenario-loop');
246
+ assert.equal(installedPackageJson.version, packageJson.version);
247
+ for (const command of options.commands) {
248
+ runGateCommand(command, { cwd: options.appRoot, env });
249
+ }
250
+ process.stdout.write([
251
+ `downstream local-package gate passed: ${options.appRoot}`,
252
+ `candidate tarball: ${tarballPath}`,
253
+ `installed version: ${installedPackageJson.version}`,
254
+ '',
255
+ ].join('\n'));
256
+ }
257
+ finally {
258
+ if (!options.keepInstall) {
259
+ restoreFiles(options.appRoot, snapshots);
260
+ process.stdout.write('downstream dependency files restored; run the app package manager if node_modules needs a clean registry install.\n');
261
+ }
262
+ }
263
+ }
264
+ main();
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+ type RunOptions = {
3
+ cwd: string;
4
+ env: NodeJS.ProcessEnv;
5
+ };
6
+ type FailedRunOutput = {
7
+ status: number | null;
8
+ stderr: string;
9
+ stdout: string;
10
+ };
11
+ type SmokeRunOptions = {
12
+ actual: number;
13
+ endedAt: string;
14
+ root: string;
15
+ runId: string;
16
+ };
17
+ /**
18
+ * Lists every file under a directory using relative POSIX-style paths.
19
+ *
20
+ * @param {string} rootDir
21
+ * @param {string} [relativeDir]
22
+ * @returns {string[]}
23
+ */
24
+ declare function listFiles(rootDir: string, relativeDir?: string): string[];
25
+ /**
26
+ * Returns whether a packed file belongs to the intentional public package surface.
27
+ *
28
+ * @param {string} filePath
29
+ * @returns {boolean}
30
+ */
31
+ declare function isAllowedPackedFile(filePath: string): boolean;
32
+ /**
33
+ * Creates a clean npm environment for repeatable package smoke checks.
34
+ *
35
+ * @param {string} tempRoot
36
+ * @returns {NodeJS.ProcessEnv}
37
+ */
38
+ declare function createSmokeEnv(tempRoot: string): NodeJS.ProcessEnv;
39
+ /**
40
+ * Runs a command and returns stdout while preserving stderr for failures.
41
+ *
42
+ * @param {string} command
43
+ * @param {string[]} args
44
+ * @param {RunOptions} options
45
+ * @returns {string}
46
+ */
47
+ declare function run(command: string, args: string[], options: RunOptions): string;
48
+ /**
49
+ * Runs a command that is expected to fail and returns captured output.
50
+ *
51
+ * @param {string} command
52
+ * @param {string[]} args
53
+ * @param {RunOptions} options
54
+ * @returns {FailedRunOutput}
55
+ */
56
+ declare function runExpectFailure(command: string, args: string[], options: RunOptions): FailedRunOutput;
57
+ /**
58
+ * Writes a tiny adb-compatible command for installed-package smoke tests.
59
+ *
60
+ * @param {{filePath: string, logcatText: string, packageName: string}} options
61
+ * @returns {void}
62
+ */
63
+ declare function writeFakeAdb({ filePath, logcatText, packageName, }: {
64
+ filePath: string;
65
+ logcatText: string;
66
+ packageName: string;
67
+ }): void;
68
+ /**
69
+ * Writes a minimal passed run directory for installed-package comparison smoke tests.
70
+ *
71
+ * @param {SmokeRunOptions} options
72
+ * @returns {string}
73
+ */
74
+ declare function writeSmokeRun({ actual, endedAt, root, runId, }: SmokeRunOptions): string;
75
+ /**
76
+ * Returns the platform-specific path for a package binary in a temp install.
77
+ *
78
+ * @param {string} installDir
79
+ * @param {string} name
80
+ * @returns {string}
81
+ */
82
+ declare function packageBinPath(installDir: string, name: string): string;
83
+ /**
84
+ * Returns the platform-specific path for the repo-local TypeScript compiler.
85
+ *
86
+ * @param {string} repoRoot
87
+ * @returns {string}
88
+ */
89
+ declare function typescriptBinPath(repoRoot: string): string;
90
+ /**
91
+ * Packs, installs, and exercises the public package surface from a temp project.
92
+ *
93
+ * @returns {void}
94
+ */
95
+ declare function main(): void;
96
+ export { createSmokeEnv, listFiles, main, isAllowedPackedFile, packageBinPath, run, runExpectFailure, typescriptBinPath, writeFakeAdb, writeSmokeRun, };