claude-code-swarm 0.3.11 → 0.3.16

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-swarm",
3
- "version": "0.3.11",
3
+ "version": "0.3.16",
4
4
  "description": "Launch Claude Code with swarmkit capabilities, including team orchestration, MAP observability, and session tracking.",
5
5
  "owner": {
6
6
  "name": "alexngai"
@@ -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.11",
4
+ "version": "0.3.16",
5
5
  "author": {
6
6
  "name": "alexngai"
7
7
  },
@@ -12,9 +12,7 @@
12
12
  "${CLAUDE_PLUGIN_ROOT}/.claude-plugin/mcp-launcher.mjs",
13
13
  "opentasks"
14
14
  ],
15
- "env": {
16
- "OPENTASKS_WORKING_DIR": "${workspaceFolder}"
17
- }
15
+ "env": {}
18
16
  },
19
17
  "agent-inbox": {
20
18
  "command": "node",
@@ -30,9 +28,7 @@
30
28
  "${CLAUDE_PLUGIN_ROOT}/.claude-plugin/mcp-launcher.mjs",
31
29
  "minimem"
32
30
  ],
33
- "env": {
34
- "MINIMEM_WORKING_DIR": "${workspaceFolder}"
35
- }
31
+ "env": {}
36
32
  }
37
33
  }
38
34
  }
package/hooks/hooks.json CHANGED
@@ -7,6 +7,21 @@
7
7
  {
8
8
  "type": "command",
9
9
  "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/bootstrap.mjs\""
10
+ },
11
+ {
12
+ "type": "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
+ }
15
+ ]
16
+ }
17
+ ],
18
+ "SessionEnd": [
19
+ {
20
+ "matcher": "",
21
+ "hooks": [
22
+ {
23
+ "type": "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"
10
25
  }
11
26
  ]
12
27
  }
@@ -17,7 +32,22 @@
17
32
  "hooks": [
18
33
  {
19
34
  "type": "command",
20
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8')).map||{};process.exit(c.enabled||c.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" inject; fi"
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
+ },
37
+ {
38
+ "type": "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
+ }
41
+ ]
42
+ }
43
+ ],
44
+ "PreToolUse": [
45
+ {
46
+ "matcher": "Task",
47
+ "hooks": [
48
+ {
49
+ "type": "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"
21
51
  }
22
52
  ]
23
53
  }
@@ -28,7 +58,16 @@
28
58
  "hooks": [
29
59
  {
30
60
  "type": "command",
31
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8'));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; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" opentasks-mcp-used; fi"
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": "Task",
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.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch post-task"
32
71
  }
33
72
  ]
34
73
  },
@@ -37,7 +76,11 @@
37
76
  "hooks": [
38
77
  {
39
78
  "type": "command",
40
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8')).map||{};process.exit(c.enabled||c.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" native-task-created; fi"
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{}};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
+ },
81
+ {
82
+ "type": "command",
83
+ "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"
41
84
  }
42
85
  ]
43
86
  },
@@ -46,7 +89,47 @@
46
89
  "hooks": [
47
90
  {
48
91
  "type": "command",
49
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8')).map||{};process.exit(c.enabled||c.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" native-task-updated; fi"
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{}};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
+ },
94
+ {
95
+ "type": "command",
96
+ "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
+ }
98
+ ]
99
+ },
100
+ {
101
+ "matcher": "TodoWrite",
102
+ "hooks": [
103
+ {
104
+ "type": "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-todo"
106
+ }
107
+ ]
108
+ },
109
+ {
110
+ "matcher": "EnterPlanMode",
111
+ "hooks": [
112
+ {
113
+ "type": "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-plan-enter"
115
+ }
116
+ ]
117
+ },
118
+ {
119
+ "matcher": "ExitPlanMode",
120
+ "hooks": [
121
+ {
122
+ "type": "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-exit"
124
+ }
125
+ ]
126
+ },
127
+ {
128
+ "matcher": "Skill",
129
+ "hooks": [
130
+ {
131
+ "type": "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-skill"
50
133
  }
51
134
  ]
52
135
  }
