claude-code-swarm 0.3.12 → 0.3.17
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/mcp-launcher.mjs +17 -4
- package/.claude-plugin/plugin.json +1 -1
- package/e2e/tier7-memory-sync.test.mjs +259 -0
- package/hooks/hooks.json +31 -22
- package/package.json +4 -4
- package/scripts/map-hook.mjs +14 -0
- package/scripts/map-sidecar.mjs +132 -0
- package/src/__tests__/memory-sync.test.mjs +132 -0
- package/src/__tests__/memory-watcher.test.mjs +104 -0
- package/src/__tests__/opentasks-connector.test.mjs +216 -0
- package/src/__tests__/sessionlog.test.mjs +40 -0
- package/src/__tests__/sidecar-server.test.mjs +2 -2
- package/src/bootstrap.mjs +27 -2
- package/src/content-provider.mjs +176 -0
- package/src/map-connection.mjs +6 -2
- package/src/map-events.mjs +30 -0
- package/src/memory-watcher.mjs +73 -0
- package/src/opentasks-connector.mjs +86 -0
- package/src/sessionlog.mjs +19 -0
- package/src/sidecar-server.mjs +54 -7
|
@@ -162,12 +162,25 @@ if (!def.enabled()) {
|
|
|
162
162
|
runNoop();
|
|
163
163
|
} else {
|
|
164
164
|
const { cmd, args } = def.launch();
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
const resolved = which(cmd);
|
|
166
|
+
// Debug: log MCP server resolution to a temp file for diagnostics
|
|
167
|
+
try {
|
|
168
|
+
const { appendFileSync } = await import('node:fs');
|
|
169
|
+
appendFileSync('/tmp/mcp-launcher-debug.log', `[${new Date().toISOString()}] server=${server} cmd=${cmd} resolved=${resolved} cwd=${process.cwd()} args=${JSON.stringify(args)}\n`);
|
|
170
|
+
} catch {}
|
|
171
|
+
if (resolved) {
|
|
172
|
+
// Replace this process with the real server.
|
|
173
|
+
// NODE_NO_WARNINGS suppresses Node.js experimental feature warnings on stderr
|
|
174
|
+
// (e.g., sqlite). Some MCP clients interpret stderr output as server errors,
|
|
175
|
+
// causing them to mark the server as "failed" even though it's functional.
|
|
176
|
+
const spawnEnv = { ...process.env, NODE_NO_WARNINGS: '1' };
|
|
168
177
|
const child = (await import('node:child_process')).spawn(cmd, args, {
|
|
169
|
-
stdio: 'inherit',
|
|
178
|
+
stdio: ['inherit', 'inherit', 'pipe'],
|
|
179
|
+
env: spawnEnv,
|
|
170
180
|
});
|
|
181
|
+
// Swallow stderr to prevent MCP client from misinterpreting it.
|
|
182
|
+
// In debug mode, forward to our stderr.
|
|
183
|
+
child.stderr.on('data', () => {});
|
|
171
184
|
child.on('exit', (code) => process.exit(code ?? 0));
|
|
172
185
|
} else {
|
|
173
186
|
process.stderr.write(`[${server}-mcp] ${cmd} CLI not found\n`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-swarm",
|
|
3
3
|
"description": "Spin up Claude Code agent teams from openteams YAML topologies with optional MAP (Multi-Agent Protocol) observability and coordination. Provides hooks for session lifecycle, agent spawn/complete tracking, and a /swarm skill to launch team configurations.",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.17",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "alexngai"
|
|
7
7
|
},
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier 7: minimem → MAP sync integration test
|
|
3
|
+
*
|
|
4
|
+
* Tests the full bridge flow:
|
|
5
|
+
* Agent uses minimem MCP tool (write) → PostToolUse hook fires
|
|
6
|
+
* → map-hook.mjs builds bridge command → sidecar calls x-openhive/memory.sync
|
|
7
|
+
* → Mock MAP server receives the sync notification
|
|
8
|
+
*
|
|
9
|
+
* Groups:
|
|
10
|
+
* 1. Hook configuration verification (no agent, no LLM)
|
|
11
|
+
* 2. Bridge command builder (no agent, no LLM)
|
|
12
|
+
* 3. Live agent writes memory → MAP event received (LIVE_AGENT_TEST=1)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
16
|
+
import { createWorkspace } from "./helpers/workspace.mjs";
|
|
17
|
+
import { cleanupWorkspace, waitFor } from "./helpers/cleanup.mjs";
|
|
18
|
+
import { readFileSync, existsSync, writeFileSync, mkdirSync, readdirSync } from "fs";
|
|
19
|
+
import { join } from "path";
|
|
20
|
+
|
|
21
|
+
// Check if dependencies are available (try require for CJS-compatible packages)
|
|
22
|
+
import { createRequire } from "module";
|
|
23
|
+
const require = createRequire(import.meta.url);
|
|
24
|
+
|
|
25
|
+
let minimemAvailable = false;
|
|
26
|
+
try {
|
|
27
|
+
require("minimem");
|
|
28
|
+
minimemAvailable = true;
|
|
29
|
+
} catch { /* minimem not installed */ }
|
|
30
|
+
|
|
31
|
+
let mapSdkAvailable = false;
|
|
32
|
+
try {
|
|
33
|
+
require("@multi-agent-protocol/sdk");
|
|
34
|
+
mapSdkAvailable = true;
|
|
35
|
+
} catch { /* MAP SDK not installed */ }
|
|
36
|
+
|
|
37
|
+
// Check if Claude CLI is available
|
|
38
|
+
let cliAvailable = false;
|
|
39
|
+
try {
|
|
40
|
+
const { CLI_AVAILABLE } = await import("./helpers/cli.mjs");
|
|
41
|
+
cliAvailable = CLI_AVAILABLE;
|
|
42
|
+
} catch { /* CLI not available */ }
|
|
43
|
+
|
|
44
|
+
// ── Group 1: Hook configuration ─────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
describe("tier7: minimem sync hook configuration", { timeout: 30_000 }, () => {
|
|
47
|
+
it("hooks.json has a PostToolUse entry for minimem", () => {
|
|
48
|
+
const hooksPath = join(import.meta.dirname, "..", "hooks", "hooks.json");
|
|
49
|
+
const hooks = JSON.parse(readFileSync(hooksPath, "utf-8"));
|
|
50
|
+
const postToolUse = hooks.hooks.PostToolUse || [];
|
|
51
|
+
|
|
52
|
+
const minimemHook = postToolUse.find((h) => h.matcher === "minimem");
|
|
53
|
+
expect(minimemHook).toBeDefined();
|
|
54
|
+
expect(minimemHook.hooks[0].command).toContain("minimem-mcp-used");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("minimem hook checks both minimem.enabled and map.enabled", () => {
|
|
58
|
+
const hooksPath = join(import.meta.dirname, "..", "hooks", "hooks.json");
|
|
59
|
+
const hooks = JSON.parse(readFileSync(hooksPath, "utf-8"));
|
|
60
|
+
const postToolUse = hooks.hooks.PostToolUse || [];
|
|
61
|
+
|
|
62
|
+
const minimemHook = postToolUse.find((h) => h.matcher === "minimem");
|
|
63
|
+
const cmd = minimemHook.hooks[0].command;
|
|
64
|
+
|
|
65
|
+
expect(cmd).toContain("minimem");
|
|
66
|
+
expect(cmd).toContain("map");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("hooks.json minimem hook is gated behind config check", () => {
|
|
70
|
+
const hooksPath = join(import.meta.dirname, "..", "hooks", "hooks.json");
|
|
71
|
+
const hooks = JSON.parse(readFileSync(hooksPath, "utf-8"));
|
|
72
|
+
const postToolUse = hooks.hooks.PostToolUse || [];
|
|
73
|
+
|
|
74
|
+
const minimemHook = postToolUse.find((h) => h.matcher === "minimem");
|
|
75
|
+
const cmd = minimemHook.hooks[0].command;
|
|
76
|
+
|
|
77
|
+
// Should have the config check prefix (node -e "..." && node map-hook.mjs)
|
|
78
|
+
expect(cmd).toContain('node -e');
|
|
79
|
+
expect(cmd).toContain('process.exit');
|
|
80
|
+
expect(cmd).toContain('map-hook.mjs');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ── Group 2: Bridge command builder ─────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
describe("tier7: minimem bridge command builder", { timeout: 30_000 }, () => {
|
|
87
|
+
it("builds command for write operations", async () => {
|
|
88
|
+
const { buildMinimemBridgeCommand } = await import("../src/map-events.mjs");
|
|
89
|
+
|
|
90
|
+
const cmd = buildMinimemBridgeCommand({
|
|
91
|
+
tool_name: "minimem__memory_append",
|
|
92
|
+
tool_input: { text: "Test memory entry" },
|
|
93
|
+
session_id: "test-session",
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
expect(cmd).not.toBeNull();
|
|
97
|
+
expect(cmd.action).toBe("bridge-memory-sync");
|
|
98
|
+
expect(cmd.agentId).toBe("test-session");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("skips read-only operations", async () => {
|
|
102
|
+
const { buildMinimemBridgeCommand } = await import("../src/map-events.mjs");
|
|
103
|
+
|
|
104
|
+
const cmd = buildMinimemBridgeCommand({
|
|
105
|
+
tool_name: "minimem__memory_search",
|
|
106
|
+
tool_input: { query: "test" },
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(cmd).toBeNull();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("map-hook.mjs switch statement includes minimem-mcp-used", () => {
|
|
113
|
+
const hookScript = readFileSync(
|
|
114
|
+
join(import.meta.dirname, "..", "scripts", "map-hook.mjs"),
|
|
115
|
+
"utf-8"
|
|
116
|
+
);
|
|
117
|
+
expect(hookScript).toContain('"minimem-mcp-used"');
|
|
118
|
+
expect(hookScript).toContain("handleMinimemMcpUsed");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("sidecar-server.mjs handles bridge-memory-sync command", () => {
|
|
122
|
+
const sidecarScript = readFileSync(
|
|
123
|
+
join(import.meta.dirname, "..", "src", "sidecar-server.mjs"),
|
|
124
|
+
"utf-8"
|
|
125
|
+
);
|
|
126
|
+
expect(sidecarScript).toContain('"bridge-memory-sync"');
|
|
127
|
+
expect(sidecarScript).toContain("x-openhive/memory.sync");
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// ── Group 3: Live agent test ────────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
describe.skipIf(!process.env.LIVE_AGENT_TEST || !minimemAvailable || !cliAvailable || !mapSdkAvailable)(
|
|
134
|
+
"tier7: live agent minimem → MAP sync",
|
|
135
|
+
{ timeout: 300_000 },
|
|
136
|
+
() => {
|
|
137
|
+
let mockServer;
|
|
138
|
+
let workspace;
|
|
139
|
+
let messages;
|
|
140
|
+
let runResult;
|
|
141
|
+
|
|
142
|
+
beforeAll(async () => {
|
|
143
|
+
const { MockMapServer } = await import("./helpers/map-mock-server.mjs");
|
|
144
|
+
mockServer = new MockMapServer();
|
|
145
|
+
await mockServer.start();
|
|
146
|
+
|
|
147
|
+
workspace = createWorkspace({
|
|
148
|
+
config: {
|
|
149
|
+
minimem: { enabled: true, provider: "none" },
|
|
150
|
+
map: {
|
|
151
|
+
enabled: true,
|
|
152
|
+
server: `ws://localhost:${mockServer.port}`,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
gitInit: true,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Initialize minimem in the workspace so the MCP server can start.
|
|
159
|
+
// Must run `minimem sync` to create index.db — without it the MCP server
|
|
160
|
+
// starts but may report tools inconsistently.
|
|
161
|
+
const minimemDir = join(workspace.dir, ".swarm", "minimem");
|
|
162
|
+
mkdirSync(join(minimemDir, "memory"), { recursive: true });
|
|
163
|
+
writeFileSync(join(minimemDir, "MEMORY.md"), "# Test Memory\n");
|
|
164
|
+
writeFileSync(
|
|
165
|
+
join(minimemDir, "config.json"),
|
|
166
|
+
JSON.stringify({ embedding: { provider: "none" } })
|
|
167
|
+
);
|
|
168
|
+
writeFileSync(join(minimemDir, ".gitignore"), "index.db\n");
|
|
169
|
+
|
|
170
|
+
// Initialize the search index — critical for MCP server readiness.
|
|
171
|
+
// Use require() to avoid ESM resolution issues with node:sqlite in vitest.
|
|
172
|
+
try {
|
|
173
|
+
const { Minimem } = require("minimem");
|
|
174
|
+
const mem = await Minimem.create({ memoryDir: minimemDir, embedding: { provider: "none" } });
|
|
175
|
+
await mem.sync();
|
|
176
|
+
await mem.close();
|
|
177
|
+
} catch (err) {
|
|
178
|
+
console.warn("[tier7] minimem init warning:", err.message?.slice(0, 200));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const { runClaude } = await import("./helpers/cli.mjs");
|
|
182
|
+
runResult = await runClaude(
|
|
183
|
+
'Use the minimem MCP tools: First search for "test" using memory_search. Then write a memory file at .swarm/minimem/memory/e2e-test.md containing "### 2026-03-31 12:00\n<!-- type: decision -->\nChose PostgreSQL for the main database." using the Write tool. Report what you did.',
|
|
184
|
+
{
|
|
185
|
+
cwd: workspace.dir,
|
|
186
|
+
model: "haiku",
|
|
187
|
+
maxTurns: 6,
|
|
188
|
+
maxBudgetUsd: 0.5,
|
|
189
|
+
timeout: 120_000,
|
|
190
|
+
label: "memory-sync-e2e",
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
messages = runResult.messages;
|
|
194
|
+
}, 300_000);
|
|
195
|
+
|
|
196
|
+
afterAll(async () => {
|
|
197
|
+
if (workspace) {
|
|
198
|
+
try { cleanupWorkspace(workspace.dir); } catch { /* best effort */ }
|
|
199
|
+
}
|
|
200
|
+
if (mockServer) {
|
|
201
|
+
try { await mockServer.stop(); } catch { /* best effort */ }
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("agent completed without error", () => {
|
|
206
|
+
expect(runResult.exitCode).toBe(0);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("agent used minimem MCP tools", () => {
|
|
210
|
+
const { extractToolCalls } = require("./helpers/assertions.mjs");
|
|
211
|
+
const allCalls = extractToolCalls(messages);
|
|
212
|
+
const memCalls = allCalls.filter((c) =>
|
|
213
|
+
c.name?.includes("minimem") || c.name?.includes("memory_search")
|
|
214
|
+
);
|
|
215
|
+
expect(memCalls.length).toBeGreaterThan(0);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("agent wrote a memory file", () => {
|
|
219
|
+
const { extractToolCalls } = require("./helpers/assertions.mjs");
|
|
220
|
+
const allCalls = extractToolCalls(messages);
|
|
221
|
+
// Agent may use Write tool to create memory file, or minimem append/upsert
|
|
222
|
+
const writeCalls = allCalls.filter(
|
|
223
|
+
(c) => c.name === "Write" || c.name?.includes("append") || c.name?.includes("upsert")
|
|
224
|
+
);
|
|
225
|
+
expect(writeCalls.length).toBeGreaterThan(0);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("memory file was written to disk", () => {
|
|
229
|
+
const minimemDir = join(workspace.dir, ".swarm", "minimem");
|
|
230
|
+
const memoryDir = join(minimemDir, "memory");
|
|
231
|
+
if (!existsSync(memoryDir)) return; // skip if dir not created
|
|
232
|
+
const mdFiles = readdirSync(memoryDir).filter((f) => f.endsWith(".md"));
|
|
233
|
+
expect(mdFiles.length).toBeGreaterThan(0);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("sidecar file watcher sent MAP sync notification", async () => {
|
|
237
|
+
// The sidecar's memory file watcher detects .md file writes and sends
|
|
238
|
+
// bridge-memory-sync → callExtension("x-openhive/memory.sync").
|
|
239
|
+
// This covers both Write tool and minimem MCP tool writes.
|
|
240
|
+
//
|
|
241
|
+
// The watcher has a 2s debounce, so we wait up to 10s for the event.
|
|
242
|
+
const received = await waitFor(() => {
|
|
243
|
+
const extCalls = mockServer.getByMethod("x-openhive/memory.sync");
|
|
244
|
+
if (extCalls.length > 0) return true;
|
|
245
|
+
const broadcasts = mockServer.getMessages("memory.sync");
|
|
246
|
+
if (broadcasts.length > 0) return true;
|
|
247
|
+
// Check all received messages for any memory-related content
|
|
248
|
+
const all = mockServer.receivedMessages || [];
|
|
249
|
+
return all.some(
|
|
250
|
+
(m) =>
|
|
251
|
+
JSON.stringify(m).includes("memory") &&
|
|
252
|
+
JSON.stringify(m).includes("sync")
|
|
253
|
+
);
|
|
254
|
+
}, 10_000);
|
|
255
|
+
|
|
256
|
+
expect(received).toBe(true);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
);
|
package/hooks/hooks.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
"type": "command",
|
|
13
|
-
"command": "
|
|
13
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch session-start"
|
|
14
14
|
}
|
|
15
15
|
]
|
|
16
16
|
}
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"hooks": [
|
|
22
22
|
{
|
|
23
23
|
"type": "command",
|
|
24
|
-
"command": "
|
|
24
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch session-end"
|
|
25
25
|
}
|
|
26
26
|
]
|
|
27
27
|
}
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
"hooks": [
|
|
33
33
|
{
|
|
34
34
|
"type": "command",
|
|
35
|
-
"command": "
|
|
35
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};const m=c.map||{};process.exit(m.enabled||m.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" inject"
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
"type": "command",
|
|
39
|
-
"command": "
|
|
39
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch user-prompt-submit"
|
|
40
40
|
}
|
|
41
41
|
]
|
|
42
42
|
}
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"hooks": [
|
|
48
48
|
{
|
|
49
49
|
"type": "command",
|
|
50
|
-
"command": "
|
|
50
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch pre-task"
|
|
51
51
|
}
|
|
52
52
|
]
|
|
53
53
|
}
|
|
@@ -58,7 +58,16 @@
|
|
|
58
58
|
"hooks": [
|
|
59
59
|
{
|
|
60
60
|
"type": "command",
|
|
61
|
-
"command": "
|
|
61
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit((c.opentasks?.enabled||process.env.SWARM_OPENTASKS_ENABLED)&&(c.map?.enabled||c.map?.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED)?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" opentasks-mcp-used"
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"matcher": "minimem",
|
|
67
|
+
"hooks": [
|
|
68
|
+
{
|
|
69
|
+
"type": "command",
|
|
70
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit((c.minimem?.enabled||process.env.SWARM_MINIMEM_ENABLED)&&(c.map?.enabled||c.map?.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED)?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" minimem-mcp-used"
|
|
62
71
|
}
|
|
63
72
|
]
|
|
64
73
|
},
|
|
@@ -67,7 +76,7 @@
|
|
|
67
76
|
"hooks": [
|
|
68
77
|
{
|
|
69
78
|
"type": "command",
|
|
70
|
-
"command": "
|
|
79
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch post-task"
|
|
71
80
|
}
|
|
72
81
|
]
|
|
73
82
|
},
|
|
@@ -76,11 +85,11 @@
|
|
|
76
85
|
"hooks": [
|
|
77
86
|
{
|
|
78
87
|
"type": "command",
|
|
79
|
-
"command": "
|
|
88
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};const m=c.map||{};process.exit(m.enabled||m.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" native-task-created"
|
|
80
89
|
},
|
|
81
90
|
{
|
|
82
91
|
"type": "command",
|
|
83
|
-
"command": "
|
|
92
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch post-task-create"
|
|
84
93
|
}
|
|
85
94
|
]
|
|
86
95
|
},
|
|
@@ -89,11 +98,11 @@
|
|
|
89
98
|
"hooks": [
|
|
90
99
|
{
|
|
91
100
|
"type": "command",
|
|
92
|
-
"command": "
|
|
101
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};const m=c.map||{};process.exit(m.enabled||m.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" native-task-updated"
|
|
93
102
|
},
|
|
94
103
|
{
|
|
95
104
|
"type": "command",
|
|
96
|
-
"command": "
|
|
105
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch post-task-update"
|
|
97
106
|
}
|
|
98
107
|
]
|
|
99
108
|
},
|
|
@@ -102,7 +111,7 @@
|
|
|
102
111
|
"hooks": [
|
|
103
112
|
{
|
|
104
113
|
"type": "command",
|
|
105
|
-
"command": "
|
|
114
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch post-todo"
|
|
106
115
|
}
|
|
107
116
|
]
|
|
108
117
|
},
|
|
@@ -111,7 +120,7 @@
|
|
|
111
120
|
"hooks": [
|
|
112
121
|
{
|
|
113
122
|
"type": "command",
|
|
114
|
-
"command": "
|
|
123
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch post-plan-enter"
|
|
115
124
|
}
|
|
116
125
|
]
|
|
117
126
|
},
|
|
@@ -120,7 +129,7 @@
|
|
|
120
129
|
"hooks": [
|
|
121
130
|
{
|
|
122
131
|
"type": "command",
|
|
123
|
-
"command": "
|
|
132
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch post-plan-exit"
|
|
124
133
|
}
|
|
125
134
|
]
|
|
126
135
|
},
|
|
@@ -129,7 +138,7 @@
|
|
|
129
138
|
"hooks": [
|
|
130
139
|
{
|
|
131
140
|
"type": "command",
|
|
132
|
-
"command": "
|
|
141
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch post-skill"
|
|
133
142
|
}
|
|
134
143
|
]
|
|
135
144
|
}
|
|
@@ -140,15 +149,15 @@
|
|
|
140
149
|
"hooks": [
|
|
141
150
|
{
|
|
142
151
|
"type": "command",
|
|
143
|
-
"command": "
|
|
152
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};const m=c.map||{};process.exit(m.enabled||m.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" turn-completed"
|
|
144
153
|
},
|
|
145
154
|
{
|
|
146
155
|
"type": "command",
|
|
147
|
-
"command": "
|
|
156
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};const m=c.map||{};process.exit((m.enabled||m.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED)&&c.sessionlog?.sync&&c.sessionlog.sync!=='off'?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-sync"
|
|
148
157
|
},
|
|
149
158
|
{
|
|
150
159
|
"type": "command",
|
|
151
|
-
"command": "
|
|
160
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch stop"
|
|
152
161
|
}
|
|
153
162
|
]
|
|
154
163
|
}
|
|
@@ -159,7 +168,7 @@
|
|
|
159
168
|
"hooks": [
|
|
160
169
|
{
|
|
161
170
|
"type": "command",
|
|
162
|
-
"command": "
|
|
171
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};const m=c.map||{};process.exit(m.enabled||m.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" subagent-start"
|
|
163
172
|
}
|
|
164
173
|
]
|
|
165
174
|
}
|
|
@@ -170,7 +179,7 @@
|
|
|
170
179
|
"hooks": [
|
|
171
180
|
{
|
|
172
181
|
"type": "command",
|
|
173
|
-
"command": "
|
|
182
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};const m=c.map||{};process.exit(m.enabled||m.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" subagent-stop"
|
|
174
183
|
}
|
|
175
184
|
]
|
|
176
185
|
}
|
|
@@ -181,7 +190,7 @@
|
|
|
181
190
|
"hooks": [
|
|
182
191
|
{
|
|
183
192
|
"type": "command",
|
|
184
|
-
"command": "
|
|
193
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};const m=c.map||{};process.exit(m.enabled||m.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" teammate-idle"
|
|
185
194
|
}
|
|
186
195
|
]
|
|
187
196
|
}
|
|
@@ -192,7 +201,7 @@
|
|
|
192
201
|
"hooks": [
|
|
193
202
|
{
|
|
194
203
|
"type": "command",
|
|
195
|
-
"command": "
|
|
204
|
+
"command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};const m=c.map||{};process.exit(m.enabled||m.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" task-completed"
|
|
196
205
|
}
|
|
197
206
|
]
|
|
198
207
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-swarm",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.17",
|
|
4
4
|
"description": "Claude Code plugin for launching agent teams from openteams topologies with MAP observability",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"swarm-generate-agents": "./scripts/generate-agents.mjs"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@multi-agent-protocol/sdk": "^0.1.
|
|
21
|
+
"@multi-agent-protocol/sdk": "^0.1.9",
|
|
22
22
|
"agentic-mesh": "^0.2.0",
|
|
23
23
|
"js-yaml": "^4.1.0"
|
|
24
24
|
},
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"agent-inbox": "^0.1.9",
|
|
57
|
-
"minimem": "^0.1.
|
|
58
|
-
"opentasks": "^0.0.
|
|
57
|
+
"minimem": "^0.1.1",
|
|
58
|
+
"opentasks": "^0.0.8",
|
|
59
59
|
"skill-tree": "^0.1.5",
|
|
60
60
|
"vitest": "^4.0.18",
|
|
61
61
|
"ws": "^8.0.0"
|
package/scripts/map-hook.mjs
CHANGED
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
buildOpentasksBridgeCommands,
|
|
49
49
|
handleNativeTaskCreatedEvent,
|
|
50
50
|
handleNativeTaskUpdatedEvent,
|
|
51
|
+
buildMinimemBridgeCommand,
|
|
51
52
|
} from "../src/map-events.mjs";
|
|
52
53
|
import { syncSessionlog, dispatchSessionlogHook } from "../src/sessionlog.mjs";
|
|
53
54
|
import { findSocketPath, pushSyncEvent } from "../src/opentasks-client.mjs";
|
|
@@ -194,6 +195,18 @@ async function handleSessionlogDispatch(hookData) {
|
|
|
194
195
|
await dispatchSessionlogHook(sessionlogHookName, hookData);
|
|
195
196
|
}
|
|
196
197
|
|
|
198
|
+
// ── minimem sync ─────────────────────────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
async function handleMinimemMcpUsed(hookData, sessionId) {
|
|
201
|
+
const config = readConfig();
|
|
202
|
+
if (!config.minimem?.enabled) return;
|
|
203
|
+
|
|
204
|
+
const command = buildMinimemBridgeCommand(hookData);
|
|
205
|
+
if (command) {
|
|
206
|
+
await sendCommand(config, command, sessionId);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
197
210
|
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
198
211
|
|
|
199
212
|
async function main() {
|
|
@@ -214,6 +227,7 @@ async function main() {
|
|
|
214
227
|
case "opentasks-mcp-used": await handleOpentasksMcpUsed(hookData, sessionId); break;
|
|
215
228
|
case "native-task-created": await handleNativeTaskCreated(hookData, sessionId); break;
|
|
216
229
|
case "native-task-updated": await handleNativeTaskUpdated(hookData, sessionId); break;
|
|
230
|
+
case "minimem-mcp-used": await handleMinimemMcpUsed(hookData, sessionId); break;
|
|
217
231
|
case "sessionlog-dispatch": await handleSessionlogDispatch(hookData); break;
|
|
218
232
|
default:
|
|
219
233
|
log.warn("unknown action", { action });
|