@xemahq/dsl 0.8.0 → 0.8.2

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 (64) hide show
  1. package/dist/payload-codec/lib/blob-store.js +0 -0
  2. package/dist/payload-codec/lib/blob-store.js.map +1 -1
  3. package/package.json +3 -4
  4. package/src/deliverable-spec/index.ts +0 -19
  5. package/src/deliverable-spec/lib/schema.ts +0 -270
  6. package/src/deliverable-spec/lib/types.ts +0 -26
  7. package/src/payload-codec/index.ts +0 -44
  8. package/src/payload-codec/lib/blob-store.ts +0 -0
  9. package/src/payload-codec/lib/codec-context.ts +0 -38
  10. package/src/payload-codec/lib/codec.ts +0 -605
  11. package/src/payload-codec/lib/enums.ts +0 -58
  12. package/src/payload-codec/lib/errors.ts +0 -54
  13. package/src/payload-codec/lib/http-blob-store.ts +0 -267
  14. package/src/payload-codec/lib/lru-cache.ts +0 -81
  15. package/src/payload-codec/lib/payload.ts +0 -26
  16. package/src/payload-codec/temporal/index.ts +0 -36
  17. package/src/workflow/index.ts +0 -108
  18. package/src/workflow/lib/action-input-validator.ts +0 -160
  19. package/src/workflow/lib/compiler/action-shape.ts +0 -71
  20. package/src/workflow/lib/compiler/canonical-json.ts +0 -66
  21. package/src/workflow/lib/compiler/compile.ts +0 -1742
  22. package/src/workflow/lib/compiler/concurrency.ts +0 -223
  23. package/src/workflow/lib/compiler/dag.ts +0 -108
  24. package/src/workflow/lib/compiler/gate-defaults.ts +0 -153
  25. package/src/workflow/lib/compiler/index.ts +0 -11
  26. package/src/workflow/lib/compiler/inputs.ts +0 -254
  27. package/src/workflow/lib/compiler/installation-resource-validator.ts +0 -114
  28. package/src/workflow/lib/compiler/manifest-source.ts +0 -71
  29. package/src/workflow/lib/compiler/matrix.ts +0 -135
  30. package/src/workflow/lib/compiler/mount-plan.ts +0 -190
  31. package/src/workflow/lib/compiler/payload-reach-in.ts +0 -497
  32. package/src/workflow/lib/compiler/permissions.ts +0 -64
  33. package/src/workflow/lib/compiler/retry-timeout.ts +0 -105
  34. package/src/workflow/lib/compiler/review-step.ts +0 -548
  35. package/src/workflow/lib/compiler/types.ts +0 -172
  36. package/src/workflow/lib/compiler/variable-requirements.ts +0 -208
  37. package/src/workflow/lib/deliverable-spec-introspection-error.ts +0 -63
  38. package/src/workflow/lib/deliverable-spec-keys.ts +0 -147
  39. package/src/workflow/lib/deliverable-spec-source-scan.ts +0 -280
  40. package/src/workflow/lib/dispatch-inputs/index.ts +0 -160
  41. package/src/workflow/lib/dispatch-inputs/to-json-schema.ts +0 -60
  42. package/src/workflow/lib/duration.ts +0 -43
  43. package/src/workflow/lib/errors.ts +0 -37
  44. package/src/workflow/lib/expression/ast.ts +0 -108
  45. package/src/workflow/lib/expression/context.ts +0 -148
  46. package/src/workflow/lib/expression/evaluator.ts +0 -492
  47. package/src/workflow/lib/expression/index.ts +0 -28
  48. package/src/workflow/lib/expression/interpolation.ts +0 -84
  49. package/src/workflow/lib/expression/parser.ts +0 -264
  50. package/src/workflow/lib/expression/template.ts +0 -117
  51. package/src/workflow/lib/expression/tokenizer.ts +0 -200
  52. package/src/workflow/lib/expression/tokens.ts +0 -30
  53. package/src/workflow/lib/expression/walk-artifact-refs.ts +0 -232
  54. package/src/workflow/lib/installation-resource-kind.ts +0 -107
  55. package/src/workflow/lib/schemas-loader.ts +0 -64
  56. package/src/workflow/lib/serializer.ts +0 -30
  57. package/src/workflow/lib/types.ts +0 -417
  58. package/src/workflow/lib/validate.ts +0 -199
  59. package/src/workspace-manifest/index.ts +0 -27
  60. package/src/workspace-manifest/lib/compile.ts +0 -619
  61. package/src/workspace-manifest/lib/interpolate.ts +0 -166
  62. package/src/workspace-manifest/lib/resolve-extends.ts +0 -260
  63. package/src/workspace-manifest/lib/schema.ts +0 -692
  64. package/src/workspace-manifest/lib/types.ts +0 -446