@@ -57,11 +140,15 @@
57
140
  "hooks": [
58
141
  {
59
142
  "type": "command",
60
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8')).map||{};process.exit(c.enabled||c.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" turn-completed; fi"
143
+ "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
+ },
145
+ {
146
+ "type": "command",
147
+ "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"
61
148
  },
62
149
  {
63
150
  "type": "command",
64
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8'));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; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-sync; fi"
151
+ "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"
65
152
  }
66
153
  ]
67
154
  }
@@ -72,7 +159,7 @@
72
159
  "hooks": [
73
160
  {
74
161
  "type": "command",
75
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8')).map||{};process.exit(c.enabled||c.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" subagent-start; fi"
162
+ "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"
76
163
  }
77
164
  ]
78
165
  }
@@ -83,7 +170,7 @@
83
170
  "hooks": [
84
171
  {
85
172
  "type": "command",
86
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8')).map||{};process.exit(c.enabled||c.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" subagent-stop; fi"
173
+ "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"
87
174
  }
88
175
  ]
89
176
  }
@@ -94,7 +181,7 @@
94
181
  "hooks": [
95
182
  {
96
183
  "type": "command",
97
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8')).map||{};process.exit(c.enabled||c.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" teammate-idle; fi"
184
+ "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"
98
185
  }
99
186
  ]
100
187
  }
@@ -105,7 +192,7 @@
105
192
  "hooks": [
106
193
  {
107
194
  "type": "command",
108
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8')).map||{};process.exit(c.enabled||c.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" task-completed; fi"
195
+ "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"
109
196
  }
110
197
  ]
111
198
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-swarm",
3
- "version": "0.3.11",
3
+ "version": "0.3.16",
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.4",
21
+ "@multi-agent-protocol/sdk": "^0.1.8",
22
22
  "agentic-mesh": "^0.2.0",
23
23
  "js-yaml": "^4.1.0"
24
24
  },
