@inixiative/hivemind 0.1.0
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/LICENSE +21 -0
- package/README.md +115 -0
- package/dist/agents/agents.test.d.ts +1 -0
- package/dist/agents/agents.test.js +167 -0
- package/dist/agents/getActiveAgents.d.ts +6 -0
- package/dist/agents/getActiveAgents.js +11 -0
- package/dist/agents/getAgent.d.ts +6 -0
- package/dist/agents/getAgent.js +7 -0
- package/dist/agents/getAgentBySessionId.d.ts +10 -0
- package/dist/agents/getAgentBySessionId.js +17 -0
- package/dist/agents/index.d.ts +10 -0
- package/dist/agents/index.js +12 -0
- package/dist/agents/markAgentDead.d.ts +6 -0
- package/dist/agents/markAgentDead.js +26 -0
- package/dist/agents/markAgentIdle.d.ts +5 -0
- package/dist/agents/markAgentIdle.js +12 -0
- package/dist/agents/registerAgent.d.ts +6 -0
- package/dist/agents/registerAgent.js +29 -0
- package/dist/agents/types.d.ts +30 -0
- package/dist/agents/types.js +1 -0
- package/dist/agents/unregisterAgent.d.ts +5 -0
- package/dist/agents/unregisterAgent.js +8 -0
- package/dist/agents/updateAgentContext.d.ts +5 -0
- package/dist/agents/updateAgentContext.js +12 -0
- package/dist/agents/updateAgentTask.d.ts +5 -0
- package/dist/agents/updateAgentTask.js +12 -0
- package/dist/agents/updateAgentWorktree.d.ts +5 -0
- package/dist/agents/updateAgentWorktree.js +12 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.js +8 -0
- package/dist/cli/init.d.ts +14 -0
- package/dist/cli/init.js +71 -0
- package/dist/cli/install.d.ts +8 -0
- package/dist/cli/install.js +47 -0
- package/dist/cli/join.d.ts +9 -0
- package/dist/cli/join.js +38 -0
- package/dist/cli/registerMcp.d.ts +28 -0
- package/dist/cli/registerMcp.js +138 -0
- package/dist/cli/status.d.ts +8 -0
- package/dist/cli/status.js +82 -0
- package/dist/cli/watch.d.ts +6 -0
- package/dist/cli/watch.js +68 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +49 -0
- package/dist/coordinator/coordinator.test.d.ts +1 -0
- package/dist/coordinator/coordinator.test.js +171 -0
- package/dist/coordinator/index.d.ts +16 -0
- package/dist/coordinator/index.js +166 -0
- package/dist/coordinator/spawn.d.ts +22 -0
- package/dist/coordinator/spawn.js +66 -0
- package/dist/datetime/datetime.test.d.ts +1 -0
- package/dist/datetime/datetime.test.js +63 -0
- package/dist/datetime/formatDate.d.ts +6 -0
- package/dist/datetime/formatDate.js +11 -0
- package/dist/datetime/formatDatetime.d.ts +6 -0
- package/dist/datetime/formatDatetime.js +12 -0
- package/dist/datetime/formatTime.d.ts +6 -0
- package/dist/datetime/formatTime.js +11 -0
- package/dist/datetime/index.d.ts +4 -0
- package/dist/datetime/index.js +7 -0
- package/dist/datetime/isStale.d.ts +10 -0
- package/dist/datetime/isStale.js +18 -0
- package/dist/datetime/now.d.ts +6 -0
- package/dist/datetime/now.js +9 -0
- package/dist/datetime/parseDatetime.d.ts +7 -0
- package/dist/datetime/parseDatetime.js +28 -0
- package/dist/db/constants.d.ts +4 -0
- package/dist/db/constants.js +6 -0
- package/dist/db/db.test.d.ts +1 -0
- package/dist/db/db.test.js +141 -0
- package/dist/db/ensureProjectDirs.d.ts +4 -0
- package/dist/db/ensureProjectDirs.js +12 -0
- package/dist/db/getConnection.d.ts +19 -0
- package/dist/db/getConnection.js +51 -0
- package/dist/db/getCurrentProject.d.ts +8 -0
- package/dist/db/getCurrentProject.js +14 -0
- package/dist/db/getProjectPaths.d.ts +21 -0
- package/dist/db/getProjectPaths.js +26 -0
- package/dist/db/index.d.ts +10 -0
- package/dist/db/index.js +13 -0
- package/dist/db/initializeDb.d.ts +7 -0
- package/dist/db/initializeDb.js +23 -0
- package/dist/db/nextEventSeq.d.ts +5 -0
- package/dist/db/nextEventSeq.js +13 -0
- package/dist/db/nextSubtaskSeq.d.ts +5 -0
- package/dist/db/nextSubtaskSeq.js +12 -0
- package/dist/db/nextTaskSeq.d.ts +5 -0
- package/dist/db/nextTaskSeq.js +13 -0
- package/dist/db/resetDb.d.ts +10 -0
- package/dist/db/resetDb.js +36 -0
- package/dist/events/emit.d.ts +6 -0
- package/dist/events/emit.js +31 -0
- package/dist/events/events.test.d.ts +1 -0
- package/dist/events/events.test.js +145 -0
- package/dist/events/getEventsByAgent.d.ts +6 -0
- package/dist/events/getEventsByAgent.js +14 -0
- package/dist/events/getEventsByBranch.d.ts +6 -0
- package/dist/events/getEventsByBranch.js +12 -0
- package/dist/events/getEventsByPlan.d.ts +6 -0
- package/dist/events/getEventsByPlan.js +14 -0
- package/dist/events/getEventsByWorktree.d.ts +6 -0
- package/dist/events/getEventsByWorktree.js +12 -0
- package/dist/events/getEventsSince.d.ts +12 -0
- package/dist/events/getEventsSince.js +47 -0
- package/dist/events/getRecentEvents.d.ts +6 -0
- package/dist/events/getRecentEvents.js +12 -0
- package/dist/events/index.d.ts +8 -0
- package/dist/events/index.js +9 -0
- package/dist/events/types.d.ts +34 -0
- package/dist/events/types.js +1 -0
- package/dist/git/getBranch.d.ts +4 -0
- package/dist/git/getBranch.js +14 -0
- package/dist/git/getCurrentWorktree.d.ts +5 -0
- package/dist/git/getCurrentWorktree.js +15 -0
- package/dist/git/getGitInfo.d.ts +10 -0
- package/dist/git/getGitInfo.js +23 -0
- package/dist/git/getRepoName.d.ts +4 -0
- package/dist/git/getRepoName.js +32 -0
- package/dist/git/getRepoRoot.d.ts +4 -0
- package/dist/git/getRepoRoot.js +14 -0
- package/dist/git/getWorktrees.d.ts +10 -0
- package/dist/git/getWorktrees.js +39 -0
- package/dist/git/index.d.ts +9 -0
- package/dist/git/index.js +7 -0
- package/dist/git/isGitRepo.d.ts +4 -0
- package/dist/git/isGitRepo.js +13 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/sessionStart.d.ts +21 -0
- package/dist/hooks/sessionStart.js +93 -0
- package/dist/ids/generateHex.d.ts +4 -0
- package/dist/ids/generateHex.js +7 -0
- package/dist/ids/getParentTaskId.d.ts +7 -0
- package/dist/ids/getParentTaskId.js +15 -0
- package/dist/ids/getPlanHexFromTaskId.d.ts +6 -0
- package/dist/ids/getPlanHexFromTaskId.js +9 -0
- package/dist/ids/ids.test.d.ts +1 -0
- package/dist/ids/ids.test.js +215 -0
- package/dist/ids/index.d.ts +16 -0
- package/dist/ids/index.js +17 -0
- package/dist/ids/isSubtask.d.ts +7 -0
- package/dist/ids/isSubtask.js +11 -0
- package/dist/ids/isValidId.d.ts +9 -0
- package/dist/ids/isValidId.js +22 -0
- package/dist/ids/makeAgentId.d.ts +8 -0
- package/dist/ids/makeAgentId.js +15 -0
- package/dist/ids/makeEventId.d.ts +11 -0
- package/dist/ids/makeEventId.js +12 -0
- package/dist/ids/makePlanId.d.ts +11 -0
- package/dist/ids/makePlanId.js +15 -0
- package/dist/ids/makeSubtaskId.d.ts +8 -0
- package/dist/ids/makeSubtaskId.js +15 -0
- package/dist/ids/makeTaskId.d.ts +8 -0
- package/dist/ids/makeTaskId.js +14 -0
- package/dist/ids/makeWorktreeId.d.ts +5 -0
- package/dist/ids/makeWorktreeId.js +12 -0
- package/dist/ids/parseId.d.ts +11 -0
- package/dist/ids/parseId.js +26 -0
- package/dist/ids/sanitizeLabel.d.ts +7 -0
- package/dist/ids/sanitizeLabel.js +12 -0
- package/dist/ids/typedIds.d.ts +34 -0
- package/dist/ids/typedIds.js +22 -0
- package/dist/ids/types.d.ts +14 -0
- package/dist/ids/types.js +1 -0
- package/dist/init/claudeConfig.d.ts +39 -0
- package/dist/init/claudeConfig.js +161 -0
- package/dist/llm/extractTasks.d.ts +28 -0
- package/dist/llm/extractTasks.js +108 -0
- package/dist/llm/index.d.ts +2 -0
- package/dist/llm/index.js +2 -0
- package/dist/llm/reconcileTasks.d.ts +21 -0
- package/dist/llm/reconcileTasks.js +82 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.js +100 -0
- package/dist/mcp/tools/emitEvent.d.ts +62 -0
- package/dist/mcp/tools/emitEvent.js +84 -0
- package/dist/mcp/tools/events.d.ts +55 -0
- package/dist/mcp/tools/events.js +56 -0
- package/dist/mcp/tools/index.d.ts +18 -0
- package/dist/mcp/tools/index.js +13 -0
- package/dist/mcp/tools/query.d.ts +54 -0
- package/dist/mcp/tools/query.js +70 -0
- package/dist/mcp/tools/register.d.ts +47 -0
- package/dist/mcp/tools/register.js +79 -0
- package/dist/mcp/tools/reset.d.ts +38 -0
- package/dist/mcp/tools/reset.js +56 -0
- package/dist/mcp/tools/setup.d.ts +42 -0
- package/dist/mcp/tools/setup.js +75 -0
- package/dist/mcp/tools/status.d.ts +44 -0
- package/dist/mcp/tools/status.js +74 -0
- package/dist/mcp/tools/tasks.d.ts +116 -0
- package/dist/mcp/tools/tasks.js +143 -0
- package/dist/mcp/tools/worktreeCleanup.d.ts +38 -0
- package/dist/mcp/tools/worktreeCleanup.js +67 -0
- package/dist/plans/createPlan.d.ts +6 -0
- package/dist/plans/createPlan.js +29 -0
- package/dist/plans/getActivePlans.d.ts +6 -0
- package/dist/plans/getActivePlans.js +11 -0
- package/dist/plans/getPlan.d.ts +6 -0
- package/dist/plans/getPlan.js +7 -0
- package/dist/plans/index.d.ts +5 -0
- package/dist/plans/index.js +5 -0
- package/dist/plans/plans.test.d.ts +1 -0
- package/dist/plans/plans.test.js +107 -0
- package/dist/plans/types.d.ts +32 -0
- package/dist/plans/types.js +1 -0
- package/dist/plans/updatePlanStatus.d.ts +6 -0
- package/dist/plans/updatePlanStatus.js +8 -0
- package/dist/tasks/assignTask.d.ts +7 -0
- package/dist/tasks/assignTask.js +20 -0
- package/dist/tasks/blockTask.d.ts +5 -0
- package/dist/tasks/blockTask.js +12 -0
- package/dist/tasks/claimTask.d.ts +7 -0
- package/dist/tasks/claimTask.js +21 -0
- package/dist/tasks/completeTask.d.ts +6 -0
- package/dist/tasks/completeTask.js +18 -0
- package/dist/tasks/createTask.d.ts +6 -0
- package/dist/tasks/createTask.js +36 -0
- package/dist/tasks/getPendingTasks.d.ts +6 -0
- package/dist/tasks/getPendingTasks.js +19 -0
- package/dist/tasks/getTask.d.ts +6 -0
- package/dist/tasks/getTask.js +7 -0
- package/dist/tasks/getTasksByPlan.d.ts +6 -0
- package/dist/tasks/getTasksByPlan.js +11 -0
- package/dist/tasks/index.d.ts +11 -0
- package/dist/tasks/index.js +12 -0
- package/dist/tasks/startTask.d.ts +5 -0
- package/dist/tasks/startTask.js +12 -0
- package/dist/tasks/tasks.test.d.ts +1 -0
- package/dist/tasks/tasks.test.js +209 -0
- package/dist/tasks/types.d.ts +36 -0
- package/dist/tasks/types.js +1 -0
- package/dist/tasks/unclaimTask.d.ts +5 -0
- package/dist/tasks/unclaimTask.js +12 -0
- package/dist/test/factories/agentFactory.d.ts +13 -0
- package/dist/test/factories/agentFactory.js +5 -0
- package/dist/test/factories/eventFactory.d.ts +20 -0
- package/dist/test/factories/eventFactory.js +16 -0
- package/dist/test/factories/factories.test.d.ts +1 -0
- package/dist/test/factories/factories.test.js +101 -0
- package/dist/test/factories/index.d.ts +4 -0
- package/dist/test/factories/index.js +4 -0
- package/dist/test/factories/planFactory.d.ts +15 -0
- package/dist/test/factories/planFactory.js +14 -0
- package/dist/test/factories/taskFactory.d.ts +22 -0
- package/dist/test/factories/taskFactory.js +44 -0
- package/dist/test/multiAgentDemo.d.ts +16 -0
- package/dist/test/multiAgentDemo.js +20 -0
- package/dist/test/multiAgentDemo.test.d.ts +1 -0
- package/dist/test/multiAgentDemo.test.js +14 -0
- package/dist/test/setup.d.ts +9 -0
- package/dist/test/setup.js +50 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/math.d.ts +6 -0
- package/dist/utils/math.js +10 -0
- package/dist/utils/math.test.d.ts +1 -0
- package/dist/utils/math.test.js +26 -0
- package/dist/utils/string.d.ts +11 -0
- package/dist/utils/string.js +17 -0
- package/dist/utils/string.test.d.ts +1 -0
- package/dist/utils/string.test.js +30 -0
- package/dist/watcher/index.d.ts +1 -0
- package/dist/watcher/index.js +1 -0
- package/dist/watcher/planWatcher.d.ts +31 -0
- package/dist/watcher/planWatcher.js +171 -0
- package/dist/worktrees/getAllWorktrees.d.ts +6 -0
- package/dist/worktrees/getAllWorktrees.js +10 -0
- package/dist/worktrees/getWorktreeById.d.ts +6 -0
- package/dist/worktrees/getWorktreeById.js +7 -0
- package/dist/worktrees/getWorktreeByPath.d.ts +6 -0
- package/dist/worktrees/getWorktreeByPath.js +7 -0
- package/dist/worktrees/index.d.ts +9 -0
- package/dist/worktrees/index.js +13 -0
- package/dist/worktrees/registerWorktree.d.ts +6 -0
- package/dist/worktrees/registerWorktree.js +28 -0
- package/dist/worktrees/removeWorktree.d.ts +6 -0
- package/dist/worktrees/removeWorktree.js +9 -0
- package/dist/worktrees/syncWorktreesFromGit.d.ts +7 -0
- package/dist/worktrees/syncWorktreesFromGit.js +51 -0
- package/dist/worktrees/types.d.ts +29 -0
- package/dist/worktrees/types.js +1 -0
- package/dist/worktrees/updateWorktreeCommit.d.ts +5 -0
- package/dist/worktrees/updateWorktreeCommit.js +13 -0
- package/dist/worktrees/updateWorktreeSeen.d.ts +5 -0
- package/dist/worktrees/updateWorktreeSeen.js +13 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Aron Greenspan / inixiative
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Hivemind
|
|
2
|
+
|
|
3
|
+
Multi-agent coordination for Claude Code. Shared event log, plans, and tasks across multiple Claude sessions.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### From npm (recommended)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @inixiative/hivemind
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then add to Claude Code:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
claude mcp add hivemind -- hivemind-mcp
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or manually add to `~/.claude/settings.json`:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"mcpServers": {
|
|
24
|
+
"hivemind": {
|
|
25
|
+
"command": "hivemind-mcp"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### From source
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
git clone https://github.com/inixiative/hivemind.git
|
|
35
|
+
cd hivemind
|
|
36
|
+
./setup.sh
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Restart Claude Code and hivemind is active.
|
|
40
|
+
|
|
41
|
+
On startup, Claude receives context about the hivemind state:
|
|
42
|
+
```
|
|
43
|
+
hivemind: agt_7a3f2b joined myproject (main)
|
|
44
|
+
session: abc123-def456
|
|
45
|
+
active: agt_c4d5e6_alice
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
This info goes into Claude's system context (not printed to terminal). Claude knows its agent ID and can see other active agents.
|
|
49
|
+
|
|
50
|
+
## How It Works
|
|
51
|
+
|
|
52
|
+
Agents are tracked by **process ID (PID)**, not heartbeats:
|
|
53
|
+
- SessionStart hook registers agent with Claude's PID
|
|
54
|
+
- Coordinator monitors PIDs every 30 seconds
|
|
55
|
+
- When Claude exits, coordinator detects dead PID and marks agent dead
|
|
56
|
+
- No polling or heartbeats required
|
|
57
|
+
|
|
58
|
+
See [ARCHITECTURE.md](ARCHITECTURE.md) for details.
|
|
59
|
+
|
|
60
|
+
## Tools
|
|
61
|
+
|
|
62
|
+
| Tool | Description | Auto |
|
|
63
|
+
|------|-------------|------|
|
|
64
|
+
| `hivemind_status` | Get status (agents, plans, events) | ✓ |
|
|
65
|
+
| `hivemind_events` | Get recent events | ✓ |
|
|
66
|
+
| `hivemind_query` | Query events with filters | ✓ |
|
|
67
|
+
| `hivemind_claim_task` | Claim a task | ✓ |
|
|
68
|
+
| `hivemind_start_task` | Mark task in progress | ✓ |
|
|
69
|
+
| `hivemind_complete_task` | Mark task done | ✓ |
|
|
70
|
+
| `hivemind_worktree_cleanup` | Clean up stale worktrees | ✓ |
|
|
71
|
+
| `hivemind_setup` | Initialize project | ✓ |
|
|
72
|
+
| `hivemind_register` | Register this agent | ✓ |
|
|
73
|
+
| `hivemind_emit` | Emit event to log | ✗ |
|
|
74
|
+
| `hivemind_reset` | Reset database | ✗ |
|
|
75
|
+
|
|
76
|
+
Tools marked ✓ are auto-approved after `./setup.sh`. Tools marked ✗ require confirmation.
|
|
77
|
+
|
|
78
|
+
## CLI
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
hivemind install # One-time global setup
|
|
82
|
+
hivemind init # Register current project
|
|
83
|
+
hivemind status # Show project status
|
|
84
|
+
hivemind watch # Live tail of events
|
|
85
|
+
hivemind join # Join as an agent
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## ID Format
|
|
89
|
+
|
|
90
|
+
`{type}_{6hex}[_{label}]`
|
|
91
|
+
|
|
92
|
+
- `agt_7a3f2b_alice` - Agent
|
|
93
|
+
- `pln_e9d2c1_auth` - Plan
|
|
94
|
+
- `tsk_e9d2c1_001` - Task (inherits plan hex)
|
|
95
|
+
- `evt_f1a2b3_00001` - Event
|
|
96
|
+
- `wkt_a1b2c3` - Worktree
|
|
97
|
+
|
|
98
|
+
## Event Types
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
agent:register/unregister
|
|
102
|
+
plan:create/join/complete
|
|
103
|
+
task:create/claim/start/complete/block
|
|
104
|
+
decision, question, answer, note
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Database
|
|
108
|
+
|
|
109
|
+
Stored at `~/.hivemind/claude_hivemind_{project}/hivemind.db`
|
|
110
|
+
|
|
111
|
+
Tables: agents, plans, tasks, events, worktrees
|
|
112
|
+
|
|
113
|
+
## Timestamps
|
|
114
|
+
|
|
115
|
+
Format: `yyyy/mm/dd hh:mm:ss TZ`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, afterAll } from 'bun:test';
|
|
2
|
+
import { createTestDb, cleanupAllTestDbs } from '../test/setup';
|
|
3
|
+
import { buildAgent, buildTask } from '../test/factories';
|
|
4
|
+
import { registerAgent } from './registerAgent';
|
|
5
|
+
import { getAgent } from './getAgent';
|
|
6
|
+
import { getActiveAgents } from './getActiveAgents';
|
|
7
|
+
import { markAgentDead } from './markAgentDead';
|
|
8
|
+
import { markAgentIdle } from './markAgentIdle';
|
|
9
|
+
import { updateAgentContext } from './updateAgentContext';
|
|
10
|
+
import { updateAgentTask } from './updateAgentTask';
|
|
11
|
+
import { unregisterAgent } from './unregisterAgent';
|
|
12
|
+
describe('Agents Module', () => {
|
|
13
|
+
let testDb;
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
testDb = createTestDb();
|
|
16
|
+
});
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
testDb.cleanup();
|
|
19
|
+
});
|
|
20
|
+
afterAll(() => {
|
|
21
|
+
cleanupAllTestDbs();
|
|
22
|
+
});
|
|
23
|
+
describe('registerAgent', () => {
|
|
24
|
+
it('creates a new agent with generated id', () => {
|
|
25
|
+
const agent = registerAgent(testDb.db, {});
|
|
26
|
+
expect(agent.id).toMatch(/^agt_[a-f0-9]{6}$/);
|
|
27
|
+
expect(agent.status).toBe('active');
|
|
28
|
+
expect(agent.created_at).toBeDefined();
|
|
29
|
+
});
|
|
30
|
+
it('creates agent with label', () => {
|
|
31
|
+
const agent = registerAgent(testDb.db, { label: 'main' });
|
|
32
|
+
expect(agent.id).toMatch(/^agt_[a-f0-9]{6}_main$/);
|
|
33
|
+
expect(agent.label).toBe('main');
|
|
34
|
+
});
|
|
35
|
+
it('creates agent with context summary', () => {
|
|
36
|
+
const agent = registerAgent(testDb.db, {
|
|
37
|
+
context_summary: 'Working on auth feature',
|
|
38
|
+
});
|
|
39
|
+
expect(agent.context_summary).toBe('Working on auth feature');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe('getAgent', () => {
|
|
43
|
+
it('retrieves existing agent', () => {
|
|
44
|
+
const { agent: created } = buildAgent(testDb.db, { label: 'test' });
|
|
45
|
+
const retrieved = getAgent(testDb.db, created.id);
|
|
46
|
+
expect(retrieved).toBeDefined();
|
|
47
|
+
expect(retrieved.id).toBe(created.id);
|
|
48
|
+
expect(retrieved.label).toBe('test');
|
|
49
|
+
});
|
|
50
|
+
it('returns null for non-existent agent', () => {
|
|
51
|
+
const agent = getAgent(testDb.db, 'agt_nonexistent');
|
|
52
|
+
expect(agent).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('getActiveAgents', () => {
|
|
56
|
+
it('returns only active agents', () => {
|
|
57
|
+
const { agent: agent1 } = buildAgent(testDb.db, { label: 'one' });
|
|
58
|
+
const { agent: agent2 } = buildAgent(testDb.db, { label: 'two' });
|
|
59
|
+
markAgentDead(testDb.db, agent2.id);
|
|
60
|
+
const active = getActiveAgents(testDb.db);
|
|
61
|
+
expect(active).toHaveLength(1);
|
|
62
|
+
expect(active[0].id).toBe(agent1.id);
|
|
63
|
+
});
|
|
64
|
+
it('returns empty array when no active agents', () => {
|
|
65
|
+
const active = getActiveAgents(testDb.db);
|
|
66
|
+
expect(active).toHaveLength(0);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
describe('markAgentDead', () => {
|
|
70
|
+
it('sets status to dead', () => {
|
|
71
|
+
const { agent } = buildAgent(testDb.db);
|
|
72
|
+
markAgentDead(testDb.db, agent.id);
|
|
73
|
+
const retrieved = getAgent(testDb.db, agent.id);
|
|
74
|
+
expect(retrieved.status).toBe('dead');
|
|
75
|
+
});
|
|
76
|
+
it('releases claimed tasks back to pending', () => {
|
|
77
|
+
const { agent } = buildAgent(testDb.db);
|
|
78
|
+
const { task } = buildTask(testDb.db, {
|
|
79
|
+
agent,
|
|
80
|
+
status: 'claimed',
|
|
81
|
+
claimed_by: agent,
|
|
82
|
+
});
|
|
83
|
+
markAgentDead(testDb.db, agent.id);
|
|
84
|
+
const updated = testDb.db.prepare(`SELECT * FROM tasks WHERE id = ?`).get(task.id);
|
|
85
|
+
expect(updated.status).toBe('pending');
|
|
86
|
+
expect(updated.claimed_by).toBeNull();
|
|
87
|
+
expect(updated.claimed_at).toBeNull();
|
|
88
|
+
});
|
|
89
|
+
it('releases in_progress tasks back to pending', () => {
|
|
90
|
+
const { agent } = buildAgent(testDb.db);
|
|
91
|
+
const { task } = buildTask(testDb.db, {
|
|
92
|
+
agent,
|
|
93
|
+
status: 'in_progress',
|
|
94
|
+
claimed_by: agent,
|
|
95
|
+
});
|
|
96
|
+
markAgentDead(testDb.db, agent.id);
|
|
97
|
+
const updated = testDb.db.prepare(`SELECT * FROM tasks WHERE id = ?`).get(task.id);
|
|
98
|
+
expect(updated.status).toBe('pending');
|
|
99
|
+
expect(updated.claimed_by).toBeNull();
|
|
100
|
+
});
|
|
101
|
+
it('clears agent current_task_id and current_plan_id', () => {
|
|
102
|
+
const { agent } = buildAgent(testDb.db);
|
|
103
|
+
const { task, plan } = buildTask(testDb.db, { agent });
|
|
104
|
+
updateAgentTask(testDb.db, agent.id, plan.id, task.id);
|
|
105
|
+
let retrieved = getAgent(testDb.db, agent.id);
|
|
106
|
+
expect(retrieved.current_plan_id).toBe(plan.id);
|
|
107
|
+
expect(retrieved.current_task_id).toBe(task.id);
|
|
108
|
+
markAgentDead(testDb.db, agent.id);
|
|
109
|
+
retrieved = getAgent(testDb.db, agent.id);
|
|
110
|
+
expect(retrieved.current_plan_id).toBeNull();
|
|
111
|
+
expect(retrieved.current_task_id).toBeNull();
|
|
112
|
+
});
|
|
113
|
+
it('does not affect done tasks', () => {
|
|
114
|
+
const { agent } = buildAgent(testDb.db);
|
|
115
|
+
const { task } = buildTask(testDb.db, {
|
|
116
|
+
agent,
|
|
117
|
+
status: 'done',
|
|
118
|
+
claimed_by: agent,
|
|
119
|
+
});
|
|
120
|
+
markAgentDead(testDb.db, agent.id);
|
|
121
|
+
const updated = testDb.db.prepare(`SELECT * FROM tasks WHERE id = ?`).get(task.id);
|
|
122
|
+
expect(updated.status).toBe('done');
|
|
123
|
+
expect(updated.claimed_by).toBe(agent.id);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
describe('markAgentIdle', () => {
|
|
127
|
+
it('sets status to idle', () => {
|
|
128
|
+
const { agent } = buildAgent(testDb.db);
|
|
129
|
+
markAgentIdle(testDb.db, agent.id);
|
|
130
|
+
const retrieved = getAgent(testDb.db, agent.id);
|
|
131
|
+
expect(retrieved.status).toBe('idle');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
describe('updateAgentContext', () => {
|
|
135
|
+
it('updates context summary', () => {
|
|
136
|
+
const { agent } = buildAgent(testDb.db);
|
|
137
|
+
updateAgentContext(testDb.db, agent.id, 'New context');
|
|
138
|
+
const retrieved = getAgent(testDb.db, agent.id);
|
|
139
|
+
expect(retrieved.context_summary).toBe('New context');
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
describe('updateAgentTask', () => {
|
|
143
|
+
it('updates current plan and task id', () => {
|
|
144
|
+
const { agent } = buildAgent(testDb.db);
|
|
145
|
+
const { task, plan } = buildTask(testDb.db, { agent });
|
|
146
|
+
updateAgentTask(testDb.db, agent.id, plan.id, task.id);
|
|
147
|
+
const retrieved = getAgent(testDb.db, agent.id);
|
|
148
|
+
expect(retrieved.current_plan_id).toBe(plan.id);
|
|
149
|
+
expect(retrieved.current_task_id).toBe(task.id);
|
|
150
|
+
});
|
|
151
|
+
it('clears plan and task with null', () => {
|
|
152
|
+
const { agent } = buildAgent(testDb.db);
|
|
153
|
+
updateAgentTask(testDb.db, agent.id, null, null);
|
|
154
|
+
const retrieved = getAgent(testDb.db, agent.id);
|
|
155
|
+
expect(retrieved.current_plan_id).toBeNull();
|
|
156
|
+
expect(retrieved.current_task_id).toBeNull();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
describe('unregisterAgent', () => {
|
|
160
|
+
it('removes agent from database', () => {
|
|
161
|
+
const { agent } = buildAgent(testDb.db);
|
|
162
|
+
unregisterAgent(testDb.db, agent.id);
|
|
163
|
+
const retrieved = getAgent(testDb.db, agent.id);
|
|
164
|
+
expect(retrieved).toBeNull();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
import type { Agent } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Get an active agent by session ID
|
|
5
|
+
*
|
|
6
|
+
* Used for MCP tools to find "their" agent without needing the agent_id.
|
|
7
|
+
* The session_id is passed by Claude Code to hooks and can be used to
|
|
8
|
+
* correlate the hook-registered agent with subsequent MCP calls.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getAgentBySessionId(db: Database, sessionId: string): Agent | null;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get an active agent by session ID
|
|
3
|
+
*
|
|
4
|
+
* Used for MCP tools to find "their" agent without needing the agent_id.
|
|
5
|
+
* The session_id is passed by Claude Code to hooks and can be used to
|
|
6
|
+
* correlate the hook-registered agent with subsequent MCP calls.
|
|
7
|
+
*/
|
|
8
|
+
export function getAgentBySessionId(db, sessionId) {
|
|
9
|
+
const stmt = db.prepare(`
|
|
10
|
+
SELECT * FROM agents
|
|
11
|
+
WHERE session_id = ?
|
|
12
|
+
AND status = 'active'
|
|
13
|
+
ORDER BY created_at DESC
|
|
14
|
+
LIMIT 1
|
|
15
|
+
`);
|
|
16
|
+
return stmt.get(sessionId) ?? null;
|
|
17
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type { AgentStatus, Agent, RegisterAgentInput } from './types';
|
|
2
|
+
export { registerAgent } from './registerAgent';
|
|
3
|
+
export { unregisterAgent } from './unregisterAgent';
|
|
4
|
+
export { getAgent } from './getAgent';
|
|
5
|
+
export { getActiveAgents } from './getActiveAgents';
|
|
6
|
+
export { updateAgentContext } from './updateAgentContext';
|
|
7
|
+
export { updateAgentTask } from './updateAgentTask';
|
|
8
|
+
export { updateAgentWorktree } from './updateAgentWorktree';
|
|
9
|
+
export { markAgentDead } from './markAgentDead';
|
|
10
|
+
export { markAgentIdle } from './markAgentIdle';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Registration
|
|
2
|
+
export { registerAgent } from './registerAgent';
|
|
3
|
+
export { unregisterAgent } from './unregisterAgent';
|
|
4
|
+
// Query
|
|
5
|
+
export { getAgent } from './getAgent';
|
|
6
|
+
export { getActiveAgents } from './getActiveAgents';
|
|
7
|
+
// Updates
|
|
8
|
+
export { updateAgentContext } from './updateAgentContext';
|
|
9
|
+
export { updateAgentTask } from './updateAgentTask';
|
|
10
|
+
export { updateAgentWorktree } from './updateAgentWorktree';
|
|
11
|
+
export { markAgentDead } from './markAgentDead';
|
|
12
|
+
export { markAgentIdle } from './markAgentIdle';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mark an agent as dead (no longer responding)
|
|
3
|
+
* Also releases any tasks the agent had claimed back to pending
|
|
4
|
+
*/
|
|
5
|
+
export function markAgentDead(db, agentId) {
|
|
6
|
+
// Release any tasks this agent had claimed or was working on
|
|
7
|
+
const releaseTasksStmt = db.prepare(`
|
|
8
|
+
UPDATE tasks
|
|
9
|
+
SET status = 'pending',
|
|
10
|
+
claimed_by = NULL,
|
|
11
|
+
claimed_at = NULL
|
|
12
|
+
WHERE claimed_by = ?
|
|
13
|
+
AND status IN ('claimed', 'in_progress')
|
|
14
|
+
`);
|
|
15
|
+
releaseTasksStmt.run(agentId);
|
|
16
|
+
// Mark the agent as dead and clear its current task/plan references
|
|
17
|
+
const stmt = db.prepare(`
|
|
18
|
+
UPDATE agents
|
|
19
|
+
SET status = 'dead',
|
|
20
|
+
current_task_id = NULL,
|
|
21
|
+
current_plan_id = NULL
|
|
22
|
+
WHERE id = ?
|
|
23
|
+
`);
|
|
24
|
+
const result = stmt.run(agentId);
|
|
25
|
+
return result.changes > 0;
|
|
26
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mark an agent as idle (alive but not actively working)
|
|
3
|
+
*/
|
|
4
|
+
export function markAgentIdle(db, agentId) {
|
|
5
|
+
const stmt = db.prepare(`
|
|
6
|
+
UPDATE agents
|
|
7
|
+
SET status = 'idle'
|
|
8
|
+
WHERE id = ?
|
|
9
|
+
`);
|
|
10
|
+
const result = stmt.run(agentId);
|
|
11
|
+
return result.changes > 0;
|
|
12
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { makeAgentId } from '../ids/makeAgentId';
|
|
2
|
+
import { parseId } from '../ids/parseId';
|
|
3
|
+
import { now } from '../datetime/now';
|
|
4
|
+
/**
|
|
5
|
+
* Register a new agent in the hivemind
|
|
6
|
+
*/
|
|
7
|
+
export function registerAgent(db, input = {}) {
|
|
8
|
+
const id = makeAgentId(input.label);
|
|
9
|
+
const parsed = parseId(id);
|
|
10
|
+
const timestamp = now();
|
|
11
|
+
const stmt = db.prepare(`
|
|
12
|
+
INSERT INTO agents (id, hex, label, status, pid, session_id, worktree_id, context_summary, created_at)
|
|
13
|
+
VALUES (?, ?, ?, 'active', ?, ?, ?, ?, ?)
|
|
14
|
+
`);
|
|
15
|
+
stmt.run(id, parsed.hex, parsed.label ?? null, input.pid ?? null, input.session_id ?? null, input.worktree_id ?? null, input.context_summary ?? null, timestamp);
|
|
16
|
+
return {
|
|
17
|
+
id,
|
|
18
|
+
hex: parsed.hex,
|
|
19
|
+
label: parsed.label ?? null,
|
|
20
|
+
status: 'active',
|
|
21
|
+
pid: input.pid ?? null,
|
|
22
|
+
session_id: input.session_id ?? null,
|
|
23
|
+
current_plan_id: null,
|
|
24
|
+
current_task_id: null,
|
|
25
|
+
worktree_id: input.worktree_id ?? null,
|
|
26
|
+
context_summary: input.context_summary ?? null,
|
|
27
|
+
created_at: timestamp,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent status values
|
|
3
|
+
*/
|
|
4
|
+
export type AgentStatus = 'active' | 'idle' | 'dead';
|
|
5
|
+
/**
|
|
6
|
+
* Agent record as stored in DB
|
|
7
|
+
*/
|
|
8
|
+
export type Agent = {
|
|
9
|
+
id: string;
|
|
10
|
+
hex: string;
|
|
11
|
+
label: string | null;
|
|
12
|
+
status: AgentStatus;
|
|
13
|
+
pid: number | null;
|
|
14
|
+
session_id: string | null;
|
|
15
|
+
current_plan_id: string | null;
|
|
16
|
+
current_task_id: string | null;
|
|
17
|
+
worktree_id: string | null;
|
|
18
|
+
context_summary: string | null;
|
|
19
|
+
created_at: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Input for registering an agent
|
|
23
|
+
*/
|
|
24
|
+
export type RegisterAgentInput = {
|
|
25
|
+
label?: string;
|
|
26
|
+
pid?: number;
|
|
27
|
+
session_id?: string;
|
|
28
|
+
worktree_id?: string;
|
|
29
|
+
context_summary?: string;
|
|
30
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update an agent's context summary (what it knows/is working on)
|
|
3
|
+
*/
|
|
4
|
+
export function updateAgentContext(db, agentId, contextSummary) {
|
|
5
|
+
const stmt = db.prepare(`
|
|
6
|
+
UPDATE agents
|
|
7
|
+
SET context_summary = ?
|
|
8
|
+
WHERE id = ?
|
|
9
|
+
`);
|
|
10
|
+
const result = stmt.run(contextSummary, agentId);
|
|
11
|
+
return result.changes > 0;
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update an agent's current plan and task
|
|
3
|
+
*/
|
|
4
|
+
export function updateAgentTask(db, agentId, planId, taskId) {
|
|
5
|
+
const stmt = db.prepare(`
|
|
6
|
+
UPDATE agents
|
|
7
|
+
SET current_plan_id = ?, current_task_id = ?
|
|
8
|
+
WHERE id = ?
|
|
9
|
+
`);
|
|
10
|
+
const result = stmt.run(planId, taskId, agentId);
|
|
11
|
+
return result.changes > 0;
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update an agent's worktree assignment
|
|
3
|
+
*/
|
|
4
|
+
export function updateAgentWorktree(db, agentId, worktreeId) {
|
|
5
|
+
const stmt = db.prepare(`
|
|
6
|
+
UPDATE agents
|
|
7
|
+
SET worktree_id = ?
|
|
8
|
+
WHERE id = ?
|
|
9
|
+
`);
|
|
10
|
+
const result = stmt.run(worktreeId, agentId);
|
|
11
|
+
return result.changes > 0;
|
|
12
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hivemind init - Register a project with hivemind
|
|
3
|
+
*
|
|
4
|
+
* Run from a project directory to:
|
|
5
|
+
* 1. Create database in ~/.hivemind/<project>/
|
|
6
|
+
* 2. Set up .claude/settings.json hooks
|
|
7
|
+
* 3. Add CLAUDE.md instructions
|
|
8
|
+
*/
|
|
9
|
+
type InitOptions = {
|
|
10
|
+
project?: string;
|
|
11
|
+
hooks?: boolean;
|
|
12
|
+
};
|
|
13
|
+
export declare function initCommand(options: InitOptions): Promise<void>;
|
|
14
|
+
export {};
|