spawnfile 0.1.11 → 0.1.13

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 (220) hide show
  1. package/README.md +21 -3
  2. package/dist/auth/runtimeCredentials.js +18 -2
  3. package/dist/cli/devCommands.d.ts +3 -0
  4. package/dist/cli/devCommands.js +115 -0
  5. package/dist/cli/lifecycleCommands.js +103 -1
  6. package/dist/cli/modelCommands.js +3 -0
  7. package/dist/cli/resolveCommandInput.d.ts +19 -0
  8. package/dist/cli/resolveCommandInput.js +25 -0
  9. package/dist/cli/runCli.d.ts +10 -1
  10. package/dist/cli/runCli.js +41 -13
  11. package/dist/cli/runtimeCommands.js +1 -0
  12. package/dist/cli/statusCommand.d.ts +43 -0
  13. package/dist/cli/statusCommand.js +428 -0
  14. package/dist/cli/surfaceCommands.js +4 -0
  15. package/dist/compiler/agentSurfaces.js +2 -4
  16. package/dist/compiler/buildProject.d.ts +9 -1
  17. package/dist/compiler/buildProject.js +44 -5
  18. package/dist/compiler/compileProject.d.ts +2 -0
  19. package/dist/compiler/compileProject.js +22 -11
  20. package/dist/compiler/containerArtifacts.d.ts +1 -0
  21. package/dist/compiler/containerArtifacts.js +208 -32
  22. package/dist/compiler/containerArtifactsPlans.js +7 -4
  23. package/dist/compiler/containerArtifactsRender.d.ts +7 -1
  24. package/dist/compiler/containerArtifactsRender.js +31 -69
  25. package/dist/compiler/containerArtifactsTypes.d.ts +9 -0
  26. package/dist/compiler/containerEntrypointRender.js +22 -32
  27. package/dist/compiler/containerPackageRender.d.ts +15 -0
  28. package/dist/compiler/containerPackageRender.js +63 -0
  29. package/dist/compiler/containerTargetResources.js +3 -1
  30. package/dist/compiler/index.d.ts +1 -0
  31. package/dist/compiler/index.js +1 -0
  32. package/dist/compiler/moltnetArtifacts.js +21 -1
  33. package/dist/compiler/moltnetBinaries.d.ts +4 -1
  34. package/dist/compiler/moltnetBinaries.js +24 -3
  35. package/dist/compiler/moltnetClientConfig.d.ts +1 -1
  36. package/dist/compiler/moltnetClientConfig.js +14 -16
  37. package/dist/compiler/moltnetNodeConfig.js +2 -4
  38. package/dist/compiler/moltnetResolution.js +1 -1
  39. package/dist/compiler/moltnetRoomMemberships.js +1 -2
  40. package/dist/compiler/moltnetRuntimeConfig.d.ts +1 -1
  41. package/dist/compiler/moltnetRuntimeConfig.js +9 -10
  42. package/dist/compiler/networkBinding.d.ts +14 -0
  43. package/dist/compiler/networkBinding.js +34 -0
  44. package/dist/compiler/publishProject.d.ts +13 -0
  45. package/dist/compiler/publishProject.js +72 -0
  46. package/dist/compiler/runProject.d.ts +26 -12
  47. package/dist/compiler/runProject.js +109 -126
  48. package/dist/compiler/runProjectAuth.d.ts +5 -0
  49. package/dist/compiler/runProjectAuth.js +98 -0
  50. package/dist/compiler/runProjectDocker.d.ts +19 -0
  51. package/dist/compiler/runProjectDocker.js +202 -0
  52. package/dist/compiler/types.d.ts +1 -2
  53. package/dist/compiler/upProject.d.ts +6 -0
  54. package/dist/compiler/upProject.js +43 -8
  55. package/dist/compiler/view/buildOrganizationView.js +4 -1
  56. package/dist/compiler/view/declarations.d.ts +4 -0
  57. package/dist/compiler/view/declarations.js +104 -0
  58. package/dist/compiler/view/renderNetworks.js +1 -2
  59. package/dist/compiler/view/renderTree.js +6 -2
  60. package/dist/compiler/view/types.d.ts +64 -1
  61. package/dist/deployment/dockerInspect.d.ts +29 -0
  62. package/dist/deployment/dockerInspect.js +130 -0
  63. package/dist/deployment/dockerLabels.d.ts +16 -0
  64. package/dist/deployment/dockerLabels.js +32 -0
  65. package/dist/deployment/dockerLogs.d.ts +24 -0
  66. package/dist/deployment/dockerLogs.js +96 -0
  67. package/dist/deployment/dockerManager.d.ts +59 -0
  68. package/dist/deployment/dockerManager.js +76 -0
  69. package/dist/deployment/dockerProbeGateway.d.ts +16 -0
  70. package/dist/deployment/dockerProbeGateway.js +48 -0
  71. package/dist/deployment/dockerRecover.d.ts +17 -0
  72. package/dist/deployment/dockerRecover.js +129 -0
  73. package/dist/deployment/homeStore.d.ts +19 -0
  74. package/dist/deployment/homeStore.js +130 -0
  75. package/dist/deployment/index.d.ts +10 -0
  76. package/dist/deployment/index.js +10 -0
  77. package/dist/deployment/names.d.ts +3 -0
  78. package/dist/deployment/names.js +12 -0
  79. package/dist/deployment/record.d.ts +76 -0
  80. package/dist/deployment/record.js +174 -0
  81. package/dist/deployment/target.d.ts +35 -0
  82. package/dist/deployment/target.js +96 -0
  83. package/dist/dev/activity.d.ts +14 -0
  84. package/dist/dev/activity.js +48 -0
  85. package/dist/dev/docker.d.ts +16 -0
  86. package/dist/dev/docker.js +71 -0
  87. package/dist/dev/index.d.ts +2 -0
  88. package/dist/dev/index.js +2 -0
  89. package/dist/dev/project.d.ts +35 -0
  90. package/dist/dev/project.js +164 -0
  91. package/dist/distribution/buildDistributionReport.d.ts +22 -0
  92. package/dist/distribution/buildDistributionReport.js +51 -0
  93. package/dist/distribution/consumeImage.d.ts +29 -0
  94. package/dist/distribution/consumeImage.js +254 -0
  95. package/dist/distribution/consumeImageSupport.d.ts +18 -0
  96. package/dist/distribution/consumeImageSupport.js +63 -0
  97. package/dist/distribution/distributionReportSchema.d.ts +84 -0
  98. package/dist/distribution/distributionReportSchema.js +107 -0
  99. package/dist/distribution/dockerRunner.d.ts +5 -0
  100. package/dist/distribution/dockerRunner.js +21 -0
  101. package/dist/distribution/extractImage.d.ts +25 -0
  102. package/dist/distribution/extractImage.js +96 -0
  103. package/dist/distribution/fingerprint.d.ts +2 -0
  104. package/dist/distribution/fingerprint.js +14 -0
  105. package/dist/distribution/imageRef.d.ts +9 -0
  106. package/dist/distribution/imageRef.js +65 -0
  107. package/dist/distribution/imageRuntimeAuth.d.ts +19 -0
  108. package/dist/distribution/imageRuntimeAuth.js +71 -0
  109. package/dist/distribution/index.d.ts +24 -0
  110. package/dist/distribution/index.js +16 -0
  111. package/dist/distribution/preflight.d.ts +11 -0
  112. package/dist/distribution/preflight.js +65 -0
  113. package/dist/distribution/projectImageView.d.ts +8 -0
  114. package/dist/distribution/projectImageView.js +83 -0
  115. package/dist/distribution/projectName.d.ts +1 -0
  116. package/dist/distribution/projectName.js +12 -0
  117. package/dist/distribution/renderImageInterface.d.ts +30 -0
  118. package/dist/distribution/renderImageInterface.js +86 -0
  119. package/dist/distribution/tarReader.d.ts +10 -0
  120. package/dist/distribution/tarReader.js +69 -0
  121. package/dist/distribution/types.d.ts +80 -0
  122. package/dist/distribution/types.js +4 -0
  123. package/dist/distribution/verifyDistributionReport.d.ts +12 -0
  124. package/dist/distribution/verifyDistributionReport.js +29 -0
  125. package/dist/filesystem/paths.d.ts +6 -0
  126. package/dist/filesystem/paths.js +11 -0
  127. package/dist/manifest/loadManifest.js +22 -2
  128. package/dist/manifest/renderSpawnfile.js +2 -4
  129. package/dist/manifest/schemas.d.ts +17 -41
  130. package/dist/manifest/surfaceSchemas.d.ts +19 -47
  131. package/dist/manifest/surfaceSchemas.js +3 -6
  132. package/dist/report/createReport.d.ts +7 -1
  133. package/dist/report/createReport.js +28 -1
  134. package/dist/report/types.d.ts +43 -0
  135. package/dist/runtime/common.d.ts +2 -0
  136. package/dist/runtime/common.js +1 -1
  137. package/dist/runtime/container.d.ts +1 -0
  138. package/dist/runtime/container.js +82 -15
  139. package/dist/runtime/index.d.ts +1 -0
  140. package/dist/runtime/index.js +1 -0
  141. package/dist/runtime/install.d.ts +1 -11
  142. package/dist/runtime/install.js +27 -30
  143. package/dist/runtime/openclaw/adapter.js +9 -1
  144. package/dist/runtime/openclaw/configAuth.d.ts +15 -0
  145. package/dist/runtime/openclaw/configAuth.js +46 -0
  146. package/dist/runtime/openclaw/runAuth.d.ts +6 -0
  147. package/dist/runtime/openclaw/runAuth.js +7 -55
  148. package/dist/runtime/openclaw/statusProbes.d.ts +2 -0
  149. package/dist/runtime/openclaw/statusProbes.js +31 -0
  150. package/dist/runtime/pi/adapter.d.ts +4 -0
  151. package/dist/runtime/pi/adapter.js +198 -0
  152. package/dist/runtime/pi/appActivitySource.d.ts +1 -0
  153. package/dist/runtime/pi/appActivitySource.js +87 -0
  154. package/dist/runtime/pi/appCoreSource.d.ts +1 -0
  155. package/dist/runtime/pi/appCoreSource.js +354 -0
  156. package/dist/runtime/pi/appPreludeSource.d.ts +1 -0
  157. package/dist/runtime/pi/appPreludeSource.js +103 -0
  158. package/dist/runtime/pi/appSource.d.ts +1 -0
  159. package/dist/runtime/pi/appSource.js +8 -0
  160. package/dist/runtime/pi/appTemplate.d.ts +28 -0
  161. package/dist/runtime/pi/appTemplate.js +144 -0
  162. package/dist/runtime/pi/runAuth.d.ts +2 -0
  163. package/dist/runtime/pi/runAuth.js +68 -0
  164. package/dist/runtime/picoclaw/adapter.js +10 -0
  165. package/dist/runtime/picoclaw/runAuth.d.ts +7 -0
  166. package/dist/runtime/picoclaw/runAuth.js +8 -68
  167. package/dist/runtime/picoclaw/statusProbes.d.ts +2 -0
  168. package/dist/runtime/picoclaw/statusProbes.js +100 -0
  169. package/dist/runtime/registry.d.ts +0 -5
  170. package/dist/runtime/registry.js +5 -12
  171. package/dist/runtime/statusProbes.d.ts +16 -0
  172. package/dist/runtime/statusProbes.js +41 -0
  173. package/dist/runtime/types.d.ts +53 -1
  174. package/dist/shared/errors.d.ts +6 -0
  175. package/dist/shared/errors.js +9 -0
  176. package/dist/status/buildStatus.d.ts +13 -0
  177. package/dist/status/buildStatus.js +266 -0
  178. package/dist/status/compileReport.d.ts +96 -0
  179. package/dist/status/compileReport.js +230 -0
  180. package/dist/status/compiledContainerObservations.d.ts +5 -0
  181. package/dist/status/compiledContainerObservations.js +63 -0
  182. package/dist/status/deploymentLogs.d.ts +12 -0
  183. package/dist/status/deploymentLogs.js +45 -0
  184. package/dist/status/deployments.d.ts +14 -0
  185. package/dist/status/deployments.js +121 -0
  186. package/dist/status/imageStatusReport.d.ts +10 -0
  187. package/dist/status/imageStatusReport.js +73 -0
  188. package/dist/status/index.d.ts +14 -0
  189. package/dist/status/index.js +14 -0
  190. package/dist/status/moltnetProbes.d.ts +21 -0
  191. package/dist/status/moltnetProbes.js +240 -0
  192. package/dist/status/registryDrift.d.ts +28 -0
  193. package/dist/status/registryDrift.js +119 -0
  194. package/dist/status/renderStatus.d.ts +2 -0
  195. package/dist/status/renderStatus.js +230 -0
  196. package/dist/status/runtimeProbes.d.ts +13 -0
  197. package/dist/status/runtimeProbes.js +111 -0
  198. package/dist/status/selectionSubjects.d.ts +8 -0
  199. package/dist/status/selectionSubjects.js +77 -0
  200. package/dist/status/selectors.d.ts +14 -0
  201. package/dist/status/selectors.js +90 -0
  202. package/dist/status/traversal.d.ts +6 -0
  203. package/dist/status/traversal.js +15 -0
  204. package/dist/status/types.d.ts +113 -0
  205. package/dist/status/types.js +1 -0
  206. package/package.json +9 -2
  207. package/runtimes.yaml +44 -21
  208. package/dist/runtime/tinyclaw/adapter.d.ts +0 -2
  209. package/dist/runtime/tinyclaw/adapter.js +0 -284
  210. package/dist/runtime/tinyclaw/runAuth.d.ts +0 -2
  211. package/dist/runtime/tinyclaw/runAuth.js +0 -49
  212. package/dist/runtime/tinyclaw/scaffold-assets/AGENTS.md +0 -160
  213. package/dist/runtime/tinyclaw/scaffold-assets/CLAUDE.md +0 -5
  214. package/dist/runtime/tinyclaw/scaffold-assets/SOUL.md +0 -177
  215. package/dist/runtime/tinyclaw/scaffold.d.ts +0 -2
  216. package/dist/runtime/tinyclaw/scaffold.js +0 -24
  217. package/dist/runtime/tinyclaw/schedules.d.ts +0 -9
  218. package/dist/runtime/tinyclaw/schedules.js +0 -62
  219. package/dist/runtime/tinyclaw/surfaces.d.ts +0 -8
  220. package/dist/runtime/tinyclaw/surfaces.js +0 -94
