spawnfile 0.1.1 → 0.1.3

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 (102) hide show
  1. package/README.md +80 -396
  2. package/dist/cli/index.js +0 -0
  3. package/dist/cli/modelCommands.d.ts +3 -0
  4. package/dist/cli/modelCommands.js +68 -0
  5. package/dist/cli/runCli.d.ts +23 -2
  6. package/dist/cli/runCli.js +78 -122
  7. package/dist/cli/runtimeCommands.d.ts +3 -0
  8. package/dist/cli/runtimeCommands.js +20 -0
  9. package/dist/cli/surfaceCommands.d.ts +3 -0
  10. package/dist/cli/surfaceCommands.js +98 -0
  11. package/dist/cli/viewCommand.d.ts +3 -0
  12. package/dist/cli/viewCommand.js +87 -0
  13. package/dist/compiler/agentSurfaces.js +51 -5
  14. package/dist/compiler/buildCompilePlan.js +38 -40
  15. package/dist/compiler/buildCompilePlanRuntime.d.ts +14 -0
  16. package/dist/compiler/buildCompilePlanRuntime.js +39 -0
  17. package/dist/compiler/buildCompilePlanTeams.d.ts +5 -0
  18. package/dist/compiler/buildCompilePlanTeams.js +38 -0
  19. package/dist/compiler/compilePlanHelpers.js +4 -1
  20. package/dist/compiler/compileProject.js +62 -13
  21. package/dist/compiler/compileProjectSupport.d.ts +17 -0
  22. package/dist/compiler/compileProjectSupport.js +136 -0
  23. package/dist/compiler/containerArtifacts.d.ts +6 -1
  24. package/dist/compiler/containerArtifacts.js +26 -4
  25. package/dist/compiler/containerArtifactsPlans.js +16 -1
  26. package/dist/compiler/containerArtifactsRender.d.ts +4 -2
  27. package/dist/compiler/containerArtifactsRender.js +21 -126
  28. package/dist/compiler/containerArtifactsTypes.d.ts +7 -0
  29. package/dist/compiler/containerEntrypointRender.d.ts +12 -0
  30. package/dist/compiler/containerEntrypointRender.js +186 -0
  31. package/dist/compiler/index.d.ts +4 -0
  32. package/dist/compiler/index.js +4 -0
  33. package/dist/compiler/interactiveSurfaceScopes.d.ts +2 -0
  34. package/dist/compiler/interactiveSurfaceScopes.js +21 -0
  35. package/dist/compiler/moltnetArtifacts.d.ts +27 -0
  36. package/dist/compiler/moltnetArtifacts.js +208 -0
  37. package/dist/compiler/moltnetBinaries.d.ts +4 -0
  38. package/dist/compiler/moltnetBinaries.js +103 -0
  39. package/dist/compiler/moltnetClientConfig.d.ts +11 -0
  40. package/dist/compiler/moltnetClientConfig.js +89 -0
  41. package/dist/compiler/moltnetRepresentativeResolution.d.ts +16 -0
  42. package/dist/compiler/moltnetRepresentativeResolution.js +86 -0
  43. package/dist/compiler/moltnetResolution.d.ts +3 -0
  44. package/dist/compiler/moltnetResolution.js +182 -0
  45. package/dist/compiler/moltnetRoomMemberships.d.ts +3 -0
  46. package/dist/compiler/moltnetRoomMemberships.js +140 -0
  47. package/dist/compiler/runProject.js +1 -1
  48. package/dist/compiler/surfaceDefinitions.d.ts +55 -0
  49. package/dist/compiler/surfaceDefinitions.js +204 -0
  50. package/dist/compiler/teamContextHelpers.d.ts +18 -0
  51. package/dist/compiler/teamContextHelpers.js +112 -0
  52. package/dist/compiler/teamContextSupport.d.ts +4 -0
  53. package/dist/compiler/teamContextSupport.js +264 -0
  54. package/dist/compiler/teamContextSupport.testHelpers.d.ts +16 -0
  55. package/dist/compiler/teamContextSupport.testHelpers.js +68 -0
  56. package/dist/compiler/teamContextTypes.d.ts +28 -0
  57. package/dist/compiler/teamContextTypes.js +1 -0
  58. package/dist/compiler/teamRoster.d.ts +12 -0
  59. package/dist/compiler/teamRoster.js +48 -0
  60. package/dist/compiler/teamRosterEntries.d.ts +13 -0
  61. package/dist/compiler/teamRosterEntries.js +230 -0
  62. package/dist/compiler/teamRosterTypes.d.ts +45 -0
  63. package/dist/compiler/teamRosterTypes.js +1 -0
  64. package/dist/compiler/types.d.ts +90 -6
  65. package/dist/compiler/updateProjectRuntime.d.ts +9 -0
  66. package/dist/compiler/updateProjectRuntime.js +67 -0
  67. package/dist/compiler/updateProjectSurfaces.d.ts +8 -0
  68. package/dist/compiler/updateProjectSurfaces.js +106 -0
  69. package/dist/compiler/view/buildOrganizationView.d.ts +2 -0
  70. package/dist/compiler/view/buildOrganizationView.js +180 -0
  71. package/dist/compiler/view/index.d.ts +4 -0
  72. package/dist/compiler/view/index.js +4 -0
  73. package/dist/compiler/view/renderNetworks.d.ts +2 -0
  74. package/dist/compiler/view/renderNetworks.js +93 -0
  75. package/dist/compiler/view/renderTree.d.ts +2 -0
  76. package/dist/compiler/view/renderTree.js +59 -0
  77. package/dist/compiler/view/sourcePaths.d.ts +2 -0
  78. package/dist/compiler/view/sourcePaths.js +19 -0
  79. package/dist/compiler/view/types.d.ts +80 -0
  80. package/dist/compiler/view/types.js +1 -0
  81. package/dist/manifest/loadManifest.js +4 -4
  82. package/dist/manifest/renderSpawnfile.js +74 -8
  83. package/dist/manifest/scaffold.js +1 -3
  84. package/dist/manifest/schemas.d.ts +227 -17
  85. package/dist/manifest/schemas.js +62 -20
  86. package/dist/manifest/surfaceSchemas.d.ts +154 -0
  87. package/dist/manifest/surfaceSchemas.js +77 -5
  88. package/dist/runtime/common.js +3 -0
  89. package/dist/runtime/openclaw/adapter.js +38 -5
  90. package/dist/runtime/openclaw/moltnet.d.ts +12 -0
  91. package/dist/runtime/openclaw/moltnet.js +124 -0
  92. package/dist/runtime/openclaw/surfaces.js +3 -0
  93. package/dist/runtime/picoclaw/adapter.js +27 -8
  94. package/dist/runtime/picoclaw/pico.d.ts +2 -0
  95. package/dist/runtime/picoclaw/pico.js +2 -0
  96. package/dist/runtime/picoclaw/surfaces.js +11 -0
  97. package/dist/runtime/tinyclaw/adapter.js +22 -8
  98. package/dist/runtime/tinyclaw/runAuth.js +28 -1
  99. package/dist/runtime/tinyclaw/surfaces.js +8 -0
  100. package/dist/runtime/types.d.ts +11 -0
  101. package/package.json +5 -3
  102. package/runtimes.yaml +4 -4
