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