spawnfile 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -397
- package/dist/cli/modelCommands.d.ts +3 -0
- package/dist/cli/modelCommands.js +68 -0
- package/dist/cli/runCli.d.ts +6 -1
- package/dist/cli/runCli.js +12 -67
- package/dist/cli/runtimeCommands.d.ts +3 -0
- package/dist/cli/runtimeCommands.js +20 -0
- package/dist/cli/surfaceCommands.d.ts +3 -0
- package/dist/cli/surfaceCommands.js +98 -0
- package/dist/compiler/agentSurfaces.js +51 -5
- package/dist/compiler/buildCompilePlan.js +36 -40
- package/dist/compiler/buildCompilePlanRuntime.d.ts +14 -0
- package/dist/compiler/buildCompilePlanRuntime.js +39 -0
- package/dist/compiler/buildCompilePlanTeams.d.ts +5 -0
- package/dist/compiler/buildCompilePlanTeams.js +38 -0
- package/dist/compiler/compilePlanHelpers.js +4 -1
- package/dist/compiler/compileProject.js +62 -13
- package/dist/compiler/compileProjectSupport.d.ts +17 -0
- package/dist/compiler/compileProjectSupport.js +136 -0
- package/dist/compiler/containerArtifacts.d.ts +6 -1
- package/dist/compiler/containerArtifacts.js +26 -4
- package/dist/compiler/containerArtifactsPlans.js +16 -1
- package/dist/compiler/containerArtifactsRender.d.ts +4 -2
- package/dist/compiler/containerArtifactsRender.js +21 -126
- package/dist/compiler/containerArtifactsTypes.d.ts +7 -0
- package/dist/compiler/containerEntrypointRender.d.ts +12 -0
- package/dist/compiler/containerEntrypointRender.js +186 -0
- package/dist/compiler/index.d.ts +2 -0
- package/dist/compiler/index.js +2 -0
- package/dist/compiler/interactiveSurfaceScopes.d.ts +2 -0
- package/dist/compiler/interactiveSurfaceScopes.js +21 -0
- package/dist/compiler/moltnetArtifacts.d.ts +27 -0
- package/dist/compiler/moltnetArtifacts.js +204 -0
- package/dist/compiler/moltnetBinaries.d.ts +4 -0
- package/dist/compiler/moltnetBinaries.js +103 -0
- package/dist/compiler/moltnetClientConfig.d.ts +11 -0
- package/dist/compiler/moltnetClientConfig.js +89 -0
- package/dist/compiler/moltnetRepresentativeResolution.d.ts +16 -0
- package/dist/compiler/moltnetRepresentativeResolution.js +86 -0
- package/dist/compiler/moltnetResolution.d.ts +3 -0
- package/dist/compiler/moltnetResolution.js +201 -0
- package/dist/compiler/runProject.js +1 -1
- package/dist/compiler/surfaceDefinitions.d.ts +55 -0
- package/dist/compiler/surfaceDefinitions.js +204 -0
- package/dist/compiler/teamContextHelpers.d.ts +18 -0
- package/dist/compiler/teamContextHelpers.js +112 -0
- package/dist/compiler/teamContextSupport.d.ts +4 -0
- package/dist/compiler/teamContextSupport.js +264 -0
- package/dist/compiler/teamContextSupport.testHelpers.d.ts +16 -0
- package/dist/compiler/teamContextSupport.testHelpers.js +68 -0
- package/dist/compiler/teamContextTypes.d.ts +28 -0
- package/dist/compiler/teamRoster.d.ts +12 -0
- package/dist/compiler/teamRoster.js +48 -0
- package/dist/compiler/teamRosterEntries.d.ts +13 -0
- package/dist/compiler/teamRosterEntries.js +230 -0
- package/dist/compiler/teamRosterTypes.d.ts +45 -0
- package/dist/compiler/types.d.ts +72 -6
- package/dist/compiler/updateProjectRuntime.d.ts +9 -0
- package/dist/compiler/updateProjectRuntime.js +67 -0
- package/dist/compiler/updateProjectSurfaces.d.ts +8 -0
- package/dist/compiler/updateProjectSurfaces.js +106 -0
- package/dist/manifest/loadManifest.js +4 -4
- package/dist/manifest/renderSpawnfile.js +74 -8
- package/dist/manifest/scaffold.js +1 -3
- package/dist/manifest/schemas.d.ts +227 -17
- package/dist/manifest/schemas.js +62 -20
- package/dist/manifest/surfaceSchemas.d.ts +154 -0
- package/dist/manifest/surfaceSchemas.js +77 -5
- package/dist/runtime/common.js +3 -0
- package/dist/runtime/openclaw/adapter.js +38 -5
- package/dist/runtime/openclaw/moltnet.d.ts +12 -0
- package/dist/runtime/openclaw/moltnet.js +124 -0
- package/dist/runtime/openclaw/surfaces.js +3 -0
- package/dist/runtime/picoclaw/adapter.js +27 -8
- package/dist/runtime/picoclaw/pico.d.ts +2 -0
- package/dist/runtime/picoclaw/pico.js +2 -0
- package/dist/runtime/picoclaw/surfaces.js +11 -0
- package/dist/runtime/tinyclaw/adapter.js +22 -8
- package/dist/runtime/tinyclaw/runAuth.js +28 -1
- package/dist/runtime/tinyclaw/surfaces.js +8 -0
- package/dist/runtime/types.d.ts +11 -0
- package/package.json +10 -3
- package/runtimes.yaml +4 -4
- package/dist/.env.example +0 -5
- package/dist/Dockerfile +0 -21
- package/dist/compiler/discordSurface.d.ts +0 -4
- package/dist/compiler/discordSurface.js +0 -28
- package/dist/container/rootfs/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/config.json +0 -16
- package/dist/container/rootfs/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/workspace/AGENTS.md +0 -1
- package/dist/e2e/cli.js +0 -40
- package/dist/e2e/dockerAuth.d.ts +0 -18
- package/dist/e2e/dockerAuth.js +0 -212
- package/dist/e2e/fixtures.d.ts +0 -2
- package/dist/e2e/fixtures.js +0 -49
- package/dist/e2e/index.d.ts +0 -4
- package/dist/e2e/index.js +0 -4
- package/dist/e2e/runtimePrompts.d.ts +0 -13
- package/dist/e2e/runtimePrompts.js +0 -132
- package/dist/e2e/scenarios.d.ts +0 -3
- package/dist/e2e/scenarios.js +0 -84
- package/dist/e2e/types.d.ts +0 -35
- package/dist/entrypoint.sh +0 -71
- package/dist/runtimes/picoclaw/agents/assistant/config.json +0 -16
- package/dist/runtimes/picoclaw/agents/assistant/workspace/AGENTS.md +0 -1
- package/dist/spawnfile-report.json +0 -71
- /package/dist/{e2e/cli.d.ts → compiler/teamContextTypes.js} +0 -0
- /package/dist/{e2e/types.js → compiler/teamRosterTypes.js} +0 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { generateTeamRoster, listTeamRepresentatives } from "./teamRoster.js";
|
|
3
|
+
export const pathSafe = (value) => {
|
|
4
|
+
const normalized = value
|
|
5
|
+
.toLowerCase()
|
|
6
|
+
.replace(/[^a-z0-9_-]+/g, "-")
|
|
7
|
+
.replace(/^-+|-+$/g, "");
|
|
8
|
+
return normalized || "context";
|
|
9
|
+
};
|
|
10
|
+
export const shortHash = (value) => createHash("sha256").update(value).digest("hex").slice(0, 8);
|
|
11
|
+
export const reportKeySegment = (value) => /^[A-Za-z0-9_-]+$/.test(value)
|
|
12
|
+
? value
|
|
13
|
+
: encodeURIComponent(value).replace(/\./g, "%2E");
|
|
14
|
+
export const findTeamBySource = (plan, source) => {
|
|
15
|
+
const node = plan.nodes.find((entry) => entry.kind === "team" && entry.value.source === source);
|
|
16
|
+
return node?.value.kind === "team" ? node.value : null;
|
|
17
|
+
};
|
|
18
|
+
export const findAgentBySource = (plan, source) => {
|
|
19
|
+
const node = plan.nodes.find((entry) => entry.kind === "agent" && entry.value.source === source);
|
|
20
|
+
return node?.value.kind === "agent" ? node.value : null;
|
|
21
|
+
};
|
|
22
|
+
export const getSystemTeamDoc = (teamNode) => teamNode.docs.find((doc) => doc.role === "system")?.content ?? "";
|
|
23
|
+
export const createContextBaseKey = (context) => context.kind === "direct"
|
|
24
|
+
? pathSafe(context.teamNode.name)
|
|
25
|
+
: `${pathSafe(context.parentTeamNode.name)}--${pathSafe(context.representedMember.id)}`;
|
|
26
|
+
export const collectMoltnetBindings = (agentNode, teamSource, memberId) => (agentNode.surfaces?.moltnet ?? [])
|
|
27
|
+
.filter((attachment) => attachment.memberId === memberId)
|
|
28
|
+
.map((attachment) => ({
|
|
29
|
+
network: attachment.network,
|
|
30
|
+
rooms: [
|
|
31
|
+
...(attachment.contextRooms?.[teamSource] ??
|
|
32
|
+
(attachment.teamSource === teamSource ? Object.keys(attachment.rooms ?? {}) : []))
|
|
33
|
+
].sort()
|
|
34
|
+
}))
|
|
35
|
+
.filter((binding) => binding.rooms.length > 0);
|
|
36
|
+
export const createTeamCard = (contextKey, member, childTeam, representatives) => ({
|
|
37
|
+
content: [
|
|
38
|
+
`# ${childTeam.name}`,
|
|
39
|
+
"",
|
|
40
|
+
childTeam.description,
|
|
41
|
+
"",
|
|
42
|
+
childTeam.docs.find((doc) => doc.role === "identity")?.content ?? "",
|
|
43
|
+
"## Representatives",
|
|
44
|
+
"",
|
|
45
|
+
...representatives.map((representative) => `- \`${representative}\``)
|
|
46
|
+
].filter((line, index, lines) => line !== "" || lines[index - 1] !== "").join("\n").trimEnd() + "\n",
|
|
47
|
+
path: `.spawnfile/team-cards/${contextKey}/${member.id}.md`
|
|
48
|
+
});
|
|
49
|
+
const formatMoltnetBindings = (surfaces) => {
|
|
50
|
+
const moltnet = (surfaces &&
|
|
51
|
+
typeof surfaces === "object" &&
|
|
52
|
+
"moltnet" in surfaces &&
|
|
53
|
+
Array.isArray(surfaces.moltnet))
|
|
54
|
+
? surfaces.moltnet
|
|
55
|
+
: [];
|
|
56
|
+
return moltnet.flatMap((binding) => {
|
|
57
|
+
const network = typeof binding.network === "string" ? binding.network : null;
|
|
58
|
+
const rooms = Array.isArray(binding.rooms)
|
|
59
|
+
? binding.rooms.filter((room) => typeof room === "string")
|
|
60
|
+
: [];
|
|
61
|
+
return network
|
|
62
|
+
? rooms.map((room) => ` Moltnet: \`${network}\` / room \`${room}\``)
|
|
63
|
+
: [];
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
const appendSurfaceBindingLines = (lines, entry) => {
|
|
67
|
+
const bindings = formatMoltnetBindings(entry.surfaces);
|
|
68
|
+
if (bindings.length === 0) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
lines.push(" Surface bindings:");
|
|
72
|
+
lines.push(...bindings);
|
|
73
|
+
};
|
|
74
|
+
export const buildContextOrientation = (index) => {
|
|
75
|
+
const lines = ["# Spawnfile Team Context", ""];
|
|
76
|
+
if (index.direct_memberships.length > 0) {
|
|
77
|
+
lines.push("You are a direct member of these teams:", "");
|
|
78
|
+
index.direct_memberships.forEach((entry, itemIndex) => {
|
|
79
|
+
lines.push(`${itemIndex + 1}. Team: \`${entry.team}\``);
|
|
80
|
+
lines.push(` Member slot: \`${entry.member}\``);
|
|
81
|
+
lines.push(` Read \`${entry.team_doc}\` and \`${entry.roster}\`.`);
|
|
82
|
+
appendSurfaceBindingLines(lines, entry);
|
|
83
|
+
lines.push("");
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (index.representations.length > 0) {
|
|
87
|
+
lines.push("You also represent teams in parent contexts:", "");
|
|
88
|
+
index.representations.forEach((entry, itemIndex) => {
|
|
89
|
+
lines.push(`${itemIndex + 1}. Parent team: \`${entry.team}\``);
|
|
90
|
+
lines.push(` Represents slot: \`${entry.represents}\``);
|
|
91
|
+
lines.push(` Delegate role: \`${entry.delegate_role}\``);
|
|
92
|
+
lines.push(` Read \`${entry.team_doc}\` and \`${entry.roster}\`.`);
|
|
93
|
+
appendSurfaceBindingLines(lines, entry);
|
|
94
|
+
lines.push("");
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
lines.push("Use the context matching the surface or message you are handling. Do not merge team documents.");
|
|
98
|
+
lines.push("For machine-readable bindings, read `.spawnfile/team-contexts.yaml`.");
|
|
99
|
+
return `${lines.join("\n").trimEnd()}\n`;
|
|
100
|
+
};
|
|
101
|
+
export const createVisibleTeamCards = (plan, contextKey, teamNode, visibleMembers) => visibleMembers
|
|
102
|
+
.filter((member) => member.kind === "team")
|
|
103
|
+
.flatMap((member) => {
|
|
104
|
+
const childTeam = findTeamBySource(plan, member.nodeSource);
|
|
105
|
+
if (!childTeam) {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
return [
|
|
109
|
+
createTeamCard(contextKey, member, childTeam, listTeamRepresentatives(plan, childTeam).map((representative) => representative.memberId))
|
|
110
|
+
];
|
|
111
|
+
});
|
|
112
|
+
export const generateContextRoster = generateTeamRoster;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { TeamCompileSupport } from "./teamContextTypes.js";
|
|
2
|
+
export type { TeamCompileSupport } from "./teamContextTypes.js";
|
|
3
|
+
import type { CompilePlan } from "./types.js";
|
|
4
|
+
export declare const prepareTeamCompileSupport: (plan: CompilePlan) => Promise<TeamCompileSupport>;
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import YAML from "yaml";
|
|
2
|
+
import { buildContextOrientation, collectMoltnetBindings, createContextBaseKey, createVisibleTeamCards, findAgentBySource, findTeamBySource, getSystemTeamDoc, reportKeySegment, shortHash } from "./teamContextHelpers.js";
|
|
3
|
+
import { generateTeamRoster, listTeamRepresentatives } from "./teamRoster.js";
|
|
4
|
+
const assignContextKeys = (contexts) => {
|
|
5
|
+
const baseCounts = new Map();
|
|
6
|
+
for (const context of contexts) {
|
|
7
|
+
const base = createContextBaseKey(context);
|
|
8
|
+
baseCounts.set(base, (baseCounts.get(base) ?? 0) + 1);
|
|
9
|
+
}
|
|
10
|
+
const used = new Set();
|
|
11
|
+
for (const context of contexts) {
|
|
12
|
+
const base = createContextBaseKey(context);
|
|
13
|
+
const tuple = context.kind === "direct"
|
|
14
|
+
? `direct:${context.membership.teamSource}:${context.membership.memberId}`
|
|
15
|
+
: `representative:${context.parentTeamNode.source}:${context.representedMember.id}:${context.representativeMemberId}`;
|
|
16
|
+
const key = baseCounts.get(base) === 1 && !used.has(base)
|
|
17
|
+
? base
|
|
18
|
+
: `${base}--${shortHash(tuple)}`;
|
|
19
|
+
context.contextKey = key;
|
|
20
|
+
used.add(key);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const collectAgentContexts = (plan) => {
|
|
24
|
+
const contextsByAgent = new Map();
|
|
25
|
+
const addContext = (agentSource, context) => {
|
|
26
|
+
const contexts = contextsByAgent.get(agentSource) ?? [];
|
|
27
|
+
contexts.push(context);
|
|
28
|
+
contextsByAgent.set(agentSource, contexts);
|
|
29
|
+
};
|
|
30
|
+
for (const membership of plan.memberships ?? []) {
|
|
31
|
+
const teamNode = findTeamBySource(plan, membership.teamSource);
|
|
32
|
+
if (teamNode) {
|
|
33
|
+
addContext(membership.agentSource, {
|
|
34
|
+
contextKey: "",
|
|
35
|
+
kind: "direct",
|
|
36
|
+
membership,
|
|
37
|
+
teamNode
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const node of plan.nodes) {
|
|
42
|
+
if (node.value.kind !== "team") {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const parentTeamNode = node.value;
|
|
46
|
+
for (const member of parentTeamNode.members.filter((entry) => entry.kind === "team")) {
|
|
47
|
+
const childTeam = findTeamBySource(plan, member.nodeSource);
|
|
48
|
+
if (!childTeam) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const delegateRole = member.id === parentTeamNode.lead ? "lead" : "representative";
|
|
52
|
+
for (const representative of listTeamRepresentatives(plan, childTeam)) {
|
|
53
|
+
addContext(representative.agentSource, {
|
|
54
|
+
contextKey: "",
|
|
55
|
+
delegateRole,
|
|
56
|
+
kind: "representative",
|
|
57
|
+
parentTeamNode,
|
|
58
|
+
representedMember: member,
|
|
59
|
+
representativeMemberId: representative.memberId,
|
|
60
|
+
representativeSource: representative.agentSource
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (const contexts of contextsByAgent.values()) {
|
|
66
|
+
assignContextKeys(contexts);
|
|
67
|
+
}
|
|
68
|
+
return contextsByAgent;
|
|
69
|
+
};
|
|
70
|
+
const addMapEntries = (target, key, entries) => {
|
|
71
|
+
if (entries.length === 0) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
target.set(key, [...(target.get(key) ?? []), ...entries]);
|
|
75
|
+
};
|
|
76
|
+
const createTeamCapabilities = (teamNode) => [
|
|
77
|
+
{
|
|
78
|
+
key: "team.roster",
|
|
79
|
+
message: "Context-scoped team rosters were emitted",
|
|
80
|
+
outcome: "supported"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
key: "team.context_orientation",
|
|
84
|
+
message: "Team context orientation was surfaced through runtime system instructions",
|
|
85
|
+
outcome: "supported"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
key: "team.representatives",
|
|
89
|
+
message: "Representative chains were resolved",
|
|
90
|
+
outcome: "supported"
|
|
91
|
+
},
|
|
92
|
+
...((teamNode.networks?.length ?? 0) > 0
|
|
93
|
+
? [
|
|
94
|
+
{
|
|
95
|
+
key: "team.networks",
|
|
96
|
+
message: "Team networks were lowered",
|
|
97
|
+
outcome: "supported"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
key: "team.networks.moltnet",
|
|
101
|
+
message: "Moltnet team networks were lowered",
|
|
102
|
+
outcome: "supported"
|
|
103
|
+
},
|
|
104
|
+
...(teamNode.networks ?? []).map((network) => ({
|
|
105
|
+
key: `team.networks.moltnet.${reportKeySegment(network.id)}`,
|
|
106
|
+
message: `Moltnet network ${network.id} was lowered`,
|
|
107
|
+
outcome: "supported"
|
|
108
|
+
}))
|
|
109
|
+
]
|
|
110
|
+
: [])
|
|
111
|
+
];
|
|
112
|
+
const createRepresentativeDiagnostics = (plan, teamNode) => {
|
|
113
|
+
const diagnostics = [];
|
|
114
|
+
for (const member of teamNode.members.filter((entry) => entry.kind === "team")) {
|
|
115
|
+
const childTeam = findTeamBySource(plan, member.nodeSource);
|
|
116
|
+
if (!childTeam) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (childTeam.mode === "swarm" && !childTeam.externalExplicit) {
|
|
120
|
+
diagnostics.push({
|
|
121
|
+
level: "warn",
|
|
122
|
+
message: `Nested swarm team ${childTeam.name} is exposed without explicit external representatives`
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (teamNode.lead === member.id && !childTeam.externalExplicit) {
|
|
126
|
+
const representatives = listTeamRepresentatives(plan, childTeam);
|
|
127
|
+
if (representatives.length > 1) {
|
|
128
|
+
diagnostics.push({
|
|
129
|
+
level: "warn",
|
|
130
|
+
message: `Team ${teamNode.name} lead ${member.id} resolves to multiple implicit representatives`
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return diagnostics;
|
|
136
|
+
};
|
|
137
|
+
const addAmbiguousBindingDiagnostics = (agentNode, contexts, diagnosticsByTeamSource) => {
|
|
138
|
+
const bindings = new Map();
|
|
139
|
+
for (const context of contexts) {
|
|
140
|
+
const teamSource = context.kind === "direct"
|
|
141
|
+
? context.membership.teamSource
|
|
142
|
+
: context.parentTeamNode.source;
|
|
143
|
+
const memberId = context.kind === "direct"
|
|
144
|
+
? context.membership.memberId
|
|
145
|
+
: context.representativeMemberId;
|
|
146
|
+
for (const binding of collectMoltnetBindings(agentNode, teamSource, memberId)) {
|
|
147
|
+
for (const room of binding.rooms) {
|
|
148
|
+
const key = `moltnet:${binding.network}:${room}`;
|
|
149
|
+
bindings.set(key, [...(bindings.get(key) ?? []), context]);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const [binding, bindingContexts] of bindings) {
|
|
154
|
+
const uniqueKeys = [...new Set(bindingContexts.map((context) => context.contextKey))];
|
|
155
|
+
if (uniqueKeys.length < 2) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
for (const context of bindingContexts) {
|
|
159
|
+
const teamSource = context.kind === "direct"
|
|
160
|
+
? context.membership.teamSource
|
|
161
|
+
: context.parentTeamNode.source;
|
|
162
|
+
addMapEntries(diagnosticsByTeamSource, teamSource, [
|
|
163
|
+
{
|
|
164
|
+
level: "warn",
|
|
165
|
+
message: `Agent ${agentNode.name} maps ${binding} to multiple team contexts: ${uniqueKeys.join(", ")}`
|
|
166
|
+
}
|
|
167
|
+
]);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
export const prepareTeamCompileSupport = async (plan) => {
|
|
172
|
+
const capabilitiesByTeamSource = new Map();
|
|
173
|
+
const diagnosticsByTeamSource = new Map();
|
|
174
|
+
const filesByAgentSource = new Map();
|
|
175
|
+
const contextsByAgent = collectAgentContexts(plan);
|
|
176
|
+
for (const node of plan.nodes) {
|
|
177
|
+
if (node.value.kind !== "team") {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
addMapEntries(capabilitiesByTeamSource, node.value.source, createTeamCapabilities(node.value));
|
|
181
|
+
addMapEntries(diagnosticsByTeamSource, node.value.source, createRepresentativeDiagnostics(plan, node.value));
|
|
182
|
+
}
|
|
183
|
+
for (const [agentSource, contexts] of contextsByAgent) {
|
|
184
|
+
const agentNode = findAgentBySource(plan, agentSource);
|
|
185
|
+
if (!agentNode) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const directCount = contexts.filter((context) => context.kind === "direct").length;
|
|
189
|
+
const directIndex = [];
|
|
190
|
+
const representationIndex = [];
|
|
191
|
+
const files = [];
|
|
192
|
+
for (const context of contexts) {
|
|
193
|
+
if (context.kind === "direct") {
|
|
194
|
+
const teamDocPath = `.spawnfile/team-contexts/${context.contextKey}/TEAM.md`;
|
|
195
|
+
const rosterPath = `.spawnfile/rosters/${context.contextKey}.yaml`;
|
|
196
|
+
const generated = generateTeamRoster(context.teamNode, plan, {
|
|
197
|
+
contextKey: context.contextKey,
|
|
198
|
+
selfMemberId: context.membership.memberId,
|
|
199
|
+
teamSource: context.membership.teamSource
|
|
200
|
+
});
|
|
201
|
+
const aliases = directCount === 1
|
|
202
|
+
? { roster: ".spawnfile/roster.yaml", team_doc: "TEAM.md" }
|
|
203
|
+
: undefined;
|
|
204
|
+
files.push({ content: getSystemTeamDoc(context.teamNode), path: teamDocPath }, { content: generated.roster, path: rosterPath }, ...createVisibleTeamCards(plan, context.contextKey, context.teamNode, generated.visibleMembers));
|
|
205
|
+
if (aliases) {
|
|
206
|
+
files.push({ content: getSystemTeamDoc(context.teamNode), path: aliases.team_doc }, { content: generated.roster, path: aliases.roster });
|
|
207
|
+
}
|
|
208
|
+
directIndex.push({
|
|
209
|
+
...(aliases ? { aliases } : {}),
|
|
210
|
+
context_key: context.contextKey,
|
|
211
|
+
member: context.membership.memberId,
|
|
212
|
+
roster: rosterPath,
|
|
213
|
+
surfaces: {
|
|
214
|
+
moltnet: collectMoltnetBindings(agentNode, context.membership.teamSource, context.membership.memberId)
|
|
215
|
+
},
|
|
216
|
+
team: context.teamNode.name,
|
|
217
|
+
team_doc: teamDocPath
|
|
218
|
+
});
|
|
219
|
+
addMapEntries(diagnosticsByTeamSource, context.teamNode.source, generated.diagnostics);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
const teamDocPath = `.spawnfile/team-contexts/${context.contextKey}/TEAM.md`;
|
|
223
|
+
const rosterPath = `.spawnfile/rosters/${context.contextKey}.yaml`;
|
|
224
|
+
const generated = generateTeamRoster(context.parentTeamNode, plan, {
|
|
225
|
+
contextKey: context.contextKey,
|
|
226
|
+
delegateRole: context.delegateRole,
|
|
227
|
+
representedSlotId: context.representedMember.id,
|
|
228
|
+
selfMemberId: context.representativeMemberId,
|
|
229
|
+
teamSource: context.parentTeamNode.source
|
|
230
|
+
});
|
|
231
|
+
const cards = createVisibleTeamCards(plan, context.contextKey, context.parentTeamNode, generated.visibleMembers);
|
|
232
|
+
files.push({ content: getSystemTeamDoc(context.parentTeamNode), path: teamDocPath }, { content: generated.roster, path: rosterPath }, ...cards);
|
|
233
|
+
representationIndex.push({
|
|
234
|
+
cards: cards.map((file) => file.path),
|
|
235
|
+
context_key: context.contextKey,
|
|
236
|
+
delegate_role: context.delegateRole,
|
|
237
|
+
representative: context.representativeMemberId,
|
|
238
|
+
represents: context.representedMember.id,
|
|
239
|
+
roster: rosterPath,
|
|
240
|
+
surfaces: {
|
|
241
|
+
moltnet: collectMoltnetBindings(agentNode, context.parentTeamNode.source, context.representativeMemberId)
|
|
242
|
+
},
|
|
243
|
+
team: context.parentTeamNode.name,
|
|
244
|
+
team_doc: teamDocPath
|
|
245
|
+
});
|
|
246
|
+
addMapEntries(diagnosticsByTeamSource, context.parentTeamNode.source, generated.diagnostics);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const contextIndex = {
|
|
250
|
+
direct_memberships: directIndex,
|
|
251
|
+
representations: representationIndex
|
|
252
|
+
};
|
|
253
|
+
files.push({
|
|
254
|
+
content: YAML.stringify(contextIndex),
|
|
255
|
+
path: ".spawnfile/team-contexts.yaml"
|
|
256
|
+
}, {
|
|
257
|
+
content: buildContextOrientation(contextIndex),
|
|
258
|
+
path: ".spawnfile/team-contexts.md"
|
|
259
|
+
});
|
|
260
|
+
filesByAgentSource.set(agentSource, files);
|
|
261
|
+
addAmbiguousBindingDiagnostics(agentNode, contexts, diagnosticsByTeamSource);
|
|
262
|
+
}
|
|
263
|
+
return { capabilitiesByTeamSource, diagnosticsByTeamSource, filesByAgentSource };
|
|
264
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { EmittedFile } from "../runtime/index.js";
|
|
2
|
+
import type { CompilePlan, ResolvedAgentNode, ResolvedAgentSurfaces, ResolvedMemberRef, ResolvedTeamNetwork, ResolvedTeamNode } from "./types.js";
|
|
3
|
+
export declare const createTestAgent: (name: string, source: string, surfaces?: ResolvedAgentSurfaces) => ResolvedAgentNode;
|
|
4
|
+
export declare const createTestTeam: (input: {
|
|
5
|
+
docs?: ResolvedTeamNode["docs"];
|
|
6
|
+
external?: string[];
|
|
7
|
+
externalExplicit?: boolean;
|
|
8
|
+
lead?: string | null;
|
|
9
|
+
members: ResolvedMemberRef[];
|
|
10
|
+
mode?: "hierarchical" | "swarm";
|
|
11
|
+
name: string;
|
|
12
|
+
networks?: ResolvedTeamNetwork[];
|
|
13
|
+
source: string;
|
|
14
|
+
}) => ResolvedTeamNode;
|
|
15
|
+
export declare const createTestPlan: (agents: ResolvedAgentNode[], teams: ResolvedTeamNode[], memberships: NonNullable<CompilePlan["memberships"]>) => CompilePlan;
|
|
16
|
+
export declare const findTestFile: (files: EmittedFile[], filePath: string) => EmittedFile;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export const createTestAgent = (name, source, surfaces) => ({
|
|
2
|
+
description: `${name} description`,
|
|
3
|
+
docs: [],
|
|
4
|
+
env: {},
|
|
5
|
+
execution: undefined,
|
|
6
|
+
kind: "agent",
|
|
7
|
+
mcpServers: [],
|
|
8
|
+
name,
|
|
9
|
+
policyMode: null,
|
|
10
|
+
policyOnDegrade: null,
|
|
11
|
+
runtime: { name: "openclaw", options: {} },
|
|
12
|
+
secrets: [],
|
|
13
|
+
skills: [],
|
|
14
|
+
source,
|
|
15
|
+
surfaces,
|
|
16
|
+
subagents: []
|
|
17
|
+
});
|
|
18
|
+
export const createTestTeam = (input) => ({
|
|
19
|
+
description: `${input.name} description`,
|
|
20
|
+
docs: input.docs ?? [
|
|
21
|
+
{
|
|
22
|
+
content: `# ${input.name} operating context\n`,
|
|
23
|
+
role: "system",
|
|
24
|
+
sourcePath: `${input.source}/TEAM.md`
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
external: input.external ?? [],
|
|
28
|
+
externalExplicit: input.externalExplicit ?? false,
|
|
29
|
+
kind: "team",
|
|
30
|
+
lead: input.lead ?? null,
|
|
31
|
+
members: input.members,
|
|
32
|
+
mode: input.mode ?? "swarm",
|
|
33
|
+
name: input.name,
|
|
34
|
+
networks: input.networks,
|
|
35
|
+
policyMode: null,
|
|
36
|
+
policyOnDegrade: null,
|
|
37
|
+
shared: { env: {}, mcpServers: [], secrets: [], skills: [] },
|
|
38
|
+
source: input.source
|
|
39
|
+
});
|
|
40
|
+
export const createTestPlan = (agents, teams, memberships) => ({
|
|
41
|
+
edges: [],
|
|
42
|
+
memberships,
|
|
43
|
+
nodes: [
|
|
44
|
+
...teams.map((team, index) => ({
|
|
45
|
+
id: `team-${index}`,
|
|
46
|
+
kind: "team",
|
|
47
|
+
runtimeName: null,
|
|
48
|
+
slug: team.name.toLowerCase().replaceAll(" ", "-"),
|
|
49
|
+
value: team
|
|
50
|
+
})),
|
|
51
|
+
...agents.map((agent, index) => ({
|
|
52
|
+
id: `agent-${index}`,
|
|
53
|
+
kind: "agent",
|
|
54
|
+
runtimeName: agent.runtime.name,
|
|
55
|
+
slug: agent.name,
|
|
56
|
+
value: agent
|
|
57
|
+
}))
|
|
58
|
+
],
|
|
59
|
+
root: teams[0]?.source ?? agents[0]?.source ?? "/tmp/Spawnfile",
|
|
60
|
+
runtimes: { openclaw: { nodeIds: agents.map((_, index) => `agent-${index}`) } }
|
|
61
|
+
});
|
|
62
|
+
export const findTestFile = (files, filePath) => {
|
|
63
|
+
const file = files.find((entry) => entry.path === filePath);
|
|
64
|
+
if (!file) {
|
|
65
|
+
throw new Error(`missing emitted file ${filePath}`);
|
|
66
|
+
}
|
|
67
|
+
return file;
|
|
68
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { CapabilityReport, DiagnosticReport } from "../report/index.js";
|
|
2
|
+
import type { EmittedFile } from "../runtime/index.js";
|
|
3
|
+
import type { ResolvedMemberRef, ResolvedTeamMembershipContext, ResolvedTeamNode } from "./types.js";
|
|
4
|
+
export interface TeamCompileSupport {
|
|
5
|
+
capabilitiesByTeamSource: Map<string, CapabilityReport[]>;
|
|
6
|
+
diagnosticsByTeamSource: Map<string, DiagnosticReport[]>;
|
|
7
|
+
filesByAgentSource: Map<string, EmittedFile[]>;
|
|
8
|
+
}
|
|
9
|
+
export interface DirectContext {
|
|
10
|
+
contextKey: string;
|
|
11
|
+
kind: "direct";
|
|
12
|
+
membership: ResolvedTeamMembershipContext;
|
|
13
|
+
teamNode: ResolvedTeamNode;
|
|
14
|
+
}
|
|
15
|
+
export interface RepresentativeContext {
|
|
16
|
+
contextKey: string;
|
|
17
|
+
delegateRole: "lead" | "representative";
|
|
18
|
+
kind: "representative";
|
|
19
|
+
parentTeamNode: ResolvedTeamNode;
|
|
20
|
+
representedMember: ResolvedMemberRef;
|
|
21
|
+
representativeMemberId: string;
|
|
22
|
+
representativeSource: string;
|
|
23
|
+
}
|
|
24
|
+
export type AgentContext = DirectContext | RepresentativeContext;
|
|
25
|
+
export interface TeamContextIndex {
|
|
26
|
+
direct_memberships: Array<Record<string, unknown>>;
|
|
27
|
+
representations: Array<Record<string, unknown>>;
|
|
28
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { DiagnosticReport } from "../report/index.js";
|
|
2
|
+
import { type TeamRepresentativeResolution } from "./moltnetResolution.js";
|
|
3
|
+
export type { GeneratedTeamRoster, GenerateTeamRosterOptions, Roster, RosterEntry, RosterRepresentativeEntry } from "./teamRosterTypes.js";
|
|
4
|
+
import type { GeneratedTeamRoster, GenerateTeamRosterOptions } from "./teamRosterTypes.js";
|
|
5
|
+
import type { CompilePlan, ResolvedTeamNode } from "./types.js";
|
|
6
|
+
export { getVisibleTeamMembers } from "./teamRosterEntries.js";
|
|
7
|
+
export declare const generateTeamRoster: (teamNode: ResolvedTeamNode, plan: CompilePlan, options: GenerateTeamRosterOptions) => GeneratedTeamRoster;
|
|
8
|
+
export declare const generateTeamRosters: (teamNode: ResolvedTeamNode, plan: CompilePlan) => {
|
|
9
|
+
diagnostics: DiagnosticReport[];
|
|
10
|
+
rosters: Map<string, string>;
|
|
11
|
+
};
|
|
12
|
+
export declare const listTeamRepresentatives: (plan: CompilePlan, teamNode: ResolvedTeamNode) => TeamRepresentativeResolution[];
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import YAML from "yaml";
|
|
2
|
+
import { resolveTeamRepresentatives } from "./moltnetResolution.js";
|
|
3
|
+
import { collectConcreteParticipants, createCoordinationDiagnostics, createRosterEntry, getVisibleTeamMembers } from "./teamRosterEntries.js";
|
|
4
|
+
export { getVisibleTeamMembers } from "./teamRosterEntries.js";
|
|
5
|
+
export const generateTeamRoster = (teamNode, plan, options) => {
|
|
6
|
+
const visibleMembers = getVisibleTeamMembers(teamNode, options.selfMemberId, options.delegateRole, options.representedSlotId);
|
|
7
|
+
const roster = {
|
|
8
|
+
...(options.representedSlotId && options.delegateRole
|
|
9
|
+
? {
|
|
10
|
+
context_kind: "representative",
|
|
11
|
+
represents: {
|
|
12
|
+
delegate_role: options.delegateRole,
|
|
13
|
+
representative: options.selfMemberId,
|
|
14
|
+
slot: options.representedSlotId
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
: { context_kind: "direct" }),
|
|
18
|
+
lead: teamNode.lead,
|
|
19
|
+
members: Object.fromEntries(visibleMembers.map((member) => [
|
|
20
|
+
member.id,
|
|
21
|
+
createRosterEntry(plan, teamNode, member, options)
|
|
22
|
+
])),
|
|
23
|
+
mode: teamNode.mode,
|
|
24
|
+
self: options.selfMemberId,
|
|
25
|
+
team: teamNode.name
|
|
26
|
+
};
|
|
27
|
+
const participants = collectConcreteParticipants(plan, teamNode, options.selfMemberId, visibleMembers, options.representedSlotId);
|
|
28
|
+
return {
|
|
29
|
+
diagnostics: createCoordinationDiagnostics(plan, teamNode, participants, options.teamSource),
|
|
30
|
+
roster: YAML.stringify(roster),
|
|
31
|
+
visibleMembers
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
export const generateTeamRosters = (teamNode, plan) => {
|
|
35
|
+
const diagnostics = [];
|
|
36
|
+
const rosters = new Map();
|
|
37
|
+
for (const member of teamNode.members.filter((entry) => entry.kind === "agent")) {
|
|
38
|
+
const generated = generateTeamRoster(teamNode, plan, {
|
|
39
|
+
contextKey: teamNode.name,
|
|
40
|
+
selfMemberId: member.id,
|
|
41
|
+
teamSource: teamNode.source
|
|
42
|
+
});
|
|
43
|
+
rosters.set(member.id, generated.roster);
|
|
44
|
+
diagnostics.push(...generated.diagnostics);
|
|
45
|
+
}
|
|
46
|
+
return { diagnostics, rosters };
|
|
47
|
+
};
|
|
48
|
+
export const listTeamRepresentatives = (plan, teamNode) => resolveTeamRepresentatives(plan, teamNode);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DiagnosticReport } from "../report/index.js";
|
|
2
|
+
import type { GenerateTeamRosterOptions, RosterEntry } from "./teamRosterTypes.js";
|
|
3
|
+
import type { CompilePlan, ResolvedMemberRef, ResolvedTeamNode } from "./types.js";
|
|
4
|
+
export declare const getVisibleTeamMembers: (teamNode: ResolvedTeamNode, selfMemberId: string, delegateRole?: "lead" | "representative", representedSlotId?: string) => ResolvedMemberRef[];
|
|
5
|
+
export declare const createRosterEntry: (plan: CompilePlan, teamNode: ResolvedTeamNode, member: ResolvedMemberRef, options: GenerateTeamRosterOptions) => RosterEntry;
|
|
6
|
+
export declare const collectConcreteParticipants: (plan: CompilePlan, teamNode: ResolvedTeamNode, selfMemberId: string, visibleMembers: ResolvedMemberRef[], representedSlotId?: string) => Array<{
|
|
7
|
+
agentSource: string;
|
|
8
|
+
id: string;
|
|
9
|
+
}>;
|
|
10
|
+
export declare const createCoordinationDiagnostics: (plan: CompilePlan, teamNode: ResolvedTeamNode, participants: Array<{
|
|
11
|
+
agentSource: string;
|
|
12
|
+
id: string;
|
|
13
|
+
}>, contextTeamSource: string) => DiagnosticReport[];
|