@xemahq/agent-session-runtime 0.11.0 → 0.11.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.
- package/package.json +4 -5
- package/dist/lib/launch-identity/compiled-session-launch-plan-store.d.ts +0 -44
- package/dist/lib/launch-identity/compiled-session-launch-plan-store.d.ts.map +0 -1
- package/dist/lib/launch-identity/compiled-session-launch-plan-store.js +0 -116
- package/dist/lib/launch-identity/compiled-session-launch-plan-store.js.map +0 -1
- package/dist/lib/launch-identity/compiled-session-launch-plan.d.ts +0 -84
- package/dist/lib/launch-identity/compiled-session-launch-plan.d.ts.map +0 -1
- package/dist/lib/launch-identity/compiled-session-launch-plan.js +0 -382
- package/dist/lib/launch-identity/compiled-session-launch-plan.js.map +0 -1
- package/dist/lib/launch-identity/preparation-generation.d.ts +0 -26
- package/dist/lib/launch-identity/preparation-generation.d.ts.map +0 -1
- package/dist/lib/launch-identity/preparation-generation.js +0 -88
- package/dist/lib/launch-identity/preparation-generation.js.map +0 -1
- package/dist/lib/launch-identity/prepared-launch-variant.d.ts +0 -46
- package/dist/lib/launch-identity/prepared-launch-variant.d.ts.map +0 -1
- package/dist/lib/launch-identity/prepared-launch-variant.js +0 -118
- package/dist/lib/launch-identity/prepared-launch-variant.js.map +0 -1
- package/src/index.ts +0 -36
- package/src/lib/agent-resolution.ts +0 -557
- package/src/lib/agent-workspace-manifest.ts +0 -632
- package/src/lib/composer.ts +0 -1315
- package/src/lib/dispatch-contract.ts +0 -151
- package/src/lib/drift-detector.ts +0 -497
- package/src/lib/environment-resolver.ts +0 -480
- package/src/lib/errors.ts +0 -43
- package/src/lib/launch-identity/canonical-contract.ts +0 -352
- package/src/lib/launch-identity/index.ts +0 -4
- package/src/lib/launch-identity/runtime-template-key.ts +0 -143
- package/src/lib/launch-identity/session-binding.ts +0 -152
- package/src/lib/launch-identity/thread-reuse-key.ts +0 -93
- package/src/lib/lifecycle-state.ts +0 -30
- package/src/lib/skill-bundle-template-resolver.ts +0 -147
- package/src/lib/types.ts +0 -453
|
@@ -1,632 +0,0 @@
|
|
|
1
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
2
|
-
// ── Agent → CompiledWorkspaceManifest reconstruction ──
|
|
3
|
-
//
|
|
4
|
-
// The composer turns a
|
|
5
|
-
// `CompiledWorkspaceManifest` into a `WorkspaceMountPlan`; before this
|
|
6
|
-
// module the only way to obtain that compiled manifest was to fetch the
|
|
7
|
-
// `WorkspaceManifest` row from `workspace-manifests-api` and run
|
|
8
|
-
// `compileManifest` on it.
|
|
9
|
-
//
|
|
10
|
-
// The Agent primitive now carries the user-data MOUNT LAYOUT
|
|
11
|
-
// on its `workspace.mountLayout` block: the boot-seeder in `llm-registry-api`
|
|
12
|
-
// projects each biome manifest's `extends:`-flattened RAW `spec.mounts` /
|
|
13
|
-
// `spec.seedFiles` / `spec.inputs` there. This module reconstructs a
|
|
14
|
-
// `WorkspaceManifest` envelope from a resolved composition + its
|
|
15
|
-
// `mountLayout`, then runs the SAME `compileManifest` the legacy path used
|
|
16
|
-
// — with the REAL run/session bind inputs, so `${input.x}` tokens in
|
|
17
|
-
// seed-file vars resolve against the caller's inputs, never against a
|
|
18
|
-
// seed-time placeholder.
|
|
19
|
-
//
|
|
20
|
-
// The result is a `CompiledWorkspaceManifest` indistinguishable from the
|
|
21
|
-
// legacy `workspace-manifests-api`-sourced one — the composer interface is
|
|
22
|
-
// unchanged.
|
|
23
|
-
//
|
|
24
|
-
// This module is RUNTIME-AGNOSTIC: it does not import a generated API
|
|
25
|
-
// client. The caller (`agent-session-api` today, `workflow-runtime-worker`
|
|
26
|
-
// next) maps its resolved-composition shape onto the small input
|
|
27
|
-
// interfaces declared here and supplies the bind-input bag.
|
|
28
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
29
|
-
|
|
30
|
-
import {
|
|
31
|
-
ManifestSurface,
|
|
32
|
-
OutputSurfaceKind,
|
|
33
|
-
type AgentRunRole,
|
|
34
|
-
type CompiledManifestCredential,
|
|
35
|
-
type CompiledManifestPermissions,
|
|
36
|
-
type ModelRef,
|
|
37
|
-
} from '@xemahq/kernel-contracts/workflow';
|
|
38
|
-
import type { PermissionMap } from '@xemahq/kernel-contracts/agent-permission';
|
|
39
|
-
import {
|
|
40
|
-
compileManifest,
|
|
41
|
-
type CompiledWorkspaceManifest,
|
|
42
|
-
type ManifestCredential,
|
|
43
|
-
type ManifestInputDeclaration,
|
|
44
|
-
type ManifestMountsBlock,
|
|
45
|
-
type ManifestOutputSurface,
|
|
46
|
-
type ManifestPermissions,
|
|
47
|
-
type ManifestSeedFile,
|
|
48
|
-
type ManifestSubAgent,
|
|
49
|
-
type ManifestWorkingFile,
|
|
50
|
-
type AgentWorkspaceSpec as DslWorkspaceManifest,
|
|
51
|
-
type WorkspaceManifestSpec,
|
|
52
|
-
} from '@xemahq/dsl/workspace-manifest';
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Re-export so callers that consume this module's `compile…` result do not
|
|
56
|
-
* need a second import from `@xemahq/dsl/workspace-manifest`.
|
|
57
|
-
*/
|
|
58
|
-
export type { CompiledWorkspaceManifest };
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Raised when a resolved composition cannot be reconstructed into a
|
|
62
|
-
* compilable workspace manifest — a missing `mountLayout`, a malformed
|
|
63
|
-
* composition ref, or a `compileManifest` failure. Fail-fast: a session /
|
|
64
|
-
* run cannot bootstrap a `/workspace/` tree without a valid mount layout.
|
|
65
|
-
*/
|
|
66
|
-
export class AgentMountLayoutError extends Error {
|
|
67
|
-
constructor(agentRef: string, detail: string) {
|
|
68
|
-
super(
|
|
69
|
-
`Cannot derive a workspace mount layout from Agent ` +
|
|
70
|
-
`"${agentRef}": ${detail}`,
|
|
71
|
-
);
|
|
72
|
-
this.name = 'AgentMountLayoutError';
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* The agent run-config the seeder projects from the source manifest's
|
|
78
|
-
* `spec.agent` block. Mirrors `AgentRunConfigDto` without
|
|
79
|
-
* importing the generated client.
|
|
80
|
-
*/
|
|
81
|
-
export interface AgentRunConfigInput {
|
|
82
|
-
/** Stage key. */
|
|
83
|
-
readonly stage: string;
|
|
84
|
-
/** Canonical `AgentRunRole` — drives renderer + system-overlay framing. */
|
|
85
|
-
readonly role: string;
|
|
86
|
-
/**
|
|
87
|
-
* Deliverable-spec ref. May carry a `${input.x}` token — resolved
|
|
88
|
-
* downstream against the bind inputs.
|
|
89
|
-
*/
|
|
90
|
-
readonly deliverableSpecRef?: string;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* The `extends:`-flattened RAW user-data mount layout carried on a
|
|
95
|
-
* composition's `workspace.mountLayout` block. The seeder owns the wire
|
|
96
|
-
* shape; `compileManifest`'s Zod schema re-validates `mounts` / `seedFiles`
|
|
97
|
-
* / `inputs` against the manifest DSL's authoritative schema, so the loose
|
|
98
|
-
* typing here is checked downstream, not silently trusted.
|
|
99
|
-
*/
|
|
100
|
-
export interface AgentMountLayoutInput {
|
|
101
|
-
/** Raw `spec.mounts` block — manifest DSL mount-declaration map. */
|
|
102
|
-
readonly mounts: Readonly<Record<string, unknown>>;
|
|
103
|
-
/** Projected `spec.agent` run-config. */
|
|
104
|
-
readonly agentRunConfig: AgentRunConfigInput;
|
|
105
|
-
/** Raw `spec.seedFiles` array — manifest DSL seed-file entries. */
|
|
106
|
-
readonly seedFiles: readonly unknown[];
|
|
107
|
-
/** Raw `spec.inputs` block — manifest DSL input-declaration map. */
|
|
108
|
-
readonly inputs: Readonly<Record<string, unknown>>;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* A single manifest-declared sub-agent binding the composition resolved.
|
|
113
|
-
* Mirrors the binding subset the manifest DSL's `ManifestSubAgent` needs.
|
|
114
|
-
*
|
|
115
|
-
* Carries the {@link NodeOverlayFragment} overlay trio (`modelOverride` /
|
|
116
|
-
* `instructions` / `permission`) so a resolved sub-agent's per-node
|
|
117
|
-
* specialization survives the manifest reconstruction. Without these the
|
|
118
|
-
* worker silently DROPPED a delegate's `modelOverride` (the root fix for the
|
|
119
|
-
* worker drift). All optional ⇒ backward-compatible with callers that only
|
|
120
|
-
* supply `slug` / `alias`.
|
|
121
|
-
*/
|
|
122
|
-
export interface AgentSubAgentBindingInput {
|
|
123
|
-
readonly slug: string;
|
|
124
|
-
readonly alias?: string;
|
|
125
|
-
/** Per-delegate model override — OVERRIDE on the referenced agent's model. */
|
|
126
|
-
readonly modelOverride?: ModelRef;
|
|
127
|
-
/** Per-delegate instructions — APPENDED to the referenced agent's prompt. */
|
|
128
|
-
readonly instructions?: string;
|
|
129
|
-
/** Per-delegate permission — RESTRICT-merged over the agent's permission. */
|
|
130
|
-
readonly permission?: PermissionMap;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* The composition's `workspace.outputSurface` block projected onto the
|
|
135
|
-
* shape the manifest DSL accepts. Mirrors `ManifestOutputSurface` from
|
|
136
|
-
* `@xemahq/dsl/workspace-manifest` but restated here so callers do not
|
|
137
|
-
* need a second import.
|
|
138
|
-
*
|
|
139
|
-
* Carried through the reconstructed manifest's `spec.outputSurface` so
|
|
140
|
-
* the compiled output's `CompiledManifestOutputSurface` matches the
|
|
141
|
-
* authoritative source — drift detection and runtime defaults read the
|
|
142
|
-
* same kind/root/port the bundle-apply path reads from
|
|
143
|
-
* `composition.workspace.outputSurface`.
|
|
144
|
-
*/
|
|
145
|
-
export interface AgentOutputSurfaceInput {
|
|
146
|
-
readonly kind: 'none' | 'web' | 'static' | 'app' | 'tunnel';
|
|
147
|
-
readonly port?: number;
|
|
148
|
-
readonly healthPath?: string;
|
|
149
|
-
readonly autoOpen?: boolean;
|
|
150
|
-
readonly mode?: 'single' | 'multi';
|
|
151
|
-
readonly root?: string;
|
|
152
|
-
readonly defaultDocument?: string;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* The minimal resolved-composition projection this module reconstructs a
|
|
157
|
-
* `WorkspaceManifest` from. The caller maps its richer resolved-composition
|
|
158
|
-
* shape onto this — keeping the runtime package free of any API client.
|
|
159
|
-
*/
|
|
160
|
-
export interface AgentManifestSource {
|
|
161
|
-
/** Version-pinned `slug@version` of the resolved composition. */
|
|
162
|
-
readonly agentRef: string;
|
|
163
|
-
/** Root node's agent slug — the manifest's primary agent. */
|
|
164
|
-
readonly primaryAgentSlug: string;
|
|
165
|
-
/** Manifest-declared sub-agent bindings (descendant nodes). */
|
|
166
|
-
readonly subAgentBindings: readonly AgentSubAgentBindingInput[];
|
|
167
|
-
/**
|
|
168
|
-
* The composition's `workspace.mountLayout` block. `undefined` when the
|
|
169
|
-
* composition declared no `workspace` block or the seeder did not project
|
|
170
|
-
* a layout — a fail-fast condition.
|
|
171
|
-
*/
|
|
172
|
-
readonly mountLayout: AgentMountLayoutInput | undefined;
|
|
173
|
-
/**
|
|
174
|
-
* The composition's `workspace.outputSurface` block. Threaded into the
|
|
175
|
-
* reconstructed manifest's `spec.outputSurface` so the compiled
|
|
176
|
-
* manifest's `outputSurface` matches the source-of-truth (drift
|
|
177
|
-
* detection + non-bundle consumers like `workflow-runtime-worker` read
|
|
178
|
-
* the same value the bundle-apply path reads). `undefined` when the
|
|
179
|
-
* composition declared no surface — the DSL compile then yields the
|
|
180
|
-
* `{ kind: 'none' }` default.
|
|
181
|
-
*/
|
|
182
|
-
readonly outputSurface?: AgentOutputSurfaceInput;
|
|
183
|
-
/**
|
|
184
|
-
* The composition's `workspace.credentials` block. Threaded into the
|
|
185
|
-
* reconstructed manifest's `spec.credentials` so the compiled manifest's
|
|
186
|
-
* `credentials` matches the source-of-truth — the EnvironmentResolver
|
|
187
|
-
* reads the same `{name, kind, sourceRef, required}` entries the
|
|
188
|
-
* authored composition declared (BUG-1 carry-through: authored
|
|
189
|
-
* `spec.credentials` were silently dropped on reconstruction). Carried
|
|
190
|
-
* as the kernel `CompiledManifestCredential[]` because that is the shape
|
|
191
|
-
* `composition.workspace.credentials` holds (the seeder projects the
|
|
192
|
-
* authored entries through the manifest compiler). `undefined` when the
|
|
193
|
-
* composition declared none — the DSL compile then yields `[]`.
|
|
194
|
-
*/
|
|
195
|
-
readonly credentials?: readonly CompiledManifestCredential[];
|
|
196
|
-
/**
|
|
197
|
-
* The composition's `workspace.permissions` block. Threaded into the
|
|
198
|
-
* reconstructed manifest's `spec.permissions` so the compiled manifest's
|
|
199
|
-
* `permissions` matches the source-of-truth — the EnvironmentResolver
|
|
200
|
-
* reads the same `{tools: {allow, deny}}` the authored composition
|
|
201
|
-
* declared (BUG-1 carry-through: authored `spec.permissions` were
|
|
202
|
-
* silently dropped on reconstruction). Carried as the kernel
|
|
203
|
-
* `CompiledManifestPermissions` because that is the shape
|
|
204
|
-
* `composition.workspace.permissions` holds. `undefined` when the
|
|
205
|
-
* composition declared none — the DSL compile then yields
|
|
206
|
-
* `{ tools: { allow: [], deny: [] } }`.
|
|
207
|
-
*/
|
|
208
|
-
readonly permissions?: CompiledManifestPermissions;
|
|
209
|
-
/**
|
|
210
|
-
* The composition's `workspace.workingFiles` block. Threaded into the
|
|
211
|
-
* reconstructed manifest's `spec.workingFiles` so the compiled manifest —
|
|
212
|
-
* and therefore `context.json.workingFiles` + the write authority on
|
|
213
|
-
* `context.json.authority.mayWriteWorkspace[]` — matches the
|
|
214
|
-
* source-of-truth. Carried as the DSL `ManifestWorkingFile[]` (raw, with
|
|
215
|
-
* `${input.x}` tokens intact); `compileManifest` interpolates them against
|
|
216
|
-
* the bind inputs, exactly like the bundle-apply path's `toBundleWorkingFiles`.
|
|
217
|
-
* `undefined` when the composition declared none — the DSL compile then
|
|
218
|
-
* yields `[]`. (BUG-2 carry-through: authored `spec.workingFiles` were
|
|
219
|
-
* silently dropped on reconstruction, so a Document Buddy session came up
|
|
220
|
-
* with NO bound working file — the agent read `context.json`, saw an empty
|
|
221
|
-
* `workingFiles[]`, and asked the user where to write instead of editing the
|
|
222
|
-
* bound page. Symmetric with the credentials/permissions BUG-1 fix above.)
|
|
223
|
-
*/
|
|
224
|
-
readonly workingFiles?: readonly ManifestWorkingFile[];
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* The implicit inputs every reconstructed manifest is bound against, on top
|
|
229
|
-
* of the caller-supplied explicit inputs.
|
|
230
|
-
*
|
|
231
|
-
* `sessionId` is the run-scoped execution identity. The agent-session-api
|
|
232
|
-
* caller supplies the `Session.id`; the workflow-runtime-worker caller —
|
|
233
|
-
* which has no session — supplies the workflow run id. Both are
|
|
234
|
-
* run-scoped; only manifests that declare a `sessionId` input ever read
|
|
235
|
-
* it, and no first-party manifest does today. Optional so a caller with
|
|
236
|
-
* no run-scoped id at all can omit it.
|
|
237
|
-
*/
|
|
238
|
-
export interface AgentManifestImplicitInputs {
|
|
239
|
-
readonly orgId: string;
|
|
240
|
-
readonly projectId: string;
|
|
241
|
-
readonly sessionId?: string;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/** Manifest input value primitives accepted by the manifest DSL. */
|
|
245
|
-
type ManifestInputValue =
|
|
246
|
-
| string
|
|
247
|
-
| number
|
|
248
|
-
| boolean
|
|
249
|
-
| readonly string[]
|
|
250
|
-
| readonly Readonly<Record<string, unknown>>[];
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Reconstruct a `WorkspaceManifest` envelope from a resolved composition
|
|
254
|
-
* and run `compileManifest` against the supplied bind inputs.
|
|
255
|
-
*
|
|
256
|
-
* The reconstructed envelope carries NO `extends:` — the boot-seeder
|
|
257
|
-
* already flattened the source manifest's `extends:` chain before
|
|
258
|
-
* projecting `mountLayout`, so the layout is the effective post-merge
|
|
259
|
-
* shape.
|
|
260
|
-
*
|
|
261
|
-
* `bindInputs` is the resolved manifest input bag (explicit inputs ∪
|
|
262
|
-
* implicit inputs), built by {@link buildAgentManifestBindInputs}.
|
|
263
|
-
* The compile fails fast on an unresolved required input or an enum
|
|
264
|
-
* violation — exactly the legacy behaviour.
|
|
265
|
-
*/
|
|
266
|
-
export function compileAgentWorkspaceManifest(
|
|
267
|
-
source: AgentManifestSource,
|
|
268
|
-
bindInputs: Readonly<Record<string, unknown>>,
|
|
269
|
-
): CompiledWorkspaceManifest {
|
|
270
|
-
const mountLayout = source.mountLayout;
|
|
271
|
-
if (!mountLayout) {
|
|
272
|
-
throw new AgentMountLayoutError(
|
|
273
|
-
source.agentRef,
|
|
274
|
-
'the composition declares no `workspace.mountLayout` block — its ' +
|
|
275
|
-
'source workspace manifest was not projected by the llm-registry-api ' +
|
|
276
|
-
'composition seeder. Re-seed compositions (boot llm-registry-api) or ' +
|
|
277
|
-
'fix the source manifest.',
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const [slug, version] = splitAgentRef(source.agentRef);
|
|
282
|
-
|
|
283
|
-
// Sub-agents the composition resolved become the manifest agent block's
|
|
284
|
-
// `subAgents[]`. The composer + EnvironmentResolver surface these on the
|
|
285
|
-
// resolved environment snapshot (drift detection). The intrinsic floor
|
|
286
|
-
// is still merged separately downstream — this is the manifest-declared
|
|
287
|
-
// layer only, exactly as a legacy manifest's `agent.subAgents` was.
|
|
288
|
-
const subAgents: ManifestSubAgent[] = source.subAgentBindings.map(
|
|
289
|
-
(binding) => ({
|
|
290
|
-
slug: binding.slug,
|
|
291
|
-
...(binding.alias !== undefined ? { alias: binding.alias } : {}),
|
|
292
|
-
// Pass the overlay trio through to the manifest agent block so a
|
|
293
|
-
// resolved delegate's per-node `modelOverride` / `instructions` /
|
|
294
|
-
// `permission` are NOT dropped on reconstruction (the worker bug).
|
|
295
|
-
// `ManifestSubAgent.defaultModel` is the manifest DSL's name for the
|
|
296
|
-
// kernel `modelOverride` slot.
|
|
297
|
-
...(binding.modelOverride !== undefined
|
|
298
|
-
? { defaultModel: binding.modelOverride }
|
|
299
|
-
: {}),
|
|
300
|
-
...(binding.instructions !== undefined
|
|
301
|
-
? { instructions: binding.instructions }
|
|
302
|
-
: {}),
|
|
303
|
-
...(binding.permission !== undefined
|
|
304
|
-
? { permission: binding.permission }
|
|
305
|
-
: {}),
|
|
306
|
-
}),
|
|
307
|
-
);
|
|
308
|
-
|
|
309
|
-
// Raw fragments carried verbatim by the seeder. They are typed loosely
|
|
310
|
-
// on the wire (the seeder owns the shape); `compileManifest`'s Zod schema
|
|
311
|
-
// below re-validates them against the manifest DSL's authoritative
|
|
312
|
-
// `WorkspaceManifestSchema`, so the casts here are checked downstream,
|
|
313
|
-
// not silently trusted.
|
|
314
|
-
const outputSurface = projectOutputSurface(source.outputSurface);
|
|
315
|
-
const credentials = projectCredentials(source.credentials);
|
|
316
|
-
const permissions = projectPermissions(source.permissions);
|
|
317
|
-
const spec: WorkspaceManifestSpec = {
|
|
318
|
-
mounts: mountLayout.mounts as unknown as ManifestMountsBlock,
|
|
319
|
-
agent: {
|
|
320
|
-
slug: source.primaryAgentSlug,
|
|
321
|
-
stage: mountLayout.agentRunConfig.stage,
|
|
322
|
-
role: mountLayout.agentRunConfig.role as AgentRunRole,
|
|
323
|
-
...(mountLayout.agentRunConfig.deliverableSpecRef !== undefined
|
|
324
|
-
? { deliverableSpecRef: mountLayout.agentRunConfig.deliverableSpecRef }
|
|
325
|
-
: {}),
|
|
326
|
-
...(subAgents.length > 0 ? { subAgents } : {}),
|
|
327
|
-
},
|
|
328
|
-
...(Object.keys(mountLayout.inputs).length > 0
|
|
329
|
-
? {
|
|
330
|
-
inputs: mountLayout.inputs as unknown as Record<
|
|
331
|
-
string,
|
|
332
|
-
ManifestInputDeclaration
|
|
333
|
-
>,
|
|
334
|
-
}
|
|
335
|
-
: {}),
|
|
336
|
-
...(mountLayout.seedFiles.length > 0
|
|
337
|
-
? {
|
|
338
|
-
seedFiles:
|
|
339
|
-
mountLayout.seedFiles as unknown as readonly ManifestSeedFile[],
|
|
340
|
-
}
|
|
341
|
-
: {}),
|
|
342
|
-
...(outputSurface === undefined ? {} : { outputSurface }),
|
|
343
|
-
...(credentials === undefined ? {} : { credentials }),
|
|
344
|
-
...(permissions === undefined ? {} : { permissions }),
|
|
345
|
-
// Carry the authored working files through to `spec.workingFiles` so the
|
|
346
|
-
// compiled manifest the composer reads (`req.manifest.workingFiles`) binds
|
|
347
|
-
// the document file into `context.json.workingFiles` + narrows write
|
|
348
|
-
// authority. Without this the reconstruction path silently drops them and
|
|
349
|
-
// the agent sees no bound working file. `compileManifest`'s Zod schema
|
|
350
|
-
// re-validates the entries (and interpolates `${input.x}`).
|
|
351
|
-
...(source.workingFiles && source.workingFiles.length > 0
|
|
352
|
-
? { workingFiles: source.workingFiles }
|
|
353
|
-
: {}),
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
const envelope: DslWorkspaceManifest = {
|
|
357
|
-
apiVersion: 'xema.dev/workspace/v1',
|
|
358
|
-
kind: 'WorkspaceManifest',
|
|
359
|
-
metadata: {
|
|
360
|
-
slug,
|
|
361
|
-
version: toManifestVersion(source.agentRef, version),
|
|
362
|
-
// A manifest RECONSTRUCTED from a bare `agentRef` carries no inherent
|
|
363
|
-
// surface restriction — the SAME agent (e.g. `demo-runner`,
|
|
364
|
-
// `requirements-coordinator`) is legitimately driven on BOTH the
|
|
365
|
-
// agent-session surface (interactive bootstrap) AND the workflow surface
|
|
366
|
-
// (pipeline `xema/agent@v1` jobs). Defaulting to AGENT_SESSION only made
|
|
367
|
-
// every workflow agent run fail `assertSurfaceCompat` with
|
|
368
|
-
// "surfaceCompat=[agent-session] but was resolved on 'workflow'". Mirror
|
|
369
|
-
// the DSL compiler's permissive `DEFAULT_SURFACE_COMPAT` ([workflow,
|
|
370
|
-
// agent-session]); the runtime EnvironmentResolver still re-asserts the
|
|
371
|
-
// ACTUAL surface, so widening the reconstructed set cannot let an agent
|
|
372
|
-
// run somewhere its real manifest forbids — an authored manifest that
|
|
373
|
-
// explicitly narrows `surfaceCompat` is parsed via the DSL path, not here.
|
|
374
|
-
surfaceCompat: [ManifestSurface.WORKFLOW, ManifestSurface.AGENT_SESSION],
|
|
375
|
-
},
|
|
376
|
-
spec,
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
const result = compileManifest(envelope, bindInputs);
|
|
380
|
-
if (!result.ok) {
|
|
381
|
-
throw new AgentMountLayoutError(
|
|
382
|
-
source.agentRef,
|
|
383
|
-
`reconstructed manifest failed to compile: ${result.errors
|
|
384
|
-
.map((e) => `${e.path}: ${e.message}`)
|
|
385
|
-
.join('; ')}`,
|
|
386
|
-
);
|
|
387
|
-
}
|
|
388
|
-
return result.compiled;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* Project the composition's `workspace.outputSurface` block (carried on
|
|
393
|
-
* the runtime-agnostic `AgentManifestSource`) into the manifest DSL's
|
|
394
|
-
* `ManifestOutputSurface` shape so the reconstructed envelope's
|
|
395
|
-
* `spec.outputSurface` carries the same kind/root/port as the source. The
|
|
396
|
-
* `kind` value strings match the DSL enum 1:1 (`none` / `web` / `static` /
|
|
397
|
-
* `app` / `tunnel`) so the cast is exact, not coerced.
|
|
398
|
-
*
|
|
399
|
-
* Returns `undefined` when the composition declared no surface — the DSL
|
|
400
|
-
* `compileManifest` then defaults the compiled `outputSurface` to
|
|
401
|
-
* `{ kind: 'none', autoOpen: false, mode: 'single' }`.
|
|
402
|
-
*/
|
|
403
|
-
function projectOutputSurface(
|
|
404
|
-
surface: AgentOutputSurfaceInput | undefined,
|
|
405
|
-
): ManifestOutputSurface | undefined {
|
|
406
|
-
if (!surface) return undefined;
|
|
407
|
-
return {
|
|
408
|
-
kind: surface.kind as OutputSurfaceKind,
|
|
409
|
-
...(surface.port === undefined ? {} : { port: surface.port }),
|
|
410
|
-
...(surface.healthPath === undefined
|
|
411
|
-
? {}
|
|
412
|
-
: { healthPath: surface.healthPath }),
|
|
413
|
-
...(surface.autoOpen === undefined ? {} : { autoOpen: surface.autoOpen }),
|
|
414
|
-
...(surface.mode === undefined ? {} : { mode: surface.mode }),
|
|
415
|
-
...(surface.root === undefined ? {} : { root: surface.root }),
|
|
416
|
-
...(surface.defaultDocument === undefined
|
|
417
|
-
? {}
|
|
418
|
-
: { defaultDocument: surface.defaultDocument }),
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
/**
|
|
423
|
-
* Project the composition's `workspace.credentials` block (carried on the
|
|
424
|
-
* runtime-agnostic `AgentManifestSource` as the kernel
|
|
425
|
-
* `CompiledManifestCredential[]`) into the manifest DSL's authored
|
|
426
|
-
* `ManifestCredential[]` shape so the reconstructed envelope's
|
|
427
|
-
* `spec.credentials` carries the same entries as the source.
|
|
428
|
-
*
|
|
429
|
-
* The compiled shape is a structural superset of the authored shape
|
|
430
|
-
* (`{name, kind, sourceRef}` plus a now-required `required` boolean), so the
|
|
431
|
-
* projection is a 1:1 field copy — `compileManifest`'s Zod schema below
|
|
432
|
-
* re-validates each entry and re-defaults `required` idempotently. The map
|
|
433
|
-
* is explicit (not a pass-through cast) so a malformed entry fails fast at
|
|
434
|
-
* the manifest compile boundary, mirroring {@link projectOutputSurface}.
|
|
435
|
-
*
|
|
436
|
-
* Returns `undefined` when the composition declared no credentials — the DSL
|
|
437
|
-
* `compileManifest` then defaults the compiled `credentials` to `[]`.
|
|
438
|
-
*/
|
|
439
|
-
function projectCredentials(
|
|
440
|
-
credentials: readonly CompiledManifestCredential[] | undefined,
|
|
441
|
-
): readonly ManifestCredential[] | undefined {
|
|
442
|
-
if (credentials === undefined) return undefined;
|
|
443
|
-
return credentials.map((credential) => ({
|
|
444
|
-
name: credential.name,
|
|
445
|
-
kind: credential.kind,
|
|
446
|
-
sourceRef: credential.sourceRef,
|
|
447
|
-
required: credential.required,
|
|
448
|
-
}));
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
/**
|
|
452
|
-
* Project the composition's `workspace.permissions` block (carried on the
|
|
453
|
-
* runtime-agnostic `AgentManifestSource` as the kernel
|
|
454
|
-
* `CompiledManifestPermissions`) into the manifest DSL's authored
|
|
455
|
-
* `ManifestPermissions` shape so the reconstructed envelope's
|
|
456
|
-
* `spec.permissions` carries the same tool allow/deny lists as the source.
|
|
457
|
-
*
|
|
458
|
-
* The compiled shape is a structural superset of the authored shape (the
|
|
459
|
-
* compiled `tools.allow` / `tools.deny` are always present; the authored
|
|
460
|
-
* ones are optional), so the projection is a 1:1 field copy —
|
|
461
|
-
* `compileManifest`'s Zod schema below re-validates and re-defaults the
|
|
462
|
-
* lists idempotently. Explicit (not a pass-through cast) so a malformed
|
|
463
|
-
* block fails fast at the manifest compile boundary, mirroring
|
|
464
|
-
* {@link projectOutputSurface}.
|
|
465
|
-
*
|
|
466
|
-
* Returns `undefined` when the composition declared no permissions — the DSL
|
|
467
|
-
* `compileManifest` then defaults the compiled `permissions` to
|
|
468
|
-
* `{ tools: { allow: [], deny: [] } }`.
|
|
469
|
-
*/
|
|
470
|
-
function projectPermissions(
|
|
471
|
-
permissions: CompiledManifestPermissions | undefined,
|
|
472
|
-
): ManifestPermissions | undefined {
|
|
473
|
-
if (permissions === undefined) return undefined;
|
|
474
|
-
return {
|
|
475
|
-
tools: {
|
|
476
|
-
allow: permissions.tools.allow,
|
|
477
|
-
deny: permissions.tools.deny,
|
|
478
|
-
},
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
/**
|
|
483
|
-
* Build the manifest bind-input bag from the caller's explicit input bag
|
|
484
|
-
* and the implicit inputs, validated against the composition's declared
|
|
485
|
-
* `mountLayout.inputs`.
|
|
486
|
-
*
|
|
487
|
-
* • an undeclared explicit input key fails fast;
|
|
488
|
-
* • a non-primitive / mixed-array input value fails fast;
|
|
489
|
-
* • a declared input with neither an explicit value nor an implicit
|
|
490
|
-
* value is simply omitted — the compiler then applies the input's
|
|
491
|
-
* own `default` (or fails fast if it is `required`).
|
|
492
|
-
*
|
|
493
|
-
* Returns `{}` when the composition declared no inputs.
|
|
494
|
-
*/
|
|
495
|
-
export function buildAgentManifestBindInputs(
|
|
496
|
-
source: AgentManifestSource,
|
|
497
|
-
explicitInputs: Record<string, unknown> | null,
|
|
498
|
-
implicitInputs: AgentManifestImplicitInputs,
|
|
499
|
-
): Readonly<Record<string, unknown>> {
|
|
500
|
-
const declared = new Set(Object.keys(source.mountLayout?.inputs ?? {}));
|
|
501
|
-
if (declared.size === 0) {
|
|
502
|
-
return {};
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
if (
|
|
506
|
-
explicitInputs !== null &&
|
|
507
|
-
(typeof explicitInputs !== 'object' || Array.isArray(explicitInputs))
|
|
508
|
-
) {
|
|
509
|
-
throw new AgentMountLayoutError(
|
|
510
|
-
source.agentRef,
|
|
511
|
-
'explicit manifest inputs must be an object when provided',
|
|
512
|
-
);
|
|
513
|
-
}
|
|
514
|
-
const explicit = explicitInputs ?? {};
|
|
515
|
-
for (const [key, value] of Object.entries(explicit)) {
|
|
516
|
-
if (!declared.has(key)) {
|
|
517
|
-
throw new AgentMountLayoutError(
|
|
518
|
-
source.agentRef,
|
|
519
|
-
`manifest inputs contain undeclared key "${key}"`,
|
|
520
|
-
);
|
|
521
|
-
}
|
|
522
|
-
assertManifestInputValue(source.agentRef, key, value);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
const implicit: Record<string, ManifestInputValue> = {
|
|
526
|
-
orgId: implicitInputs.orgId,
|
|
527
|
-
projectId: implicitInputs.projectId,
|
|
528
|
-
...(implicitInputs.sessionId !== undefined
|
|
529
|
-
? { sessionId: implicitInputs.sessionId }
|
|
530
|
-
: {}),
|
|
531
|
-
};
|
|
532
|
-
|
|
533
|
-
const out: Record<string, unknown> = {};
|
|
534
|
-
for (const key of declared) {
|
|
535
|
-
if (Object.hasOwn(explicit, key)) {
|
|
536
|
-
out[key] = explicit[key];
|
|
537
|
-
continue;
|
|
538
|
-
}
|
|
539
|
-
if (Object.hasOwn(implicit, key)) {
|
|
540
|
-
out[key] = implicit[key];
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
return out;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
/** An authored manifest version: MAJOR.MINOR.PATCH. */
|
|
547
|
-
const MANIFEST_SEMVER = /^\d+\.\d+\.\d+$/;
|
|
548
|
-
/** An `AgentRef` version: the `AgentRevision.sequence` integer. */
|
|
549
|
-
const AGENT_REVISION_SEQUENCE = /^\d+$/;
|
|
550
|
-
|
|
551
|
-
/**
|
|
552
|
-
* Map an `AgentRef` version onto `WorkspaceManifestMetadataSchema.version`.
|
|
553
|
-
*
|
|
554
|
-
* These are two different vocabularies and the reconstruction sits exactly on
|
|
555
|
-
* the seam. An `AgentRef` is pinned by `AgentRevision.sequence` — a monotonic
|
|
556
|
-
* INTEGER, so real refs look like `document-md-default@2`. The manifest schema
|
|
557
|
-
* was written for AUTHORED YAML and requires MAJOR.MINOR.PATCH. Passing the
|
|
558
|
-
* sequence straight through made `compileManifest` reject every reconstructed
|
|
559
|
-
* manifest with "version must follow semver MAJOR.MINOR.PATCH", which is a
|
|
560
|
-
* schema complaint about a field no author ever wrote.
|
|
561
|
-
*
|
|
562
|
-
* Sequence N maps to `N.0.0`: total, injective, and order-preserving, so
|
|
563
|
-
* `drift-detector`'s version comparison keeps working and two distinct
|
|
564
|
-
* revisions can never collide on one manifest version.
|
|
565
|
-
*
|
|
566
|
-
* A semver-shaped version passes through untouched — the legacy
|
|
567
|
-
* `workspace-manifests-api` refs are genuinely semver, and this module serves
|
|
568
|
-
* both. Anything else is REFUSED rather than coerced: a ref we cannot map is a
|
|
569
|
-
* contract we do not understand, and inventing a version for it would put a
|
|
570
|
-
* fabricated identity into the compiled manifest.
|
|
571
|
-
*/
|
|
572
|
-
function toManifestVersion(agentRef: string, version: string): string {
|
|
573
|
-
if (MANIFEST_SEMVER.test(version)) {
|
|
574
|
-
return version;
|
|
575
|
-
}
|
|
576
|
-
if (AGENT_REVISION_SEQUENCE.test(version)) {
|
|
577
|
-
return `${version}.0.0`;
|
|
578
|
-
}
|
|
579
|
-
throw new AgentMountLayoutError(
|
|
580
|
-
agentRef,
|
|
581
|
-
`cannot map ref version "${version}" onto a manifest version — expected ` +
|
|
582
|
-
'either an AgentRevision sequence integer or semver MAJOR.MINOR.PATCH',
|
|
583
|
-
);
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
/** Split a `slug@version` composition ref; fail fast on a bare slug. */
|
|
587
|
-
function splitAgentRef(ref: string): [slug: string, version: string] {
|
|
588
|
-
const at = ref.lastIndexOf('@');
|
|
589
|
-
if (at <= 0 || at === ref.length - 1) {
|
|
590
|
-
throw new AgentMountLayoutError(
|
|
591
|
-
ref,
|
|
592
|
-
'expected a version-pinned `slug@version` composition ref — the ' +
|
|
593
|
-
'session resolver always pins the resolved version',
|
|
594
|
-
);
|
|
595
|
-
}
|
|
596
|
-
return [ref.slice(0, at), ref.slice(at + 1)];
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
/**
|
|
600
|
-
* Manifest inputs flow into seed-file templates via Handlebars
|
|
601
|
-
* interpolation. Non-primitives produce `[object Object]` silently;
|
|
602
|
-
* mixed-type arrays break string-array inputs. Reject both at the
|
|
603
|
-
* boundary so the error surfaces near the cause.
|
|
604
|
-
*/
|
|
605
|
-
function assertManifestInputValue(
|
|
606
|
-
agentRef: string,
|
|
607
|
-
key: string,
|
|
608
|
-
value: unknown,
|
|
609
|
-
): void {
|
|
610
|
-
if (
|
|
611
|
-
value !== null &&
|
|
612
|
-
typeof value !== 'string' &&
|
|
613
|
-
typeof value !== 'number' &&
|
|
614
|
-
typeof value !== 'boolean' &&
|
|
615
|
-
!Array.isArray(value)
|
|
616
|
-
) {
|
|
617
|
-
throw new AgentMountLayoutError(
|
|
618
|
-
agentRef,
|
|
619
|
-
`manifest input "${key}" must be a primitive ` +
|
|
620
|
-
`(string, number, boolean, string[]) — got ${typeof value}`,
|
|
621
|
-
);
|
|
622
|
-
}
|
|
623
|
-
if (
|
|
624
|
-
Array.isArray(value) &&
|
|
625
|
-
!value.every((entry) => typeof entry === 'string')
|
|
626
|
-
) {
|
|
627
|
-
throw new AgentMountLayoutError(
|
|
628
|
-
agentRef,
|
|
629
|
-
`manifest input "${key}" is an array — every element must be a string`,
|
|
630
|
-
);
|
|
631
|
-
}
|
|
632
|
-
}
|