spawnfile 0.1.1 → 0.1.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 (102) hide show
  1. package/README.md +80 -396
  2. package/dist/cli/index.js +0 -0
  3. package/dist/cli/modelCommands.d.ts +3 -0
  4. package/dist/cli/modelCommands.js +68 -0
  5. package/dist/cli/runCli.d.ts +23 -2
  6. package/dist/cli/runCli.js +78 -122
  7. package/dist/cli/runtimeCommands.d.ts +3 -0
  8. package/dist/cli/runtimeCommands.js +20 -0
  9. package/dist/cli/surfaceCommands.d.ts +3 -0
  10. package/dist/cli/surfaceCommands.js +98 -0
  11. package/dist/cli/viewCommand.d.ts +3 -0
  12. package/dist/cli/viewCommand.js +87 -0
  13. package/dist/compiler/agentSurfaces.js +51 -5
  14. package/dist/compiler/buildCompilePlan.js +38 -40
  15. package/dist/compiler/buildCompilePlanRuntime.d.ts +14 -0
  16. package/dist/compiler/buildCompilePlanRuntime.js +39 -0
  17. package/dist/compiler/buildCompilePlanTeams.d.ts +5 -0
  18. package/dist/compiler/buildCompilePlanTeams.js +38 -0
  19. package/dist/compiler/compilePlanHelpers.js +4 -1
  20. package/dist/compiler/compileProject.js +62 -13
  21. package/dist/compiler/compileProjectSupport.d.ts +17 -0
  22. package/dist/compiler/compileProjectSupport.js +136 -0
  23. package/dist/compiler/containerArtifacts.d.ts +6 -1
  24. package/dist/compiler/containerArtifacts.js +26 -4
  25. package/dist/compiler/containerArtifactsPlans.js +16 -1
  26. package/dist/compiler/containerArtifactsRender.d.ts +4 -2
  27. package/dist/compiler/containerArtifactsRender.js +21 -126
  28. package/dist/compiler/containerArtifactsTypes.d.ts +7 -0
  29. package/dist/compiler/containerEntrypointRender.d.ts +12 -0
  30. package/dist/compiler/containerEntrypointRender.js +186 -0
  31. package/dist/compiler/index.d.ts +4 -0
  32. package/dist/compiler/index.js +4 -0
  33. package/dist/compiler/interactiveSurfaceScopes.d.ts +2 -0
  34. package/dist/compiler/interactiveSurfaceScopes.js +21 -0
  35. package/dist/compiler/moltnetArtifacts.d.ts +27 -0
  36. package/dist/compiler/moltnetArtifacts.js +208 -0
  37. package/dist/compiler/moltnetBinaries.d.ts +4 -0
  38. package/dist/compiler/moltnetBinaries.js +103 -0
  39. package/dist/compiler/moltnetClientConfig.d.ts +11 -0
  40. package/dist/compiler/moltnetClientConfig.js +89 -0
  41. package/dist/compiler/moltnetRepresentativeResolution.d.ts +16 -0
  42. package/dist/compiler/moltnetRepresentativeResolution.js +86 -0
  43. package/dist/compiler/moltnetResolution.d.ts +3 -0
  44. package/dist/compiler/moltnetResolution.js +182 -0
  45. package/dist/compiler/moltnetRoomMemberships.d.ts +3 -0
  46. package/dist/compiler/moltnetRoomMemberships.js +140 -0
  47. package/dist/compiler/runProject.js +1 -1
  48. package/dist/compiler/surfaceDefinitions.d.ts +55 -0
  49. package/dist/compiler/surfaceDefinitions.js +204 -0
  50. package/dist/compiler/teamContextHelpers.d.ts +18 -0
  51. package/dist/compiler/teamContextHelpers.js +112 -0
  52. package/dist/compiler/teamContextSupport.d.ts +4 -0
  53. package/dist/compiler/teamContextSupport.js +264 -0
  54. package/dist/compiler/teamContextSupport.testHelpers.d.ts +16 -0
  55. package/dist/compiler/teamContextSupport.testHelpers.js +68 -0
  56. package/dist/compiler/teamContextTypes.d.ts +28 -0
  57. package/dist/compiler/teamContextTypes.js +1 -0
  58. package/dist/compiler/teamRoster.d.ts +12 -0
  59. package/dist/compiler/teamRoster.js +48 -0
  60. package/dist/compiler/teamRosterEntries.d.ts +13 -0
  61. package/dist/compiler/teamRosterEntries.js +230 -0
  62. package/dist/compiler/teamRosterTypes.d.ts +45 -0
  63. package/dist/compiler/teamRosterTypes.js +1 -0
  64. package/dist/compiler/types.d.ts +90 -6
  65. package/dist/compiler/updateProjectRuntime.d.ts +9 -0
  66. package/dist/compiler/updateProjectRuntime.js +67 -0
  67. package/dist/compiler/updateProjectSurfaces.d.ts +8 -0
  68. package/dist/compiler/updateProjectSurfaces.js +106 -0
  69. package/dist/compiler/view/buildOrganizationView.d.ts +2 -0
  70. package/dist/compiler/view/buildOrganizationView.js +180 -0
  71. package/dist/compiler/view/index.d.ts +4 -0
  72. package/dist/compiler/view/index.js +4 -0
  73. package/dist/compiler/view/renderNetworks.d.ts +2 -0
  74. package/dist/compiler/view/renderNetworks.js +93 -0
  75. package/dist/compiler/view/renderTree.d.ts +2 -0
  76. package/dist/compiler/view/renderTree.js +59 -0
  77. package/dist/compiler/view/sourcePaths.d.ts +2 -0
  78. package/dist/compiler/view/sourcePaths.js +19 -0
  79. package/dist/compiler/view/types.d.ts +80 -0
  80. package/dist/compiler/view/types.js +1 -0
  81. package/dist/manifest/loadManifest.js +4 -4
  82. package/dist/manifest/renderSpawnfile.js +74 -8
  83. package/dist/manifest/scaffold.js +1 -3
  84. package/dist/manifest/schemas.d.ts +227 -17
  85. package/dist/manifest/schemas.js +62 -20
  86. package/dist/manifest/surfaceSchemas.d.ts +154 -0
  87. package/dist/manifest/surfaceSchemas.js +77 -5
  88. package/dist/runtime/common.js +3 -0
  89. package/dist/runtime/openclaw/adapter.js +38 -5
  90. package/dist/runtime/openclaw/moltnet.d.ts +12 -0
  91. package/dist/runtime/openclaw/moltnet.js +124 -0
  92. package/dist/runtime/openclaw/surfaces.js +3 -0
  93. package/dist/runtime/picoclaw/adapter.js +27 -8
  94. package/dist/runtime/picoclaw/pico.d.ts +2 -0
  95. package/dist/runtime/picoclaw/pico.js +2 -0
  96. package/dist/runtime/picoclaw/surfaces.js +11 -0
  97. package/dist/runtime/tinyclaw/adapter.js +22 -8
  98. package/dist/runtime/tinyclaw/runAuth.js +28 -1
  99. package/dist/runtime/tinyclaw/surfaces.js +8 -0
  100. package/dist/runtime/types.d.ts +11 -0
  101. package/package.json +5 -3
  102. package/runtimes.yaml +4 -4
