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
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
2
|
import {
|
|
3
|
-
buildSpawnCommand,
|
|
4
|
-
buildDoneCommand,
|
|
5
3
|
buildSubagentSpawnCommand,
|
|
6
4
|
buildSubagentDoneCommand,
|
|
7
5
|
buildStateCommand,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
buildTaskSyncPayload,
|
|
7
|
+
buildOpentasksBridgeCommands,
|
|
8
|
+
handleTaskCreated,
|
|
9
|
+
handleTaskCompleted,
|
|
10
|
+
handleTaskStatusCompleted,
|
|
11
11
|
} from "../map-events.mjs";
|
|
12
12
|
import {
|
|
13
13
|
makeHookData,
|
|
@@ -20,86 +20,6 @@ import {
|
|
|
20
20
|
describe("map-events", () => {
|
|
21
21
|
// ── Agent lifecycle commands ──────────────────────────────────────────────
|
|
22
22
|
|
|
23
|
-
describe("buildSpawnCommand", () => {
|
|
24
|
-
it("returns action 'spawn'", () => {
|
|
25
|
-
const cmd = buildSpawnCommand("agent-1", "executor", "gsd", makeHookData());
|
|
26
|
-
expect(cmd.action).toBe("spawn");
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("sets agentId to teamName-role when matched", () => {
|
|
30
|
-
const cmd = buildSpawnCommand("agent-1", "executor", "gsd", makeHookData());
|
|
31
|
-
expect(cmd.agent.agentId).toBe("gsd-executor");
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("sets agentId to agentName when no role match", () => {
|
|
35
|
-
const cmd = buildSpawnCommand("my-agent", null, "gsd", makeHookData());
|
|
36
|
-
expect(cmd.agent.agentId).toBe("my-agent");
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("sets name to matchedRole when provided", () => {
|
|
40
|
-
const cmd = buildSpawnCommand("a", "executor", "t", makeHookData());
|
|
41
|
-
expect(cmd.agent.name).toBe("executor");
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("sets name to agentName when no match", () => {
|
|
45
|
-
const cmd = buildSpawnCommand("my-agent", null, "t", makeHookData());
|
|
46
|
-
expect(cmd.agent.name).toBe("my-agent");
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("sets role from matchedRole or 'internal'", () => {
|
|
50
|
-
expect(buildSpawnCommand("a", "executor", "t", makeHookData()).agent.role).toBe("executor");
|
|
51
|
-
expect(buildSpawnCommand("a", null, "t", makeHookData()).agent.role).toBe("internal");
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("sets scopes to swarm:teamName", () => {
|
|
55
|
-
const cmd = buildSpawnCommand("a", null, "my-team", makeHookData());
|
|
56
|
-
expect(cmd.agent.scopes).toEqual(["swarm:my-team"]);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("sets metadata.isTeamRole based on matchedRole", () => {
|
|
60
|
-
expect(buildSpawnCommand("a", "exec", "t", makeHookData()).agent.metadata.isTeamRole).toBe(true);
|
|
61
|
-
expect(buildSpawnCommand("a", null, "t", makeHookData()).agent.metadata.isTeamRole).toBe(false);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("sets metadata.template to teamName", () => {
|
|
65
|
-
const cmd = buildSpawnCommand("a", null, "gsd", makeHookData());
|
|
66
|
-
expect(cmd.agent.metadata.template).toBe("gsd");
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("truncates task in metadata to 300 characters", () => {
|
|
70
|
-
const longPrompt = "x".repeat(500);
|
|
71
|
-
const cmd = buildSpawnCommand("a", null, "t", makeHookData({ prompt: longPrompt }));
|
|
72
|
-
expect(cmd.agent.metadata.task.length).toBe(300);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("uses tool_input.prompt for task metadata", () => {
|
|
76
|
-
const cmd = buildSpawnCommand("a", null, "t", makeHookData({ prompt: "do X" }));
|
|
77
|
-
expect(cmd.agent.metadata.task).toBe("do X");
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
describe("buildDoneCommand", () => {
|
|
82
|
-
it("returns action 'done'", () => {
|
|
83
|
-
const cmd = buildDoneCommand("a", "executor", "t");
|
|
84
|
-
expect(cmd.action).toBe("done");
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it("sets agentId to teamName-role when matched", () => {
|
|
88
|
-
const cmd = buildDoneCommand("a", "executor", "gsd");
|
|
89
|
-
expect(cmd.agentId).toBe("gsd-executor");
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it("sets agentId to agentName when no match", () => {
|
|
93
|
-
const cmd = buildDoneCommand("my-agent", null, "gsd");
|
|
94
|
-
expect(cmd.agentId).toBe("my-agent");
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("sets reason to 'completed'", () => {
|
|
98
|
-
const cmd = buildDoneCommand("a", null, "t");
|
|
99
|
-
expect(cmd.reason).toBe("completed");
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
|
|
103
23
|
describe("buildSubagentSpawnCommand", () => {
|
|
104
24
|
it("returns action 'spawn'", () => {
|
|
105
25
|
const cmd = buildSubagentSpawnCommand(makeSubagentStartData(), "gsd");
|
|
@@ -216,110 +136,554 @@ describe("map-events", () => {
|
|
|
216
136
|
});
|
|
217
137
|
});
|
|
218
138
|
|
|
219
|
-
// ── Task lifecycle
|
|
139
|
+
// ── Task lifecycle handlers (opentasks daemon + MAP event bridge) ─────────
|
|
140
|
+
//
|
|
141
|
+
// These test the full two-step flow:
|
|
142
|
+
// 1. Task CRUD in opentasks daemon (via mocked opentasks-client)
|
|
143
|
+
// 2. Bridge event emission to MAP (via mocked sidecar-client capturing commands)
|
|
144
|
+
|
|
145
|
+
describe("handleTaskCreated", () => {
|
|
146
|
+
let mockCreateTask;
|
|
147
|
+
let mockFindSocketPath;
|
|
148
|
+
let sidecarCommands;
|
|
149
|
+
|
|
150
|
+
beforeEach(async () => {
|
|
151
|
+
mockCreateTask = vi.fn().mockResolvedValue({ id: "created-task-1" });
|
|
152
|
+
mockFindSocketPath = vi.fn().mockReturnValue("/tmp/test.sock");
|
|
153
|
+
sidecarCommands = [];
|
|
154
|
+
|
|
155
|
+
// Mock opentasks-client (dynamic import target)
|
|
156
|
+
vi.doMock("../opentasks-client.mjs", () => ({
|
|
157
|
+
createTask: mockCreateTask,
|
|
158
|
+
findSocketPath: mockFindSocketPath,
|
|
159
|
+
}));
|
|
160
|
+
|
|
161
|
+
// Mock sidecar-client to capture commands sent via sendCommand
|
|
162
|
+
vi.doMock("../sidecar-client.mjs", () => ({
|
|
163
|
+
sendToSidecar: vi.fn(async (cmd) => {
|
|
164
|
+
sidecarCommands.push(cmd);
|
|
165
|
+
return true;
|
|
166
|
+
}),
|
|
167
|
+
ensureSidecar: vi.fn().mockResolvedValue(false),
|
|
168
|
+
}));
|
|
169
|
+
|
|
170
|
+
// Mock paths to avoid filesystem access
|
|
171
|
+
vi.doMock("../paths.mjs", () => ({
|
|
172
|
+
sessionPaths: vi.fn(() => ({
|
|
173
|
+
socketPath: "/tmp/sidecar.sock",
|
|
174
|
+
inboxSocketPath: "/tmp/inbox.sock",
|
|
175
|
+
})),
|
|
176
|
+
}));
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
afterEach(() => {
|
|
180
|
+
vi.restoreAllMocks();
|
|
181
|
+
vi.resetModules();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("creates task in opentasks with correct params", async () => {
|
|
185
|
+
const { handleTaskCreated } = await import("../map-events.mjs");
|
|
186
|
+
const hookData = makeHookData({ prompt: "Fix the bug" });
|
|
187
|
+
const config = { map: { enabled: true } };
|
|
188
|
+
|
|
189
|
+
await handleTaskCreated(config, hookData, "gsd", "executor", "test-agent", null);
|
|
190
|
+
|
|
191
|
+
expect(mockCreateTask).toHaveBeenCalledWith("/tmp/test.sock", expect.objectContaining({
|
|
192
|
+
title: "Fix the bug",
|
|
193
|
+
status: "open",
|
|
194
|
+
content: "Fix the bug",
|
|
195
|
+
assignee: "gsd-executor",
|
|
196
|
+
metadata: expect.objectContaining({
|
|
197
|
+
source: "claude-code-swarm",
|
|
198
|
+
teamName: "gsd",
|
|
199
|
+
role: "executor",
|
|
200
|
+
}),
|
|
201
|
+
}));
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("emits bridge-task-created command with task data", async () => {
|
|
205
|
+
const { handleTaskCreated } = await import("../map-events.mjs");
|
|
206
|
+
const hookData = makeHookData({ prompt: "Fix the bug" });
|
|
207
|
+
const config = { map: { enabled: true } };
|
|
208
|
+
|
|
209
|
+
await handleTaskCreated(config, hookData, "gsd", "executor", "test-agent", "sess-1");
|
|
210
|
+
|
|
211
|
+
const createdCmd = sidecarCommands.find((c) => c.action === "bridge-task-created");
|
|
212
|
+
expect(createdCmd).toBeDefined();
|
|
213
|
+
expect(createdCmd.task.id).toBe("created-task-1");
|
|
214
|
+
expect(createdCmd.task.title).toBe("Fix the bug");
|
|
215
|
+
expect(createdCmd.task.status).toBe("open");
|
|
216
|
+
expect(createdCmd.task.assignee).toBe("gsd-executor");
|
|
217
|
+
expect(createdCmd.agentId).toBe("gsd-executor");
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("emits bridge-task-assigned command when assignee exists", async () => {
|
|
221
|
+
const { handleTaskCreated } = await import("../map-events.mjs");
|
|
222
|
+
const hookData = makeHookData({ prompt: "Do X" });
|
|
223
|
+
const config = { map: { enabled: true } };
|
|
224
|
+
|
|
225
|
+
await handleTaskCreated(config, hookData, "gsd", "executor", "test-agent", null);
|
|
226
|
+
|
|
227
|
+
const assignedCmd = sidecarCommands.find((c) => c.action === "bridge-task-assigned");
|
|
228
|
+
expect(assignedCmd).toBeDefined();
|
|
229
|
+
expect(assignedCmd.taskId).toBe("created-task-1");
|
|
230
|
+
expect(assignedCmd.assignee).toBe("gsd-executor");
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("uses agentName as assignee when no role matched", async () => {
|
|
234
|
+
const { handleTaskCreated } = await import("../map-events.mjs");
|
|
235
|
+
const hookData = makeHookData();
|
|
236
|
+
const config = { map: { enabled: true } };
|
|
237
|
+
|
|
238
|
+
await handleTaskCreated(config, hookData, "gsd", null, "my-agent", null);
|
|
239
|
+
|
|
240
|
+
expect(mockCreateTask).toHaveBeenCalledWith("/tmp/test.sock", expect.objectContaining({
|
|
241
|
+
assignee: "my-agent",
|
|
242
|
+
}));
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("falls back to tool_use_id for taskId when createTask returns null", async () => {
|
|
246
|
+
mockCreateTask.mockResolvedValue(null);
|
|
247
|
+
const { handleTaskCreated } = await import("../map-events.mjs");
|
|
248
|
+
const hookData = makeHookData({ toolUseId: "tu-fallback" });
|
|
249
|
+
const config = { map: { enabled: true } };
|
|
250
|
+
|
|
251
|
+
await handleTaskCreated(config, hookData, "gsd", null, "agent", null);
|
|
252
|
+
|
|
253
|
+
const createdCmd = sidecarCommands.find((c) => c.action === "bridge-task-created");
|
|
254
|
+
expect(createdCmd.task.id).toBe("tu-fallback");
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
describe("handleTaskCompleted", () => {
|
|
259
|
+
let mockUpdateTask;
|
|
260
|
+
let mockFindSocketPath;
|
|
261
|
+
let sidecarCommands;
|
|
262
|
+
|
|
263
|
+
beforeEach(() => {
|
|
264
|
+
mockUpdateTask = vi.fn().mockResolvedValue({ id: "task-1" });
|
|
265
|
+
mockFindSocketPath = vi.fn().mockReturnValue("/tmp/test.sock");
|
|
266
|
+
sidecarCommands = [];
|
|
220
267
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
268
|
+
vi.doMock("../opentasks-client.mjs", () => ({
|
|
269
|
+
updateTask: mockUpdateTask,
|
|
270
|
+
findSocketPath: mockFindSocketPath,
|
|
271
|
+
}));
|
|
272
|
+
|
|
273
|
+
vi.doMock("../sidecar-client.mjs", () => ({
|
|
274
|
+
sendToSidecar: vi.fn(async (cmd) => {
|
|
275
|
+
sidecarCommands.push(cmd);
|
|
276
|
+
return true;
|
|
277
|
+
}),
|
|
278
|
+
ensureSidecar: vi.fn().mockResolvedValue(false),
|
|
279
|
+
}));
|
|
280
|
+
|
|
281
|
+
vi.doMock("../paths.mjs", () => ({
|
|
282
|
+
sessionPaths: vi.fn(() => ({
|
|
283
|
+
socketPath: "/tmp/sidecar.sock",
|
|
284
|
+
inboxSocketPath: "/tmp/inbox.sock",
|
|
285
|
+
})),
|
|
286
|
+
}));
|
|
225
287
|
});
|
|
226
288
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
289
|
+
afterEach(() => {
|
|
290
|
+
vi.restoreAllMocks();
|
|
291
|
+
vi.resetModules();
|
|
230
292
|
});
|
|
231
293
|
|
|
232
|
-
it("
|
|
233
|
-
const
|
|
234
|
-
|
|
294
|
+
it("updates task to closed status in opentasks", async () => {
|
|
295
|
+
const { handleTaskCompleted } = await import("../map-events.mjs");
|
|
296
|
+
const hookData = makeHookData({ toolUseId: "task-42" });
|
|
297
|
+
const config = { map: { enabled: true } };
|
|
298
|
+
|
|
299
|
+
await handleTaskCompleted(config, hookData, "gsd", "executor", "test-agent", null);
|
|
300
|
+
|
|
301
|
+
expect(mockUpdateTask).toHaveBeenCalledWith("/tmp/test.sock", "task-42", expect.objectContaining({
|
|
302
|
+
status: "closed",
|
|
303
|
+
metadata: expect.objectContaining({
|
|
304
|
+
completedBy: "gsd-executor",
|
|
305
|
+
source: "claude-code-swarm",
|
|
306
|
+
}),
|
|
307
|
+
}));
|
|
235
308
|
});
|
|
236
309
|
|
|
237
|
-
it("
|
|
238
|
-
const
|
|
239
|
-
|
|
310
|
+
it("emits bridge-task-status with completed status", async () => {
|
|
311
|
+
const { handleTaskCompleted } = await import("../map-events.mjs");
|
|
312
|
+
const hookData = makeHookData({ toolUseId: "task-42" });
|
|
313
|
+
const config = { map: { enabled: true } };
|
|
314
|
+
|
|
315
|
+
await handleTaskCompleted(config, hookData, "gsd", "executor", "test-agent", "sess-2");
|
|
316
|
+
|
|
317
|
+
const statusCmd = sidecarCommands.find((c) => c.action === "bridge-task-status");
|
|
318
|
+
expect(statusCmd).toBeDefined();
|
|
319
|
+
expect(statusCmd.taskId).toBe("task-42");
|
|
320
|
+
expect(statusCmd.previous).toBe("open");
|
|
321
|
+
expect(statusCmd.current).toBe("completed");
|
|
322
|
+
expect(statusCmd.agentId).toBe("gsd-executor");
|
|
240
323
|
});
|
|
241
324
|
|
|
242
|
-
it("
|
|
243
|
-
const
|
|
244
|
-
|
|
325
|
+
it("skips opentasks update when no taskId", async () => {
|
|
326
|
+
const { handleTaskCompleted } = await import("../map-events.mjs");
|
|
327
|
+
const hookData = { tool_input: {} }; // no tool_use_id
|
|
328
|
+
const config = { map: { enabled: true } };
|
|
329
|
+
|
|
330
|
+
await handleTaskCompleted(config, hookData, "gsd", null, "agent", null);
|
|
331
|
+
|
|
332
|
+
expect(mockUpdateTask).not.toHaveBeenCalled();
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it("still emits bridge event even when no taskId", async () => {
|
|
336
|
+
const { handleTaskCompleted } = await import("../map-events.mjs");
|
|
337
|
+
const hookData = { tool_input: {} };
|
|
338
|
+
const config = { map: { enabled: true } };
|
|
339
|
+
|
|
340
|
+
await handleTaskCompleted(config, hookData, "gsd", null, "agent", null);
|
|
341
|
+
|
|
342
|
+
const statusCmd = sidecarCommands.find((c) => c.action === "bridge-task-status");
|
|
343
|
+
expect(statusCmd).toBeDefined();
|
|
344
|
+
expect(statusCmd.current).toBe("completed");
|
|
245
345
|
});
|
|
346
|
+
});
|
|
246
347
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
348
|
+
describe("handleTaskStatusCompleted", () => {
|
|
349
|
+
let mockUpdateTask;
|
|
350
|
+
let mockFindSocketPath;
|
|
351
|
+
let sidecarCommands;
|
|
352
|
+
|
|
353
|
+
beforeEach(() => {
|
|
354
|
+
mockUpdateTask = vi.fn().mockResolvedValue({ id: "task-1" });
|
|
355
|
+
mockFindSocketPath = vi.fn().mockReturnValue("/tmp/test.sock");
|
|
356
|
+
sidecarCommands = [];
|
|
357
|
+
|
|
358
|
+
vi.doMock("../opentasks-client.mjs", () => ({
|
|
359
|
+
updateTask: mockUpdateTask,
|
|
360
|
+
findSocketPath: mockFindSocketPath,
|
|
361
|
+
}));
|
|
362
|
+
|
|
363
|
+
vi.doMock("../sidecar-client.mjs", () => ({
|
|
364
|
+
sendToSidecar: vi.fn(async (cmd) => {
|
|
365
|
+
sidecarCommands.push(cmd);
|
|
366
|
+
return true;
|
|
367
|
+
}),
|
|
368
|
+
ensureSidecar: vi.fn().mockResolvedValue(false),
|
|
369
|
+
}));
|
|
370
|
+
|
|
371
|
+
vi.doMock("../paths.mjs", () => ({
|
|
372
|
+
sessionPaths: vi.fn(() => ({
|
|
373
|
+
socketPath: "/tmp/sidecar.sock",
|
|
374
|
+
inboxSocketPath: "/tmp/inbox.sock",
|
|
375
|
+
})),
|
|
376
|
+
}));
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
afterEach(() => {
|
|
380
|
+
vi.restoreAllMocks();
|
|
381
|
+
vi.resetModules();
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it("updates task with richer metadata from TaskCompleted hook", async () => {
|
|
385
|
+
const { handleTaskStatusCompleted } = await import("../map-events.mjs");
|
|
386
|
+
const hookData = makeTaskCompletedData({
|
|
387
|
+
taskId: "task-99",
|
|
388
|
+
taskSubject: "Fix bug",
|
|
389
|
+
teammateName: "builder",
|
|
390
|
+
teamName: "gsd",
|
|
391
|
+
});
|
|
392
|
+
const config = { map: { enabled: true } };
|
|
393
|
+
|
|
394
|
+
await handleTaskStatusCompleted(config, hookData, "gsd", "builder", null);
|
|
395
|
+
|
|
396
|
+
expect(mockUpdateTask).toHaveBeenCalledWith("/tmp/test.sock", "task-99", expect.objectContaining({
|
|
397
|
+
status: "closed",
|
|
398
|
+
title: "Fix bug",
|
|
399
|
+
metadata: expect.objectContaining({
|
|
400
|
+
completedBy: "builder",
|
|
401
|
+
teamName: "gsd",
|
|
402
|
+
role: "builder",
|
|
403
|
+
isTeamRole: true,
|
|
404
|
+
source: "claude-code-swarm",
|
|
405
|
+
}),
|
|
406
|
+
}));
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it("emits bridge-task-status with in_progress → completed", async () => {
|
|
410
|
+
const { handleTaskStatusCompleted } = await import("../map-events.mjs");
|
|
411
|
+
const hookData = makeTaskCompletedData({ taskId: "task-99", teammateName: "builder" });
|
|
412
|
+
const config = { map: { enabled: true } };
|
|
413
|
+
|
|
414
|
+
await handleTaskStatusCompleted(config, hookData, "gsd", "builder", "sess-3");
|
|
415
|
+
|
|
416
|
+
const statusCmd = sidecarCommands.find((c) => c.action === "bridge-task-status");
|
|
417
|
+
expect(statusCmd).toBeDefined();
|
|
418
|
+
expect(statusCmd.taskId).toBe("task-99");
|
|
419
|
+
expect(statusCmd.previous).toBe("in_progress");
|
|
420
|
+
expect(statusCmd.current).toBe("completed");
|
|
421
|
+
expect(statusCmd.agentId).toBe("builder");
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it("skips opentasks update when no taskId", async () => {
|
|
425
|
+
const { handleTaskStatusCompleted } = await import("../map-events.mjs");
|
|
426
|
+
const hookData = { teammate_name: "builder" }; // no task_id
|
|
427
|
+
const config = { map: { enabled: true } };
|
|
428
|
+
|
|
429
|
+
await handleTaskStatusCompleted(config, hookData, "gsd", "builder", null);
|
|
430
|
+
|
|
431
|
+
expect(mockUpdateTask).not.toHaveBeenCalled();
|
|
251
432
|
});
|
|
252
433
|
});
|
|
253
434
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
435
|
+
// ── Task sync payloads (opentasks ↔ MAP bridge) ───────────────────────────
|
|
436
|
+
|
|
437
|
+
describe("buildTaskSyncPayload", () => {
|
|
438
|
+
it("sets type to 'task.sync'", () => {
|
|
439
|
+
const p = buildTaskSyncPayload({ tool_input: { taskId: "t-1" } }, "gsd");
|
|
440
|
+
expect(p.type).toBe("task.sync");
|
|
258
441
|
});
|
|
259
442
|
|
|
260
|
-
it("sets
|
|
261
|
-
const p =
|
|
262
|
-
expect(p.
|
|
443
|
+
it("sets uri from tool_input.taskId", () => {
|
|
444
|
+
const p = buildTaskSyncPayload({ tool_input: { taskId: "t-1" } }, "gsd");
|
|
445
|
+
expect(p.uri).toBe("claude://gsd/t-1");
|
|
263
446
|
});
|
|
264
447
|
|
|
265
|
-
it("
|
|
266
|
-
const p =
|
|
267
|
-
expect(p.
|
|
448
|
+
it("falls back to hookData.task_id for uri", () => {
|
|
449
|
+
const p = buildTaskSyncPayload({ task_id: "t-2", tool_input: {} }, "gsd");
|
|
450
|
+
expect(p.uri).toBe("claude://gsd/t-2");
|
|
268
451
|
});
|
|
269
452
|
|
|
270
|
-
it("
|
|
271
|
-
const p =
|
|
453
|
+
it("maps status pending to open", () => {
|
|
454
|
+
const p = buildTaskSyncPayload({ tool_input: { status: "pending" } }, "t");
|
|
455
|
+
expect(p.status).toBe("open");
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it("maps status completed to completed", () => {
|
|
459
|
+
const p = buildTaskSyncPayload({ tool_input: { status: "completed" } }, "t");
|
|
272
460
|
expect(p.status).toBe("completed");
|
|
273
461
|
});
|
|
274
|
-
});
|
|
275
462
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
expect(p.type).toBe("task.completed");
|
|
463
|
+
it("maps status in_progress to in_progress", () => {
|
|
464
|
+
const p = buildTaskSyncPayload({ tool_input: { status: "in_progress" } }, "t");
|
|
465
|
+
expect(p.status).toBe("in_progress");
|
|
280
466
|
});
|
|
281
467
|
|
|
282
|
-
it("
|
|
283
|
-
const p =
|
|
284
|
-
expect(p.
|
|
468
|
+
it("defaults status to open when not provided", () => {
|
|
469
|
+
const p = buildTaskSyncPayload({ tool_input: {} }, "t");
|
|
470
|
+
expect(p.status).toBe("open");
|
|
285
471
|
});
|
|
286
472
|
|
|
287
|
-
it("
|
|
288
|
-
const p =
|
|
289
|
-
expect(p.
|
|
473
|
+
it("uses tool_input.subject for subject", () => {
|
|
474
|
+
const p = buildTaskSyncPayload({ tool_input: { subject: "Fix bug" } }, "t");
|
|
475
|
+
expect(p.subject).toBe("Fix bug");
|
|
290
476
|
});
|
|
291
477
|
|
|
292
|
-
it("
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
expect(p.taskDescription.length).toBe(300);
|
|
478
|
+
it("falls back to task_subject from hookData", () => {
|
|
479
|
+
const p = buildTaskSyncPayload({ task_subject: "Add feature", tool_input: {} }, "t");
|
|
480
|
+
expect(p.subject).toBe("Add feature");
|
|
296
481
|
});
|
|
297
482
|
|
|
298
|
-
it("sets
|
|
299
|
-
const p =
|
|
300
|
-
expect(p.
|
|
483
|
+
it("sets source to claude-code", () => {
|
|
484
|
+
const p = buildTaskSyncPayload({ tool_input: {} }, "t");
|
|
485
|
+
expect(p.source).toBe("claude-code");
|
|
301
486
|
});
|
|
487
|
+
});
|
|
302
488
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
489
|
+
describe("buildOpentasksBridgeCommands", () => {
|
|
490
|
+
// ── create_task ──────────────────────────────────────────────────────
|
|
491
|
+
|
|
492
|
+
it("create_task returns bridge-task-created + bridge-task-assigned", () => {
|
|
493
|
+
const cmds = buildOpentasksBridgeCommands({
|
|
494
|
+
tool_name: "mcp__opentasks__create_task",
|
|
495
|
+
tool_input: { title: "Fix bug", assignee: "worker-1" },
|
|
496
|
+
tool_output: JSON.stringify({ content: [{ text: JSON.stringify({ id: "task-42", title: "Fix bug", status: "open", assignee: "worker-1" }) }] }),
|
|
497
|
+
});
|
|
498
|
+
expect(cmds).toHaveLength(2);
|
|
499
|
+
expect(cmds[0].action).toBe("bridge-task-created");
|
|
500
|
+
expect(cmds[0].task.id).toBe("task-42");
|
|
501
|
+
expect(cmds[0].task.title).toBe("Fix bug");
|
|
502
|
+
expect(cmds[0].task.assignee).toBe("worker-1");
|
|
503
|
+
expect(cmds[0].agentId).toBe("worker-1");
|
|
504
|
+
expect(cmds[1].action).toBe("bridge-task-assigned");
|
|
505
|
+
expect(cmds[1].taskId).toBe("task-42");
|
|
506
|
+
expect(cmds[1].assignee).toBe("worker-1");
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it("create_task uses input fields when tool_output is missing", () => {
|
|
510
|
+
const cmds = buildOpentasksBridgeCommands({
|
|
511
|
+
tool_name: "mcp__opentasks__create_task",
|
|
512
|
+
tool_input: { title: "New task", status: "open" },
|
|
513
|
+
});
|
|
514
|
+
expect(cmds).toHaveLength(1); // no assignee → no assigned command
|
|
515
|
+
expect(cmds[0].action).toBe("bridge-task-created");
|
|
516
|
+
expect(cmds[0].task.title).toBe("New task");
|
|
517
|
+
expect(cmds[0].task.id).toBe("");
|
|
518
|
+
expect(cmds[0].agentId).toBe("opentasks"); // default when no assignee
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it("create_task returns empty when no id and no title", () => {
|
|
522
|
+
const cmds = buildOpentasksBridgeCommands({
|
|
523
|
+
tool_name: "mcp__opentasks__create_task",
|
|
524
|
+
tool_input: {},
|
|
525
|
+
});
|
|
526
|
+
expect(cmds).toHaveLength(0);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it("create_task parses already-parsed tool_output", () => {
|
|
530
|
+
const cmds = buildOpentasksBridgeCommands({
|
|
531
|
+
tool_name: "mcp__opentasks__create_task",
|
|
532
|
+
tool_input: {},
|
|
533
|
+
tool_output: { id: "direct-1", title: "Direct", status: "open" },
|
|
534
|
+
});
|
|
535
|
+
expect(cmds[0].task.id).toBe("direct-1");
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
// ── update_task ──────────────────────────────────────────────────────
|
|
539
|
+
|
|
540
|
+
it("update_task returns bridge-task-status", () => {
|
|
541
|
+
const cmds = buildOpentasksBridgeCommands({
|
|
542
|
+
tool_name: "mcp__opentasks__update_task",
|
|
543
|
+
tool_input: { id: "task-1", status: "completed" },
|
|
544
|
+
tool_output: JSON.stringify({ content: [{ text: JSON.stringify({ id: "task-1", status: "completed", assignee: "builder" }) }] }),
|
|
545
|
+
});
|
|
546
|
+
expect(cmds).toHaveLength(1);
|
|
547
|
+
expect(cmds[0].action).toBe("bridge-task-status");
|
|
548
|
+
expect(cmds[0].taskId).toBe("task-1");
|
|
549
|
+
expect(cmds[0].current).toBe("completed");
|
|
550
|
+
expect(cmds[0].agentId).toBe("builder");
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it("update_task uses input.transition as status fallback", () => {
|
|
554
|
+
const cmds = buildOpentasksBridgeCommands({
|
|
555
|
+
tool_name: "mcp__opentasks__update_task",
|
|
556
|
+
tool_input: { id: "task-2", transition: "close" },
|
|
557
|
+
});
|
|
558
|
+
expect(cmds[0].current).toBe("close");
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it("update_task returns empty when no id", () => {
|
|
562
|
+
const cmds = buildOpentasksBridgeCommands({
|
|
563
|
+
tool_name: "mcp__opentasks__update_task",
|
|
564
|
+
tool_input: { status: "completed" },
|
|
565
|
+
});
|
|
566
|
+
expect(cmds).toHaveLength(0);
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
it("update_task returns empty when no status change", () => {
|
|
570
|
+
const cmds = buildOpentasksBridgeCommands({
|
|
571
|
+
tool_name: "mcp__opentasks__update_task",
|
|
572
|
+
tool_input: { id: "task-3", title: "Renamed" }, // no status/transition
|
|
573
|
+
});
|
|
574
|
+
expect(cmds).toHaveLength(0);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
it("update_task sets previous to undefined when explicit status in input", () => {
|
|
578
|
+
const cmds = buildOpentasksBridgeCommands({
|
|
579
|
+
tool_name: "mcp__opentasks__update_task",
|
|
580
|
+
tool_input: { id: "task-1", status: "in_progress" },
|
|
581
|
+
});
|
|
582
|
+
expect(cmds[0].previous).toBeUndefined();
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
// ── link ──────────────────────────────────────────────────────────────
|
|
586
|
+
|
|
587
|
+
it("link returns emit command with task.linked payload", () => {
|
|
588
|
+
const cmds = buildOpentasksBridgeCommands({
|
|
589
|
+
tool_name: "mcp__opentasks__link",
|
|
590
|
+
tool_input: { fromId: "task-a", toId: "task-b", type: "blocks" },
|
|
591
|
+
});
|
|
592
|
+
expect(cmds).toHaveLength(1);
|
|
593
|
+
expect(cmds[0].action).toBe("emit");
|
|
594
|
+
expect(cmds[0].event.type).toBe("task.linked");
|
|
595
|
+
expect(cmds[0].event.from).toBe("task-a");
|
|
596
|
+
expect(cmds[0].event.to).toBe("task-b");
|
|
597
|
+
expect(cmds[0].event.linkType).toBe("blocks");
|
|
598
|
+
expect(cmds[0].event.source).toBe("opentasks");
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
it("link defaults linkType to related and remove to false", () => {
|
|
602
|
+
const cmds = buildOpentasksBridgeCommands({
|
|
603
|
+
tool_name: "mcp__opentasks__link",
|
|
604
|
+
tool_input: { fromId: "a", toId: "b" },
|
|
605
|
+
});
|
|
606
|
+
expect(cmds[0].event.linkType).toBe("related");
|
|
607
|
+
expect(cmds[0].event.remove).toBe(false);
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
it("link returns empty when fromId or toId is missing", () => {
|
|
611
|
+
expect(buildOpentasksBridgeCommands({
|
|
612
|
+
tool_name: "mcp__opentasks__link",
|
|
613
|
+
tool_input: { fromId: "a" },
|
|
614
|
+
})).toHaveLength(0);
|
|
615
|
+
expect(buildOpentasksBridgeCommands({
|
|
616
|
+
tool_name: "mcp__opentasks__link",
|
|
617
|
+
tool_input: { toId: "b" },
|
|
618
|
+
})).toHaveLength(0);
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
// ── annotate ──────────────────────────────────────────────────────────
|
|
622
|
+
|
|
623
|
+
it("annotate returns emit command with task.sync payload", () => {
|
|
624
|
+
const cmds = buildOpentasksBridgeCommands({
|
|
625
|
+
tool_name: "mcp__opentasks__annotate",
|
|
626
|
+
tool_input: { target: "task://1", feedback: { type: "suggestion" } },
|
|
627
|
+
});
|
|
628
|
+
expect(cmds).toHaveLength(1);
|
|
629
|
+
expect(cmds[0].action).toBe("emit");
|
|
630
|
+
expect(cmds[0].event.type).toBe("task.sync");
|
|
631
|
+
expect(cmds[0].event.uri).toBe("task://1");
|
|
632
|
+
expect(cmds[0].event.annotation).toBe("suggestion");
|
|
633
|
+
expect(cmds[0].event.source).toBe("opentasks");
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
it("annotate defaults annotation to comment", () => {
|
|
637
|
+
const cmds = buildOpentasksBridgeCommands({
|
|
638
|
+
tool_name: "mcp__opentasks__annotate",
|
|
639
|
+
tool_input: { target: "task://1" },
|
|
640
|
+
});
|
|
641
|
+
expect(cmds[0].event.annotation).toBe("comment");
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
it("annotate returns empty when target is missing", () => {
|
|
645
|
+
expect(buildOpentasksBridgeCommands({
|
|
646
|
+
tool_name: "mcp__opentasks__annotate",
|
|
647
|
+
tool_input: {},
|
|
648
|
+
})).toHaveLength(0);
|
|
306
649
|
});
|
|
307
650
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
651
|
+
// ── read-only tools ──────────────────────────────────────────────────
|
|
652
|
+
|
|
653
|
+
it("query tool returns empty array (read-only)", () => {
|
|
654
|
+
expect(buildOpentasksBridgeCommands({
|
|
655
|
+
tool_name: "mcp__opentasks__query",
|
|
656
|
+
tool_input: { filter: "status:open" },
|
|
657
|
+
})).toHaveLength(0);
|
|
311
658
|
});
|
|
312
659
|
|
|
313
|
-
it("
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
660
|
+
it("list_tasks returns empty array (read-only)", () => {
|
|
661
|
+
expect(buildOpentasksBridgeCommands({
|
|
662
|
+
tool_name: "mcp__opentasks__list_tasks",
|
|
663
|
+
tool_input: {},
|
|
664
|
+
})).toHaveLength(0);
|
|
317
665
|
});
|
|
318
666
|
|
|
319
|
-
it("
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
667
|
+
it("get_task returns empty array (read-only)", () => {
|
|
668
|
+
expect(buildOpentasksBridgeCommands({
|
|
669
|
+
tool_name: "mcp__opentasks__get_task",
|
|
670
|
+
tool_input: { id: "task-1" },
|
|
671
|
+
})).toHaveLength(0);
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
// ── edge cases ───────────────────────────────────────────────────────
|
|
675
|
+
|
|
676
|
+
it("returns empty array when hook data is empty", () => {
|
|
677
|
+
expect(buildOpentasksBridgeCommands({})).toHaveLength(0);
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
it("handles plugin-namespaced tool names", () => {
|
|
681
|
+
const cmds = buildOpentasksBridgeCommands({
|
|
682
|
+
tool_name: "mcp__plugin_claude-code-swarm_opentasks__create_task",
|
|
683
|
+
tool_input: { title: "Namespaced" },
|
|
684
|
+
});
|
|
685
|
+
expect(cmds).toHaveLength(1);
|
|
686
|
+
expect(cmds[0].action).toBe("bridge-task-created");
|
|
323
687
|
});
|
|
324
688
|
});
|
|
325
689
|
});
|