claude-code-swarm 0.3.5 → 0.3.7
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.claude-plugin/run-agent-inbox-mcp.sh +22 -3
- package/.gitattributes +3 -0
- package/.opentasks/config.json +9 -0
- package/.opentasks/graph.jsonl +0 -0
- package/e2e/helpers/opentasks-daemon.mjs +149 -0
- package/e2e/tier6-live-inbox-flow.test.mjs +938 -0
- package/e2e/tier7-hooks.test.mjs +992 -0
- package/e2e/tier7-minimem.test.mjs +461 -0
- package/e2e/tier7-opentasks.test.mjs +513 -0
- package/e2e/tier7-skilltree.test.mjs +506 -0
- package/e2e/vitest.config.e2e.mjs +1 -1
- package/package.json +6 -2
- package/references/agent-inbox/package-lock.json +2 -2
- package/references/agent-inbox/package.json +1 -1
- package/references/agent-inbox/src/index.ts +16 -2
- package/references/agent-inbox/src/ipc/ipc-server.ts +58 -0
- package/references/agent-inbox/src/mcp/mcp-proxy.ts +326 -0
- package/references/agent-inbox/src/types.ts +26 -0
- package/references/agent-inbox/test/ipc-new-commands.test.ts +200 -0
- package/references/agent-inbox/test/mcp-proxy.test.ts +191 -0
- package/references/minimem/package-lock.json +2 -2
- package/references/minimem/package.json +1 -1
- package/scripts/bootstrap.mjs +8 -1
- package/scripts/map-hook.mjs +6 -2
- package/scripts/map-sidecar.mjs +19 -0
- package/scripts/team-loader.mjs +15 -8
- package/skills/swarm/SKILL.md +16 -22
- package/src/__tests__/agent-generator.test.mjs +9 -10
- package/src/__tests__/context-output.test.mjs +13 -14
- package/src/__tests__/e2e-inbox-integration.test.mjs +732 -0
- package/src/__tests__/e2e-live-inbox.test.mjs +597 -0
- package/src/__tests__/inbox-integration.test.mjs +298 -0
- package/src/__tests__/integration.test.mjs +12 -11
- package/src/__tests__/skilltree-client.test.mjs +47 -1
- package/src/agent-generator.mjs +79 -88
- package/src/bootstrap.mjs +24 -3
- package/src/context-output.mjs +238 -64
- package/src/index.mjs +2 -0
- package/src/sidecar-server.mjs +30 -0
- package/src/skilltree-client.mjs +50 -5
package/src/bootstrap.mjs
CHANGED
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
|
|
13
13
|
import fs from "fs";
|
|
14
14
|
import { execSync } from "child_process";
|
|
15
|
-
import { readConfig, resolveScope } from "./config.mjs";
|
|
15
|
+
import { readConfig, resolveScope, resolveTeamName } from "./config.mjs";
|
|
16
16
|
import { SOCKET_PATH, MAP_DIR, pluginDir, ensureSwarmDir, ensureOpentasksDir, ensureSessionDir, listSessionDirs } from "./paths.mjs";
|
|
17
17
|
import { findSocketPath, isDaemonAlive, ensureDaemon } from "./opentasks-client.mjs";
|
|
18
18
|
import { loadTeam } from "./template.mjs";
|
|
19
|
-
import { killSidecar, startSidecar } from "./sidecar-client.mjs";
|
|
19
|
+
import { killSidecar, startSidecar, sendToInbox } from "./sidecar-client.mjs";
|
|
20
20
|
import { checkSessionlogStatus, syncSessionlog } from "./sessionlog.mjs";
|
|
21
21
|
import { resolveSwarmkit, configureNodePath } from "./swarmkit-resolver.mjs";
|
|
22
22
|
|
|
@@ -316,7 +316,28 @@ export async function bootstrap(pluginDirOverride, sessionId) {
|
|
|
316
316
|
}
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
-
// 3b.
|
|
319
|
+
// 3b. Register main agent in inbox for message routing
|
|
320
|
+
if (config.map.enabled && config.inbox?.enabled) {
|
|
321
|
+
const teamName = resolveTeamName(config);
|
|
322
|
+
const sPaths = sessionId
|
|
323
|
+
? (await import("./paths.mjs")).sessionPaths(sessionId)
|
|
324
|
+
: { inboxSocketPath: (await import("./paths.mjs")).INBOX_SOCKET_PATH };
|
|
325
|
+
sendToInbox({
|
|
326
|
+
action: "notify",
|
|
327
|
+
event: {
|
|
328
|
+
type: "agent.spawn",
|
|
329
|
+
agent: {
|
|
330
|
+
agentId: `${teamName}-main`,
|
|
331
|
+
name: `${teamName}-main`,
|
|
332
|
+
role: "orchestrator",
|
|
333
|
+
scopes: [scope],
|
|
334
|
+
metadata: { isMain: true, sessionId },
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
}, sPaths.inboxSocketPath).catch(() => {});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// 3c. Initial sessionlog sync (fire and forget)
|
|
320
341
|
if (config.map.enabled && config.sessionlog.sync !== "off") {
|
|
321
342
|
syncSessionlog(config, sessionId).catch(() => {});
|
|
322
343
|
}
|
package/src/context-output.mjs
CHANGED
|
@@ -2,72 +2,249 @@
|
|
|
2
2
|
* context-output.mjs — Markdown formatting for hook stdout
|
|
3
3
|
*
|
|
4
4
|
* Generates the markdown context that gets injected into Claude's conversation
|
|
5
|
-
* by SessionStart and team-loader hooks.
|
|
5
|
+
* by SessionStart and team-loader hooks. Also provides buildCapabilitiesContext()
|
|
6
|
+
* which is shared between SessionStart injection and AGENT.md generation.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import fs from "fs";
|
|
9
10
|
|
|
11
|
+
// ── Capabilities context (shared between main agent + spawned agents) ────────
|
|
12
|
+
|
|
10
13
|
/**
|
|
11
|
-
*
|
|
14
|
+
* Build the unified capabilities context markdown.
|
|
15
|
+
*
|
|
16
|
+
* Assembles conditional sections based on which capabilities are enabled.
|
|
17
|
+
* Used by:
|
|
18
|
+
* - formatBootstrapContext() → injected into main agent via SessionStart stdout
|
|
19
|
+
* - generateAgentMd() → embedded in each spawned agent's AGENT.md
|
|
20
|
+
*
|
|
21
|
+
* @param {object} options
|
|
22
|
+
* @param {string|null} options.role - null for main agent, role name for spawned agents
|
|
23
|
+
* @param {boolean} options.opentasksEnabled
|
|
24
|
+
* @param {string} options.opentasksStatus - "connected", "enabled", "disabled", etc.
|
|
25
|
+
* @param {boolean} options.minimemEnabled
|
|
26
|
+
* @param {string} options.minimemStatus - "ready", "installed", "disabled"
|
|
27
|
+
* @param {boolean} options.skilltreeEnabled
|
|
28
|
+
* @param {string} options.skilltreeStatus - "ready", "installed", "disabled"
|
|
29
|
+
* @param {boolean} options.inboxEnabled
|
|
30
|
+
* @param {boolean} options.meshEnabled
|
|
31
|
+
* @param {boolean} options.mapEnabled
|
|
32
|
+
* @param {string} options.mapStatus - "connected (scope: ...)", "disabled", etc.
|
|
33
|
+
* @param {string} options.sessionlogSync - "off", "lifecycle", "metrics", "full"
|
|
34
|
+
* @param {string} options.teamName - team name for spawned agent context
|
|
35
|
+
* @returns {string} Markdown capabilities context
|
|
12
36
|
*/
|
|
13
|
-
export function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
export function buildCapabilitiesContext({
|
|
38
|
+
role = null,
|
|
39
|
+
opentasksEnabled = false,
|
|
40
|
+
opentasksStatus = "disabled",
|
|
41
|
+
minimemEnabled = false,
|
|
42
|
+
minimemStatus = "disabled",
|
|
43
|
+
skilltreeEnabled = false,
|
|
44
|
+
skilltreeStatus = "disabled",
|
|
45
|
+
skillProfile = "",
|
|
46
|
+
inboxEnabled = false,
|
|
47
|
+
meshEnabled = false,
|
|
48
|
+
mapEnabled = false,
|
|
49
|
+
mapStatus = "disabled",
|
|
50
|
+
sessionlogSync = "off",
|
|
51
|
+
teamName = "",
|
|
52
|
+
} = {}) {
|
|
53
|
+
const isAgent = role !== null;
|
|
54
|
+
const lines = ["## Swarm Capabilities", ""];
|
|
25
55
|
|
|
26
|
-
|
|
27
|
-
|
|
56
|
+
// ── Team Orchestration ───────────────────────────────────────────────
|
|
57
|
+
lines.push("### Team Orchestration");
|
|
58
|
+
lines.push("");
|
|
59
|
+
if (isAgent) {
|
|
60
|
+
lines.push(`You are part of the **${teamName}** team. The orchestrator spawns and coordinates all teammates — spawned agents cannot spawn other agents.`);
|
|
28
61
|
} else {
|
|
29
|
-
lines.push("
|
|
62
|
+
lines.push("Use `/swarm` to launch a team from the configured topology, or `/swarm <template>` to pick a different one.");
|
|
63
|
+
lines.push("Available templates: **gsd**, **bmad-method**, **bug-fix-pipeline**, **docs-sync**, **security-audit**, and more.");
|
|
64
|
+
lines.push("Only the orchestrator spawns teammates — spawned agents cannot spawn other agents.");
|
|
30
65
|
}
|
|
66
|
+
lines.push("");
|
|
31
67
|
|
|
32
|
-
|
|
33
|
-
|
|
68
|
+
// ── Task Management ──────────────────────────────────────────────────
|
|
69
|
+
lines.push("### Task Management");
|
|
70
|
+
lines.push("");
|
|
71
|
+
const otActive = opentasksEnabled && (opentasksStatus === "connected" || opentasksStatus === "enabled");
|
|
72
|
+
if (otActive) {
|
|
73
|
+
lines.push("Use **opentasks MCP tools** for task management:");
|
|
74
|
+
lines.push("- `opentasks__create_task` — create tasks with metadata and links");
|
|
75
|
+
lines.push("- `opentasks__update_task` — claim, update status, annotate");
|
|
76
|
+
lines.push("- `opentasks__list_tasks` / `opentasks__query` — check progress, filter by status/assignee");
|
|
77
|
+
lines.push("Cross-system task graph supports linking (`opentasks__link`) and annotations.");
|
|
78
|
+
lines.push("Native Claude tasks are auto-federated into the graph via the claude-tasks provider.");
|
|
79
|
+
} else {
|
|
80
|
+
lines.push("Use Claude Code native task tools:");
|
|
81
|
+
lines.push("- `TaskCreate` — create tasks for the team");
|
|
82
|
+
lines.push("- `TaskUpdate` — claim (set owner), update status");
|
|
83
|
+
lines.push("- `TaskList` — check progress");
|
|
84
|
+
lines.push("Tasks are shared team-wide when agents use the same `team_name`.");
|
|
34
85
|
}
|
|
86
|
+
lines.push("");
|
|
35
87
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
88
|
+
// ── Communication ────────────────────────────────────────────────────
|
|
89
|
+
lines.push("### Communication");
|
|
90
|
+
lines.push("");
|
|
91
|
+
lines.push("Use `SendMessage` for quick same-team agent-to-agent messaging:");
|
|
92
|
+
lines.push("- Direct: `SendMessage(recipient=\"<agent-name>\", content=\"...\")`");
|
|
93
|
+
lines.push("- Broadcast only when truly necessary (messages every teammate).");
|
|
94
|
+
lines.push("");
|
|
95
|
+
if (inboxEnabled) {
|
|
96
|
+
lines.push("**Structured messaging** via agent-inbox MCP tools (persistent, threaded, cross-system):");
|
|
97
|
+
lines.push("- `agent-inbox__check_inbox(agentId)` — check for new messages (auto-marks as read)");
|
|
98
|
+
lines.push("- `agent-inbox__send_message(to, body, from)` — send to an agent, or `agent@system` for federated");
|
|
99
|
+
lines.push("- `agent-inbox__read_thread(threadTag)` — read full conversation thread");
|
|
100
|
+
lines.push("- `agent-inbox__list_agents()` — see who is registered (local + federated)");
|
|
101
|
+
lines.push("");
|
|
102
|
+
lines.push("Use inbox for: cross-system messages, threaded conversations, delivery tracking, messaging external observers.");
|
|
103
|
+
lines.push("Use `SendMessage` for: quick same-team coordination that doesn't need persistence.");
|
|
104
|
+
}
|
|
105
|
+
if (meshEnabled) {
|
|
106
|
+
lines.push("Encrypted P2P transport via MeshPeer with agent discovery.");
|
|
107
|
+
}
|
|
108
|
+
lines.push("");
|
|
109
|
+
|
|
110
|
+
// ── Memory (minimem) ────────────────────────────────────────────────
|
|
111
|
+
if (minimemEnabled && minimemStatus !== "disabled") {
|
|
112
|
+
lines.push("### Memory");
|
|
113
|
+
lines.push("");
|
|
114
|
+
if (minimemStatus === "ready") {
|
|
115
|
+
lines.push("Use **minimem MCP tools** for persistent, searchable team memory. Memory is shared team-wide.");
|
|
116
|
+
lines.push("");
|
|
117
|
+
lines.push("**Searching (two-phase workflow):**");
|
|
118
|
+
lines.push("1. `minimem__memory_search(query)` — returns compact index (path, score, preview)");
|
|
119
|
+
lines.push("2. `minimem__memory_get_details(results)` — fetch full text for relevant results");
|
|
120
|
+
lines.push("Use `detail: \"full\"` for quick lookups. Filter by type: `type: \"decision\"` (decision, bugfix, feature, discovery, context, note).");
|
|
121
|
+
lines.push("");
|
|
122
|
+
lines.push("**Knowledge search** (structured metadata):");
|
|
123
|
+
lines.push("- `minimem__knowledge_search(query, { domain, entities, minConfidence })` — filter by domain/entity/confidence");
|
|
124
|
+
lines.push("- `minimem__knowledge_graph(nodeId, depth)` — traverse knowledge relationships");
|
|
125
|
+
lines.push("- `minimem__knowledge_path(fromId, toId)` — find path between knowledge nodes");
|
|
126
|
+
lines.push("");
|
|
127
|
+
if (!isAgent) {
|
|
128
|
+
lines.push("**Storing memories** (use filesystem tools to write Markdown):");
|
|
129
|
+
lines.push("- `MEMORY.md` — important decisions and architecture notes");
|
|
130
|
+
lines.push("- `memory/YYYY-MM-DD.md` — daily logs and session notes");
|
|
131
|
+
lines.push("- `memory/<topic>.md` — topic-specific files");
|
|
132
|
+
lines.push("");
|
|
133
|
+
lines.push("Format entries with a type comment for filtered search:");
|
|
134
|
+
lines.push("```");
|
|
135
|
+
lines.push("### YYYY-MM-DD HH:MM");
|
|
136
|
+
lines.push("<!-- type: decision -->");
|
|
137
|
+
lines.push("<content>");
|
|
138
|
+
lines.push("```");
|
|
139
|
+
lines.push("Types: `decision`, `bugfix`, `feature`, `discovery`, `context`, `note`.");
|
|
140
|
+
lines.push("Wrap secrets in `<private>` tags to exclude from indexing.");
|
|
141
|
+
lines.push("");
|
|
142
|
+
lines.push("**Team strategy**: Search memory before spawning agents for prior context on the user's goal. After team completion, store key decisions and outcomes.");
|
|
143
|
+
lines.push("");
|
|
144
|
+
} else {
|
|
145
|
+
lines.push("**Before major work**: Search memory for relevant prior decisions and context.");
|
|
146
|
+
lines.push("**After completing work**: Store key decisions and findings using filesystem tools — write to `memory/` files with `<!-- type: decision -->` tags.");
|
|
147
|
+
lines.push("");
|
|
41
148
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
lines.push(
|
|
149
|
+
} else {
|
|
150
|
+
lines.push(`Memory: ${minimemStatus} (minimem installed but not fully ready).`);
|
|
151
|
+
lines.push("");
|
|
45
152
|
}
|
|
46
153
|
}
|
|
47
154
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
155
|
+
// ── Per-Role Skills (skill-tree) ─────────────────────────────────────
|
|
156
|
+
if (skilltreeEnabled && skilltreeStatus !== "disabled") {
|
|
157
|
+
lines.push("### Per-Role Skills");
|
|
158
|
+
lines.push("");
|
|
159
|
+
if (isAgent) {
|
|
160
|
+
lines.push("Your **skill loadout** is embedded in the `## Skills` section above. These are versioned, reusable patterns selected for your role.");
|
|
161
|
+
if (skillProfile) {
|
|
162
|
+
lines.push(`Your loadout was compiled from the **${skillProfile}** profile.`);
|
|
163
|
+
}
|
|
164
|
+
lines.push("Read and apply these skills to guide your approach — they represent proven patterns for your type of work.");
|
|
165
|
+
} else {
|
|
166
|
+
lines.push("Each spawned agent receives a **skill loadout** compiled per-role and embedded in their AGENT.md.");
|
|
167
|
+
lines.push("Skills are versioned, reusable patterns from the skill-tree library, selected based on role profiles.");
|
|
168
|
+
lines.push("");
|
|
169
|
+
lines.push("Loadouts are configured via the `skilltree:` block in team.yaml, or auto-inferred from role names.");
|
|
170
|
+
lines.push("Built-in profiles: **code-review**, **implementation**, **debugging**, **security**, **testing**, **refactoring**, **documentation**, **devops**.");
|
|
171
|
+
lines.push("Skills are cached per template — delete the template cache to refresh.");
|
|
52
172
|
}
|
|
173
|
+
lines.push("");
|
|
53
174
|
}
|
|
54
175
|
|
|
55
|
-
|
|
56
|
-
|
|
176
|
+
// ── External Observability (MAP) ─────────────────────────────────────
|
|
177
|
+
lines.push("### External Observability");
|
|
178
|
+
lines.push("");
|
|
179
|
+
if (mapEnabled) {
|
|
180
|
+
if (isAgent) {
|
|
181
|
+
lines.push("MAP is active — lifecycle and task events are emitted automatically by hooks. No direct interaction needed.");
|
|
182
|
+
} else {
|
|
183
|
+
lines.push(`MAP: ${mapStatus}`);
|
|
184
|
+
lines.push("Agent lifecycle and task events are emitted automatically by hooks. No direct MAP interaction needed.");
|
|
185
|
+
}
|
|
186
|
+
if (sessionlogSync && sessionlogSync !== "off") {
|
|
187
|
+
lines.push(`Session trajectory checkpoints synced to MAP (level: ${sessionlogSync}).`);
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
lines.push("No external observability configured.");
|
|
57
191
|
}
|
|
192
|
+
lines.push("");
|
|
58
193
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
194
|
+
return lines.join("\n");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ── SessionStart context ─────────────────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Format the SessionStart bootstrap context.
|
|
201
|
+
*/
|
|
202
|
+
export function formatBootstrapContext({
|
|
203
|
+
template,
|
|
204
|
+
team,
|
|
205
|
+
mapEnabled = false,
|
|
206
|
+
mapStatus,
|
|
207
|
+
sessionlogStatus,
|
|
208
|
+
sessionlogSync,
|
|
209
|
+
opentasksEnabled = false,
|
|
210
|
+
opentasksStatus = "disabled",
|
|
211
|
+
inboxEnabled = false,
|
|
212
|
+
meshEnabled = false,
|
|
213
|
+
minimemEnabled = false,
|
|
214
|
+
minimemStatus = "disabled",
|
|
215
|
+
skilltreeEnabled = false,
|
|
216
|
+
skilltreeStatus = "disabled",
|
|
217
|
+
}) {
|
|
218
|
+
const lines = ["## Claude Code Swarm", ""];
|
|
219
|
+
|
|
220
|
+
if (template) {
|
|
221
|
+
lines.push(`Team template configured: **${template}**`);
|
|
222
|
+
} else {
|
|
223
|
+
lines.push("No team template configured.");
|
|
64
224
|
}
|
|
225
|
+
lines.push("");
|
|
65
226
|
|
|
66
|
-
|
|
67
|
-
|
|
227
|
+
// Sessionlog warning (non-capability — surface config issues)
|
|
228
|
+
if (sessionlogStatus && sessionlogStatus !== "active" && sessionlogStatus !== "not installed") {
|
|
229
|
+
lines.push(`Sessionlog: WARNING — configured but ${sessionlogStatus}`);
|
|
230
|
+
lines.push("");
|
|
68
231
|
}
|
|
69
232
|
|
|
70
|
-
|
|
233
|
+
// Capabilities context
|
|
234
|
+
lines.push(buildCapabilitiesContext({
|
|
235
|
+
role: null,
|
|
236
|
+
opentasksEnabled,
|
|
237
|
+
opentasksStatus,
|
|
238
|
+
minimemEnabled,
|
|
239
|
+
minimemStatus,
|
|
240
|
+
skilltreeEnabled,
|
|
241
|
+
skilltreeStatus,
|
|
242
|
+
inboxEnabled,
|
|
243
|
+
meshEnabled,
|
|
244
|
+
mapEnabled,
|
|
245
|
+
mapStatus: mapStatus || "disabled",
|
|
246
|
+
sessionlogSync: sessionlogSync || "off",
|
|
247
|
+
}));
|
|
71
248
|
|
|
72
249
|
if (team) {
|
|
73
250
|
// Embed the SKILL.md content directly so the agent has the topology immediately
|
|
@@ -87,7 +264,6 @@ export function formatBootstrapContext({
|
|
|
87
264
|
lines.push(
|
|
88
265
|
"Use `/swarm` to launch the team (creates a native Claude Code team)."
|
|
89
266
|
);
|
|
90
|
-
lines.push("Templates available via openteams: **gsd**, **bmad-method**, **bug-fix-pipeline**, **docs-sync** (and more)");
|
|
91
267
|
lines.push("");
|
|
92
268
|
|
|
93
269
|
return lines.join("\n");
|
|
@@ -95,8 +271,9 @@ export function formatBootstrapContext({
|
|
|
95
271
|
|
|
96
272
|
/**
|
|
97
273
|
* Format the team-loaded context output.
|
|
274
|
+
* Uses buildCapabilitiesContext for coordination/tool instructions.
|
|
98
275
|
*/
|
|
99
|
-
export function formatTeamLoadedContext(generatedDir, templatePath, teamName,
|
|
276
|
+
export function formatTeamLoadedContext(generatedDir, templatePath, teamName, options = {}) {
|
|
100
277
|
const lines = ["## Claude Code Swarm — Team Loaded", ""];
|
|
101
278
|
|
|
102
279
|
// Include the catalog if available
|
|
@@ -121,13 +298,13 @@ export function formatTeamLoadedContext(generatedDir, templatePath, teamName, {
|
|
|
121
298
|
"1. Call **TeamCreate** to set up a native Claude Code team"
|
|
122
299
|
);
|
|
123
300
|
lines.push(
|
|
124
|
-
"2. Create tasks
|
|
301
|
+
"2. Create tasks based on the user's goal"
|
|
125
302
|
);
|
|
126
303
|
lines.push(
|
|
127
304
|
"3. Spawn agents directly as teammates (only you can spawn — teammates cannot spawn other teammates)"
|
|
128
305
|
);
|
|
129
306
|
lines.push(
|
|
130
|
-
"4. Coordinate the team via **SendMessage** and track progress
|
|
307
|
+
"4. Coordinate the team via **SendMessage** and track progress"
|
|
131
308
|
);
|
|
132
309
|
lines.push("");
|
|
133
310
|
lines.push(
|
|
@@ -137,25 +314,22 @@ export function formatTeamLoadedContext(generatedDir, templatePath, teamName, {
|
|
|
137
314
|
"Read a role's agent prompt before spawning an agent for that role."
|
|
138
315
|
);
|
|
139
316
|
lines.push("");
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
);
|
|
157
|
-
lines.push("Agents do not interact with MAP directly.");
|
|
158
|
-
lines.push("");
|
|
317
|
+
|
|
318
|
+
// Capabilities context for coordination details
|
|
319
|
+
lines.push(buildCapabilitiesContext({
|
|
320
|
+
role: null,
|
|
321
|
+
opentasksEnabled: options.opentasksEnabled,
|
|
322
|
+
opentasksStatus: options.opentasksStatus || "disabled",
|
|
323
|
+
minimemEnabled: options.minimemEnabled,
|
|
324
|
+
minimemStatus: options.minimemStatus || "disabled",
|
|
325
|
+
skilltreeEnabled: options.skilltreeEnabled,
|
|
326
|
+
skilltreeStatus: options.skilltreeStatus || "disabled",
|
|
327
|
+
inboxEnabled: options.inboxEnabled,
|
|
328
|
+
meshEnabled: options.meshEnabled,
|
|
329
|
+
mapEnabled: options.mapEnabled,
|
|
330
|
+
mapStatus: options.mapStatus || "disabled",
|
|
331
|
+
sessionlogSync: options.sessionlogSync || "off",
|
|
332
|
+
}));
|
|
159
333
|
|
|
160
334
|
return lines.join("\n");
|
|
161
335
|
}
|
package/src/index.mjs
CHANGED
|
@@ -41,6 +41,7 @@ export { formatInboxAsMarkdown, formatAge } from "./inbox.mjs";
|
|
|
41
41
|
|
|
42
42
|
// Context output
|
|
43
43
|
export {
|
|
44
|
+
buildCapabilitiesContext,
|
|
44
45
|
formatBootstrapContext,
|
|
45
46
|
formatTeamLoadedContext,
|
|
46
47
|
formatNoTemplateMessage,
|
|
@@ -123,6 +124,7 @@ export {
|
|
|
123
124
|
parseSkillTreeExtension,
|
|
124
125
|
compileRoleLoadout,
|
|
125
126
|
compileAllRoleLoadouts,
|
|
127
|
+
inferProfileFromRole,
|
|
126
128
|
} from "./skilltree-client.mjs";
|
|
127
129
|
|
|
128
130
|
// Bootstrap
|
package/src/sidecar-server.mjs
CHANGED
|
@@ -176,6 +176,21 @@ export function createCommandHandler(connection, scope, registeredAgents, opts =
|
|
|
176
176
|
metadata,
|
|
177
177
|
});
|
|
178
178
|
registeredAgents.set(agentId, { name, role, metadata });
|
|
179
|
+
|
|
180
|
+
// Also register in inbox storage for message routing
|
|
181
|
+
if (inboxInstance?.storage) {
|
|
182
|
+
try {
|
|
183
|
+
inboxInstance.storage.putAgent({
|
|
184
|
+
agent_id: agentId,
|
|
185
|
+
scope,
|
|
186
|
+
status: "active",
|
|
187
|
+
metadata: { name, role, ...metadata },
|
|
188
|
+
registered_at: new Date().toISOString(),
|
|
189
|
+
last_active_at: new Date().toISOString(),
|
|
190
|
+
});
|
|
191
|
+
} catch { /* best-effort */ }
|
|
192
|
+
}
|
|
193
|
+
|
|
179
194
|
respond(client, { ok: true, agent: result });
|
|
180
195
|
} catch (err) {
|
|
181
196
|
process.stderr.write(
|
|
@@ -232,6 +247,21 @@ export function createCommandHandler(connection, scope, registeredAgents, opts =
|
|
|
232
247
|
} catch {
|
|
233
248
|
// Agent may already be gone
|
|
234
249
|
}
|
|
250
|
+
|
|
251
|
+
// Also update inbox storage
|
|
252
|
+
if (inboxInstance?.storage) {
|
|
253
|
+
try {
|
|
254
|
+
inboxInstance.storage.putAgent({
|
|
255
|
+
agent_id: agentId,
|
|
256
|
+
scope,
|
|
257
|
+
status: "disconnected",
|
|
258
|
+
metadata: {},
|
|
259
|
+
registered_at: new Date().toISOString(),
|
|
260
|
+
last_active_at: new Date().toISOString(),
|
|
261
|
+
});
|
|
262
|
+
} catch { /* best-effort */ }
|
|
263
|
+
}
|
|
264
|
+
|
|
235
265
|
registeredAgents.delete(agentId);
|
|
236
266
|
}
|
|
237
267
|
respond(client, { ok: true });
|
package/src/skilltree-client.mjs
CHANGED
|
@@ -135,13 +135,49 @@ export async function compileRoleLoadout(roleName, criteria, config) {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Role name → built-in profile auto-mapping.
|
|
140
|
+
* Used as fallback when no explicit criteria or default profile is configured.
|
|
141
|
+
*/
|
|
142
|
+
const ROLE_PROFILE_MAP = {
|
|
143
|
+
executor: "implementation",
|
|
144
|
+
developer: "implementation",
|
|
145
|
+
"quick-flow-dev": "implementation",
|
|
146
|
+
debugger: "debugging",
|
|
147
|
+
verifier: "testing",
|
|
148
|
+
qa: "testing",
|
|
149
|
+
"plan-checker": "code-review",
|
|
150
|
+
"integration-checker": "code-review",
|
|
151
|
+
"tech-writer": "documentation",
|
|
152
|
+
architect: "refactoring",
|
|
153
|
+
"ux-designer": "documentation",
|
|
154
|
+
"security-auditor": "security",
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Infer a skill-tree profile from a role name.
|
|
159
|
+
* Returns empty string if no match found.
|
|
160
|
+
*/
|
|
161
|
+
export function inferProfileFromRole(roleName) {
|
|
162
|
+
// Direct match
|
|
163
|
+
if (ROLE_PROFILE_MAP[roleName]) return ROLE_PROFILE_MAP[roleName];
|
|
164
|
+
|
|
165
|
+
// Partial match (e.g., "senior-developer" matches "developer")
|
|
166
|
+
for (const [pattern, profile] of Object.entries(ROLE_PROFILE_MAP)) {
|
|
167
|
+
if (roleName.includes(pattern)) return profile;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return "";
|
|
171
|
+
}
|
|
172
|
+
|
|
138
173
|
/**
|
|
139
174
|
* Compile skill loadouts for all roles in a team manifest.
|
|
140
175
|
* Reads the skilltree extension from the manifest, compiles loadouts per role.
|
|
176
|
+
* Returns metadata alongside content for richer agent context.
|
|
141
177
|
*
|
|
142
178
|
* @param {object} manifest - Parsed team.yaml manifest
|
|
143
179
|
* @param {object} config - Plugin config (skilltree section)
|
|
144
|
-
* @returns {Promise<object>} Map of roleName →
|
|
180
|
+
* @returns {Promise<object>} Map of roleName → { content, profile }
|
|
145
181
|
*/
|
|
146
182
|
export async function compileAllRoleLoadouts(manifest, config) {
|
|
147
183
|
const { defaults, roles: roleOverrides } = parseSkillTreeExtension(manifest);
|
|
@@ -152,20 +188,29 @@ export async function compileAllRoleLoadouts(manifest, config) {
|
|
|
152
188
|
// Merge defaults with role-specific overrides
|
|
153
189
|
const roleCriteria = roleOverrides[roleName]
|
|
154
190
|
? { ...defaults, ...roleOverrides[roleName] }
|
|
155
|
-
: defaults;
|
|
191
|
+
: { ...defaults };
|
|
156
192
|
|
|
157
|
-
//
|
|
193
|
+
// Fallback chain for profile selection
|
|
158
194
|
if (!roleCriteria.profile && !roleCriteria.tags && !roleCriteria.include && !roleCriteria.taskDescription) {
|
|
159
195
|
if (config?.defaultProfile) {
|
|
160
196
|
roleCriteria.profile = config.defaultProfile;
|
|
161
197
|
} else {
|
|
162
|
-
|
|
198
|
+
// Auto-infer from role name
|
|
199
|
+
const inferred = inferProfileFromRole(roleName);
|
|
200
|
+
if (inferred) {
|
|
201
|
+
roleCriteria.profile = inferred;
|
|
202
|
+
} else {
|
|
203
|
+
continue; // No criteria at all — skip
|
|
204
|
+
}
|
|
163
205
|
}
|
|
164
206
|
}
|
|
165
207
|
|
|
166
208
|
const loadout = await compileRoleLoadout(roleName, roleCriteria, config);
|
|
167
209
|
if (loadout) {
|
|
168
|
-
result[roleName] =
|
|
210
|
+
result[roleName] = {
|
|
211
|
+
content: loadout,
|
|
212
|
+
profile: roleCriteria.profile || "",
|
|
213
|
+
};
|
|
169
214
|
}
|
|
170
215
|
}
|
|
171
216
|
|