@@ -1,6 +1,5 @@
1
1
  import { getCanonicalManifestPath, getManifestPath, resolveProjectPath } from "../filesystem/index.js";
2
- import { isAgentManifest, isTeamManifest, loadManifest, mergeExecution, normalizeRuntimeBinding } from "../manifest/index.js";
3
- import { assertRuntimeCanCompile } from "../runtime/index.js";
2
+ import { isAgentManifest, isTeamManifest, loadManifest, mergeExecution } from "../manifest/index.js";
4
3
  import { SpawnfileError } from "../shared/index.js";
5
4
  import { getAgentFingerprint, getMcpNames, getTeamFingerprint, validateEffectiveSkillRequirements } from "./compilePlanHelpers.js";
6
5
  import { resolveAgentSurfaces } from "./agentSurfaces.js";
@@ -9,30 +8,17 @@ import { loadResolvedDocuments, mergeResolvedSkills, loadResolvedSkills, mergeSh
9
8
  import { assertRuntimeSupportsExecutionModelAuth } from "./modelAuth.js";
10
9
  import { assertRuntimeSupportsAgentSurfaces } from "./surfaceSupport.js";
11
10
  import { applyExecutionDefaults } from "./executionDefaults.js";
12
- const resolveRuntime = async (manifest, context) => {
13
- const localRuntime = normalizeRuntimeBinding(manifest.runtime);
14
- if (context.isSubagent) {
15
- if (!context.inheritedRuntime) {
16
- throw new SpawnfileError("runtime_error", `Subagent ${manifest.name} is missing inherited runtime context`);
17
- }
18
- if (localRuntime &&
19
- localRuntime.name !== context.inheritedRuntime.name) {
20
- throw new SpawnfileError("runtime_error", `Subagent ${manifest.name} must match parent runtime`);
21
- }
22
- return context.inheritedRuntime;
23
- }
24
- if (!localRuntime) {
25
- throw new SpawnfileError("runtime_error", `Agent ${manifest.name} does not declare a runtime`);
26
- }
27
- await assertRuntimeCanCompile(localRuntime.name);
28
- return localRuntime;
29
- };
11
+ import { resolvePlanMoltnetAttachments } from "./moltnetResolution.js";
12
+ import { resolveMoltnetRoomMemberships } from "./moltnetRoomMemberships.js";
13
+ import { normalizeDescription, resolveDescription, resolveRuntime } from "./buildCompilePlanRuntime.js";
14
+ import { resolveTeamExternalIds, resolveTeamNetworks, validateTeamNetworkRooms } from "./buildCompilePlanTeams.js";
30
15
  export const buildCompilePlan = async (inputPath) => {
31
16
  const rootManifestPath = getCanonicalManifestPath(getManifestPath(inputPath));
32
17
  const loadCache = new Map();
33
18
  const nodeCache = new Map();
34
19
  const fingerprintCache = new Map();
35
20
  const edges = [];
21
+ const memberships = new Map();
36
22
  const visitStack = [];
37
23
  const getLoadedManifest = (manifestPath) => {
38
24
  const canonicalPath = getCanonicalManifestPath(manifestPath);
@@ -70,10 +56,13 @@ export const buildCompilePlan = async (inputPath) => {
70
56
  const localSkills = await loadResolvedSkills(canonicalPath, loadedManifest.manifest.skills);
71
57
  const skills = mergeResolvedSkills(inheritedSkills, localSkills);
72
58
  validateEffectiveSkillRequirements(loadedManifest.manifest.name, getMcpNames(sharedSurface.mcpServers), skills);
59
+ const docs = await loadResolvedDocuments(canonicalPath, loadedManifest.manifest.docs);
73
60
  const candidate = {
74
- docs: await loadResolvedDocuments(canonicalPath, loadedManifest.manifest.docs),
61
+ description: resolveDescription(loadedManifest.manifest.description, docs),
62
+ docs,
75
63
  env: sharedSurface.env,
76
64
  execution,
65
+ expose: loadedManifest.manifest.expose ?? false,
77
66
  kind: "agent",
78
67
  mcpServers: sharedSurface.mcpServers,
79
68
  name: loadedManifest.manifest.name,
@@ -135,31 +124,29 @@ export const buildCompilePlan = async (inputPath) => {
135
124
  }
136
125
  const sharedSkills = await loadResolvedSkills(canonicalPath, loadedManifest.manifest.shared?.skills);
137
126
  validateEffectiveSkillRequirements(loadedManifest.manifest.name, getMcpNames(loadedManifest.manifest.shared?.mcp_servers ?? []), sharedSkills);
138
- const structure = loadedManifest.manifest.structure;
139
- const memberIds = loadedManifest.manifest.members.map((member) => member.id);
140
- const resolvedExternal = structure.external
141
- ?? (structure.mode === "hierarchical" && structure.leader
142
- ? [structure.leader]
143
- : memberIds);
127
+ const manifest = loadedManifest.manifest;
128
+ const resolvedExternal = resolveTeamExternalIds(manifest);
129
+ const docs = await loadResolvedDocuments(canonicalPath, manifest.docs);
144
130
  const candidate = {
145
- docs: await loadResolvedDocuments(canonicalPath, loadedManifest.manifest.docs),
131
+ description: manifest.description ? normalizeDescription(manifest.description) : "",
132
+ docs,
133
+ external: resolvedExternal,
134
+ externalExplicit: manifest.external !== undefined,
146
135
  kind: "team",
136
+ lead: manifest.lead ?? null,
147
137
  members: [],
148
- name: loadedManifest.manifest.name,
149
- policyMode: loadedManifest.manifest.policy?.mode ?? null,
150
- policyOnDegrade: loadedManifest.manifest.policy?.on_degrade ?? null,
138
+ mode: manifest.mode,
139
+ name: manifest.name,
140
+ networks: resolveTeamNetworks(manifest),
141
+ policyMode: manifest.policy?.mode ?? null,
142
+ policyOnDegrade: manifest.policy?.on_degrade ?? null,
151
143
  shared: {
152
- env: loadedManifest.manifest.shared?.env ?? {},
153
- mcpServers: loadedManifest.manifest.shared?.mcp_servers ?? [],
154
- secrets: loadedManifest.manifest.shared?.secrets ?? [],
144
+ env: manifest.shared?.env ?? {},
145
+ mcpServers: manifest.shared?.mcp_servers ?? [],
146
+ secrets: manifest.shared?.secrets ?? [],
155
147
  skills: sharedSkills
156
148
  },
157
149
  source: canonicalPath,
158
- structure: {
159
- external: resolvedExternal,
160
- leader: structure.leader ?? null,
161
- mode: structure.mode
162
- }
163
150
  };
164
151
  const fingerprint = getTeamFingerprint(candidate);
165
152
  const existingFingerprint = fingerprintCache.get(canonicalPath);
@@ -195,6 +182,12 @@ export const buildCompilePlan = async (inputPath) => {
195
182
  nodeSource: resolvedAgent.source,
196
183
  runtimeName: resolvedAgent.runtime.name
197
184
  };
185
+ memberships.set(`${canonicalPath}::${member.id}::${resolvedAgent.source}`, {
186
+ agentSource: resolvedAgent.source,
187
+ memberId: member.id,
188
+ teamName: candidate.name,
189
+ teamSource: canonicalPath
190
+ });
198
191
  }
199
192
  else {
200
193
  const resolvedTeam = await visitTeam(childManifestPath);
@@ -213,6 +206,7 @@ export const buildCompilePlan = async (inputPath) => {
213
206
  to: resolvedMember.nodeSource
214
207
  });
215
208
  }
209
+ validateTeamNetworkRooms(candidate);
216
210
  visitStack.pop();
217
211
  return candidate;
218
212
  };
@@ -249,10 +243,14 @@ export const buildCompilePlan = async (inputPath) => {
249
243
  groups[node.runtimeName] = group;
250
244
  return groups;
251
245
  }, {});
252
- return {
246
+ const compilePlan = {
253
247
  edges: compilePlanEdges,
248
+ memberships: [...memberships.values()].sort((left, right) => `${left.agentSource}:${left.teamSource}:${left.memberId}`.localeCompare(`${right.agentSource}:${right.teamSource}:${right.memberId}`)),
254
249
  nodes: compilePlanNodes,
255
250
  root: rootManifestPath,
256
251
  runtimes
257
252
  };
253
+ compilePlan.moltnetRoomMemberships = resolveMoltnetRoomMemberships(compilePlan);
254
+ resolvePlanMoltnetAttachments(compilePlan);
255
+ return compilePlan;
258
256
  };
@@ -0,0 +1,14 @@
1
+ import { type AgentManifest, type ExecutionBlock, type SharedSurface } from "../manifest/index.js";
2
+ import type { ResolvedDocument, ResolvedRuntime } from "./types.js";
3
+ export interface AgentVisitContext {
4
+ inheritedExecution?: ExecutionBlock;
5
+ inheritedShared?: {
6
+ manifestPath: string;
7
+ surface: SharedSurface | undefined;
8
+ };
9
+ inheritedRuntime?: ResolvedRuntime;
10
+ isSubagent: boolean;
11
+ }
12
+ export declare const resolveRuntime: (manifest: AgentManifest, context: AgentVisitContext) => Promise<ResolvedRuntime>;
13
+ export declare const normalizeDescription: (description: string) => string;
14
+ export declare const resolveDescription: (description: string | undefined, docs: ResolvedDocument[]) => string;
@@ -0,0 +1,39 @@
1
+ import { normalizeRuntimeBinding } from "../manifest/index.js";
2
+ import { assertRuntimeCanCompile } from "../runtime/index.js";
3
+ import { SpawnfileError } from "../shared/index.js";
4
+ export const resolveRuntime = async (manifest, context) => {
5
+ const localRuntime = normalizeRuntimeBinding(manifest.runtime);
6
+ if (context.isSubagent) {
7
+ if (!context.inheritedRuntime) {
8
+ throw new SpawnfileError("runtime_error", `Subagent ${manifest.name} is missing inherited runtime context`);
9
+ }
10
+ if (localRuntime &&
11
+ localRuntime.name !== context.inheritedRuntime.name) {
12
+ throw new SpawnfileError("runtime_error", `Subagent ${manifest.name} must match parent runtime`);
13
+ }
14
+ return context.inheritedRuntime;
15
+ }
16
+ if (!localRuntime) {
17
+ throw new SpawnfileError("runtime_error", `Agent ${manifest.name} does not declare a runtime`);
18
+ }
19
+ await assertRuntimeCanCompile(localRuntime.name);
20
+ return localRuntime;
21
+ };
22
+ export const normalizeDescription = (description) => description.replace(/\s+/g, " ").trim();
23
+ const deriveDescriptionFromDocs = (docs) => {
24
+ const identity = docs.find((doc) => doc.role === "identity")?.content;
25
+ if (!identity) {
26
+ return "";
27
+ }
28
+ const paragraph = identity
29
+ .split(/\n\s*\n/)
30
+ .map((block) => normalizeDescription(block))
31
+ .find((block) => block.length > 0 && !/^#{1,6}\s+/.test(block));
32
+ if (!paragraph) {
33
+ return "";
34
+ }
35
+ return paragraph.length > 200 ? paragraph.slice(0, 200).trimEnd() : paragraph;
36
+ };
37
+ export const resolveDescription = (description, docs) => description !== undefined
38
+ ? normalizeDescription(description)
39
+ : deriveDescriptionFromDocs(docs);
@@ -0,0 +1,5 @@
1
+ import type { TeamManifest } from "../manifest/index.js";
2
+ import type { ResolvedTeamNetwork, ResolvedTeamNode } from "./types.js";
3
+ export declare const resolveTeamExternalIds: (manifest: TeamManifest) => string[];
4
+ export declare const resolveTeamNetworks: (manifest: TeamManifest) => ResolvedTeamNetwork[];
5
+ export declare const validateTeamNetworkRooms: (teamNode: ResolvedTeamNode) => void;
@@ -0,0 +1,38 @@
1
+ import { SpawnfileError } from "../shared/index.js";
2
+ export const resolveTeamExternalIds = (manifest) => {
3
+ const memberIds = manifest.members.map((member) => member.id);
4
+ if (manifest.lead && !memberIds.includes(manifest.lead)) {
5
+ throw new SpawnfileError("validation_error", `Team ${manifest.name} lead references unknown member ${manifest.lead}`);
6
+ }
7
+ for (const externalMemberId of manifest.external ?? []) {
8
+ if (!memberIds.includes(externalMemberId)) {
9
+ throw new SpawnfileError("validation_error", `Team ${manifest.name} external representative references unknown member ${externalMemberId}`);
10
+ }
11
+ }
12
+ return manifest.external
13
+ ?? (manifest.mode === "hierarchical" && manifest.lead
14
+ ? [manifest.lead]
15
+ : memberIds);
16
+ };
17
+ export const resolveTeamNetworks = (manifest) => (manifest.networks ?? []).map((network) => ({
18
+ expose: network.expose ?? false,
19
+ id: network.id,
20
+ name: network.name ?? network.id,
21
+ provider: network.provider,
22
+ rooms: network.rooms.map((room) => ({
23
+ id: room.id,
24
+ members: [...room.members]
25
+ }))
26
+ }));
27
+ export const validateTeamNetworkRooms = (teamNode) => {
28
+ for (const network of teamNode.networks ?? []) {
29
+ for (const room of network.rooms) {
30
+ for (const roomMemberId of room.members) {
31
+ const resolvedMember = teamNode.members.find((member) => member.id === roomMemberId);
32
+ if (!resolvedMember) {
33
+ throw new SpawnfileError("validation_error", `Team ${teamNode.name} Moltnet room ${room.id} references unknown member ${roomMemberId}`);
34
+ }
35
+ }
36
+ }
37
+ }
38
+ };
@@ -25,7 +25,10 @@ export const getAgentFingerprint = (node) => stableStringify({
25
25
  });
26
26
  export const getTeamFingerprint = (node) => stableStringify({
27
27
  members: node.members,
28
- structure: node.structure,
28
+ mode: node.mode,
29
+ lead: node.lead,
30
+ external: node.external,
31
+ networks: node.networks ?? [],
29
32
  shared: {
30
33
  env: node.shared.env,
31
34
  mcpServers: node.shared.mcpServers,
@@ -1,23 +1,19 @@
1
1
  import path from "node:path";
2
2
  import { chmod } from "node:fs/promises";
3
- import { ensureDirectory, removeDirectory, writeUtf8File } from "../filesystem/index.js";
3
+ import { ensureDirectory, removeDirectory } from "../filesystem/index.js";
4
4
  import { createCompileReport, createDiagnostic, writeCompileReport } from "../report/index.js";
5
5
  import { DEFAULT_OUTPUT_DIRECTORY } from "../shared/index.js";
6
6
  import { assertRuntimeCanCompile, createRuntimeLifecycleDiagnostics, getRuntimeAdapter } from "../runtime/index.js";
7
7
  import { buildCompilePlan } from "./buildCompilePlan.js";
8
8
  import { createContainerArtifacts } from "./containerArtifacts.js";
9
- const writeEmittedFiles = async (outputDirectory, files) => {
10
- await Promise.all(files.map(async (file) => {
11
- const targetPath = path.join(outputDirectory, file.path);
12
- await ensureDirectory(path.dirname(targetPath));
13
- await writeUtf8File(targetPath, file.content);
14
- }));
15
- };
9
+ import { injectMoltnetWorkspaceFiles, injectTeamCompileSupportFiles, prepareTeamCompileSupport, writeEmittedFiles } from "./compileProjectSupport.js";
10
+ import { generateMoltnetArtifacts } from "./moltnetArtifacts.js";
11
+ import { stageMoltnetBinaries } from "./moltnetBinaries.js";
16
12
  const createTeamCapabilities = (outcome, message) => [
17
13
  { key: "team.members", message, outcome },
18
- { key: "team.structure.mode", message, outcome },
19
- { key: "team.structure.leader", message, outcome },
20
- { key: "team.structure.external", message, outcome },
14
+ { key: "team.mode", message, outcome },
15
+ { key: "team.lead", message, outcome },
16
+ { key: "team.external", message, outcome },
21
17
  { key: "team.shared", message, outcome },
22
18
  { key: "team.nested", message, outcome }
23
19
  ];
@@ -148,6 +144,46 @@ const enforcePolicy = (nodeReport, policyMode, onDegrade) => {
148
144
  }
149
145
  }
150
146
  };
147
+ const createIdentityCapabilities = (node) => [
148
+ ...(node.surfaces?.slack?.identity
149
+ ? [{
150
+ key: "surfaces.slack.identity",
151
+ message: "Declared Slack identity was preserved for roster output",
152
+ outcome: "supported"
153
+ }]
154
+ : []),
155
+ ...(node.surfaces?.discord?.identity
156
+ ? [{
157
+ key: "surfaces.discord.identity",
158
+ message: "Declared Discord identity was preserved for roster output",
159
+ outcome: "supported"
160
+ }]
161
+ : []),
162
+ ...(node.surfaces?.telegram?.identity
163
+ ? [{
164
+ key: "surfaces.telegram.identity",
165
+ message: "Declared Telegram identity was preserved for roster output",
166
+ outcome: "supported"
167
+ }]
168
+ : []),
169
+ ...(node.surfaces?.whatsapp?.identity
170
+ ? [{
171
+ key: "surfaces.whatsapp.identity",
172
+ message: "Declared WhatsApp identity was preserved for roster output",
173
+ outcome: "supported"
174
+ }]
175
+ : [])
176
+ ];
177
+ const augmentNodeReports = (compiledNodes, support) => {
178
+ for (const compiled of compiledNodes) {
179
+ if (compiled.value.kind === "team") {
180
+ compiled.report.capabilities.push(...(support.capabilitiesByTeamSource.get(compiled.value.source) ?? []));
181
+ compiled.report.diagnostics.push(...(support.diagnosticsByTeamSource.get(compiled.value.source) ?? []));
182
+ continue;
183
+ }
184
+ compiled.report.capabilities.push(...createIdentityCapabilities(compiled.value));
185
+ }
186
+ };
151
187
  export const compileProject = async (inputPath, options = {}) => {
152
188
  const plan = await buildCompilePlan(inputPath);
153
189
  const outputDirectory = path.resolve(options.outputDirectory ?? DEFAULT_OUTPUT_DIRECTORY);
@@ -155,21 +191,34 @@ export const compileProject = async (inputPath, options = {}) => {
155
191
  await removeDirectory(outputDirectory);
156
192
  }
157
193
  await ensureDirectory(outputDirectory);
194
+ const teamCompileSupport = await prepareTeamCompileSupport(plan);
158
195
  const nodeReports = [];
159
196
  const compiledNodes = [];
160
197
  for (const node of plan.nodes) {
161
198
  let compiled;
162
199
  if (node.kind === "agent") {
163
200
  compiled = await compileAgentNode(outputDirectory, node);
201
+ await injectTeamCompileSupportFiles(outputDirectory, compiled, teamCompileSupport);
164
202
  }
165
203
  else {
166
204
  compiled = await compileTeamNode(outputDirectory, node);
167
205
  }
168
- enforcePolicy(compiled.report, node.value.policyMode, node.value.policyOnDegrade);
169
206
  nodeReports.push(compiled.report);
170
207
  compiledNodes.push(compiled);
171
208
  }
172
- const containerArtifacts = await createContainerArtifacts(plan, compiledNodes);
209
+ augmentNodeReports(compiledNodes, teamCompileSupport);
210
+ for (const compiled of compiledNodes) {
211
+ enforcePolicy(compiled.report, compiled.value.policyMode, compiled.value.policyOnDegrade);
212
+ }
213
+ const moltnetArtifacts = await generateMoltnetArtifacts(plan);
214
+ const hasStagedMoltnetBinaries = moltnetArtifacts
215
+ ? await stageMoltnetBinaries(outputDirectory)
216
+ : false;
217
+ await injectMoltnetWorkspaceFiles(outputDirectory, compiledNodes, moltnetArtifacts);
218
+ const containerArtifacts = await createContainerArtifacts(plan, compiledNodes, {
219
+ hasStagedMoltnetBinaries,
220
+ moltnet: moltnetArtifacts
221
+ });
173
222
  await writeEmittedFiles(outputDirectory, containerArtifacts.files);
174
223
  await Promise.all(containerArtifacts.executablePaths.map((filePath) => chmod(path.join(outputDirectory, filePath), 0o755)));
175
224
  const report = createCompileReport(plan.root, nodeReports, [], containerArtifacts.report);
@@ -0,0 +1,17 @@
1
+ import { type EmittedFile } from "../runtime/index.js";
2
+ import type { MoltnetArtifacts } from "./moltnetArtifacts.js";
3
+ import type { TeamCompileSupport } from "./teamContextSupport.js";
4
+ import type { ResolvedAgentNode, ResolvedTeamNode } from "./types.js";
5
+ export { prepareTeamCompileSupport } from "./teamContextSupport.js";
6
+ export type { TeamCompileSupport } from "./teamContextSupport.js";
7
+ export interface CompiledNodeOutput {
8
+ emittedFiles: EmittedFile[];
9
+ kind: "agent" | "team";
10
+ report: {
11
+ output_dir: string | null;
12
+ };
13
+ value: ResolvedAgentNode | ResolvedTeamNode;
14
+ }
15
+ export declare const writeEmittedFiles: (outputDirectory: string, files: EmittedFile[]) => Promise<void>;
16
+ export declare const injectTeamCompileSupportFiles: (outputDirectory: string, compiled: CompiledNodeOutput, support: TeamCompileSupport) => Promise<void>;
17
+ export declare const injectMoltnetWorkspaceFiles: (outputDirectory: string, compiledNodes: CompiledNodeOutput[], artifacts: MoltnetArtifacts | null) => Promise<void>;
@@ -0,0 +1,136 @@
1
+ import { execFile as execFileCallback } from "node:child_process";
2
+ import { chmod, readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { promisify } from "node:util";
5
+ import { ensureDirectory, writeUtf8File } from "../filesystem/index.js";
6
+ import { getRuntimeAdapter } from "../runtime/index.js";
7
+ import { SpawnfileError } from "../shared/index.js";
8
+ import { resolveMoltnetCliCommand } from "./moltnetBinaries.js";
9
+ import { createMoltnetClientConfigFiles, resolveMoltnetWorkspaceLayout } from "./moltnetClientConfig.js";
10
+ export { prepareTeamCompileSupport } from "./teamContextSupport.js";
11
+ const execFile = promisify(execFileCallback);
12
+ const resolveAgentWorkspacePaths = (node) => {
13
+ const systemInstructionSurface = getRuntimeAdapter(node.runtime.name).systemInstructionSurface;
14
+ if (!systemInstructionSurface) {
15
+ return null;
16
+ }
17
+ const systemInstructionPath = systemInstructionSurface.resolvePath({ node });
18
+ const systemInstructionDirectory = path.posix.dirname(systemInstructionPath);
19
+ return {
20
+ systemInstructionPath,
21
+ spawnfileDirectory: systemInstructionDirectory === "."
22
+ ? ".spawnfile"
23
+ : `${systemInstructionDirectory}/.spawnfile`
24
+ };
25
+ };
26
+ const upsertEmittedFile = (emittedFiles, nextFile) => {
27
+ const existing = emittedFiles.find((file) => file.path === nextFile.path);
28
+ if (existing) {
29
+ existing.content = nextFile.content;
30
+ return existing;
31
+ }
32
+ emittedFiles.push(nextFile);
33
+ return nextFile;
34
+ };
35
+ export const writeEmittedFiles = async (outputDirectory, files) => {
36
+ await Promise.all(files.map(async (file) => {
37
+ const targetPath = path.join(outputDirectory, file.path);
38
+ await ensureDirectory(path.dirname(targetPath));
39
+ await writeUtf8File(targetPath, file.content);
40
+ if (file.mode !== undefined) {
41
+ await chmod(targetPath, file.mode);
42
+ }
43
+ }));
44
+ };
45
+ export const injectTeamCompileSupportFiles = async (outputDirectory, compiled, support) => {
46
+ if (compiled.kind !== "agent" ||
47
+ compiled.value.kind !== "agent" ||
48
+ !compiled.report.output_dir) {
49
+ return;
50
+ }
51
+ const supportFiles = support.filesByAgentSource.get(compiled.value.source);
52
+ if (!supportFiles || supportFiles.length === 0) {
53
+ return;
54
+ }
55
+ const workspacePaths = resolveAgentWorkspacePaths(compiled.value);
56
+ if (!workspacePaths) {
57
+ return;
58
+ }
59
+ const filesToWrite = supportFiles.map((file) => upsertEmittedFile(compiled.emittedFiles, {
60
+ ...file,
61
+ path: file.path.startsWith(".spawnfile/") || file.path === "TEAM.md"
62
+ ? path.posix.join(path.posix.dirname(workspacePaths.spawnfileDirectory), file.path)
63
+ : file.path
64
+ }));
65
+ const rosterBlock = "\n\n<!-- spawnfile-team-context:start -->\n" +
66
+ "## Spawnfile Team Context\n\n" +
67
+ "Read `.spawnfile/team-contexts.md` and `.spawnfile/team-contexts.yaml` for generated team membership, representative context, and surface bindings.\n" +
68
+ "<!-- spawnfile-team-context:end -->\n";
69
+ const existingAgentsMd = compiled.emittedFiles.find((file) => file.path === workspacePaths.systemInstructionPath);
70
+ filesToWrite.push(existingAgentsMd
71
+ ? (() => {
72
+ existingAgentsMd.content += rosterBlock;
73
+ return existingAgentsMd;
74
+ })()
75
+ : upsertEmittedFile(compiled.emittedFiles, {
76
+ content: rosterBlock.trimStart(),
77
+ path: workspacePaths.systemInstructionPath
78
+ }));
79
+ await writeEmittedFiles(path.join(outputDirectory, compiled.report.output_dir), filesToWrite);
80
+ };
81
+ export const injectMoltnetWorkspaceFiles = async (outputDirectory, compiledNodes, artifacts) => {
82
+ if (!artifacts) {
83
+ return;
84
+ }
85
+ let moltnetCliCommand = null;
86
+ for (const compiled of compiledNodes) {
87
+ if (compiled.kind !== "agent" ||
88
+ compiled.value.kind !== "agent" ||
89
+ !compiled.report.output_dir) {
90
+ continue;
91
+ }
92
+ const runtimeOutputDirectory = path.join(outputDirectory, compiled.report.output_dir);
93
+ const moltnetClientConfigFiles = createMoltnetClientConfigFiles(compiled.value, artifacts);
94
+ if (moltnetClientConfigFiles.length === 0) {
95
+ continue;
96
+ }
97
+ const layout = resolveMoltnetWorkspaceLayout(compiled.value.runtime.name, compiled.value.name);
98
+ const workspacePath = path.join(runtimeOutputDirectory, layout.workspaceRootPath);
99
+ for (const file of moltnetClientConfigFiles) {
100
+ upsertEmittedFile(compiled.emittedFiles, file);
101
+ }
102
+ await writeEmittedFiles(runtimeOutputDirectory, moltnetClientConfigFiles);
103
+ if (!moltnetCliCommand) {
104
+ moltnetCliCommand = await resolveMoltnetCliCommand();
105
+ }
106
+ try {
107
+ await execFile(moltnetCliCommand, [
108
+ "skill",
109
+ "install",
110
+ "--runtime",
111
+ layout.cliRuntime,
112
+ "--workspace",
113
+ workspacePath
114
+ ]);
115
+ }
116
+ catch (error) {
117
+ const reason = error instanceof Error ? error.message : String(error);
118
+ throw new SpawnfileError("compile_error", `Unable to install the Moltnet skill into ${compiled.value.name}: ${reason}`);
119
+ }
120
+ const moltnetSkillFiles = await Promise.all(layout.skillPaths.map(async (filePath) => {
121
+ try {
122
+ return {
123
+ content: await readFile(path.join(runtimeOutputDirectory, filePath), "utf8"),
124
+ path: filePath
125
+ };
126
+ }
127
+ catch (error) {
128
+ const reason = error instanceof Error ? error.message : String(error);
129
+ throw new SpawnfileError("compile_error", `Moltnet skill install for ${compiled.value.name} did not produce ${filePath}: ${reason}`);
130
+ }
131
+ }));
132
+ for (const file of moltnetSkillFiles) {
133
+ upsertEmittedFile(compiled.emittedFiles, file);
134
+ }
135
+ }
136
+ };
@@ -1,4 +1,9 @@
1
+ import type { MoltnetArtifacts } from "./moltnetArtifacts.js";
1
2
  import type { CompiledNodeArtifact, GeneratedContainerArtifacts } from "./containerArtifactsTypes.js";
2
3
  import type { CompilePlan } from "./types.js";
3
4
  export type { CompiledNodeArtifact, GeneratedContainerArtifacts } from "./containerArtifactsTypes.js";
4
- export declare const createContainerArtifacts: (plan: CompilePlan, compiledNodes: CompiledNodeArtifact[]) => Promise<GeneratedContainerArtifacts>;
5
+ export interface ContainerArtifactOptions {
6
+ hasStagedMoltnetBinaries?: boolean;
7
+ moltnet?: MoltnetArtifacts | null;
8
+ }
9
+ export declare const createContainerArtifacts: (plan: CompilePlan, compiledNodes: CompiledNodeArtifact[], options?: ContainerArtifactOptions) => Promise<GeneratedContainerArtifacts>;
@@ -1,6 +1,6 @@
1
1
  import { createEnvVariableMap, createRuntimeTargetPlans } from "./containerArtifactsPlans.js";
2
2
  import { createRootfsFiles, renderDockerfile, renderEntrypoint, renderEnvExample } from "./containerArtifactsRender.js";
3
- export const createContainerArtifacts = async (plan, compiledNodes) => {
3
+ export const createContainerArtifacts = async (plan, compiledNodes, options = {}) => {
4
4
  const runtimePlans = await createRuntimeTargetPlans(plan, compiledNodes);
5
5
  const envVariables = [...createEnvVariableMap(compiledNodes, runtimePlans).values()].sort((left, right) => left.name.localeCompare(right.name));
6
6
  const requiredSecrets = envVariables
@@ -17,12 +17,24 @@ export const createContainerArtifacts = async (plan, compiledNodes) => {
17
17
  .sort();
18
18
  const files = [
19
19
  ...createRootfsFiles(runtimePlans),
20
+ ...(options.moltnet?.files ?? []),
20
21
  {
21
- content: await renderDockerfile(runtimePlans),
22
+ content: await renderDockerfile(runtimePlans, {
23
+ hasMoltnet: Boolean(options.moltnet),
24
+ hasStagedMoltnetBinaries: options.hasStagedMoltnetBinaries,
25
+ moltnetPublishedPorts: options.moltnet?.publishedPorts ?? []
26
+ }),
22
27
  path: "Dockerfile"
23
28
  },
24
29
  {
25
- content: renderEntrypoint(runtimePlans, requiredSecrets.filter((secretName) => !modelSecretsRequired.includes(secretName))),
30
+ content: renderEntrypoint(runtimePlans, requiredSecrets.filter((secretName) => !modelSecretsRequired.includes(secretName)), {
31
+ moltnet: options.moltnet
32
+ ? {
33
+ bridgePlans: options.moltnet.bridgePlans,
34
+ serverPlans: options.moltnet.serverPlans
35
+ }
36
+ : undefined
37
+ }),
26
38
  path: "entrypoint.sh"
27
39
  },
28
40
  {
@@ -30,7 +42,9 @@ export const createContainerArtifacts = async (plan, compiledNodes) => {
30
42
  path: ".env.example"
31
43
  }
32
44
  ];
33
- const ports = [...new Set(runtimePlans.flatMap((plan) => (plan.port ? [plan.port] : [])))].sort((left, right) => left - right);
45
+ const runtimePorts = runtimePlans.flatMap((plan) => plan.publishedPort ? [plan.publishedPort] : []);
46
+ const moltnetPorts = options.moltnet?.publishedPorts ?? [];
47
+ const ports = [...new Set([...runtimePorts, ...moltnetPorts])].sort((left, right) => left - right);
34
48
  const runtimeHomes = [
35
49
  ...new Set(runtimePlans.flatMap((plan) => (plan.instancePaths.homePath ? [plan.instancePaths.homePath] : [])))
36
50
  ].sort();
@@ -48,6 +62,14 @@ export const createContainerArtifacts = async (plan, compiledNodes) => {
48
62
  return {
49
63
  executablePaths: ["entrypoint.sh"],
50
64
  files,
65
+ ...(options.moltnet
66
+ ? {
67
+ moltnet: {
68
+ bridgePlans: options.moltnet.bridgePlans,
69
+ serverPlans: options.moltnet.serverPlans
70
+ }
71
+ }
72
+ : {}),
51
73
  report: {
52
74
  dockerfile: "Dockerfile",
53
75
  entrypoint: "entrypoint.sh",
@@ -76,6 +76,9 @@ export const createEnvVariableMap = (compiledNodes, runtimePlans) => {
76
76
  for (const variable of runtimePlan.meta.env ?? []) {
77
77
  register(variable.name, variable.required, variable.description, "runtime");
78
78
  }
79
+ for (const binding of runtimePlan.targetConfigEnvBindings ?? []) {
80
+ register(binding.envName, true, `Injected into ${runtimePlan.runtimeName} runtime config`, "runtime");
81
+ }
79
82
  }
80
83
  return variables;
81
84
  };
@@ -115,6 +118,13 @@ const resolveTargetModelAuthMethods = (target, inputs) => {
115
118
  }
116
119
  return Object.fromEntries([...methods.entries()].sort(([left], [right]) => left.localeCompare(right)));
117
120
  };
121
+ const resolveTargetExposure = (target, inputs) => {
122
+ const sourceIds = new Set(target.sourceIds ?? []);
123
+ if (sourceIds.size === 0) {
124
+ return false;
125
+ }
126
+ return inputs.some((input) => sourceIds.has(input.id) && input.value.kind === "agent" && input.value.expose === true);
127
+ };
118
128
  export const createRuntimeTargetPlans = async (plan, compiledNodes) => {
119
129
  const runtimeNames = Object.keys(plan.runtimes).sort();
120
130
  const runtimePlans = [];
@@ -135,6 +145,7 @@ export const createRuntimeTargetPlans = async (plan, compiledNodes) => {
135
145
  targets.forEach((target, index) => {
136
146
  assertTargetHasConfig(runtimeName, target.id, adapter.container, target.files);
137
147
  const instancePaths = resolveInstancePaths(runtimeName, target.id, adapter.container);
148
+ const portStride = adapter.container.portStride ?? 1;
138
149
  runtimePlans.push({
139
150
  configEnvBindings: resolveTargetConfigEnvBindings(adapter.container, target) ?? [],
140
151
  envFiles: resolveTargetEnvFiles(instancePaths.configPath, target),
@@ -143,9 +154,13 @@ export const createRuntimeTargetPlans = async (plan, compiledNodes) => {
143
154
  meta: adapter.container,
144
155
  modelAuthMethods: resolveTargetModelAuthMethods(target, targetInputs),
145
156
  modelSecretsRequired: resolveTargetModelSecrets(target, targetInputs),
146
- port: adapter.container.port ? adapter.container.port + index : undefined,
157
+ port: adapter.container.port ? adapter.container.port + (index * portStride) : undefined,
158
+ publishedPort: resolveTargetExposure(target, targetInputs) && adapter.container.port
159
+ ? adapter.container.port + (index * portStride)
160
+ : undefined,
147
161
  runtimeName,
148
162
  runtimeRoot: recipe.runtimeRoot,
163
+ targetConfigEnvBindings: target.configEnvBindings,
149
164
  targetFiles: target.files
150
165
  });
151
166
  });
@@ -1,6 +1,8 @@
1
1
  import type { EmittedFile } from "../runtime/index.js";
2
2
  import type { ContainerEnvVariable, RuntimeTargetPlan } from "./containerArtifactsTypes.js";
3
+ import type { EntrypointOptions } from "./containerEntrypointRender.js";
4
+ export { renderEntrypoint } from "./containerEntrypointRender.js";
5
+ export type { EntrypointOptions } from "./containerEntrypointRender.js";
3
6
  export declare const renderEnvExample: (variables: ContainerEnvVariable[]) => string;
4
- export declare const renderDockerfile: (runtimePlans: RuntimeTargetPlan[]) => Promise<string>;
7
+ export declare const renderDockerfile: (runtimePlans: RuntimeTargetPlan[], options?: EntrypointOptions) => Promise<string>;
5
8
  export declare const createRootfsFiles: (runtimePlans: RuntimeTargetPlan[]) => EmittedFile[];
6
- export declare const renderEntrypoint: (runtimePlans: RuntimeTargetPlan[], requiredSecrets: string[]) => string;