@farmslot/agent-runtime 0.3.0 → 0.4.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 (40) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/bin/farmslot-agent.mjs +1 -1
  3. package/dist/execution-template/execution-template.test.js +278 -1
  4. package/dist/execution-template/execution-template.test.js.map +1 -1
  5. package/dist/execution-template/frontmatter.js +1 -1
  6. package/dist/execution-template/frontmatter.js.map +1 -1
  7. package/dist/execution-template/index.d.ts +8 -1
  8. package/dist/execution-template/index.d.ts.map +1 -1
  9. package/dist/execution-template/index.js +5 -0
  10. package/dist/execution-template/index.js.map +1 -1
  11. package/dist/execution-template/infer.d.ts.map +1 -1
  12. package/dist/execution-template/infer.js +12 -1
  13. package/dist/execution-template/infer.js.map +1 -1
  14. package/dist/execution-template/project-worker.d.ts +19 -0
  15. package/dist/execution-template/project-worker.d.ts.map +1 -0
  16. package/dist/execution-template/project-worker.js +143 -0
  17. package/dist/execution-template/project-worker.js.map +1 -0
  18. package/dist/execution-template/resolve.d.ts +1 -1
  19. package/dist/execution-template/resolve.d.ts.map +1 -1
  20. package/dist/execution-template/resolve.js +12 -2
  21. package/dist/execution-template/resolve.js.map +1 -1
  22. package/dist/execution-template/select.d.ts +8 -0
  23. package/dist/execution-template/select.d.ts.map +1 -0
  24. package/dist/execution-template/select.js +80 -0
  25. package/dist/execution-template/select.js.map +1 -0
  26. package/dist/execution-template/snapshot.d.ts +13 -0
  27. package/dist/execution-template/snapshot.d.ts.map +1 -0
  28. package/dist/execution-template/snapshot.js +46 -0
  29. package/dist/execution-template/snapshot.js.map +1 -0
  30. package/dist/execution-template/source-config.d.ts +19 -0
  31. package/dist/execution-template/source-config.d.ts.map +1 -0
  32. package/dist/execution-template/source-config.js +90 -0
  33. package/dist/execution-template/source-config.js.map +1 -0
  34. package/dist/execution-template/types.d.ts +35 -1
  35. package/dist/execution-template/types.d.ts.map +1 -1
  36. package/dist/execution-template/types.js +7 -2
  37. package/dist/execution-template/types.js.map +1 -1
  38. package/package.json +4 -3
  39. package/scripts/check-task-artifact-contract.mjs +25 -34
  40. package/scripts/execution-template-cli.mjs +150 -19
@@ -1,3 +1,8 @@
1
- /** Shared Markdown execution-template catalog (ADR-049). */
2
- export {};
1
+ export class ExecutionTemplateSelectionError extends Error {
2
+ constructor(code, message) {
3
+ super(message);
4
+ this.code = code;
5
+ this.name = 'ExecutionTemplateSelectionError';
6
+ }
7
+ }
3
8
  //# sourceMappingURL=types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/execution-template/types.ts"],"names":[],"mappings":"AAAA,4DAA4D"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/execution-template/types.ts"],"names":[],"mappings":"AAmGA,MAAM,OAAO,+BAAgC,SAAQ,KAAK;IACxD,YACkB,IAAyC,EACzD,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,SAAI,GAAJ,IAAI,CAAqC;QAIzD,IAAI,CAAC,IAAI,GAAG,iCAAiC,CAAC;IAChD,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farmslot/agent-runtime",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -16,7 +16,8 @@
16
16
  "./scripts/mark-checklist-step.cjs": "./scripts/mark-checklist-step.cjs",
17
17
  "./scripts/checklist-target.cjs": "./scripts/checklist-target.cjs",
18
18
  "./scripts/worker-terminal-contract.cjs": "./scripts/worker-terminal-contract.cjs",
19
- "./scripts/check-task-artifact-contract.mjs": "./scripts/check-task-artifact-contract.mjs"
19
+ "./scripts/check-task-artifact-contract.mjs": "./scripts/check-task-artifact-contract.mjs",
20
+ "./scripts/execution-template-cli.mjs": "./scripts/execution-template-cli.mjs"
20
21
  },
21
22
  "scripts": {
22
23
  "typecheck": "yarn workspace @farmslot/protocol build && tsc --noEmit --project tsconfig.json",
@@ -31,7 +32,7 @@
31
32
  "prepublishOnly": "node ../../scripts/quality/check-farmslot-package-readiness.mjs --publish --packages @farmslot/agent-runtime"
32
33
  },