@@ -55,7 +55,7 @@
55
55
  "devDependencies": {
56
56
  "agent-inbox": "^0.1.9",
57
57
  "minimem": "^0.1.0",
58
- "opentasks": "^0.0.6",
58
+ "opentasks": "^0.0.8",
59
59
  "skill-tree": "^0.1.5",
60
60
  "vitest": "^4.0.18",
61
61
  "ws": "^8.0.0"
@@ -19,6 +19,7 @@
19
19
  * teammate-idle — Update teammate state to idle
20
20
  * task-completed — Complete task in opentasks + emit bridge event
21
21
  * opentasks-mcp-used — Bridge opentasks MCP tool use → MAP task sync payload
22
+ * sessionlog-dispatch — Dispatch a sessionlog lifecycle hook via programmatic API
22
23
  *
23
24
  * Usage: node map-hook.mjs <action>
24
25
  * Hook event data is read from stdin (JSON).
@@ -48,7 +49,7 @@ import {
48
49
  handleNativeTaskCreatedEvent,
49
50
  handleNativeTaskUpdatedEvent,
50
51
  } from "../src/map-events.mjs";
51
- import { syncSessionlog } from "../src/sessionlog.mjs";
52
+ import { syncSessionlog, dispatchSessionlogHook } from "../src/sessionlog.mjs";
52
53
  import { findSocketPath, pushSyncEvent } from "../src/opentasks-client.mjs";
53
54
 
54
55
  const action = process.argv[2];
@@ -184,6 +185,15 @@ async function handleNativeTaskUpdated(hookData, sessionId) {
184
185
  await handleNativeTaskUpdatedEvent(config, hookData, sessionId);
185
186
  }
186
187
 
188
+ async function handleSessionlogDispatch(hookData) {
189
+ const sessionlogHookName = process.argv[3];
190
+ if (!sessionlogHookName) {
191
+ log.warn("sessionlog-dispatch: missing hook name argument");
192
+ return;
193
+ }
194
+ await dispatchSessionlogHook(sessionlogHookName, hookData);
195
+ }
196
+
187
197
  // ── Main ──────────────────────────────────────────────────────────────────────
188
198
 
189
199
  async function main() {
@@ -204,6 +214,7 @@ async function main() {
204
214
  case "opentasks-mcp-used": await handleOpentasksMcpUsed(hookData, sessionId); break;
205
215
  case "native-task-created": await handleNativeTaskCreated(hookData, sessionId); break;
206
216
  case "native-task-updated": await handleNativeTaskUpdated(hookData, sessionId); break;
217
+ case "sessionlog-dispatch": await handleSessionlogDispatch(hookData); break;
207
218
  default:
208
219
  log.warn("unknown action", { action });
209
220
  }
@@ -24,6 +24,7 @@ import { SOCKET_PATH, PID_PATH, INBOX_SOCKET_PATH, sessionPaths, pluginDir } fro
24
24
  import { connectToMAP } from "../src/map-connection.mjs";
25
25
  import { createMeshPeer, createMeshInbox } from "../src/mesh-connection.mjs";
26
26
  import { createSocketServer, createCommandHandler } from "../src/sidecar-server.mjs";
27
+ import { createContentProvider } from "../src/content-provider.mjs";
27
28
  import { readConfig } from "../src/config.mjs";
28
29
  import { createLogger, init as initLog } from "../src/log.mjs";
29
30
  import { configureNodePath, resolvePackage } from "../src/swarmkit-resolver.mjs";
@@ -50,6 +51,38 @@ const RECONNECT_INTERVAL_MS = parseInt(getArg("reconnect-interval", ""), 10) ||
50
51
  // Auth credential for server-driven auth negotiation (opaque — type determined by server)
51
52
  const AUTH_CREDENTIAL = getArg("credential", "");
52
53
 
54
+ // Project context for swarm identification (sent as agent metadata)
55
+ import { execSync } from "child_process";
56
+ function getProjectContext() {
57
+ const context = {};
58
+ try { context.project = path.basename(process.cwd()); } catch {}
59
+ try {
60
+ context.branch = execSync("git rev-parse --abbrev-ref HEAD", {
61
+ encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"],
62
+ }).trim();
63
+ } catch {}
64
+ try {
65
+ const config = readConfig();
66
+ if (config.template) context.template = config.template;
67
+
68
+ // Include task_graph metadata when opentasks is enabled
69
+ if (config.opentasks?.enabled) {
70
+ try {
71
+ const opentasksDir = path.resolve(".opentasks");
72
+ const configPath = path.join(opentasksDir, "config.json");
73
+ const taskGraph = { path: opentasksDir };
74
+ if (fs.existsSync(configPath)) {
75
+ const otConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
76
+ if (otConfig.location?.hash) taskGraph.location_hash = otConfig.location.hash;
77
+ }
78
+ context.task_graph = taskGraph;
79
+ } catch { /* opentasks config not available */ }
80
+ }
81
+ } catch {}
82
+ return context;
83
+ }
84
+ const PROJECT_CONTEXT = getProjectContext();
85
+
53
86
  // Configure NODE_PATH so dynamic imports of globally-installed packages
54
87
  // (@multi-agent-protocol/sdk, agent-inbox, agentic-mesh) resolve correctly.
55
88
  // Must happen before any dynamic import() calls.
@@ -186,6 +219,7 @@ function startSlowReconnectLoop() {
186
219
  scope: MAP_SCOPE,
187
220
  systemId: SYSTEM_ID,
188
221
  credential: AUTH_CREDENTIAL || undefined,
222
+ projectContext: PROJECT_CONTEXT,
189
223
  onMessage: () => resetInactivityTimer(),
190
224
  });
191
225
 
@@ -300,15 +334,77 @@ async function tryMeshTransport() {
300
334
  return true;
301
335
  }
302
336
 
337
+ /**
338
+ * Register opentasks notification handlers on a MAP connection.
339
+ * Delegates to the extracted opentasks-connector module for testability.
340
+ */
341
+ async function registerOpenTasksHandler(conn) {
342
+ const { registerOpenTasksHandler: _register } = await import("../src/opentasks-connector.mjs");
343
+ return _register(conn, {
344
+ scope: MAP_SCOPE,
345
+ onActivity: resetInactivityTimer,
346
+ });
347
+ }
348
+
303
349
  /**
304
350
  * Start with direct MAP SDK WebSocket transport (fallback).
305
351
  */
352
+ /**
353
+ * Register the trajectory/content.request notification handler on a connection.
354
+ * When the hub sends a content request, the sidecar reads the transcript
355
+ * from sessionlog and responds with a trajectory/content.response notification.
356
+ */
357
+ function registerContentHandler(conn) {
358
+ if (!conn || typeof conn.onNotification !== "function") return;
359
+
360
+ const contentProvider = createContentProvider();
361
+
362
+ conn.onNotification("trajectory/content.request", async (params) => {
363
+ const requestId = params?.request_id;
364
+ const checkpointId = params?.checkpoint_id;
365
+ if (!requestId) return;
366
+
367
+ log.info("content request received", { requestId, checkpointId });
368
+ resetInactivityTimer();
369
+
370
+ try {
371
+ const content = checkpointId ? await contentProvider(checkpointId) : null;
372
+
373
+ if (content) {
374
+ conn.sendNotification("trajectory/content.response", {
375
+ request_id: requestId,
376
+ transcript: content.transcript,
377
+ metadata: content.metadata,
378
+ prompts: content.prompts,
379
+ context: content.context,
380
+ });
381
+ log.info("content response sent", { requestId, size: content.transcript.length });
382
+ } else {
383
+ conn.sendNotification("trajectory/content.response", {
384
+ request_id: requestId,
385
+ error: "Content not found",
386
+ });
387
+ log.warn("content not found", { requestId, checkpointId });
388
+ }
389
+ } catch (err) {
390
+ log.error("content provider error", { requestId, error: err.message });
391
+ try {
392
+ conn.sendNotification("trajectory/content.response", {
393
+ request_id: requestId,
394
+ error: err.message,
395
+ });
396
+ } catch { /* ignore */ }
397
+ }
398
+ });
399
+ }
400
+
306
401
  async function startWebSocketTransport() {
307
402
  connection = await connectToMAP({
308
403
  server: MAP_SERVER,
309
404
  scope: MAP_SCOPE,
310
405
  systemId: SYSTEM_ID,
311
406
  credential: AUTH_CREDENTIAL || undefined,
407
+ projectContext: PROJECT_CONTEXT,
312
408
  onMessage: () => {
313
409
  resetInactivityTimer();
314
410
  },
@@ -316,6 +412,16 @@ async function startWebSocketTransport() {
316
412
 
317
413
  transportMode = "websocket";
318
414
 
415
+ // Register trajectory content handler for on-demand transcript serving
416
+ if (connection) {
417
+ registerContentHandler(connection);
418
+ }
419
+
420
+ // Register opentasks connector for remote graph queries (only when opentasks is enabled)
421
+ if (connection && PROJECT_CONTEXT.task_graph) {
422
+ await registerOpenTasksHandler(connection);
423
+ }
424
+
319
425
  // Start agent-inbox with MAP connection (legacy mode)
320
426
  if (INBOX_CONFIG && connection) {
321
427
  inboxInstance = await startLegacyAgentInbox(connection);
@@ -22,6 +22,8 @@ vi.mock("../map-events.mjs", () => ({
22
22
 
23
23
  vi.mock("../sessionlog.mjs", () => ({
24
24
  checkSessionlogStatus: vi.fn(() => "not installed"),
25
+ ensureSessionlogEnabled: vi.fn().mockResolvedValue(false),
26
+ hasStandaloneHooks: vi.fn().mockReturnValue(false),
25
27
  syncSessionlog: vi.fn().mockResolvedValue(undefined),
26
28
  annotateSwarmSession: vi.fn().mockResolvedValue(undefined),
27
29
  }));
@@ -86,7 +88,7 @@ const { bootstrap, backgroundInit } = await import("../bootstrap.mjs");
86
88
  const { readConfig } = await import("../config.mjs");
87
89
  const { killSidecar, startSidecar } = await import("../sidecar-client.mjs");
88
90
  const { sendCommand } = await import("../map-events.mjs");
89
- const { checkSessionlogStatus, syncSessionlog, annotateSwarmSession } = await import("../sessionlog.mjs");
91
+ const { checkSessionlogStatus, ensureSessionlogEnabled, hasStandaloneHooks, syncSessionlog, annotateSwarmSession } = await import("../sessionlog.mjs");
90
92
  const { pluginDir, ensureOpentasksDir, ensureSessionDir, listSessionDirs } = await import("../paths.mjs");
91
93
  const { findSocketPath, isDaemonAlive, ensureDaemon } = await import("../opentasks-client.mjs");
92
94
  const { resolveSwarmkit, configureNodePath } = await import("../swarmkit-resolver.mjs");
@@ -161,16 +163,43 @@ describe("bootstrap", () => {
161
163
  });
162
164
 
163
165
  describe("sessionlog", () => {
164
- it("returns 'checking' when sessionlog enabled (actual check is in background)", async () => {
166
+ it("defers to standalone when standalone hooks are present", async () => {
167
+ hasStandaloneHooks.mockReturnValue(true);
165
168
  readConfig.mockReturnValue(makeConfig({ sessionlogEnabled: true }));
166
169
  const result = await bootstrap();
167
- expect(result.sessionlogStatus).toBe("checking");
170
+ expect(result.sessionlogStatus).toBe("active (standalone)");
171
+ expect(ensureSessionlogEnabled).not.toHaveBeenCalled();
168
172
  });
169
173
 
170
- it("does not call checkSessionlogStatus synchronously", async () => {
174
+ it("enables sessionlog when no standalone hooks", async () => {
175
+ hasStandaloneHooks.mockReturnValue(false);
176
+ ensureSessionlogEnabled.mockResolvedValue(true);
171
177
  readConfig.mockReturnValue(makeConfig({ sessionlogEnabled: true }));
178
+ const result = await bootstrap();
179
+ expect(result.sessionlogStatus).toBe("active");
180
+ expect(ensureSessionlogEnabled).toHaveBeenCalled();
181
+ });
182
+
183
+ it("reports status when enable fails", async () => {
184
+ hasStandaloneHooks.mockReturnValue(false);
185
+ ensureSessionlogEnabled.mockResolvedValue(false);
186
+ readConfig.mockReturnValue(makeConfig({ sessionlogEnabled: true }));
187
+ const result = await bootstrap();
188
+ expect(result.sessionlogStatus).toBe("installed but not enabled");
189
+ });
190
+
191
+ it("returns 'checking' when hasStandaloneHooks throws", async () => {
192
+ hasStandaloneHooks.mockImplementation(() => { throw new Error("unexpected"); });
193
+ readConfig.mockReturnValue(makeConfig({ sessionlogEnabled: true }));
194
+ const result = await bootstrap();
195
+ expect(result.sessionlogStatus).toBe("checking");
196
+ });
197
+
198
+ it("does not check standalone when disabled", async () => {
199
+ readConfig.mockReturnValue(makeConfig({ sessionlogEnabled: false }));
172
200
  await bootstrap();
173
- expect(checkSessionlogStatus).not.toHaveBeenCalled();
201
+ expect(hasStandaloneHooks).not.toHaveBeenCalled();
202
+ expect(ensureSessionlogEnabled).not.toHaveBeenCalled();
174
203
  });
175
204
  });
176
205
 
@@ -32,6 +32,7 @@ export function makeConfig(overrides = {}) {
32
32
  sessionlog: {
33
33
  enabled: overrides.sessionlogEnabled ?? false,
34
34
  sync: overrides.sessionlogSync ?? "off",
35
+ mode: overrides.sessionlogMode ?? "auto",
35
36
  },
36
37
  opentasks: {
37
38
  enabled: overrides.opentasksEnabled ?? false,