spawnfile 0.1.1 → 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.
Files changed (85) hide show
  1. package/README.md +79 -396
  2. package/dist/cli/modelCommands.d.ts +3 -0
  3. package/dist/cli/modelCommands.js +68 -0
  4. package/dist/cli/runCli.d.ts +6 -1
  5. package/dist/cli/runCli.js +12 -67
  6. package/dist/cli/runtimeCommands.d.ts +3 -0
  7. package/dist/cli/runtimeCommands.js +20 -0
  8. package/dist/cli/surfaceCommands.d.ts +3 -0
  9. package/dist/cli/surfaceCommands.js +98 -0
  10. package/dist/compiler/agentSurfaces.js +51 -5
  11. package/dist/compiler/buildCompilePlan.js +36 -40
  12. package/dist/compiler/buildCompilePlanRuntime.d.ts +14 -0
  13. package/dist/compiler/buildCompilePlanRuntime.js +39 -0
  14. package/dist/compiler/buildCompilePlanTeams.d.ts +5 -0
  15. package/dist/compiler/buildCompilePlanTeams.js +38 -0
  16. package/dist/compiler/compilePlanHelpers.js +4 -1
  17. package/dist/compiler/compileProject.js +62 -13
  18. package/dist/compiler/compileProjectSupport.d.ts +17 -0
  19. package/dist/compiler/compileProjectSupport.js +136 -0
  20. package/dist/compiler/containerArtifacts.d.ts +6 -1
  21. package/dist/compiler/containerArtifacts.js +26 -4
  22. package/dist/compiler/containerArtifactsPlans.js +16 -1
  23. package/dist/compiler/containerArtifactsRender.d.ts +4 -2
  24. package/dist/compiler/containerArtifactsRender.js +21 -126
  25. package/dist/compiler/containerArtifactsTypes.d.ts +7 -0
  26. package/dist/compiler/containerEntrypointRender.d.ts +12 -0
  27. package/dist/compiler/containerEntrypointRender.js +186 -0
  28. package/dist/compiler/index.d.ts +2 -0
  29. package/dist/compiler/index.js +2 -0
  30. package/dist/compiler/interactiveSurfaceScopes.d.ts +2 -0
  31. package/dist/compiler/interactiveSurfaceScopes.js +21 -0
  32. package/dist/compiler/moltnetArtifacts.d.ts +27 -0
  33. package/dist/compiler/moltnetArtifacts.js +204 -0
  34. package/dist/compiler/moltnetBinaries.d.ts +4 -0
  35. package/dist/compiler/moltnetBinaries.js +103 -0
  36. package/dist/compiler/moltnetClientConfig.d.ts +11 -0
  37. package/dist/compiler/moltnetClientConfig.js +89 -0
  38. package/dist/compiler/moltnetRepresentativeResolution.d.ts +16 -0
  39. package/dist/compiler/moltnetRepresentativeResolution.js +86 -0
  40. package/dist/compiler/moltnetResolution.d.ts +3 -0
  41. package/dist/compiler/moltnetResolution.js +201 -0
  42. package/dist/compiler/runProject.js +1 -1
  43. package/dist/compiler/surfaceDefinitions.d.ts +55 -0
  44. package/dist/compiler/surfaceDefinitions.js +204 -0
  45. package/dist/compiler/teamContextHelpers.d.ts +18 -0
  46. package/dist/compiler/teamContextHelpers.js +112 -0
  47. package/dist/compiler/teamContextSupport.d.ts +4 -0
  48. package/dist/compiler/teamContextSupport.js +264 -0
  49. package/dist/compiler/teamContextSupport.testHelpers.d.ts +16 -0
  50. package/dist/compiler/teamContextSupport.testHelpers.js +68 -0
  51. package/dist/compiler/teamContextTypes.d.ts +28 -0
  52. package/dist/compiler/teamContextTypes.js +1 -0
  53. package/dist/compiler/teamRoster.d.ts +12 -0
  54. package/dist/compiler/teamRoster.js +48 -0
  55. package/dist/compiler/teamRosterEntries.d.ts +13 -0
  56. package/dist/compiler/teamRosterEntries.js +230 -0
  57. package/dist/compiler/teamRosterTypes.d.ts +45 -0
  58. package/dist/compiler/teamRosterTypes.js +1 -0
  59. package/dist/compiler/types.d.ts +72 -6
  60. package/dist/compiler/updateProjectRuntime.d.ts +9 -0
  61. package/dist/compiler/updateProjectRuntime.js +67 -0
  62. package/dist/compiler/updateProjectSurfaces.d.ts +8 -0
  63. package/dist/compiler/updateProjectSurfaces.js +106 -0
  64. package/dist/manifest/loadManifest.js +4 -4
  65. package/dist/manifest/renderSpawnfile.js +74 -8
  66. package/dist/manifest/scaffold.js +1 -3
  67. package/dist/manifest/schemas.d.ts +227 -17
  68. package/dist/manifest/schemas.js +62 -20
  69. package/dist/manifest/surfaceSchemas.d.ts +154 -0
  70. package/dist/manifest/surfaceSchemas.js +77 -5
  71. package/dist/runtime/common.js +3 -0
  72. package/dist/runtime/openclaw/adapter.js +38 -5
  73. package/dist/runtime/openclaw/moltnet.d.ts +12 -0
  74. package/dist/runtime/openclaw/moltnet.js +124 -0
  75. package/dist/runtime/openclaw/surfaces.js +3 -0
  76. package/dist/runtime/picoclaw/adapter.js +27 -8
  77. package/dist/runtime/picoclaw/pico.d.ts +2 -0
  78. package/dist/runtime/picoclaw/pico.js +2 -0
  79. package/dist/runtime/picoclaw/surfaces.js +11 -0
  80. package/dist/runtime/tinyclaw/adapter.js +22 -8
  81. package/dist/runtime/tinyclaw/runAuth.js +28 -1
  82. package/dist/runtime/tinyclaw/surfaces.js +8 -0
  83. package/dist/runtime/types.d.ts +11 -0
  84. package/package.json +4 -2
  85. package/runtimes.yaml +4 -4
