spawnfile 0.1.0
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/LICENSE +21 -0
- package/README.md +464 -0
- package/dist/.env.example +5 -0
- package/dist/Dockerfile +21 -0
- package/dist/auth/importers.d.ts +8 -0
- package/dist/auth/importers.js +93 -0
- package/dist/auth/index.d.ts +5 -0
- package/dist/auth/index.js +5 -0
- package/dist/auth/paths.d.ts +6 -0
- package/dist/auth/paths.js +18 -0
- package/dist/auth/profileStore.d.ts +10 -0
- package/dist/auth/profileStore.js +125 -0
- package/dist/auth/runtimeCredentials.d.ts +14 -0
- package/dist/auth/runtimeCredentials.js +76 -0
- package/dist/auth/types.d.ts +22 -0
- package/dist/auth/types.js +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +4 -0
- package/dist/cli/runCli.d.ts +27 -0
- package/dist/cli/runCli.js +314 -0
- package/dist/compiler/addProjectNode.d.ts +21 -0
- package/dist/compiler/addProjectNode.js +126 -0
- package/dist/compiler/agentSurfaces.d.ts +4 -0
- package/dist/compiler/agentSurfaces.js +70 -0
- package/dist/compiler/buildCompilePlan.d.ts +2 -0
- package/dist/compiler/buildCompilePlan.js +258 -0
- package/dist/compiler/buildProject.d.ts +20 -0
- package/dist/compiler/buildProject.js +52 -0
- package/dist/compiler/compilePlanHelpers.d.ts +7 -0
- package/dist/compiler/compilePlanHelpers.js +39 -0
- package/dist/compiler/compileProject.d.ts +11 -0
- package/dist/compiler/compileProject.js +182 -0
- package/dist/compiler/containerArtifacts.d.ts +4 -0
- package/dist/compiler/containerArtifacts.js +64 -0
- package/dist/compiler/containerArtifactsPlans.d.ts +4 -0
- package/dist/compiler/containerArtifactsPlans.js +154 -0
- package/dist/compiler/containerArtifactsRender.d.ts +6 -0
- package/dist/compiler/containerArtifactsRender.js +237 -0
- package/dist/compiler/containerArtifactsTypes.d.ts +42 -0
- package/dist/compiler/containerArtifactsTypes.js +1 -0
- package/dist/compiler/discordSurface.d.ts +4 -0
- package/dist/compiler/discordSurface.js +28 -0
- package/dist/compiler/executionDefaults.d.ts +2 -0
- package/dist/compiler/executionDefaults.js +9 -0
- package/dist/compiler/helpers.d.ts +7 -0
- package/dist/compiler/helpers.js +35 -0
- package/dist/compiler/index.d.ts +9 -0
- package/dist/compiler/index.js +9 -0
- package/dist/compiler/initProject.d.ts +9 -0
- package/dist/compiler/initProject.js +46 -0
- package/dist/compiler/modelAuth.d.ts +2 -0
- package/dist/compiler/modelAuth.js +17 -0
- package/dist/compiler/modelEnv.d.ts +10 -0
- package/dist/compiler/modelEnv.js +97 -0
- package/dist/compiler/runProject.d.ts +34 -0
- package/dist/compiler/runProject.js +197 -0
- package/dist/compiler/runProjectAuth.d.ts +9 -0
- package/dist/compiler/runProjectAuth.js +59 -0
- package/dist/compiler/surfaceSupport.d.ts +2 -0
- package/dist/compiler/surfaceSupport.js +13 -0
- package/dist/compiler/surfaces.d.ts +21 -0
- package/dist/compiler/surfaces.js +59 -0
- package/dist/compiler/syncProjectAuth.d.ts +7 -0
- package/dist/compiler/syncProjectAuth.js +65 -0
- package/dist/compiler/types.d.ts +134 -0
- package/dist/compiler/types.js +1 -0
- package/dist/compiler/updateProjectModels.d.ts +20 -0
- package/dist/compiler/updateProjectModels.js +181 -0
- package/dist/container/rootfs/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/config.json +16 -0
- package/dist/container/rootfs/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/workspace/AGENTS.md +1 -0
- package/dist/e2e/cli.d.ts +1 -0
- package/dist/e2e/cli.js +40 -0
- package/dist/e2e/dockerAuth.d.ts +18 -0
- package/dist/e2e/dockerAuth.js +212 -0
- package/dist/e2e/fixtures.d.ts +2 -0
- package/dist/e2e/fixtures.js +49 -0
- package/dist/e2e/index.d.ts +4 -0
- package/dist/e2e/index.js +4 -0
- package/dist/e2e/runtimePrompts.d.ts +13 -0
- package/dist/e2e/runtimePrompts.js +132 -0
- package/dist/e2e/scenarios.d.ts +3 -0
- package/dist/e2e/scenarios.js +84 -0
- package/dist/e2e/types.d.ts +35 -0
- package/dist/e2e/types.js +1 -0
- package/dist/entrypoint.sh +71 -0
- package/dist/filesystem/index.d.ts +2 -0
- package/dist/filesystem/index.js +2 -0
- package/dist/filesystem/io.d.ts +11 -0
- package/dist/filesystem/io.js +57 -0
- package/dist/filesystem/paths.d.ts +6 -0
- package/dist/filesystem/paths.js +30 -0
- package/dist/manifest/index.d.ts +5 -0
- package/dist/manifest/index.js +5 -0
- package/dist/manifest/loadManifest.d.ts +12 -0
- package/dist/manifest/loadManifest.js +208 -0
- package/dist/manifest/renderSpawnfile.d.ts +2 -0
- package/dist/manifest/renderSpawnfile.js +211 -0
- package/dist/manifest/scaffold.d.ts +16 -0
- package/dist/manifest/scaffold.js +41 -0
- package/dist/manifest/schemas.d.ts +989 -0
- package/dist/manifest/schemas.js +314 -0
- package/dist/manifest/skillFrontmatter.d.ts +5 -0
- package/dist/manifest/skillFrontmatter.js +32 -0
- package/dist/manifest/surfaceSchemas.d.ts +148 -0
- package/dist/manifest/surfaceSchemas.js +162 -0
- package/dist/report/createDiagnostic.d.ts +2 -0
- package/dist/report/createDiagnostic.js +4 -0
- package/dist/report/createReport.d.ts +2 -0
- package/dist/report/createReport.js +7 -0
- package/dist/report/index.d.ts +4 -0
- package/dist/report/index.js +4 -0
- package/dist/report/types.d.ts +50 -0
- package/dist/report/types.js +1 -0
- package/dist/report/writeReport.d.ts +2 -0
- package/dist/report/writeReport.js +9 -0
- package/dist/runtime/common.d.ts +13 -0
- package/dist/runtime/common.js +63 -0
- package/dist/runtime/container.d.ts +8 -0
- package/dist/runtime/container.js +67 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/install.d.ts +51 -0
- package/dist/runtime/install.js +167 -0
- package/dist/runtime/openclaw/adapter.d.ts +2 -0
- package/dist/runtime/openclaw/adapter.js +194 -0
- package/dist/runtime/openclaw/runAuth.d.ts +2 -0
- package/dist/runtime/openclaw/runAuth.js +125 -0
- package/dist/runtime/openclaw/scaffold-assets/AGENTS.md +120 -0
- package/dist/runtime/openclaw/scaffold-assets/CLAUDE.md +5 -0
- package/dist/runtime/openclaw/scaffold-assets/IDENTITY.md +23 -0
- package/dist/runtime/openclaw/scaffold-assets/SOUL.md +36 -0
- package/dist/runtime/openclaw/scaffold.d.ts +2 -0
- package/dist/runtime/openclaw/scaffold.js +28 -0
- package/dist/runtime/openclaw/surfaces.d.ts +5 -0
- package/dist/runtime/openclaw/surfaces.js +253 -0
- package/dist/runtime/picoclaw/adapter.d.ts +2 -0
- package/dist/runtime/picoclaw/adapter.js +204 -0
- package/dist/runtime/picoclaw/runAuth.d.ts +2 -0
- package/dist/runtime/picoclaw/runAuth.js +117 -0
- package/dist/runtime/picoclaw/scaffold-assets/AGENTS.md +3 -0
- package/dist/runtime/picoclaw/scaffold-assets/CLAUDE.md +5 -0
- package/dist/runtime/picoclaw/scaffold-assets/IDENTITY.md +3 -0
- package/dist/runtime/picoclaw/scaffold-assets/SOUL.md +3 -0
- package/dist/runtime/picoclaw/scaffold.d.ts +2 -0
- package/dist/runtime/picoclaw/scaffold.js +28 -0
- package/dist/runtime/picoclaw/surfaces.d.ts +5 -0
- package/dist/runtime/picoclaw/surfaces.js +111 -0
- package/dist/runtime/registry.d.ts +41 -0
- package/dist/runtime/registry.js +134 -0
- package/dist/runtime/scaffoldAssets.d.ts +1 -0
- package/dist/runtime/scaffoldAssets.js +2 -0
- package/dist/runtime/tinyclaw/adapter.d.ts +2 -0
- package/dist/runtime/tinyclaw/adapter.js +263 -0
- package/dist/runtime/tinyclaw/runAuth.d.ts +2 -0
- package/dist/runtime/tinyclaw/runAuth.js +22 -0
- package/dist/runtime/tinyclaw/scaffold-assets/AGENTS.md +160 -0
- package/dist/runtime/tinyclaw/scaffold-assets/CLAUDE.md +5 -0
- package/dist/runtime/tinyclaw/scaffold-assets/SOUL.md +177 -0
- package/dist/runtime/tinyclaw/scaffold.d.ts +2 -0
- package/dist/runtime/tinyclaw/scaffold.js +24 -0
- package/dist/runtime/tinyclaw/surfaces.d.ts +8 -0
- package/dist/runtime/tinyclaw/surfaces.js +86 -0
- package/dist/runtime/types.d.ts +87 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtimes/picoclaw/agents/assistant/config.json +16 -0
- package/dist/runtimes/picoclaw/agents/assistant/workspace/AGENTS.md +1 -0
- package/dist/shared/constants.d.ts +7 -0
- package/dist/shared/constants.js +7 -0
- package/dist/shared/errors.d.ts +6 -0
- package/dist/shared/errors.js +9 -0
- package/dist/shared/index.d.ts +3 -0
- package/dist/shared/index.js +3 -0
- package/dist/shared/types.d.ts +9 -0
- package/dist/shared/types.js +1 -0
- package/dist/spawnfile-report.json +71 -0
- package/package.json +41 -0
- package/runtimes.yaml +62 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileExists, getManifestPath, getProjectRoot, writeUtf8File } from "../filesystem/index.js";
|
|
3
|
+
import { isAgentManifest, isTeamManifest, loadManifest, renderSpawnfile } from "../manifest/index.js";
|
|
4
|
+
import { SpawnfileError } from "../shared/index.js";
|
|
5
|
+
import { initProject } from "./initProject.js";
|
|
6
|
+
const assertAddableId = (id) => {
|
|
7
|
+
if (id.trim().length === 0) {
|
|
8
|
+
throw new SpawnfileError("validation_error", "Child id must not be empty");
|
|
9
|
+
}
|
|
10
|
+
if (/\s/.test(id)) {
|
|
11
|
+
throw new SpawnfileError("validation_error", `Child id must not contain whitespace: ${id}`);
|
|
12
|
+
}
|
|
13
|
+
if (/[\\/]/.test(id)) {
|
|
14
|
+
throw new SpawnfileError("validation_error", `Child id must not contain path separators: ${id}`);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const resolveTargetManifestPath = (inputPath) => getManifestPath(path.resolve(inputPath ?? process.cwd()));
|
|
18
|
+
const assertChildDirectoryAvailable = async (childDirectory) => {
|
|
19
|
+
if (await fileExists(childDirectory)) {
|
|
20
|
+
throw new SpawnfileError("io_error", `Refusing to overwrite existing child directory at ${childDirectory}`);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const assertUniqueRef = (refs, id, label) => {
|
|
24
|
+
if (refs.some((entry) => entry.id === id)) {
|
|
25
|
+
throw new SpawnfileError("validation_error", `Duplicate ${label} id: ${id}`);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const updateParentManifest = async (manifestPath, manifest) => {
|
|
29
|
+
await writeUtf8File(manifestPath, renderSpawnfile(manifest));
|
|
30
|
+
};
|
|
31
|
+
const getRuntimeName = (runtime) => typeof runtime === "string" ? runtime : runtime?.name;
|
|
32
|
+
const createTeamMember = (id, relativeRef) => ({
|
|
33
|
+
id,
|
|
34
|
+
ref: relativeRef
|
|
35
|
+
});
|
|
36
|
+
export const addAgentProject = async (options) => {
|
|
37
|
+
assertAddableId(options.id);
|
|
38
|
+
const manifestPath = resolveTargetManifestPath(options.path);
|
|
39
|
+
const loadedManifest = await loadManifest(manifestPath);
|
|
40
|
+
if (!isTeamManifest(loadedManifest.manifest)) {
|
|
41
|
+
throw new SpawnfileError("validation_error", "spawnfile add agent only works on team projects");
|
|
42
|
+
}
|
|
43
|
+
assertUniqueRef(loadedManifest.manifest.members, options.id, "member");
|
|
44
|
+
const targetDirectory = getProjectRoot(manifestPath);
|
|
45
|
+
const childDirectory = path.join(targetDirectory, "agents", options.id);
|
|
46
|
+
await assertChildDirectoryAvailable(childDirectory);
|
|
47
|
+
const child = await initProject({
|
|
48
|
+
directory: childDirectory,
|
|
49
|
+
runtime: options.runtime
|
|
50
|
+
});
|
|
51
|
+
await updateParentManifest(manifestPath, {
|
|
52
|
+
...loadedManifest.manifest,
|
|
53
|
+
members: [
|
|
54
|
+
...loadedManifest.manifest.members,
|
|
55
|
+
createTeamMember(options.id, `./agents/${options.id}`)
|
|
56
|
+
]
|
|
57
|
+
});
|
|
58
|
+
return {
|
|
59
|
+
createdFiles: child.createdFiles,
|
|
60
|
+
targetDirectory,
|
|
61
|
+
updatedFiles: [manifestPath]
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
export const addSubagentProject = async (options) => {
|
|
65
|
+
assertAddableId(options.id);
|
|
66
|
+
const manifestPath = resolveTargetManifestPath(options.path);
|
|
67
|
+
const loadedManifest = await loadManifest(manifestPath);
|
|
68
|
+
if (!isAgentManifest(loadedManifest.manifest)) {
|
|
69
|
+
throw new SpawnfileError("validation_error", "spawnfile add subagent only works on agent projects");
|
|
70
|
+
}
|
|
71
|
+
assertUniqueRef(loadedManifest.manifest.subagents ?? [], options.id, "subagent");
|
|
72
|
+
const runtimeName = getRuntimeName(loadedManifest.manifest.runtime);
|
|
73
|
+
if (!runtimeName) {
|
|
74
|
+
throw new SpawnfileError("validation_error", "Agent must declare a runtime before adding subagents");
|
|
75
|
+
}
|
|
76
|
+
const targetDirectory = getProjectRoot(manifestPath);
|
|
77
|
+
const childDirectory = path.join(targetDirectory, "subagents", options.id);
|
|
78
|
+
await assertChildDirectoryAvailable(childDirectory);
|
|
79
|
+
const child = await initProject({
|
|
80
|
+
directory: childDirectory,
|
|
81
|
+
runtime: runtimeName
|
|
82
|
+
});
|
|
83
|
+
await updateParentManifest(manifestPath, {
|
|
84
|
+
...loadedManifest.manifest,
|
|
85
|
+
subagents: [
|
|
86
|
+
...(loadedManifest.manifest.subagents ?? []),
|
|
87
|
+
{
|
|
88
|
+
id: options.id,
|
|
89
|
+
ref: `./subagents/${options.id}`
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
createdFiles: child.createdFiles,
|
|
95
|
+
targetDirectory,
|
|
96
|
+
updatedFiles: [manifestPath]
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
export const addTeamProject = async (options) => {
|
|
100
|
+
assertAddableId(options.id);
|
|
101
|
+
const manifestPath = resolveTargetManifestPath(options.path);
|
|
102
|
+
const loadedManifest = await loadManifest(manifestPath);
|
|
103
|
+
if (!isTeamManifest(loadedManifest.manifest)) {
|
|
104
|
+
throw new SpawnfileError("validation_error", "spawnfile add team only works on team projects");
|
|
105
|
+
}
|
|
106
|
+
assertUniqueRef(loadedManifest.manifest.members, options.id, "member");
|
|
107
|
+
const targetDirectory = getProjectRoot(manifestPath);
|
|
108
|
+
const childDirectory = path.join(targetDirectory, "teams", options.id);
|
|
109
|
+
await assertChildDirectoryAvailable(childDirectory);
|
|
110
|
+
const child = await initProject({
|
|
111
|
+
directory: childDirectory,
|
|
112
|
+
team: true
|
|
113
|
+
});
|
|
114
|
+
await updateParentManifest(manifestPath, {
|
|
115
|
+
...loadedManifest.manifest,
|
|
116
|
+
members: [
|
|
117
|
+
...loadedManifest.manifest.members,
|
|
118
|
+
createTeamMember(options.id, `./teams/${options.id}`)
|
|
119
|
+
]
|
|
120
|
+
});
|
|
121
|
+
return {
|
|
122
|
+
createdFiles: child.createdFiles,
|
|
123
|
+
targetDirectory,
|
|
124
|
+
updatedFiles: [manifestPath]
|
|
125
|
+
};
|
|
126
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
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[];
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { DEFAULT_DISCORD_BOT_TOKEN_SECRET, DEFAULT_SLACK_APP_TOKEN_SECRET, DEFAULT_SLACK_BOT_TOKEN_SECRET, DEFAULT_TELEGRAM_BOT_TOKEN_SECRET } from "../shared/index.js";
|
|
2
|
+
export const resolveAgentSurfaces = (surfaces) => {
|
|
3
|
+
if (!surfaces) {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
const resolved = {};
|
|
7
|
+
if (surfaces.discord) {
|
|
8
|
+
resolved.discord = {
|
|
9
|
+
...(surfaces.discord.access
|
|
10
|
+
? {
|
|
11
|
+
access: {
|
|
12
|
+
channels: [...(surfaces.discord.access.channels ?? [])],
|
|
13
|
+
guilds: [...(surfaces.discord.access.guilds ?? [])],
|
|
14
|
+
mode: surfaces.discord.access.mode ?? "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
|
+
if (surfaces.telegram) {
|
|
23
|
+
resolved.telegram = {
|
|
24
|
+
...(surfaces.telegram.access
|
|
25
|
+
? {
|
|
26
|
+
access: {
|
|
27
|
+
chats: [...(surfaces.telegram.access.chats ?? [])],
|
|
28
|
+
mode: surfaces.telegram.access.mode ?? "allowlist",
|
|
29
|
+
users: [...(surfaces.telegram.access.users ?? [])]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
: {}),
|
|
33
|
+
botTokenSecret: surfaces.telegram.bot_token_secret ?? DEFAULT_TELEGRAM_BOT_TOKEN_SECRET
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (surfaces.whatsapp) {
|
|
37
|
+
resolved.whatsapp = surfaces.whatsapp.access
|
|
38
|
+
? {
|
|
39
|
+
access: {
|
|
40
|
+
groups: [...(surfaces.whatsapp.access.groups ?? [])],
|
|
41
|
+
mode: surfaces.whatsapp.access.mode ?? "allowlist",
|
|
42
|
+
users: [...(surfaces.whatsapp.access.users ?? [])]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
: {};
|
|
46
|
+
}
|
|
47
|
+
if (surfaces.slack) {
|
|
48
|
+
resolved.slack = {
|
|
49
|
+
...(surfaces.slack.access
|
|
50
|
+
? {
|
|
51
|
+
access: {
|
|
52
|
+
channels: [...(surfaces.slack.access.channels ?? [])],
|
|
53
|
+
mode: surfaces.slack.access.mode ?? "allowlist",
|
|
54
|
+
users: [...(surfaces.slack.access.users ?? [])]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
: {}),
|
|
58
|
+
appTokenSecret: surfaces.slack.app_token_secret ?? DEFAULT_SLACK_APP_TOKEN_SECRET,
|
|
59
|
+
botTokenSecret: surfaces.slack.bot_token_secret ?? DEFAULT_SLACK_BOT_TOKEN_SECRET
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return Object.keys(resolved).length > 0 ? resolved : undefined;
|
|
63
|
+
};
|
|
64
|
+
export const listAgentSurfaceSecretNames = (surfaces) => [
|
|
65
|
+
...(surfaces?.discord ? [surfaces.discord.botTokenSecret] : []),
|
|
66
|
+
...(surfaces?.slack
|
|
67
|
+
? [surfaces.slack.appTokenSecret, surfaces.slack.botTokenSecret]
|
|
68
|
+
: []),
|
|
69
|
+
...(surfaces?.telegram ? [surfaces.telegram.botTokenSecret] : [])
|
|
70
|
+
].sort();
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { getCanonicalManifestPath, getManifestPath, resolveProjectPath } from "../filesystem/index.js";
|
|
2
|
+
import { isAgentManifest, isTeamManifest, loadManifest, mergeExecution, normalizeRuntimeBinding } from "../manifest/index.js";
|
|
3
|
+
import { assertRuntimeCanCompile } from "../runtime/index.js";
|
|
4
|
+
import { SpawnfileError } from "../shared/index.js";
|
|
5
|
+
import { getAgentFingerprint, getMcpNames, getTeamFingerprint, validateEffectiveSkillRequirements } from "./compilePlanHelpers.js";
|
|
6
|
+
import { resolveAgentSurfaces } from "./agentSurfaces.js";
|
|
7
|
+
import { assignStableNodeIds } from "./helpers.js";
|
|
8
|
+
import { loadResolvedDocuments, mergeResolvedSkills, loadResolvedSkills, mergeSharedSurface } from "./surfaces.js";
|
|
9
|
+
import { assertRuntimeSupportsExecutionModelAuth } from "./modelAuth.js";
|
|
10
|
+
import { assertRuntimeSupportsAgentSurfaces } from "./surfaceSupport.js";
|
|
11
|
+
import { applyExecutionDefaults } from "./executionDefaults.js";
|
|
12
|
+
const resolveRuntime = async (manifest, context) => {
|
|
13
|
+
const localRuntime = normalizeRuntimeBinding(manifest.runtime);
|
|
14
|
+
if (context.isSubagent) {
|
|
15
|
+
if (!context.inheritedRuntime) {
|
|
16
|
+
throw new SpawnfileError("runtime_error", `Subagent ${manifest.name} is missing inherited runtime context`);
|
|
17
|
+
}
|
|
18
|
+
if (localRuntime &&
|
|
19
|
+
localRuntime.name !== context.inheritedRuntime.name) {
|
|
20
|
+
throw new SpawnfileError("runtime_error", `Subagent ${manifest.name} must match parent runtime`);
|
|
21
|
+
}
|
|
22
|
+
return context.inheritedRuntime;
|
|
23
|
+
}
|
|
24
|
+
if (!localRuntime) {
|
|
25
|
+
throw new SpawnfileError("runtime_error", `Agent ${manifest.name} does not declare a runtime`);
|
|
26
|
+
}
|
|
27
|
+
await assertRuntimeCanCompile(localRuntime.name);
|
|
28
|
+
return localRuntime;
|
|
29
|
+
};
|
|
30
|
+
export const buildCompilePlan = async (inputPath) => {
|
|
31
|
+
const rootManifestPath = getCanonicalManifestPath(getManifestPath(inputPath));
|
|
32
|
+
const loadCache = new Map();
|
|
33
|
+
const nodeCache = new Map();
|
|
34
|
+
const fingerprintCache = new Map();
|
|
35
|
+
const edges = [];
|
|
36
|
+
const visitStack = [];
|
|
37
|
+
const getLoadedManifest = (manifestPath) => {
|
|
38
|
+
const canonicalPath = getCanonicalManifestPath(manifestPath);
|
|
39
|
+
const cached = loadCache.get(canonicalPath);
|
|
40
|
+
if (cached) {
|
|
41
|
+
return cached;
|
|
42
|
+
}
|
|
43
|
+
const promise = loadManifest(canonicalPath);
|
|
44
|
+
loadCache.set(canonicalPath, promise);
|
|
45
|
+
return promise;
|
|
46
|
+
};
|
|
47
|
+
const visitAgent = async (manifestPath, context) => {
|
|
48
|
+
const canonicalPath = getCanonicalManifestPath(manifestPath);
|
|
49
|
+
if (visitStack.includes(canonicalPath)) {
|
|
50
|
+
throw new SpawnfileError("compile_error", `Cycle detected while visiting ${canonicalPath}`);
|
|
51
|
+
}
|
|
52
|
+
const loadedManifest = await getLoadedManifest(canonicalPath);
|
|
53
|
+
if (!isAgentManifest(loadedManifest.manifest)) {
|
|
54
|
+
throw new SpawnfileError("compile_error", `Expected agent manifest, got ${loadedManifest.manifest.kind} at ${canonicalPath}`);
|
|
55
|
+
}
|
|
56
|
+
const runtime = await resolveRuntime(loadedManifest.manifest, context);
|
|
57
|
+
const execution = applyExecutionDefaults(context.isSubagent
|
|
58
|
+
? mergeExecution(context.inheritedExecution, loadedManifest.manifest.execution)
|
|
59
|
+
: loadedManifest.manifest.execution);
|
|
60
|
+
assertRuntimeSupportsExecutionModelAuth(runtime.name, execution, loadedManifest.manifest.name);
|
|
61
|
+
const sharedSurface = mergeSharedSurface(context.inheritedShared?.surface, {
|
|
62
|
+
env: loadedManifest.manifest.env,
|
|
63
|
+
mcpServers: loadedManifest.manifest.mcp_servers,
|
|
64
|
+
secrets: loadedManifest.manifest.secrets,
|
|
65
|
+
skills: loadedManifest.manifest.skills
|
|
66
|
+
});
|
|
67
|
+
const inheritedSkills = context.inheritedShared
|
|
68
|
+
? await loadResolvedSkills(context.inheritedShared.manifestPath, context.inheritedShared.surface?.skills)
|
|
69
|
+
: [];
|
|
70
|
+
const localSkills = await loadResolvedSkills(canonicalPath, loadedManifest.manifest.skills);
|
|
71
|
+
const skills = mergeResolvedSkills(inheritedSkills, localSkills);
|
|
72
|
+
validateEffectiveSkillRequirements(loadedManifest.manifest.name, getMcpNames(sharedSurface.mcpServers), skills);
|
|
73
|
+
const candidate = {
|
|
74
|
+
docs: await loadResolvedDocuments(canonicalPath, loadedManifest.manifest.docs),
|
|
75
|
+
env: sharedSurface.env,
|
|
76
|
+
execution,
|
|
77
|
+
kind: "agent",
|
|
78
|
+
mcpServers: sharedSurface.mcpServers,
|
|
79
|
+
name: loadedManifest.manifest.name,
|
|
80
|
+
policyMode: loadedManifest.manifest.policy?.mode ?? null,
|
|
81
|
+
policyOnDegrade: loadedManifest.manifest.policy?.on_degrade ?? null,
|
|
82
|
+
runtime,
|
|
83
|
+
secrets: sharedSurface.secrets,
|
|
84
|
+
skills,
|
|
85
|
+
source: canonicalPath,
|
|
86
|
+
surfaces: resolveAgentSurfaces(loadedManifest.manifest.surfaces),
|
|
87
|
+
subagents: []
|
|
88
|
+
};
|
|
89
|
+
assertRuntimeSupportsAgentSurfaces(runtime.name, candidate.surfaces, loadedManifest.manifest.name);
|
|
90
|
+
const fingerprint = getAgentFingerprint(candidate);
|
|
91
|
+
const existingFingerprint = fingerprintCache.get(canonicalPath);
|
|
92
|
+
if (existingFingerprint && existingFingerprint !== fingerprint) {
|
|
93
|
+
throw new SpawnfileError("compile_error", `Manifest ${canonicalPath} resolves differently across compile contexts`);
|
|
94
|
+
}
|
|
95
|
+
const cachedNode = nodeCache.get(canonicalPath);
|
|
96
|
+
if (cachedNode) {
|
|
97
|
+
return cachedNode.value;
|
|
98
|
+
}
|
|
99
|
+
fingerprintCache.set(canonicalPath, fingerprint);
|
|
100
|
+
nodeCache.set(canonicalPath, {
|
|
101
|
+
runtimeName: runtime.name,
|
|
102
|
+
source: canonicalPath,
|
|
103
|
+
value: candidate
|
|
104
|
+
});
|
|
105
|
+
visitStack.push(canonicalPath);
|
|
106
|
+
for (const subagent of loadedManifest.manifest.subagents ?? []) {
|
|
107
|
+
const childManifestPath = getManifestPath(resolveProjectPath(canonicalPath, subagent.ref));
|
|
108
|
+
const resolvedSubagent = await visitAgent(childManifestPath, {
|
|
109
|
+
inheritedExecution: execution,
|
|
110
|
+
inheritedRuntime: runtime,
|
|
111
|
+
isSubagent: true
|
|
112
|
+
});
|
|
113
|
+
candidate.subagents.push({
|
|
114
|
+
id: subagent.id,
|
|
115
|
+
nodeSource: resolvedSubagent.source
|
|
116
|
+
});
|
|
117
|
+
edges.push({
|
|
118
|
+
from: canonicalPath,
|
|
119
|
+
kind: "subagent",
|
|
120
|
+
label: subagent.id,
|
|
121
|
+
to: resolvedSubagent.source
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
visitStack.pop();
|
|
125
|
+
return candidate;
|
|
126
|
+
};
|
|
127
|
+
const visitTeam = async (manifestPath) => {
|
|
128
|
+
const canonicalPath = getCanonicalManifestPath(manifestPath);
|
|
129
|
+
if (visitStack.includes(canonicalPath)) {
|
|
130
|
+
throw new SpawnfileError("compile_error", `Cycle detected while visiting ${canonicalPath}`);
|
|
131
|
+
}
|
|
132
|
+
const loadedManifest = await getLoadedManifest(canonicalPath);
|
|
133
|
+
if (!isTeamManifest(loadedManifest.manifest)) {
|
|
134
|
+
throw new SpawnfileError("compile_error", `Expected team manifest, got ${loadedManifest.manifest.kind} at ${canonicalPath}`);
|
|
135
|
+
}
|
|
136
|
+
const sharedSkills = await loadResolvedSkills(canonicalPath, loadedManifest.manifest.shared?.skills);
|
|
137
|
+
validateEffectiveSkillRequirements(loadedManifest.manifest.name, getMcpNames(loadedManifest.manifest.shared?.mcp_servers ?? []), sharedSkills);
|
|
138
|
+
const structure = loadedManifest.manifest.structure;
|
|
139
|
+
const memberIds = loadedManifest.manifest.members.map((member) => member.id);
|
|
140
|
+
const resolvedExternal = structure.external
|
|
141
|
+
?? (structure.mode === "hierarchical" && structure.leader
|
|
142
|
+
? [structure.leader]
|
|
143
|
+
: memberIds);
|
|
144
|
+
const candidate = {
|
|
145
|
+
docs: await loadResolvedDocuments(canonicalPath, loadedManifest.manifest.docs),
|
|
146
|
+
kind: "team",
|
|
147
|
+
members: [],
|
|
148
|
+
name: loadedManifest.manifest.name,
|
|
149
|
+
policyMode: loadedManifest.manifest.policy?.mode ?? null,
|
|
150
|
+
policyOnDegrade: loadedManifest.manifest.policy?.on_degrade ?? null,
|
|
151
|
+
shared: {
|
|
152
|
+
env: loadedManifest.manifest.shared?.env ?? {},
|
|
153
|
+
mcpServers: loadedManifest.manifest.shared?.mcp_servers ?? [],
|
|
154
|
+
secrets: loadedManifest.manifest.shared?.secrets ?? [],
|
|
155
|
+
skills: sharedSkills
|
|
156
|
+
},
|
|
157
|
+
source: canonicalPath,
|
|
158
|
+
structure: {
|
|
159
|
+
external: resolvedExternal,
|
|
160
|
+
leader: structure.leader ?? null,
|
|
161
|
+
mode: structure.mode
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
const fingerprint = getTeamFingerprint(candidate);
|
|
165
|
+
const existingFingerprint = fingerprintCache.get(canonicalPath);
|
|
166
|
+
if (existingFingerprint && existingFingerprint !== fingerprint) {
|
|
167
|
+
throw new SpawnfileError("compile_error", `Team manifest ${canonicalPath} resolves differently across compile contexts`);
|
|
168
|
+
}
|
|
169
|
+
const cachedNode = nodeCache.get(canonicalPath);
|
|
170
|
+
if (cachedNode) {
|
|
171
|
+
return cachedNode.value;
|
|
172
|
+
}
|
|
173
|
+
fingerprintCache.set(canonicalPath, fingerprint);
|
|
174
|
+
nodeCache.set(canonicalPath, {
|
|
175
|
+
runtimeName: null,
|
|
176
|
+
source: canonicalPath,
|
|
177
|
+
value: candidate
|
|
178
|
+
});
|
|
179
|
+
visitStack.push(canonicalPath);
|
|
180
|
+
for (const member of loadedManifest.manifest.members) {
|
|
181
|
+
const childManifestPath = getManifestPath(resolveProjectPath(canonicalPath, member.ref));
|
|
182
|
+
const childManifest = await getLoadedManifest(childManifestPath);
|
|
183
|
+
let resolvedMember;
|
|
184
|
+
if (isAgentManifest(childManifest.manifest)) {
|
|
185
|
+
const resolvedAgent = await visitAgent(childManifestPath, {
|
|
186
|
+
inheritedShared: {
|
|
187
|
+
manifestPath: canonicalPath,
|
|
188
|
+
surface: loadedManifest.manifest.shared
|
|
189
|
+
},
|
|
190
|
+
isSubagent: false
|
|
191
|
+
});
|
|
192
|
+
resolvedMember = {
|
|
193
|
+
id: member.id,
|
|
194
|
+
kind: "agent",
|
|
195
|
+
nodeSource: resolvedAgent.source,
|
|
196
|
+
runtimeName: resolvedAgent.runtime.name
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
const resolvedTeam = await visitTeam(childManifestPath);
|
|
201
|
+
resolvedMember = {
|
|
202
|
+
id: member.id,
|
|
203
|
+
kind: "team",
|
|
204
|
+
nodeSource: resolvedTeam.source,
|
|
205
|
+
runtimeName: null
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
candidate.members.push(resolvedMember);
|
|
209
|
+
edges.push({
|
|
210
|
+
from: canonicalPath,
|
|
211
|
+
kind: "team_member",
|
|
212
|
+
label: member.id,
|
|
213
|
+
to: resolvedMember.nodeSource
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
visitStack.pop();
|
|
217
|
+
return candidate;
|
|
218
|
+
};
|
|
219
|
+
const rootLoadedManifest = await getLoadedManifest(rootManifestPath);
|
|
220
|
+
if (isAgentManifest(rootLoadedManifest.manifest)) {
|
|
221
|
+
await visitAgent(rootManifestPath, { isSubagent: false });
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
await visitTeam(rootManifestPath);
|
|
225
|
+
}
|
|
226
|
+
const nodes = assignStableNodeIds([...nodeCache.values()]
|
|
227
|
+
.sort((left, right) => left.source.localeCompare(right.source))
|
|
228
|
+
.map((node) => ({
|
|
229
|
+
id: "",
|
|
230
|
+
kind: node.value.kind,
|
|
231
|
+
runtimeName: node.runtimeName,
|
|
232
|
+
slug: "",
|
|
233
|
+
source: node.source,
|
|
234
|
+
value: node.value
|
|
235
|
+
})));
|
|
236
|
+
const idBySource = new Map(nodes.map((node) => [node.value.source, node.id]));
|
|
237
|
+
const compilePlanNodes = nodes;
|
|
238
|
+
const compilePlanEdges = edges.map((edge) => ({
|
|
239
|
+
...edge,
|
|
240
|
+
from: idBySource.get(edge.from) ?? edge.from,
|
|
241
|
+
to: idBySource.get(edge.to) ?? edge.to
|
|
242
|
+
}));
|
|
243
|
+
const runtimes = compilePlanNodes.reduce((groups, node) => {
|
|
244
|
+
if (!node.runtimeName) {
|
|
245
|
+
return groups;
|
|
246
|
+
}
|
|
247
|
+
const group = groups[node.runtimeName] ?? { nodeIds: [] };
|
|
248
|
+
group.nodeIds.push(node.id);
|
|
249
|
+
groups[node.runtimeName] = group;
|
|
250
|
+
return groups;
|
|
251
|
+
}, {});
|
|
252
|
+
return {
|
|
253
|
+
edges: compilePlanEdges,
|
|
254
|
+
nodes: compilePlanNodes,
|
|
255
|
+
root: rootManifestPath,
|
|
256
|
+
runtimes
|
|
257
|
+
};
|
|
258
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type CompileProjectOptions, type CompileProjectResult } from "./compileProject.js";
|
|
2
|
+
export interface DockerBuildInvocation {
|
|
3
|
+
args: string[];
|
|
4
|
+
command: string;
|
|
5
|
+
cwd: string;
|
|
6
|
+
imageTag: string;
|
|
7
|
+
}
|
|
8
|
+
export type DockerBuildRunner = (invocation: DockerBuildInvocation) => Promise<void>;
|
|
9
|
+
export interface BuildProjectOptions extends CompileProjectOptions {
|
|
10
|
+
buildRunner?: DockerBuildRunner;
|
|
11
|
+
dockerCommand?: string;
|
|
12
|
+
imageTag?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface BuildProjectResult extends CompileProjectResult {
|
|
15
|
+
imageTag: string;
|
|
16
|
+
}
|
|
17
|
+
export declare const createDefaultImageTag: (projectRoot: string) => string;
|
|
18
|
+
export declare const createDockerBuildInvocation: (outputDirectory: string, imageTag: string, dockerCommand?: string) => DockerBuildInvocation;
|
|
19
|
+
export declare const runDockerBuild: DockerBuildRunner;
|
|
20
|
+
export declare const buildProject: (inputPath: string, options?: BuildProjectOptions) => Promise<BuildProjectResult>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { SpawnfileError } from "../shared/index.js";
|
|
4
|
+
import { compileProject } from "./compileProject.js";
|
|
5
|
+
import { slugify } from "./helpers.js";
|
|
6
|
+
export const createDefaultImageTag = (projectRoot) => {
|
|
7
|
+
const baseName = slugify(path.basename(projectRoot));
|
|
8
|
+
return `spawnfile-${baseName || "project"}`;
|
|
9
|
+
};
|
|
10
|
+
const resolveImageTagRoot = (inputPath) => {
|
|
11
|
+
const resolvedPath = path.resolve(inputPath);
|
|
12
|
+
return path.basename(resolvedPath).toLowerCase() === "spawnfile"
|
|
13
|
+
? path.dirname(resolvedPath)
|
|
14
|
+
: resolvedPath;
|
|
15
|
+
};
|
|
16
|
+
export const createDockerBuildInvocation = (outputDirectory, imageTag, dockerCommand = "docker") => ({
|
|
17
|
+
args: ["build", "-t", imageTag, "."],
|
|
18
|
+
command: dockerCommand,
|
|
19
|
+
cwd: outputDirectory,
|
|
20
|
+
imageTag
|
|
21
|
+
});
|
|
22
|
+
export const runDockerBuild = async (invocation) => new Promise((resolve, reject) => {
|
|
23
|
+
const child = spawn(invocation.command, invocation.args, {
|
|
24
|
+
cwd: invocation.cwd,
|
|
25
|
+
stdio: "inherit"
|
|
26
|
+
});
|
|
27
|
+
child.once("error", (error) => {
|
|
28
|
+
reject(new SpawnfileError("compile_error", `Unable to start docker build for ${invocation.imageTag}: ${error.message}`));
|
|
29
|
+
});
|
|
30
|
+
child.once("exit", (code, signal) => {
|
|
31
|
+
if (code === 0) {
|
|
32
|
+
resolve();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
reject(new SpawnfileError("compile_error", signal
|
|
36
|
+
? `Docker build for ${invocation.imageTag} exited from signal ${signal}`
|
|
37
|
+
: `Docker build for ${invocation.imageTag} failed with exit code ${code ?? "unknown"}`));
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
export const buildProject = async (inputPath, options = {}) => {
|
|
41
|
+
const compileResult = await compileProject(inputPath, {
|
|
42
|
+
clean: options.clean,
|
|
43
|
+
outputDirectory: options.outputDirectory
|
|
44
|
+
});
|
|
45
|
+
const imageTag = options.imageTag ?? createDefaultImageTag(resolveImageTagRoot(inputPath));
|
|
46
|
+
const invocation = createDockerBuildInvocation(compileResult.outputDirectory, imageTag, options.dockerCommand);
|
|
47
|
+
await (options.buildRunner ?? runDockerBuild)(invocation);
|
|
48
|
+
return {
|
|
49
|
+
...compileResult,
|
|
50
|
+
imageTag
|
|
51
|
+
};
|
|
52
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ResolvedAgentNode, ResolvedSkill, ResolvedTeamNode } from "./types.js";
|
|
2
|
+
export declare const getMcpNames: (servers: Array<{
|
|
3
|
+
name: string;
|
|
4
|
+
}>) => Set<string>;
|
|
5
|
+
export declare const validateEffectiveSkillRequirements: (nodeName: string, mcpNames: Set<string>, skills: ResolvedSkill[]) => void;
|
|
6
|
+
export declare const getAgentFingerprint: (node: ResolvedAgentNode) => string;
|
|
7
|
+
export declare const getTeamFingerprint: (node: ResolvedTeamNode) => string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { SpawnfileError } from "../shared/index.js";
|
|
2
|
+
import { stableStringify } from "./helpers.js";
|
|
3
|
+
export const getMcpNames = (servers) => new Set(servers.map((server) => server.name));
|
|
4
|
+
export const validateEffectiveSkillRequirements = (nodeName, mcpNames, skills) => {
|
|
5
|
+
for (const skill of skills) {
|
|
6
|
+
for (const mcpName of skill.requiresMcp) {
|
|
7
|
+
if (!mcpNames.has(mcpName)) {
|
|
8
|
+
throw new SpawnfileError("validation_error", `Skill ${skill.name} on ${nodeName} requires undeclared MCP server: ${mcpName}`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export const getAgentFingerprint = (node) => stableStringify({
|
|
14
|
+
env: node.env,
|
|
15
|
+
execution: node.execution,
|
|
16
|
+
mcpServers: node.mcpServers,
|
|
17
|
+
runtime: node.runtime,
|
|
18
|
+
secrets: node.secrets,
|
|
19
|
+
skills: node.skills.map((skill) => ({
|
|
20
|
+
name: skill.name,
|
|
21
|
+
ref: skill.ref,
|
|
22
|
+
requiresMcp: skill.requiresMcp
|
|
23
|
+
})),
|
|
24
|
+
surfaces: node.surfaces
|
|
25
|
+
});
|
|
26
|
+
export const getTeamFingerprint = (node) => stableStringify({
|
|
27
|
+
members: node.members,
|
|
28
|
+
structure: node.structure,
|
|
29
|
+
shared: {
|
|
30
|
+
env: node.shared.env,
|
|
31
|
+
mcpServers: node.shared.mcpServers,
|
|
32
|
+
secrets: node.shared.secrets,
|
|
33
|
+
skills: node.shared.skills.map((skill) => ({
|
|
34
|
+
name: skill.name,
|
|
35
|
+
ref: skill.ref,
|
|
36
|
+
requiresMcp: skill.requiresMcp
|
|
37
|
+
}))
|
|
38
|
+
}
|
|
39
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CompileReport } from "../report/index.js";
|
|
2
|
+
export interface CompileProjectOptions {
|
|
3
|
+
clean?: boolean;
|
|
4
|
+
outputDirectory?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface CompileProjectResult {
|
|
7
|
+
outputDirectory: string;
|
|
8
|
+
report: CompileReport;
|
|
9
|
+
reportPath: string;
|
|
10
|
+
}
|
|
11
|
+
export declare const compileProject: (inputPath: string, options?: CompileProjectOptions) => Promise<CompileProjectResult>;
|