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.
- package/README.md +21 -3
- package/dist/auth/runtimeCredentials.js +18 -2
- package/dist/cli/devCommands.d.ts +3 -0
- package/dist/cli/devCommands.js +115 -0
- package/dist/cli/lifecycleCommands.js +103 -1
- package/dist/cli/modelCommands.js +3 -0
- package/dist/cli/resolveCommandInput.d.ts +19 -0
- package/dist/cli/resolveCommandInput.js +25 -0
- package/dist/cli/runCli.d.ts +10 -1
- package/dist/cli/runCli.js +41 -13
- package/dist/cli/runtimeCommands.js +1 -0
- package/dist/cli/statusCommand.d.ts +43 -0
- package/dist/cli/statusCommand.js +428 -0
- package/dist/cli/surfaceCommands.js +4 -0
- package/dist/compiler/agentSurfaces.js +2 -4
- package/dist/compiler/buildProject.d.ts +9 -1
- package/dist/compiler/buildProject.js +44 -5
- package/dist/compiler/compileProject.d.ts +2 -0
- package/dist/compiler/compileProject.js +22 -11
- package/dist/compiler/containerArtifacts.d.ts +1 -0
- package/dist/compiler/containerArtifacts.js +208 -32
- package/dist/compiler/containerArtifactsPlans.js +7 -4
- package/dist/compiler/containerArtifactsRender.d.ts +7 -1
- package/dist/compiler/containerArtifactsRender.js +31 -69
- package/dist/compiler/containerArtifactsTypes.d.ts +9 -0
- package/dist/compiler/containerEntrypointRender.js +22 -32
- package/dist/compiler/containerPackageRender.d.ts +15 -0
- package/dist/compiler/containerPackageRender.js +63 -0
- package/dist/compiler/containerTargetResources.js +3 -1
- package/dist/compiler/index.d.ts +1 -0
- package/dist/compiler/index.js +1 -0
- package/dist/compiler/moltnetArtifacts.js +21 -1
- package/dist/compiler/moltnetBinaries.d.ts +4 -1
- package/dist/compiler/moltnetBinaries.js +24 -3
- package/dist/compiler/moltnetClientConfig.d.ts +1 -1
- package/dist/compiler/moltnetClientConfig.js +14 -16
- package/dist/compiler/moltnetNodeConfig.js +2 -4
- package/dist/compiler/moltnetResolution.js +1 -1
- package/dist/compiler/moltnetRoomMemberships.js +1 -2
- package/dist/compiler/moltnetRuntimeConfig.d.ts +1 -1
- package/dist/compiler/moltnetRuntimeConfig.js +9 -10
- package/dist/compiler/networkBinding.d.ts +14 -0
- package/dist/compiler/networkBinding.js +34 -0
- package/dist/compiler/publishProject.d.ts +13 -0
- package/dist/compiler/publishProject.js +72 -0
- package/dist/compiler/runProject.d.ts +26 -12
- package/dist/compiler/runProject.js +109 -126
- package/dist/compiler/runProjectAuth.d.ts +5 -0
- package/dist/compiler/runProjectAuth.js +98 -0
- package/dist/compiler/runProjectDocker.d.ts +19 -0
- package/dist/compiler/runProjectDocker.js +202 -0
- package/dist/compiler/types.d.ts +1 -2
- package/dist/compiler/upProject.d.ts +6 -0
- package/dist/compiler/upProject.js +43 -8
- package/dist/compiler/view/buildOrganizationView.js +4 -1
- package/dist/compiler/view/declarations.d.ts +4 -0
- package/dist/compiler/view/declarations.js +104 -0
- package/dist/compiler/view/renderNetworks.js +1 -2
- package/dist/compiler/view/renderTree.js +6 -2
- package/dist/compiler/view/types.d.ts +64 -1
- package/dist/deployment/dockerInspect.d.ts +29 -0
- package/dist/deployment/dockerInspect.js +130 -0
- package/dist/deployment/dockerLabels.d.ts +16 -0
- package/dist/deployment/dockerLabels.js +32 -0
- package/dist/deployment/dockerLogs.d.ts +24 -0
- package/dist/deployment/dockerLogs.js +96 -0
- package/dist/deployment/dockerManager.d.ts +59 -0
- package/dist/deployment/dockerManager.js +76 -0
- package/dist/deployment/dockerProbeGateway.d.ts +16 -0
- package/dist/deployment/dockerProbeGateway.js +48 -0
- package/dist/deployment/dockerRecover.d.ts +17 -0
- package/dist/deployment/dockerRecover.js +129 -0
- package/dist/deployment/homeStore.d.ts +19 -0
- package/dist/deployment/homeStore.js +130 -0
- package/dist/deployment/index.d.ts +10 -0
- package/dist/deployment/index.js +10 -0
- package/dist/deployment/names.d.ts +3 -0
- package/dist/deployment/names.js +12 -0
- package/dist/deployment/record.d.ts +76 -0
- package/dist/deployment/record.js +174 -0
- package/dist/deployment/target.d.ts +35 -0
- package/dist/deployment/target.js +96 -0
- package/dist/dev/activity.d.ts +14 -0
- package/dist/dev/activity.js +48 -0
- package/dist/dev/docker.d.ts +16 -0
- package/dist/dev/docker.js +71 -0
- package/dist/dev/index.d.ts +2 -0
- package/dist/dev/index.js +2 -0
- package/dist/dev/project.d.ts +35 -0
- package/dist/dev/project.js +164 -0
- package/dist/distribution/buildDistributionReport.d.ts +22 -0
- package/dist/distribution/buildDistributionReport.js +51 -0
- package/dist/distribution/consumeImage.d.ts +29 -0
- package/dist/distribution/consumeImage.js +254 -0
- package/dist/distribution/consumeImageSupport.d.ts +18 -0
- package/dist/distribution/consumeImageSupport.js +63 -0
- package/dist/distribution/distributionReportSchema.d.ts +84 -0
- package/dist/distribution/distributionReportSchema.js +107 -0
- package/dist/distribution/dockerRunner.d.ts +5 -0
- package/dist/distribution/dockerRunner.js +21 -0
- package/dist/distribution/extractImage.d.ts +25 -0
- package/dist/distribution/extractImage.js +96 -0
- package/dist/distribution/fingerprint.d.ts +2 -0
- package/dist/distribution/fingerprint.js +14 -0
- package/dist/distribution/imageRef.d.ts +9 -0
- package/dist/distribution/imageRef.js +65 -0
- package/dist/distribution/imageRuntimeAuth.d.ts +19 -0
- package/dist/distribution/imageRuntimeAuth.js +71 -0
- package/dist/distribution/index.d.ts +24 -0
- package/dist/distribution/index.js +16 -0
- package/dist/distribution/preflight.d.ts +11 -0
- package/dist/distribution/preflight.js +65 -0
- package/dist/distribution/projectImageView.d.ts +8 -0
- package/dist/distribution/projectImageView.js +83 -0
- package/dist/distribution/projectName.d.ts +1 -0
- package/dist/distribution/projectName.js +12 -0
- package/dist/distribution/renderImageInterface.d.ts +30 -0
- package/dist/distribution/renderImageInterface.js +86 -0
- package/dist/distribution/tarReader.d.ts +10 -0
- package/dist/distribution/tarReader.js +69 -0
- package/dist/distribution/types.d.ts +80 -0
- package/dist/distribution/types.js +4 -0
- package/dist/distribution/verifyDistributionReport.d.ts +12 -0
- package/dist/distribution/verifyDistributionReport.js +29 -0
- package/dist/filesystem/paths.d.ts +6 -0
- package/dist/filesystem/paths.js +11 -0
- package/dist/manifest/loadManifest.js +22 -2
- package/dist/manifest/renderSpawnfile.js +2 -4
- package/dist/manifest/schemas.d.ts +17 -41
- package/dist/manifest/surfaceSchemas.d.ts +19 -47
- package/dist/manifest/surfaceSchemas.js +3 -6
- package/dist/report/createReport.d.ts +7 -1
- package/dist/report/createReport.js +28 -1
- package/dist/report/types.d.ts +43 -0
- package/dist/runtime/common.d.ts +2 -0
- package/dist/runtime/common.js +1 -1
- package/dist/runtime/container.d.ts +1 -0
- package/dist/runtime/container.js +82 -15
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/install.d.ts +1 -11
- package/dist/runtime/install.js +27 -30
- package/dist/runtime/openclaw/adapter.js +9 -1
- package/dist/runtime/openclaw/configAuth.d.ts +15 -0
- package/dist/runtime/openclaw/configAuth.js +46 -0
- package/dist/runtime/openclaw/runAuth.d.ts +6 -0
- package/dist/runtime/openclaw/runAuth.js +7 -55
- package/dist/runtime/openclaw/statusProbes.d.ts +2 -0
- package/dist/runtime/openclaw/statusProbes.js +31 -0
- package/dist/runtime/pi/adapter.d.ts +4 -0
- package/dist/runtime/pi/adapter.js +198 -0
- package/dist/runtime/pi/appActivitySource.d.ts +1 -0
- package/dist/runtime/pi/appActivitySource.js +87 -0
- package/dist/runtime/pi/appCoreSource.d.ts +1 -0
- package/dist/runtime/pi/appCoreSource.js +354 -0
- package/dist/runtime/pi/appPreludeSource.d.ts +1 -0
- package/dist/runtime/pi/appPreludeSource.js +103 -0
- package/dist/runtime/pi/appSource.d.ts +1 -0
- package/dist/runtime/pi/appSource.js +8 -0
- package/dist/runtime/pi/appTemplate.d.ts +28 -0
- package/dist/runtime/pi/appTemplate.js +144 -0
- package/dist/runtime/pi/runAuth.d.ts +2 -0
- package/dist/runtime/pi/runAuth.js +68 -0
- package/dist/runtime/picoclaw/adapter.js +10 -0
- package/dist/runtime/picoclaw/runAuth.d.ts +7 -0
- package/dist/runtime/picoclaw/runAuth.js +8 -68
- package/dist/runtime/picoclaw/statusProbes.d.ts +2 -0
- package/dist/runtime/picoclaw/statusProbes.js +100 -0
- package/dist/runtime/registry.d.ts +0 -5
- package/dist/runtime/registry.js +5 -12
- package/dist/runtime/statusProbes.d.ts +16 -0
- package/dist/runtime/statusProbes.js +41 -0
- package/dist/runtime/types.d.ts +53 -1
- package/dist/shared/errors.d.ts +6 -0
- package/dist/shared/errors.js +9 -0
- package/dist/status/buildStatus.d.ts +13 -0
- package/dist/status/buildStatus.js +266 -0
- package/dist/status/compileReport.d.ts +96 -0
- package/dist/status/compileReport.js +230 -0
- package/dist/status/compiledContainerObservations.d.ts +5 -0
- package/dist/status/compiledContainerObservations.js +63 -0
- package/dist/status/deploymentLogs.d.ts +12 -0
- package/dist/status/deploymentLogs.js +45 -0
- package/dist/status/deployments.d.ts +14 -0
- package/dist/status/deployments.js +121 -0
- package/dist/status/imageStatusReport.d.ts +10 -0
- package/dist/status/imageStatusReport.js +73 -0
- package/dist/status/index.d.ts +14 -0
- package/dist/status/index.js +14 -0
- package/dist/status/moltnetProbes.d.ts +21 -0
- package/dist/status/moltnetProbes.js +240 -0
- package/dist/status/registryDrift.d.ts +28 -0
- package/dist/status/registryDrift.js +119 -0
- package/dist/status/renderStatus.d.ts +2 -0
- package/dist/status/renderStatus.js +230 -0
- package/dist/status/runtimeProbes.d.ts +13 -0
- package/dist/status/runtimeProbes.js +111 -0
- package/dist/status/selectionSubjects.d.ts +8 -0
- package/dist/status/selectionSubjects.js +77 -0
- package/dist/status/selectors.d.ts +14 -0
- package/dist/status/selectors.js +90 -0
- package/dist/status/traversal.d.ts +6 -0
- package/dist/status/traversal.js +15 -0
- package/dist/status/types.d.ts +113 -0
- package/dist/status/types.js +1 -0
- package/package.json +9 -2
- package/runtimes.yaml +44 -21
- package/dist/runtime/tinyclaw/adapter.d.ts +0 -2
- package/dist/runtime/tinyclaw/adapter.js +0 -284
- package/dist/runtime/tinyclaw/runAuth.d.ts +0 -2
- package/dist/runtime/tinyclaw/runAuth.js +0 -49
- package/dist/runtime/tinyclaw/scaffold-assets/AGENTS.md +0 -160
- package/dist/runtime/tinyclaw/scaffold-assets/CLAUDE.md +0 -5
- package/dist/runtime/tinyclaw/scaffold-assets/SOUL.md +0 -177
- package/dist/runtime/tinyclaw/scaffold.d.ts +0 -2
- package/dist/runtime/tinyclaw/scaffold.js +0 -24
- package/dist/runtime/tinyclaw/schedules.d.ts +0 -9
- package/dist/runtime/tinyclaw/schedules.js +0 -62
- package/dist/runtime/tinyclaw/surfaces.d.ts +0 -8
- package/dist/runtime/tinyclaw/surfaces.js +0 -94
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { resolveMoltnetStorePath } from "./moltnetConfigLowering.js";
|
|
3
|
+
import { networkUrlEnvName } from "./networkBinding.js";
|
|
3
4
|
import { createWorkspaceResourceCommands, createWorkspaceResourceShellFunctions } from "./containerWorkspaceResourceRender.js";
|
|
4
5
|
const MOLTNET_SERVER_DATA_DIRECTORY = "/var/lib/spawnfile/moltnet/servers";
|
|
5
6
|
const shellQuote = (value) => `'${value.replace(/'/g, `'\"'\"'`)}'`;
|
|
@@ -8,9 +9,9 @@ const createEnvironmentAssignments = (plan) => {
|
|
|
8
9
|
if (plan.instancePaths.homePath) {
|
|
9
10
|
envAssignments.push(`HOME=${shellQuote(plan.instancePaths.homePath)}`);
|
|
10
11
|
}
|
|
11
|
-
if (plan.instancePaths.homePath &&
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
if (plan.instancePaths.homePath &&
|
|
13
|
+
plan.runtimeName === "picoclaw" &&
|
|
14
|
+
plan.modelAuthMethods.openai === "codex") {
|
|
14
15
|
envAssignments.push(`CODEX_HOME=${shellQuote(path.posix.join(plan.instancePaths.homePath, ".codex"))}`);
|
|
15
16
|
}
|
|
16
17
|
if (plan.meta.homeEnv && plan.instancePaths.homePath) {
|
|
@@ -29,14 +30,6 @@ const createEnvironmentAssignments = (plan) => {
|
|
|
29
30
|
};
|
|
30
31
|
const createEnvFileWrites = (plan) => plan.envFiles.map((binding) => `write_env_file ${shellQuote(binding.envName)} ${shellQuote(binding.filePath)}`);
|
|
31
32
|
const createConfigEnvWrites = (plan) => (plan.configEnvBindings ?? []).map((binding) => `apply_json_env_value ${shellQuote(plan.instancePaths.configPath)} ${shellQuote(binding.envName)} ${shellQuote(binding.jsonPath)}`);
|
|
32
|
-
const createAuthSetupCommands = (plan) => {
|
|
33
|
-
if (plan.runtimeName !== "tinyclaw" ||
|
|
34
|
-
!plan.instancePaths.homePath ||
|
|
35
|
-
plan.modelAuthMethods.openai !== "api_key") {
|
|
36
|
-
return [];
|
|
37
|
-
}
|
|
38
|
-
return [`configure_codex_api_key_auth ${shellQuote(plan.instancePaths.homePath)}`];
|
|
39
|
-
};
|
|
40
33
|
const createMoltnetStorePrepareCommands = (serverPlan) => {
|
|
41
34
|
const server = serverPlan.server;
|
|
42
35
|
if (server.mode !== "managed" || server.store.kind === "memory" || server.store.kind === "postgres") {
|
|
@@ -50,11 +43,14 @@ const createMoltnetStorePrepareCommands = (serverPlan) => {
|
|
|
50
43
|
};
|
|
51
44
|
const resolveStartCommand = (plan) => plan.meta.startCommand
|
|
52
45
|
.map((token) => token
|
|
46
|
+
.replaceAll("<config-path>", plan.instancePaths.configPath)
|
|
47
|
+
.replaceAll("<home-path>", plan.instancePaths.homePath ?? "")
|
|
53
48
|
.replaceAll("<runtime-root>", plan.runtimeRoot)
|
|
49
|
+
.replaceAll("<workspace-path>", plan.instancePaths.workspacePath)
|
|
54
50
|
.replaceAll("<port>", plan.port ? String(plan.port) : ""))
|
|
55
51
|
.filter((token) => token.length > 0);
|
|
56
52
|
const createRuntimeReadinessWait = (plan) => {
|
|
57
|
-
if (plan.runtimeName
|
|
53
|
+
if (!["openclaw", "pi"].includes(plan.runtimeName) || !plan.port) {
|
|
58
54
|
return [];
|
|
59
55
|
}
|
|
60
56
|
return [
|
|
@@ -145,15 +141,6 @@ export const renderEntrypoint = (runtimePlans, requiredSecrets, options = {}) =>
|
|
|
145
141
|
"PY",
|
|
146
142
|
"}",
|
|
147
143
|
"",
|
|
148
|
-
"configure_codex_api_key_auth() {",
|
|
149
|
-
' local home_path="$1"',
|
|
150
|
-
' if [ -z "${OPENAI_API_KEY:-}" ]; then',
|
|
151
|
-
" return",
|
|
152
|
-
" fi",
|
|
153
|
-
' mkdir -p "$home_path/.codex"',
|
|
154
|
-
' printf "%s\\n" "${OPENAI_API_KEY:-}" | HOME="$home_path" CODEX_HOME="$home_path/.codex" codex login --with-api-key >/dev/null',
|
|
155
|
-
"}",
|
|
156
|
-
"",
|
|
157
144
|
...createWorkspaceResourceShellFunctions()
|
|
158
145
|
];
|
|
159
146
|
lines.push('if [ -n "${OPENCLAW_GATEWAY_TOKEN:-}" ] && [ -z "${OPENCLAW_HOOKS_TOKEN:-}" ]; then', ' export OPENCLAW_HOOKS_TOKEN="hooks-${OPENCLAW_GATEWAY_TOKEN}"', "fi", "");
|
|
@@ -171,7 +158,7 @@ export const renderEntrypoint = (runtimePlans, requiredSecrets, options = {}) =>
|
|
|
171
158
|
const plan = runtimePlans[0];
|
|
172
159
|
const commandTokens = resolveStartCommand(plan);
|
|
173
160
|
const envAssignments = createEnvironmentAssignments(plan);
|
|
174
|
-
lines.push(`mkdir -p ${shellQuote(plan.instancePaths.workspacePath)}`, ...createWorkspaceResourceCommands(plan), `require_file ${shellQuote(plan.instancePaths.configPath)}`, ...createEnvFileWrites(plan), ...createConfigEnvWrites(plan),
|
|
161
|
+
lines.push(`mkdir -p ${shellQuote(plan.instancePaths.workspacePath)}`, ...createWorkspaceResourceCommands(plan), `require_file ${shellQuote(plan.instancePaths.configPath)}`, ...createEnvFileWrites(plan), ...createConfigEnvWrites(plan), `${envAssignments.join(" ")} exec ${commandTokens.map(shellQuote).join(" ")}`);
|
|
175
162
|
return `${lines.join("\n").trimEnd()}\n`;
|
|
176
163
|
}
|
|
177
164
|
lines.push("PIDS=()", "", "terminate_children() {", ' for pid in "${PIDS[@]:-}"; do', ' kill "$pid" 2>/dev/null || true', " done", "}", "", "trap terminate_children INT TERM EXIT", "");
|
|
@@ -183,25 +170,28 @@ export const renderEntrypoint = (runtimePlans, requiredSecrets, options = {}) =>
|
|
|
183
170
|
if (!serverPlan.configPath) {
|
|
184
171
|
continue;
|
|
185
172
|
}
|
|
173
|
+
const urlEnv = networkUrlEnvName(serverPlan.networkId);
|
|
174
|
+
const serverLines = [];
|
|
186
175
|
for (const patch of serverPlan.secretPatches) {
|
|
187
|
-
|
|
176
|
+
serverLines.push(`apply_json_env_value ${shellQuote(serverPlan.configPath)} ${shellQuote(patch.envName)} ${shellQuote(patch.jsonPath)}`);
|
|
188
177
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (!serverPlan.port) {
|
|
193
|
-
continue;
|
|
178
|
+
serverLines.push(...createMoltnetStorePrepareCommands(serverPlan), `MOLTNET_CONFIG=${shellQuote(serverPlan.configPath)} /usr/local/bin/moltnet &`, 'PIDS+=("$!")');
|
|
179
|
+
if (serverPlan.port) {
|
|
180
|
+
serverLines.push(`until curl -sf ${shellQuote(`http://127.0.0.1:${serverPlan.port}/healthz`)} >/dev/null; do sleep 1; done`);
|
|
194
181
|
}
|
|
195
|
-
|
|
196
|
-
lines.push("");
|
|
182
|
+
// Suppress the in-image managed server when an external endpoint is bound.
|
|
183
|
+
lines.push(`if [ -z "\${${urlEnv}:-}" ]; then`, ...serverLines.map((line) => ` ${line}`), "fi", "");
|
|
197
184
|
}
|
|
198
185
|
for (const plan of runtimePlans) {
|
|
199
186
|
const commandTokens = resolveStartCommand(plan);
|
|
200
187
|
const envAssignments = createEnvironmentAssignments(plan);
|
|
201
|
-
lines.push(`mkdir -p ${shellQuote(plan.instancePaths.workspacePath)}`, ...createWorkspaceResourceCommands(plan), `require_file ${shellQuote(plan.instancePaths.configPath)}`, ...createEnvFileWrites(plan), ...createConfigEnvWrites(plan),
|
|
188
|
+
lines.push(`mkdir -p ${shellQuote(plan.instancePaths.workspacePath)}`, ...createWorkspaceResourceCommands(plan), `require_file ${shellQuote(plan.instancePaths.configPath)}`, ...createEnvFileWrites(plan), ...createConfigEnvWrites(plan), `${envAssignments.join(" ")} ${commandTokens.map(shellQuote).join(" ")} &`, 'PIDS+=("$!")', "", ...createRuntimeReadinessWait(plan));
|
|
202
189
|
}
|
|
203
190
|
for (const nodePlan of moltnetNodePlans) {
|
|
204
|
-
|
|
191
|
+
const urlEnv = networkUrlEnvName(nodePlan.networkId);
|
|
192
|
+
lines.push(
|
|
193
|
+
// Rebind the bridge endpoint when an external network URL is provided.
|
|
194
|
+
`if [ -n "\${${urlEnv}:-}" ]; then`, ` apply_json_env_value ${shellQuote(nodePlan.configPath)} ${shellQuote(urlEnv)} ${shellQuote("moltnet.base_url")}`, "fi", `/usr/local/bin/moltnet node ${shellQuote(nodePlan.configPath)} &`, 'PIDS+=("$!")', "");
|
|
205
195
|
}
|
|
206
196
|
lines.push('if [ "${#PIDS[@]}" -eq 0 ]; then', ' echo "No runtime targets were generated for this compile output" >&2', " exit 1", "fi", "", "status=0", 'for pid in "${PIDS[@]}"; do', ' if ! wait "$pid"; then', " status=1", " fi", "done", "", 'exit "$status"');
|
|
207
197
|
return `${lines.join("\n").trimEnd()}\n`;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RuntimeTargetPlan } from "./containerArtifactsTypes.js";
|
|
2
|
+
import type { ResolvedPackage } from "./types.js";
|
|
3
|
+
export interface PackagesByManager {
|
|
4
|
+
apt: ResolvedPackage[];
|
|
5
|
+
npm: ResolvedPackage[];
|
|
6
|
+
pipx: ResolvedPackage[];
|
|
7
|
+
}
|
|
8
|
+
export declare const createPackageInstallCommand: (packages: string[]) => string;
|
|
9
|
+
export declare const createNpmPackageInstallCommand: (packages: string[]) => string;
|
|
10
|
+
export declare const createPipxPackageInstallCommand: (packages: string[]) => string;
|
|
11
|
+
export declare const createAptPackageInstallItem: (pkg: ResolvedPackage) => string;
|
|
12
|
+
export declare const createNpmPackageInstallItem: (pkg: ResolvedPackage) => string;
|
|
13
|
+
export declare const createPipxPackageInstallItem: (pkg: ResolvedPackage) => string;
|
|
14
|
+
export declare const getNpmInstallItemName: (item: string) => string;
|
|
15
|
+
export declare const collectPackagesByManager: (runtimePlans: RuntimeTargetPlan[]) => PackagesByManager;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { SpawnfileError } from "../shared/index.js";
|
|
2
|
+
export const createPackageInstallCommand = (packages) => `RUN apt-get update && apt-get install -y --no-install-recommends ${packages.join(" ")} && rm -rf /var/lib/apt/lists/*`;
|
|
3
|
+
export const createNpmPackageInstallCommand = (packages) => `RUN npm install -g --omit=dev --no-fund --no-audit ${packages.join(" ")}`;
|
|
4
|
+
export const createPipxPackageInstallCommand = (packages) => `RUN PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx install ${packages.join(" ")}`;
|
|
5
|
+
const packageIdentity = (pkg) => `${pkg.manager}\u0000${pkg.name}\u0000${pkg.version ?? ""}\u0000${pkg.scope ?? ""}`;
|
|
6
|
+
const packageConflictIdentity = (pkg) => `${pkg.manager}\u0000${pkg.name}`;
|
|
7
|
+
const dedupePackages = (packages) => {
|
|
8
|
+
const seen = new Map();
|
|
9
|
+
for (const currentPackage of packages) {
|
|
10
|
+
seen.set(packageIdentity(currentPackage), currentPackage);
|
|
11
|
+
}
|
|
12
|
+
return [...seen.values()].sort((left, right) => packageIdentity(left).localeCompare(packageIdentity(right)));
|
|
13
|
+
};
|
|
14
|
+
const assertImagePackageCompatibility = (packages) => {
|
|
15
|
+
const byName = new Map();
|
|
16
|
+
for (const currentPackage of packages) {
|
|
17
|
+
const conflictIdentity = packageConflictIdentity(currentPackage);
|
|
18
|
+
const existingPackage = byName.get(conflictIdentity);
|
|
19
|
+
if (!existingPackage) {
|
|
20
|
+
byName.set(conflictIdentity, currentPackage);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (packageIdentity(existingPackage) !== packageIdentity(currentPackage)) {
|
|
24
|
+
throw new SpawnfileError("validation_error", `Generated container declares conflicting package definitions for ${currentPackage.manager} package ${currentPackage.name}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
export const createAptPackageInstallItem = (pkg) => pkg.version ? `${pkg.name}=${pkg.version}` : pkg.name;
|
|
29
|
+
export const createNpmPackageInstallItem = (pkg) => pkg.version ? `${pkg.name}@${pkg.version}` : pkg.name;
|
|
30
|
+
export const createPipxPackageInstallItem = (pkg) => pkg.version ? `${pkg.name}==${pkg.version}` : pkg.name;
|
|
31
|
+
export const getNpmInstallItemName = (item) => {
|
|
32
|
+
if (!item.startsWith("@")) {
|
|
33
|
+
const versionMarker = item.indexOf("@");
|
|
34
|
+
return versionMarker === -1 ? item : item.slice(0, versionMarker);
|
|
35
|
+
}
|
|
36
|
+
const slashIndex = item.indexOf("/");
|
|
37
|
+
if (slashIndex === -1) {
|
|
38
|
+
return item;
|
|
39
|
+
}
|
|
40
|
+
const versionMarker = item.indexOf("@", slashIndex);
|
|
41
|
+
return versionMarker === -1 ? item : item.slice(0, versionMarker);
|
|
42
|
+
};
|
|
43
|
+
export const collectPackagesByManager = (runtimePlans) => {
|
|
44
|
+
const packagesByManager = {
|
|
45
|
+
apt: [],
|
|
46
|
+
npm: [],
|
|
47
|
+
pipx: []
|
|
48
|
+
};
|
|
49
|
+
const imagePackages = runtimePlans.flatMap((plan) => plan.packages ?? []);
|
|
50
|
+
assertImagePackageCompatibility(imagePackages);
|
|
51
|
+
for (const packageConfig of dedupePackages(imagePackages)) {
|
|
52
|
+
if (packageConfig.manager === "apt") {
|
|
53
|
+
packagesByManager.apt.push(packageConfig);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (packageConfig.manager === "npm") {
|
|
57
|
+
packagesByManager.npm.push(packageConfig);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
packagesByManager.pipx.push(packageConfig);
|
|
61
|
+
}
|
|
62
|
+
return packagesByManager;
|
|
63
|
+
};
|
|
@@ -4,13 +4,15 @@ import { mergeWorkspaceResourcePlans } from "./workspaceResources.js";
|
|
|
4
4
|
const CONFIG_FILE_PLACEHOLDER = "<config-file>";
|
|
5
5
|
const INSTANCE_ROOT_PLACEHOLDER = "<instance-root>";
|
|
6
6
|
const SOURCE_AGENT_PLACEHOLDER = "<agent-name>";
|
|
7
|
+
const SOURCE_SLUG_PLACEHOLDER = "<source-slug>";
|
|
7
8
|
const replaceSourceWorkspacePathTemplate = (template, input, instancePaths, meta) => {
|
|
8
9
|
const agentName = input.value.kind === "agent" ? input.value.name : input.slug;
|
|
9
10
|
const instanceRoot = instancePaths.instanceRoot ?? path.posix.dirname(instancePaths.workspacePath);
|
|
10
11
|
return template
|
|
11
12
|
.replaceAll(INSTANCE_ROOT_PLACEHOLDER, instanceRoot)
|
|
12
13
|
.replaceAll(CONFIG_FILE_PLACEHOLDER, meta.configFileName)
|
|
13
|
-
.replaceAll(SOURCE_AGENT_PLACEHOLDER, agentName)
|
|
14
|
+
.replaceAll(SOURCE_AGENT_PLACEHOLDER, agentName)
|
|
15
|
+
.replaceAll(SOURCE_SLUG_PLACEHOLDER, input.slug);
|
|
14
16
|
};
|
|
15
17
|
const resolveSourceWorkspacePath = (input, instancePaths, meta) => meta.instancePaths.sourceWorkspacePathTemplate
|
|
16
18
|
? replaceSourceWorkspacePathTemplate(meta.instancePaths.sourceWorkspacePathTemplate, input, instancePaths, meta)
|
package/dist/compiler/index.d.ts
CHANGED
package/dist/compiler/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SpawnfileError } from "../shared/index.js";
|
|
2
|
-
import { createMoltnetOpenTokenDirectory, createMoltnetNativeServerConfig, createMoltnetNodeConfigPath, createMoltnetServerConfigPath, resolveMoltnetBaseUrl, resolveMoltnetStorePersistenceMountPath } from "./moltnetConfigLowering.js";
|
|
2
|
+
import { createMoltnetOpenTokenDirectory, createMoltnetNativeServerConfig, createMoltnetNodeConfigPath, createMoltnetServerConfigPath, resolveMoltnetClientAuth, resolveMoltnetBaseUrl, resolveMoltnetStorePersistenceMountPath } from "./moltnetConfigLowering.js";
|
|
3
|
+
import { assertNetworkBindingEnvUniqueness } from "./networkBinding.js";
|
|
3
4
|
import { listConcreteMoltnetRoomMemberIds } from "./moltnetRoomMemberships.js";
|
|
4
5
|
import { assertCompatibleMoltnetNetworkName, assertCompatibleMoltnetRoomPolicy, assertCompatibleMoltnetServer } from "./moltnetRoomPolicyCompatibility.js";
|
|
5
6
|
import { createMoltnetNodeConfigContent } from "./moltnetNodeConfig.js";
|
|
@@ -177,6 +178,21 @@ export const generateMoltnetArtifacts = async (plan) => {
|
|
|
177
178
|
throw new SpawnfileError("validation_error", `Duplicate Moltnet node attachment for ${attachment.network}/${attachment.memberId}`);
|
|
178
179
|
}
|
|
179
180
|
nodePlanKeys.add(nodePlanKey);
|
|
181
|
+
if (agentNode.runtime.name === "pi") {
|
|
182
|
+
const clientAuth = resolveMoltnetClientAuth(serverPlan.server, attachment.network, attachment.memberId, node.slug);
|
|
183
|
+
const usesPerAttachmentOpenToken = clientAuth.mode === "open" &&
|
|
184
|
+
clientAuth.staticToken !== true &&
|
|
185
|
+
Boolean(clientAuth.tokenEnv || clientAuth.tokenPath);
|
|
186
|
+
if (usesPerAttachmentOpenToken && clientAuth.tokenPath) {
|
|
187
|
+
const mountId = `agent-${node.slug}-moltnet-tokens`;
|
|
188
|
+
addPersistentMount({
|
|
189
|
+
id: mountId,
|
|
190
|
+
mountPath: createMoltnetOpenTokenDirectory(node.slug),
|
|
191
|
+
reason: `Moltnet open-mode generated agent tokens for ${agentNode.name}`,
|
|
192
|
+
volumeName: createPersistentVolumeName(plan.root, mountId)
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
180
196
|
const nodeConfig = createMoltnetNodeConfigContent({
|
|
181
197
|
agentNode,
|
|
182
198
|
attachment: { ...attachment, memberId: attachment.memberId },
|
|
@@ -207,6 +223,10 @@ export const generateMoltnetArtifacts = async (plan) => {
|
|
|
207
223
|
}
|
|
208
224
|
}
|
|
209
225
|
const managedServerPlans = [...serverPlans.values()].filter((serverPlan) => serverPlan.mode === "managed");
|
|
226
|
+
assertNetworkBindingEnvUniqueness([...serverPlans.values()].map((serverPlan) => ({
|
|
227
|
+
id: serverPlan.networkId,
|
|
228
|
+
members: [...new Set(serverPlan.rooms.flatMap((room) => room.members))]
|
|
229
|
+
})));
|
|
210
230
|
return {
|
|
211
231
|
files: configFiles,
|
|
212
232
|
nodePlans: nodePlans.sort((left, right) => left.configPath.localeCompare(right.configPath)),
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export declare const MOLTNET_BIN_DIRECTORY = "moltnet-bin";
|
|
2
2
|
export declare const MOLTNET_BINARY_NAMES: readonly ["moltnet"];
|
|
3
|
+
export type MoltnetTargetArchitecture = "amd64" | "arm64";
|
|
3
4
|
export declare const resolveMoltnetCliCommand: () => Promise<string>;
|
|
4
|
-
export declare const stageMoltnetBinaries: (outputDirectory: string
|
|
5
|
+
export declare const stageMoltnetBinaries: (outputDirectory: string, options?: {
|
|
6
|
+
architecture?: MoltnetTargetArchitecture;
|
|
7
|
+
}) => Promise<boolean>;
|
|
@@ -7,11 +7,32 @@ import { SpawnfileError } from "../shared/index.js";
|
|
|
7
7
|
const execFile = promisify(execFileCallback);
|
|
8
8
|
const MOLTNET_CLI_ENV = "SPAWNFILE_MOLTNET_CLI";
|
|
9
9
|
const MOLTNET_RELEASE_DIR_ENV = "SPAWNFILE_MOLTNET_RELEASE_DIR";
|
|
10
|
+
const MOLTNET_TARGET_ARCH_ENV = "SPAWNFILE_MOLTNET_TARGET_ARCH";
|
|
10
11
|
const LOCAL_MOLTNET_CLI_PATH = path.resolve(process.cwd(), "moltnet", "bin", "moltnet");
|
|
11
12
|
const MOLTNET_TARGET_OS = "linux";
|
|
12
13
|
export const MOLTNET_BIN_DIRECTORY = "moltnet-bin";
|
|
13
14
|
export const MOLTNET_BINARY_NAMES = ["moltnet"];
|
|
14
|
-
const
|
|
15
|
+
const normalizeTargetArchitecture = (architecture) => {
|
|
16
|
+
switch (architecture) {
|
|
17
|
+
case "amd64":
|
|
18
|
+
case "x86_64":
|
|
19
|
+
case "x64":
|
|
20
|
+
return "amd64";
|
|
21
|
+
case "aarch64":
|
|
22
|
+
case "arm64":
|
|
23
|
+
return "arm64";
|
|
24
|
+
default:
|
|
25
|
+
throw new SpawnfileError("compile_error", `Moltnet container installs do not support target architecture ${architecture}`);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const resolveTargetArchitecture = (architecture) => {
|
|
29
|
+
if (architecture) {
|
|
30
|
+
return architecture;
|
|
31
|
+
}
|
|
32
|
+
const configuredArchitecture = process.env[MOLTNET_TARGET_ARCH_ENV]?.trim();
|
|
33
|
+
if (configuredArchitecture) {
|
|
34
|
+
return normalizeTargetArchitecture(configuredArchitecture);
|
|
35
|
+
}
|
|
15
36
|
switch (process.arch) {
|
|
16
37
|
case "arm64":
|
|
17
38
|
return "arm64";
|
|
@@ -73,12 +94,12 @@ export const resolveMoltnetCliCommand = async () => {
|
|
|
73
94
|
}
|
|
74
95
|
return validateMoltnetCli("moltnet", "PATH");
|
|
75
96
|
};
|
|
76
|
-
export const stageMoltnetBinaries = async (outputDirectory) => {
|
|
97
|
+
export const stageMoltnetBinaries = async (outputDirectory, options = {}) => {
|
|
77
98
|
const releaseDirectory = await resolveConfiguredReleaseDirectory();
|
|
78
99
|
if (!releaseDirectory) {
|
|
79
100
|
return false;
|
|
80
101
|
}
|
|
81
|
-
const architecture = resolveTargetArchitecture();
|
|
102
|
+
const architecture = resolveTargetArchitecture(options.architecture);
|
|
82
103
|
const releaseAssetPath = path.join(releaseDirectory, createReleaseAssetName(architecture));
|
|
83
104
|
if (!(await fileExists(releaseAssetPath))) {
|
|
84
105
|
throw new SpawnfileError("compile_error", `Moltnet release asset ${releaseAssetPath} does not exist. Build it with \`cd moltnet && make release-assets\` or set ${MOLTNET_RELEASE_DIR_ENV}.`);
|
|
@@ -7,5 +7,5 @@ export interface MoltnetWorkspaceLayout {
|
|
|
7
7
|
skillPaths: string[];
|
|
8
8
|
workspaceRootPath: string;
|
|
9
9
|
}
|
|
10
|
-
export declare const resolveMoltnetWorkspaceLayout: (runtimeName: string,
|
|
10
|
+
export declare const resolveMoltnetWorkspaceLayout: (runtimeName: string, _agentName: string) => MoltnetWorkspaceLayout;
|
|
11
11
|
export declare const createMoltnetClientConfigFiles: (node: ResolvedAgentNode, artifacts: MoltnetArtifacts, agentSlug?: string) => EmittedFile[];
|
|
@@ -38,8 +38,7 @@ const createAttachmentConfig = (node, artifacts, attachment, agentSlug) => {
|
|
|
38
38
|
? {
|
|
39
39
|
dms: {
|
|
40
40
|
enabled: attachment.dms.enabled,
|
|
41
|
-
...(attachment.dms.
|
|
42
|
-
...(attachment.dms.reply ? { reply: attachment.dms.reply } : {})
|
|
41
|
+
...(attachment.dms.wake ? { wake: attachment.dms.wake } : {})
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
44
|
: {}),
|
|
@@ -56,32 +55,31 @@ const createAttachmentConfig = (node, artifacts, attachment, agentSlug) => {
|
|
|
56
55
|
id: roomId,
|
|
57
56
|
...(room?.visibility ? { visibility: room.visibility } : {}),
|
|
58
57
|
...(room?.write_policy ? { write_policy: room.write_policy } : {}),
|
|
59
|
-
...(policy.
|
|
60
|
-
...(policy.reply ? { reply: policy.reply } : {})
|
|
58
|
+
...(policy.wake ? { wake: policy.wake } : {})
|
|
61
59
|
};
|
|
62
60
|
})
|
|
63
61
|
}
|
|
64
62
|
: {})
|
|
65
63
|
};
|
|
66
64
|
};
|
|
67
|
-
export const resolveMoltnetWorkspaceLayout = (runtimeName,
|
|
68
|
-
if (runtimeName === "
|
|
65
|
+
export const resolveMoltnetWorkspaceLayout = (runtimeName, _agentName) => {
|
|
66
|
+
if (runtimeName === "daimon" || runtimeName === "pi") {
|
|
69
67
|
return {
|
|
70
|
-
clientConfigPath: `workspace
|
|
71
|
-
cliRuntime:
|
|
72
|
-
skillPaths: [
|
|
68
|
+
clientConfigPath: `workspace/.moltnet/config.json`,
|
|
69
|
+
cliRuntime: "codex",
|
|
70
|
+
skillPaths: [
|
|
71
|
+
`workspace/.agents/skills/${GENERATED_SKILL_NAME}/SKILL.md`,
|
|
72
|
+
`workspace/.codex/skills/${GENERATED_SKILL_NAME}/SKILL.md`
|
|
73
|
+
],
|
|
73
74
|
workspaceRootPath: "workspace"
|
|
74
75
|
};
|
|
75
76
|
}
|
|
76
|
-
if (runtimeName === "
|
|
77
|
+
if (runtimeName === "openclaw" || runtimeName === "picoclaw") {
|
|
77
78
|
return {
|
|
78
|
-
clientConfigPath: `workspace/${
|
|
79
|
+
clientConfigPath: `workspace/${GENERATED_CONFIG_PATH}`,
|
|
79
80
|
cliRuntime: runtimeName,
|
|
80
|
-
skillPaths: [
|
|
81
|
-
|
|
82
|
-
`workspace/${agentName}/.claude/skills/${GENERATED_SKILL_NAME}/SKILL.md`
|
|
83
|
-
],
|
|
84
|
-
workspaceRootPath: `workspace/${agentName}`
|
|
81
|
+
skillPaths: [`workspace/skills/${GENERATED_SKILL_NAME}/SKILL.md`],
|
|
82
|
+
workspaceRootPath: "workspace"
|
|
85
83
|
};
|
|
86
84
|
}
|
|
87
85
|
throw new SpawnfileError("compile_error", `Moltnet client config does not know how to emit files for runtime ${runtimeName}`);
|
|
@@ -47,8 +47,7 @@ export const createMoltnetNodeConfigContent = ({ agentNode, attachment, networkS
|
|
|
47
47
|
id: roomId,
|
|
48
48
|
...(room?.visibility ? { visibility: room.visibility } : {}),
|
|
49
49
|
...(room?.write_policy ? { write_policy: room.write_policy } : {}),
|
|
50
|
-
...(policy.
|
|
51
|
-
...(policy.reply ? { reply: policy.reply } : {})
|
|
50
|
+
...(policy.wake ? { wake: policy.wake } : {})
|
|
52
51
|
};
|
|
53
52
|
})
|
|
54
53
|
}
|
|
@@ -57,8 +56,7 @@ export const createMoltnetNodeConfigContent = ({ agentNode, attachment, networkS
|
|
|
57
56
|
? {
|
|
58
57
|
dms: {
|
|
59
58
|
enabled: attachment.dms.enabled,
|
|
60
|
-
...(attachment.dms.
|
|
61
|
-
...(attachment.dms.reply ? { reply: attachment.dms.reply } : {})
|
|
59
|
+
...(attachment.dms.wake ? { wake: attachment.dms.wake } : {})
|
|
62
60
|
}
|
|
63
61
|
}
|
|
64
62
|
: {})
|
|
@@ -52,7 +52,7 @@ const synthesizeRepresentativeAttachments = (memberships) => memberships
|
|
|
52
52
|
teamSource: membership.declaringTeamSource
|
|
53
53
|
}));
|
|
54
54
|
const roomPolicyKey = (policy) => JSON.stringify(policy ?? {});
|
|
55
|
-
const hasRoomPolicy = (policy) => policy.
|
|
55
|
+
const hasRoomPolicy = (policy) => policy.wake !== undefined;
|
|
56
56
|
const mergeAttachment = (target, next, nodeName) => {
|
|
57
57
|
if (target.dms &&
|
|
58
58
|
next.dms &&
|
|
@@ -16,8 +16,7 @@ const findTeamBySource = (plan, source) => {
|
|
|
16
16
|
return node.value;
|
|
17
17
|
};
|
|
18
18
|
const clonePolicy = (policy) => ({
|
|
19
|
-
...(policy.
|
|
20
|
-
...(policy.reply ? { reply: policy.reply } : {})
|
|
19
|
+
...(policy.wake ? { wake: policy.wake } : {})
|
|
21
20
|
});
|
|
22
21
|
const findDirectRoomPolicy = (plan, teamNode, agentSource, memberId, networkId, roomId) => {
|
|
23
22
|
const agentNode = findAgentBySource(plan, agentSource);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { CompilePlan, ResolvedAgentNode } from "./types.js";
|
|
2
|
-
export declare const resolveRuntimeConfig: (plan: CompilePlan, agentNode: ResolvedAgentNode, nodeSlug: string,
|
|
2
|
+
export declare const resolveRuntimeConfig: (plan: CompilePlan, agentNode: ResolvedAgentNode, nodeSlug: string, _networkId: string, _agentId: string) => Record<string, string>;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { getRuntimeAdapter } from "../runtime/index.js";
|
|
2
2
|
import { SpawnfileError } from "../shared/index.js";
|
|
3
|
-
const DEFAULT_TINYCLAW_PORT = 3777;
|
|
4
3
|
const INSTANCE_ROOT_PLACEHOLDER = "<instance-root>";
|
|
5
4
|
const CONFIG_FILE_PLACEHOLDER = "<config-file>";
|
|
6
5
|
const resolveSequentialRuntimePort = (plan, runtimeName, slug) => {
|
|
@@ -16,7 +15,6 @@ const resolveSequentialRuntimePort = (plan, runtimeName, slug) => {
|
|
|
16
15
|
}
|
|
17
16
|
return basePort + (index * (adapter.container.portStride ?? 1));
|
|
18
17
|
};
|
|
19
|
-
const createTinyClawChannel = (networkId, agentId) => `moltnet:${networkId}:${agentId}`;
|
|
20
18
|
const replaceContainerPathTemplate = (template, instanceRoot, configFileName) => template
|
|
21
19
|
.replaceAll(INSTANCE_ROOT_PLACEHOLDER, instanceRoot)
|
|
22
20
|
.replaceAll(CONFIG_FILE_PLACEHOLDER, configFileName);
|
|
@@ -30,7 +28,7 @@ const resolveRuntimeInstancePaths = (runtimeName, slug) => {
|
|
|
30
28
|
: undefined
|
|
31
29
|
};
|
|
32
30
|
};
|
|
33
|
-
export const resolveRuntimeConfig = (plan, agentNode, nodeSlug,
|
|
31
|
+
export const resolveRuntimeConfig = (plan, agentNode, nodeSlug, _networkId, _agentId) => {
|
|
34
32
|
switch (agentNode.runtime.name) {
|
|
35
33
|
case "openclaw": {
|
|
36
34
|
const port = resolveSequentialRuntimePort(plan, "openclaw", nodeSlug);
|
|
@@ -53,14 +51,15 @@ export const resolveRuntimeConfig = (plan, agentNode, nodeSlug, networkId, agent
|
|
|
53
51
|
kind: "picoclaw"
|
|
54
52
|
};
|
|
55
53
|
}
|
|
56
|
-
case "
|
|
57
|
-
|
|
54
|
+
case "daimon":
|
|
55
|
+
case "pi": {
|
|
56
|
+
const port = getRuntimeAdapter(agentNode.runtime.name).container.port;
|
|
57
|
+
if (!port) {
|
|
58
|
+
throw new SpawnfileError("compile_error", `Unable to resolve ${agentNode.runtime.name} control port for Moltnet agent ${agentNode.name}`);
|
|
59
|
+
}
|
|
58
60
|
return {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
inbound_url: `http://127.0.0.1:${DEFAULT_TINYCLAW_PORT}/api/message`,
|
|
62
|
-
kind: "tinyclaw",
|
|
63
|
-
outbound_url: `http://127.0.0.1:${DEFAULT_TINYCLAW_PORT}/api/responses/pending?channel=${encodeURIComponent(channel)}`
|
|
61
|
+
control_url: `http://127.0.0.1:${port}/agents/${nodeSlug}/wake`,
|
|
62
|
+
kind: "pi"
|
|
64
63
|
};
|
|
65
64
|
}
|
|
66
65
|
default:
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Uppercases an id and replaces every non-[A-Z0-9] character with `_`. */
|
|
2
|
+
export declare const normalizeNetworkEnvSegment: (id: string) => string;
|
|
3
|
+
export declare const networkUrlEnvName: (networkId: string) => string;
|
|
4
|
+
export declare const networkTokenEnvName: (networkId: string, memberId?: string) => string;
|
|
5
|
+
export interface NetworkBindingInput {
|
|
6
|
+
id: string;
|
|
7
|
+
members: string[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Asserts that every generated network binding env name (URL and token vars,
|
|
11
|
+
* across all networks and members) is globally unique after normalization.
|
|
12
|
+
* Collisions are baked into immutable images, so they must fail at compile.
|
|
13
|
+
*/
|
|
14
|
+
export declare const assertNetworkBindingEnvUniqueness: (networks: NetworkBindingInput[]) => void;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { SpawnfileError } from "../shared/index.js";
|
|
2
|
+
const ENV_PREFIX = "SPAWNFILE_NETWORK_";
|
|
3
|
+
/** Uppercases an id and replaces every non-[A-Z0-9] character with `_`. */
|
|
4
|
+
export const normalizeNetworkEnvSegment = (id) => id.toUpperCase().replace(/[^A-Z0-9]+/g, "_");
|
|
5
|
+
export const networkUrlEnvName = (networkId) => `${ENV_PREFIX}${normalizeNetworkEnvSegment(networkId)}_URL`;
|
|
6
|
+
export const networkTokenEnvName = (networkId, memberId) => memberId === undefined
|
|
7
|
+
? `${ENV_PREFIX}${normalizeNetworkEnvSegment(networkId)}_TOKEN`
|
|
8
|
+
: `${ENV_PREFIX}${normalizeNetworkEnvSegment(networkId)}_TOKEN_${normalizeNetworkEnvSegment(memberId)}`;
|
|
9
|
+
/**
|
|
10
|
+
* Asserts that every generated network binding env name (URL and token vars,
|
|
11
|
+
* across all networks and members) is globally unique after normalization.
|
|
12
|
+
* Collisions are baked into immutable images, so they must fail at compile.
|
|
13
|
+
*/
|
|
14
|
+
export const assertNetworkBindingEnvUniqueness = (networks) => {
|
|
15
|
+
const seen = new Map();
|
|
16
|
+
const claim = (envName, owner) => {
|
|
17
|
+
const existing = seen.get(envName);
|
|
18
|
+
if (existing && existing !== owner) {
|
|
19
|
+
throw new SpawnfileError("validation_error", `Network binding env name ${envName} collides between ${existing} and ${owner}. ` +
|
|
20
|
+
"Rename a network or member so their normalized env names stay unique.");
|
|
21
|
+
}
|
|
22
|
+
seen.set(envName, owner);
|
|
23
|
+
};
|
|
24
|
+
for (const network of networks) {
|
|
25
|
+
claim(networkUrlEnvName(network.id), `network ${network.id} url`);
|
|
26
|
+
const members = [...new Set(network.members)];
|
|
27
|
+
if (members.length === 1) {
|
|
28
|
+
claim(networkTokenEnvName(network.id), `network ${network.id} token`);
|
|
29
|
+
}
|
|
30
|
+
for (const member of members) {
|
|
31
|
+
claim(networkTokenEnvName(network.id, member), `network ${network.id} member ${member}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type BuildProjectOptions, type BuildProjectResult } from "./buildProject.js";
|
|
2
|
+
export type DockerPushRunner = (command: string, args: string[]) => Promise<string>;
|
|
3
|
+
export interface PublishProjectOptions extends BuildProjectOptions {
|
|
4
|
+
pushRunner?: DockerPushRunner;
|
|
5
|
+
}
|
|
6
|
+
export interface PublishProjectResult extends BuildProjectResult {
|
|
7
|
+
digest: string | null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Compiles, builds, verifies, and pushes a project image. Pre-push verification
|
|
11
|
+
* refuses to publish a report that leaks creator paths or omits secret markers.
|
|
12
|
+
*/
|
|
13
|
+
export declare const publishProject: (inputPath: string, options?: PublishProjectOptions) => Promise<PublishProjectResult>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { DISTRIBUTION_REPORT_OUTPUT_FILE, verifyDistributionReport } from "../distribution/index.js";
|
|
5
|
+
import { SpawnfileError } from "../shared/index.js";
|
|
6
|
+
import { buildProject } from "./buildProject.js";
|
|
7
|
+
/* v8 ignore start -- docker push runner is covered by distribution E2E */
|
|
8
|
+
const runDockerCapture = async (command, args) => new Promise((resolve, reject) => {
|
|
9
|
+
const child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
10
|
+
const stdout = [];
|
|
11
|
+
const stderr = [];
|
|
12
|
+
child.stdout.on("data", (chunk) => stdout.push(String(chunk)));
|
|
13
|
+
child.stderr.on("data", (chunk) => stderr.push(String(chunk)));
|
|
14
|
+
child.on("error", reject);
|
|
15
|
+
child.on("close", (code) => {
|
|
16
|
+
if (code === 0) {
|
|
17
|
+
resolve(stdout.join(""));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
reject(new SpawnfileError("runtime_error", `docker ${args[0]} failed (${code}): ${stderr.join("").trim()}`));
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
/* v8 ignore stop */
|
|
24
|
+
const resolvePushedDigest = async (command, imageTag, runDocker) => {
|
|
25
|
+
try {
|
|
26
|
+
const raw = (await runDocker(command, ["image", "inspect", "--format", "{{json .RepoDigests}}", imageTag])).trim();
|
|
27
|
+
const digests = JSON.parse(raw);
|
|
28
|
+
const match = digests
|
|
29
|
+
.map((entry) => (entry.includes("@") ? entry.slice(entry.indexOf("@") + 1) : null))
|
|
30
|
+
.find((entry) => Boolean(entry));
|
|
31
|
+
return match ?? null;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Compiles, builds, verifies, and pushes a project image. Pre-push verification
|
|
39
|
+
* refuses to publish a report that leaks creator paths or omits secret markers.
|
|
40
|
+
*/
|
|
41
|
+
export const publishProject = async (inputPath, options = {}) => {
|
|
42
|
+
if (!options.imageTag) {
|
|
43
|
+
throw new SpawnfileError("validation_error", "publish requires --tag with a registry image reference");
|
|
44
|
+
}
|
|
45
|
+
const buildResult = await buildProject(inputPath, options);
|
|
46
|
+
const dockerCommand = options.dockerCommand ?? "docker";
|
|
47
|
+
const runDocker = options.pushRunner ?? runDockerCapture;
|
|
48
|
+
const reportPath = path.join(buildResult.outputDirectory, DISTRIBUTION_REPORT_OUTPUT_FILE);
|
|
49
|
+
let parsedReport;
|
|
50
|
+
try {
|
|
51
|
+
parsedReport = JSON.parse(await readFile(reportPath, "utf8"));
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
/* v8 ignore next 2 -- compile always emits the report; defensive only */
|
|
55
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
56
|
+
throw new SpawnfileError("validation_error", `Unable to read distribution report for publish: ${reason}`);
|
|
57
|
+
}
|
|
58
|
+
verifyDistributionReport({
|
|
59
|
+
forbiddenPathFragments: [
|
|
60
|
+
path.resolve(inputPath),
|
|
61
|
+
buildResult.outputDirectory,
|
|
62
|
+
buildResult.report.root
|
|
63
|
+
],
|
|
64
|
+
report: parsedReport
|
|
65
|
+
});
|
|
66
|
+
await runDocker(dockerCommand, ["push", buildResult.imageTag]);
|
|
67
|
+
const digest = await resolvePushedDigest(dockerCommand, buildResult.imageTag, runDocker);
|
|
68
|
+
return {
|
|
69
|
+
...buildResult,
|
|
70
|
+
digest
|
|
71
|
+
};
|
|
72
|
+
};
|