@@ -1,8 +1,11 @@
1
1
  import { Command } from "commander";
2
2
  import { importClaudeCodeAuth, importCodexAuth, importEnvFile, requireAuthProfile } from "../auth/index.js";
3
- import { addAgentProject, addProjectModelFallback, addSubagentProject, addTeamProject, buildCompilePlan, buildProject, clearProjectModelFallbacks, compileProject, initProject, runProject, setProjectPrimaryModel, syncProjectAuth } from "../compiler/index.js";
3
+ import { addAgentProject, addProjectSurface, addProjectModelFallback, addSubagentProject, addTeamProject, buildCompilePlan, buildProject, clearProjectModelFallbacks, compileProject, initProject, removeProjectSurface, runProject, setProjectPrimaryModel, setProjectRuntime, setProjectSurfaceAccess, showProjectSurfaces, syncProjectAuth } from "../compiler/index.js";
4
4
  import { isSpawnfileError } from "../shared/index.js";
5
5
  import { listRuntimeAdapters } from "../runtime/index.js";
6
+ import { registerModelCommands } from "./modelCommands.js";
7
+ import { registerRuntimeCommands } from "./runtimeCommands.js";
8
+ import { registerSurfaceCommands } from "./surfaceCommands.js";
6
9
  const createDefaultStreams = () => ({
7
10
  stderr: (message) => process.stderr.write(`${message}\n`),
8
11
  stdout: (message) => process.stdout.write(`${message}\n`)
@@ -13,6 +16,7 @@ const createDefaultHandlers = () => ({
13
16
  compileProject,
14
17
  addAgentProject,
15
18
  addProjectModelFallback,
19
+ addProjectSurface,
16
20
  addSubagentProject,
17
21
  addTeamProject,
18
22
  clearProjectModelFallbacks,
@@ -21,9 +25,13 @@ const createDefaultHandlers = () => ({
21
25
  importEnvFile,
22
26
  initProject,
23
27
  listRuntimeAdapters,
28
+ removeProjectSurface,
24
29
  requireAuthProfile,
25
30
  runProject,
26
31
  setProjectPrimaryModel,
32
+ setProjectRuntime,
33
+ setProjectSurfaceAccess,
34
+ showProjectSurfaces,
27
35
  syncProjectAuth
28
36
  });
29
37
  const formatPlanSummary = (plan) => [
@@ -155,72 +163,9 @@ export const runCli = async (argv, streams = createDefaultStreams(), handlerOver
155
163
  streams.stdout(`created ${filePath}`);
156
164
  }
157
165
  });
158
- const modelCommand = program
159
- .command("model")
160
- .description("Edit primary and fallback model declarations");
161
- modelCommand
162
- .command("set")
163
- .argument("<provider>", "Model provider")
164
- .argument("<name>", "Model name")
165
- .argument("[path]", "Project directory or Spawnfile path", process.cwd())
166
- .option("--auth <method>", "Model auth method")
167
- .option("--key <env>", "Environment variable for api_key auth")
168
- .option("--compat <compatibility>", "Endpoint compatibility for custom/local models")
169
- .option("--base-url <url>", "Endpoint base URL for custom/local models")
170
- .option("--recursive", "Update the target project and its descendants")
171
- .action(async (provider, name, inputPath, options) => {
172
- const result = await handlers.setProjectPrimaryModel({
173
- authKey: options.key,
174
- authMethod: options.auth,
175
- endpointBaseUrl: options.baseUrl,
176
- endpointCompatibility: options.compat,
177
- name,
178
- path: inputPath,
179
- provider,
180
- recursive: options.recursive
181
- });
182
- for (const filePath of result.updatedFiles) {
183
- streams.stdout(`updated ${filePath}`);
184
- }
185
- });
186
- modelCommand
187
- .command("add-fallback")
188
- .argument("<provider>", "Model provider")
189
- .argument("<name>", "Model name")
190
- .argument("[path]", "Project directory or Spawnfile path", process.cwd())
191
- .option("--auth <method>", "Model auth method")
192
- .option("--key <env>", "Environment variable for api_key auth")
193
- .option("--compat <compatibility>", "Endpoint compatibility for custom/local models")
194
- .option("--base-url <url>", "Endpoint base URL for custom/local models")
195
- .option("--recursive", "Update the target project and its descendants")
196
- .action(async (provider, name, inputPath, options) => {
197
- const result = await handlers.addProjectModelFallback({
198
- authKey: options.key,
199
- authMethod: options.auth,
200
- endpointBaseUrl: options.baseUrl,
201
- endpointCompatibility: options.compat,
202
- name,
203
- path: inputPath,
204
- provider,
205
- recursive: options.recursive
206
- });
207
- for (const filePath of result.updatedFiles) {
208
- streams.stdout(`updated ${filePath}`);
209
- }
210
- });
211
- modelCommand
212
- .command("clear-fallbacks")
213
- .argument("[path]", "Project directory or Spawnfile path", process.cwd())
214
- .option("--recursive", "Update the target project and its descendants")
215
- .action(async (inputPath, options) => {
216
- const result = await handlers.clearProjectModelFallbacks({
217
- path: inputPath,
218
- recursive: options.recursive
219
- });
220
- for (const filePath of result.updatedFiles) {
221
- streams.stdout(`updated ${filePath}`);
222
- }
223
- });
166
+ registerModelCommands(program, handlers, streams);
167
+ registerRuntimeCommands(program, handlers, streams);
168
+ registerSurfaceCommands(program, handlers, streams);
224
169
  program
225
170
  .command("validate")
226
171
  .argument("[path]", "Project directory or Spawnfile path", process.cwd())
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ import type { CliHandlers, CliStreams } from "./runCli.js";
3
+ export declare const registerRuntimeCommands: (program: Command, handlers: CliHandlers, streams: CliStreams) => void;
@@ -0,0 +1,20 @@
1
+ export const registerRuntimeCommands = (program, handlers, streams) => {
2
+ const runtimeCommand = program
3
+ .command("runtime")
4
+ .description("Edit runtime declarations");
5
+ runtimeCommand
6
+ .command("set")
7
+ .argument("<name>", "Runtime name")
8
+ .argument("[path]", "Project directory or Spawnfile path", process.cwd())
9
+ .option("--recursive", "Update the target project and its descendants")
10
+ .action(async (runtime, inputPath, options) => {
11
+ const result = await handlers.setProjectRuntime({
12
+ path: inputPath,
13
+ recursive: options.recursive,
14
+ runtime
15
+ });
16
+ for (const filePath of result.updatedFiles) {
17
+ streams.stdout(`updated ${filePath}`);
18
+ }
19
+ });
20
+ };
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ import type { CliHandlers, CliStreams } from "./runCli.js";
3
+ export declare const registerSurfaceCommands: (program: Command, handlers: CliHandlers, streams: CliStreams) => void;
@@ -0,0 +1,98 @@
1
+ import YAML from "yaml";
2
+ import { resolvePortableSurfaceName } from "../compiler/index.js";
3
+ const collectStringOption = (value, previous) => [...previous, value];
4
+ const emitSurfaceSummaries = (streams, entries) => {
5
+ entries.forEach((entry, index) => {
6
+ if (index > 0) {
7
+ streams.stdout("");
8
+ }
9
+ streams.stdout(`path: ${entry.manifestPath}`);
10
+ streams.stdout(`kind: ${entry.kind}`);
11
+ streams.stdout(`name: ${entry.name}`);
12
+ if (!entry.surfaces) {
13
+ streams.stdout("surfaces: none");
14
+ return;
15
+ }
16
+ streams.stdout(YAML.stringify({ surfaces: entry.surfaces }).trimEnd());
17
+ });
18
+ };
19
+ export const registerSurfaceCommands = (program, handlers, streams) => {
20
+ const surfaceCommand = program
21
+ .command("surface")
22
+ .description("Edit agent surfaces");
23
+ surfaceCommand
24
+ .command("add")
25
+ .argument("<surface>", "Surface name")
26
+ .argument("[path]", "Project directory or Spawnfile path", process.cwd())
27
+ .option("--bot-token-secret <env>", "Override the bot token env var name")
28
+ .option("--app-token-secret <env>", "Override the Slack app token env var name")
29
+ .option("--recursive", "Update the target project and its descendants")
30
+ .action(async (surface, inputPath, options) => {
31
+ const surfaceName = resolvePortableSurfaceName(surface);
32
+ const result = await handlers.addProjectSurface({
33
+ appTokenSecret: options.appTokenSecret,
34
+ botTokenSecret: options.botTokenSecret,
35
+ path: inputPath,
36
+ recursive: options.recursive,
37
+ surface: surfaceName
38
+ });
39
+ for (const filePath of result.updatedFiles) {
40
+ streams.stdout(`updated ${filePath}`);
41
+ }
42
+ });
43
+ surfaceCommand
44
+ .command("remove")
45
+ .argument("<surface>", "Surface name")
46
+ .argument("[path]", "Project directory or Spawnfile path", process.cwd())
47
+ .option("--recursive", "Update the target project and its descendants")
48
+ .action(async (surface, inputPath, options) => {
49
+ const surfaceName = resolvePortableSurfaceName(surface);
50
+ const result = await handlers.removeProjectSurface({
51
+ path: inputPath,
52
+ recursive: options.recursive,
53
+ surface: surfaceName
54
+ });
55
+ for (const filePath of result.updatedFiles) {
56
+ streams.stdout(`updated ${filePath}`);
57
+ }
58
+ });
59
+ surfaceCommand
60
+ .command("set-access")
61
+ .argument("<surface>", "Surface name")
62
+ .argument("[path]", "Project directory or Spawnfile path", process.cwd())
63
+ .requiredOption("--mode <mode>", "Access mode")
64
+ .option("--user <id>", "Allowlisted user id", collectStringOption, [])
65
+ .option("--channel <id>", "Allowlisted channel id", collectStringOption, [])
66
+ .option("--guild <id>", "Allowlisted guild id", collectStringOption, [])
67
+ .option("--chat <id>", "Allowlisted chat id", collectStringOption, [])
68
+ .option("--group <id>", "Allowlisted group id", collectStringOption, [])
69
+ .option("--recursive", "Update the target project and its descendants")
70
+ .action(async (surface, inputPath, options) => {
71
+ const surfaceName = resolvePortableSurfaceName(surface);
72
+ const result = await handlers.setProjectSurfaceAccess({
73
+ channels: options.channel,
74
+ chats: options.chat,
75
+ groups: options.group,
76
+ guilds: options.guild,
77
+ mode: options.mode,
78
+ path: inputPath,
79
+ recursive: options.recursive,
80
+ surface: surfaceName,
81
+ users: options.user
82
+ });
83
+ for (const filePath of result.updatedFiles) {
84
+ streams.stdout(`updated ${filePath}`);
85
+ }
86
+ });
87
+ surfaceCommand
88
+ .command("show")
89
+ .argument("[path]", "Project directory or Spawnfile path", process.cwd())
90
+ .option("--recursive", "Show descendant agent surfaces too")
91
+ .action(async (inputPath, options) => {
92
+ const result = await handlers.showProjectSurfaces({
93
+ path: inputPath,
94
+ recursive: options.recursive
95
+ });
96
+ emitSurfaceSummaries(streams, result.entries);
97
+ });
98
+ };
@@ -5,6 +5,7 @@ export const resolveAgentSurfaces = (surfaces) => {
5
5
  }
6
6
  const resolved = {};
7
7
  if (surfaces.discord) {
8
+ const identity = surfaces.discord.identity;
8
9
  resolved.discord = {
9
10
  ...(surfaces.discord.access
10
11
  ? {
@@ -16,10 +17,41 @@ export const resolveAgentSurfaces = (surfaces) => {
16
17
  }
17
18
  }
18
19
  : {}),
19
- botTokenSecret: surfaces.discord.bot_token_secret ?? DEFAULT_DISCORD_BOT_TOKEN_SECRET
20
+ botTokenSecret: surfaces.discord.bot_token_secret ?? DEFAULT_DISCORD_BOT_TOKEN_SECRET,
21
+ ...(identity ? { identity: { userId: identity.user_id } } : {})
20
22
  };
21
23
  }
24
+ if (surfaces.moltnet) {
25
+ resolved.moltnet = surfaces.moltnet.map((attachment) => ({
26
+ ...(attachment.dms
27
+ ? {
28
+ dms: {
29
+ enabled: attachment.dms.enabled,
30
+ ...(attachment.dms.read ? { read: attachment.dms.read } : {}),
31
+ ...(attachment.dms.reply ? { reply: attachment.dms.reply } : {})
32
+ }
33
+ }
34
+ : {}),
35
+ memberId: null,
36
+ network: attachment.network,
37
+ ...(attachment.rooms
38
+ ? {
39
+ rooms: Object.fromEntries(Object.entries(attachment.rooms)
40
+ .sort(([left], [right]) => left.localeCompare(right))
41
+ .map(([roomId, behavior]) => [
42
+ roomId,
43
+ {
44
+ ...(behavior.read ? { read: behavior.read } : {}),
45
+ ...(behavior.reply ? { reply: behavior.reply } : {})
46
+ }
47
+ ]))
48
+ }
49
+ : {}),
50
+ teamSource: null
51
+ }));
52
+ }
22
53
  if (surfaces.telegram) {
54
+ const identity = surfaces.telegram.identity;
23
55
  resolved.telegram = {
24
56
  ...(surfaces.telegram.access
25
57
  ? {
@@ -30,21 +62,34 @@ export const resolveAgentSurfaces = (surfaces) => {
30
62
  }
31
63
  }
32
64
  : {}),
33
- botTokenSecret: surfaces.telegram.bot_token_secret ?? DEFAULT_TELEGRAM_BOT_TOKEN_SECRET
65
+ botTokenSecret: surfaces.telegram.bot_token_secret ?? DEFAULT_TELEGRAM_BOT_TOKEN_SECRET,
66
+ ...(identity
67
+ ? {
68
+ identity: {
69
+ ...(identity.user_id ? { userId: identity.user_id } : {}),
70
+ ...(identity.username ? { username: identity.username } : {})
71
+ }
72
+ }
73
+ : {})
34
74
  };
35
75
  }
36
76
  if (surfaces.whatsapp) {
77
+ const identity = surfaces.whatsapp.identity;
37
78
  resolved.whatsapp = surfaces.whatsapp.access
38
79
  ? {
39
80
  access: {
40
81
  groups: [...(surfaces.whatsapp.access.groups ?? [])],
41
82
  mode: surfaces.whatsapp.access.mode ?? "allowlist",
42
83
  users: [...(surfaces.whatsapp.access.users ?? [])]
43
- }
84
+ },
85
+ ...(identity ? { identity: { phone: identity.phone } } : {})
44
86
  }
45
- : {};
87
+ : {
88
+ ...(identity ? { identity: { phone: identity.phone } } : {})
89
+ };
46
90
  }
47
91
  if (surfaces.slack) {
92
+ const identity = surfaces.slack.identity;
48
93
  resolved.slack = {
49
94
  ...(surfaces.slack.access
50
95
  ? {
@@ -56,7 +101,8 @@ export const resolveAgentSurfaces = (surfaces) => {
56
101
  }
57
102
  : {}),
58
103
  appTokenSecret: surfaces.slack.app_token_secret ?? DEFAULT_SLACK_APP_TOKEN_SECRET,
59
- botTokenSecret: surfaces.slack.bot_token_secret ?? DEFAULT_SLACK_BOT_TOKEN_SECRET
104
+ botTokenSecret: surfaces.slack.bot_token_secret ?? DEFAULT_SLACK_BOT_TOKEN_SECRET,
105
+ ...(identity ? { identity: { userId: identity.user_id } } : {})
60
106
  };
61
107
  }
62
108
  return Object.keys(resolved).length > 0 ? resolved : undefined;
@@ -1,6 +1,5 @@
1
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";
2
+ import { isAgentManifest, isTeamManifest, loadManifest, mergeExecution } from "../manifest/index.js";
4
3
  import { SpawnfileError } from "../shared/index.js";
5
4
  import { getAgentFingerprint, getMcpNames, getTeamFingerprint, validateEffectiveSkillRequirements } from "./compilePlanHelpers.js";
6
5
  import { resolveAgentSurfaces } from "./agentSurfaces.js";
@@ -9,30 +8,16 @@ import { loadResolvedDocuments, mergeResolvedSkills, loadResolvedSkills, mergeSh
9
8
  import { assertRuntimeSupportsExecutionModelAuth } from "./modelAuth.js";
10
9
  import { assertRuntimeSupportsAgentSurfaces } from "./surfaceSupport.js";
11
10
  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
- };
11
+ import { resolvePlanMoltnetAttachments } from "./moltnetResolution.js";
12
+ import { normalizeDescription, resolveDescription, resolveRuntime } from "./buildCompilePlanRuntime.js";
13
+ import { resolveTeamExternalIds, resolveTeamNetworks, validateTeamNetworkRooms } from "./buildCompilePlanTeams.js";
30
14
  export const buildCompilePlan = async (inputPath) => {
31
15
  const rootManifestPath = getCanonicalManifestPath(getManifestPath(inputPath));
32
16
  const loadCache = new Map();
33
17
  const nodeCache = new Map();
34
18
  const fingerprintCache = new Map();
35
19
  const edges = [];
20
+ const memberships = new Map();
36
21
  const visitStack = [];
37
22
  const getLoadedManifest = (manifestPath) => {
38
23
  const canonicalPath = getCanonicalManifestPath(manifestPath);
@@ -70,10 +55,13 @@ export const buildCompilePlan = async (inputPath) => {
70
55
  const localSkills = await loadResolvedSkills(canonicalPath, loadedManifest.manifest.skills);
71
56
  const skills = mergeResolvedSkills(inheritedSkills, localSkills);
72
57
  validateEffectiveSkillRequirements(loadedManifest.manifest.name, getMcpNames(sharedSurface.mcpServers), skills);
58
+ const docs = await loadResolvedDocuments(canonicalPath, loadedManifest.manifest.docs);
73
59
  const candidate = {
74
- docs: await loadResolvedDocuments(canonicalPath, loadedManifest.manifest.docs),
60
+ description: resolveDescription(loadedManifest.manifest.description, docs),
61
+ docs,
75
62
  env: sharedSurface.env,
76
63
  execution,
64
+ expose: loadedManifest.manifest.expose ?? false,
77
65
  kind: "agent",
78
66
  mcpServers: sharedSurface.mcpServers,
79
67
  name: loadedManifest.manifest.name,
@@ -135,31 +123,29 @@ export const buildCompilePlan = async (inputPath) => {
135
123
  }
136
124
  const sharedSkills = await loadResolvedSkills(canonicalPath, loadedManifest.manifest.shared?.skills);
137
125
  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);
126
+ const manifest = loadedManifest.manifest;
127
+ const resolvedExternal = resolveTeamExternalIds(manifest);
128
+ const docs = await loadResolvedDocuments(canonicalPath, manifest.docs);
144
129
  const candidate = {
145
- docs: await loadResolvedDocuments(canonicalPath, loadedManifest.manifest.docs),
130
+ description: manifest.description ? normalizeDescription(manifest.description) : "",
131
+ docs,
132
+ external: resolvedExternal,
133
+ externalExplicit: manifest.external !== undefined,
146
134
  kind: "team",
135
+ lead: manifest.lead ?? null,
147
136
  members: [],
148
- name: loadedManifest.manifest.name,
149
- policyMode: loadedManifest.manifest.policy?.mode ?? null,
150
- policyOnDegrade: loadedManifest.manifest.policy?.on_degrade ?? null,
137
+ mode: manifest.mode,
138
+ name: manifest.name,
139
+ networks: resolveTeamNetworks(manifest),
140
+ policyMode: manifest.policy?.mode ?? null,
141
+ policyOnDegrade: manifest.policy?.on_degrade ?? null,
151
142
  shared: {
152
- env: loadedManifest.manifest.shared?.env ?? {},
153
- mcpServers: loadedManifest.manifest.shared?.mcp_servers ?? [],
154
- secrets: loadedManifest.manifest.shared?.secrets ?? [],
143
+ env: manifest.shared?.env ?? {},
144
+ mcpServers: manifest.shared?.mcp_servers ?? [],
145
+ secrets: manifest.shared?.secrets ?? [],
155
146
  skills: sharedSkills
156
147
  },
157
148
  source: canonicalPath,
158
- structure: {
159
- external: resolvedExternal,
160
- leader: structure.leader ?? null,
161
- mode: structure.mode
162
- }
163
149
  };
164
150
  const fingerprint = getTeamFingerprint(candidate);
165
151
  const existingFingerprint = fingerprintCache.get(canonicalPath);
@@ -195,6 +181,12 @@ export const buildCompilePlan = async (inputPath) => {
195
181
  nodeSource: resolvedAgent.source,
196
182
  runtimeName: resolvedAgent.runtime.name
197
183
  };
184
+ memberships.set(`${canonicalPath}::${member.id}::${resolvedAgent.source}`, {
185
+ agentSource: resolvedAgent.source,
186
+ memberId: member.id,
187
+ teamName: candidate.name,
188
+ teamSource: canonicalPath
189
+ });
198
190
  }
199
191
  else {
200
192
  const resolvedTeam = await visitTeam(childManifestPath);
@@ -213,6 +205,7 @@ export const buildCompilePlan = async (inputPath) => {
213
205
  to: resolvedMember.nodeSource
214
206
  });
215
207
  }
208
+ validateTeamNetworkRooms(candidate);
216
209
  visitStack.pop();
217
210
  return candidate;
218
211
  };
@@ -249,10 +242,13 @@ export const buildCompilePlan = async (inputPath) => {
249
242
  groups[node.runtimeName] = group;
250
243
  return groups;
251
244
  }, {});
252
- return {
245
+ const compilePlan = {
253
246
  edges: compilePlanEdges,
247
+ memberships: [...memberships.values()].sort((left, right) => `${left.agentSource}:${left.teamSource}:${left.memberId}`.localeCompare(`${right.agentSource}:${right.teamSource}:${right.memberId}`)),
254
248
  nodes: compilePlanNodes,
255
249
  root: rootManifestPath,
256
250
  runtimes
257
251
  };
252
+ resolvePlanMoltnetAttachments(compilePlan);
253
+ return compilePlan;
258
254
  };
@@ -0,0 +1,14 @@
1
+ import { type AgentManifest, type ExecutionBlock, type SharedSurface } from "../manifest/index.js";
2
+ import type { ResolvedDocument, ResolvedRuntime } from "./types.js";
3
+ export interface AgentVisitContext {
4
+ inheritedExecution?: ExecutionBlock;
5
+ inheritedShared?: {
6
+ manifestPath: string;
7
+ surface: SharedSurface | undefined;
8
+ };
9
+ inheritedRuntime?: ResolvedRuntime;
10
+ isSubagent: boolean;
11
+ }
12
+ export declare const resolveRuntime: (manifest: AgentManifest, context: AgentVisitContext) => Promise<ResolvedRuntime>;
13
+ export declare const normalizeDescription: (description: string) => string;
14
+ export declare const resolveDescription: (description: string | undefined, docs: ResolvedDocument[]) => string;
@@ -0,0 +1,39 @@
1
+ import { normalizeRuntimeBinding } from "../manifest/index.js";
2
+ import { assertRuntimeCanCompile } from "../runtime/index.js";
3
+ import { SpawnfileError } from "../shared/index.js";
4
+ export const resolveRuntime = async (manifest, context) => {
5
+ const localRuntime = normalizeRuntimeBinding(manifest.runtime);
6
+ if (context.isSubagent) {
7
+ if (!context.inheritedRuntime) {
8
+ throw new SpawnfileError("runtime_error", `Subagent ${manifest.name} is missing inherited runtime context`);
9
+ }
10
+ if (localRuntime &&
11
+ localRuntime.name !== context.inheritedRuntime.name) {
12
+ throw new SpawnfileError("runtime_error", `Subagent ${manifest.name} must match parent runtime`);
13
+ }
14
+ return context.inheritedRuntime;
15
+ }
16
+ if (!localRuntime) {
17
+ throw new SpawnfileError("runtime_error", `Agent ${manifest.name} does not declare a runtime`);
18
+ }
19
+ await assertRuntimeCanCompile(localRuntime.name);
20
+ return localRuntime;
21
+ };
22
+ export const normalizeDescription = (description) => description.replace(/\s+/g, " ").trim();
23
+ const deriveDescriptionFromDocs = (docs) => {
24
+ const identity = docs.find((doc) => doc.role === "identity")?.content;
25
+ if (!identity) {
26
+ return "";
27
+ }
28
+ const paragraph = identity
29
+ .split(/\n\s*\n/)
30
+ .map((block) => normalizeDescription(block))
31
+ .find((block) => block.length > 0 && !/^#{1,6}\s+/.test(block));
32
+ if (!paragraph) {
33
+ return "";
34
+ }
35
+ return paragraph.length > 200 ? paragraph.slice(0, 200).trimEnd() : paragraph;
36
+ };
37
+ export const resolveDescription = (description, docs) => description !== undefined
38
+ ? normalizeDescription(description)
39
+ : deriveDescriptionFromDocs(docs);
@@ -0,0 +1,5 @@
1
+ import type { TeamManifest } from "../manifest/index.js";
2
+ import type { ResolvedTeamNetwork, ResolvedTeamNode } from "./types.js";
3
+ export declare const resolveTeamExternalIds: (manifest: TeamManifest) => string[];
4
+ export declare const resolveTeamNetworks: (manifest: TeamManifest) => ResolvedTeamNetwork[];
5
+ export declare const validateTeamNetworkRooms: (teamNode: ResolvedTeamNode) => void;
@@ -0,0 +1,38 @@
1
+ import { SpawnfileError } from "../shared/index.js";
2
+ export const resolveTeamExternalIds = (manifest) => {
3
+ const memberIds = manifest.members.map((member) => member.id);
4
+ if (manifest.lead && !memberIds.includes(manifest.lead)) {
5
+ throw new SpawnfileError("validation_error", `Team ${manifest.name} lead references unknown member ${manifest.lead}`);
6
+ }
7
+ for (const externalMemberId of manifest.external ?? []) {
8
+ if (!memberIds.includes(externalMemberId)) {
9
+ throw new SpawnfileError("validation_error", `Team ${manifest.name} external representative references unknown member ${externalMemberId}`);
10
+ }
11
+ }
12
+ return manifest.external
13
+ ?? (manifest.mode === "hierarchical" && manifest.lead
14
+ ? [manifest.lead]
15
+ : memberIds);
16
+ };
17
+ export const resolveTeamNetworks = (manifest) => (manifest.networks ?? []).map((network) => ({
18
+ expose: network.expose ?? false,
19
+ id: network.id,
20
+ name: network.name ?? network.id,
21
+ provider: network.provider,
22
+ rooms: network.rooms.map((room) => ({
23
+ id: room.id,
24
+ members: [...room.members]
25
+ }))
26
+ }));
27
+ export const validateTeamNetworkRooms = (teamNode) => {
28
+ for (const network of teamNode.networks ?? []) {
29
+ for (const room of network.rooms) {
30
+ for (const roomMemberId of room.members) {
31
+ const resolvedMember = teamNode.members.find((member) => member.id === roomMemberId);
32
+ if (!resolvedMember) {
33
+ throw new SpawnfileError("validation_error", `Team ${teamNode.name} Moltnet room ${room.id} references unknown member ${roomMemberId}`);
34
+ }
35
+ }
36
+ }
37
+ }
38
+ };
@@ -25,7 +25,10 @@ export const getAgentFingerprint = (node) => stableStringify({
25
25
  });
26
26
  export const getTeamFingerprint = (node) => stableStringify({
27
27
  members: node.members,
28
- structure: node.structure,
28
+ mode: node.mode,
29
+ lead: node.lead,
30
+ external: node.external,
31
+ networks: node.networks ?? [],
29
32
  shared: {
30
33
  env: node.shared.env,
31
34
  mcpServers: node.shared.mcpServers,