@@ -22,9 +22,7 @@ const createTeamOutputDirectory = (baseDirectory, runtimeName, node) => path.joi
22
22
  const compileAgentNode = async (baseDirectory, node) => {
23
23
  const runtime = await assertRuntimeCanCompile(node.runtimeName ?? node.value.runtime.name);
24
24
  const adapter = getRuntimeAdapter(runtime.name);
25
- const diagnostics = [
26
- ...createRuntimeLifecycleDiagnostics(runtime)
27
- ];
25
+ const diagnostics = [...createRuntimeLifecycleDiagnostics(runtime)];
28
26
  for (const diagnostic of adapter.validateRuntimeOptions?.(node.value.runtime.options) ?? []) {
29
27
  diagnostics.push(diagnostic);
30
28
  }
@@ -37,6 +35,7 @@ const compileAgentNode = async (baseDirectory, node) => {
37
35
  await writeEmittedFiles(outputDirectory, result.files);
38
36
  return {
39
37
  emittedFiles: result.files,
38
+ id: node.id,
40
39
  kind: node.kind,
41
40
  report: {
42
41
  capabilities: result.capabilities,
@@ -63,6 +62,7 @@ const compileTeamNode = async (baseDirectory, node) => {
63
62
  if (!runtimeName) {
64
63
  return {
65
64
  emittedFiles: [],
65
+ id: node.id,
66
66
  kind: node.kind,
67
67
  report: {
68
68
  capabilities: createTeamCapabilities("degraded", "Team spans multiple runtimes and cannot lower to one native team artifact in v0.1"),
@@ -88,6 +88,7 @@ const compileTeamNode = async (baseDirectory, node) => {
88
88
  if (!adapter.compileTeam) {
89
89
  return {
90
90
  emittedFiles: [],
91
+ id: node.id,
91
92
  kind: node.kind,
92
93
  report: {
93
94
  capabilities: createTeamCapabilities("degraded", `Runtime ${runtime.name} does not provide native team compilation in v0.1`),
@@ -113,6 +114,7 @@ const compileTeamNode = async (baseDirectory, node) => {
113
114
  await writeEmittedFiles(outputDirectory, result.files);
114
115
  return {
115
116
  emittedFiles: result.files,
117
+ id: node.id,
116
118
  kind: node.kind,
117
119
  report: {
118
120
  capabilities: result.capabilities,
@@ -200,7 +202,11 @@ const augmentNodeReports = (compiledNodes, support) => {
200
202
  };
201
203
  export const compileProject = async (inputPath, options = {}) => {
202
204
  const plan = await buildCompilePlan(inputPath);
203
- const outputDirectory = path.resolve(options.outputDirectory ?? DEFAULT_OUTPUT_DIRECTORY);
205
+ // The default output directory lives under the resolved project root, not the
206
+ // process working directory, so `spawnfile compile ./org` writes ./org/.spawn.
207
+ const outputDirectory = options.outputDirectory
208
+ ? path.resolve(options.outputDirectory)
209
+ : path.join(path.dirname(plan.root), DEFAULT_OUTPUT_DIRECTORY);
204
210
  if (options.clean ?? true) {
205
211
  await removeDirectory(outputDirectory);
206
212
  }
@@ -226,20 +232,25 @@ export const compileProject = async (inputPath, options = {}) => {
226
232
  }
227
233
  const moltnetArtifacts = await generateMoltnetArtifacts(plan);
228
234
  const hasStagedMoltnetBinaries = moltnetArtifacts
229
- ? await stageMoltnetBinaries(outputDirectory)
235
+ ? await stageMoltnetBinaries(outputDirectory, {
236
+ architecture: options.containerArchitecture
237
+ })
230
238
  : false;
231
239
  await injectMoltnetWorkspaceFiles(outputDirectory, compiledNodes, moltnetArtifacts);
240
+ const generatedAt = new Date().toISOString();
232
241
  const containerArtifacts = await createContainerArtifacts(plan, compiledNodes, {
242
+ generatedAt,
233
243
  hasStagedMoltnetBinaries,
234
244
  moltnet: moltnetArtifacts
235
245
  });
236
246
  await writeEmittedFiles(outputDirectory, containerArtifacts.files);
237
247
  await Promise.all(containerArtifacts.executablePaths.map((filePath) => chmod(path.join(outputDirectory, filePath), 0o755)));
238
- const report = createCompileReport(plan.root, nodeReports, [], containerArtifacts.report);
239
- const reportPath = await writeCompileReport(outputDirectory, report);
240
- return {
248
+ const report = createCompileReport(plan.root, nodeReports, [], containerArtifacts.report, {
249
+ compileFingerprint: containerArtifacts.distribution.fingerprint,
250
+ generatedAt,
241
251
  outputDirectory,
242
- report,
243
- reportPath
244
- };
252
+ projectName: containerArtifacts.distribution.report.organization.project
253
+ });
254
+ const reportPath = await writeCompileReport(outputDirectory, report);
255
+ return { outputDirectory, report, reportPath };
245
256
  };
@@ -3,6 +3,7 @@ import type { CompiledNodeArtifact, GeneratedContainerArtifacts } from "./contai
3
3
  import type { CompilePlan } from "./types.js";
4
4
  export type { CompiledNodeArtifact, GeneratedContainerArtifacts } from "./containerArtifactsTypes.js";
5
5
  export interface ContainerArtifactOptions {
6
+ generatedAt?: string;
6
7
  hasStagedMoltnetBinaries?: boolean;
7
8
  moltnet?: MoltnetArtifacts | null;
8
9
  }
@@ -1,5 +1,106 @@
1
+ import { buildDistributionReport, createDistributionImageLabels, DISTRIBUTION_REPORT_OUTPUT_FILE, normalizeProjectLabelSlug } from "../distribution/index.js";
2
+ import { SpawnfileError } from "../shared/index.js";
1
3
  import { createEnvVariableMap, createRuntimeTargetPlans } from "./containerArtifactsPlans.js";
2
4
  import { createRootfsFiles, renderDockerfile, renderEntrypoint, renderEnvExample } from "./containerArtifactsRender.js";
5
+ const createOrganizationSummary = (plan, compiledNodes) => {
6
+ const nodes = compiledNodes.map((node) => ({
7
+ id: node.id ?? `${node.kind}:${node.slug}`,
8
+ kind: node.kind,
9
+ name: node.value.name,
10
+ runtimeName: node.runtimeName,
11
+ source: node.value.source
12
+ }));
13
+ const rootNode = nodes.find((node) => node.source === plan.root)
14
+ ?? nodes.find((node) => !plan.edges.some((edge) => edge.to === node.id))
15
+ ?? nodes[0];
16
+ if (!rootNode) {
17
+ throw new SpawnfileError("compile_error", `Unable to resolve the root node for ${plan.root}`);
18
+ }
19
+ const memberEdges = plan.edges.filter((edge) => edge.kind === "team_member");
20
+ const teamsByAgent = new Map();
21
+ for (const edge of memberEdges) {
22
+ teamsByAgent.set(edge.to, [...(teamsByAgent.get(edge.to) ?? []), edge.from]);
23
+ }
24
+ const agentNodes = nodes.filter((node) => node.kind === "agent");
25
+ const teamNodes = nodes.filter((node) => node.kind === "team");
26
+ const agentIds = new Set(agentNodes.map((node) => node.id));
27
+ return {
28
+ agents: agentNodes
29
+ .map((node) => ({
30
+ id: node.id,
31
+ name: node.name,
32
+ runtime: node.runtimeName,
33
+ teams: [...(teamsByAgent.get(node.id) ?? [])].sort()
34
+ }))
35
+ .sort((left, right) => left.id.localeCompare(right.id)),
36
+ project: rootNode.name,
37
+ teams: teamNodes
38
+ .map((node) => ({
39
+ agents: memberEdges
40
+ .filter((edge) => edge.from === node.id && agentIds.has(edge.to))
41
+ .map((edge) => edge.to)
42
+ .sort(),
43
+ id: node.id,
44
+ name: node.name
45
+ }))
46
+ .sort((left, right) => left.id.localeCompare(right.id))
47
+ };
48
+ };
49
+ const resolveMoltnetOperatorTokenSecret = (plan) => {
50
+ const client = plan.server.auth.client;
51
+ if (!client) {
52
+ return undefined;
53
+ }
54
+ if (client.token_env) {
55
+ return client.token_env;
56
+ }
57
+ if (client.token_path) {
58
+ return client.token_path;
59
+ }
60
+ if (!client.token_id) {
61
+ return undefined;
62
+ }
63
+ return plan.server.auth.tokens?.find((token) => token.id === client.token_id)?.secret;
64
+ };
65
+ const createMoltnetSummary = (moltnet) => {
66
+ if (!moltnet) {
67
+ return undefined;
68
+ }
69
+ return {
70
+ node_plans: moltnet.nodePlans.map((plan) => ({
71
+ config_path: plan.configPath,
72
+ network_id: plan.networkId
73
+ })),
74
+ server_plans: moltnet.serverPlans.map((plan) => {
75
+ const operatorTokenSecret = resolveMoltnetOperatorTokenSecret(plan);
76
+ return {
77
+ auth_mode: plan.server.auth.mode,
78
+ base_url: plan.baseUrl,
79
+ ...(plan.configPath ? { config_path: plan.configPath } : {}),
80
+ ...(plan.server.mode === "managed"
81
+ ? { direct_messages: plan.server.direct_messages }
82
+ : {}),
83
+ id: plan.id,
84
+ mode: plan.mode,
85
+ network_id: plan.networkId,
86
+ ...(operatorTokenSecret ? { operator_token_secret: operatorTokenSecret } : {}),
87
+ ...(plan.port ? { port: plan.port } : {}),
88
+ ...(plan.server.auth.public_read !== undefined
89
+ ? { public_read: plan.server.auth.public_read }
90
+ : {}),
91
+ rooms: plan.rooms.map((room) => ({
92
+ id: room.id,
93
+ members: [...room.members],
94
+ ...(room.visibility ? { visibility: room.visibility } : {}),
95
+ ...(room.write_policy ? { write_policy: room.write_policy } : {})
96
+ })),
97
+ ...(plan.server.mode === "managed"
98
+ ? { store_kind: plan.server.store.kind }
99
+ : {})
100
+ };
101
+ })
102
+ };
103
+ };
3
104
  export const createContainerArtifacts = async (plan, compiledNodes, options = {}) => {
4
105
  const runtimePlans = await createRuntimeTargetPlans(plan, compiledNodes);
5
106
  const envVariables = [...createEnvVariableMap(compiledNodes, runtimePlans).values()].sort((left, right) => left.name.localeCompare(right.name));
@@ -23,11 +124,108 @@ export const createContainerArtifacts = async (plan, compiledNodes, options = {}
23
124
  volume_name: mount.volumeName
24
125
  }))
25
126
  .sort((left, right) => left.id.localeCompare(right.id));
127
+ const runtimeInternalPorts = runtimePlans.flatMap((plan) => plan.port ? [plan.port] : []);
128
+ const internalPorts = [...new Set([...runtimeInternalPorts, ...(options.moltnet?.ports ?? [])])].sort((left, right) => left - right);
129
+ const runtimePublishedPorts = runtimePlans.flatMap((plan) => plan.publishedPort ? [plan.publishedPort] : []);
130
+ const publishedPorts = [
131
+ ...new Set([...runtimePublishedPorts, ...(options.moltnet?.publishedPorts ?? [])])
132
+ ].sort((left, right) => left - right);
133
+ const portMappings = runtimePlans
134
+ .flatMap((plan) => plan.port && plan.publishedPort
135
+ ? [{ internal_port: plan.port, published_port: plan.publishedPort }]
136
+ : [])
137
+ .sort((left, right) => left.published_port - right.published_port
138
+ || left.internal_port - right.internal_port);
139
+ const runtimeHomes = [
140
+ ...new Set(runtimePlans.flatMap((plan) => (plan.instancePaths.homePath ? [plan.instancePaths.homePath] : [])))
141
+ ].sort();
142
+ const runtimeInstances = runtimePlans
143
+ .map((plan) => ({
144
+ config_path: plan.instancePaths.configPath,
145
+ home_path: plan.instancePaths.homePath ?? null,
146
+ id: plan.id,
147
+ internal_port: plan.port ?? null,
148
+ model_auth_methods: plan.modelAuthMethods,
149
+ model_secrets_required: plan.modelSecretsRequired,
150
+ node_ids: [...(plan.sourceIds ?? [])],
151
+ published_port: plan.publishedPort ?? null,
152
+ runtime: plan.runtimeName,
153
+ workspace_path: plan.instancePaths.workspacePath
154
+ }))
155
+ .sort((left, right) => left.id.localeCompare(right.id));
156
+ const runtimesInstalled = [...new Set(runtimePlans.map((plan) => plan.runtimeName))].sort();
157
+ const workspaceResources = [
158
+ ...new Map(runtimePlans.flatMap((plan) => (plan.resources ?? []).map((resource) => [
159
+ `${resource.kind}:${resource.id}:${resource.linkPath}`,
160
+ {
161
+ backing_path: resource.backingPath,
162
+ id: resource.id,
163
+ kind: resource.kind,
164
+ link_path: resource.linkPath,
165
+ mode: resource.mode,
166
+ mount: resource.mount,
167
+ sharing: resource.sharing
168
+ }
169
+ ]))).values()
170
+ ].sort((left, right) => left.link_path.localeCompare(right.link_path) || left.id.localeCompare(right.id));
171
+ const moltnetSummary = createMoltnetSummary(options.moltnet);
172
+ const organization = createOrganizationSummary(plan, compiledNodes);
173
+ const projectSlug = normalizeProjectLabelSlug(organization.project);
174
+ const mergedModelAuthMethods = Object.assign({}, ...runtimePlans.map((runtimePlan) => runtimePlan.modelAuthMethods));
175
+ const moltnetNetworks = [
176
+ ...new Map((options.moltnet?.serverPlans ?? []).map((serverPlan) => [
177
+ serverPlan.networkId,
178
+ {
179
+ binding: "env",
180
+ id: serverPlan.networkId,
181
+ server_mode: serverPlan.mode
182
+ }
183
+ ])).values()
184
+ ];
185
+ const distributionReport = buildDistributionReport({
186
+ envVariables: envVariables.map((variable) => ({
187
+ categories: variable.categories,
188
+ generated: variable.generated,
189
+ name: variable.name,
190
+ required: variable.required
191
+ })),
192
+ generatedAt: options.generatedAt ?? new Date().toISOString(),
193
+ internalPorts,
194
+ modelAuthMethods: mergedModelAuthMethods,
195
+ moltnetNetworks,
196
+ organization,
197
+ persistentMounts: persistentMounts.map((mount) => ({
198
+ durability: "persistent",
199
+ id: mount.id,
200
+ kind: "volume",
201
+ target: mount.mount_path
202
+ })),
203
+ portMappings,
204
+ publishedPorts,
205
+ resources: workspaceResources.map((resource) => ({
206
+ id: resource.id,
207
+ kind: resource.kind,
208
+ link_path: resource.link_path,
209
+ mode: resource.mode,
210
+ mount: resource.mount,
211
+ sharing: resource.sharing
212
+ })),
213
+ runtimeInstances
214
+ });
215
+ const distributionLabels = createDistributionImageLabels(projectSlug, distributionReport.compile_fingerprint);
26
216
  const files = [
27
217
  ...createRootfsFiles(runtimePlans),
28
218
  ...(options.moltnet?.files ?? []),
219
+ {
220
+ content: `${JSON.stringify(distributionReport, null, 2)}\n`,
221
+ path: DISTRIBUTION_REPORT_OUTPUT_FILE
222
+ },
29
223
  {
30
224
  content: await renderDockerfile(runtimePlans, {
225
+ distribution: {
226
+ labels: distributionLabels,
227
+ reportOutputFile: DISTRIBUTION_REPORT_OUTPUT_FILE
228
+ },
31
229
  hasMoltnet: Boolean(options.moltnet),
32
230
  hasStagedMoltnetBinaries: options.hasStagedMoltnetBinaries,
33
231
  moltnetPublishedPorts: options.moltnet?.publishedPorts ?? [],
@@ -51,38 +249,12 @@ export const createContainerArtifacts = async (plan, compiledNodes, options = {}
51
249
  path: ".env.example"
52
250
  }
53
251
  ];
54
- const runtimePorts = runtimePlans.flatMap((plan) => plan.publishedPort ? [plan.publishedPort] : []);
55
- const moltnetPorts = options.moltnet?.publishedPorts ?? [];
56
- const ports = [...new Set([...runtimePorts, ...moltnetPorts])].sort((left, right) => left - right);
57
- const runtimeHomes = [
58
- ...new Set(runtimePlans.flatMap((plan) => (plan.instancePaths.homePath ? [plan.instancePaths.homePath] : [])))
59
- ].sort();
60
- const runtimeInstances = runtimePlans
61
- .map((plan) => ({
62
- config_path: plan.instancePaths.configPath,
63
- home_path: plan.instancePaths.homePath ?? null,
64
- id: plan.id,
65
- model_auth_methods: plan.modelAuthMethods,
66
- model_secrets_required: plan.modelSecretsRequired,
67
- runtime: plan.runtimeName
68
- }))
69
- .sort((left, right) => left.id.localeCompare(right.id));
70
- const runtimesInstalled = [...new Set(runtimePlans.map((plan) => plan.runtimeName))].sort();
71
- const workspaceResources = [
72
- ...new Map(runtimePlans.flatMap((plan) => (plan.resources ?? []).map((resource) => [
73
- `${resource.kind}:${resource.id}:${resource.linkPath}`,
74
- {
75
- backing_path: resource.backingPath,
76
- id: resource.id,
77
- kind: resource.kind,
78
- link_path: resource.linkPath,
79
- mode: resource.mode,
80
- mount: resource.mount,
81
- sharing: resource.sharing
82
- }
83
- ]))).values()
84
- ].sort((left, right) => left.link_path.localeCompare(right.link_path) || left.id.localeCompare(right.id));
85
252
  return {
253
+ distribution: {
254
+ fingerprint: distributionReport.compile_fingerprint,
255
+ labels: distributionLabels,
256
+ report: distributionReport
257
+ },
86
258
  executablePaths: ["entrypoint.sh"],
87
259
  files,
88
260
  ...(options.moltnet
@@ -97,8 +269,12 @@ export const createContainerArtifacts = async (plan, compiledNodes, options = {}
97
269
  dockerfile: "Dockerfile",
98
270
  entrypoint: "entrypoint.sh",
99
271
  env_example: ".env.example",
272
+ internal_ports: internalPorts,
100
273
  model_secrets_required: modelSecretsRequired,
101
- ports,
274
+ ...(moltnetSummary ? { moltnet: moltnetSummary } : {}),
275
+ port_mappings: portMappings,
276
+ ports: publishedPorts,
277
+ published_ports: publishedPorts,
102
278
  runtime_instances: runtimeInstances,
103
279
  runtime_homes: runtimeHomes,
104
280
  runtime_secrets_required: runtimeSecretsRequired,
@@ -65,12 +65,13 @@ const resolveInstancePaths = (runtimeName, targetId, meta) => {
65
65
  };
66
66
  export const createEnvVariableMap = (compiledNodes, runtimePlans) => {
67
67
  const variables = new Map();
68
- const register = (name, required, description, category) => {
68
+ const register = (name, required, description, category, generated = false) => {
69
69
  const current = variables.get(name);
70
70
  if (!current) {
71
71
  variables.set(name, {
72
72
  categories: [category],
73
73
  description,
74
+ generated,
74
75
  name,
75
76
  required
76
77
  });
@@ -79,6 +80,7 @@ export const createEnvVariableMap = (compiledNodes, runtimePlans) => {
79
80
  variables.set(name, {
80
81
  ...current,
81
82
  categories: [...new Set([...current.categories, category])],
83
+ generated: current.generated && generated,
82
84
  required: current.required || required
83
85
  });
84
86
  };
@@ -104,10 +106,10 @@ export const createEnvVariableMap = (compiledNodes, runtimePlans) => {
104
106
  }
105
107
  for (const runtimePlan of runtimePlans) {
106
108
  for (const variable of runtimePlan.meta.env ?? []) {
107
- register(variable.name, variable.required, variable.description, "runtime");
109
+ register(variable.name, variable.required, variable.description, "runtime", variable.generated ?? false);
108
110
  }
109
111
  for (const binding of runtimePlan.targetConfigEnvBindings ?? []) {
110
- register(binding.envName, true, `Injected into ${runtimePlan.runtimeName} runtime config`, "runtime");
112
+ register(binding.envName, true, `Injected into ${runtimePlan.runtimeName} runtime config`, "runtime", binding.generated ?? false);
111
113
  }
112
114
  }
113
115
  return variables;
@@ -165,7 +167,7 @@ export const createRuntimeTargetPlans = async (plan, compiledNodes) => {
165
167
  .filter((node) => node.runtimeName === runtimeName && node.emittedFiles.length > 0)
166
168
  .map((node) => ({
167
169
  emittedFiles: node.emittedFiles,
168
- id: `${node.kind}:${node.slug}`,
170
+ id: node.id ?? `${node.kind}:${node.slug}`,
169
171
  kind: node.kind,
170
172
  slug: node.slug,
171
173
  value: node.value
@@ -192,6 +194,7 @@ export const createRuntimeTargetPlans = async (plan, compiledNodes) => {
192
194
  resources: resolveTargetResources(target, targetInputs, instancePaths, adapter.container),
193
195
  runtimeName,
194
196
  runtimeRoot: recipe.runtimeRoot,
197
+ sourceIds: [...(target.sourceIds ?? [])].sort(),
195
198
  targetConfigEnvBindings: target.configEnvBindings,
196
199
  targetFiles: target.files
197
200
  });
@@ -4,5 +4,11 @@ import type { EntrypointOptions } from "./containerEntrypointRender.js";
4
4
  export { renderEntrypoint } from "./containerEntrypointRender.js";
5
5
  export type { EntrypointOptions } from "./containerEntrypointRender.js";
6
6
  export declare const renderEnvExample: (variables: ContainerEnvVariable[]) => string;
7
- export declare const renderDockerfile: (runtimePlans: RuntimeTargetPlan[], options?: EntrypointOptions) => Promise<string>;
7
+ export interface DockerfileDistributionOptions {
8
+ labels: Record<string, string>;
9
+ reportOutputFile: string;
10
+ }
11
+ export declare const renderDockerfile: (runtimePlans: RuntimeTargetPlan[], options?: EntrypointOptions & {
12
+ distribution?: DockerfileDistributionOptions;
13
+ }) => Promise<string>;
8
14
  export declare const createRootfsFiles: (runtimePlans: RuntimeTargetPlan[]) => EmittedFile[];
@@ -1,18 +1,18 @@
1
1
  import path from "node:path";
2
+ import { DISTRIBUTION_REPORT_IMAGE_PATH } from "../distribution/index.js";
2
3
  import { createRuntimeInstallRecipe } from "../runtime/index.js";
3
4
  import { SpawnfileError } from "../shared/index.js";
4
5
  export { renderEntrypoint } from "./containerEntrypointRender.js";
5
6
  import { MOLTNET_BIN_DIRECTORY, MOLTNET_BINARY_NAMES } from "./moltnetBinaries.js";
7
+ import { collectPackagesByManager, createAptPackageInstallItem, createNpmPackageInstallCommand, createNpmPackageInstallItem, createPackageInstallCommand, createPipxPackageInstallCommand, createPipxPackageInstallItem, getNpmInstallItemName } from "./containerPackageRender.js";
6
8
  const CONTAINER_ROOTFS_ROOT = "container/rootfs";
7
9
  const GATEWAY_PORT_PLACEHOLDER = "<gateway-port>";
8
10
  const MOLTNET_INSTALL_SCRIPT_URL = "https://moltnet.dev/install.sh";
9
11
  const MOLTNET_RELEASE_METADATA_URL = "https://api.github.com/repos/noopolis/moltnet/releases/latest";
10
12
  const WORKSPACE_PLACEHOLDER = "<workspace-path>";
13
+ const RUNTIME_ROOT_PLACEHOLDER = "<runtime-root>";
11
14
  const shellQuote = (value) => `'${value.replace(/'/g, `'\"'\"'`)}'`;
12
15
  const extractNodeMajorVersion = (image) => Number(image.match(/^node:(\d+)/)?.[1] ?? "0");
13
- const createPackageInstallCommand = (packages) => `RUN apt-get update && apt-get install -y --no-install-recommends ${packages.join(" ")} && rm -rf /var/lib/apt/lists/*`;
14
- const createNpmPackageInstallCommand = (packages) => `RUN npm install -g --omit=dev --no-fund --no-audit ${packages.join(" ")}`;
15
- const createPipxPackageInstallCommand = (packages) => `RUN PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx install ${packages.join(" ")}`;
16
16
  const createStateOwnershipCommand = (persistentMountPaths = []) => {
17
17
  const mountPaths = [...new Set(persistentMountPaths)].sort();
18
18
  const mkdirPaths = [...new Set(["/var/lib/spawnfile", ...mountPaths])].sort();
@@ -20,7 +20,6 @@ const createStateOwnershipCommand = (persistentMountPaths = []) => {
20
20
  const chownPaths = [
21
21
  ...new Set([
22
22
  "/var/lib/spawnfile",
23
- "/opt/spawnfile",
24
23
  ...mountPaths.filter((mountPath) => !mountPath.startsWith("/var/lib/spawnfile/"))
25
24
  ])
26
25
  ].sort();
@@ -30,70 +29,13 @@ const createStateOwnershipCommand = (persistentMountPaths = []) => {
30
29
  `chown -R spawnfile:spawnfile ${chownPaths.map(shellQuote).join(" ")}`
31
30
  ].join(" && ");
32
31
  };
33
- const dedupePackages = (packages) => {
34
- const seen = new Map();
35
- for (const currentPackage of packages) {
36
- seen.set(`${currentPackage.manager}\u0000${currentPackage.name}\u0000${currentPackage.version ?? ""}\u0000${currentPackage.scope ?? ""}`, currentPackage);
37
- }
38
- return [...seen.values()].sort((left, right) => `${left.manager}\u0000${left.name}\u0000${left.version ?? ""}\u0000${left.scope ?? ""}`.localeCompare(`${right.manager}\u0000${right.name}\u0000${right.version ?? ""}\u0000${right.scope ?? ""}`));
39
- };
40
- const createPackageIdentity = (pkg) => `${pkg.manager}\u0000${pkg.name}\u0000${pkg.version ?? ""}\u0000${pkg.scope ?? ""}`;
41
- const createPackageConflictIdentity = (pkg) => `${pkg.manager}\u0000${pkg.name}`;
42
- const assertImagePackageCompatibility = (packages) => {
43
- const byName = new Map();
44
- for (const currentPackage of packages) {
45
- const conflictIdentity = createPackageConflictIdentity(currentPackage);
46
- const existingPackage = byName.get(conflictIdentity);
47
- if (!existingPackage) {
48
- byName.set(conflictIdentity, currentPackage);
49
- continue;
50
- }
51
- if (createPackageIdentity(existingPackage) !== createPackageIdentity(currentPackage)) {
52
- throw new SpawnfileError("validation_error", `Generated container declares conflicting package definitions for ${currentPackage.manager} package ${currentPackage.name}`);
53
- }
54
- }
55
- };
56
- const createAptPackageInstallItem = (pkg) => pkg.version ? `${pkg.name}=${pkg.version}` : pkg.name;
57
- const createNpmPackageInstallItem = (pkg) => pkg.version ? `${pkg.name}@${pkg.version}` : pkg.name;
58
- const createPipxPackageInstallItem = (pkg) => pkg.version ? `${pkg.name}==${pkg.version}` : pkg.name;
59
- const getNpmInstallItemName = (item) => {
60
- if (!item.startsWith("@")) {
61
- const versionMarker = item.indexOf("@");
62
- return versionMarker === -1 ? item : item.slice(0, versionMarker);
63
- }
64
- const slashIndex = item.indexOf("/");
65
- if (slashIndex === -1) {
66
- return item;
67
- }
68
- const versionMarker = item.indexOf("@", slashIndex);
69
- return versionMarker === -1 ? item : item.slice(0, versionMarker);
70
- };
71
- const collectPackagesByManager = (runtimePlans) => {
72
- const packagesByManager = {
73
- apt: [],
74
- npm: [],
75
- pipx: []
76
- };
77
- const imagePackages = runtimePlans.flatMap((plan) => plan.packages ?? []);
78
- assertImagePackageCompatibility(imagePackages);
79
- const resolvedPackages = dedupePackages(imagePackages);
80
- for (const packageConfig of resolvedPackages) {
81
- if (packageConfig.manager === "apt") {
82
- packagesByManager.apt.push(packageConfig);
83
- continue;
84
- }
85
- if (packageConfig.manager === "npm") {
86
- packagesByManager.npm.push(packageConfig);
87
- continue;
88
- }
89
- if (packageConfig.manager === "pipx") {
90
- packagesByManager.pipx.push(packageConfig);
91
- continue;
92
- }
32
+ const selectBaseImage = (runtimePlans, runtimeRecipes) => {
33
+ const recipeBaseImages = [
34
+ ...new Set(runtimeRecipes.flatMap((recipe) => recipe.baseImage ? [recipe.baseImage] : []))
35
+ ];
36
+ if (recipeBaseImages.length === 1 && runtimePlans.length === runtimeRecipes.length) {
37
+ return recipeBaseImages[0];
93
38
  }
94
- return packagesByManager;
95
- };
96
- const selectBaseImage = (runtimePlans) => {
97
39
  const firstRuntimeMeta = runtimePlans[0]?.meta;
98
40
  if (runtimePlans.length <= 1) {
99
41
  return firstRuntimeMeta?.standaloneBaseImage ?? "debian:bookworm-slim";
@@ -132,12 +74,13 @@ export const renderEnvExample = (variables) => {
132
74
  export const renderDockerfile = async (runtimePlans, options = {}) => {
133
75
  const runtimeNames = [...new Set(runtimePlans.map((plan) => plan.runtimeName))];
134
76
  const runtimeRecipes = await Promise.all(runtimeNames.map((runtimeName) => createRuntimeInstallRecipe(runtimeName)));
135
- const baseImage = selectBaseImage(runtimePlans);
77
+ const recipeByRuntimeName = new Map(runtimeRecipes.map((recipe) => [recipe.runtimeName, recipe]));
78
+ const baseImage = selectBaseImage(runtimePlans, runtimeRecipes);
136
79
  const needsJsonEnvWriter = runtimePlans.some((plan) => (plan.configEnvBindings?.length ?? 0) > 0);
137
80
  const needsGit = runtimePlans.some((plan) => (plan.resources ?? []).some((resource) => resource.kind === "git"));
138
81
  const systemDeps = [
139
82
  ...new Set([
140
- ...runtimePlans.flatMap((plan) => plan.meta.systemDeps),
83
+ ...runtimePlans.flatMap((plan) => recipeByRuntimeName.get(plan.runtimeName)?.baseImage ? [] : plan.meta.systemDeps),
141
84
  ...(needsGit ? ["git"] : []),
142
85
  ...(options.hasMoltnet && !options.hasStagedMoltnetBinaries
143
86
  ? ["ca-certificates", "curl", "tar"]
@@ -194,6 +137,17 @@ export const renderDockerfile = async (runtimePlans, options = {}) => {
194
137
  }
195
138
  lines.push('RUN if ! id -u spawnfile >/dev/null 2>&1; then useradd --create-home --home-dir /home/spawnfile --shell /bin/bash spawnfile; fi', "");
196
139
  lines.push("COPY container/rootfs/ /", "COPY .env.example /opt/spawnfile/.env.example", 'COPY entrypoint.sh /opt/spawnfile/entrypoint.sh', "RUN chmod +x /opt/spawnfile/entrypoint.sh");
140
+ const postRootfsCommands = [
141
+ ...new Set(runtimePlans.flatMap((plan) => plan.meta.postRootfsCommands ?? []))
142
+ ];
143
+ for (const command of postRootfsCommands) {
144
+ lines.push(`RUN ${command}`);
145
+ }
146
+ if (options.distribution) {
147
+ lines.push("", `COPY ${options.distribution.reportOutputFile} ${DISTRIBUTION_REPORT_IMAGE_PATH}`, ...Object.entries(options.distribution.labels)
148
+ .sort(([left], [right]) => left.localeCompare(right))
149
+ .map(([name, value]) => `LABEL ${name}=${shellQuote(value)}`));
150
+ }
197
151
  lines.push(`RUN ${createStateOwnershipCommand(options.persistentMountPaths)}`);
198
152
  if (exposedPorts.length > 0) {
199
153
  lines.push(`EXPOSE ${exposedPorts.join(" ")}`);
@@ -207,11 +161,19 @@ export const createRootfsFiles = (runtimePlans) => runtimePlans.flatMap((plan) =
207
161
  return {
208
162
  content: file.content
209
163
  .replaceAll(WORKSPACE_PLACEHOLDER, plan.instancePaths.workspacePath)
164
+ .replaceAll(RUNTIME_ROOT_PLACEHOLDER, plan.runtimeRoot)
210
165
  .replaceAll(`"${GATEWAY_PORT_PLACEHOLDER}"`, plan.port ? String(plan.port) : "0")
211
166
  .replaceAll(GATEWAY_PORT_PLACEHOLDER, plan.port ? String(plan.port) : ""),
212
167
  path: `${CONTAINER_ROOTFS_ROOT}${plan.instancePaths.configPath}`
213
168
  };
214
169
  }
170
+ if (file.path.startsWith("runtime/")) {
171
+ const relativeRuntimePath = file.path.slice("runtime/".length);
172
+ return {
173
+ content: file.content,
174
+ path: `${CONTAINER_ROOTFS_ROOT}${path.posix.join(plan.runtimeRoot, relativeRuntimePath)}`
175
+ };
176
+ }
215
177
  if (file.path.startsWith("workspace/")) {
216
178
  const relativeWorkspacePath = file.path.slice("workspace/".length);
217
179
  return {
@@ -1,3 +1,4 @@
1
+ import type { DistributionReport } from "../distribution/index.js";
1
2
  import type { ContainerReport } from "../report/index.js";
2
3
  import type { EmittedFile, RuntimeContainerConfigEnvBinding, RuntimeContainerMeta } from "../runtime/index.js";
3
4
  import type { ModelAuthMethod } from "../shared/index.js";
@@ -7,6 +8,7 @@ import type { WorkspaceResourcePlan } from "./workspaceResources.js";
7
8
  export interface ContainerEnvVariable {
8
9
  categories: Array<"model" | "project" | "runtime" | "surface">;
9
10
  description: string;
11
+ generated: boolean;
10
12
  name: string;
11
13
  required: boolean;
12
14
  }
@@ -32,17 +34,24 @@ export interface RuntimeTargetPlan {
32
34
  resources?: WorkspaceResourcePlan[];
33
35
  runtimeName: string;
34
36
  runtimeRoot: string;
37
+ sourceIds?: string[];
35
38
  targetConfigEnvBindings?: RuntimeContainerConfigEnvBinding[];
36
39
  targetFiles: EmittedFile[];
37
40
  }
38
41
  export interface CompiledNodeArtifact {
39
42
  emittedFiles: EmittedFile[];
43
+ id?: string;
40
44
  kind: "agent" | "team";
41
45
  runtimeName: string | null;
42
46
  slug: string;
43
47
  value: ResolvedAgentNode | ResolvedTeamNode;
44
48
  }
45
49
  export interface GeneratedContainerArtifacts {
50
+ distribution: {
51
+ fingerprint: string;
52
+ labels: Record<string, string>;
53
+ report: DistributionReport;
54
+ };
46
55
  executablePaths: string[];
47
56
  files: EmittedFile[];
48
57
  moltnet?: {