spawnfile 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -397
- package/dist/cli/modelCommands.d.ts +3 -0
- package/dist/cli/modelCommands.js +68 -0
- package/dist/cli/runCli.d.ts +6 -1
- package/dist/cli/runCli.js +12 -67
- package/dist/cli/runtimeCommands.d.ts +3 -0
- package/dist/cli/runtimeCommands.js +20 -0
- package/dist/cli/surfaceCommands.d.ts +3 -0
- package/dist/cli/surfaceCommands.js +98 -0
- package/dist/compiler/agentSurfaces.js +51 -5
- package/dist/compiler/buildCompilePlan.js +36 -40
- package/dist/compiler/buildCompilePlanRuntime.d.ts +14 -0
- package/dist/compiler/buildCompilePlanRuntime.js +39 -0
- package/dist/compiler/buildCompilePlanTeams.d.ts +5 -0
- package/dist/compiler/buildCompilePlanTeams.js +38 -0
- package/dist/compiler/compilePlanHelpers.js +4 -1
- package/dist/compiler/compileProject.js +62 -13
- package/dist/compiler/compileProjectSupport.d.ts +17 -0
- package/dist/compiler/compileProjectSupport.js +136 -0
- package/dist/compiler/containerArtifacts.d.ts +6 -1
- package/dist/compiler/containerArtifacts.js +26 -4
- package/dist/compiler/containerArtifactsPlans.js +16 -1
- package/dist/compiler/containerArtifactsRender.d.ts +4 -2
- package/dist/compiler/containerArtifactsRender.js +21 -126
- package/dist/compiler/containerArtifactsTypes.d.ts +7 -0
- package/dist/compiler/containerEntrypointRender.d.ts +12 -0
- package/dist/compiler/containerEntrypointRender.js +186 -0
- package/dist/compiler/index.d.ts +2 -0
- package/dist/compiler/index.js +2 -0
- package/dist/compiler/interactiveSurfaceScopes.d.ts +2 -0
- package/dist/compiler/interactiveSurfaceScopes.js +21 -0
- package/dist/compiler/moltnetArtifacts.d.ts +27 -0
- package/dist/compiler/moltnetArtifacts.js +204 -0
- package/dist/compiler/moltnetBinaries.d.ts +4 -0
- package/dist/compiler/moltnetBinaries.js +103 -0
- package/dist/compiler/moltnetClientConfig.d.ts +11 -0
- package/dist/compiler/moltnetClientConfig.js +89 -0
- package/dist/compiler/moltnetRepresentativeResolution.d.ts +16 -0
- package/dist/compiler/moltnetRepresentativeResolution.js +86 -0
- package/dist/compiler/moltnetResolution.d.ts +3 -0
- package/dist/compiler/moltnetResolution.js +201 -0
- package/dist/compiler/runProject.js +1 -1
- package/dist/compiler/surfaceDefinitions.d.ts +55 -0
- package/dist/compiler/surfaceDefinitions.js +204 -0
- package/dist/compiler/teamContextHelpers.d.ts +18 -0
- package/dist/compiler/teamContextHelpers.js +112 -0
- package/dist/compiler/teamContextSupport.d.ts +4 -0
- package/dist/compiler/teamContextSupport.js +264 -0
- package/dist/compiler/teamContextSupport.testHelpers.d.ts +16 -0
- package/dist/compiler/teamContextSupport.testHelpers.js +68 -0
- package/dist/compiler/teamContextTypes.d.ts +28 -0
- package/dist/compiler/teamRoster.d.ts +12 -0
- package/dist/compiler/teamRoster.js +48 -0
- package/dist/compiler/teamRosterEntries.d.ts +13 -0
- package/dist/compiler/teamRosterEntries.js +230 -0
- package/dist/compiler/teamRosterTypes.d.ts +45 -0
- package/dist/compiler/types.d.ts +72 -6
- package/dist/compiler/updateProjectRuntime.d.ts +9 -0
- package/dist/compiler/updateProjectRuntime.js +67 -0
- package/dist/compiler/updateProjectSurfaces.d.ts +8 -0
- package/dist/compiler/updateProjectSurfaces.js +106 -0
- package/dist/manifest/loadManifest.js +4 -4
- package/dist/manifest/renderSpawnfile.js +74 -8
- package/dist/manifest/scaffold.js +1 -3
- package/dist/manifest/schemas.d.ts +227 -17
- package/dist/manifest/schemas.js +62 -20
- package/dist/manifest/surfaceSchemas.d.ts +154 -0
- package/dist/manifest/surfaceSchemas.js +77 -5
- package/dist/runtime/common.js +3 -0
- package/dist/runtime/openclaw/adapter.js +38 -5
- package/dist/runtime/openclaw/moltnet.d.ts +12 -0
- package/dist/runtime/openclaw/moltnet.js +124 -0
- package/dist/runtime/openclaw/surfaces.js +3 -0
- package/dist/runtime/picoclaw/adapter.js +27 -8
- package/dist/runtime/picoclaw/pico.d.ts +2 -0
- package/dist/runtime/picoclaw/pico.js +2 -0
- package/dist/runtime/picoclaw/surfaces.js +11 -0
- package/dist/runtime/tinyclaw/adapter.js +22 -8
- package/dist/runtime/tinyclaw/runAuth.js +28 -1
- package/dist/runtime/tinyclaw/surfaces.js +8 -0
- package/dist/runtime/types.d.ts +11 -0
- package/package.json +10 -3
- package/runtimes.yaml +4 -4
- package/dist/.env.example +0 -5
- package/dist/Dockerfile +0 -21
- package/dist/compiler/discordSurface.d.ts +0 -4
- package/dist/compiler/discordSurface.js +0 -28
- package/dist/container/rootfs/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/config.json +0 -16
- package/dist/container/rootfs/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/workspace/AGENTS.md +0 -1
- package/dist/e2e/cli.js +0 -40
- package/dist/e2e/dockerAuth.d.ts +0 -18
- package/dist/e2e/dockerAuth.js +0 -212
- package/dist/e2e/fixtures.d.ts +0 -2
- package/dist/e2e/fixtures.js +0 -49
- package/dist/e2e/index.d.ts +0 -4
- package/dist/e2e/index.js +0 -4
- package/dist/e2e/runtimePrompts.d.ts +0 -13
- package/dist/e2e/runtimePrompts.js +0 -132
- package/dist/e2e/scenarios.d.ts +0 -3
- package/dist/e2e/scenarios.js +0 -84
- package/dist/e2e/types.d.ts +0 -35
- package/dist/entrypoint.sh +0 -71
- package/dist/runtimes/picoclaw/agents/assistant/config.json +0 -16
- package/dist/runtimes/picoclaw/agents/assistant/workspace/AGENTS.md +0 -1
- package/dist/spawnfile-report.json +0 -71
- /package/dist/{e2e/cli.d.ts → compiler/teamContextTypes.js} +0 -0
- /package/dist/{e2e/types.js → compiler/teamRosterTypes.js} +0 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
const MOLTNET_OPTION_KEYS = new Set([
|
|
2
|
+
"base_url",
|
|
3
|
+
"enabled",
|
|
4
|
+
"network_id",
|
|
5
|
+
"timeout_ms",
|
|
6
|
+
"token",
|
|
7
|
+
"token_secret"
|
|
8
|
+
]);
|
|
9
|
+
const parseMoltnetOptions = (options) => {
|
|
10
|
+
const raw = options.moltnet;
|
|
11
|
+
if (raw === undefined) {
|
|
12
|
+
return { errors: [] };
|
|
13
|
+
}
|
|
14
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
15
|
+
return {
|
|
16
|
+
errors: [
|
|
17
|
+
"OpenClaw runtime option moltnet must be an object with enabled/base_url/network_id/timeout_ms/token/token_secret"
|
|
18
|
+
]
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const value = raw;
|
|
22
|
+
const errors = [];
|
|
23
|
+
for (const key of Object.keys(value)) {
|
|
24
|
+
if (!MOLTNET_OPTION_KEYS.has(key)) {
|
|
25
|
+
errors.push(`OpenClaw runtime option moltnet.${key} is unsupported`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const parsed = {};
|
|
29
|
+
if ("enabled" in value) {
|
|
30
|
+
if (typeof value.enabled !== "boolean") {
|
|
31
|
+
errors.push("OpenClaw runtime option moltnet.enabled must be a boolean");
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
parsed.enabled = value.enabled;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if ("base_url" in value) {
|
|
38
|
+
if (typeof value.base_url !== "string" || value.base_url.trim().length === 0) {
|
|
39
|
+
errors.push("OpenClaw runtime option moltnet.base_url must be a non-empty string");
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
try {
|
|
43
|
+
const url = new URL(value.base_url);
|
|
44
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
45
|
+
errors.push("OpenClaw runtime option moltnet.base_url must use http or https");
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
parsed.baseUrl = value.base_url.trim().replace(/\/+$/, "");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
errors.push("OpenClaw runtime option moltnet.base_url must be a valid URL");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if ("network_id" in value) {
|
|
57
|
+
if (typeof value.network_id !== "string" || value.network_id.trim().length === 0) {
|
|
58
|
+
errors.push("OpenClaw runtime option moltnet.network_id must be a non-empty string");
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
parsed.networkId = value.network_id.trim();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if ("timeout_ms" in value) {
|
|
65
|
+
if (typeof value.timeout_ms !== "number" ||
|
|
66
|
+
!Number.isInteger(value.timeout_ms) ||
|
|
67
|
+
value.timeout_ms <= 0) {
|
|
68
|
+
errors.push("OpenClaw runtime option moltnet.timeout_ms must be a positive integer");
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
parsed.timeoutMs = value.timeout_ms;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if ("token" in value) {
|
|
75
|
+
if (typeof value.token !== "string" || value.token.trim().length === 0) {
|
|
76
|
+
errors.push("OpenClaw runtime option moltnet.token must be a non-empty string");
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
parsed.token = value.token.trim();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if ("token_secret" in value) {
|
|
83
|
+
if (typeof value.token_secret !== "string" || value.token_secret.trim().length === 0) {
|
|
84
|
+
errors.push("OpenClaw runtime option moltnet.token_secret must be a non-empty string");
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
parsed.tokenSecret = value.token_secret.trim();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (parsed.enabled === true && !parsed.baseUrl) {
|
|
91
|
+
errors.push("OpenClaw runtime option moltnet.base_url is required when moltnet.enabled=true");
|
|
92
|
+
}
|
|
93
|
+
if (parsed.token && parsed.tokenSecret) {
|
|
94
|
+
errors.push("OpenClaw runtime option moltnet must not declare both token and token_secret");
|
|
95
|
+
}
|
|
96
|
+
return errors.length > 0 ? { errors } : { errors, value: parsed };
|
|
97
|
+
};
|
|
98
|
+
export const validateOpenClawMoltnetRuntimeOptions = (options) => parseMoltnetOptions(options).errors;
|
|
99
|
+
export const buildOpenClawMoltnetConfig = (options) => {
|
|
100
|
+
const parsed = parseMoltnetOptions(options);
|
|
101
|
+
if (parsed.errors.length > 0 || !parsed.value) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
const config = {
|
|
105
|
+
...(parsed.value.enabled !== undefined ? { enabled: parsed.value.enabled } : {}),
|
|
106
|
+
...(parsed.value.baseUrl ? { baseUrl: parsed.value.baseUrl } : {}),
|
|
107
|
+
...(parsed.value.networkId ? { networkId: parsed.value.networkId } : {}),
|
|
108
|
+
...(parsed.value.timeoutMs !== undefined ? { timeoutMs: parsed.value.timeoutMs } : {}),
|
|
109
|
+
...(parsed.value.token ? { token: parsed.value.token } : {})
|
|
110
|
+
};
|
|
111
|
+
return Object.keys(config).length > 0 ? config : undefined;
|
|
112
|
+
};
|
|
113
|
+
export const buildOpenClawMoltnetEnvBindings = (options) => {
|
|
114
|
+
const parsed = parseMoltnetOptions(options);
|
|
115
|
+
if (parsed.errors.length > 0 || !parsed.value?.tokenSecret) {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
return [
|
|
119
|
+
{
|
|
120
|
+
envName: parsed.value.tokenSecret,
|
|
121
|
+
jsonPath: "moltnet.token"
|
|
122
|
+
}
|
|
123
|
+
];
|
|
124
|
+
};
|
|
@@ -246,6 +246,9 @@ export const buildOpenClawSurfaceEnvBindings = (surfaces) => {
|
|
|
246
246
|
return bindings.length > 0 ? bindings : undefined;
|
|
247
247
|
};
|
|
248
248
|
export const assertSupportedOpenClawSurfaces = (surfaces) => {
|
|
249
|
+
if (surfaces?.http) {
|
|
250
|
+
throw new SpawnfileError("validation_error", "OpenClaw does not support portable HTTP surfaces in Spawnfile v0.1");
|
|
251
|
+
}
|
|
249
252
|
const discordAccess = surfaces?.discord?.access;
|
|
250
253
|
if (discordAccess && discordAccess.mode === "allowlist" && discordAccess.channels.length > 0 && discordAccess.guilds.length !== 1) {
|
|
251
254
|
throw new SpawnfileError("validation_error", "OpenClaw Discord channel allowlists require exactly one guild id");
|
|
@@ -4,6 +4,7 @@ import { createAgentCapabilities, createDiagnostic, createDocumentFiles, createS
|
|
|
4
4
|
import { preparePicoClawRuntimeAuth } from "./runAuth.js";
|
|
5
5
|
import { createPicoClawAgentScaffold } from "./scaffold.js";
|
|
6
6
|
import { assertSupportedPicoClawSurfaces, buildPicoClawChannelConfig, buildPicoClawSurfaceEnvBindings } from "./surfaces.js";
|
|
7
|
+
import { PICOCLAW_GATEWAY_BASE_PORT, PICOCLAW_INTERNAL_PICO_TOKEN } from "./pico.js";
|
|
7
8
|
const formatModelName = (node) => {
|
|
8
9
|
const primary = node.execution?.model?.primary;
|
|
9
10
|
if (!primary)
|
|
@@ -79,6 +80,21 @@ const buildMcpServers = (servers) => {
|
|
|
79
80
|
}
|
|
80
81
|
return result;
|
|
81
82
|
};
|
|
83
|
+
const buildPicoClawToolsConfig = (node) => {
|
|
84
|
+
const tools = {};
|
|
85
|
+
if (node.surfaces?.moltnet) {
|
|
86
|
+
tools.exec = {
|
|
87
|
+
enabled: true
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (node.mcpServers.length > 0) {
|
|
91
|
+
tools.mcp = {
|
|
92
|
+
enabled: true,
|
|
93
|
+
servers: buildMcpServers(node.mcpServers)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return Object.keys(tools).length > 0 ? tools : undefined;
|
|
97
|
+
};
|
|
82
98
|
const buildPicoClawConfig = (node) => {
|
|
83
99
|
const modelName = formatModelName(node);
|
|
84
100
|
const restrictToWorkspace = node.runtime.options.restrict_to_workspace ?? true;
|
|
@@ -98,13 +114,9 @@ const buildPicoClawConfig = (node) => {
|
|
|
98
114
|
if (Object.keys(channels).length > 0) {
|
|
99
115
|
config.channels = channels;
|
|
100
116
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
enabled: true,
|
|
105
|
-
servers: buildMcpServers(node.mcpServers)
|
|
106
|
-
}
|
|
107
|
-
};
|
|
117
|
+
const tools = buildPicoClawToolsConfig(node);
|
|
118
|
+
if (tools) {
|
|
119
|
+
config.tools = tools;
|
|
108
120
|
}
|
|
109
121
|
return `${JSON.stringify(config, null, 2)}\n`;
|
|
110
122
|
};
|
|
@@ -163,15 +175,22 @@ export const picoClawAdapter = {
|
|
|
163
175
|
homePathTemplate: "<instance-root>/picoclaw",
|
|
164
176
|
workspacePathTemplate: "<instance-root>/picoclaw/workspace"
|
|
165
177
|
},
|
|
166
|
-
port:
|
|
178
|
+
port: PICOCLAW_GATEWAY_BASE_PORT,
|
|
167
179
|
portEnv: "PICOCLAW_GATEWAY_PORT",
|
|
168
180
|
standaloneBaseImage: "debian:bookworm-slim",
|
|
169
181
|
startCommand: ["picoclaw", "gateway", "--allow-empty"],
|
|
170
182
|
staticEnv: {
|
|
183
|
+
PICOCLAW_CHANNELS_PICO_TOKEN: PICOCLAW_INTERNAL_PICO_TOKEN,
|
|
171
184
|
PICOCLAW_GATEWAY_HOST: "0.0.0.0"
|
|
172
185
|
},
|
|
173
186
|
systemDeps: ["bash", "ca-certificates", "curl", "nodejs", "npm", "tar"]
|
|
174
187
|
},
|
|
188
|
+
systemInstructionSurface: {
|
|
189
|
+
placement: "append_pointer",
|
|
190
|
+
resolvePath() {
|
|
191
|
+
return "workspace/AGENTS.md";
|
|
192
|
+
}
|
|
193
|
+
},
|
|
175
194
|
async compileAgent(node) {
|
|
176
195
|
return {
|
|
177
196
|
capabilities: createAgentCapabilities(node),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SpawnfileError } from "../../shared/index.js";
|
|
2
|
+
import { PICOCLAW_INTERNAL_PICO_TOKEN } from "./pico.js";
|
|
2
3
|
export const buildPicoClawChannelConfig = (surfaces) => {
|
|
3
4
|
if (!surfaces) {
|
|
4
5
|
return {};
|
|
@@ -41,6 +42,13 @@ export const buildPicoClawChannelConfig = (surfaces) => {
|
|
|
41
42
|
}
|
|
42
43
|
};
|
|
43
44
|
}
|
|
45
|
+
if (surfaces.moltnet) {
|
|
46
|
+
channels.pico = {
|
|
47
|
+
allow_token_query: true,
|
|
48
|
+
enabled: true,
|
|
49
|
+
token: PICOCLAW_INTERNAL_PICO_TOKEN
|
|
50
|
+
};
|
|
51
|
+
}
|
|
44
52
|
return channels;
|
|
45
53
|
};
|
|
46
54
|
export const buildPicoClawSurfaceEnvBindings = (surfaces) => {
|
|
@@ -72,6 +80,9 @@ export const buildPicoClawSurfaceEnvBindings = (surfaces) => {
|
|
|
72
80
|
return bindings.length > 0 ? bindings : undefined;
|
|
73
81
|
};
|
|
74
82
|
export const assertSupportedPicoClawSurfaces = (surfaces) => {
|
|
83
|
+
if (surfaces?.http) {
|
|
84
|
+
throw new SpawnfileError("validation_error", "PicoClaw does not support portable HTTP surfaces in Spawnfile v0.1");
|
|
85
|
+
}
|
|
75
86
|
const discordAccess = surfaces?.discord?.access;
|
|
76
87
|
if (discordAccess) {
|
|
77
88
|
if (discordAccess.mode === "pairing") {
|
|
@@ -5,6 +5,7 @@ import { prepareTinyClawRuntimeAuth } from "./runAuth.js";
|
|
|
5
5
|
import { createTinyClawAgentScaffold } from "./scaffold.js";
|
|
6
6
|
import { assertSupportedTinyClawSurfaces, buildTinyClawChannels, resolveTinyClawSurfaceTokenBindings } from "./surfaces.js";
|
|
7
7
|
const WORKSPACE_PLACEHOLDER = "<workspace-path>";
|
|
8
|
+
const SUPPORTED_TINYCLAW_OPENAI_MODEL_PREFIXES = ["gpt-5"];
|
|
8
9
|
const TINYCLAW_START_SCRIPT = `
|
|
9
10
|
set -euo pipefail
|
|
10
11
|
PIDS=()
|
|
@@ -55,6 +56,7 @@ for pid in "\${PIDS[@]}"; do
|
|
|
55
56
|
done
|
|
56
57
|
exit "$status"
|
|
57
58
|
`.trim();
|
|
59
|
+
const isSupportedTinyClawOpenAiModel = (modelName) => SUPPORTED_TINYCLAW_OPENAI_MODEL_PREFIXES.some((prefix) => modelName.startsWith(prefix));
|
|
58
60
|
const buildTinyClawSettings = (node) => {
|
|
59
61
|
const [primary] = listEffectiveExecutionModelTargets(node.execution);
|
|
60
62
|
const agentEntry = {
|
|
@@ -168,12 +170,15 @@ export const tinyClawAdapter = {
|
|
|
168
170
|
throw new SpawnfileError("validation_error", "TinyClaw custom or local endpoints are not supported in Spawnfile v0.1");
|
|
169
171
|
}
|
|
170
172
|
if (target.provider === "anthropic") {
|
|
171
|
-
if (target.auth.method === "claude-code") {
|
|
173
|
+
if (target.auth.method === "claude-code" || target.auth.method === "api_key") {
|
|
172
174
|
return;
|
|
173
175
|
}
|
|
174
176
|
}
|
|
175
177
|
else if (target.provider === "openai") {
|
|
176
|
-
if (target.
|
|
178
|
+
if (!isSupportedTinyClawOpenAiModel(target.name)) {
|
|
179
|
+
throw new SpawnfileError("validation_error", `TinyClaw OpenAI models must use the runtime's Codex/GPT-5 path; received ${target.name}`);
|
|
180
|
+
}
|
|
181
|
+
if (target.auth.method === "codex" || target.auth.method === "api_key") {
|
|
177
182
|
return;
|
|
178
183
|
}
|
|
179
184
|
}
|
|
@@ -190,11 +195,11 @@ export const tinyClawAdapter = {
|
|
|
190
195
|
configEnvBindings: [
|
|
191
196
|
{
|
|
192
197
|
envName: "ANTHROPIC_API_KEY",
|
|
193
|
-
jsonPath: "models.anthropic.
|
|
198
|
+
jsonPath: "models.anthropic.api_key"
|
|
194
199
|
},
|
|
195
200
|
{
|
|
196
201
|
envName: "OPENAI_API_KEY",
|
|
197
|
-
jsonPath: "models.openai.
|
|
202
|
+
jsonPath: "models.openai.api_key"
|
|
198
203
|
}
|
|
199
204
|
],
|
|
200
205
|
homeEnv: "TINYAGI_HOME",
|
|
@@ -210,6 +215,12 @@ export const tinyClawAdapter = {
|
|
|
210
215
|
startCommand: ["bash", "-lc", TINYCLAW_START_SCRIPT],
|
|
211
216
|
systemDeps: ["bash", "ca-certificates", "curl", "g++", "make", "python3", "tar"]
|
|
212
217
|
},
|
|
218
|
+
systemInstructionSurface: {
|
|
219
|
+
placement: "append_pointer",
|
|
220
|
+
resolvePath({ node }) {
|
|
221
|
+
return `workspace/${node.name}/AGENTS.md`;
|
|
222
|
+
}
|
|
223
|
+
},
|
|
213
224
|
async compileAgent(node) {
|
|
214
225
|
return {
|
|
215
226
|
capabilities: createAgentCapabilities(node, {
|
|
@@ -234,17 +245,20 @@ export const tinyClawAdapter = {
|
|
|
234
245
|
const agentIds = node.members
|
|
235
246
|
.filter((member) => member.kind === "agent")
|
|
236
247
|
.map((member) => member.id);
|
|
248
|
+
const leadAgent = node.lead
|
|
249
|
+
? node.members.find((member) => member.id === node.lead && member.kind === "agent")
|
|
250
|
+
: null;
|
|
237
251
|
const teamConfig = {
|
|
238
252
|
name: node.name,
|
|
239
253
|
agents: agentIds,
|
|
240
|
-
leader_agent:
|
|
254
|
+
...(leadAgent ? { leader_agent: leadAgent.id } : {})
|
|
241
255
|
};
|
|
242
256
|
return {
|
|
243
257
|
capabilities: [
|
|
244
258
|
createCapability("team.members", "supported"),
|
|
245
|
-
createCapability("team.
|
|
246
|
-
createCapability("team.
|
|
247
|
-
createCapability("team.
|
|
259
|
+
createCapability("team.mode", node.mode === "hierarchical" ? "supported" : "degraded", "TinyClaw only supports leader-led teams"),
|
|
260
|
+
createCapability("team.lead", leadAgent ? "supported" : "degraded", leadAgent ? "" : "TinyClaw requires a concrete leader_agent"),
|
|
261
|
+
createCapability("team.external", "degraded", "TinyClaw does not enforce external boundary"),
|
|
248
262
|
createCapability("team.shared", "supported"),
|
|
249
263
|
createCapability("team.nested", "degraded", "TinyClaw nested teams flatten in v0.1")
|
|
250
264
|
],
|
|
@@ -1,4 +1,20 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
import { loadImportedClaudeCodeCredential, loadImportedCodexCredential } from "../../auth/index.js";
|
|
3
|
+
import { ensureDirectory, readUtf8File, writeUtf8File } from "../../filesystem/index.js";
|
|
4
|
+
const resolveRootfsSourcePath = (outputDirectory, containerPath) => path.join(outputDirectory, "container", "rootfs", ...path.posix.relative("/", containerPath).split("/"));
|
|
5
|
+
const createMountArgs = (hostPath, containerPath) => [
|
|
6
|
+
"-v",
|
|
7
|
+
`${hostPath}:${containerPath}`
|
|
8
|
+
];
|
|
9
|
+
const writeRuntimeAuthFile = async (input, relativePath, content) => {
|
|
10
|
+
const hostPath = path.join(input.tempRoot, "runtime-auth", "tinyclaw", input.instance.id, ...relativePath.split("/"));
|
|
11
|
+
await ensureDirectory(path.dirname(hostPath));
|
|
12
|
+
await writeUtf8File(hostPath, content);
|
|
13
|
+
return hostPath;
|
|
14
|
+
};
|
|
15
|
+
const writePatchedSettings = async (input, config) => {
|
|
16
|
+
return writeRuntimeAuthFile(input, "settings.json", `${JSON.stringify(config, null, 2)}\n`);
|
|
17
|
+
};
|
|
2
18
|
export const prepareTinyClawRuntimeAuth = async (input) => {
|
|
3
19
|
const coveredModelSecrets = [];
|
|
4
20
|
const claudeCode = input.authProfile.imports["claude-code"]
|
|
@@ -15,8 +31,19 @@ export const prepareTinyClawRuntimeAuth = async (input) => {
|
|
|
15
31
|
codex) {
|
|
16
32
|
coveredModelSecrets.push("OPENAI_API_KEY");
|
|
17
33
|
}
|
|
34
|
+
const sourceConfig = JSON.parse(await readUtf8File(resolveRootfsSourcePath(input.outputDirectory, input.instance.config_path)));
|
|
35
|
+
const patchedConfigPath = await writePatchedSettings(input, sourceConfig);
|
|
36
|
+
const mountArgs = createMountArgs(patchedConfigPath, input.instance.config_path);
|
|
37
|
+
if (input.instance.home_path && input.instance.model_auth_methods.anthropic === "claude-code" && claudeCode) {
|
|
38
|
+
const mountedClaudePath = await writeRuntimeAuthFile(input, ".claude/.credentials.json", await readUtf8File(path.join(input.authProfile.imports["claude-code"].path, ".credentials.json")));
|
|
39
|
+
mountArgs.push(...createMountArgs(mountedClaudePath, path.posix.join(input.instance.home_path, ".claude", ".credentials.json")));
|
|
40
|
+
}
|
|
41
|
+
if (input.instance.home_path && input.instance.model_auth_methods.openai === "codex" && codex) {
|
|
42
|
+
const mountedAuthPath = await writeRuntimeAuthFile(input, ".codex/auth.json", await readUtf8File(path.join(input.authProfile.imports.codex.path, "auth.json")));
|
|
43
|
+
mountArgs.push(...createMountArgs(mountedAuthPath, path.posix.join(input.instance.home_path, ".codex", "auth.json")));
|
|
44
|
+
}
|
|
18
45
|
return {
|
|
19
46
|
coveredModelSecrets,
|
|
20
|
-
mountArgs
|
|
47
|
+
mountArgs
|
|
21
48
|
};
|
|
22
49
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { listInteractiveSurfaceScopes } from "../../compiler/interactiveSurfaceScopes.js";
|
|
1
2
|
import { SpawnfileError } from "../../shared/index.js";
|
|
2
3
|
export const buildTinyClawChannels = (surfaces) => {
|
|
3
4
|
const enabled = [];
|
|
@@ -51,6 +52,9 @@ export const resolveTinyClawSurfaceTokenBindings = (inputs) => {
|
|
|
51
52
|
return bindings.length > 0 ? bindings : undefined;
|
|
52
53
|
};
|
|
53
54
|
export const assertSupportedTinyClawSurfaces = (surfaces) => {
|
|
55
|
+
if (surfaces?.http) {
|
|
56
|
+
throw new SpawnfileError("validation_error", "TinyClaw does not support portable HTTP surfaces in Spawnfile v0.1");
|
|
57
|
+
}
|
|
54
58
|
const discordAccess = surfaces?.discord?.access;
|
|
55
59
|
if (discordAccess) {
|
|
56
60
|
if (discordAccess.mode !== "pairing") {
|
|
@@ -83,4 +87,8 @@ export const assertSupportedTinyClawSurfaces = (surfaces) => {
|
|
|
83
87
|
if (surfaces?.slack) {
|
|
84
88
|
throw new SpawnfileError("validation_error", "TinyClaw does not support Slack in Spawnfile v0.1");
|
|
85
89
|
}
|
|
90
|
+
const interactiveScopes = listInteractiveSurfaceScopes(surfaces);
|
|
91
|
+
if (interactiveScopes.length > 1) {
|
|
92
|
+
throw new SpawnfileError("validation_error", `TinyClaw preserves only one interactive conversation scope in Spawnfile v0.1; declared ${interactiveScopes.join(", ")}`);
|
|
93
|
+
}
|
|
86
94
|
};
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { CapabilityReport, DiagnosticReport } from "../report/index.js";
|
|
|
5
5
|
import type { ContainerRuntimeInstanceReport } from "../report/index.js";
|
|
6
6
|
export interface EmittedFile {
|
|
7
7
|
content: string;
|
|
8
|
+
mode?: number;
|
|
8
9
|
path: string;
|
|
9
10
|
}
|
|
10
11
|
export interface RuntimeAgentScaffold {
|
|
@@ -51,6 +52,7 @@ export interface RuntimeContainerMeta {
|
|
|
51
52
|
instancePaths: RuntimeContainerInstancePaths;
|
|
52
53
|
globalNpmPackages?: string[];
|
|
53
54
|
port?: number;
|
|
55
|
+
portStride?: number;
|
|
54
56
|
portEnv?: string;
|
|
55
57
|
standaloneBaseImage: string;
|
|
56
58
|
startCommand: string[];
|
|
@@ -68,6 +70,14 @@ export interface RuntimeAuthPreparationResult {
|
|
|
68
70
|
coveredModelSecrets: string[];
|
|
69
71
|
mountArgs: string[];
|
|
70
72
|
}
|
|
73
|
+
export interface RuntimeSystemInstructionSurfaceInput {
|
|
74
|
+
node: ResolvedAgentNode;
|
|
75
|
+
}
|
|
76
|
+
export type RuntimeSystemInstructionPlacement = "append_pointer" | "append_inline" | "replace_generated_block";
|
|
77
|
+
export interface RuntimeSystemInstructionSurface {
|
|
78
|
+
placement: RuntimeSystemInstructionPlacement;
|
|
79
|
+
resolvePath(input: RuntimeSystemInstructionSurfaceInput): string;
|
|
80
|
+
}
|
|
71
81
|
export interface AdapterCompileResult {
|
|
72
82
|
capabilities: CapabilityReport[];
|
|
73
83
|
diagnostics: DiagnosticReport[];
|
|
@@ -83,5 +93,6 @@ export interface RuntimeAdapter {
|
|
|
83
93
|
name: string;
|
|
84
94
|
prepareRuntimeAuth?(input: RuntimeAuthPreparationInput): Promise<RuntimeAuthPreparationResult>;
|
|
85
95
|
scaffoldAgentProject?(): RuntimeAgentScaffold;
|
|
96
|
+
systemInstructionSurface?: RuntimeSystemInstructionSurface;
|
|
86
97
|
validateRuntimeOptions?(options: Record<string, unknown>): DiagnosticReport[];
|
|
87
98
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spawnfile",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Canonical source compiler for autonomous agents and teams.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -16,13 +16,19 @@
|
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
18
|
"blueprints": "./scripts/blueprints.sh",
|
|
19
|
-
"build": "tsc --project tsconfig.build.json && node ./scripts/copy-runtime-scaffold-assets.mjs",
|
|
19
|
+
"build": "rm -rf dist && tsc --project tsconfig.build.json && node ./scripts/copy-runtime-scaffold-assets.mjs",
|
|
20
20
|
"clean": "rm -rf coverage dist",
|
|
21
21
|
"coverage": "vitest run --coverage",
|
|
22
22
|
"dev": "tsx src/cli/index.ts",
|
|
23
|
+
"prepack": "npm run build",
|
|
24
|
+
"prepublishOnly": "npm run typecheck && npm test",
|
|
25
|
+
"release:patch": "npm version patch && npm publish",
|
|
26
|
+
"release:minor": "npm version minor && npm publish",
|
|
27
|
+
"release:major": "npm version major && npm publish",
|
|
23
28
|
"runtimes": "./scripts/runtimes.sh",
|
|
24
29
|
"runtimes:sync": "./scripts/runtimes.sh && ./scripts/blueprints.sh",
|
|
25
30
|
"test:e2e:docker-auth": "tsx src/e2e/cli.ts",
|
|
31
|
+
"test:e2e:moltnet-team-chat": "tsx src/e2e/cli.ts moltnet-team-chat",
|
|
26
32
|
"test": "vitest run",
|
|
27
33
|
"typecheck": "tsc --project tsconfig.json --noEmit"
|
|
28
34
|
},
|
|
@@ -37,5 +43,6 @@
|
|
|
37
43
|
"tsx": "^4.20.6",
|
|
38
44
|
"typescript": "^5.9.3",
|
|
39
45
|
"vitest": "^3.2.4"
|
|
40
|
-
}
|
|
46
|
+
},
|
|
47
|
+
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be"
|
|
41
48
|
}
|
package/runtimes.yaml
CHANGED
|
@@ -26,12 +26,12 @@ runtimes:
|
|
|
26
26
|
|
|
27
27
|
picoclaw:
|
|
28
28
|
remote: git@github.com:sipeed/picoclaw.git
|
|
29
|
-
ref: v0.2.
|
|
29
|
+
ref: v0.2.5
|
|
30
30
|
default_branch: main
|
|
31
31
|
install:
|
|
32
32
|
kind: github_release_archive
|
|
33
33
|
repository: sipeed/picoclaw
|
|
34
|
-
tag: v0.2.
|
|
34
|
+
tag: v0.2.5
|
|
35
35
|
binary: picoclaw
|
|
36
36
|
assets:
|
|
37
37
|
linux_amd64: picoclaw_Linux_x86_64.tar.gz
|
|
@@ -40,12 +40,12 @@ runtimes:
|
|
|
40
40
|
|
|
41
41
|
tinyclaw:
|
|
42
42
|
remote: git@github.com:TinyAGI/tinyclaw.git
|
|
43
|
-
ref: v0.0.
|
|
43
|
+
ref: v0.0.20
|
|
44
44
|
default_branch: main
|
|
45
45
|
install:
|
|
46
46
|
kind: github_release_bundle
|
|
47
47
|
repository: TinyAGI/tinyagi
|
|
48
|
-
tag: v0.0.
|
|
48
|
+
tag: v0.0.20
|
|
49
49
|
asset: tinyagi-bundle.tar.gz
|
|
50
50
|
status: active
|
|
51
51
|
|
package/dist/.env.example
DELETED
package/dist/Dockerfile
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
FROM debian:bookworm-slim
|
|
2
|
-
USER root
|
|
3
|
-
|
|
4
|
-
WORKDIR /opt/spawnfile
|
|
5
|
-
RUN apt-get update && apt-get install -y --no-install-recommends bash ca-certificates curl nodejs npm tar && rm -rf /var/lib/apt/lists/*
|
|
6
|
-
|
|
7
|
-
RUN npm install -g --omit=dev --no-fund --no-audit @anthropic-ai/claude-code @openai/codex
|
|
8
|
-
|
|
9
|
-
RUN mkdir -p /opt/spawnfile/runtime-installs/picoclaw/bin
|
|
10
|
-
RUN arch="$(dpkg --print-architecture)" && case "$arch" in amd64) asset="picoclaw_Linux_x86_64.tar.gz" ;; arm64) asset="picoclaw_Linux_arm64.tar.gz" ;; *) echo "Unsupported PicoClaw release architecture: $arch" >&2; exit 1 ;; esac && url="https://github.com/sipeed/picoclaw/releases/download/v0.2.3/$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 "picoclaw" | head -n 1)" && [ -n "$binary_path" ] && install -m 0755 "$binary_path" /opt/spawnfile/runtime-installs/picoclaw/bin/picoclaw && ln -sf /opt/spawnfile/runtime-installs/picoclaw/bin/picoclaw /usr/local/bin/picoclaw && rm -rf /tmp/picoclaw.tar.gz /tmp/picoclaw-extract
|
|
11
|
-
|
|
12
|
-
RUN if ! id -u spawnfile >/dev/null 2>&1; then useradd --create-home --home-dir /home/spawnfile --shell /bin/bash spawnfile; fi
|
|
13
|
-
|
|
14
|
-
COPY container/rootfs/ /
|
|
15
|
-
COPY .env.example /opt/spawnfile/.env.example
|
|
16
|
-
COPY entrypoint.sh /opt/spawnfile/entrypoint.sh
|
|
17
|
-
RUN chmod +x /opt/spawnfile/entrypoint.sh
|
|
18
|
-
RUN mkdir -p /var/lib/spawnfile && chown -R spawnfile:spawnfile /var/lib/spawnfile /opt/spawnfile
|
|
19
|
-
EXPOSE 18790
|
|
20
|
-
USER spawnfile
|
|
21
|
-
ENTRYPOINT ["/opt/spawnfile/entrypoint.sh"]
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import { SurfacesBlock } from "../manifest/index.js";
|
|
2
|
-
import type { ResolvedAgentSurfaces } from "./types.js";
|
|
3
|
-
export declare const resolveAgentSurfaces: (surfaces: SurfacesBlock | undefined) => ResolvedAgentSurfaces | undefined;
|
|
4
|
-
export declare const listAgentSurfaceSecretNames: (surfaces: ResolvedAgentSurfaces | undefined) => string[];
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_DISCORD_BOT_TOKEN_SECRET } from "../shared/index.js";
|
|
2
|
-
export const resolveAgentSurfaces = (surfaces) => {
|
|
3
|
-
if (!surfaces?.discord) {
|
|
4
|
-
return undefined;
|
|
5
|
-
}
|
|
6
|
-
return {
|
|
7
|
-
discord: {
|
|
8
|
-
...(surfaces.discord.access
|
|
9
|
-
? {
|
|
10
|
-
access: {
|
|
11
|
-
channels: [...(surfaces.discord.access.channels ?? [])],
|
|
12
|
-
guilds: [...(surfaces.discord.access.guilds ?? [])],
|
|
13
|
-
mode: surfaces.discord.access.mode ??
|
|
14
|
-
"allowlist",
|
|
15
|
-
users: [...(surfaces.discord.access.users ?? [])]
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
: {}),
|
|
19
|
-
botTokenSecret: surfaces.discord.bot_token_secret ?? DEFAULT_DISCORD_BOT_TOKEN_SECRET
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
|
-
export const listAgentSurfaceSecretNames = (surfaces) => {
|
|
24
|
-
if (!surfaces?.discord) {
|
|
25
|
-
return [];
|
|
26
|
-
}
|
|
27
|
-
return [surfaces.discord.botTokenSecret];
|
|
28
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"agents": {
|
|
3
|
-
"defaults": {
|
|
4
|
-
"workspace": "/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/workspace",
|
|
5
|
-
"restrict_to_workspace": true,
|
|
6
|
-
"model_name": "claude-sonnet-4.6"
|
|
7
|
-
}
|
|
8
|
-
},
|
|
9
|
-
"model_list": [
|
|
10
|
-
{
|
|
11
|
-
"api_key": "file://secrets/ANTHROPIC_API_KEY",
|
|
12
|
-
"model_name": "claude-sonnet-4.6",
|
|
13
|
-
"model": "anthropic/claude-sonnet-4.6"
|
|
14
|
-
}
|
|
15
|
-
]
|
|
16
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
You are a concise assistant. Reply with exactly the requested token and nothing else.
|
package/dist/e2e/cli.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { isSpawnfileError } from "../shared/index.js";
|
|
3
|
-
import { runDockerAuthE2E } from "./dockerAuth.js";
|
|
4
|
-
const collect = (value, previous) => [...previous, value];
|
|
5
|
-
const main = async () => {
|
|
6
|
-
const program = new Command();
|
|
7
|
-
program
|
|
8
|
-
.name("spawnfile-e2e")
|
|
9
|
-
.description("Run opt-in Docker auth E2E scenarios against real runtime images")
|
|
10
|
-
.option("--scenario <id>", "Scenario id to run", collect, [])
|
|
11
|
-
.option("--runtime <runtime>", "Runtime filter", collect, [])
|
|
12
|
-
.option("--auth <method>", "Auth method filter", collect, [])
|
|
13
|
-
.option("--env-file <path>", "Env file for api_key scenarios")
|
|
14
|
-
.option("--claude-from <directory>", "Claude Code config directory override")
|
|
15
|
-
.option("--codex-from <directory>", "Codex config directory override")
|
|
16
|
-
.option("--keep-artifacts", "Keep temporary projects and compile output")
|
|
17
|
-
.option("--keep-images", "Keep built Docker images after each scenario");
|
|
18
|
-
await program.parseAsync(process.argv);
|
|
19
|
-
const options = program.opts();
|
|
20
|
-
const result = await runDockerAuthE2E({
|
|
21
|
-
authMethods: options.auth,
|
|
22
|
-
claudeCodeDirectory: options.claudeFrom,
|
|
23
|
-
codexDirectory: options.codexFrom,
|
|
24
|
-
envFilePath: options.envFile,
|
|
25
|
-
keepArtifacts: options.keepArtifacts,
|
|
26
|
-
keepImages: options.keepImages,
|
|
27
|
-
runtimes: options.runtime,
|
|
28
|
-
scenarioIds: options.scenario
|
|
29
|
-
});
|
|
30
|
-
console.log(`Docker auth E2E passed (${result.results.length} scenarios)`);
|
|
31
|
-
};
|
|
32
|
-
main().catch((error) => {
|
|
33
|
-
const message = isSpawnfileError(error)
|
|
34
|
-
? `${error.code}: ${error.message}`
|
|
35
|
-
: error instanceof Error
|
|
36
|
-
? error.message
|
|
37
|
-
: String(error);
|
|
38
|
-
process.stderr.write(`${message}\n`);
|
|
39
|
-
process.exitCode = 1;
|
|
40
|
-
});
|
package/dist/e2e/dockerAuth.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { DockerAuthE2EFilters, DockerAuthE2EScenarioResult } from "./types.js";
|
|
2
|
-
export interface DockerAuthE2ELogger {
|
|
3
|
-
error(message: string): void;
|
|
4
|
-
info(message: string): void;
|
|
5
|
-
}
|
|
6
|
-
export interface RunDockerAuthE2EOptions extends DockerAuthE2EFilters {
|
|
7
|
-
claudeCodeDirectory?: string;
|
|
8
|
-
codexDirectory?: string;
|
|
9
|
-
dockerCommand?: string;
|
|
10
|
-
envFilePath?: string;
|
|
11
|
-
keepArtifacts?: boolean;
|
|
12
|
-
keepImages?: boolean;
|
|
13
|
-
logger?: DockerAuthE2ELogger;
|
|
14
|
-
}
|
|
15
|
-
export interface RunDockerAuthE2EResult {
|
|
16
|
-
results: DockerAuthE2EScenarioResult[];
|
|
17
|
-
}
|
|
18
|
-
export declare const runDockerAuthE2E: (options?: RunDockerAuthE2EOptions) => Promise<RunDockerAuthE2EResult>;
|