spawnfile 0.1.12 → 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 +9 -2
- 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 +2 -0
- package/dist/cli/runCli.d.ts +6 -0
- package/dist/cli/runCli.js +4 -0
- package/dist/cli/statusCommand.d.ts +2 -1
- package/dist/cli/statusCommand.js +50 -16
- 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 +3 -1
- package/dist/compiler/containerArtifactsRender.js +25 -69
- package/dist/compiler/containerEntrypointRender.js +4 -1
- 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/moltnetArtifacts.js +16 -1
- package/dist/compiler/moltnetBinaries.d.ts +4 -1
- package/dist/compiler/moltnetBinaries.js +24 -3
- package/dist/compiler/moltnetClientConfig.js +11 -0
- package/dist/compiler/moltnetRuntimeConfig.js +11 -0
- package/dist/compiler/runProject.js +7 -1
- package/dist/compiler/runProjectDocker.js +157 -12
- package/dist/compiler/upProject.js +1 -0
- package/dist/deployment/dockerRecover.d.ts +17 -0
- package/dist/deployment/dockerRecover.js +129 -0
- package/dist/deployment/index.d.ts +1 -0
- package/dist/deployment/index.js +1 -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/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 +93 -10
- package/dist/runtime/install.d.ts +1 -1
- package/dist/runtime/install.js +43 -1
- 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/registry.js +3 -0
- package/dist/runtime/types.d.ts +1 -0
- package/dist/status/deployments.js +5 -12
- package/package.json +7 -2
- package/runtimes.yaml +27 -11
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { SpawnfileError } from "../shared/index.js";
|
|
2
2
|
import { resolveRuntimeInstallSelection } from "./install.js";
|
|
3
3
|
export const RUNTIME_INSTALL_ROOT = "/opt/spawnfile/runtime-installs";
|
|
4
|
+
const PI_RUNTIME_BASE_IMAGE_ENV = "SPAWNFILE_PI_RUNTIME_BASE_IMAGE";
|
|
5
|
+
const DAIMON_RUNTIME_IMAGE_ENV = "SPAWNFILE_DAIMON_RUNTIME_IMAGE";
|
|
6
|
+
const DAIMON_RUNTIME_BASE_IMAGE_ENV = "SPAWNFILE_DAIMON_RUNTIME_BASE_IMAGE";
|
|
7
|
+
const OPENCLAW_RUNTIME_IMAGE_ENV = "SPAWNFILE_OPENCLAW_RUNTIME_IMAGE";
|
|
8
|
+
const PICOCLAW_RUNTIME_IMAGE_ENV = "SPAWNFILE_PICOCLAW_RUNTIME_IMAGE";
|
|
9
|
+
const PI_AI_PACKAGE_NAME = "@earendil-works/pi-ai";
|
|
10
|
+
const PI_CODING_AGENT_PACKAGE_NAME = "@earendil-works/pi-coding-agent";
|
|
11
|
+
const PI_PACKAGE_VERSION = "0.79.9";
|
|
4
12
|
const assertArtifactInstallSelection = (runtimeName, selection) => {
|
|
5
13
|
if (selection.kind !== "source_repo") {
|
|
6
14
|
return;
|
|
@@ -11,29 +19,50 @@ const createPicoClawArchiveInstallCommands = (runtimeRoot, selection) => [
|
|
|
11
19
|
`mkdir -p ${runtimeRoot}/bin`,
|
|
12
20
|
`arch="$(dpkg --print-architecture)" && case "$arch" in amd64) asset=${JSON.stringify(selection.versionedAssets.linux_amd64)} ;; arm64) asset=${JSON.stringify(selection.versionedAssets.linux_arm64)} ;; *) echo "Unsupported PicoClaw release architecture: $arch" >&2; exit 1 ;; esac && url="https://github.com/${selection.repository}/releases/download/${selection.tag}/$asset" && curl -fsSL -o /tmp/picoclaw.tar.gz "$url" && rm -rf /tmp/picoclaw-extract && mkdir -p /tmp/picoclaw-extract && tar -xzf /tmp/picoclaw.tar.gz -C /tmp/picoclaw-extract && binary_path="$(find /tmp/picoclaw-extract -type f -name ${JSON.stringify(selection.binaryName)} | head -n 1)" && [ -n "$binary_path" ] && install -m 0755 "$binary_path" ${runtimeRoot}/bin/${selection.binaryName} && ln -sf ${runtimeRoot}/bin/${selection.binaryName} /usr/local/bin/${selection.binaryName} && rm -rf /tmp/picoclaw.tar.gz /tmp/picoclaw-extract`
|
|
13
21
|
];
|
|
22
|
+
const createRuntimeImageCopyCommand = (imageRef, runtimeRoot) => `COPY --from=${imageRef} ${runtimeRoot} ${runtimeRoot}`;
|
|
23
|
+
const resolveRuntimeImageRef = (envName, selection) => process.env[envName]?.trim() ||
|
|
24
|
+
(selection.kind === "container_image"
|
|
25
|
+
? `${selection.image}:${selection.tag}`
|
|
26
|
+
: undefined);
|
|
14
27
|
export const createRuntimeInstallRecipe = async (runtimeName) => {
|
|
15
28
|
const selection = await resolveRuntimeInstallSelection(runtimeName);
|
|
16
29
|
assertArtifactInstallSelection(runtimeName, selection);
|
|
17
30
|
const installRoot = `${RUNTIME_INSTALL_ROOT}/${runtimeName}`;
|
|
18
31
|
switch (runtimeName) {
|
|
19
32
|
case "openclaw": {
|
|
20
|
-
|
|
33
|
+
const openClawRuntimeImage = resolveRuntimeImageRef(OPENCLAW_RUNTIME_IMAGE_ENV, selection);
|
|
34
|
+
if (openClawRuntimeImage) {
|
|
35
|
+
return {
|
|
36
|
+
commands: [],
|
|
37
|
+
copyCommands: [createRuntimeImageCopyCommand(openClawRuntimeImage, installRoot)],
|
|
38
|
+
runtimeName,
|
|
39
|
+
runtimeRoot: installRoot
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (selection.kind !== "npm") {
|
|
21
43
|
throw new SpawnfileError("runtime_error", `Runtime ${runtimeName} has no compiled artifact recipe for ${selection.kind}`);
|
|
22
44
|
}
|
|
23
45
|
return {
|
|
24
|
-
commands:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
copyCommands:
|
|
28
|
-
? [`COPY --from=${selection.image}:${selection.tag} /app ${installRoot}`]
|
|
29
|
-
: [],
|
|
46
|
+
commands: [
|
|
47
|
+
`npm install -g --omit=dev --no-fund --no-audit ${selection.packageName}@${selection.version}`
|
|
48
|
+
],
|
|
49
|
+
copyCommands: [],
|
|
30
50
|
runtimeName,
|
|
31
|
-
runtimeRoot: selection.
|
|
32
|
-
? installRoot
|
|
33
|
-
: `/usr/local/lib/node_modules/${selection.packageName}`
|
|
51
|
+
runtimeRoot: `/usr/local/lib/node_modules/${selection.packageName}`
|
|
34
52
|
};
|
|
35
53
|
}
|
|
36
54
|
case "picoclaw": {
|
|
55
|
+
const picoClawRuntimeImage = resolveRuntimeImageRef(PICOCLAW_RUNTIME_IMAGE_ENV, selection);
|
|
56
|
+
if (picoClawRuntimeImage) {
|
|
57
|
+
return {
|
|
58
|
+
commands: [
|
|
59
|
+
`mkdir -p /usr/local/bin && ln -sf ${installRoot}/bin/picoclaw /usr/local/bin/picoclaw`
|
|
60
|
+
],
|
|
61
|
+
copyCommands: [createRuntimeImageCopyCommand(picoClawRuntimeImage, installRoot)],
|
|
62
|
+
runtimeName,
|
|
63
|
+
runtimeRoot: installRoot
|
|
64
|
+
};
|
|
65
|
+
}
|
|
37
66
|
if (selection.kind !== "github_release_archive") {
|
|
38
67
|
throw new SpawnfileError("runtime_error", `Runtime ${runtimeName} has no compiled artifact recipe for ${selection.kind}`);
|
|
39
68
|
}
|
|
@@ -44,6 +73,60 @@ export const createRuntimeInstallRecipe = async (runtimeName) => {
|
|
|
44
73
|
runtimeRoot: installRoot
|
|
45
74
|
};
|
|
46
75
|
}
|
|
76
|
+
case "daimon": {
|
|
77
|
+
const daimonRuntimeImage = process.env[DAIMON_RUNTIME_IMAGE_ENV]?.trim() ||
|
|
78
|
+
process.env[DAIMON_RUNTIME_BASE_IMAGE_ENV]?.trim() ||
|
|
79
|
+
(selection.kind === "container_image"
|
|
80
|
+
? `${selection.image}:${selection.tag}`
|
|
81
|
+
: undefined);
|
|
82
|
+
if (daimonRuntimeImage) {
|
|
83
|
+
return {
|
|
84
|
+
commands: [],
|
|
85
|
+
copyCommands: [createRuntimeImageCopyCommand(daimonRuntimeImage, installRoot)],
|
|
86
|
+
runtimeName,
|
|
87
|
+
runtimeRoot: installRoot
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (selection.kind !== "npm") {
|
|
91
|
+
throw new SpawnfileError("runtime_error", `Runtime ${runtimeName} has no compiled artifact recipe for ${selection.kind}`);
|
|
92
|
+
}
|
|
93
|
+
const npmPackages = [
|
|
94
|
+
`${selection.packageName}@${selection.version}`,
|
|
95
|
+
`${PI_CODING_AGENT_PACKAGE_NAME}@${PI_PACKAGE_VERSION}`,
|
|
96
|
+
`${PI_AI_PACKAGE_NAME}@${PI_PACKAGE_VERSION}`
|
|
97
|
+
];
|
|
98
|
+
return {
|
|
99
|
+
commands: [
|
|
100
|
+
`mkdir -p ${installRoot}`,
|
|
101
|
+
`cd ${installRoot} && npm install --omit=dev --no-fund --no-audit ${npmPackages.join(" ")}`
|
|
102
|
+
],
|
|
103
|
+
copyCommands: [],
|
|
104
|
+
runtimeName,
|
|
105
|
+
runtimeRoot: installRoot
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
case "pi": {
|
|
109
|
+
if (selection.kind !== "npm") {
|
|
110
|
+
throw new SpawnfileError("runtime_error", `Runtime ${runtimeName} has no compiled artifact recipe for ${selection.kind}`);
|
|
111
|
+
}
|
|
112
|
+
const prebuiltBaseImage = process.env[PI_RUNTIME_BASE_IMAGE_ENV]?.trim() || undefined;
|
|
113
|
+
const npmPackages = [
|
|
114
|
+
`${selection.packageName}@${selection.version}`,
|
|
115
|
+
`${PI_AI_PACKAGE_NAME}@${selection.version}`
|
|
116
|
+
];
|
|
117
|
+
return {
|
|
118
|
+
...(prebuiltBaseImage ? { baseImage: prebuiltBaseImage } : {}),
|
|
119
|
+
commands: prebuiltBaseImage
|
|
120
|
+
? [`mkdir -p ${installRoot}`]
|
|
121
|
+
: [
|
|
122
|
+
`mkdir -p ${installRoot}`,
|
|
123
|
+
`cd ${installRoot} && npm install --omit=dev --no-fund --no-audit ${npmPackages.join(" ")}`
|
|
124
|
+
],
|
|
125
|
+
copyCommands: [],
|
|
126
|
+
runtimeName,
|
|
127
|
+
runtimeRoot: installRoot
|
|
128
|
+
};
|
|
129
|
+
}
|
|
47
130
|
/* c8 ignore next 5 -- compileable runtimes are exhaustively covered above */
|
|
48
131
|
default:
|
|
49
132
|
throw new SpawnfileError("runtime_error", `Runtime ${runtimeName} has no container install recipe`);
|
package/dist/runtime/install.js
CHANGED
|
@@ -9,6 +9,27 @@ const convertRemoteToHttps = (remote) => {
|
|
|
9
9
|
return remote.replace(/^git\+/, "");
|
|
10
10
|
};
|
|
11
11
|
const installHints = new Map([
|
|
12
|
+
[
|
|
13
|
+
"daimon",
|
|
14
|
+
{
|
|
15
|
+
container_image: {
|
|
16
|
+
ecosystem: "node",
|
|
17
|
+
installHint: "Copy a pinned Daimon runtime image."
|
|
18
|
+
},
|
|
19
|
+
github_release_archive: {
|
|
20
|
+
ecosystem: "node",
|
|
21
|
+
installHint: "Download a pinned Daimon runtime release archive."
|
|
22
|
+
},
|
|
23
|
+
npm: {
|
|
24
|
+
ecosystem: "node",
|
|
25
|
+
installHint: "Install the pinned Daimon package and Pi SDK dependencies inside the generated runtime app."
|
|
26
|
+
},
|
|
27
|
+
source_repo: {
|
|
28
|
+
ecosystem: "node",
|
|
29
|
+
installHint: "Checkout the pinned Daimon repo ref and install from the repository root."
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
],
|
|
12
33
|
[
|
|
13
34
|
"openclaw",
|
|
14
35
|
{
|
|
@@ -50,6 +71,27 @@ const installHints = new Map([
|
|
|
50
71
|
installHint: "Checkout the pinned repo ref and build/install from the repository root."
|
|
51
72
|
}
|
|
52
73
|
}
|
|
74
|
+
],
|
|
75
|
+
[
|
|
76
|
+
"pi",
|
|
77
|
+
{
|
|
78
|
+
container_image: {
|
|
79
|
+
ecosystem: "node",
|
|
80
|
+
installHint: "Copy a pinned Pi harness runtime image."
|
|
81
|
+
},
|
|
82
|
+
github_release_archive: {
|
|
83
|
+
ecosystem: "node",
|
|
84
|
+
installHint: "Download a pinned Pi harness release archive."
|
|
85
|
+
},
|
|
86
|
+
npm: {
|
|
87
|
+
ecosystem: "node",
|
|
88
|
+
installHint: "Install pinned Pi SDK dependencies inside the generated runtime app."
|
|
89
|
+
},
|
|
90
|
+
source_repo: {
|
|
91
|
+
ecosystem: "node",
|
|
92
|
+
installHint: "Checkout the pinned Pi repo ref and install from the repository root."
|
|
93
|
+
}
|
|
94
|
+
}
|
|
53
95
|
]
|
|
54
96
|
]);
|
|
55
97
|
export const resolveRuntimeInstallSelection = async (runtimeName) => {
|
|
@@ -61,7 +103,7 @@ export const resolveRuntimeInstallSelection = async (runtimeName) => {
|
|
|
61
103
|
switch (runtime.install?.kind) {
|
|
62
104
|
case "container_image":
|
|
63
105
|
return {
|
|
64
|
-
ecosystem:
|
|
106
|
+
ecosystem: installProfile.container_image.ecosystem,
|
|
65
107
|
image: runtime.install.image,
|
|
66
108
|
installHint: installProfile.container_image.installHint,
|
|
67
109
|
kind: "container_image",
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { listExecutionModelSecretNames } from "../../compiler/modelEnv.js";
|
|
3
|
+
import { SpawnfileError } from "../../shared/index.js";
|
|
4
|
+
import { createAgentCapabilities, createDiagnostic, createDocumentFiles, createSkillFiles } from "../common.js";
|
|
5
|
+
import { createPiAgentConfig, PI_PACKAGE_NAME, PI_PACKAGE_VERSION, renderPiApp, renderPiAppConfig, renderPiModelsConfig, renderPiPackageJson } from "./appTemplate.js";
|
|
6
|
+
import { preparePiRuntimeAuth } from "./runAuth.js";
|
|
7
|
+
const PI_CONFIG_FILE = "pi-app.json";
|
|
8
|
+
const PI_CONTROL_PORT = 19690;
|
|
9
|
+
const PI_MODELS_FILE = "home/.pi/agent/models.json";
|
|
10
|
+
const moveWorkspaceFileToAgentWorkspace = (file, agentSlug) => {
|
|
11
|
+
if (!file.path.startsWith("workspace/")) {
|
|
12
|
+
return file;
|
|
13
|
+
}
|
|
14
|
+
const relativePath = file.path.slice("workspace/".length);
|
|
15
|
+
return {
|
|
16
|
+
...file,
|
|
17
|
+
path: path.posix.join("workspace", "agents", agentSlug, relativePath)
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
const createContainerTargets = async (inputs) => {
|
|
21
|
+
const agentInputs = inputs.filter((input) => input.kind === "agent" && input.value.kind === "agent");
|
|
22
|
+
if (agentInputs.length === 0) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
const agents = agentInputs.map((input) => createPiAgentConfig(input.value, input.slug, input.id));
|
|
26
|
+
const envFiles = [
|
|
27
|
+
...new Set(agentInputs.flatMap((input) => listExecutionModelSecretNames(input.value.execution)))
|
|
28
|
+
].map((secretName) => ({
|
|
29
|
+
envName: secretName,
|
|
30
|
+
relativePath: `secrets/${secretName}`
|
|
31
|
+
}));
|
|
32
|
+
return [
|
|
33
|
+
{
|
|
34
|
+
envFiles,
|
|
35
|
+
files: [
|
|
36
|
+
...agentInputs.flatMap((input) => input.emittedFiles.map((file) => moveWorkspaceFileToAgentWorkspace(file, input.slug))),
|
|
37
|
+
{
|
|
38
|
+
content: renderPiAppConfig(agents),
|
|
39
|
+
path: PI_CONFIG_FILE
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
content: renderPiModelsConfig(agentInputs.map((input) => input.value)),
|
|
43
|
+
path: PI_MODELS_FILE
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
content: renderPiPackageJson(),
|
|
47
|
+
path: "runtime/package.json"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
content: renderPiApp(),
|
|
51
|
+
mode: 0o755,
|
|
52
|
+
path: "runtime/app.mjs"
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
id: "pi-app",
|
|
56
|
+
sourceIds: agentInputs.map((input) => input.id).sort()
|
|
57
|
+
}
|
|
58
|
+
];
|
|
59
|
+
};
|
|
60
|
+
const scheduleOutcomeFor = (node) => {
|
|
61
|
+
if (!node.schedule) {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
if (node.schedule.kind === "every" || node.schedule.kind === "disabled") {
|
|
65
|
+
return {
|
|
66
|
+
message: "Pi generated runtime app owns this schedule",
|
|
67
|
+
outcome: "supported"
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
message: "Pi generated runtime app supports every schedules in Spawnfile v0.1",
|
|
72
|
+
outcome: "degraded"
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
const createScheduleDiagnostics = (node) => node.schedule?.kind === "cron"
|
|
76
|
+
? [
|
|
77
|
+
createDiagnostic("warn", "Pi generated runtime app supports every schedules in Spawnfile v0.1; cron schedules are degraded")
|
|
78
|
+
]
|
|
79
|
+
: [];
|
|
80
|
+
const moltnetCapabilityOptions = (node) => node.surfaces?.moltnet
|
|
81
|
+
? {
|
|
82
|
+
moltnetMessage: "Pi generated runtime app exposes a control endpoint for Moltnet bridge wake delivery",
|
|
83
|
+
moltnetOutcome: "supported"
|
|
84
|
+
}
|
|
85
|
+
: {};
|
|
86
|
+
const assertSupportedPiSurfaces = (surfaces) => {
|
|
87
|
+
if (!surfaces) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const unsupported = [
|
|
91
|
+
surfaces.discord ? "discord" : null,
|
|
92
|
+
surfaces.http ? "http" : null,
|
|
93
|
+
surfaces.slack ? "slack" : null,
|
|
94
|
+
surfaces.telegram ? "telegram" : null,
|
|
95
|
+
surfaces.webhook ? "webhook" : null,
|
|
96
|
+
surfaces.whatsapp ? "whatsapp" : null
|
|
97
|
+
].filter((surface) => surface !== null);
|
|
98
|
+
if (unsupported.length > 0) {
|
|
99
|
+
throw new SpawnfileError("validation_error", `Pi runtime only supports Moltnet surfaces in Spawnfile v0.1; unsupported surfaces: ${unsupported.join(", ")}`);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
export const piAdapter = {
|
|
103
|
+
assertSupportedModelTarget(target) {
|
|
104
|
+
if (target.endpoint) {
|
|
105
|
+
if (target.provider !== "custom" && target.provider !== "local") {
|
|
106
|
+
throw new SpawnfileError("validation_error", "Pi runtime only supports endpoints on custom or local model providers");
|
|
107
|
+
}
|
|
108
|
+
if (target.auth.method === "none" || target.auth.method === "api_key") {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
throw new SpawnfileError("validation_error", `Pi runtime endpoint models only support none or api_key auth, got ${target.auth.method}`);
|
|
112
|
+
}
|
|
113
|
+
if (target.provider === "openai") {
|
|
114
|
+
if (target.auth.method === "api_key" || target.auth.method === "codex") {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (target.provider === "anthropic" && target.auth.method === "api_key") {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (target.provider === "anthropic" && target.auth.method === "claude-code") {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
throw new SpawnfileError("validation_error", `Pi runtime does not support model auth method ${target.auth.method} for provider ${target.provider}`);
|
|
125
|
+
},
|
|
126
|
+
assertSupportedSurfaces(surfaces) {
|
|
127
|
+
assertSupportedPiSurfaces(surfaces);
|
|
128
|
+
},
|
|
129
|
+
container: {
|
|
130
|
+
configFileName: PI_CONFIG_FILE,
|
|
131
|
+
configPathEnv: "SPAWNFILE_PI_CONFIG",
|
|
132
|
+
homeEnv: "SPAWNFILE_PI_HOME",
|
|
133
|
+
instancePaths: {
|
|
134
|
+
configPathTemplate: "<instance-root>/pi/<config-file>",
|
|
135
|
+
homePathTemplate: "<instance-root>/home",
|
|
136
|
+
sourceWorkspacePathTemplate: "<instance-root>/workspace/agents/<source-slug>",
|
|
137
|
+
workspacePathTemplate: "<instance-root>/workspace"
|
|
138
|
+
},
|
|
139
|
+
port: PI_CONTROL_PORT,
|
|
140
|
+
portEnv: "SPAWNFILE_PI_CONTROL_PORT",
|
|
141
|
+
standaloneBaseImage: "node:24-bookworm-slim",
|
|
142
|
+
startCommand: ["node", "<runtime-root>/app.mjs", "<config-path>"],
|
|
143
|
+
systemDeps: ["bash", "ca-certificates", "curl", "git", "procps"]
|
|
144
|
+
},
|
|
145
|
+
systemInstructionSurface: {
|
|
146
|
+
placement: "append_pointer",
|
|
147
|
+
resolvePath() {
|
|
148
|
+
return "workspace/AGENTS.md";
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
async compileAgent(node) {
|
|
152
|
+
const scheduleOutcome = scheduleOutcomeFor(node);
|
|
153
|
+
return {
|
|
154
|
+
capabilities: createAgentCapabilities(node, {
|
|
155
|
+
...moltnetCapabilityOptions(node),
|
|
156
|
+
mcpOutcome: node.mcpServers.length > 0 ? "degraded" : "supported",
|
|
157
|
+
sandboxOutcome: node.execution?.sandbox ? "degraded" : "supported",
|
|
158
|
+
scheduleMessage: scheduleOutcome.message,
|
|
159
|
+
scheduleOutcome: scheduleOutcome.outcome,
|
|
160
|
+
subagentOutcome: node.subagents.length > 0 ? "degraded" : "supported"
|
|
161
|
+
}),
|
|
162
|
+
diagnostics: [
|
|
163
|
+
...createScheduleDiagnostics(node),
|
|
164
|
+
...(node.execution?.sandbox
|
|
165
|
+
? [createDiagnostic("warn", "Pi runtime relies on container and workspace isolation; Pi itself is not a sandbox engine")]
|
|
166
|
+
: []),
|
|
167
|
+
...(node.mcpServers.length > 0
|
|
168
|
+
? [createDiagnostic("warn", "Pi runtime does not lower MCP server declarations in Spawnfile v0.1")]
|
|
169
|
+
: []),
|
|
170
|
+
...(node.subagents.length > 0
|
|
171
|
+
? [createDiagnostic("warn", "Pi runtime groups compiled agents but does not preserve native parent-owned subagent semantics in v0.1")]
|
|
172
|
+
: [])
|
|
173
|
+
],
|
|
174
|
+
files: [
|
|
175
|
+
...createDocumentFiles("workspace", node.docs),
|
|
176
|
+
...createSkillFiles("workspace/skills", node.skills)
|
|
177
|
+
]
|
|
178
|
+
};
|
|
179
|
+
},
|
|
180
|
+
async createContainerTargets(inputs) {
|
|
181
|
+
return createContainerTargets(inputs);
|
|
182
|
+
},
|
|
183
|
+
name: "pi",
|
|
184
|
+
prepareRuntimeAuth: preparePiRuntimeAuth,
|
|
185
|
+
validateRuntimeOptions(options) {
|
|
186
|
+
const diagnostics = [];
|
|
187
|
+
const unsupported = Object.keys(options).filter((key) => key !== "restrict_to_workspace");
|
|
188
|
+
for (const key of unsupported) {
|
|
189
|
+
diagnostics.push(createDiagnostic("warn", `Pi runtime option ${key} is not used yet`));
|
|
190
|
+
}
|
|
191
|
+
return diagnostics;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
export const daimonAdapter = {
|
|
195
|
+
...piAdapter,
|
|
196
|
+
name: "daimon"
|
|
197
|
+
};
|
|
198
|
+
export const PI_RUNTIME_PACKAGE = `${PI_PACKAGE_NAME}@${PI_PACKAGE_VERSION}`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const renderPiActivitySource: () => string;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export const renderPiActivitySource = () => String.raw `
|
|
2
|
+
const maxActivityEvents = 500;
|
|
3
|
+
|
|
4
|
+
const createActivityBroker = () => {
|
|
5
|
+
let sequence = 0;
|
|
6
|
+
const events = [];
|
|
7
|
+
const clients = new Set();
|
|
8
|
+
|
|
9
|
+
const normalizeFilter = (value) => typeof value === "string" && value.trim().length > 0
|
|
10
|
+
? value.trim()
|
|
11
|
+
: "";
|
|
12
|
+
const matchesFilter = (event, filter) => {
|
|
13
|
+
const normalized = normalizeFilter(filter);
|
|
14
|
+
return normalized === "" ||
|
|
15
|
+
event.agent_id === normalized ||
|
|
16
|
+
event.agent_slug === normalized ||
|
|
17
|
+
event.agent_name === normalized ||
|
|
18
|
+
event.agent_id === "agent:" + normalized;
|
|
19
|
+
};
|
|
20
|
+
const parseTail = (value) => {
|
|
21
|
+
const parsed = Number(value);
|
|
22
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : maxActivityEvents;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const publish = (event) => {
|
|
26
|
+
const full = {
|
|
27
|
+
version: "spawnfile.activity.v1",
|
|
28
|
+
sequence: ++sequence,
|
|
29
|
+
created_at: new Date().toISOString(),
|
|
30
|
+
...event
|
|
31
|
+
};
|
|
32
|
+
events.push(full);
|
|
33
|
+
if (events.length > maxActivityEvents) {
|
|
34
|
+
events.splice(0, events.length - maxActivityEvents);
|
|
35
|
+
}
|
|
36
|
+
const frame = "data: " + JSON.stringify(full) + "\n\n";
|
|
37
|
+
for (const client of [...clients]) {
|
|
38
|
+
if (!matchesFilter(full, client.filter)) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
client.response.write(frame);
|
|
43
|
+
} catch {
|
|
44
|
+
clients.delete(client);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return full;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const list = (filter, tail) => {
|
|
51
|
+
const selected = events.filter((event) => matchesFilter(event, filter));
|
|
52
|
+
return selected.slice(Math.max(0, selected.length - parseTail(tail)));
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const stream = (request, response, filter, tail) => {
|
|
56
|
+
response.writeHead(200, {
|
|
57
|
+
"cache-control": "no-cache",
|
|
58
|
+
"connection": "keep-alive",
|
|
59
|
+
"content-type": "text/event-stream"
|
|
60
|
+
});
|
|
61
|
+
response.write("event: ready\ndata: {\"status\":\"ok\"}\n\n");
|
|
62
|
+
for (const event of list(filter, tail)) {
|
|
63
|
+
response.write("data: " + JSON.stringify(event) + "\n\n");
|
|
64
|
+
}
|
|
65
|
+
const client = { filter: normalizeFilter(filter), response };
|
|
66
|
+
clients.add(client);
|
|
67
|
+
request.on("close", () => clients.delete(client));
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return { list, publish, stream };
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const redactActivityText = (value) => {
|
|
74
|
+
let redacted = String(value);
|
|
75
|
+
redacted = redacted.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/gi, "Bearer [REDACTED]");
|
|
76
|
+
redacted = redacted.replace(/\bmagt_v1_[A-Za-z0-9_-]{16,}\b/g, "[REDACTED]");
|
|
77
|
+
redacted = redacted.replace(/\b(?:sk|sk-proj)-[A-Za-z0-9_-]{20,}\b/g, "[REDACTED]");
|
|
78
|
+
redacted = redacted.replace(
|
|
79
|
+
/("([^"]*(?:api[_-]?key|token|secret|password)[^"]*)"\s*:\s*")([^"]+)(")/gi,
|
|
80
|
+
"$1[REDACTED]$4"
|
|
81
|
+
);
|
|
82
|
+
redacted = redacted.replace(/\/(?:Users|home|private|tmp|var|opt|run)\/[^\s"']+/g, "[path]");
|
|
83
|
+
return redacted.length > 1000 ? redacted.slice(0, 1000) + "..." : redacted;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const formatActivityError = (error) => redactActivityText(error instanceof Error ? error.message : String(error));
|
|
87
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const renderPiCoreSource: () => string;
|