@@ -1,619 +0,0 @@
1
- import yaml from 'js-yaml';
2
- import { ZodError } from 'zod';
3
-
4
- import {
5
- AWP_V1_SPEC,
6
- findSlot,
7
- isKnownSlotKey,
8
- isWorkingFileBinaryFormat,
9
- WORKING_FILE_BINARY_FORMATS,
10
- } from '@xemahq/kernel-contracts/agent-workspace';
11
- import {
12
- ManifestSurface,
13
- OutputSurfaceKind,
14
- type CompiledManifestCredential,
15
- type CompiledManifestDisplay,
16
- type CompiledManifestPermissions,
17
- type CompiledManifestPersistence,
18
- type CompiledManifestOutputSurface,
19
- type CompiledManifestSkillRef,
20
- type CompiledSlotPersistenceOverride,
21
- type CompiledWorkingFile,
22
- type ManifestSurface as ManifestSurfaceType,
23
- type ModelRef,
24
- type SubAgentBinding,
25
- } from '@xemahq/kernel-contracts/workflow';
26
-
27
- import type { ToolSelectionEntry } from '@xemahq/kernel-contracts/mcp-tool';
28
-
29
- import { interpolate } from './interpolate';
30
- import { WorkspaceManifestSchema } from './schema';
31
- import type {
32
- CompileResult,
33
- CompiledMountDeclaration,
34
- CompiledSeedFile,
35
- CompiledSeedFileSource,
36
- ManifestInputDeclaration,
37
- CompiledWorkspaceManifest,
38
- ManifestIssue,
39
- AgentWorkspaceSpec,
40
- } from './types';
41
-
42
- const DEFAULT_SURFACE_COMPAT: readonly ManifestSurfaceType[] = [
43
- ManifestSurface.WORKFLOW,
44
- ManifestSurface.AGENT_SESSION,
45
- ] as const;
46
-
47
- /**
48
- * Compile a AgentWorkspaceSpec YAML or object into a `CompiledWorkspaceManifest`.
49
- * Stages:
50
- *
51
- * 1. YAML parse (if input is a string).
52
- * 2. Zod schema validation — accumulate every issue.
53
- * 3. Apply `bindInputs` defaults from `spec.inputs.*.default`.
54
- * 4. Validate required inputs are present after defaulting.
55
- * 5. Validate enum constraints on string inputs.
56
- * 6. Walk the spec, resolve every `${input.x}` interpolation.
57
- * 7. Validate declared mount slots against `AWP_V1_SPEC` (no platform
58
- * slots like `agents-md` declared by hand; they're auto-emitted).
59
- * 8. Validate seedFile slots + paths.
60
- * 9. Classify seed-file sources (inline vs template) — template names
61
- * are surfaced via `requiredTemplateNames` for the API service to
62
- * resolve against the templates table.
63
- *
64
- * Returns a `CompileResult` discriminated union — never throws. The
65
- * editor's preview path needs every issue at once, and downstream
66
- * persistence flows want a fail-fast 400 with the same shape; both get
67
- * it without try/catch ceremony.
68
- *
69
- * The kernel package never resolves templates — that is a fetch of
70
- * skill-bundle resources, done by the `agent-session-runtime` template
71
- * resolver, not by the compiler.
72
- */
73
- export function compileManifest(
74
- input: string | unknown,
75
- bindInputs: Readonly<Record<string, unknown>> = {},
76
- options: Readonly<CompileManifestOptions> = {},
77
- ): CompileResult {
78
- const issues: ManifestIssue[] = [];
79
-
80
- const parsed = parseYamlOrObject(input, issues);
81
- if (parsed === undefined) {
82
- return { ok: false, errors: issues };
83
- }
84
-
85
- const result = WorkspaceManifestSchema.safeParse(parsed);
86
- if (!result.success) {
87
- return { ok: false, errors: zodIssues(result.error) };
88
- }
89
- const manifest = result.data as AgentWorkspaceSpec;
90
-
91
- const inputResolutionMode = options.inputResolutionMode ?? 'strict';
92
- const inputs = applyInputDefaults(manifest, bindInputs, issues, inputResolutionMode);
93
- if (issues.length > 0) {
94
- return { ok: false, errors: issues };
95
- }
96
-
97
- const { resolved, issues: interpIssues } = interpolate(manifest.spec, { inputs });
98
- if (interpIssues.length > 0) {
99
- return { ok: false, errors: interpIssues };
100
- }
101
- const resolvedSpec = resolved as AgentWorkspaceSpec['spec'];
102
-
103
- const compiledMounts = compileMounts(resolvedSpec, issues);
104
- const compiledSeedFiles = compileSeedFiles(resolvedSpec, issues);
105
- const compiledCredentials = compileCredentials(resolvedSpec, issues);
106
- const compiledEnv = compileEnv(resolvedSpec, compiledCredentials, issues);
107
- const compiledWorkingFiles = compileWorkingFiles(resolvedSpec, issues);
108
-
109
- if (issues.length > 0) {
110
- return { ok: false, errors: issues };
111
- }
112
-
113
- const requiredTemplateNames = collectTemplateNames(compiledSeedFiles);
114
-
115
- const compiled: CompiledWorkspaceManifest = {
116
- slug: manifest.metadata.slug,
117
- version: manifest.metadata.version,
118
- surfaceCompat: manifest.metadata.surfaceCompat ?? DEFAULT_SURFACE_COMPAT,
119
- display: compileDisplay(manifest.metadata.display),
120
- agent: {
121
- slug: resolvedSpec.agent.slug,
122
- stageKey: resolvedSpec.agent.stage,
123
- role: resolvedSpec.agent.role,
124
- ...(resolvedSpec.agent.deliverableSpecRef === undefined
125
- ? {}
126
- : { deliverableSpecRef: resolvedSpec.agent.deliverableSpecRef }),
127
- ...(resolvedSpec.agent.defaultModel === undefined
128
- ? {}
129
- : { defaultModel: resolvedSpec.agent.defaultModel as ModelRef }),
130
- ...(resolvedSpec.agent.instructions === undefined
131
- ? {}
132
- : { instructions: resolvedSpec.agent.instructions }),
133
- ...(resolvedSpec.agent.promptMode === undefined
134
- ? {}
135
- : { promptMode: resolvedSpec.agent.promptMode }),
136
- ...(resolvedSpec.agent.permission === undefined
137
- ? {}
138
- : { permission: resolvedSpec.agent.permission }),
139
- subAgents: compileSubAgents(resolvedSpec.agent.subAgents),
140
- },
141
- mounts: compiledMounts,
142
- skills: compileSkills(resolvedSpec.skills),
143
- toolSelection: compileToolSelection(resolvedSpec.toolSelection),
144
- credentials: compiledCredentials,
145
- permissions: compilePermissions(resolvedSpec.permissions),
146
- persistence: compilePersistence(resolvedSpec.persistence, issues),
147
- outputSurface: compileOutputSurface(resolvedSpec.outputSurface),
148
- seedFiles: compiledSeedFiles,
149
- env: compiledEnv,
150
- workingFiles: compiledWorkingFiles,
151
- requiredTemplateNames,
152
- };
153
-
154
- return { ok: true, compiled };
155
- }
156
-
157
- function compileDisplay(
158
- display: AgentWorkspaceSpec['metadata']['display'],
159
- ): CompiledManifestDisplay {
160
- return {
161
- ...(display?.title !== undefined ? { title: display.title } : {}),
162
- ...(display?.blurb !== undefined ? { blurb: display.blurb } : {}),
163
- ...(display?.icon !== undefined ? { icon: display.icon } : {}),
164
- ...(display?.category !== undefined ? { category: display.category } : {}),
165
- badges: display?.badges ?? [],
166
- ...(display?.sortOrder !== undefined ? { sortOrder: display.sortOrder } : {}),
167
- hidden: display?.hidden ?? false,
168
- ...(display?.ctaText !== undefined ? { ctaText: display.ctaText } : {}),
169
- curated: display?.curated ?? false,
170
- };
171
- }
172
-
173
- function compileSubAgents(
174
- subAgents: AgentWorkspaceSpec['spec']['agent']['subAgents'],
175
- ): readonly SubAgentBinding[] {
176
- return (subAgents ?? []).map((s) => ({
177
- slug: s.slug,
178
- ...(s.alias === undefined ? {} : { alias: s.alias }),
179
- ...(s.defaultModel === undefined
180
- ? {}
181
- : { modelOverride: s.defaultModel as ModelRef }),
182
- ...(s.instructions === undefined ? {} : { instructions: s.instructions }),
183
- ...(s.promptMode === undefined ? {} : { promptMode: s.promptMode }),
184
- ...(s.permission === undefined ? {} : { permission: s.permission }),
185
- }));
186
- }
187
-
188
- function compileSkills(
189
- skills: AgentWorkspaceSpec['spec']['skills'],
190
- ): readonly CompiledManifestSkillRef[] {
191
- return (skills ?? []).map((s) => ({
192
- slug: s.slug,
193
- ...(s.version !== undefined ? { version: s.version } : {}),
194
- }));
195
- }
196
-
197
- function compileToolSelection(
198
- toolSelection: AgentWorkspaceSpec['spec']['toolSelection'],
199
- ): readonly ToolSelectionEntry[] {
200
- return (toolSelection ?? []).map((entry) => ({ ...entry }));
201
- }
202
-
203
- function compileCredentials(
204
- spec: AgentWorkspaceSpec['spec'],
205
- issues: ManifestIssue[],
206
- ): readonly CompiledManifestCredential[] {
207
- const out: CompiledManifestCredential[] = [];
208
- const seen = new Set<string>();
209
- for (const [idx, c] of (spec.credentials ?? []).entries()) {
210
- if (seen.has(c.name)) {
211
- issues.push({
212
- path: `$.spec.credentials[${idx}].name`,
213
- message: `duplicate credential name '${c.name}'`,
214
- });
215
- continue;
216
- }
217
- seen.add(c.name);
218
- out.push({
219
- name: c.name,
220
- kind: c.kind,
221
- sourceRef: c.sourceRef,
222
- required: c.required ?? false,
223
- });
224
- }
225
- return out;
226
- }
227
-
228
- function compilePermissions(
229
- permissions: AgentWorkspaceSpec['spec']['permissions'],
230
- ): CompiledManifestPermissions {
231
- return {
232
- tools: {
233
- allow: permissions?.tools.allow ?? [],
234
- deny: permissions?.tools.deny ?? [],
235
- },
236
- };
237
- }
238
-
239
- function compilePersistence(
240
- persistence: AgentWorkspaceSpec['spec']['persistence'],
241
- issues: ManifestIssue[],
242
- ): CompiledManifestPersistence {
243
- const overrides: Record<string, CompiledSlotPersistenceOverride> = {};
244
- const sourceOverrides = persistence?.overrides;
245
- if (sourceOverrides) {
246
- for (const [slotKey, override] of Object.entries(sourceOverrides)) {
247
- // Slot key must exist in the kernel spec.
248
- if (!isKnownSlotKey(AWP_V1_SPEC, slotKey)) {
249
- issues.push({
250
- path: `spec.persistence.overrides.${slotKey}`,
251
- message: `unknown slot key "${slotKey}" — not present in AWP v1 spec`,
252
- });
253
- continue;
254
- }
255
- // Overriding a slot only makes sense if its default policy is
256
- // `git-push` — `tarball` and `none` have no fields to tune.
257
- const slot = findSlot(AWP_V1_SPEC, slotKey);
258
- if (slot?.persistencePolicy?.kind !== 'git-push') {
259
- issues.push({
260
- path: `spec.persistence.overrides.${slotKey}`,
261
- message: `slot "${slotKey}" is not git-push-persisted (default kind: ${slot?.persistencePolicy?.kind ?? 'none'}) — override has no effect`,
262
- });
263
- continue;
264
- }
265
- overrides[slotKey] = override;
266
- }
267
- }
268
- return {
269
- paths: persistence?.paths ?? [],
270
- overrides,
271
- };
272
- }
273
-
274
- function compileOutputSurface(
275
- outputSurface: AgentWorkspaceSpec['spec']['outputSurface'],
276
- ): CompiledManifestOutputSurface {
277
- if (outputSurface === undefined) {
278
- return { kind: OutputSurfaceKind.NONE, autoOpen: false, mode: 'single' };
279
- }
280
- const base = {
281
- kind: outputSurface.kind,
282
- ...(outputSurface.port !== undefined ? { port: outputSurface.port } : {}),
283
- ...(outputSurface.healthPath !== undefined
284
- ? { healthPath: outputSurface.healthPath }
285
- : {}),
286
- autoOpen: outputSurface.autoOpen ?? false,
287
- mode: outputSurface.mode ?? 'single',
288
- };
289
- if (outputSurface.kind === OutputSurfaceKind.STATIC) {
290
- // Schema guarantees `root !== undefined` when kind === 'static'.
291
- return {
292
- ...base,
293
- root: outputSurface.root!,
294
- defaultDocument: outputSurface.defaultDocument ?? 'index.html',
295
- };
296
- }
297
- return base;
298
- }
299
-
300
- /**
301
- * Compile env vars and validate that any `${credential.NAME}` reference
302
- * points at a declared credential. Keeps the env block safe from typos
303
- * — at compile time the manifest already knows every credential its
304
- * runtime resolver will be asked to fetch.
305
- */
306
- function compileEnv(
307
- spec: AgentWorkspaceSpec['spec'],
308
- credentials: readonly CompiledManifestCredential[],
309
- issues: ManifestIssue[],
310
- ): readonly { readonly name: string; readonly value: string }[] {
311
- const declaredNames = new Set(credentials.map((c) => c.name));
312
- const out: { name: string; value: string }[] = [];
313
- for (const [idx, e] of (spec.env ?? []).entries()) {
314
- for (const m of e.value.matchAll(/\$\{\s*credential\.([A-Z_][A-Z0-9_]*)\s*\}/g)) {
315
- const credName = m[1]!;
316
- if (!declaredNames.has(credName)) {
317
- issues.push({
318
- path: `$.spec.env[${idx}].value`,
319
- message: `env var '${e.name}' references undeclared credential '${credName}'`,
320
- });
321
- }
322
- }
323
- out.push({ name: e.name, value: e.value });
324
- }
325
- return out;
326
- }
327
-
328
- function parseYamlOrObject(
329
- input: string | unknown,
330
- issues: ManifestIssue[],
331
- ): unknown | undefined {
332
- if (typeof input !== 'string') return input;
333
- try {
334
- return yaml.load(input);
335
- } catch (error) {
336
- issues.push({ path: '$', message: `YAML parse error: ${(error as Error).message}` });
337
- return undefined;
338
- }
339
- }
340
-
341
- function zodIssues(error: ZodError): ManifestIssue[] {
342
- return error.issues.map((iss) => ({
343
- path: '$' + (iss.path.length > 0 ? '.' + iss.path.join('.') : ''),
344
- message: iss.message,
345
- }));
346
- }
347
-
348
- function applyInputDefaults(
349
- manifest: AgentWorkspaceSpec,
350
- bindInputs: Readonly<Record<string, unknown>>,
351
- issues: ManifestIssue[],
352
- inputResolutionMode: InputResolutionMode,
353
- ): Record<string, unknown> {
354
- const declared = manifest.spec.inputs ?? {};
355
- const out: Record<string, unknown> = { ...bindInputs };
356
- const synthesizedInputs = new Set<string>();
357
- for (const [name, decl] of Object.entries(declared)) {
358
- if (out[name] === undefined && decl.default !== undefined) {
359
- out[name] = decl.default as unknown;
360
- }
361
- if (out[name] === undefined) {
362
- if (inputResolutionMode === 'deferred') {
363
- out[name] = placeholderForInputType(name, decl.type);
364
- synthesizedInputs.add(name);
365
- } else if (decl.required === true) {
366
- issues.push({
367
- path: `$.spec.inputs.${name}`,
368
- message: `required input '${name}' was not supplied and has no default`,
369
- });
370
- continue;
371
- }
372
- }
373
- if (
374
- decl.enum !== undefined &&
375
- out[name] !== undefined &&
376
- typeof out[name] === 'string' &&
377
- !synthesizedInputs.has(name) &&
378
- !decl.enum.includes(out[name] as string)
379
- ) {
380
- issues.push({
381
- path: `$.spec.inputs.${name}`,
382
- message: `input '${name}' value '${String(out[name])}' is not in enum [${decl.enum.join(', ')}]`,
383
- });
384
- }
385
- }
386
- for (const provided of Object.keys(bindInputs)) {
387
- if (!Object.prototype.hasOwnProperty.call(declared, provided)) {
388
- issues.push({
389
- path: `$.spec.inputs.${provided}`,
390
- message: `bind input '${provided}' is not declared in spec.inputs`,
391
- });
392
- }
393
- }
394
- return out;
395
- }
396
-
397
- function placeholderForInputType(
398
- name: string,
399
- type: ManifestInputDeclaration['type'],
400
- ): unknown {
401
- switch (type) {
402
- case 'string':
403
- return `${name}-deferred`;
404
- case 'string[]':
405
- return [`${name}-deferred`];
406
- case 'boolean':
407
- return true;
408
- case 'number':
409
- return 1;
410
- case 'object[]':
411
- return [];
412
- }
413
- }
414
-
415
- type InputResolutionMode = 'strict' | 'deferred';
416
-
417
- export interface CompileManifestOptions {
418
- /**
419
- * strict: missing required inputs fail compilation.
420
- * deferred: synthesize typed placeholders for missing declared inputs,
421
- * allowing authoring-time compile/validation without runtime bind values.
422
- */
423
- readonly inputResolutionMode?: InputResolutionMode;
424
- }
425
-
426
- function compileMounts(
427
- spec: AgentWorkspaceSpec['spec'],
428
- issues: ManifestIssue[],
429
- ): Record<string, CompiledMountDeclaration> {
430
- const out: Record<string, CompiledMountDeclaration> = {};
431
- const declared = spec.mounts ?? {};
432
- const PLATFORM_SLOTS: ReadonlySet<string> = new Set([
433
- 'agents-md',
434
- 'context-json',
435
- 'agent-bundles',
436
- 'skill-bundles',
437
- 'instructions',
438
- ]);
439
-
440
- for (const [slotKey, value] of Object.entries(declared)) {
441
- if (!isKnownSlotKey(AWP_V1_SPEC, slotKey)) {
442
- issues.push({
443
- path: `$.spec.mounts.${slotKey}`,
444
- message: `unknown slot '${slotKey}' — must be one of ${AWP_V1_SPEC.slots.map((s) => s.key).join(', ')}`,
445
- });
446
- continue;
447
- }
448
- if (PLATFORM_SLOTS.has(slotKey)) {
449
- issues.push({
450
- path: `$.spec.mounts.${slotKey}`,
451
- message: `slot '${slotKey}' is platform-rendered and auto-emitted by the composer — do not declare it in mounts`,
452
- });
453
- continue;
454
- }
455
- if (typeof value === 'boolean') {
456
- out[slotKey] = { enabled: value, config: {} };
457
- continue;
458
- }
459
- if (value === null || typeof value !== 'object') {
460
- issues.push({
461
- path: `$.spec.mounts.${slotKey}`,
462
- message: `mount declaration must be a boolean or an object`,
463
- });
464
- continue;
465
- }
466
- const config = { ...(value as Record<string, unknown>) };
467
- const mode = config['mode'];
468
- delete config['mode'];
469
- if (mode !== undefined && mode !== 'read-only' && mode !== 'read-write') {
470
- issues.push({
471
- path: `$.spec.mounts.${slotKey}.mode`,
472
- message: `mode must be 'read-only' or 'read-write'`,
473
- });
474
- continue;
475
- }
476
- out[slotKey] = {
477
- enabled: true,
478
- ...(mode === undefined ? {} : { mode: mode as 'read-only' | 'read-write' }),
479
- config,
480
- };
481
- }
482
- return out;
483
- }
484
-
485
- function compileSeedFiles(
486
- spec: AgentWorkspaceSpec['spec'],
487
- issues: ManifestIssue[],
488
- ): readonly CompiledSeedFile[] {
489
- const out: CompiledSeedFile[] = [];
490
- for (const [idx, sf] of (spec.seedFiles ?? []).entries()) {
491
- const slotKey = sf.slot ?? 'inputs';
492
- const slotDef = AWP_V1_SPEC.slots.find((s) => s.key === slotKey);
493
- if (!slotDef) {
494
- issues.push({
495
- path: `$.spec.seedFiles[${idx}].slot`,
496
- message: `unknown slot '${slotKey}'`,
497
- });
498
- continue;
499
- }
500
- if (slotDef.singleFile === true) {
501
- issues.push({
502
- path: `$.spec.seedFiles[${idx}]`,
503
- message: `slot '${slotKey}' is a single-file slot — seed files must target a multi-file slot`,
504
- });
505
- continue;
506
- }
507
- if (sf.path.startsWith('/') || sf.path.includes('..')) {
508
- issues.push({
509
- path: `$.spec.seedFiles[${idx}].path`,
510
- message: `path must be relative and contain no '..' segments`,
511
- });
512
- continue;
513
- }
514
- let source: CompiledSeedFileSource;
515
- if (sf.template !== undefined) {
516
- source = {
517
- kind: 'template',
518
- name: sf.template,
519
- vars: sf.vars ?? {},
520
- };
521
- } else if (sf.content !== undefined) {
522
- source = {
523
- kind: 'inline',
524
- bytesBase64: Buffer.from(sf.content, 'utf8').toString('base64'),
525
- };
526
- } else {
527
- issues.push({
528
- path: `$.spec.seedFiles[${idx}]`,
529
- message: `seedFile must declare either 'template' or 'content'`,
530
- });
531
- continue;
532
- }
533
- out.push({ slot: slotKey, relPath: sf.path, source });
534
- }
535
- return out;
536
- }
537
-
538
- /**
539
- * Compile + validate `spec.workingFiles[]`. Interpolation already ran on
540
- * `path` / `sourceRef` values during the `interpolate()` walk earlier in
541
- * the pipeline, so this function only enforces cross-binding invariants:
542
- *
543
- * - `slug` is unique within the manifest.
544
- * - The file basename (path's last `/`-segment, sans extension) equals
545
- * `slug` — the agent reads $XEMA_WORKING_FILE_PATH and expects the
546
- * basename to match the slug it sees on `context.json.workingFiles`.
547
- */
548
- function compileWorkingFiles(
549
- spec: AgentWorkspaceSpec['spec'],
550
- issues: ManifestIssue[],
551
- ): readonly CompiledWorkingFile[] {
552
- const out: CompiledWorkingFile[] = [];
553
- const seenSlugs = new Set<string>();
554
- for (const [idx, wf] of (spec.workingFiles ?? []).entries()) {
555
- if (seenSlugs.has(wf.slug)) {
556
- issues.push({
557
- path: `$.spec.workingFiles[${idx}].slug`,
558
- message: `duplicate workingFile slug '${wf.slug}'`,
559
- });
560
- continue;
561
- }
562
- seenSlugs.add(wf.slug);
563
- const basename = wf.path.split('/').pop() ?? '';
564
- const basenameNoExt = basename.includes('.')
565
- ? basename.slice(0, basename.lastIndexOf('.'))
566
- : basename;
567
- // Skip the basename↔slug parity check when either side carries an
568
- // unresolved `${input.x}` template token — the workspace pipeline
569
- // re-runs this check on the resolved literals at session-time.
570
- const hasTemplate =
571
- basenameNoExt.includes('${') || wf.slug.includes('${');
572
- if (!hasTemplate && basenameNoExt !== wf.slug) {
573
- issues.push({
574
- path: `$.spec.workingFiles[${idx}].path`,
575
- message:
576
- `workingFile path basename '${basenameNoExt}' must equal slug '${wf.slug}' ` +
577
- `so the agent's mental model of <slug> and the on-disk file stay aligned`,
578
- });
579
- continue;
580
- }
581
- // `binaryFormat` is the closed-enum hint that signals a companion
582
- // binary (XLSX/PPTX/DOCX/PDF) lives alongside the synopsis. Validate now so
583
- // a typo cannot reach the worker as a silent no-op.
584
- const binaryFormatRaw = wf.sourceRef['binaryFormat'];
585
- if (
586
- typeof binaryFormatRaw === 'string' &&
587
- binaryFormatRaw.length > 0 &&
588
- !binaryFormatRaw.includes('${') &&
589
- !isWorkingFileBinaryFormat(binaryFormatRaw)
590
- ) {
591
- issues.push({
592
- path: `$.spec.workingFiles[${idx}].sourceRef.binaryFormat`,
593
- message:
594
- `unknown binaryFormat '${binaryFormatRaw}'; expected one of ` +
595
- `${WORKING_FILE_BINARY_FORMATS.join(', ')}`,
596
- });
597
- continue;
598
- }
599
- out.push({
600
- slug: wf.slug,
601
- path: wf.path,
602
- format: wf.format,
603
- syncDirection: wf.syncDirection,
604
- sourceKind: wf.sourceKind,
605
- sourceRef: { ...wf.sourceRef },
606
- });
607
- }
608
- return out;
609
- }
610
-
611
- function collectTemplateNames(seedFiles: readonly CompiledSeedFile[]): readonly string[] {
612
- const names = new Set<string>();
613
- for (const sf of seedFiles) {
614
- if (sf.source.kind === 'template') {
615
- names.add(sf.source.name);
616
- }
617
- }
618
- return [...names].sort();
619
- }