33
34
  "dependencies": {
34
- "@farmslot/protocol": "0.11.1"
35
+ "@farmslot/protocol": "0.13.0"
35
36
  },
36
37
  "devDependencies": {
37
38
  "@types/node": "^22.0.0",
@@ -39,14 +39,14 @@ try {
39
39
  }
40
40
  }
41
41
  let recipeProtocol = null;
42
- try {
43
- recipeProtocol = await import('@farmslot/protocol/recipe');
44
- } catch (error) {
45
- if (!isMissingWorkspaceProtocolBuild(error, 'dist/recipe/index.js')) throw error;
46
- // Source checkouts can run before @farmslot/protocol has emitted dist/.
47
- // Keep strict local validation until the shared validator is available.
48
- if (process.env.FARMSLOT_DEBUG_AGENT_RUNTIME) {
49
- console.warn(`[agent-runtime] using local Recipe v1 fallback: ${error.message}`);
42
+ if (process.env.FARMSLOT_TEST_DISABLE_RECIPE_PROTOCOL !== '1') {
43
+ try {
44
+ recipeProtocol = await import('@farmslot/protocol/recipe');
45
+ } catch (error) {
46
+ if (!isMissingWorkspaceProtocolBuild(error, 'dist/recipe/index.js')) throw error;
47
+ if (process.env.FARMSLOT_DEBUG_AGENT_RUNTIME) {
48
+ console.warn(`[agent-runtime] shared Recipe v1 validator unavailable: ${error.message}`);
49
+ }
50
50
  }
51
51
  }
52
52
  if (recipeProtocol) {
@@ -325,6 +325,7 @@ function validateRecipeDocumentArtifact() {
325
325
  `${packageRoot}/artifact-manifest.json`,
326
326
  'recipe-run/artifact-manifest.json',
327
327
  );
328
+ const trace = readJsonArtifact(`${packageRoot}/trace.json`, 'recipe-run/trace.json');
328
329
  const recipeResolution = readJsonArtifact(
329
330
  `${packageRoot}/recipe-resolution.json`,
330
331
  'recipe-run/recipe-resolution.json',
@@ -339,34 +340,24 @@ function validateRecipeDocumentArtifact() {
339
340
  if (document !== undefined) resolvedRecipes[String(dependency.digest)] = document;
340
341
  }
341
342
  }
342
- if (sharedRecipeArtifactPackageValidator) {
343
- const result = sharedRecipeArtifactPackageValidator({
344
- recipe,
345
- manifest,
346
- recipeResolution,
347
- resolvedRecipes,
348
- artifactPaths: listArtifactPaths(path.join(taskDir, packageRoot)),
349
- });
350
- for (const finding of result.findings) {
351
- if (finding.severity === 'error') {
352
- issues.push(`${finding.code} ${finding.path}: ${finding.message}`);
353
- }
354
- }
343
+ if (!sharedRecipeArtifactPackageValidator) {
344
+ issues.push(
345
+ 'recipe-run package validation requires built @farmslot/protocol; build the protocol package and retry',
346
+ );
355
347
  return;
356
348
  }
357
- if (manifest === undefined) issues.push('artifact-manifest.json is missing');
358
- if (recipeResolution === undefined) issues.push('recipe-resolution.json is missing');
359
- const externalRecipeIds = new Set(
360
- isRecord(recipeResolution) && Array.isArray(recipeResolution.dependencies)
361
- ? recipeResolution.dependencies
362
- .filter(isRecord)
363
- .map((dependency) => dependency.ref)
364
- .filter((ref) => typeof ref === 'string')
365
- : [],
366
- );
367
- validateRecipeDocumentValue(recipe, 'recipe.json', { externalRecipeIds });
368
- for (const [digest, document] of Object.entries(resolvedRecipes)) {
369
- validateRecipeDocumentValue(document, `resolved recipe ${digest}`, { externalRecipeIds });
349
+ const result = sharedRecipeArtifactPackageValidator({
350
+ recipe,
351
+ manifest,
352
+ trace,
353
+ recipeResolution,
354
+ resolvedRecipes,
355
+ artifactPaths: listArtifactPaths(path.join(taskDir, packageRoot)),
356
+ });
357
+ for (const finding of result.findings) {
358
+ if (finding.severity === 'error') {
359
+ issues.push(`${finding.code} ${finding.path}: ${finding.message}`);
360
+ }
370
361
  }
371
362
  }
372
363
 
@@ -3,7 +3,7 @@
3
3
  * Standalone execution-template CLI for ADR-049.
4
4
  * Used by `farmslot-agent execution-template` and thin Consensys wrappers.
5
5
  */
6
- import { existsSync, statSync } from 'node:fs';
6
+ import { existsSync, mkdirSync, statSync, writeFileSync } from 'node:fs';
7
7
  import { dirname, resolve } from 'node:path';
8
8
  import { fileURLToPath, pathToFileURL } from 'node:url';
9
9
 
@@ -12,11 +12,13 @@ const distEntry = resolve(packageRoot, 'dist', 'index.js');
12
12
 
13
13
  function usage(exitCode = 0) {
14
14
  const text = [
15
- 'Usage: execution-template <list|lint|new> [options]',
15
+ 'Usage: execution-template <list|materialize|lint|new> [options]',
16
16
  '',
17
- 'list --dir <path> --project-worker <path> --package-templates <path>',
17
+ 'list --dir <path> --domain-dir <domain=path> --project-worker <path> --package-templates <path>',
18
18
  ' [--project-name name] [--package-id id] [--flow f] [--run-mode m]',
19
- ' [--platform p] [--no-include-shadowed] [--json]',
19
+ ' [--platform p] [--domain d] [--no-include-shadowed] [--json]',
20
+ 'materialize <output> --flow f --run-mode m --platform p [--domain d] [--id id]',
21
+ ' [the same source options as list] [--provenance path] [--json]',
20
22
  'lint <file-or-dir> [--json]',
21
23
  'new <path> [--flow f] [--run-mode m] [--platform p] [--title t] [--force] [--json]',
22
24
  ].join('\n');
@@ -44,9 +46,22 @@ function parseRunMode(value) {
44
46
  throw new Error(`--run-mode must be autonomous|interactive|validation (got ${value})`);
45
47
  }
46
48
 
47
- async function cmdList(args, runtime) {
49
+ function parseDomainDir(value) {
50
+ const separator = value.indexOf('=');
51
+ if (separator <= 0 || separator === value.length - 1) {
52
+ throw new Error('--domain-dir must use domain=path');
53
+ }
54
+ const domain = value.slice(0, separator);
55
+ if (!/^[a-z][a-z0-9-]*$/.test(domain)) {
56
+ throw new Error(`--domain-dir domain must be a lowercase slug (got ${domain})`);
57
+ }
58
+ return { domain, root: value.slice(separator + 1) };
59
+ }
60
+
61
+ function parseCatalogArgs(args, { materialize = false } = {}) {
48
62
  const opts = {
49
63
  dirs: [],
64
+ domainDirs: [],
50
65
  projectWorker: null,
51
66
  projectName: 'project',
52
67
  packageTemplates: null,
@@ -54,12 +69,18 @@ async function cmdList(args, runtime) {
54
69
  flow: undefined,
55
70
  runMode: undefined,
56
71
  platform: undefined,
72
+ domain: undefined,
73
+ id: undefined,
57
74
  includeShadowed: true,
75
+ output: null,
76
+ provenance: null,
58
77
  json: false,
59
78
  };
60
79
  for (let i = 0; i < args.length; i += 1) {
61
80
  const arg = args[i];
62
81
  if (arg === '--dir') opts.dirs.push(takeValue(args, i++, arg));
82
+ else if (arg === '--domain-dir')
83
+ opts.domainDirs.push(parseDomainDir(takeValue(args, i++, arg)));
63
84
  else if (arg === '--project-worker') opts.projectWorker = takeValue(args, i++, arg);
64
85
  else if (arg === '--project-name') opts.projectName = takeValue(args, i++, arg);
65
86
  else if (arg === '--package-templates') opts.packageTemplates = takeValue(args, i++, arg);
@@ -67,20 +88,51 @@ async function cmdList(args, runtime) {
67
88
  else if (arg === '--flow') opts.flow = takeValue(args, i++, arg);
68
89
  else if (arg === '--run-mode') opts.runMode = parseRunMode(takeValue(args, i++, arg));
69
90
  else if (arg === '--platform') opts.platform = takeValue(args, i++, arg);
91
+ else if (arg === '--domain') opts.domain = takeValue(args, i++, arg);
92
+ else if (arg === '--id') opts.id = takeValue(args, i++, arg);
93
+ else if (arg === '--provenance' && materialize) opts.provenance = takeValue(args, i++, arg);
70
94
  else if (arg === '--include-shadowed') opts.includeShadowed = true;
71
95
  else if (arg === '--no-include-shadowed') opts.includeShadowed = false;
72
96
  else if (arg === '--json') opts.json = true;
73
97
  else if (arg === '-h' || arg === '--help') usage(0);
98
+ else if (materialize && !arg.startsWith('-') && !opts.output) opts.output = arg;
74
99
  else throw new Error(`unknown option ${arg}`);
75
100
  }
101
+ return opts;
102
+ }
76
103
 
104
+ function buildSources(opts, runtime) {
77
105
  const sources = [];
106
+ const domainSourceIds = new Set();
78
107
  for (const [index, dir] of opts.dirs.entries()) {
79
108
  const root = resolve(dir);
80
109
  if (!existsSync(root) || !statSync(root).isDirectory()) {
81
110
  throw new Error(`--dir is not a directory: ${root}`);
82
111
  }
83
- sources.push(runtime.customTemplateSource(`dir-${index + 1}`, root, 'flow-tree'));
112
+ const source = runtime.customTemplateSource(`dir-${index + 1}`, root, 'flow-tree');
113
+ source.sourceRevision = runtime.executionTemplateSourceRevision(root);
114
+ source.sourceDirty = runtime.executionTemplateSourceDirty(root);
115
+ sources.push(source);
116
+ }
117
+ for (const item of opts.domainDirs) {
118
+ const root = resolve(item.root);
119
+ if (!existsSync(root) || !statSync(root).isDirectory()) {
120
+ throw new Error(`--domain-dir is not a directory: ${root}`);
121
+ }
122
+ const sourceId = `team:${item.domain}`;
123
+ if (domainSourceIds.has(sourceId)) {
124
+ throw new Error(`--domain-dir repeats domain ${item.domain}`);
125
+ }
126
+ domainSourceIds.add(sourceId);
127
+ sources.push({
128
+ id: sourceId,
129
+ kind: 'workspace',
130
+ root,
131
+ layout: 'flow-tree',
132
+ domains: [item.domain],
133
+ sourceRevision: runtime.executionTemplateSourceRevision(root),
134
+ sourceDirty: runtime.executionTemplateSourceDirty(root),
135
+ });
84
136
  }
85
137
  if (opts.projectWorker) {
86
138
  const input = resolve(opts.projectWorker);
@@ -92,34 +144,112 @@ async function cmdList(args, runtime) {
92
144
  if (!existsSync(workerRoot) || !statSync(workerRoot).isDirectory()) {
93
145
  throw new Error(`--project-worker is not a directory: ${workerRoot}`);
94
146
  }
95
- sources.push(runtime.projectWorkerTemplateSource(opts.projectName, projectTemplatesDir));
147
+ sources.push(
148
+ runtime.projectWorkerTemplateSource(
149
+ opts.projectName,
150
+ projectTemplatesDir,
151
+ runtime.executionTemplateSourceRevision(projectTemplatesDir),
152
+ runtime.executionTemplateSourceDirty(projectTemplatesDir),
153
+ ),
154
+ );
96
155
  }
97
156
  if (opts.packageTemplates) {
98
157
  const root = resolve(opts.packageTemplates);
99
158
  if (!existsSync(root) || !statSync(root).isDirectory()) {
100
159
  throw new Error(`--package-templates is not a directory: ${root}`);
101
160
  }
102
- sources.push(runtime.packageFlowTreeTemplateSource(opts.packageId, root));
161
+ const source = runtime.packageFlowTreeTemplateSource(opts.packageId, root);
162
+ source.sourceRevision = runtime.executionTemplateSourceRevision(root);
163
+ source.sourceDirty = runtime.executionTemplateSourceDirty(root);
164
+ sources.push(source);
103
165
  }
104
166
  if (sources.length === 0) {
105
- throw new Error('provide --dir, --project-worker, and/or --package-templates');
167
+ throw new Error('provide --dir, --domain-dir, --project-worker, and/or --package-templates');
106
168
  }
169
+ return sources;
170
+ }
107
171
 
108
- const templates = runtime.listExecutionTemplates({
109
- sources,
110
- flow: opts.flow,
111
- runMode: opts.runMode,
112
- platform: opts.platform,
113
- includeShadowed: opts.includeShadowed,
114
- });
172
+ async function cmdList(args, runtime) {
173
+ const opts = parseCatalogArgs(args);
174
+ if (opts.id) throw new Error('--id is only valid with materialize');
175
+ const sources = buildSources(opts, runtime);
176
+ const templates =
177
+ opts.flow && opts.platform && opts.runMode
178
+ ? runtime.listCompatibleExecutionTemplates({
179
+ sources,
180
+ flow: opts.flow,
181
+ platform: opts.platform,
182
+ runMode: opts.runMode,
183
+ ...(opts.domain ? { domain: opts.domain } : {}),
184
+ })
185
+ : runtime.listExecutionTemplates({
186
+ sources,
187
+ flow: opts.flow,
188
+ runMode: opts.runMode,
189
+ platform: opts.platform,
190
+ domain: opts.domain,
191
+ includeShadowed: opts.includeShadowed,
192
+ });
193
+ const catalog = templates.map((entry) => ({
194
+ ...runtime.executionTemplateReference(entry),
195
+ title: entry.title,
196
+ sourceKind: entry.sourceKind,
197
+ ...(entry.shadowedBy ? { shadowedBy: entry.shadowedBy } : {}),
198
+ }));
115
199
  if (opts.json) {
116
- process.stdout.write(`${JSON.stringify({ templates }, null, 2)}\n`);
200
+ process.stdout.write(`${JSON.stringify({ templates: catalog }, null, 2)}\n`);
117
201
  return;
118
202
  }
119
- for (const entry of templates) {
203
+ for (const entry of catalog) {
120
204
  const shadow = entry.shadowedBy ? ` (shadowed by ${entry.shadowedBy})` : '';
121
205
  process.stdout.write(
122
- `${entry.id}\t${entry.flow}\t${entry.runMode ?? '-'}\t${entry.platforms.join(',')}\t${entry.sourceId}\t${entry.path}${shadow}\n`,
206
+ `${entry.id}\t${entry.flow}\t${entry.runMode ?? '-'}\t${entry.platforms.join(',')}\t${entry.sourceId}${shadow}\n`,
207
+ );
208
+ }
209
+ }
210
+
211
+ async function cmdMaterialize(args, runtime) {
212
+ const opts = parseCatalogArgs(args, { materialize: true });
213
+ if (!opts.output) throw new Error('materialize requires <output>');
214
+ if (!opts.flow) throw new Error('materialize requires --flow');
215
+ if (!opts.runMode) throw new Error('materialize requires --run-mode');
216
+ if (!opts.platform) throw new Error('materialize requires --platform');
217
+ const output = resolve(opts.output);
218
+ if (existsSync(output)) {
219
+ throw new Error(`Execution-template destination already exists: ${output}`);
220
+ }
221
+ if (opts.provenance && existsSync(resolve(opts.provenance))) {
222
+ throw new Error(
223
+ `Execution-template provenance destination already exists: ${resolve(opts.provenance)}`,
224
+ );
225
+ }
226
+ const selected = runtime.selectExecutionTemplate({
227
+ sources: buildSources(opts, runtime),
228
+ flow: opts.flow,
229
+ platform: opts.platform,
230
+ runMode: opts.runMode,
231
+ ...(opts.domain ? { domain: opts.domain } : {}),
232
+ ...(opts.id ? { explicitId: opts.id } : {}),
233
+ });
234
+ const materialized = runtime.materializeExecutionTemplate(selected.entry, output);
235
+ const provenance = {
236
+ schemaVersion: 1,
237
+ selectionReason: selected.reason,
238
+ executionTemplate: materialized.reference,
239
+ };
240
+ if (opts.provenance) {
241
+ const destination = resolve(opts.provenance);
242
+ mkdirSync(dirname(destination), { recursive: true });
243
+ writeFileSync(destination, `${JSON.stringify(provenance, null, 2)}\n`, {
244
+ encoding: 'utf8',
245
+ flag: 'wx',
246
+ });
247
+ }
248
+ const result = { ...materialized, provenance };
249
+ if (opts.json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
250
+ else {
251
+ process.stdout.write(
252
+ `materialized ${selected.entry.id} from ${selected.entry.sourceId} -> ${materialized.path}\n`,
123
253
  );
124
254
  }
125
255
  }
@@ -192,6 +322,7 @@ async function main() {
192
322
  if (!command || command === '-h' || command === '--help') usage(0);
193
323
  const runtime = await loadRuntime();
194
324
  if (command === 'list') await cmdList(rest, runtime);
325
+ else if (command === 'materialize') await cmdMaterialize(rest, runtime);
195
326
  else if (command === 'lint') await cmdLint(rest, runtime);
196
327
  else if (command === 'new') await cmdNew(rest, runtime);
197
328
  else usage(2);