@xemahq/dsl 0.8.1 → 0.8.3

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 (80) hide show
  1. package/dist/payload-codec/index.d.ts.map +1 -1
  2. package/dist/payload-codec/index.js.map +1 -1
  3. package/dist/payload-codec/lib/codec.d.ts.map +1 -1
  4. package/dist/payload-codec/lib/codec.js +2 -1
  5. package/dist/payload-codec/lib/codec.js.map +1 -1
  6. package/dist/payload-codec/lib/payload.d.ts.map +1 -1
  7. package/dist/schema/action.schema.json +205 -39
  8. package/dist/schema/workflow.schema.json +335 -88
  9. package/dist/workflow/index.d.ts.map +1 -1
  10. package/dist/workflow/index.js.map +1 -1
  11. package/dist/workflow/lib/compiler/compile.js +1 -1
  12. package/dist/workflow/lib/compiler/compile.js.map +1 -1
  13. package/package.json +2 -18
  14. package/schema/action.schema.json +205 -39
  15. package/schema/workflow.schema.json +335 -88
  16. package/dist/payload-codec/temporal/index.d.ts +0 -5
  17. package/dist/payload-codec/temporal/index.d.ts.map +0 -1
  18. package/dist/payload-codec/temporal/index.js +0 -7
  19. package/dist/payload-codec/temporal/index.js.map +0 -1
  20. package/src/deliverable-spec/index.ts +0 -19
  21. package/src/deliverable-spec/lib/schema.ts +0 -270
  22. package/src/deliverable-spec/lib/types.ts +0 -26
  23. package/src/payload-codec/index.ts +0 -44
  24. package/src/payload-codec/lib/blob-store.ts +0 -176
  25. package/src/payload-codec/lib/codec-context.ts +0 -38
  26. package/src/payload-codec/lib/codec.ts +0 -605
  27. package/src/payload-codec/lib/enums.ts +0 -58
  28. package/src/payload-codec/lib/errors.ts +0 -54
  29. package/src/payload-codec/lib/http-blob-store.ts +0 -267
  30. package/src/payload-codec/lib/lru-cache.ts +0 -81
  31. package/src/payload-codec/lib/payload.ts +0 -26
  32. package/src/payload-codec/temporal/index.ts +0 -36
  33. package/src/workflow/index.ts +0 -108
  34. package/src/workflow/lib/action-input-validator.ts +0 -160
  35. package/src/workflow/lib/compiler/action-shape.ts +0 -71
  36. package/src/workflow/lib/compiler/canonical-json.ts +0 -66
  37. package/src/workflow/lib/compiler/compile.ts +0 -1742
  38. package/src/workflow/lib/compiler/concurrency.ts +0 -223
  39. package/src/workflow/lib/compiler/dag.ts +0 -108
  40. package/src/workflow/lib/compiler/gate-defaults.ts +0 -153
  41. package/src/workflow/lib/compiler/index.ts +0 -11
  42. package/src/workflow/lib/compiler/inputs.ts +0 -254
  43. package/src/workflow/lib/compiler/installation-resource-validator.ts +0 -114
  44. package/src/workflow/lib/compiler/manifest-source.ts +0 -71
  45. package/src/workflow/lib/compiler/matrix.ts +0 -135
  46. package/src/workflow/lib/compiler/mount-plan.ts +0 -190
  47. package/src/workflow/lib/compiler/payload-reach-in.ts +0 -497
  48. package/src/workflow/lib/compiler/permissions.ts +0 -64
  49. package/src/workflow/lib/compiler/retry-timeout.ts +0 -105
  50. package/src/workflow/lib/compiler/review-step.ts +0 -548
  51. package/src/workflow/lib/compiler/types.ts +0 -172
  52. package/src/workflow/lib/compiler/variable-requirements.ts +0 -208
  53. package/src/workflow/lib/deliverable-spec-introspection-error.ts +0 -63
  54. package/src/workflow/lib/deliverable-spec-keys.ts +0 -147
  55. package/src/workflow/lib/deliverable-spec-source-scan.ts +0 -280
  56. package/src/workflow/lib/dispatch-inputs/index.ts +0 -160
  57. package/src/workflow/lib/dispatch-inputs/to-json-schema.ts +0 -60
  58. package/src/workflow/lib/duration.ts +0 -43
  59. package/src/workflow/lib/errors.ts +0 -37
  60. package/src/workflow/lib/expression/ast.ts +0 -108
  61. package/src/workflow/lib/expression/context.ts +0 -148
  62. package/src/workflow/lib/expression/evaluator.ts +0 -492
  63. package/src/workflow/lib/expression/index.ts +0 -28
  64. package/src/workflow/lib/expression/interpolation.ts +0 -84
  65. package/src/workflow/lib/expression/parser.ts +0 -264
  66. package/src/workflow/lib/expression/template.ts +0 -117
  67. package/src/workflow/lib/expression/tokenizer.ts +0 -200
  68. package/src/workflow/lib/expression/tokens.ts +0 -30
  69. package/src/workflow/lib/expression/walk-artifact-refs.ts +0 -232
  70. package/src/workflow/lib/installation-resource-kind.ts +0 -107
  71. package/src/workflow/lib/schemas-loader.ts +0 -64
  72. package/src/workflow/lib/serializer.ts +0 -30
  73. package/src/workflow/lib/types.ts +0 -417
  74. package/src/workflow/lib/validate.ts +0 -199
  75. package/src/workspace-manifest/index.ts +0 -27
  76. package/src/workspace-manifest/lib/compile.ts +0 -619
  77. package/src/workspace-manifest/lib/interpolate.ts +0 -166
  78. package/src/workspace-manifest/lib/resolve-extends.ts +0 -260
  79. package/src/workspace-manifest/lib/schema.ts +0 -692
  80. 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
- }