@@ -0,0 +1,182 @@
1
+ import { SpawnfileError } from "../shared/index.js";
2
+ import { resolveMoltnetRoomMemberships } from "./moltnetRoomMemberships.js";
3
+ import { resolveMoltnetAttachments } from "./moltnetRepresentativeResolution.js";
4
+ export { resolveMoltnetAttachments, resolveTeamRepresentatives } from "./moltnetRepresentativeResolution.js";
5
+ const findTeamBySource = (plan, source) => {
6
+ const node = plan.nodes.find((entry) => entry.kind === "team" && entry.value.source === source);
7
+ if (!node || node.value.kind !== "team") {
8
+ throw new SpawnfileError("compile_error", `Unable to find team node at ${source}`);
9
+ }
10
+ return node.value;
11
+ };
12
+ const hasMoltnetIntent = (plan) => plan.nodes.some((node) => {
13
+ if (node.value.kind === "team") {
14
+ return (node.value.networks?.length ?? 0) > 0;
15
+ }
16
+ return (node.value.surfaces?.moltnet?.length ?? 0) > 0;
17
+ });
18
+ const validateGlobalMemberIds = (plan) => {
19
+ if (!hasMoltnetIntent(plan)) {
20
+ return;
21
+ }
22
+ const seen = new Map();
23
+ const uniqueContexts = new Map((plan.memberships ?? []).map((context) => [
24
+ `${context.teamSource}::${context.memberId}::${context.agentSource}`,
25
+ context
26
+ ]));
27
+ for (const context of uniqueContexts.values()) {
28
+ const previous = seen.get(context.memberId);
29
+ const label = `${context.teamName} (${context.teamSource}) member ${context.memberId}`;
30
+ if (previous && previous !== label) {
31
+ throw new SpawnfileError("validation_error", `Moltnet member_id ${context.memberId} is declared by multiple direct agent member slots: ${previous}; ${label}`);
32
+ }
33
+ seen.set(context.memberId, label);
34
+ }
35
+ };
36
+ const getRoomMemberships = (plan) => {
37
+ const memberships = plan.moltnetRoomMemberships ?? resolveMoltnetRoomMemberships(plan);
38
+ plan.moltnetRoomMemberships = memberships;
39
+ return memberships;
40
+ };
41
+ const synthesizeRepresentativeAttachments = (memberships) => memberships
42
+ .filter((membership) => membership.representedSlot !== undefined)
43
+ .map((membership) => ({
44
+ contextRooms: {
45
+ [membership.declaringTeamSource]: [membership.roomId]
46
+ },
47
+ memberId: membership.concreteMemberId,
48
+ network: membership.networkId,
49
+ rooms: {
50
+ [membership.roomId]: {}
51
+ },
52
+ teamSource: membership.declaringTeamSource
53
+ }));
54
+ const roomPolicyKey = (policy) => JSON.stringify(policy ?? {});
55
+ const hasRoomPolicy = (policy) => policy.read !== undefined || policy.reply !== undefined;
56
+ const mergeAttachment = (target, next, nodeName) => {
57
+ if (target.dms &&
58
+ next.dms &&
59
+ roomPolicyKey(target.dms) !== roomPolicyKey(next.dms)) {
60
+ throw new SpawnfileError("validation_error", `Agent ${nodeName} declares incompatible Moltnet dms for ${next.network}/${next.memberId ?? "unknown"}`);
61
+ }
62
+ target.dms ??= next.dms ? { ...next.dms } : undefined;
63
+ target.teamSource ??= next.teamSource;
64
+ target.rooms ??= {};
65
+ for (const [roomId, policy] of Object.entries(next.rooms ?? {})) {
66
+ const existingPolicy = target.rooms[roomId];
67
+ const existingHasPolicy = existingPolicy ? hasRoomPolicy(existingPolicy) : false;
68
+ const nextHasPolicy = hasRoomPolicy(policy);
69
+ if (existingHasPolicy &&
70
+ nextHasPolicy &&
71
+ roomPolicyKey(existingPolicy) !== roomPolicyKey(policy)) {
72
+ throw new SpawnfileError("validation_error", `Agent ${nodeName} declares incompatible Moltnet room policy for ${next.network}/${next.memberId ?? "unknown"} room ${roomId}`);
73
+ }
74
+ target.rooms[roomId] = existingPolicy && existingHasPolicy && !nextHasPolicy
75
+ ? { ...existingPolicy }
76
+ : { ...policy };
77
+ }
78
+ if (next.contextRooms) {
79
+ target.contextRooms ??= {};
80
+ for (const [teamSource, roomIds] of Object.entries(next.contextRooms)) {
81
+ target.contextRooms[teamSource] = [
82
+ ...new Set([...(target.contextRooms[teamSource] ?? []), ...roomIds])
83
+ ].sort();
84
+ }
85
+ }
86
+ else if (next.teamSource) {
87
+ target.contextRooms ??= {};
88
+ target.contextRooms[next.teamSource] = [
89
+ ...new Set([
90
+ ...(target.contextRooms[next.teamSource] ?? []),
91
+ ...Object.keys(next.rooms ?? {})
92
+ ])
93
+ ].sort();
94
+ }
95
+ };
96
+ const mergeAgentAttachments = (agentNode, attachments) => {
97
+ const merged = new Map();
98
+ for (const attachment of attachments) {
99
+ const key = `${attachment.network}::${attachment.memberId ?? ""}`;
100
+ const existing = merged.get(key);
101
+ if (existing) {
102
+ mergeAttachment(existing, attachment, agentNode.name);
103
+ continue;
104
+ }
105
+ merged.set(key, {
106
+ contextRooms: attachment.contextRooms
107
+ ? Object.fromEntries(Object.entries(attachment.contextRooms).map(([teamSource, roomIds]) => [
108
+ teamSource,
109
+ [...roomIds].sort()
110
+ ]))
111
+ : attachment.teamSource
112
+ ? { [attachment.teamSource]: Object.keys(attachment.rooms ?? {}).sort() }
113
+ : undefined,
114
+ ...(attachment.dms ? { dms: { ...attachment.dms } } : {}),
115
+ memberId: attachment.memberId,
116
+ network: attachment.network,
117
+ ...(attachment.rooms
118
+ ? {
119
+ rooms: Object.fromEntries(Object.entries(attachment.rooms).map(([roomId, policy]) => [
120
+ roomId,
121
+ { ...policy }
122
+ ]))
123
+ }
124
+ : {}),
125
+ teamSource: attachment.teamSource
126
+ });
127
+ }
128
+ return [...merged.values()].sort((left, right) => `${left.network}:${left.memberId ?? ""}`.localeCompare(`${right.network}:${right.memberId ?? ""}`));
129
+ };
130
+ export const resolvePlanMoltnetAttachments = (plan) => {
131
+ validateGlobalMemberIds(plan);
132
+ const roomMemberships = getRoomMemberships(plan);
133
+ const synthesizedAttachments = synthesizeRepresentativeAttachments(roomMemberships);
134
+ const synthesizedByAgent = new Map();
135
+ for (const attachment of synthesizedAttachments) {
136
+ const representativeContext = (plan.memberships ?? []).find((context) => context.memberId === attachment.memberId);
137
+ if (!representativeContext) {
138
+ throw new SpawnfileError("validation_error", `Unable to find direct member context for synthesized Moltnet member ${attachment.memberId ?? "unknown"}`);
139
+ }
140
+ const group = synthesizedByAgent.get(representativeContext.agentSource) ?? [];
141
+ group.push(attachment);
142
+ synthesizedByAgent.set(representativeContext.agentSource, group);
143
+ }
144
+ for (const node of plan.nodes) {
145
+ if (node.value.kind !== "agent") {
146
+ continue;
147
+ }
148
+ const agentNode = node.value;
149
+ const declaredAttachments = agentNode.surfaces?.moltnet;
150
+ const directContexts = (plan.memberships ?? []).filter((context) => context.agentSource === agentNode.source);
151
+ const resolvedAttachments = [];
152
+ if (declaredAttachments && directContexts.length === 0) {
153
+ throw new SpawnfileError("validation_error", `Agent ${agentNode.name} declares Moltnet surfaces but is not attached to a team network`);
154
+ }
155
+ for (const context of directContexts) {
156
+ if (!declaredAttachments) {
157
+ continue;
158
+ }
159
+ const teamNode = findTeamBySource(plan, context.teamSource);
160
+ const resolved = resolveMoltnetAttachments(declaredAttachments, {
161
+ memberId: context.memberId,
162
+ networks: teamNode.networks ?? [],
163
+ teamName: context.teamName,
164
+ teamSource: context.teamSource
165
+ }, agentNode.name);
166
+ resolvedAttachments.push(...(resolved ?? []));
167
+ }
168
+ resolvedAttachments.push(...(synthesizedByAgent.get(agentNode.source) ?? []));
169
+ if (resolvedAttachments.length > 0) {
170
+ agentNode.surfaces = {
171
+ ...agentNode.surfaces,
172
+ moltnet: mergeAgentAttachments(agentNode, resolvedAttachments)
173
+ };
174
+ }
175
+ else if (agentNode.surfaces?.moltnet) {
176
+ agentNode.surfaces = {
177
+ ...agentNode.surfaces,
178
+ moltnet: undefined
179
+ };
180
+ }
181
+ }
182
+ };
@@ -0,0 +1,3 @@
1
+ import type { CompilePlan, ResolvedMoltnetRoomMembership, ResolvedTeamNetworkRoom, ResolvedTeamNode } from "./types.js";
2
+ export declare const listConcreteMoltnetRoomMemberIds: (plan: CompilePlan, teamNode: ResolvedTeamNode, networkId: string, room: ResolvedTeamNetworkRoom, memberships?: ResolvedMoltnetRoomMembership[] | undefined) => string[];
3
+ export declare const resolveMoltnetRoomMemberships: (plan: CompilePlan) => ResolvedMoltnetRoomMembership[];
@@ -0,0 +1,140 @@
1
+ import { SpawnfileError } from "../shared/index.js";
2
+ import { resolveTeamRepresentatives } from "./moltnetRepresentativeResolution.js";
3
+ const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
4
+ const findAgentBySource = (plan, source) => {
5
+ const node = plan.nodes.find((entry) => entry.kind === "agent" && entry.value.source === source);
6
+ if (!node || node.value.kind !== "agent") {
7
+ throw new SpawnfileError("compile_error", `Unable to find agent node at ${source}`);
8
+ }
9
+ return node.value;
10
+ };
11
+ const findTeamBySource = (plan, source) => {
12
+ const node = plan.nodes.find((entry) => entry.kind === "team" && entry.value.source === source);
13
+ if (!node || node.value.kind !== "team") {
14
+ throw new SpawnfileError("compile_error", `Unable to find team node at ${source}`);
15
+ }
16
+ return node.value;
17
+ };
18
+ const clonePolicy = (policy) => ({
19
+ ...(policy.read ? { read: policy.read } : {}),
20
+ ...(policy.reply ? { reply: policy.reply } : {})
21
+ });
22
+ const findDirectRoomPolicy = (plan, teamNode, agentSource, memberId, networkId, roomId) => {
23
+ const agentNode = findAgentBySource(plan, agentSource);
24
+ for (const attachment of agentNode.surfaces?.moltnet ?? []) {
25
+ if (attachment.network !== networkId) {
26
+ continue;
27
+ }
28
+ if (attachment.teamSource && attachment.teamSource !== teamNode.source) {
29
+ continue;
30
+ }
31
+ if (attachment.memberId && attachment.memberId !== memberId) {
32
+ continue;
33
+ }
34
+ if (!attachment.rooms || !hasOwn(attachment.rooms, roomId)) {
35
+ continue;
36
+ }
37
+ return clonePolicy(attachment.rooms[roomId] ?? {});
38
+ }
39
+ return undefined;
40
+ };
41
+ const compareRoomMemberships = (left, right) => [
42
+ left.declaringTeamSource.localeCompare(right.declaringTeamSource),
43
+ left.networkId.localeCompare(right.networkId),
44
+ left.roomId.localeCompare(right.roomId),
45
+ left.declaredSlot.localeCompare(right.declaredSlot),
46
+ (left.representativePath ?? []).join("/").localeCompare((right.representativePath ?? []).join("/")),
47
+ left.concreteMemberId.localeCompare(right.concreteMemberId),
48
+ left.agentSource.localeCompare(right.agentSource)
49
+ ].find((result) => result !== 0) ?? 0;
50
+ export const listConcreteMoltnetRoomMemberIds = (plan, teamNode, networkId, room, memberships = plan.moltnetRoomMemberships) => {
51
+ if (memberships) {
52
+ return [
53
+ ...new Set(memberships
54
+ .filter((membership) => membership.declaringTeamSource === teamNode.source
55
+ && membership.networkId === networkId
56
+ && membership.roomId === room.id)
57
+ .map((membership) => membership.concreteMemberId))
58
+ ].sort();
59
+ }
60
+ const concreteMembers = [];
61
+ for (const declaredSlot of room.members) {
62
+ const member = teamNode.members.find((entry) => entry.id === declaredSlot);
63
+ if (!member) {
64
+ throw new SpawnfileError("validation_error", `Team ${teamNode.name} Moltnet room ${room.id} references unknown member ${declaredSlot}`);
65
+ }
66
+ if (member.kind === "agent") {
67
+ concreteMembers.push(member.id);
68
+ continue;
69
+ }
70
+ const childTeam = findTeamBySource(plan, member.nodeSource);
71
+ const representatives = resolveTeamRepresentatives(plan, childTeam);
72
+ if (representatives.length === 0) {
73
+ throw new SpawnfileError("validation_error", `Team ${childTeam.name} has no concrete representative for Moltnet room ${room.id} on ${teamNode.name}`);
74
+ }
75
+ concreteMembers.push(...representatives.map((representative) => representative.memberId));
76
+ }
77
+ return [...new Set(concreteMembers)].sort();
78
+ };
79
+ export const resolveMoltnetRoomMemberships = (plan) => {
80
+ const memberships = [];
81
+ for (const node of plan.nodes) {
82
+ if (node.value.kind !== "team") {
83
+ continue;
84
+ }
85
+ const teamNode = node.value;
86
+ for (const network of teamNode.networks ?? []) {
87
+ for (const room of network.rooms) {
88
+ for (const declaredSlot of room.members) {
89
+ const declaredMember = teamNode.members.find((member) => member.id === declaredSlot);
90
+ if (!declaredMember) {
91
+ throw new SpawnfileError("validation_error", `Team ${teamNode.name} Moltnet room ${room.id} references unknown member ${declaredSlot}`);
92
+ }
93
+ if (declaredMember.kind === "agent") {
94
+ const agentNode = findAgentBySource(plan, declaredMember.nodeSource);
95
+ const policy = findDirectRoomPolicy(plan, teamNode, agentNode.source, declaredSlot, network.id, room.id);
96
+ memberships.push({
97
+ agentName: agentNode.name,
98
+ agentSource: agentNode.source,
99
+ concreteMemberId: declaredSlot,
100
+ declaredSlot,
101
+ declaringTeamName: teamNode.name,
102
+ declaringTeamSource: teamNode.source,
103
+ directTeamName: teamNode.name,
104
+ directTeamSource: teamNode.source,
105
+ networkId: network.id,
106
+ ...(policy ? { policy } : {}),
107
+ roomId: room.id
108
+ });
109
+ continue;
110
+ }
111
+ const childTeam = findTeamBySource(plan, declaredMember.nodeSource);
112
+ const representatives = resolveTeamRepresentatives(plan, childTeam);
113
+ if (representatives.length === 0) {
114
+ throw new SpawnfileError("validation_error", `Team ${childTeam.name} has no concrete representative for Moltnet room ${room.id} on ${teamNode.name}`);
115
+ }
116
+ for (const representative of representatives) {
117
+ const agentNode = findAgentBySource(plan, representative.agentSource);
118
+ memberships.push({
119
+ agentName: agentNode.name,
120
+ agentSource: agentNode.source,
121
+ concreteMemberId: representative.memberId,
122
+ declaredSlot,
123
+ declaringTeamName: teamNode.name,
124
+ declaringTeamSource: teamNode.source,
125
+ directTeamName: representative.sourceTeamName,
126
+ directTeamSource: representative.sourceTeamSource,
127
+ networkId: network.id,
128
+ representedSlot: declaredSlot,
129
+ representedTeamName: childTeam.name,
130
+ representedTeamSource: childTeam.source,
131
+ representativePath: [declaredSlot, ...representative.path],
132
+ roomId: room.id
133
+ });
134
+ }
135
+ }
136
+ }
137
+ }
138
+ }
139
+ return memberships.sort(compareRoomMemberships);
140
+ };
@@ -22,7 +22,7 @@ const createDefaultContainerName = (imageTag) => {
22
22
  };
