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
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import net from "net";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import { findSocketPath, rpcRequest, isDaemonAlive, pushSyncEvent, createTask, updateTask } from "../opentasks-client.mjs";
|
|
6
|
+
import { makeTmpDir, cleanupTmpDir } from "./helpers.mjs";
|
|
7
|
+
|
|
8
|
+
// ── Helper: JSON-RPC 2.0 server ─────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
function createRpcServer(socketPath, handler) {
|
|
11
|
+
const connections = [];
|
|
12
|
+
const server = net.createServer((conn) => {
|
|
13
|
+
connections.push(conn);
|
|
14
|
+
let buffer = "";
|
|
15
|
+
conn.on("data", (data) => {
|
|
16
|
+
buffer += data.toString();
|
|
17
|
+
const lines = buffer.split("\n");
|
|
18
|
+
buffer = lines.pop(); // keep incomplete line
|
|
19
|
+
for (const line of lines) {
|
|
20
|
+
if (!line.trim()) continue;
|
|
21
|
+
try {
|
|
22
|
+
const req = JSON.parse(line);
|
|
23
|
+
const result = handler(req.method, req.params, req.id);
|
|
24
|
+
if (result === null) {
|
|
25
|
+
conn.write(JSON.stringify({ jsonrpc: "2.0", id: req.id, error: { code: -1, message: "not found" } }) + "\n");
|
|
26
|
+
} else {
|
|
27
|
+
conn.write(JSON.stringify({ jsonrpc: "2.0", id: req.id, result: result ?? {} }) + "\n");
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
// ignore parse errors
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
server.__testConns = connections;
|
|
36
|
+
return server;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function listenServer(server, socketPath) {
|
|
40
|
+
await new Promise((resolve) => server.listen(socketPath, resolve));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function closeServer(server) {
|
|
44
|
+
if (!server) return;
|
|
45
|
+
for (const conn of server.__testConns || []) {
|
|
46
|
+
conn.destroy();
|
|
47
|
+
}
|
|
48
|
+
await new Promise((r) => server.close(r));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── Tests ────────────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
describe("opentasks-client", () => {
|
|
54
|
+
describe("findSocketPath", () => {
|
|
55
|
+
let tmpDir;
|
|
56
|
+
let originalCwd;
|
|
57
|
+
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
tmpDir = makeTmpDir("opentasks-find-");
|
|
60
|
+
originalCwd = process.cwd();
|
|
61
|
+
process.chdir(tmpDir);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
afterEach(() => {
|
|
65
|
+
process.chdir(originalCwd);
|
|
66
|
+
cleanupTmpDir(tmpDir);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("returns swarmkit default when no socket files exist", () => {
|
|
70
|
+
const result = findSocketPath();
|
|
71
|
+
expect(result).toBe(path.join(".swarm", "opentasks", "daemon.sock"));
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("returns .swarm/opentasks/daemon.sock when it exists (first priority)", () => {
|
|
75
|
+
const sockDir = path.join(tmpDir, ".swarm", "opentasks");
|
|
76
|
+
fs.mkdirSync(sockDir, { recursive: true });
|
|
77
|
+
fs.writeFileSync(path.join(sockDir, "daemon.sock"), "");
|
|
78
|
+
|
|
79
|
+
const result = findSocketPath();
|
|
80
|
+
expect(result).toBe(path.join(".swarm", "opentasks", "daemon.sock"));
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("returns .opentasks/daemon.sock when swarmkit path does not exist", () => {
|
|
84
|
+
const sockDir = path.join(tmpDir, ".opentasks");
|
|
85
|
+
fs.mkdirSync(sockDir, { recursive: true });
|
|
86
|
+
fs.writeFileSync(path.join(sockDir, "daemon.sock"), "");
|
|
87
|
+
|
|
88
|
+
const result = findSocketPath();
|
|
89
|
+
expect(result).toBe(path.join(".opentasks", "daemon.sock"));
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("returns .git/opentasks/daemon.sock as third priority", () => {
|
|
93
|
+
const sockDir = path.join(tmpDir, ".git", "opentasks");
|
|
94
|
+
fs.mkdirSync(sockDir, { recursive: true });
|
|
95
|
+
fs.writeFileSync(path.join(sockDir, "daemon.sock"), "");
|
|
96
|
+
|
|
97
|
+
const result = findSocketPath();
|
|
98
|
+
expect(result).toBe(path.join(".git", "opentasks", "daemon.sock"));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("prefers swarmkit path over .opentasks when both exist", () => {
|
|
102
|
+
fs.mkdirSync(path.join(tmpDir, ".swarm", "opentasks"), { recursive: true });
|
|
103
|
+
fs.writeFileSync(path.join(tmpDir, ".swarm", "opentasks", "daemon.sock"), "");
|
|
104
|
+
fs.mkdirSync(path.join(tmpDir, ".opentasks"), { recursive: true });
|
|
105
|
+
fs.writeFileSync(path.join(tmpDir, ".opentasks", "daemon.sock"), "");
|
|
106
|
+
|
|
107
|
+
const result = findSocketPath();
|
|
108
|
+
expect(result).toBe(path.join(".swarm", "opentasks", "daemon.sock"));
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe("rpcRequest", () => {
|
|
113
|
+
let tmpDir;
|
|
114
|
+
let socketPath;
|
|
115
|
+
let server;
|
|
116
|
+
|
|
117
|
+
beforeEach(() => {
|
|
118
|
+
tmpDir = makeTmpDir("opentasks-rpc-");
|
|
119
|
+
socketPath = path.join(tmpDir, "test.sock");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
afterEach(async () => {
|
|
123
|
+
await closeServer(server);
|
|
124
|
+
server = null;
|
|
125
|
+
cleanupTmpDir(tmpDir);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("returns result from successful RPC response", async () => {
|
|
129
|
+
server = createRpcServer(socketPath, (method) => {
|
|
130
|
+
if (method === "test.echo") return { value: "hello" };
|
|
131
|
+
return {};
|
|
132
|
+
});
|
|
133
|
+
await listenServer(server, socketPath);
|
|
134
|
+
|
|
135
|
+
const result = await rpcRequest("test.echo", {}, socketPath);
|
|
136
|
+
expect(result).toEqual({ value: "hello" });
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("returns {} when server returns result without explicit value", async () => {
|
|
140
|
+
server = createRpcServer(socketPath, () => undefined);
|
|
141
|
+
await listenServer(server, socketPath);
|
|
142
|
+
|
|
143
|
+
const result = await rpcRequest("ping", {}, socketPath);
|
|
144
|
+
expect(result).toEqual({});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("returns null when socket does not exist", async () => {
|
|
148
|
+
const result = await rpcRequest("ping", {}, path.join(tmpDir, "nope.sock"));
|
|
149
|
+
expect(result).toBeNull();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("returns null when server responds with JSON-RPC error", async () => {
|
|
153
|
+
server = createRpcServer(socketPath, () => null); // handler returns null → error response
|
|
154
|
+
await listenServer(server, socketPath);
|
|
155
|
+
|
|
156
|
+
const result = await rpcRequest("bad.method", {}, socketPath);
|
|
157
|
+
expect(result).toBeNull();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("returns null on timeout", async () => {
|
|
161
|
+
// Server that connects but never responds
|
|
162
|
+
const conns = [];
|
|
163
|
+
server = net.createServer((conn) => { conns.push(conn); });
|
|
164
|
+
server.__testConns = conns;
|
|
165
|
+
await listenServer(server, socketPath);
|
|
166
|
+
|
|
167
|
+
const result = await rpcRequest("ping", {}, socketPath, 200);
|
|
168
|
+
expect(result).toBeNull();
|
|
169
|
+
}, 5000);
|
|
170
|
+
|
|
171
|
+
it("sends correct JSON-RPC 2.0 format", async () => {
|
|
172
|
+
let receivedRequest = null;
|
|
173
|
+
server = createRpcServer(socketPath, (method, params, id) => {
|
|
174
|
+
receivedRequest = { method, params, id };
|
|
175
|
+
return { ok: true };
|
|
176
|
+
});
|
|
177
|
+
await listenServer(server, socketPath);
|
|
178
|
+
|
|
179
|
+
await rpcRequest("graph.update", { uri: "test://1" }, socketPath);
|
|
180
|
+
// Wait briefly for server to process
|
|
181
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
182
|
+
|
|
183
|
+
expect(receivedRequest).not.toBeNull();
|
|
184
|
+
expect(receivedRequest.method).toBe("graph.update");
|
|
185
|
+
expect(receivedRequest.params).toEqual({ uri: "test://1" });
|
|
186
|
+
expect(typeof receivedRequest.id).toBe("string");
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe("isDaemonAlive", () => {
|
|
191
|
+
let tmpDir;
|
|
192
|
+
let socketPath;
|
|
193
|
+
let server;
|
|
194
|
+
|
|
195
|
+
beforeEach(() => {
|
|
196
|
+
tmpDir = makeTmpDir("opentasks-alive-");
|
|
197
|
+
socketPath = path.join(tmpDir, "test.sock");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
afterEach(async () => {
|
|
201
|
+
await closeServer(server);
|
|
202
|
+
server = null;
|
|
203
|
+
cleanupTmpDir(tmpDir);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("returns true when daemon responds to ping", async () => {
|
|
207
|
+
server = createRpcServer(socketPath, () => ({ pong: true }));
|
|
208
|
+
await listenServer(server, socketPath);
|
|
209
|
+
|
|
210
|
+
const result = await isDaemonAlive(socketPath);
|
|
211
|
+
expect(result).toBe(true);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("returns false when socket does not exist", async () => {
|
|
215
|
+
const result = await isDaemonAlive(path.join(tmpDir, "nope.sock"));
|
|
216
|
+
expect(result).toBe(false);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe("pushSyncEvent", () => {
|
|
221
|
+
let tmpDir;
|
|
222
|
+
let socketPath;
|
|
223
|
+
let server;
|
|
224
|
+
let rpcCalls;
|
|
225
|
+
|
|
226
|
+
beforeEach(() => {
|
|
227
|
+
tmpDir = makeTmpDir("opentasks-sync-");
|
|
228
|
+
socketPath = path.join(tmpDir, "test.sock");
|
|
229
|
+
rpcCalls = [];
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
afterEach(async () => {
|
|
233
|
+
await closeServer(server);
|
|
234
|
+
server = null;
|
|
235
|
+
cleanupTmpDir(tmpDir);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
function startSyncServer(updateResult = {}) {
|
|
239
|
+
server = createRpcServer(socketPath, (method, params) => {
|
|
240
|
+
rpcCalls.push({ method, params });
|
|
241
|
+
return updateResult;
|
|
242
|
+
});
|
|
243
|
+
return listenServer(server, socketPath);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
it("task.sync with id sends graph.update with correct params", async () => {
|
|
247
|
+
await startSyncServer({ ok: true });
|
|
248
|
+
|
|
249
|
+
const result = await pushSyncEvent(socketPath, {
|
|
250
|
+
type: "task.sync",
|
|
251
|
+
id: "task-1",
|
|
252
|
+
uri: "claude://team/task-1",
|
|
253
|
+
status: "open",
|
|
254
|
+
subject: "Fix bug",
|
|
255
|
+
source: "claude-code",
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
expect(result).toBe(true);
|
|
259
|
+
expect(rpcCalls[0].method).toBe("graph.update");
|
|
260
|
+
expect(rpcCalls[0].params.id).toBe("task-1");
|
|
261
|
+
expect(rpcCalls[0].params.status).toBe("open");
|
|
262
|
+
expect(rpcCalls[0].params.title).toBe("Fix bug");
|
|
263
|
+
expect(rpcCalls[0].params.metadata.source).toBe("claude-code");
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("task.sync without id sends graph.create directly", async () => {
|
|
267
|
+
await startSyncServer({ created: true });
|
|
268
|
+
|
|
269
|
+
const result = await pushSyncEvent(socketPath, {
|
|
270
|
+
type: "task.sync",
|
|
271
|
+
uri: "claude://team/task-1",
|
|
272
|
+
status: "open",
|
|
273
|
+
subject: "Fix bug",
|
|
274
|
+
source: "claude-code",
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
expect(result).toBe(true);
|
|
278
|
+
expect(rpcCalls[0].method).toBe("graph.create");
|
|
279
|
+
expect(rpcCalls[0].params.type).toBe("task");
|
|
280
|
+
expect(rpcCalls[0].params.uri).toBe("claude://team/task-1");
|
|
281
|
+
expect(rpcCalls[0].params.title).toBe("Fix bug");
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("task.sync falls back to graph.create when update returns null", async () => {
|
|
285
|
+
// Server returns null on first call (graph.update), then success on second (graph.create)
|
|
286
|
+
let callCount = 0;
|
|
287
|
+
server = createRpcServer(socketPath, (method, params) => {
|
|
288
|
+
rpcCalls.push({ method, params });
|
|
289
|
+
callCount++;
|
|
290
|
+
if (callCount === 1) return null; // update fails → error response
|
|
291
|
+
return { created: true };
|
|
292
|
+
});
|
|
293
|
+
await listenServer(server, socketPath);
|
|
294
|
+
|
|
295
|
+
const result = await pushSyncEvent(socketPath, {
|
|
296
|
+
type: "task.sync",
|
|
297
|
+
id: "existing-task",
|
|
298
|
+
uri: "claude://team/new-task",
|
|
299
|
+
status: "open",
|
|
300
|
+
subject: "New task",
|
|
301
|
+
source: "claude-code",
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
expect(result).toBe(true);
|
|
305
|
+
expect(rpcCalls.length).toBe(2);
|
|
306
|
+
expect(rpcCalls[0].method).toBe("graph.update");
|
|
307
|
+
expect(rpcCalls[0].params.id).toBe("existing-task");
|
|
308
|
+
expect(rpcCalls[1].method).toBe("graph.create");
|
|
309
|
+
expect(rpcCalls[1].params.type).toBe("task");
|
|
310
|
+
expect(rpcCalls[1].params.uri).toBe("claude://team/new-task");
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("task.claimed sends graph.update with in_progress and assignee", async () => {
|
|
314
|
+
await startSyncServer({ ok: true });
|
|
315
|
+
|
|
316
|
+
await pushSyncEvent(socketPath, {
|
|
317
|
+
type: "task.claimed",
|
|
318
|
+
id: "task-2",
|
|
319
|
+
agent: "worker-1",
|
|
320
|
+
source: "claude-code",
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
expect(rpcCalls[0].method).toBe("graph.update");
|
|
324
|
+
expect(rpcCalls[0].params.id).toBe("task-2");
|
|
325
|
+
expect(rpcCalls[0].params.status).toBe("in_progress");
|
|
326
|
+
expect(rpcCalls[0].params.assignee).toBe("worker-1");
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it("task.claimed returns false when id is missing", async () => {
|
|
330
|
+
await startSyncServer({ ok: true });
|
|
331
|
+
|
|
332
|
+
const result = await pushSyncEvent(socketPath, {
|
|
333
|
+
type: "task.claimed",
|
|
334
|
+
agent: "worker-1",
|
|
335
|
+
source: "claude-code",
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
expect(result).toBe(false);
|
|
339
|
+
expect(rpcCalls.length).toBe(0);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it("task.unblocked sends graph.update with open status", async () => {
|
|
343
|
+
await startSyncServer({ ok: true });
|
|
344
|
+
|
|
345
|
+
await pushSyncEvent(socketPath, {
|
|
346
|
+
type: "task.unblocked",
|
|
347
|
+
id: "task-3",
|
|
348
|
+
unblockedBy: "task-1",
|
|
349
|
+
source: "claude-code",
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
expect(rpcCalls[0].method).toBe("graph.update");
|
|
353
|
+
expect(rpcCalls[0].params.id).toBe("task-3");
|
|
354
|
+
expect(rpcCalls[0].params.status).toBe("open");
|
|
355
|
+
expect(rpcCalls[0].params.metadata.unblockedBy).toBe("task-1");
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it("task.unblocked returns false when id is missing", async () => {
|
|
359
|
+
await startSyncServer({ ok: true });
|
|
360
|
+
|
|
361
|
+
const result = await pushSyncEvent(socketPath, {
|
|
362
|
+
type: "task.unblocked",
|
|
363
|
+
unblockedBy: "task-1",
|
|
364
|
+
source: "claude-code",
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
expect(result).toBe(false);
|
|
368
|
+
expect(rpcCalls.length).toBe(0);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it("task.linked sends tools.link with fromId, toId, type", async () => {
|
|
372
|
+
await startSyncServer({ ok: true });
|
|
373
|
+
|
|
374
|
+
await pushSyncEvent(socketPath, {
|
|
375
|
+
type: "task.linked",
|
|
376
|
+
from: "task://a",
|
|
377
|
+
to: "task://b",
|
|
378
|
+
linkType: "blocks",
|
|
379
|
+
source: "opentasks",
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
expect(rpcCalls[0].method).toBe("tools.link");
|
|
383
|
+
expect(rpcCalls[0].params.fromId).toBe("task://a");
|
|
384
|
+
expect(rpcCalls[0].params.toId).toBe("task://b");
|
|
385
|
+
expect(rpcCalls[0].params.type).toBe("blocks");
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("defaults linkType to 'related' when not specified", async () => {
|
|
389
|
+
await startSyncServer({ ok: true });
|
|
390
|
+
|
|
391
|
+
await pushSyncEvent(socketPath, {
|
|
392
|
+
type: "task.linked",
|
|
393
|
+
from: "task://a",
|
|
394
|
+
to: "task://b",
|
|
395
|
+
source: "opentasks",
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
expect(rpcCalls[0].params.type).toBe("related");
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it("returns false for unknown event type", async () => {
|
|
402
|
+
await startSyncServer({ ok: true });
|
|
403
|
+
|
|
404
|
+
const result = await pushSyncEvent(socketPath, {
|
|
405
|
+
type: "unknown.event",
|
|
406
|
+
uri: "test://1",
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
expect(result).toBe(false);
|
|
410
|
+
expect(rpcCalls.length).toBe(0);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it("returns true for task.sync even on socket error (best-effort)", async () => {
|
|
414
|
+
const result = await pushSyncEvent(path.join(tmpDir, "nope.sock"), {
|
|
415
|
+
type: "task.sync",
|
|
416
|
+
uri: "test://1",
|
|
417
|
+
source: "test",
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// pushSyncEvent catches errors and returns true for task.sync (best-effort success)
|
|
421
|
+
expect(result).toBe(true);
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
describe("createTask", () => {
|
|
426
|
+
let tmpDir;
|
|
427
|
+
let socketPath;
|
|
428
|
+
let server;
|
|
429
|
+
let rpcCalls;
|
|
430
|
+
|
|
431
|
+
beforeEach(() => {
|
|
432
|
+
tmpDir = makeTmpDir("opentasks-create-");
|
|
433
|
+
socketPath = path.join(tmpDir, "test.sock");
|
|
434
|
+
rpcCalls = [];
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
afterEach(async () => {
|
|
438
|
+
await closeServer(server);
|
|
439
|
+
server = null;
|
|
440
|
+
cleanupTmpDir(tmpDir);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it("sends graph.create with type 'task' and provided params", async () => {
|
|
444
|
+
server = createRpcServer(socketPath, (method, params) => {
|
|
445
|
+
rpcCalls.push({ method, params });
|
|
446
|
+
return { id: "new-task-1" };
|
|
447
|
+
});
|
|
448
|
+
await listenServer(server, socketPath);
|
|
449
|
+
|
|
450
|
+
const result = await createTask(socketPath, {
|
|
451
|
+
title: "Fix bug",
|
|
452
|
+
status: "open",
|
|
453
|
+
assignee: "worker-1",
|
|
454
|
+
metadata: { source: "test" },
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
expect(result).toEqual({ id: "new-task-1" });
|
|
458
|
+
expect(rpcCalls[0].method).toBe("graph.create");
|
|
459
|
+
expect(rpcCalls[0].params.type).toBe("task");
|
|
460
|
+
expect(rpcCalls[0].params.title).toBe("Fix bug");
|
|
461
|
+
expect(rpcCalls[0].params.status).toBe("open");
|
|
462
|
+
expect(rpcCalls[0].params.assignee).toBe("worker-1");
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it("returns null on socket error", async () => {
|
|
466
|
+
const result = await createTask(path.join(tmpDir, "nope.sock"), {
|
|
467
|
+
title: "Test",
|
|
468
|
+
});
|
|
469
|
+
expect(result).toBeNull();
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
describe("updateTask", () => {
|
|
474
|
+
let tmpDir;
|
|
475
|
+
let socketPath;
|
|
476
|
+
let server;
|
|
477
|
+
let rpcCalls;
|
|
478
|
+
|
|
479
|
+
beforeEach(() => {
|
|
480
|
+
tmpDir = makeTmpDir("opentasks-update-");
|
|
481
|
+
socketPath = path.join(tmpDir, "test.sock");
|
|
482
|
+
rpcCalls = [];
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
afterEach(async () => {
|
|
486
|
+
await closeServer(server);
|
|
487
|
+
server = null;
|
|
488
|
+
cleanupTmpDir(tmpDir);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it("sends graph.update with id and flat update fields", async () => {
|
|
492
|
+
server = createRpcServer(socketPath, (method, params) => {
|
|
493
|
+
rpcCalls.push({ method, params });
|
|
494
|
+
return { id: "task-1", status: "closed" };
|
|
495
|
+
});
|
|
496
|
+
await listenServer(server, socketPath);
|
|
497
|
+
|
|
498
|
+
const result = await updateTask(socketPath, "task-1", {
|
|
499
|
+
status: "closed",
|
|
500
|
+
metadata: { completedBy: "worker-1" },
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
expect(result).toEqual({ id: "task-1", status: "closed" });
|
|
504
|
+
expect(rpcCalls[0].method).toBe("graph.update");
|
|
505
|
+
expect(rpcCalls[0].params.id).toBe("task-1");
|
|
506
|
+
expect(rpcCalls[0].params.status).toBe("closed");
|
|
507
|
+
expect(rpcCalls[0].params.metadata.completedBy).toBe("worker-1");
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it("returns null on socket error", async () => {
|
|
511
|
+
const result = await updateTask(path.join(tmpDir, "nope.sock"), "task-1", {
|
|
512
|
+
status: "closed",
|
|
513
|
+
});
|
|
514
|
+
expect(result).toBeNull();
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
});
|