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.
- package/README.md +79 -396
- package/dist/cli/modelCommands.d.ts +3 -0
- package/dist/cli/modelCommands.js +68 -0
- package/dist/cli/runCli.d.ts +6 -1
- package/dist/cli/runCli.js +12 -67
- package/dist/cli/runtimeCommands.d.ts +3 -0
- package/dist/cli/runtimeCommands.js +20 -0
- package/dist/cli/surfaceCommands.d.ts +3 -0
- package/dist/cli/surfaceCommands.js +98 -0
- package/dist/compiler/agentSurfaces.js +51 -5
- package/dist/compiler/buildCompilePlan.js +36 -40
- package/dist/compiler/buildCompilePlanRuntime.d.ts +14 -0
- package/dist/compiler/buildCompilePlanRuntime.js +39 -0
- package/dist/compiler/buildCompilePlanTeams.d.ts +5 -0
- package/dist/compiler/buildCompilePlanTeams.js +38 -0
- package/dist/compiler/compilePlanHelpers.js +4 -1
- package/dist/compiler/compileProject.js +62 -13
- package/dist/compiler/compileProjectSupport.d.ts +17 -0
- package/dist/compiler/compileProjectSupport.js +136 -0
- package/dist/compiler/containerArtifacts.d.ts +6 -1
- package/dist/compiler/containerArtifacts.js +26 -4
- package/dist/compiler/containerArtifactsPlans.js +16 -1
- package/dist/compiler/containerArtifactsRender.d.ts +4 -2
- package/dist/compiler/containerArtifactsRender.js +21 -126
- package/dist/compiler/containerArtifactsTypes.d.ts +7 -0
- package/dist/compiler/containerEntrypointRender.d.ts +12 -0
- package/dist/compiler/containerEntrypointRender.js +186 -0
- package/dist/compiler/index.d.ts +2 -0
- package/dist/compiler/index.js +2 -0
- package/dist/compiler/interactiveSurfaceScopes.d.ts +2 -0
- package/dist/compiler/interactiveSurfaceScopes.js +21 -0
- package/dist/compiler/moltnetArtifacts.d.ts +27 -0
- package/dist/compiler/moltnetArtifacts.js +204 -0
- package/dist/compiler/moltnetBinaries.d.ts +4 -0
- package/dist/compiler/moltnetBinaries.js +103 -0
- package/dist/compiler/moltnetClientConfig.d.ts +11 -0
- package/dist/compiler/moltnetClientConfig.js +89 -0
- package/dist/compiler/moltnetRepresentativeResolution.d.ts +16 -0
- package/dist/compiler/moltnetRepresentativeResolution.js +86 -0
- package/dist/compiler/moltnetResolution.d.ts +3 -0
- package/dist/compiler/moltnetResolution.js +201 -0
- package/dist/compiler/runProject.js +1 -1
- package/dist/compiler/surfaceDefinitions.d.ts +55 -0
- package/dist/compiler/surfaceDefinitions.js +204 -0
- package/dist/compiler/teamContextHelpers.d.ts +18 -0
- package/dist/compiler/teamContextHelpers.js +112 -0
- package/dist/compiler/teamContextSupport.d.ts +4 -0
- package/dist/compiler/teamContextSupport.js +264 -0
- package/dist/compiler/teamContextSupport.testHelpers.d.ts +16 -0
- package/dist/compiler/teamContextSupport.testHelpers.js +68 -0
- package/dist/compiler/teamContextTypes.d.ts +28 -0
- package/dist/compiler/teamContextTypes.js +1 -0
- package/dist/compiler/teamRoster.d.ts +12 -0
- package/dist/compiler/teamRoster.js +48 -0
- package/dist/compiler/teamRosterEntries.d.ts +13 -0
- package/dist/compiler/teamRosterEntries.js +230 -0
- package/dist/compiler/teamRosterTypes.d.ts +45 -0
- package/dist/compiler/teamRosterTypes.js +1 -0
- package/dist/compiler/types.d.ts +72 -6
- package/dist/compiler/updateProjectRuntime.d.ts +9 -0
- package/dist/compiler/updateProjectRuntime.js +67 -0
- package/dist/compiler/updateProjectSurfaces.d.ts +8 -0
- package/dist/compiler/updateProjectSurfaces.js +106 -0
- package/dist/manifest/loadManifest.js +4 -4
- package/dist/manifest/renderSpawnfile.js +74 -8
- package/dist/manifest/scaffold.js +1 -3
- package/dist/manifest/schemas.d.ts +227 -17
- package/dist/manifest/schemas.js +62 -20
- package/dist/manifest/surfaceSchemas.d.ts +154 -0
- package/dist/manifest/surfaceSchemas.js +77 -5
- package/dist/runtime/common.js +3 -0
- package/dist/runtime/openclaw/adapter.js +38 -5
- package/dist/runtime/openclaw/moltnet.d.ts +12 -0
- package/dist/runtime/openclaw/moltnet.js +124 -0
- package/dist/runtime/openclaw/surfaces.js +3 -0
- package/dist/runtime/picoclaw/adapter.js +27 -8
- package/dist/runtime/picoclaw/pico.d.ts +2 -0
- package/dist/runtime/picoclaw/pico.js +2 -0
- package/dist/runtime/picoclaw/surfaces.js +11 -0
- package/dist/runtime/tinyclaw/adapter.js +22 -8
- package/dist/runtime/tinyclaw/runAuth.js +28 -1
- package/dist/runtime/tinyclaw/surfaces.js +8 -0
- package/dist/runtime/types.d.ts +11 -0
- package/package.json +4 -2
- package/runtimes.yaml +4 -4
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { resolveTeamRepresentatives } from "./moltnetResolution.js";
|
|
2
|
+
const SURFACE_ORDER = ["moltnet", "slack", "discord", "telegram", "whatsapp"];
|
|
3
|
+
const findNode = (plan, source) => plan.nodes.find((node) => node.value.source === source)?.value ?? null;
|
|
4
|
+
const findAgent = (plan, source) => {
|
|
5
|
+
const node = findNode(plan, source);
|
|
6
|
+
return node?.kind === "agent" ? node : null;
|
|
7
|
+
};
|
|
8
|
+
const findTeam = (plan, source) => {
|
|
9
|
+
const node = findNode(plan, source);
|
|
10
|
+
return node?.kind === "team" ? node : null;
|
|
11
|
+
};
|
|
12
|
+
const lookupDescription = (source, plan) => findNode(plan, source)?.description ?? "";
|
|
13
|
+
const getContextRoomIds = (contextTeamSource, attachment) => {
|
|
14
|
+
const contextRooms = attachment.contextRooms?.[contextTeamSource];
|
|
15
|
+
if (contextRooms) {
|
|
16
|
+
return [...contextRooms].sort();
|
|
17
|
+
}
|
|
18
|
+
if (attachment.teamSource === contextTeamSource) {
|
|
19
|
+
return Object.keys(attachment.rooms ?? {}).sort();
|
|
20
|
+
}
|
|
21
|
+
return [];
|
|
22
|
+
};
|
|
23
|
+
const createMoltnetAddresses = (agent, contextTeamSource, memberId) => {
|
|
24
|
+
const addresses = {};
|
|
25
|
+
for (const attachment of agent.surfaces?.moltnet ?? []) {
|
|
26
|
+
if (attachment.memberId !== memberId) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const rooms = getContextRoomIds(contextTeamSource, attachment);
|
|
30
|
+
if (rooms.length === 0 && attachment.teamSource !== contextTeamSource) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
addresses[attachment.network] = {
|
|
34
|
+
fqid: `molt://${attachment.network}/agents/${memberId}`,
|
|
35
|
+
rooms
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return addresses;
|
|
39
|
+
};
|
|
40
|
+
const createIdentityAddresses = (surfaces) => ({
|
|
41
|
+
...(surfaces?.slack?.identity
|
|
42
|
+
? { slack: { user_id: surfaces.slack.identity.userId } }
|
|
43
|
+
: {}),
|
|
44
|
+
...(surfaces?.discord?.identity
|
|
45
|
+
? { discord: { user_id: surfaces.discord.identity.userId } }
|
|
46
|
+
: {}),
|
|
47
|
+
...(surfaces?.telegram?.identity
|
|
48
|
+
? {
|
|
49
|
+
telegram: {
|
|
50
|
+
...(surfaces.telegram.identity.userId
|
|
51
|
+
? { user_id: surfaces.telegram.identity.userId }
|
|
52
|
+
: {}),
|
|
53
|
+
...(surfaces.telegram.identity.username
|
|
54
|
+
? { username: surfaces.telegram.identity.username }
|
|
55
|
+
: {})
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
: {}),
|
|
59
|
+
...(surfaces?.whatsapp?.identity
|
|
60
|
+
? { whatsapp: { phone: surfaces.whatsapp.identity.phone } }
|
|
61
|
+
: {})
|
|
62
|
+
});
|
|
63
|
+
const createConcreteEntry = (plan, agentSource, memberId, role, contextTeamSource, delegateRole) => {
|
|
64
|
+
const agent = findAgent(plan, agentSource);
|
|
65
|
+
const moltnet = agent
|
|
66
|
+
? createMoltnetAddresses(agent, contextTeamSource, memberId)
|
|
67
|
+
: {};
|
|
68
|
+
const identity = createIdentityAddresses(agent?.surfaces);
|
|
69
|
+
const addresses = {
|
|
70
|
+
...identity,
|
|
71
|
+
...(Object.keys(moltnet).length > 0 ? { moltnet } : {})
|
|
72
|
+
};
|
|
73
|
+
const surfaces = SURFACE_ORDER.filter((surface) => {
|
|
74
|
+
if (surface === "moltnet") {
|
|
75
|
+
return Object.keys(moltnet).length > 0;
|
|
76
|
+
}
|
|
77
|
+
return Boolean(agent?.surfaces?.[surface]);
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
addresses,
|
|
81
|
+
...(delegateRole ? { delegate_role: delegateRole } : {}),
|
|
82
|
+
description: agent?.description ?? "",
|
|
83
|
+
role,
|
|
84
|
+
surfaces
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
const createCardPath = (contextKey, memberId) => `.spawnfile/team-cards/${contextKey}/${memberId}.md`;
|
|
88
|
+
const createTeamEntry = (plan, teamNode, member, contextKey, contextTeamSource) => {
|
|
89
|
+
const childTeam = findTeam(plan, member.nodeSource);
|
|
90
|
+
const representatives = childTeam
|
|
91
|
+
? resolveTeamRepresentatives(plan, childTeam)
|
|
92
|
+
: [];
|
|
93
|
+
const isLead = member.id === teamNode.lead;
|
|
94
|
+
const delegateRole = isLead ? "lead" : "representative";
|
|
95
|
+
return {
|
|
96
|
+
addresses: {},
|
|
97
|
+
card: {
|
|
98
|
+
path: createCardPath(contextKey, member.id),
|
|
99
|
+
summary: lookupDescription(member.nodeSource, plan)
|
|
100
|
+
},
|
|
101
|
+
description: lookupDescription(member.nodeSource, plan),
|
|
102
|
+
...(isLead ? { is_lead: true } : {}),
|
|
103
|
+
representatives: Object.fromEntries(representatives.map((representative) => {
|
|
104
|
+
const entry = createConcreteEntry(plan, representative.agentSource, representative.memberId, delegateRole === "lead" ? "lead" : "member", contextTeamSource, delegateRole);
|
|
105
|
+
const { role: _role, ...representativeEntry } = entry;
|
|
106
|
+
return [representative.memberId, representativeEntry];
|
|
107
|
+
})),
|
|
108
|
+
role: "team",
|
|
109
|
+
surfaces: []
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
export const getVisibleTeamMembers = (teamNode, selfMemberId, delegateRole, representedSlotId) => {
|
|
113
|
+
const excluded = representedSlotId ?? selfMemberId;
|
|
114
|
+
if (teamNode.mode === "swarm") {
|
|
115
|
+
return teamNode.members.filter((member) => member.id !== excluded);
|
|
116
|
+
}
|
|
117
|
+
const hasLeadVisibility = selfMemberId === teamNode.lead || delegateRole === "lead";
|
|
118
|
+
if (hasLeadVisibility) {
|
|
119
|
+
return teamNode.members.filter((member) => member.id !== excluded);
|
|
120
|
+
}
|
|
121
|
+
const lead = teamNode.lead
|
|
122
|
+
? teamNode.members.find((member) => member.id === teamNode.lead)
|
|
123
|
+
: undefined;
|
|
124
|
+
return lead && lead.id !== excluded ? [lead] : [];
|
|
125
|
+
};
|
|
126
|
+
export const createRosterEntry = (plan, teamNode, member, options) => {
|
|
127
|
+
if (member.kind === "team") {
|
|
128
|
+
return createTeamEntry(plan, teamNode, member, options.contextKey, options.teamSource);
|
|
129
|
+
}
|
|
130
|
+
return createConcreteEntry(plan, member.nodeSource, member.id, member.id === teamNode.lead ? "lead" : "member", options.teamSource);
|
|
131
|
+
};
|
|
132
|
+
export const collectConcreteParticipants = (plan, teamNode, selfMemberId, visibleMembers, representedSlotId) => {
|
|
133
|
+
const selfMember = representedSlotId
|
|
134
|
+
? null
|
|
135
|
+
: teamNode.members.find((member) => member.id === selfMemberId && member.kind === "agent");
|
|
136
|
+
const participants = selfMember
|
|
137
|
+
? [{ agentSource: selfMember.nodeSource, id: selfMember.id }]
|
|
138
|
+
: [];
|
|
139
|
+
if (representedSlotId) {
|
|
140
|
+
const representedMember = teamNode.members.find((member) => member.id === representedSlotId);
|
|
141
|
+
if (representedMember?.kind === "team") {
|
|
142
|
+
const childTeam = findTeam(plan, representedMember.nodeSource);
|
|
143
|
+
const representative = childTeam
|
|
144
|
+
? resolveTeamRepresentatives(plan, childTeam).find((entry) => entry.memberId === selfMemberId)
|
|
145
|
+
: undefined;
|
|
146
|
+
if (representative) {
|
|
147
|
+
participants.push({
|
|
148
|
+
agentSource: representative.agentSource,
|
|
149
|
+
id: representative.memberId
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
for (const member of visibleMembers) {
|
|
155
|
+
if (member.kind === "agent") {
|
|
156
|
+
participants.push({ agentSource: member.nodeSource, id: member.id });
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const childTeam = findTeam(plan, member.nodeSource);
|
|
160
|
+
if (!childTeam) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
for (const representative of resolveTeamRepresentatives(plan, childTeam)) {
|
|
164
|
+
participants.push({
|
|
165
|
+
agentSource: representative.agentSource,
|
|
166
|
+
id: representative.memberId
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return participants;
|
|
171
|
+
};
|
|
172
|
+
const getCoordinationBindings = (plan, participant, contextTeamSource) => {
|
|
173
|
+
const agent = findAgent(plan, participant.agentSource);
|
|
174
|
+
if (!agent) {
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
const bindings = new Set();
|
|
178
|
+
for (const surface of ["slack", "discord", "telegram", "whatsapp"]) {
|
|
179
|
+
if (agent.surfaces?.[surface]) {
|
|
180
|
+
bindings.add(surface);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
for (const attachment of agent.surfaces?.moltnet ?? []) {
|
|
184
|
+
if (attachment.memberId !== participant.id) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
for (const roomId of getContextRoomIds(contextTeamSource, attachment)) {
|
|
188
|
+
bindings.add(`moltnet:${attachment.network}:${roomId}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return [...bindings].sort();
|
|
192
|
+
};
|
|
193
|
+
export const createCoordinationDiagnostics = (plan, teamNode, participants, contextTeamSource) => {
|
|
194
|
+
if (participants.length <= 1) {
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
const bindingsByParticipant = new Map(participants.map((participant) => [
|
|
198
|
+
participant.id,
|
|
199
|
+
getCoordinationBindings(plan, participant, contextTeamSource)
|
|
200
|
+
]));
|
|
201
|
+
const connected = new Set();
|
|
202
|
+
let edgeCount = 0;
|
|
203
|
+
for (let index = 0; index < participants.length; index += 1) {
|
|
204
|
+
for (let other = index + 1; other < participants.length; other += 1) {
|
|
205
|
+
const left = participants[index];
|
|
206
|
+
const right = participants[other];
|
|
207
|
+
const leftBindings = new Set(bindingsByParticipant.get(left.id) ?? []);
|
|
208
|
+
const hasSharedBinding = (bindingsByParticipant.get(right.id) ?? []).some((binding) => leftBindings.has(binding));
|
|
209
|
+
if (hasSharedBinding) {
|
|
210
|
+
edgeCount += 1;
|
|
211
|
+
connected.add(left.id);
|
|
212
|
+
connected.add(right.id);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return [
|
|
217
|
+
...(edgeCount === 0
|
|
218
|
+
? [{
|
|
219
|
+
level: "warn",
|
|
220
|
+
message: `Team ${teamNode.name} context has no shared coordination surface between visible participants`
|
|
221
|
+
}]
|
|
222
|
+
: []),
|
|
223
|
+
...participants
|
|
224
|
+
.filter((participant) => !connected.has(participant.id))
|
|
225
|
+
.map((participant) => ({
|
|
226
|
+
level: "warn",
|
|
227
|
+
message: `Team ${teamNode.name} member ${participant.id} has no shared coordination surface with any visible teammate`
|
|
228
|
+
}))
|
|
229
|
+
];
|
|
230
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { DiagnosticReport } from "../report/index.js";
|
|
2
|
+
import type { ResolvedMemberRef } from "./types.js";
|
|
3
|
+
export interface RosterRepresentativeEntry {
|
|
4
|
+
addresses: Record<string, unknown>;
|
|
5
|
+
delegate_role?: "lead" | "representative";
|
|
6
|
+
description: string;
|
|
7
|
+
surfaces: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface RosterEntry {
|
|
10
|
+
addresses: Record<string, unknown>;
|
|
11
|
+
card?: {
|
|
12
|
+
path: string;
|
|
13
|
+
summary: string;
|
|
14
|
+
};
|
|
15
|
+
description: string;
|
|
16
|
+
is_lead?: boolean;
|
|
17
|
+
representatives?: Record<string, RosterRepresentativeEntry>;
|
|
18
|
+
role: "lead" | "member" | "team";
|
|
19
|
+
surfaces: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface Roster {
|
|
22
|
+
context_kind?: "direct" | "representative";
|
|
23
|
+
lead: string | null;
|
|
24
|
+
members: Record<string, RosterEntry>;
|
|
25
|
+
mode: "hierarchical" | "swarm";
|
|
26
|
+
represents?: {
|
|
27
|
+
delegate_role: "lead" | "representative";
|
|
28
|
+
representative: string;
|
|
29
|
+
slot: string;
|
|
30
|
+
};
|
|
31
|
+
self: string;
|
|
32
|
+
team: string;
|
|
33
|
+
}
|
|
34
|
+
export interface GenerateTeamRosterOptions {
|
|
35
|
+
contextKey: string;
|
|
36
|
+
delegateRole?: "lead" | "representative";
|
|
37
|
+
representedSlotId?: string;
|
|
38
|
+
selfMemberId: string;
|
|
39
|
+
teamSource: string;
|
|
40
|
+
}
|
|
41
|
+
export interface GeneratedTeamRoster {
|
|
42
|
+
diagnostics: DiagnosticReport[];
|
|
43
|
+
roster: string;
|
|
44
|
+
visibleMembers: ResolvedMemberRef[];
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/compiler/types.d.ts
CHANGED
|
@@ -24,6 +24,9 @@ export interface ResolvedDiscordSurface {
|
|
|
24
24
|
users: string[];
|
|
25
25
|
};
|
|
26
26
|
botTokenSecret: string;
|
|
27
|
+
identity?: {
|
|
28
|
+
userId: string;
|
|
29
|
+
};
|
|
27
30
|
}
|
|
28
31
|
export interface ResolvedTelegramSurface {
|
|
29
32
|
access?: {
|
|
@@ -32,6 +35,10 @@ export interface ResolvedTelegramSurface {
|
|
|
32
35
|
users: string[];
|
|
33
36
|
};
|
|
34
37
|
botTokenSecret: string;
|
|
38
|
+
identity?: {
|
|
39
|
+
userId?: string;
|
|
40
|
+
username?: string;
|
|
41
|
+
};
|
|
35
42
|
}
|
|
36
43
|
export interface ResolvedWhatsAppSurface {
|
|
37
44
|
access?: {
|
|
@@ -39,6 +46,9 @@ export interface ResolvedWhatsAppSurface {
|
|
|
39
46
|
mode: "allowlist" | "open" | "pairing";
|
|
40
47
|
users: string[];
|
|
41
48
|
};
|
|
49
|
+
identity?: {
|
|
50
|
+
phone: string;
|
|
51
|
+
};
|
|
42
52
|
}
|
|
43
53
|
export interface ResolvedSlackSurface {
|
|
44
54
|
access?: {
|
|
@@ -48,13 +58,60 @@ export interface ResolvedSlackSurface {
|
|
|
48
58
|
};
|
|
49
59
|
appTokenSecret: string;
|
|
50
60
|
botTokenSecret: string;
|
|
61
|
+
identity?: {
|
|
62
|
+
userId: string;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export interface ResolvedHttpSurface {
|
|
66
|
+
access?: {
|
|
67
|
+
mode: "open";
|
|
68
|
+
};
|
|
69
|
+
auth?: {
|
|
70
|
+
mode: "bearer";
|
|
71
|
+
tokenSecret?: string;
|
|
72
|
+
};
|
|
73
|
+
pathPrefix: string;
|
|
74
|
+
port?: number;
|
|
75
|
+
}
|
|
76
|
+
export interface ResolvedMoltnetRoomPolicy {
|
|
77
|
+
read?: "all" | "mentions" | "thread_only";
|
|
78
|
+
reply?: "auto" | "never";
|
|
79
|
+
}
|
|
80
|
+
export interface ResolvedMoltnetDMConfig extends ResolvedMoltnetRoomPolicy {
|
|
81
|
+
enabled: boolean;
|
|
82
|
+
}
|
|
83
|
+
export interface ResolvedMoltnetAttachment {
|
|
84
|
+
contextRooms?: Record<string, string[]>;
|
|
85
|
+
dms?: ResolvedMoltnetDMConfig;
|
|
86
|
+
memberId: string | null;
|
|
87
|
+
network: string;
|
|
88
|
+
rooms?: Record<string, ResolvedMoltnetRoomPolicy>;
|
|
89
|
+
teamSource: string | null;
|
|
90
|
+
}
|
|
91
|
+
export interface ResolvedWebhookSurface {
|
|
92
|
+
signingSecret?: string;
|
|
93
|
+
url: string;
|
|
51
94
|
}
|
|
52
95
|
export interface ResolvedAgentSurfaces {
|
|
53
96
|
discord?: ResolvedDiscordSurface;
|
|
97
|
+
http?: ResolvedHttpSurface;
|
|
98
|
+
moltnet?: ResolvedMoltnetAttachment[];
|
|
54
99
|
slack?: ResolvedSlackSurface;
|
|
55
100
|
telegram?: ResolvedTelegramSurface;
|
|
101
|
+
webhook?: ResolvedWebhookSurface;
|
|
56
102
|
whatsapp?: ResolvedWhatsAppSurface;
|
|
57
103
|
}
|
|
104
|
+
export interface ResolvedTeamNetworkRoom {
|
|
105
|
+
id: string;
|
|
106
|
+
members: string[];
|
|
107
|
+
}
|
|
108
|
+
export interface ResolvedTeamNetwork {
|
|
109
|
+
expose?: boolean;
|
|
110
|
+
id: string;
|
|
111
|
+
name: string;
|
|
112
|
+
provider: "moltnet";
|
|
113
|
+
rooms: ResolvedTeamNetworkRoom[];
|
|
114
|
+
}
|
|
58
115
|
export interface EffectiveModelTarget {
|
|
59
116
|
auth: {
|
|
60
117
|
key?: string;
|
|
@@ -75,9 +132,11 @@ export interface ResolvedMemberRef {
|
|
|
75
132
|
runtimeName: string | null;
|
|
76
133
|
}
|
|
77
134
|
export interface ResolvedAgentNode {
|
|
135
|
+
description: string;
|
|
78
136
|
docs: ResolvedDocument[];
|
|
79
137
|
env: StringMap;
|
|
80
138
|
execution: ExecutionBlock | undefined;
|
|
139
|
+
expose?: boolean;
|
|
81
140
|
kind: "agent";
|
|
82
141
|
mcpServers: McpServer[];
|
|
83
142
|
name: string;
|
|
@@ -90,16 +149,17 @@ export interface ResolvedAgentNode {
|
|
|
90
149
|
surfaces?: ResolvedAgentSurfaces;
|
|
91
150
|
subagents: ResolvedSubagentRef[];
|
|
92
151
|
}
|
|
93
|
-
export interface ResolvedTeamStructure {
|
|
94
|
-
external: string[];
|
|
95
|
-
leader: string | null;
|
|
96
|
-
mode: "hierarchical" | "swarm";
|
|
97
|
-
}
|
|
98
152
|
export interface ResolvedTeamNode {
|
|
153
|
+
description: string;
|
|
99
154
|
docs: ResolvedDocument[];
|
|
155
|
+
external: string[];
|
|
156
|
+
externalExplicit?: boolean;
|
|
100
157
|
kind: "team";
|
|
158
|
+
lead: string | null;
|
|
101
159
|
members: ResolvedMemberRef[];
|
|
160
|
+
mode: "hierarchical" | "swarm";
|
|
102
161
|
name: string;
|
|
162
|
+
networks?: ResolvedTeamNetwork[];
|
|
103
163
|
policyMode: string | null;
|
|
104
164
|
policyOnDegrade: string | null;
|
|
105
165
|
shared: {
|
|
@@ -109,7 +169,6 @@ export interface ResolvedTeamNode {
|
|
|
109
169
|
skills: ResolvedSkill[];
|
|
110
170
|
};
|
|
111
171
|
source: string;
|
|
112
|
-
structure: ResolvedTeamStructure;
|
|
113
172
|
}
|
|
114
173
|
export interface CompilePlanEdge {
|
|
115
174
|
from: string;
|
|
@@ -124,8 +183,15 @@ export interface CompilePlanNode {
|
|
|
124
183
|
slug: string;
|
|
125
184
|
value: ResolvedAgentNode | ResolvedTeamNode;
|
|
126
185
|
}
|
|
186
|
+
export interface ResolvedTeamMembershipContext {
|
|
187
|
+
agentSource: string;
|
|
188
|
+
memberId: string;
|
|
189
|
+
teamName: string;
|
|
190
|
+
teamSource: string;
|
|
191
|
+
}
|
|
127
192
|
export interface CompilePlan {
|
|
128
193
|
edges: CompilePlanEdge[];
|
|
194
|
+
memberships?: ResolvedTeamMembershipContext[];
|
|
129
195
|
nodes: CompilePlanNode[];
|
|
130
196
|
root: string;
|
|
131
197
|
runtimes: Record<string, {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface ProjectRuntimeOptions {
|
|
2
|
+
path?: string;
|
|
3
|
+
recursive?: boolean;
|
|
4
|
+
runtime: string;
|
|
5
|
+
}
|
|
6
|
+
export interface UpdateProjectRuntimeResult {
|
|
7
|
+
updatedFiles: string[];
|
|
8
|
+
}
|
|
9
|
+
export declare const setProjectRuntime: (options: ProjectRuntimeOptions) => Promise<UpdateProjectRuntimeResult>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { getCanonicalManifestPath, getManifestPath, getProjectRoot, writeUtf8File } from "../filesystem/index.js";
|
|
3
|
+
import { loadManifest, renderSpawnfile } from "../manifest/index.js";
|
|
4
|
+
import { assertRuntimeCanCompile } from "../runtime/index.js";
|
|
5
|
+
import { SpawnfileError } from "../shared/index.js";
|
|
6
|
+
import { assertRuntimeSupportsExecutionModelAuth } from "./modelAuth.js";
|
|
7
|
+
import { validateAgentSurfaceSupport } from "./surfaceDefinitions.js";
|
|
8
|
+
const TEAM_RUNTIME_COMMAND_ERROR = "spawnfile runtime commands only write agent manifests; use --recursive to update descendant agents of a team project";
|
|
9
|
+
const resolveTargetManifestPath = (inputPath) => getManifestPath(path.resolve(inputPath ?? process.cwd()));
|
|
10
|
+
const collectManifestPaths = async (manifestPath, recursive, visited = new Set()) => {
|
|
11
|
+
const canonicalPath = getCanonicalManifestPath(manifestPath);
|
|
12
|
+
if (visited.has(canonicalPath)) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
visited.add(canonicalPath);
|
|
16
|
+
if (!recursive) {
|
|
17
|
+
return [canonicalPath];
|
|
18
|
+
}
|
|
19
|
+
const loadedManifest = await loadManifest(canonicalPath);
|
|
20
|
+
const childRefs = loadedManifest.manifest.kind === "team"
|
|
21
|
+
? loadedManifest.manifest.members.map((member) => member.ref)
|
|
22
|
+
: (loadedManifest.manifest.subagents ?? []).map((subagent) => subagent.ref);
|
|
23
|
+
const nestedPaths = await Promise.all(childRefs.map((ref) => collectManifestPaths(getManifestPath(path.resolve(getProjectRoot(canonicalPath), ref)), true, visited)));
|
|
24
|
+
return [canonicalPath, ...nestedPaths.flat()];
|
|
25
|
+
};
|
|
26
|
+
const manifestChanged = (current, next) => JSON.stringify(current) !== JSON.stringify(next);
|
|
27
|
+
const assertRuntimeMutationAllowed = (manifest, recursive) => {
|
|
28
|
+
if (manifest.kind === "agent") {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
if (recursive) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
throw new SpawnfileError("validation_error", TEAM_RUNTIME_COMMAND_ERROR);
|
|
35
|
+
};
|
|
36
|
+
const validateAgentRuntimeMutation = (manifest) => {
|
|
37
|
+
validateAgentSurfaceSupport(manifest);
|
|
38
|
+
const runtimeName = typeof manifest.runtime === "string" ? manifest.runtime : manifest.runtime?.name;
|
|
39
|
+
if (!runtimeName) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
assertRuntimeSupportsExecutionModelAuth(runtimeName, manifest.execution, manifest.name);
|
|
43
|
+
};
|
|
44
|
+
export const setProjectRuntime = async (options) => {
|
|
45
|
+
await assertRuntimeCanCompile(options.runtime);
|
|
46
|
+
const recursive = options.recursive ?? false;
|
|
47
|
+
const manifestPaths = await collectManifestPaths(resolveTargetManifestPath(options.path), recursive);
|
|
48
|
+
const updatedFiles = [];
|
|
49
|
+
for (const manifestPath of manifestPaths) {
|
|
50
|
+
const loadedManifest = await loadManifest(manifestPath);
|
|
51
|
+
const manifest = loadedManifest.manifest;
|
|
52
|
+
if (!assertRuntimeMutationAllowed(manifest, recursive)) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const nextManifest = {
|
|
56
|
+
...manifest,
|
|
57
|
+
runtime: options.runtime
|
|
58
|
+
};
|
|
59
|
+
validateAgentRuntimeMutation(nextManifest);
|
|
60
|
+
if (!manifestChanged(manifest, nextManifest)) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
await writeUtf8File(manifestPath, renderSpawnfile(nextManifest));
|
|
64
|
+
updatedFiles.push(manifestPath);
|
|
65
|
+
}
|
|
66
|
+
return { updatedFiles };
|
|
67
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type AddProjectSurfaceOptions, type ProjectSurfaceSummariesResult, type RemoveProjectSurfaceOptions, type ShowProjectSurfacesOptions, type UpdateProjectSurfacesResult } from "./surfaceDefinitions.js";
|
|
2
|
+
import type { ProjectSurfaceAccessOptions } from "./surfaceDefinitions.js";
|
|
3
|
+
export { type AddProjectSurfaceOptions, type ProjectSurfaceAccessOptions, type ProjectSurfaceSummariesResult, type RemoveProjectSurfaceOptions, type ShowProjectSurfacesOptions, type UpdateProjectSurfacesResult, resolvePortableSurfaceName } from "./surfaceDefinitions.js";
|
|
4
|
+
export type { ProjectSurfaceSummary, SurfaceAccessMode, SurfaceName } from "./surfaceDefinitions.js";
|
|
5
|
+
export declare const addProjectSurface: (options: AddProjectSurfaceOptions) => Promise<UpdateProjectSurfacesResult>;
|
|
6
|
+
export declare const setProjectSurfaceAccess: (options: ProjectSurfaceAccessOptions) => Promise<UpdateProjectSurfacesResult>;
|
|
7
|
+
export declare const removeProjectSurface: (options: RemoveProjectSurfaceOptions) => Promise<UpdateProjectSurfacesResult>;
|
|
8
|
+
export declare const showProjectSurfaces: (options?: ShowProjectSurfacesOptions) => Promise<ProjectSurfaceSummariesResult>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { getCanonicalManifestPath, getManifestPath, getProjectRoot, writeUtf8File } from "../filesystem/index.js";
|
|
3
|
+
import { loadManifest, renderSpawnfile } from "../manifest/index.js";
|
|
4
|
+
import { assertSurfaceMutationAllowed, removeSurface, updateSurfaceAccess, upsertSurface, validateAgentSurfaceSupport } from "./surfaceDefinitions.js";
|
|
5
|
+
const resolveTargetManifestPath = (inputPath) => getManifestPath(path.resolve(inputPath ?? process.cwd()));
|
|
6
|
+
const collectManifestPaths = async (manifestPath, recursive, visited = new Set()) => {
|
|
7
|
+
const canonicalPath = getCanonicalManifestPath(manifestPath);
|
|
8
|
+
if (visited.has(canonicalPath)) {
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
visited.add(canonicalPath);
|
|
12
|
+
if (!recursive) {
|
|
13
|
+
return [canonicalPath];
|
|
14
|
+
}
|
|
15
|
+
const loadedManifest = await loadManifest(canonicalPath);
|
|
16
|
+
const childRefs = loadedManifest.manifest.kind === "team"
|
|
17
|
+
? loadedManifest.manifest.members.map((member) => member.ref)
|
|
18
|
+
: (loadedManifest.manifest.subagents ?? []).map((subagent) => subagent.ref);
|
|
19
|
+
const nestedPaths = await Promise.all(childRefs.map((ref) => collectManifestPaths(getManifestPath(path.resolve(getProjectRoot(canonicalPath), ref)), true, visited)));
|
|
20
|
+
return [canonicalPath, ...nestedPaths.flat()];
|
|
21
|
+
};
|
|
22
|
+
const manifestChanged = (current, next) => JSON.stringify(current) !== JSON.stringify(next);
|
|
23
|
+
const rewriteTouchedManifests = async (manifestPaths, mutate) => {
|
|
24
|
+
const rewrites = [];
|
|
25
|
+
for (const manifestPath of manifestPaths) {
|
|
26
|
+
const loadedManifest = await loadManifest(manifestPath);
|
|
27
|
+
const nextManifest = mutate(loadedManifest.manifest, manifestPath);
|
|
28
|
+
if (!nextManifest || !manifestChanged(loadedManifest.manifest, nextManifest)) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (nextManifest.kind === "agent") {
|
|
32
|
+
validateAgentSurfaceSupport(nextManifest);
|
|
33
|
+
}
|
|
34
|
+
rewrites.push({
|
|
35
|
+
manifest: nextManifest,
|
|
36
|
+
manifestPath
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
for (const rewrite of rewrites) {
|
|
40
|
+
await writeUtf8File(rewrite.manifestPath, renderSpawnfile(rewrite.manifest));
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
updatedFiles: rewrites.map((rewrite) => rewrite.manifestPath)
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
export { resolvePortableSurfaceName } from "./surfaceDefinitions.js";
|
|
47
|
+
export const addProjectSurface = async (options) => {
|
|
48
|
+
const recursive = options.recursive ?? false;
|
|
49
|
+
const manifestPaths = await collectManifestPaths(resolveTargetManifestPath(options.path), recursive);
|
|
50
|
+
return rewriteTouchedManifests(manifestPaths, (manifest) => {
|
|
51
|
+
if (!assertSurfaceMutationAllowed(manifest, recursive)) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
...manifest,
|
|
56
|
+
surfaces: upsertSurface(manifest.surfaces, options)
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
export const setProjectSurfaceAccess = async (options) => {
|
|
61
|
+
const recursive = options.recursive ?? false;
|
|
62
|
+
const manifestPaths = await collectManifestPaths(resolveTargetManifestPath(options.path), recursive);
|
|
63
|
+
return rewriteTouchedManifests(manifestPaths, (manifest, manifestPath) => {
|
|
64
|
+
if (!assertSurfaceMutationAllowed(manifest, recursive)) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const nextSurfaces = updateSurfaceAccess(manifest.surfaces, options, manifestPath, recursive);
|
|
68
|
+
if (!nextSurfaces) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
...manifest,
|
|
73
|
+
surfaces: nextSurfaces
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
export const removeProjectSurface = async (options) => {
|
|
78
|
+
const recursive = options.recursive ?? false;
|
|
79
|
+
const manifestPaths = await collectManifestPaths(resolveTargetManifestPath(options.path), recursive);
|
|
80
|
+
return rewriteTouchedManifests(manifestPaths, (manifest) => {
|
|
81
|
+
if (!assertSurfaceMutationAllowed(manifest, recursive)) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
...manifest,
|
|
86
|
+
surfaces: removeSurface(manifest.surfaces, options.surface)
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
export const showProjectSurfaces = async (options = {}) => {
|
|
91
|
+
const manifestPaths = await collectManifestPaths(resolveTargetManifestPath(options.path), options.recursive ?? false);
|
|
92
|
+
const entries = [];
|
|
93
|
+
for (const manifestPath of manifestPaths) {
|
|
94
|
+
const loadedManifest = await loadManifest(manifestPath);
|
|
95
|
+
if (options.recursive && loadedManifest.manifest.kind !== "agent") {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
entries.push({
|
|
99
|
+
kind: loadedManifest.manifest.kind,
|
|
100
|
+
manifestPath,
|
|
101
|
+
name: loadedManifest.manifest.name,
|
|
102
|
+
surfaces: loadedManifest.manifest.surfaces
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return { entries };
|
|
106
|
+
};
|
|
@@ -102,12 +102,12 @@ const validateLocalTeamManifest = async (manifestPath, manifest) => {
|
|
|
102
102
|
await validateManifestRefs(manifestPath, manifest.members.map((member) => member.ref), "Member");
|
|
103
103
|
validateSkillRequirements(manifest.shared?.skills, getSharedMcpNames(manifest.shared));
|
|
104
104
|
const memberIds = new Set(manifest.members.map((member) => member.id));
|
|
105
|
-
if (manifest.
|
|
106
|
-
throw new SpawnfileError("validation_error", `
|
|
105
|
+
if (manifest.lead && !memberIds.has(manifest.lead)) {
|
|
106
|
+
throw new SpawnfileError("validation_error", `Lead is not a declared team member: ${manifest.lead}`);
|
|
107
107
|
}
|
|
108
|
-
for (const id of manifest.
|
|
108
|
+
for (const id of manifest.external ?? []) {
|
|
109
109
|
if (!memberIds.has(id)) {
|
|
110
|
-
throw new SpawnfileError("validation_error", `
|
|
110
|
+
throw new SpawnfileError("validation_error", `External references undeclared member: ${id}`);
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
};
|