23
23
  const getImportMountTargetName = (kind) => kind === "claude-code" ? ".claude" : ".codex";
24
24
  const createGeneratedRuntimeSecret = (secretName) => {
25
- if (secretName === "OPENCLAW_GATEWAY_TOKEN") {
25
+ if (secretName === "OPENCLAW_GATEWAY_TOKEN" || secretName === "OPENCLAW_HOOKS_TOKEN") {
26
26
  return randomBytes(24).toString("hex");
27
27
  }
28
28
  return null;
@@ -0,0 +1,55 @@
1
+ import type { AgentManifest, SurfacesBlock, TeamManifest } from "../manifest/index.js";
2
+ type ProjectManifest = AgentManifest | TeamManifest;
3
+ export type SurfaceAccessMode = "allowlist" | "open" | "pairing";
4
+ export declare const PORTABLE_SURFACE_NAMES: readonly ["discord", "slack", "telegram", "whatsapp"];
5
+ export type SurfaceName = (typeof PORTABLE_SURFACE_NAMES)[number];
6
+ export interface ProjectSurfaceSecretOptions {
7
+ appTokenSecret?: string;
8
+ botTokenSecret?: string;
9
+ }
10
+ export interface AddProjectSurfaceOptions extends ProjectSurfaceSecretOptions {
11
+ path?: string;
12
+ recursive?: boolean;
13
+ surface: string;
14
+ }
15
+ export interface ProjectSurfaceAccessOptions {
16
+ channels?: string[];
17
+ chats?: string[];
18
+ groups?: string[];
19
+ guilds?: string[];
20
+ mode: SurfaceAccessMode;
21
+ path?: string;
22
+ recursive?: boolean;
23
+ surface: string;
24
+ users?: string[];
25
+ }
26
+ export interface RemoveProjectSurfaceOptions {
27
+ path?: string;
28
+ recursive?: boolean;
29
+ surface: string;
30
+ }
31
+ export interface ShowProjectSurfacesOptions {
32
+ path?: string;
33
+ recursive?: boolean;
34
+ }
35
+ export interface ProjectSurfaceSummary {
36
+ kind: "agent" | "team";
37
+ manifestPath: string;
38
+ name: string;
39
+ surfaces?: SurfacesBlock;
40
+ }
41
+ export interface ProjectSurfaceSummariesResult {
42
+ entries: ProjectSurfaceSummary[];
43
+ }
44
+ export interface UpdateProjectSurfacesResult {
45
+ updatedFiles: string[];
46
+ }
47
+ export declare const TEAM_SURFACE_COMMAND_ERROR = "spawnfile surface commands only write agent manifests; use --recursive to update descendant agents of a team project";
48
+ export declare const getRuntimeName: (runtime: AgentManifest["runtime"]) => string | undefined;
49
+ export declare const resolvePortableSurfaceName: (surface: string) => SurfaceName;
50
+ export declare const assertSurfaceMutationAllowed: (manifest: ProjectManifest, recursive: boolean) => manifest is AgentManifest;
51
+ export declare const upsertSurface: (surfaces: SurfacesBlock | undefined, options: AddProjectSurfaceOptions) => SurfacesBlock;
52
+ export declare const updateSurfaceAccess: (surfaces: SurfacesBlock | undefined, options: ProjectSurfaceAccessOptions, manifestPath: string, recursive: boolean) => SurfacesBlock | null;
53
+ export declare const removeSurface: (surfaces: SurfacesBlock | undefined, surface: string) => SurfacesBlock | undefined;
54
+ export declare const validateAgentSurfaceSupport: (manifest: AgentManifest) => void;
55
+ export {};
@@ -0,0 +1,204 @@
1
+ import { SpawnfileError } from "../shared/index.js";
2
+ import { resolveAgentSurfaces } from "./agentSurfaces.js";
3
+ import { assertRuntimeSupportsAgentSurfaces } from "./surfaceSupport.js";
4
+ export const PORTABLE_SURFACE_NAMES = ["discord", "slack", "telegram", "whatsapp"];
5
+ export const TEAM_SURFACE_COMMAND_ERROR = "spawnfile surface commands only write agent manifests; use --recursive to update descendant agents of a team project";
6
+ export const getRuntimeName = (runtime) => typeof runtime === "string" ? runtime : runtime?.name;
7
+ export const resolvePortableSurfaceName = (surface) => {
8
+ if (!PORTABLE_SURFACE_NAMES.includes(surface)) {
9
+ throw new SpawnfileError("validation_error", `Unsupported portable surface ${surface}; expected one of: ${PORTABLE_SURFACE_NAMES.join(", ")}`);
10
+ }
11
+ return surface;
12
+ };
13
+ export const assertSurfaceMutationAllowed = (manifest, recursive) => {
14
+ if (manifest.kind === "agent") {
15
+ return true;
16
+ }
17
+ if (recursive) {
18
+ return false;
19
+ }
20
+ throw new SpawnfileError("validation_error", TEAM_SURFACE_COMMAND_ERROR);
21
+ };
22
+ const normalizeEntries = (values) => {
23
+ const normalized = [...new Set((values ?? []).map((value) => value.trim()).filter(Boolean))].sort();
24
+ return normalized.length > 0 ? normalized : undefined;
25
+ };
26
+ const validateSecrets = (surface, options) => {
27
+ if (options.appTokenSecret && surface !== "slack") {
28
+ throw new SpawnfileError("validation_error", "--app-token-secret is only valid for slack surfaces");
29
+ }
30
+ if (options.botTokenSecret && !["discord", "slack", "telegram"].includes(surface)) {
31
+ throw new SpawnfileError("validation_error", `--bot-token-secret is not valid for ${surface} surfaces`);
32
+ }
33
+ };
34
+ const buildDiscordAccess = (options) => {
35
+ const users = normalizeEntries(options.users);
36
+ const guilds = normalizeEntries(options.guilds);
37
+ const channels = normalizeEntries(options.channels);
38
+ const hasAllowlistEntries = Boolean(users || guilds || channels);
39
+ if (options.mode === "allowlist" && !hasAllowlistEntries) {
40
+ throw new SpawnfileError("validation_error", "discord allowlist access requires at least one --user, --guild, or --channel");
41
+ }
42
+ if (options.mode !== "allowlist" && hasAllowlistEntries) {
43
+ throw new SpawnfileError("validation_error", "discord allowlist entries are only valid with --mode allowlist");
44
+ }
45
+ return {
46
+ ...(channels ? { channels } : {}),
47
+ ...(guilds ? { guilds } : {}),
48
+ mode: options.mode,
49
+ ...(users ? { users } : {})
50
+ };
51
+ };
52
+ const buildTelegramAccess = (options) => {
53
+ const users = normalizeEntries(options.users);
54
+ const chats = normalizeEntries(options.chats);
55
+ const hasAllowlistEntries = Boolean(users || chats);
56
+ if (options.mode === "allowlist" && !hasAllowlistEntries) {
57
+ throw new SpawnfileError("validation_error", "telegram allowlist access requires at least one --user or --chat");
58
+ }
59
+ if (options.mode !== "allowlist" && hasAllowlistEntries) {
60
+ throw new SpawnfileError("validation_error", "telegram allowlist entries are only valid with --mode allowlist");
61
+ }
62
+ return {
63
+ ...(chats ? { chats } : {}),
64
+ mode: options.mode,
65
+ ...(users ? { users } : {})
66
+ };
67
+ };
68
+ const buildWhatsAppAccess = (options) => {
69
+ const users = normalizeEntries(options.users);
70
+ const groups = normalizeEntries(options.groups);
71
+ const hasAllowlistEntries = Boolean(users || groups);
72
+ if (options.mode === "allowlist" && !hasAllowlistEntries) {
73
+ throw new SpawnfileError("validation_error", "whatsapp allowlist access requires at least one --user or --group");
74
+ }
75
+ if (options.mode !== "allowlist" && hasAllowlistEntries) {
76
+ throw new SpawnfileError("validation_error", "whatsapp allowlist entries are only valid with --mode allowlist");
77
+ }
78
+ return {
79
+ ...(groups ? { groups } : {}),
80
+ mode: options.mode,
81
+ ...(users ? { users } : {})
82
+ };
83
+ };
84
+ const buildSlackAccess = (options) => {
85
+ const users = normalizeEntries(options.users);
86
+ const channels = normalizeEntries(options.channels);
87
+ const hasAllowlistEntries = Boolean(users || channels);
88
+ if (options.mode === "allowlist" && !hasAllowlistEntries) {
89
+ throw new SpawnfileError("validation_error", "slack allowlist access requires at least one --user or --channel");
90
+ }
91
+ if (options.mode !== "allowlist" && hasAllowlistEntries) {
92
+ throw new SpawnfileError("validation_error", "slack allowlist entries are only valid with --mode allowlist");
93
+ }
94
+ return {
95
+ ...(channels ? { channels } : {}),
96
+ mode: options.mode,
97
+ ...(users ? { users } : {})
98
+ };
99
+ };
100
+ const buildSurfaceAccess = (options) => {
101
+ const surface = resolvePortableSurfaceName(options.surface);
102
+ switch (surface) {
103
+ case "discord":
104
+ return buildDiscordAccess(options);
105
+ case "telegram":
106
+ return buildTelegramAccess(options);
107
+ case "whatsapp":
108
+ return buildWhatsAppAccess(options);
109
+ case "slack":
110
+ return buildSlackAccess(options);
111
+ }
112
+ };
113
+ export const upsertSurface = (surfaces, options) => {
114
+ const surface = resolvePortableSurfaceName(options.surface);
115
+ validateSecrets(surface, options);
116
+ const nextSurfaces = { ...(surfaces ?? {}) };
117
+ switch (surface) {
118
+ case "discord":
119
+ nextSurfaces.discord = {
120
+ ...(surfaces?.discord ?? {}),
121
+ ...(options.botTokenSecret ? { bot_token_secret: options.botTokenSecret } : {})
122
+ };
123
+ break;
124
+ case "telegram":
125
+ nextSurfaces.telegram = {
126
+ ...(surfaces?.telegram ?? {}),
127
+ ...(options.botTokenSecret ? { bot_token_secret: options.botTokenSecret } : {})
128
+ };
129
+ break;
130
+ case "whatsapp":
131
+ nextSurfaces.whatsapp = {
132
+ ...(surfaces?.whatsapp ?? {})
133
+ };
134
+ break;
135
+ case "slack":
136
+ nextSurfaces.slack = {
137
+ ...(surfaces?.slack ?? {}),
138
+ ...(options.appTokenSecret ? { app_token_secret: options.appTokenSecret } : {}),
139
+ ...(options.botTokenSecret ? { bot_token_secret: options.botTokenSecret } : {})
140
+ };
141
+ break;
142
+ }
143
+ return nextSurfaces;
144
+ };
145
+ export const updateSurfaceAccess = (surfaces, options, manifestPath, recursive) => {
146
+ const surface = resolvePortableSurfaceName(options.surface);
147
+ const nextSurfaces = { ...(surfaces ?? {}) };
148
+ const access = buildSurfaceAccess({ ...options, surface });
149
+ switch (surface) {
150
+ case "discord":
151
+ if (!nextSurfaces.discord) {
152
+ if (recursive) {
153
+ return null;
154
+ }
155
+ throw new SpawnfileError("validation_error", `Surface discord is not declared in ${manifestPath}; use spawnfile surface add discord first`);
156
+ }
157
+ nextSurfaces.discord = { ...nextSurfaces.discord, access: access };
158
+ break;
159
+ case "telegram":
160
+ if (!nextSurfaces.telegram) {
161
+ if (recursive) {
162
+ return null;
163
+ }
164
+ throw new SpawnfileError("validation_error", `Surface telegram is not declared in ${manifestPath}; use spawnfile surface add telegram first`);
165
+ }
166
+ nextSurfaces.telegram = { ...nextSurfaces.telegram, access: access };
167
+ break;
168
+ case "whatsapp":
169
+ if (!nextSurfaces.whatsapp) {
170
+ if (recursive) {
171
+ return null;
172
+ }
173
+ throw new SpawnfileError("validation_error", `Surface whatsapp is not declared in ${manifestPath}; use spawnfile surface add whatsapp first`);
174
+ }
175
+ nextSurfaces.whatsapp = { ...nextSurfaces.whatsapp, access: access };
176
+ break;
177
+ case "slack":
178
+ if (!nextSurfaces.slack) {
179
+ if (recursive) {
180
+ return null;
181
+ }
182
+ throw new SpawnfileError("validation_error", `Surface slack is not declared in ${manifestPath}; use spawnfile surface add slack first`);
183
+ }
184
+ nextSurfaces.slack = { ...nextSurfaces.slack, access: access };
185
+ break;
186
+ }
187
+ return nextSurfaces;
188
+ };
189
+ export const removeSurface = (surfaces, surface) => {
190
+ const surfaceName = resolvePortableSurfaceName(surface);
191
+ if (!surfaces?.[surfaceName]) {
192
+ return surfaces;
193
+ }
194
+ const nextSurfaces = { ...surfaces };
195
+ delete nextSurfaces[surfaceName];
196
+ return Object.keys(nextSurfaces).length > 0 ? nextSurfaces : undefined;
197
+ };
198
+ export const validateAgentSurfaceSupport = (manifest) => {
199
+ const runtimeName = getRuntimeName(manifest.runtime);
200
+ if (!runtimeName) {
201
+ return;
202
+ }
203
+ assertRuntimeSupportsAgentSurfaces(runtimeName, resolveAgentSurfaces(manifest.surfaces), manifest.name);
204
+ };
@@ -0,0 +1,18 @@
1
+ import type { EmittedFile } from "../runtime/index.js";
2
+ import type { CompilePlan, ResolvedAgentNode, ResolvedMemberRef, ResolvedTeamNode } from "./types.js";
3
+ import type { AgentContext, TeamContextIndex } from "./teamContextTypes.js";
4
+ export declare const pathSafe: (value: string) => string;
5
+ export declare const shortHash: (value: string) => string;
6
+ export declare const reportKeySegment: (value: string) => string;
7
+ export declare const findTeamBySource: (plan: CompilePlan, source: string) => ResolvedTeamNode | null;
8
+ export declare const findAgentBySource: (plan: CompilePlan, source: string) => ResolvedAgentNode | null;
9
+ export declare const getSystemTeamDoc: (teamNode: ResolvedTeamNode) => string;
10
+ export declare const createContextBaseKey: (context: AgentContext) => string;
11
+ export declare const collectMoltnetBindings: (agentNode: ResolvedAgentNode, teamSource: string, memberId: string) => Array<{
12
+ network: string;
13
+ rooms: string[];
14
+ }>;
15
+ export declare const createTeamCard: (contextKey: string, member: ResolvedMemberRef, childTeam: ResolvedTeamNode, representatives: string[]) => EmittedFile;
16
+ export declare const buildContextOrientation: (index: TeamContextIndex) => string;
17
+ export declare const createVisibleTeamCards: (plan: CompilePlan, contextKey: string, teamNode: ResolvedTeamNode, visibleMembers: ResolvedMemberRef[]) => EmittedFile[];
18
+ export declare const generateContextRoster: (teamNode: ResolvedTeamNode, plan: CompilePlan, options: import("./teamRosterTypes.js").GenerateTeamRosterOptions) => import("./teamRosterTypes.js").GeneratedTeamRoster;