claude-code-swarm 0.3.3 → 0.3.5
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 +22 -1
- package/.claude-plugin/run-agent-inbox-mcp.sh +76 -0
- package/.claude-plugin/run-minimem-mcp.sh +98 -0
- package/.claude-plugin/run-opentasks-mcp.sh +65 -0
- package/CLAUDE.md +200 -36
- package/README.md +65 -0
- package/e2e/helpers/cleanup.mjs +17 -3
- package/e2e/helpers/map-mock-server.mjs +201 -25
- package/e2e/helpers/sidecar.mjs +222 -0
- package/e2e/helpers/workspace.mjs +2 -1
- package/e2e/tier5-sidecar-inbox.test.mjs +900 -0
- package/e2e/tier6-inbox-mcp.test.mjs +173 -0
- package/e2e/tier6-live-agent.test.mjs +759 -0
- package/e2e/vitest.config.e2e.mjs +1 -1
- package/hooks/hooks.json +15 -8
- package/package.json +13 -1
- package/references/agent-inbox/CLAUDE.md +151 -0
- package/references/agent-inbox/README.md +238 -0
- package/references/agent-inbox/docs/CLAUDE-CODE-SWARM-PROPOSAL.md +137 -0
- package/references/agent-inbox/docs/DESIGN.md +1156 -0
- package/references/agent-inbox/hooks/inbox-hook.mjs +119 -0
- package/references/agent-inbox/hooks/register-hook.mjs +69 -0
- package/references/agent-inbox/package-lock.json +3347 -0
- package/references/agent-inbox/package.json +58 -0
- package/references/agent-inbox/rules/agent-inbox.md +78 -0
- package/references/agent-inbox/src/federation/address.ts +61 -0
- package/references/agent-inbox/src/federation/connection-manager.ts +573 -0
- package/references/agent-inbox/src/federation/delivery-queue.ts +222 -0
- package/references/agent-inbox/src/federation/index.ts +6 -0
- package/references/agent-inbox/src/federation/routing-engine.ts +188 -0
- package/references/agent-inbox/src/federation/trust.ts +71 -0
- package/references/agent-inbox/src/index.ts +390 -0
- package/references/agent-inbox/src/ipc/ipc-server.ts +207 -0
- package/references/agent-inbox/src/jsonrpc/mail-server.ts +382 -0
- package/references/agent-inbox/src/map/map-client.ts +414 -0
- package/references/agent-inbox/src/mcp/mcp-server.ts +272 -0
- package/references/agent-inbox/src/mesh/delivery-bridge.ts +110 -0
- package/references/agent-inbox/src/mesh/mesh-connector.ts +41 -0
- package/references/agent-inbox/src/mesh/mesh-transport.ts +157 -0
- package/references/agent-inbox/src/mesh/type-mapper.ts +239 -0
- package/references/agent-inbox/src/push/notifier.ts +233 -0
- package/references/agent-inbox/src/registry/warm-registry.ts +255 -0
- package/references/agent-inbox/src/router/message-router.ts +175 -0
- package/references/agent-inbox/src/storage/interface.ts +48 -0
- package/references/agent-inbox/src/storage/memory.ts +145 -0
- package/references/agent-inbox/src/storage/sqlite.ts +671 -0
- package/references/agent-inbox/src/traceability/traceability.ts +183 -0
- package/references/agent-inbox/src/types.ts +303 -0
- package/references/agent-inbox/test/federation/address.test.ts +101 -0
- package/references/agent-inbox/test/federation/connection-manager.test.ts +546 -0
- package/references/agent-inbox/test/federation/delivery-queue.test.ts +159 -0
- package/references/agent-inbox/test/federation/integration.test.ts +857 -0
- package/references/agent-inbox/test/federation/routing-engine.test.ts +117 -0
- package/references/agent-inbox/test/federation/sdk-integration.test.ts +744 -0
- package/references/agent-inbox/test/federation/trust.test.ts +89 -0
- package/references/agent-inbox/test/ipc-jsonrpc.test.ts +113 -0
- package/references/agent-inbox/test/ipc-server.test.ts +197 -0
- package/references/agent-inbox/test/mail-server.test.ts +285 -0
- package/references/agent-inbox/test/map-client.test.ts +408 -0
- package/references/agent-inbox/test/mesh/delivery-bridge.test.ts +178 -0
- package/references/agent-inbox/test/mesh/e2e-mesh.test.ts +527 -0
- package/references/agent-inbox/test/mesh/e2e-real-meshpeer.test.ts +629 -0
- package/references/agent-inbox/test/mesh/federation-mesh.test.ts +269 -0
- package/references/agent-inbox/test/mesh/mesh-connector.test.ts +66 -0
- package/references/agent-inbox/test/mesh/mesh-transport.test.ts +191 -0
- package/references/agent-inbox/test/mesh/meshpeer-integration.test.ts +442 -0
- package/references/agent-inbox/test/mesh/mock-mesh.ts +125 -0
- package/references/agent-inbox/test/mesh/mock-meshpeer.ts +266 -0
- package/references/agent-inbox/test/mesh/type-mapper.test.ts +226 -0
- package/references/agent-inbox/test/message-router.test.ts +184 -0
- package/references/agent-inbox/test/push-notifier.test.ts +139 -0
- package/references/agent-inbox/test/registry/warm-registry.test.ts +171 -0
- package/references/agent-inbox/test/sqlite-prefix.test.ts +192 -0
- package/references/agent-inbox/test/sqlite-storage.test.ts +243 -0
- package/references/agent-inbox/test/storage.test.ts +196 -0
- package/references/agent-inbox/test/traceability.test.ts +123 -0
- package/references/agent-inbox/test/wake.test.ts +330 -0
- package/references/agent-inbox/tsconfig.json +20 -0
- package/references/agent-inbox/tsup.config.ts +10 -0
- package/references/agent-inbox/vitest.config.ts +8 -0
- package/references/minimem/.claude/settings.json +7 -0
- package/references/minimem/.sudocode/issues.jsonl +18 -0
- package/references/minimem/.sudocode/specs.jsonl +1 -0
- package/references/minimem/CLAUDE.md +329 -0
- package/references/minimem/README.md +565 -0
- package/references/minimem/claude-plugin/.claude-plugin/plugin.json +10 -0
- package/references/minimem/claude-plugin/.mcp.json +7 -0
- package/references/minimem/claude-plugin/README.md +158 -0
- package/references/minimem/claude-plugin/commands/recall.md +47 -0
- package/references/minimem/claude-plugin/commands/remember.md +41 -0
- package/references/minimem/claude-plugin/hooks/__tests__/hooks.test.ts +272 -0
- package/references/minimem/claude-plugin/hooks/hooks.json +27 -0
- package/references/minimem/claude-plugin/hooks/session-end.sh +86 -0
- package/references/minimem/claude-plugin/hooks/session-start.sh +85 -0
- package/references/minimem/claude-plugin/skills/memory/SKILL.md +108 -0
- package/references/minimem/media/banner.png +0 -0
- package/references/minimem/package-lock.json +5373 -0
- package/references/minimem/package.json +76 -0
- package/references/minimem/scripts/postbuild.js +49 -0
- package/references/minimem/src/__tests__/edge-cases.test.ts +371 -0
- package/references/minimem/src/__tests__/errors.test.ts +265 -0
- package/references/minimem/src/__tests__/helpers.ts +199 -0
- package/references/minimem/src/__tests__/internal.test.ts +407 -0
- package/references/minimem/src/__tests__/knowledge-frontmatter.test.ts +148 -0
- package/references/minimem/src/__tests__/knowledge.test.ts +148 -0
- package/references/minimem/src/__tests__/minimem.integration.test.ts +1127 -0
- package/references/minimem/src/__tests__/session.test.ts +190 -0
- package/references/minimem/src/cli/__tests__/commands.test.ts +760 -0
- package/references/minimem/src/cli/__tests__/contained-layout.test.ts +286 -0
- package/references/minimem/src/cli/commands/__tests__/conflicts.test.ts +141 -0
- package/references/minimem/src/cli/commands/append.ts +76 -0
- package/references/minimem/src/cli/commands/config.ts +262 -0
- package/references/minimem/src/cli/commands/conflicts.ts +415 -0
- package/references/minimem/src/cli/commands/daemon.ts +169 -0
- package/references/minimem/src/cli/commands/index.ts +12 -0
- package/references/minimem/src/cli/commands/init.ts +166 -0
- package/references/minimem/src/cli/commands/mcp.ts +221 -0
- package/references/minimem/src/cli/commands/push-pull.ts +213 -0
- package/references/minimem/src/cli/commands/search.ts +223 -0
- package/references/minimem/src/cli/commands/status.ts +84 -0
- package/references/minimem/src/cli/commands/store.ts +189 -0
- package/references/minimem/src/cli/commands/sync-init.ts +290 -0
- package/references/minimem/src/cli/commands/sync.ts +70 -0
- package/references/minimem/src/cli/commands/upsert.ts +197 -0
- package/references/minimem/src/cli/config.ts +611 -0
- package/references/minimem/src/cli/index.ts +299 -0
- package/references/minimem/src/cli/shared.ts +189 -0
- package/references/minimem/src/cli/sync/__tests__/central.test.ts +152 -0
- package/references/minimem/src/cli/sync/__tests__/conflicts.test.ts +209 -0
- package/references/minimem/src/cli/sync/__tests__/daemon.test.ts +118 -0
- package/references/minimem/src/cli/sync/__tests__/detection.test.ts +207 -0
- package/references/minimem/src/cli/sync/__tests__/integration.test.ts +476 -0
- package/references/minimem/src/cli/sync/__tests__/registry.test.ts +363 -0
- package/references/minimem/src/cli/sync/__tests__/state.test.ts +255 -0
- package/references/minimem/src/cli/sync/__tests__/validation.test.ts +193 -0
- package/references/minimem/src/cli/sync/__tests__/watcher.test.ts +178 -0
- package/references/minimem/src/cli/sync/central.ts +292 -0
- package/references/minimem/src/cli/sync/conflicts.ts +205 -0
- package/references/minimem/src/cli/sync/daemon.ts +407 -0
- package/references/minimem/src/cli/sync/detection.ts +138 -0
- package/references/minimem/src/cli/sync/index.ts +107 -0
- package/references/minimem/src/cli/sync/operations.ts +373 -0
- package/references/minimem/src/cli/sync/registry.ts +279 -0
- package/references/minimem/src/cli/sync/state.ts +358 -0
- package/references/minimem/src/cli/sync/validation.ts +206 -0
- package/references/minimem/src/cli/sync/watcher.ts +237 -0
- package/references/minimem/src/cli/version.ts +34 -0
- package/references/minimem/src/core/index.ts +9 -0
- package/references/minimem/src/core/indexer.ts +628 -0
- package/references/minimem/src/core/searcher.ts +221 -0
- package/references/minimem/src/db/schema.ts +183 -0
- package/references/minimem/src/db/sqlite-vec.ts +24 -0
- package/references/minimem/src/embeddings/__tests__/embeddings.test.ts +431 -0
- package/references/minimem/src/embeddings/batch-gemini.ts +392 -0
- package/references/minimem/src/embeddings/batch-openai.ts +409 -0
- package/references/minimem/src/embeddings/embeddings.ts +434 -0
- package/references/minimem/src/index.ts +132 -0
- package/references/minimem/src/internal.ts +299 -0
- package/references/minimem/src/minimem.ts +1291 -0
- package/references/minimem/src/search/__tests__/hybrid.test.ts +247 -0
- package/references/minimem/src/search/graph.ts +234 -0
- package/references/minimem/src/search/hybrid.ts +151 -0
- package/references/minimem/src/search/search.ts +256 -0
- package/references/minimem/src/server/__tests__/mcp.test.ts +347 -0
- package/references/minimem/src/server/__tests__/tools.test.ts +364 -0
- package/references/minimem/src/server/mcp.ts +326 -0
- package/references/minimem/src/server/tools.ts +720 -0
- package/references/minimem/src/session.ts +460 -0
- package/references/minimem/src/store/__tests__/manifest.test.ts +177 -0
- package/references/minimem/src/store/__tests__/materialize.test.ts +52 -0
- package/references/minimem/src/store/__tests__/store-graph.test.ts +228 -0
- package/references/minimem/src/store/index.ts +27 -0
- package/references/minimem/src/store/manifest.ts +203 -0
- package/references/minimem/src/store/materialize.ts +185 -0
- package/references/minimem/src/store/store-graph.ts +252 -0
- package/references/minimem/tsconfig.json +19 -0
- package/references/minimem/tsup.config.ts +26 -0
- package/references/minimem/vitest.config.ts +29 -0
- package/references/openteams/src/cli/generate.ts +23 -1
- package/references/openteams/src/generators/agent-prompt-generator.test.ts +94 -0
- package/references/openteams/src/generators/agent-prompt-generator.ts +42 -13
- package/references/openteams/src/generators/package-generator.ts +9 -1
- package/references/openteams/src/generators/skill-generator.test.ts +28 -0
- package/references/openteams/src/generators/skill-generator.ts +10 -4
- package/references/skill-tree/.claude/settings.json +6 -0
- package/references/skill-tree/.sudocode/issues.jsonl +19 -0
- package/references/skill-tree/.sudocode/specs.jsonl +3 -0
- package/references/skill-tree/CLAUDE.md +132 -0
- package/references/skill-tree/README.md +396 -0
- package/references/skill-tree/docs/GAPS_v1.md +221 -0
- package/references/skill-tree/docs/INTEGRATION_PLAN.md +467 -0
- package/references/skill-tree/docs/TODOS.md +91 -0
- package/references/skill-tree/docs/anthropic_skill_guide.md +1364 -0
- package/references/skill-tree/docs/design/federated-skill-trees.md +524 -0
- package/references/skill-tree/docs/design/multi-agent-sync.md +759 -0
- package/references/skill-tree/docs/scraper/BRAINSTORM.md +583 -0
- package/references/skill-tree/docs/scraper/POC_PLAN.md +420 -0
- package/references/skill-tree/docs/scraper/README.md +170 -0
- package/references/skill-tree/examples/basic-usage.ts +157 -0
- package/references/skill-tree/package-lock.json +1852 -0
- package/references/skill-tree/package.json +66 -0
- package/references/skill-tree/plan.md +78 -0
- package/references/skill-tree/scraper/README.md +123 -0
- package/references/skill-tree/scraper/docs/DESIGN.md +683 -0
- package/references/skill-tree/scraper/docs/PLAN.md +336 -0
- package/references/skill-tree/scraper/drizzle.config.ts +10 -0
- package/references/skill-tree/scraper/package-lock.json +6329 -0
- package/references/skill-tree/scraper/package.json +68 -0
- package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-description.md +7 -0
- package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-name.md +7 -0
- package/references/skill-tree/scraper/test/fixtures/minimal-skill/SKILL.md +27 -0
- package/references/skill-tree/scraper/test/fixtures/skill-json/SKILL.json +21 -0
- package/references/skill-tree/scraper/test/fixtures/skill-with-meta/SKILL.md +54 -0
- package/references/skill-tree/scraper/test/fixtures/skill-with-meta/_meta.json +24 -0
- package/references/skill-tree/scraper/test/fixtures/valid-skill/SKILL.md +93 -0
- package/references/skill-tree/scraper/test/fixtures/valid-skill/_meta.json +22 -0
- package/references/skill-tree/scraper/tsup.config.ts +14 -0
- package/references/skill-tree/scraper/vitest.config.ts +17 -0
- package/references/skill-tree/scripts/convert-to-vitest.ts +166 -0
- package/references/skill-tree/skills/skill-writer/SKILL.md +339 -0
- package/references/skill-tree/skills/skill-writer/references/examples.md +326 -0
- package/references/skill-tree/skills/skill-writer/references/patterns.md +210 -0
- package/references/skill-tree/skills/skill-writer/references/quality-checklist.md +123 -0
- package/references/skill-tree/test/run-all.ts +106 -0
- package/references/skill-tree/test/utils.ts +128 -0
- package/references/skill-tree/vitest.config.ts +16 -0
- package/references/swarmkit/src/commands/init/phases/configure.ts +0 -22
- package/references/swarmkit/src/commands/init/phases/global-setup.ts +5 -3
- package/references/swarmkit/src/commands/init/wizard.ts +2 -2
- package/references/swarmkit/src/packages/setup.test.ts +53 -7
- package/references/swarmkit/src/packages/setup.ts +37 -1
- package/scripts/bootstrap.mjs +26 -1
- package/scripts/generate-agents.mjs +5 -1
- package/scripts/map-hook.mjs +97 -64
- package/scripts/map-sidecar.mjs +179 -25
- package/scripts/team-loader.mjs +12 -41
- package/skills/swarm/SKILL.md +89 -25
- package/src/__tests__/agent-generator.test.mjs +6 -13
- package/src/__tests__/bootstrap.test.mjs +124 -1
- package/src/__tests__/config.test.mjs +200 -27
- package/src/__tests__/e2e-live-map.test.mjs +536 -0
- package/src/__tests__/e2e-mesh-sidecar.test.mjs +570 -0
- package/src/__tests__/e2e-native-task-hooks.test.mjs +376 -0
- package/src/__tests__/e2e-sidecar-bridge.test.mjs +477 -0
- package/src/__tests__/helpers.mjs +13 -0
- package/src/__tests__/inbox.test.mjs +22 -89
- package/src/__tests__/index.test.mjs +35 -9
- package/src/__tests__/integration.test.mjs +513 -0
- package/src/__tests__/map-events.test.mjs +514 -150
- package/src/__tests__/mesh-connection.test.mjs +308 -0
- package/src/__tests__/opentasks-client.test.mjs +517 -0
- package/src/__tests__/paths.test.mjs +185 -41
- package/src/__tests__/sidecar-client.test.mjs +35 -0
- package/src/__tests__/sidecar-server.test.mjs +124 -0
- package/src/__tests__/skilltree-client.test.mjs +80 -0
- package/src/agent-generator.mjs +104 -33
- package/src/bootstrap.mjs +150 -10
- package/src/config.mjs +81 -17
- package/src/context-output.mjs +58 -8
- package/src/inbox.mjs +9 -54
- package/src/index.mjs +39 -8
- package/src/map-connection.mjs +4 -3
- package/src/map-events.mjs +350 -80
- package/src/mesh-connection.mjs +148 -0
- package/src/opentasks-client.mjs +269 -0
- package/src/paths.mjs +182 -27
- package/src/sessionlog.mjs +14 -9
- package/src/sidecar-client.mjs +81 -27
- package/src/sidecar-server.mjs +175 -16
- package/src/skilltree-client.mjs +173 -0
- package/src/template.mjs +68 -4
- package/vitest.config.mjs +1 -0
package/src/sidecar-client.mjs
CHANGED
|
@@ -2,14 +2,47 @@
|
|
|
2
2
|
* sidecar-client.mjs — UNIX socket client for communicating with the MAP sidecar
|
|
3
3
|
*
|
|
4
4
|
* Provides send, health check, and recovery logic used by hooks.
|
|
5
|
+
* Supports per-session sidecar instances via sessionId parameter.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import fs from "fs";
|
|
8
9
|
import path from "path";
|
|
9
10
|
import net from "net";
|
|
10
11
|
import { spawn } from "child_process";
|
|
11
|
-
import { SOCKET_PATH, PID_PATH, pluginDir } from "./paths.mjs";
|
|
12
|
-
import { resolveScope, DEFAULTS } from "./config.mjs";
|
|
12
|
+
import { SOCKET_PATH, PID_PATH, pluginDir, sessionPaths } from "./paths.mjs";
|
|
13
|
+
import { resolveScope, resolveMapServer, DEFAULTS } from "./config.mjs";
|
|
14
|
+
import { meshFireAndForget } from "./mesh-connection.mjs";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Send a command to the agent-inbox IPC socket and return the response.
|
|
18
|
+
* Returns the parsed response or null on failure. Never throws.
|
|
19
|
+
*/
|
|
20
|
+
export function sendToInbox(command, socketPath) {
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
const client = net.createConnection(socketPath, () => {
|
|
23
|
+
client.write(JSON.stringify(command) + "\n");
|
|
24
|
+
});
|
|
25
|
+
let buffer = "";
|
|
26
|
+
client.on("data", (data) => {
|
|
27
|
+
buffer += data.toString();
|
|
28
|
+
const idx = buffer.indexOf("\n");
|
|
29
|
+
if (idx !== -1) {
|
|
30
|
+
const line = buffer.slice(0, idx);
|
|
31
|
+
client.end();
|
|
32
|
+
try {
|
|
33
|
+
resolve(JSON.parse(line));
|
|
34
|
+
} catch {
|
|
35
|
+
resolve(null);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
client.on("error", () => resolve(null));
|
|
40
|
+
setTimeout(() => {
|
|
41
|
+
client.destroy();
|
|
42
|
+
resolve(null);
|
|
43
|
+
}, 2000);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
13
46
|
|
|
14
47
|
/**
|
|
15
48
|
* Send a command to the sidecar via UNIX socket.
|
|
@@ -33,9 +66,9 @@ export function sendToSidecar(command, socketPath = SOCKET_PATH) {
|
|
|
33
66
|
/**
|
|
34
67
|
* Check if the sidecar process is alive via PID file.
|
|
35
68
|
*/
|
|
36
|
-
export function isSidecarAlive() {
|
|
69
|
+
export function isSidecarAlive(pidPath = PID_PATH) {
|
|
37
70
|
try {
|
|
38
|
-
const pid = parseInt(fs.readFileSync(
|
|
71
|
+
const pid = parseInt(fs.readFileSync(pidPath, "utf-8").trim());
|
|
39
72
|
process.kill(pid, 0);
|
|
40
73
|
return true;
|
|
41
74
|
} catch {
|
|
@@ -46,35 +79,47 @@ export function isSidecarAlive() {
|
|
|
46
79
|
/**
|
|
47
80
|
* Start the sidecar as a detached background process.
|
|
48
81
|
* Writes PID file and waits for socket to appear (up to 2s).
|
|
82
|
+
* When sessionId is provided, the sidecar uses per-session paths.
|
|
49
83
|
* Returns true if sidecar is ready.
|
|
50
84
|
*/
|
|
51
|
-
export async function startSidecar(config, pluginDirOverride) {
|
|
85
|
+
export async function startSidecar(config, pluginDirOverride, sessionId) {
|
|
52
86
|
const dir = pluginDirOverride || pluginDir();
|
|
53
87
|
const sidecarPath = path.join(dir, "scripts", "map-sidecar.mjs");
|
|
88
|
+
const sPaths = sessionPaths(sessionId);
|
|
54
89
|
|
|
55
|
-
const server = config
|
|
90
|
+
const server = resolveMapServer(config);
|
|
56
91
|
const scope = resolveScope(config);
|
|
57
92
|
const systemId = config.map?.systemId || DEFAULTS.mapSystemId;
|
|
58
93
|
|
|
59
94
|
try {
|
|
60
|
-
fs.mkdirSync(path.dirname(
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
95
|
+
fs.mkdirSync(path.dirname(sPaths.pidPath), { recursive: true });
|
|
96
|
+
|
|
97
|
+
const args = [sidecarPath, "--server", server, "--scope", scope, "--system-id", systemId];
|
|
98
|
+
if (sessionId) {
|
|
99
|
+
args.push("--session-id", sessionId);
|
|
100
|
+
}
|
|
101
|
+
if (config.inbox?.enabled) {
|
|
102
|
+
args.push("--inbox-config", JSON.stringify(config.inbox));
|
|
103
|
+
}
|
|
104
|
+
if (config.mesh?.enabled) {
|
|
105
|
+
args.push("--mesh-enabled");
|
|
106
|
+
if (config.mesh.peerId) {
|
|
107
|
+
args.push("--mesh-peer-id", config.mesh.peerId);
|
|
68
108
|
}
|
|
69
|
-
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const child = spawn("node", args, {
|
|
112
|
+
detached: true,
|
|
113
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
114
|
+
});
|
|
70
115
|
child.unref();
|
|
71
|
-
fs.writeFileSync(
|
|
116
|
+
fs.writeFileSync(sPaths.pidPath, String(child.pid));
|
|
72
117
|
|
|
73
118
|
// Wait for socket to appear (up to 2s)
|
|
74
119
|
for (let i = 0; i < 8; i++) {
|
|
75
120
|
await new Promise((r) => setTimeout(r, 250));
|
|
76
|
-
if (fs.existsSync(
|
|
77
|
-
const ok = await sendToSidecar({ action: "ping" });
|
|
121
|
+
if (fs.existsSync(sPaths.socketPath)) {
|
|
122
|
+
const ok = await sendToSidecar({ action: "ping" }, sPaths.socketPath);
|
|
78
123
|
if (ok) return true;
|
|
79
124
|
}
|
|
80
125
|
}
|
|
@@ -87,21 +132,28 @@ export async function startSidecar(config, pluginDirOverride) {
|
|
|
87
132
|
|
|
88
133
|
/**
|
|
89
134
|
* Kill an existing sidecar process (for session restart).
|
|
135
|
+
* When sessionId is provided, kills only that session's sidecar.
|
|
90
136
|
*/
|
|
91
|
-
export function killSidecar() {
|
|
137
|
+
export function killSidecar(sessionId) {
|
|
138
|
+
const sPaths = sessionPaths(sessionId);
|
|
92
139
|
try {
|
|
93
|
-
const pid = parseInt(fs.readFileSync(
|
|
140
|
+
const pid = parseInt(fs.readFileSync(sPaths.pidPath, "utf-8").trim());
|
|
94
141
|
process.kill(pid);
|
|
95
142
|
} catch {
|
|
96
143
|
// Process already dead or PID file missing
|
|
97
144
|
}
|
|
98
145
|
try {
|
|
99
|
-
fs.unlinkSync(
|
|
146
|
+
fs.unlinkSync(sPaths.pidPath);
|
|
147
|
+
} catch {
|
|
148
|
+
// ignore
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
fs.unlinkSync(sPaths.socketPath);
|
|
100
152
|
} catch {
|
|
101
153
|
// ignore
|
|
102
154
|
}
|
|
103
155
|
try {
|
|
104
|
-
fs.unlinkSync(
|
|
156
|
+
fs.unlinkSync(sPaths.inboxSocketPath);
|
|
105
157
|
} catch {
|
|
106
158
|
// ignore
|
|
107
159
|
}
|
|
@@ -111,21 +163,23 @@ export function killSidecar() {
|
|
|
111
163
|
* Ensure the sidecar is running. If not, attempt recovery.
|
|
112
164
|
* Returns true if sidecar is available after this call.
|
|
113
165
|
*/
|
|
114
|
-
export async function ensureSidecar(config) {
|
|
166
|
+
export async function ensureSidecar(config, sessionId) {
|
|
167
|
+
const sPaths = sessionPaths(sessionId);
|
|
168
|
+
|
|
115
169
|
// 1. Try pinging
|
|
116
|
-
const alive = await sendToSidecar({ action: "ping" });
|
|
170
|
+
const alive = await sendToSidecar({ action: "ping" }, sPaths.socketPath);
|
|
117
171
|
if (alive) return true;
|
|
118
172
|
|
|
119
173
|
// 2. Only attempt recovery in session mode
|
|
120
174
|
if (config.map?.sidecar === "persistent") return false;
|
|
121
175
|
|
|
122
176
|
// 3. Check PID
|
|
123
|
-
if (isSidecarAlive()) {
|
|
177
|
+
if (isSidecarAlive(sPaths.pidPath)) {
|
|
124
178
|
// Process exists but socket not ready — wait briefly
|
|
125
179
|
await new Promise((r) => setTimeout(r, 500));
|
|
126
|
-
return sendToSidecar({ action: "ping" });
|
|
180
|
+
return sendToSidecar({ action: "ping" }, sPaths.socketPath);
|
|
127
181
|
}
|
|
128
182
|
|
|
129
183
|
// 4. Restart sidecar
|
|
130
|
-
return startSidecar(config);
|
|
184
|
+
return startSidecar(config, undefined, sessionId);
|
|
131
185
|
}
|
package/src/sidecar-server.mjs
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides the socket server that hooks communicate with, and the command
|
|
5
5
|
* dispatch logic for all sidecar operations.
|
|
6
|
+
*
|
|
7
|
+
* Supports two transport modes:
|
|
8
|
+
* - "mesh": Uses inbox registry for agent lifecycle (spawn/done), MeshPeer
|
|
9
|
+
* connection for task bridge events and trajectory.
|
|
10
|
+
* - "websocket": Uses MAP SDK primitives directly (legacy mode).
|
|
6
11
|
*/
|
|
7
12
|
|
|
8
13
|
import fs from "fs";
|
|
@@ -37,7 +42,13 @@ export function createSocketServer(socketPath, onCommand) {
|
|
|
37
42
|
if (!line.trim()) continue;
|
|
38
43
|
try {
|
|
39
44
|
const command = JSON.parse(line);
|
|
40
|
-
|
|
45
|
+
// Must await the async handler to catch SDK errors;
|
|
46
|
+
// without this, rejections become uncaught and crash the process.
|
|
47
|
+
onCommand(command, client).catch((err) => {
|
|
48
|
+
process.stderr.write(
|
|
49
|
+
`[sidecar] Async command error (${command.action}): ${err.message}\n`
|
|
50
|
+
);
|
|
51
|
+
});
|
|
41
52
|
} catch (err) {
|
|
42
53
|
process.stderr.write(
|
|
43
54
|
`[sidecar] Invalid command: ${err.message}\n`
|
|
@@ -67,19 +78,27 @@ export function createSocketServer(socketPath, onCommand) {
|
|
|
67
78
|
/**
|
|
68
79
|
* Create a command handler for the sidecar socket server.
|
|
69
80
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* typed message payloads (task lifecycle). No custom swarm.* event types.
|
|
81
|
+
* In mesh mode, agent lifecycle (spawn/done) is delegated to the inbox
|
|
82
|
+
* registry when available, providing structured agent management. Task
|
|
83
|
+
* bridge events and other MAP primitives still go through the connection.
|
|
74
84
|
*
|
|
75
|
-
*
|
|
85
|
+
* In websocket mode, all operations use MAP SDK primitives directly
|
|
86
|
+
* (conn.spawn, conn.updateState, conn.send, etc.).
|
|
87
|
+
*
|
|
88
|
+
* @param {object|null} connection - MAP AgentConnection or MeshPeer connection
|
|
76
89
|
* @param {string} scope - MAP scope name
|
|
77
90
|
* @param {Map} registeredAgents - Map of agentId → spawn metadata
|
|
91
|
+
* @param {object} [opts] - Additional options
|
|
92
|
+
* @param {object} [opts.inboxInstance] - Agent-inbox instance (mesh mode)
|
|
93
|
+
* @param {object} [opts.meshPeer] - MeshPeer instance (mesh mode, for agent registration)
|
|
94
|
+
* @param {string} [opts.transportMode] - "mesh" or "websocket"
|
|
78
95
|
* @returns {Function} async (command, client) => void
|
|
79
96
|
*/
|
|
80
|
-
export function createCommandHandler(connection, scope, registeredAgents) {
|
|
97
|
+
export function createCommandHandler(connection, scope, registeredAgents, opts = {}) {
|
|
81
98
|
// Use a getter pattern so the connection ref can be updated
|
|
82
99
|
let conn = connection;
|
|
100
|
+
const { inboxInstance, meshPeer, transportMode = "websocket" } = opts;
|
|
101
|
+
const useMeshRegistry = transportMode === "mesh" && inboxInstance;
|
|
83
102
|
|
|
84
103
|
const handler = async (command, client) => {
|
|
85
104
|
const { action } = command;
|
|
@@ -106,12 +125,48 @@ export function createCommandHandler(connection, scope, registeredAgents) {
|
|
|
106
125
|
break;
|
|
107
126
|
}
|
|
108
127
|
|
|
109
|
-
// ---
|
|
128
|
+
// --- Agent lifecycle ---
|
|
129
|
+
// In mesh mode: delegate to inbox registry
|
|
130
|
+
// In websocket mode: use MAP SDK primitives
|
|
110
131
|
|
|
111
132
|
case "spawn": {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
133
|
+
const { agentId, name, role, scopes: agentScopes, metadata } =
|
|
134
|
+
command.agent;
|
|
135
|
+
|
|
136
|
+
if (useMeshRegistry) {
|
|
137
|
+
// Mesh mode: register via inbox registry + MeshPeer MapServer
|
|
138
|
+
try {
|
|
139
|
+
// Register in inbox storage for message routing
|
|
140
|
+
if (inboxInstance.storage) {
|
|
141
|
+
inboxInstance.storage.putAgent({
|
|
142
|
+
agent_id: agentId,
|
|
143
|
+
scope,
|
|
144
|
+
status: "active",
|
|
145
|
+
metadata: { name, role, ...metadata },
|
|
146
|
+
registered_at: new Date().toISOString(),
|
|
147
|
+
last_active_at: new Date().toISOString(),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
registeredAgents.set(agentId, { name, role, metadata });
|
|
151
|
+
|
|
152
|
+
// Also register on the MeshPeer's MapServer for observability
|
|
153
|
+
if (meshPeer) {
|
|
154
|
+
try {
|
|
155
|
+
await meshPeer.createAgent({ agentId, name, role, metadata });
|
|
156
|
+
} catch {
|
|
157
|
+
// Best-effort — agent may already exist
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
respond(client, { ok: true, agent: { agentId, name, role } });
|
|
162
|
+
} catch (err) {
|
|
163
|
+
process.stderr.write(
|
|
164
|
+
`[sidecar] spawn (mesh) failed: ${err.message}\n`
|
|
165
|
+
);
|
|
166
|
+
respond(client, { ok: false, error: err.message });
|
|
167
|
+
}
|
|
168
|
+
} else if (conn) {
|
|
169
|
+
// WebSocket mode: use MAP SDK
|
|
115
170
|
try {
|
|
116
171
|
const result = await conn.spawn({
|
|
117
172
|
agentId,
|
|
@@ -135,8 +190,40 @@ export function createCommandHandler(connection, scope, registeredAgents) {
|
|
|
135
190
|
}
|
|
136
191
|
|
|
137
192
|
case "done": {
|
|
138
|
-
|
|
139
|
-
|
|
193
|
+
const { agentId, reason } = command;
|
|
194
|
+
|
|
195
|
+
if (useMeshRegistry) {
|
|
196
|
+
// Mesh mode: disconnect via inbox registry + MeshPeer MapServer
|
|
197
|
+
try {
|
|
198
|
+
// Remove from inbox storage
|
|
199
|
+
if (inboxInstance.storage) {
|
|
200
|
+
try {
|
|
201
|
+
inboxInstance.storage.putAgent({
|
|
202
|
+
agent_id: agentId,
|
|
203
|
+
scope,
|
|
204
|
+
status: "disconnected",
|
|
205
|
+
metadata: {},
|
|
206
|
+
registered_at: new Date().toISOString(),
|
|
207
|
+
last_active_at: new Date().toISOString(),
|
|
208
|
+
});
|
|
209
|
+
} catch { /* best-effort */ }
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
212
|
+
// Agent may already be gone
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Also unregister from MeshPeer's MapServer
|
|
216
|
+
if (meshPeer) {
|
|
217
|
+
try {
|
|
218
|
+
meshPeer.server.unregisterAgent(agentId);
|
|
219
|
+
} catch {
|
|
220
|
+
// Best-effort
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
registeredAgents.delete(agentId);
|
|
225
|
+
} else if (conn) {
|
|
226
|
+
// WebSocket mode: use MAP SDK
|
|
140
227
|
try {
|
|
141
228
|
await conn.callExtension("map/agents/unregister", {
|
|
142
229
|
agentId,
|
|
@@ -184,12 +271,84 @@ export function createCommandHandler(connection, scope, registeredAgents) {
|
|
|
184
271
|
break;
|
|
185
272
|
}
|
|
186
273
|
|
|
274
|
+
// --- Task event bridge (opentasks → MAP) ---
|
|
275
|
+
// These emit task events over the shared connection using
|
|
276
|
+
// the opentasks event bridge pattern. Works identically in
|
|
277
|
+
// both mesh and websocket modes.
|
|
278
|
+
|
|
279
|
+
case "bridge-task-created": {
|
|
280
|
+
if (conn) {
|
|
281
|
+
try {
|
|
282
|
+
await conn.send({ scope }, {
|
|
283
|
+
type: "task.created",
|
|
284
|
+
task: command.task,
|
|
285
|
+
_origin: command.agentId || "opentasks",
|
|
286
|
+
}, { relationship: "broadcast" });
|
|
287
|
+
} catch { /* best effort */ }
|
|
288
|
+
}
|
|
289
|
+
respond(client, { ok: true });
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
case "bridge-task-status": {
|
|
294
|
+
if (conn) {
|
|
295
|
+
try {
|
|
296
|
+
await conn.send({ scope }, {
|
|
297
|
+
type: "task.status",
|
|
298
|
+
taskId: command.taskId,
|
|
299
|
+
previous: command.previous || "open",
|
|
300
|
+
current: command.current,
|
|
301
|
+
_origin: command.agentId || "opentasks",
|
|
302
|
+
}, { relationship: "broadcast" });
|
|
303
|
+
// Also emit task.completed for terminal states
|
|
304
|
+
if (command.current === "completed" || command.current === "closed") {
|
|
305
|
+
await conn.send({ scope }, {
|
|
306
|
+
type: "task.completed",
|
|
307
|
+
taskId: command.taskId,
|
|
308
|
+
_origin: command.agentId || "opentasks",
|
|
309
|
+
}, { relationship: "broadcast" });
|
|
310
|
+
}
|
|
311
|
+
} catch { /* best effort */ }
|
|
312
|
+
}
|
|
313
|
+
respond(client, { ok: true });
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
case "bridge-task-assigned": {
|
|
318
|
+
if (conn) {
|
|
319
|
+
try {
|
|
320
|
+
await conn.send({ scope }, {
|
|
321
|
+
type: "task.assigned",
|
|
322
|
+
taskId: command.taskId,
|
|
323
|
+
agentId: command.assignee,
|
|
324
|
+
_origin: command.agentId || "opentasks",
|
|
325
|
+
}, { relationship: "broadcast" });
|
|
326
|
+
} catch { /* best effort */ }
|
|
327
|
+
}
|
|
328
|
+
respond(client, { ok: true });
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
|
|
187
332
|
case "state": {
|
|
188
333
|
if (conn) {
|
|
189
334
|
try {
|
|
190
335
|
if (command.agentId) {
|
|
191
|
-
// State update for a specific child agent
|
|
192
|
-
|
|
336
|
+
// State update for a specific child agent
|
|
337
|
+
let agentKey = command.agentId;
|
|
338
|
+
let existing = registeredAgents.get(agentKey);
|
|
339
|
+
if (!existing) {
|
|
340
|
+
// Extract role from fallback ID (e.g. "gsd-coordinator" → "coordinator")
|
|
341
|
+
const fallbackRole = command.agentId.includes("-")
|
|
342
|
+
? command.agentId.split("-").slice(1).join("-")
|
|
343
|
+
: command.agentId;
|
|
344
|
+
for (const [id, entry] of registeredAgents) {
|
|
345
|
+
if (entry.role === fallbackRole || id.endsWith(`/${fallbackRole}`)) {
|
|
346
|
+
agentKey = id;
|
|
347
|
+
existing = entry;
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
193
352
|
if (existing) {
|
|
194
353
|
existing.lastState = command.state;
|
|
195
354
|
if (command.metadata) {
|
|
@@ -212,7 +371,7 @@ export function createCommandHandler(connection, scope, registeredAgents) {
|
|
|
212
371
|
}
|
|
213
372
|
|
|
214
373
|
case "ping": {
|
|
215
|
-
respond(client, { ok: true, pid: process.pid });
|
|
374
|
+
respond(client, { ok: true, pid: process.pid, transport: transportMode });
|
|
216
375
|
break;
|
|
217
376
|
}
|
|
218
377
|
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* skilltree-client.mjs — Skill-tree loadout compilation for claude-code-swarm
|
|
3
|
+
*
|
|
4
|
+
* Compiles per-role skill loadouts from team.yaml skilltree extension.
|
|
5
|
+
* Uses skill-tree's SkillBank + SkillGraphServer programmatically.
|
|
6
|
+
* Results are cached per template alongside generated AGENT.md files.
|
|
7
|
+
*
|
|
8
|
+
* Never throws — returns empty results on any error (best-effort pattern).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import { createRequire } from "module";
|
|
14
|
+
import { getGlobalNodeModules } from "./swarmkit-resolver.mjs";
|
|
15
|
+
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
|
|
18
|
+
let _skillTree = undefined;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Load the skill-tree module. Returns null if not available.
|
|
22
|
+
* Tries local require first, then falls back to global node_modules.
|
|
23
|
+
*/
|
|
24
|
+
function loadSkillTree() {
|
|
25
|
+
if (_skillTree !== undefined) return _skillTree;
|
|
26
|
+
|
|
27
|
+
// 1. Local require (works if skill-tree is in node_modules or NODE_PATH)
|
|
28
|
+
try {
|
|
29
|
+
_skillTree = require("skill-tree");
|
|
30
|
+
return _skillTree;
|
|
31
|
+
} catch {
|
|
32
|
+
// Not locally available
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 2. Global node_modules fallback (where swarmkit installs it)
|
|
36
|
+
const globalNm = getGlobalNodeModules();
|
|
37
|
+
if (globalNm) {
|
|
38
|
+
const globalPath = path.join(globalNm, "skill-tree");
|
|
39
|
+
if (fs.existsSync(globalPath)) {
|
|
40
|
+
try {
|
|
41
|
+
_skillTree = require(globalPath);
|
|
42
|
+
return _skillTree;
|
|
43
|
+
} catch {
|
|
44
|
+
// require failed
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_skillTree = null;
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Parse the skilltree extension namespace from a team.yaml manifest.
|
|
55
|
+
* Returns { defaults, roles } where defaults is a LoadoutCriteria
|
|
56
|
+
* and roles is a map of roleName → LoadoutCriteria.
|
|
57
|
+
*
|
|
58
|
+
* team.yaml example:
|
|
59
|
+
* skilltree:
|
|
60
|
+
* defaults:
|
|
61
|
+
* profile: implementation
|
|
62
|
+
* maxSkills: 6
|
|
63
|
+
* roles:
|
|
64
|
+
* orchestrator:
|
|
65
|
+
* profile: code-review
|
|
66
|
+
* executor:
|
|
67
|
+
* profile: implementation
|
|
68
|
+
* tags: [development]
|
|
69
|
+
* verifier:
|
|
70
|
+
* profile: testing
|
|
71
|
+
*/
|
|
72
|
+
export function parseSkillTreeExtension(manifest) {
|
|
73
|
+
const ext = manifest?.skilltree;
|
|
74
|
+
if (!ext) return { defaults: {}, roles: {} };
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
defaults: ext.defaults || {},
|
|
78
|
+
roles: ext.roles || {},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Compile a skill loadout for a single role.
|
|
84
|
+
* Creates a temporary SkillBank, sets the loadout via criteria or profile,
|
|
85
|
+
* and returns the rendered markdown string.
|
|
86
|
+
*
|
|
87
|
+
* @param {string} roleName - Role name (for logging)
|
|
88
|
+
* @param {object} criteria - LoadoutCriteria (profile, tags, maxSkills, etc.)
|
|
89
|
+
* @param {object} config - Plugin config (skilltree section)
|
|
90
|
+
* @returns {Promise<string>} Rendered loadout markdown, or empty string on failure
|
|
91
|
+
*/
|
|
92
|
+
export async function compileRoleLoadout(roleName, criteria, config) {
|
|
93
|
+
const st = loadSkillTree();
|
|
94
|
+
if (!st?.createSkillBank) return "";
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
// Determine skill bank base path
|
|
98
|
+
const basePath = config?.basePath || ".swarm/skill-tree";
|
|
99
|
+
|
|
100
|
+
// Only attempt if the base path exists
|
|
101
|
+
if (!fs.existsSync(basePath)) return "";
|
|
102
|
+
|
|
103
|
+
const bank = st.createSkillBank({
|
|
104
|
+
storage: { basePath },
|
|
105
|
+
});
|
|
106
|
+
await bank.initialize();
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const { server } = await bank.createServingLayer({
|
|
110
|
+
outputFormat: "markdown",
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Set loadout from profile or criteria
|
|
114
|
+
if (criteria.profile) {
|
|
115
|
+
try {
|
|
116
|
+
await server.setLoadoutFromProfile(criteria.profile);
|
|
117
|
+
} catch {
|
|
118
|
+
// Profile not found — skip this role
|
|
119
|
+
return "";
|
|
120
|
+
}
|
|
121
|
+
} else if (criteria.tags || criteria.include || criteria.taskDescription) {
|
|
122
|
+
await server.setLoadout(criteria);
|
|
123
|
+
} else {
|
|
124
|
+
// No criteria specified
|
|
125
|
+
return "";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return server.renderSystemPrompt();
|
|
129
|
+
} finally {
|
|
130
|
+
await bank.shutdown();
|
|
131
|
+
}
|
|
132
|
+
} catch (err) {
|
|
133
|
+
process.stderr.write(`[skilltree] Warning: loadout compilation failed for ${roleName}: ${err.message}\n`);
|
|
134
|
+
return "";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Compile skill loadouts for all roles in a team manifest.
|
|
140
|
+
* Reads the skilltree extension from the manifest, compiles loadouts per role.
|
|
141
|
+
*
|
|
142
|
+
* @param {object} manifest - Parsed team.yaml manifest
|
|
143
|
+
* @param {object} config - Plugin config (skilltree section)
|
|
144
|
+
* @returns {Promise<object>} Map of roleName → loadout markdown
|
|
145
|
+
*/
|
|
146
|
+
export async function compileAllRoleLoadouts(manifest, config) {
|
|
147
|
+
const { defaults, roles: roleOverrides } = parseSkillTreeExtension(manifest);
|
|
148
|
+
const allRoles = manifest.roles || [];
|
|
149
|
+
const result = {};
|
|
150
|
+
|
|
151
|
+
for (const roleName of allRoles) {
|
|
152
|
+
// Merge defaults with role-specific overrides
|
|
153
|
+
const roleCriteria = roleOverrides[roleName]
|
|
154
|
+
? { ...defaults, ...roleOverrides[roleName] }
|
|
155
|
+
: defaults;
|
|
156
|
+
|
|
157
|
+
// Apply config-level default profile as final fallback
|
|
158
|
+
if (!roleCriteria.profile && !roleCriteria.tags && !roleCriteria.include && !roleCriteria.taskDescription) {
|
|
159
|
+
if (config?.defaultProfile) {
|
|
160
|
+
roleCriteria.profile = config.defaultProfile;
|
|
161
|
+
} else {
|
|
162
|
+
continue; // No criteria at all — skip
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const loadout = await compileRoleLoadout(roleName, roleCriteria, config);
|
|
167
|
+
if (loadout) {
|
|
168
|
+
result[roleName] = loadout;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return result;
|
|
173
|
+
}
|