@monoes/monomindcli 1.6.0 → 1.6.2
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/commands/monomind-do.md +41 -0
- package/.claude/commands/monomind-repeat.md +55 -1
- package/dist/src/mcp-tools/agent-tools.js +12 -1
- package/dist/src/mcp-tools/hive-mind-tools.js +18 -1
- package/dist/src/mcp-tools/hooks-tools.js +11 -1
- package/dist/src/mcp-tools/swarm-tools.js +11 -1
- package/dist/src/ui/collector.mjs +98 -9
- package/dist/src/ui/dashboard.html +1173 -511
- package/dist/src/ui/server.mjs +168 -3
- package/package.json +1 -1
|
@@ -55,6 +55,35 @@ If `cargo` is also missing, output this and STOP:
|
|
|
55
55
|
|
|
56
56
|
---
|
|
57
57
|
|
|
58
|
+
## Step 1.5: Initialize Loop State
|
|
59
|
+
|
|
60
|
+
Generate a loop ID and write the initial state file so the dashboard can track this run:
|
|
61
|
+
```bash
|
|
62
|
+
mkdir -p .monomind/loops
|
|
63
|
+
export DO_LOOP_ID="do-$(date +%s%3N)"
|
|
64
|
+
cat > ".monomind/loops/${DO_LOOP_ID}.json" << EOF
|
|
65
|
+
{
|
|
66
|
+
"id": "${DO_LOOP_ID}",
|
|
67
|
+
"type": "do",
|
|
68
|
+
"prompt": "/monomind:do $ARGUMENTS",
|
|
69
|
+
"currentTask": "discovering...",
|
|
70
|
+
"spaceId": "${SPACE_ID:-}",
|
|
71
|
+
"boardId": "${TASK_BOARD_ID:-}",
|
|
72
|
+
"filter": "${FILTER:-}",
|
|
73
|
+
"startedAt": $(date +%s%3N),
|
|
74
|
+
"lastRunAt": $(date +%s%3N),
|
|
75
|
+
"nextRunAt": 0,
|
|
76
|
+
"status": "running"
|
|
77
|
+
}
|
|
78
|
+
EOF
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Also check if a stop was requested from a previous cycle:
|
|
82
|
+
```bash
|
|
83
|
+
[ -f ".monomind/loops/${DO_LOOP_ID}.stop" ] && echo "DO_STOP_REQUESTED=true"
|
|
84
|
+
```
|
|
85
|
+
If `DO_STOP_REQUESTED=true`, output `[monomind:do] Stop requested via dashboard. Halting.`, remove state files, and STOP.
|
|
86
|
+
|
|
58
87
|
## Step 2: Find Next Task
|
|
59
88
|
|
|
60
89
|
1. List cards in `Todo` first (prioritized), then `Backlog`:
|
|
@@ -69,6 +98,13 @@ If `cargo` is also missing, output this and STOP:
|
|
|
69
98
|
```
|
|
70
99
|
[monomind:do] No tasks in Todo or Backlog. Checking again in 2 minutes...
|
|
71
100
|
```
|
|
101
|
+
Update loop state before scheduling:
|
|
102
|
+
```bash
|
|
103
|
+
NEXT_AT=$(( $(date +%s%3N) + 120000 ))
|
|
104
|
+
cat > ".monomind/loops/${DO_LOOP_ID}.json" << EOF
|
|
105
|
+
{"id":"${DO_LOOP_ID}","type":"do","prompt":"/monomind:do $ARGUMENTS","currentTask":"queue empty — waiting","spaceId":"${SPACE_ID:-}","boardId":"${TASK_BOARD_ID:-}","filter":"${FILTER:-}","startedAt":$(cat .monomind/loops/${DO_LOOP_ID}.json 2>/dev/null | python3 -c "import sys,json;print(json.load(sys.stdin).get('startedAt',0))" 2>/dev/null || date +%s%3N),"lastRunAt":$(date +%s%3N),"nextRunAt":${NEXT_AT},"status":"waiting"}
|
|
106
|
+
EOF
|
|
107
|
+
```
|
|
72
108
|
Then use `ScheduleWakeup` with `delaySeconds: 120` and prompt `/monomind:do --space $SPACE_ID --board $TASK_BOARD_ID` (plus `--filter` if one was set) to check again. STOP this iteration.
|
|
73
109
|
|
|
74
110
|
4. Store `CURRENT_CARD_ID` and `CURRENT_CARD_TITLE`.
|
|
@@ -273,4 +309,9 @@ If no tasks remain, output:
|
|
|
273
309
|
[monomind:do] All tasks processed. Queue empty.
|
|
274
310
|
```
|
|
275
311
|
|
|
312
|
+
Remove the loop state file:
|
|
313
|
+
```bash
|
|
314
|
+
rm -f ".monomind/loops/${DO_LOOP_ID}.json" ".monomind/loops/${DO_LOOP_ID}.stop"
|
|
315
|
+
```
|
|
316
|
+
|
|
276
317
|
Do NOT schedule another wake-up. STOP.
|
|
@@ -37,6 +37,27 @@ Extract:
|
|
|
37
37
|
- `MAX_REPS` — from `--times` flag, default `10`
|
|
38
38
|
- `PROMPT` — everything remaining after flags are removed
|
|
39
39
|
- `CURRENT_REP` — starts at `1`
|
|
40
|
+
- `LOOP_ID` — generate as `repeat-<unix-timestamp-ms>` (use `date +%s000`)
|
|
41
|
+
|
|
42
|
+
Write the initial loop state file so the dashboard can track this run:
|
|
43
|
+
```bash
|
|
44
|
+
mkdir -p .monomind/loops
|
|
45
|
+
LOOP_ID="repeat-$(date +%s%3N)"
|
|
46
|
+
cat > ".monomind/loops/${LOOP_ID}.json" << EOF
|
|
47
|
+
{
|
|
48
|
+
"id": "${LOOP_ID}",
|
|
49
|
+
"type": "repeat",
|
|
50
|
+
"prompt": "PROMPT",
|
|
51
|
+
"interval": INTERVAL,
|
|
52
|
+
"currentRep": 1,
|
|
53
|
+
"maxReps": MAX_REPS,
|
|
54
|
+
"startedAt": $(date +%s%3N),
|
|
55
|
+
"lastRunAt": $(date +%s%3N),
|
|
56
|
+
"nextRunAt": $(date +%s%3N),
|
|
57
|
+
"status": "running"
|
|
58
|
+
}
|
|
59
|
+
EOF
|
|
60
|
+
```
|
|
40
61
|
|
|
41
62
|
Output:
|
|
42
63
|
```
|
|
@@ -58,6 +79,16 @@ Run the `PROMPT` as if the user typed it directly. This means:
|
|
|
58
79
|
|
|
59
80
|
## Step 3: Report and Schedule Next
|
|
60
81
|
|
|
82
|
+
Before scheduling the next run, check if a stop was requested:
|
|
83
|
+
```bash
|
|
84
|
+
[ -f ".monomind/loops/${LOOP_ID}.stop" ] && echo "STOP_REQUESTED=true"
|
|
85
|
+
```
|
|
86
|
+
If `STOP_REQUESTED=true`, output `[monomind:repeat] Stop requested via dashboard. Halting.` and remove the state files:
|
|
87
|
+
```bash
|
|
88
|
+
rm -f ".monomind/loops/${LOOP_ID}.json" ".monomind/loops/${LOOP_ID}.stop"
|
|
89
|
+
```
|
|
90
|
+
Then STOP.
|
|
91
|
+
|
|
61
92
|
After execution completes, output:
|
|
62
93
|
```
|
|
63
94
|
[monomind:repeat] Run CURRENT_REP/MAX_REPS complete. Next in INTERVAL minutes...
|
|
@@ -69,9 +100,32 @@ If `CURRENT_REP > MAX_REPS`, output:
|
|
|
69
100
|
```
|
|
70
101
|
[monomind:repeat] All MAX_REPS repetitions complete.
|
|
71
102
|
```
|
|
103
|
+
Remove the state file:
|
|
104
|
+
```bash
|
|
105
|
+
rm -f ".monomind/loops/${LOOP_ID}.json"
|
|
106
|
+
```
|
|
72
107
|
STOP. Do NOT schedule another wake-up.
|
|
73
108
|
|
|
74
|
-
Otherwise,
|
|
109
|
+
Otherwise, update the loop state before scheduling:
|
|
110
|
+
```bash
|
|
111
|
+
NEXT_AT=$(( $(date +%s%3N) + INTERVAL * 60 * 1000 ))
|
|
112
|
+
cat > ".monomind/loops/${LOOP_ID}.json" << EOF
|
|
113
|
+
{
|
|
114
|
+
"id": "${LOOP_ID}",
|
|
115
|
+
"type": "repeat",
|
|
116
|
+
"prompt": "PROMPT",
|
|
117
|
+
"interval": INTERVAL,
|
|
118
|
+
"currentRep": CURRENT_REP,
|
|
119
|
+
"maxReps": MAX_REPS,
|
|
120
|
+
"startedAt": STARTED_AT,
|
|
121
|
+
"lastRunAt": $(date +%s%3N),
|
|
122
|
+
"nextRunAt": ${NEXT_AT},
|
|
123
|
+
"status": "running"
|
|
124
|
+
}
|
|
125
|
+
EOF
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Use `ScheduleWakeup` with:
|
|
75
129
|
- `delaySeconds`: `INTERVAL * 60`
|
|
76
130
|
- `prompt`: `/monomind:repeat --every INTERVAL --times MAX_REPS --rep CURRENT_REP PROMPT`
|
|
77
131
|
- `reason`: `"repeat run CURRENT_REP/MAX_REPS of: PROMPT"`
|
|
@@ -4,9 +4,17 @@
|
|
|
4
4
|
* Tool definitions for agent lifecycle management with file persistence.
|
|
5
5
|
* Includes model routing integration for intelligent model selection.
|
|
6
6
|
*/
|
|
7
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, appendFileSync } from 'node:fs';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import { getProjectCwd } from './types.js';
|
|
10
|
+
function logEvent(kind, data) {
|
|
11
|
+
try {
|
|
12
|
+
const dir = join(getProjectCwd(), '.monomind', 'swarm');
|
|
13
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
14
|
+
const event = { ts: new Date().toISOString(), source: 'mcp', kind, ...data };
|
|
15
|
+
appendFileSync(join(dir, 'events.jsonl'), JSON.stringify(event) + '\n');
|
|
16
|
+
} catch { }
|
|
17
|
+
}
|
|
10
18
|
// Storage paths
|
|
11
19
|
const STORAGE_DIR = '.monomind';
|
|
12
20
|
const AGENT_DIR = 'agents';
|
|
@@ -189,6 +197,7 @@ export const agentTools = [
|
|
|
189
197
|
register(agentId, sandbox);
|
|
190
198
|
}
|
|
191
199
|
catch { /* optional — @monoes/security may not be installed */ }
|
|
200
|
+
logEvent('agent.spawn', { agentId, agentType, model: routingResult.model, routedBy: routingResult.routedBy, domain: input.domain, task });
|
|
192
201
|
// Include Agent Booster routing info if applicable
|
|
193
202
|
const response = {
|
|
194
203
|
success: true,
|
|
@@ -228,8 +237,10 @@ export const agentTools = [
|
|
|
228
237
|
const store = loadAgentStore();
|
|
229
238
|
const agentId = input.agentId;
|
|
230
239
|
if (store.agents[agentId]) {
|
|
240
|
+
const agentType = store.agents[agentId].agentType;
|
|
231
241
|
store.agents[agentId].status = 'terminated';
|
|
232
242
|
saveAgentStore(store);
|
|
243
|
+
logEvent('agent.terminate', { agentId, agentType });
|
|
233
244
|
// Task 46: AgentSandboxing — clean up sandbox on termination
|
|
234
245
|
try {
|
|
235
246
|
const { cleanup } = await import('@monoes/security');
|
|
@@ -3,10 +3,18 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Tool definitions for collective intelligence and swarm coordination.
|
|
5
5
|
*/
|
|
6
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
6
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, appendFileSync } from 'node:fs';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { getProjectCwd } from './types.js';
|
|
9
9
|
import { weightedTally } from '../consensus/vote-signer.js';
|
|
10
|
+
function logEvent(kind, data) {
|
|
11
|
+
try {
|
|
12
|
+
const dir = join(getProjectCwd(), '.monomind', 'swarm');
|
|
13
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
14
|
+
const event = { ts: new Date().toISOString(), source: 'mcp', kind, ...data };
|
|
15
|
+
appendFileSync(join(dir, 'events.jsonl'), JSON.stringify(event) + '\n');
|
|
16
|
+
} catch { }
|
|
17
|
+
}
|
|
10
18
|
// Storage paths
|
|
11
19
|
const STORAGE_DIR = '.monomind';
|
|
12
20
|
const HIVE_DIR = 'hive-mind';
|
|
@@ -190,6 +198,9 @@ export const hiveMindTools = [
|
|
|
190
198
|
}
|
|
191
199
|
saveAgentStore(agentStore);
|
|
192
200
|
saveHiveState(state);
|
|
201
|
+
for (const w of spawnedWorkers) {
|
|
202
|
+
logEvent('hive.spawn', { agentId: w.agentId, role: w.role, agentType });
|
|
203
|
+
}
|
|
193
204
|
return {
|
|
194
205
|
success: true,
|
|
195
206
|
spawned: count,
|
|
@@ -224,6 +235,7 @@ export const hiveMindTools = [
|
|
|
224
235
|
term: 1,
|
|
225
236
|
};
|
|
226
237
|
saveHiveState(state);
|
|
238
|
+
logEvent('hive.init', { hiveId, topology: state.topology, consensus: input.consensus || 'byzantine', queenId });
|
|
227
239
|
return {
|
|
228
240
|
success: true,
|
|
229
241
|
hiveId,
|
|
@@ -464,6 +476,7 @@ export const hiveMindTools = [
|
|
|
464
476
|
};
|
|
465
477
|
state.consensus.pending.push(proposal);
|
|
466
478
|
saveHiveState(state);
|
|
479
|
+
logEvent('hive.consensus.propose', { proposalId, type: proposal.type, strategy, proposedBy: proposal.proposedBy, totalNodes });
|
|
467
480
|
return {
|
|
468
481
|
action,
|
|
469
482
|
proposalId,
|
|
@@ -585,9 +598,11 @@ export const hiveMindTools = [
|
|
|
585
598
|
// Try to resolve
|
|
586
599
|
const resolution = divergenceGateOpen ? tryResolveProposal(proposal, totalNodes) : null;
|
|
587
600
|
let resolved = false;
|
|
601
|
+
logEvent('hive.consensus.vote', { proposalId: proposal.proposalId, voterId, vote: voteValue, strategy: proposalStrategy, votesFor, votesAgainst });
|
|
588
602
|
if (resolution !== null) {
|
|
589
603
|
resolved = true;
|
|
590
604
|
proposal.status = resolution;
|
|
605
|
+
logEvent('hive.consensus.resolved', { proposalId: proposal.proposalId, result: resolution, strategy: proposalStrategy, votesFor, votesAgainst });
|
|
591
606
|
state.consensus.history.push({
|
|
592
607
|
proposalId: proposal.proposalId,
|
|
593
608
|
type: proposal.type,
|
|
@@ -735,6 +750,7 @@ export const hiveMindTools = [
|
|
|
735
750
|
// Keep only last 100 broadcasts
|
|
736
751
|
state.sharedMemory.broadcasts = messages.slice(-100);
|
|
737
752
|
saveHiveState(state);
|
|
753
|
+
logEvent('hive.broadcast', { messageId, fromId: input.fromId || 'system', message: input.message, priority: input.priority || 'normal', recipients: state.workers.length });
|
|
738
754
|
return {
|
|
739
755
|
success: true,
|
|
740
756
|
messageId,
|
|
@@ -784,6 +800,7 @@ export const hiveMindTools = [
|
|
|
784
800
|
// Reset hive state
|
|
785
801
|
const shutdownTime = new Date().toISOString();
|
|
786
802
|
const previousQueen = state.queen?.agentId;
|
|
803
|
+
logEvent('hive.shutdown', { workersTerminated: workerCount, previousQueen, graceful, pendingConsensus, topology: state.topology });
|
|
787
804
|
state.initialized = false;
|
|
788
805
|
state.queen = undefined;
|
|
789
806
|
state.workers = [];
|
|
@@ -2,9 +2,17 @@
|
|
|
2
2
|
* Hooks MCP Tools
|
|
3
3
|
* Provides intelligent hooks functionality via MCP protocol
|
|
4
4
|
*/
|
|
5
|
-
import { mkdirSync, writeFileSync, existsSync, readFileSync, statSync, unlinkSync, readdirSync } from 'fs';
|
|
5
|
+
import { mkdirSync, writeFileSync, existsSync, readFileSync, statSync, unlinkSync, readdirSync, appendFileSync } from 'fs';
|
|
6
6
|
import { dirname, join, resolve } from 'path';
|
|
7
7
|
import { getProjectCwd } from './types.js';
|
|
8
|
+
function logEvent(kind, data) {
|
|
9
|
+
try {
|
|
10
|
+
const dir = join(getProjectCwd(), '.monomind', 'swarm');
|
|
11
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
12
|
+
const event = { ts: new Date().toISOString(), source: 'hook', kind, ...data };
|
|
13
|
+
appendFileSync(join(dir, 'events.jsonl'), JSON.stringify(event) + '\n');
|
|
14
|
+
} catch { }
|
|
15
|
+
}
|
|
8
16
|
// Real vector search functions - lazy loaded to avoid circular imports
|
|
9
17
|
let searchEntriesFn = null;
|
|
10
18
|
async function getRealSearchFunction() {
|
|
@@ -1094,6 +1102,7 @@ export const hooksPreTask = {
|
|
|
1094
1102
|
}
|
|
1095
1103
|
}
|
|
1096
1104
|
catch { /* non-critical */ }
|
|
1105
|
+
logEvent('task.start', { taskId, description: description?.slice(0, 200), complexity, suggestedAgent: suggestion.agents[0], modelTier: modelRouting?.tier });
|
|
1097
1106
|
return {
|
|
1098
1107
|
taskId,
|
|
1099
1108
|
description,
|
|
@@ -1256,6 +1265,7 @@ export const hooksPostTask = {
|
|
|
1256
1265
|
storeAs: 'heuristics',
|
|
1257
1266
|
note: 'Spawn agents sequentially: Diagnoser → Critics in parallel → Aggregator',
|
|
1258
1267
|
} : { needed: false };
|
|
1268
|
+
logEvent('task.end', { taskId, success, duration, agent, quality, task: taskText?.slice(0, 200) });
|
|
1259
1269
|
return {
|
|
1260
1270
|
taskId,
|
|
1261
1271
|
success,
|
|
@@ -4,9 +4,17 @@
|
|
|
4
4
|
* Tool definitions for swarm coordination with file-based state persistence.
|
|
5
5
|
* Replaces previous stub implementations with real state tracking.
|
|
6
6
|
*/
|
|
7
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync } from 'node:fs';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import { getProjectCwd } from './types.js';
|
|
10
|
+
function logEvent(kind, data) {
|
|
11
|
+
try {
|
|
12
|
+
const dir = join(getProjectCwd(), '.monomind', 'swarm');
|
|
13
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
14
|
+
const event = { ts: new Date().toISOString(), source: 'mcp', kind, ...data };
|
|
15
|
+
appendFileSync(join(dir, 'events.jsonl'), JSON.stringify(event) + '\n');
|
|
16
|
+
} catch { }
|
|
17
|
+
}
|
|
10
18
|
// Swarm state persistence
|
|
11
19
|
const SWARM_DIR = '.monomind/swarm';
|
|
12
20
|
const SWARM_STATE_FILE = 'swarm-state.json';
|
|
@@ -88,6 +96,7 @@ export const swarmTools = [
|
|
|
88
96
|
const store = loadSwarmStore();
|
|
89
97
|
store.swarms[swarmId] = swarmState;
|
|
90
98
|
saveSwarmStore(store);
|
|
99
|
+
logEvent('swarm.init', { swarmId, topology, strategy, maxAgents, config: swarmState.config });
|
|
91
100
|
return {
|
|
92
101
|
success: true,
|
|
93
102
|
swarmId,
|
|
@@ -195,6 +204,7 @@ export const swarmTools = [
|
|
|
195
204
|
target.status = 'terminated';
|
|
196
205
|
target.updatedAt = new Date().toISOString();
|
|
197
206
|
saveSwarmStore(store);
|
|
207
|
+
logEvent('swarm.shutdown', { swarmId: target.swarmId, topology: target.topology, agentCount: target.agents.length, graceful: input.graceful ?? true });
|
|
198
208
|
return {
|
|
199
209
|
success: true,
|
|
200
210
|
swarmId: target.swarmId,
|
|
@@ -119,16 +119,104 @@ function collectSessions(projectDir) {
|
|
|
119
119
|
};
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
const _appendedSwarmIds = new Set();
|
|
123
|
+
|
|
122
124
|
function collectSwarm(projectDir) {
|
|
123
125
|
const base = path.join(projectDir, '.monomind');
|
|
126
|
+
const state = readJSON(path.join(base, 'swarm', 'swarm-state.json')) || {};
|
|
127
|
+
const dotSwarmState = readJSON(path.join(projectDir, '.swarm', 'state.json')) || {};
|
|
128
|
+
const merged = { ...dotSwarmState, ...state };
|
|
129
|
+
|
|
130
|
+
const terminalStatuses = ['stopped', 'terminated', 'completed', 'error'];
|
|
131
|
+
const swarmId = merged.swarmId || merged.id;
|
|
132
|
+
if (swarmId && terminalStatuses.includes(merged.status) && !_appendedSwarmIds.has(swarmId)) {
|
|
133
|
+
_appendedSwarmIds.add(swarmId);
|
|
134
|
+
const agents = (merged.agents || merged.agentPlan || []).map(a => ({
|
|
135
|
+
id: a.id || a.type || a.role,
|
|
136
|
+
type: a.type || a.role || '?',
|
|
137
|
+
role: a.role || 'worker',
|
|
138
|
+
tasksCompleted: a.tasksCompleted || a.count || 0,
|
|
139
|
+
tasksFailed: a.tasksFailed || 0,
|
|
140
|
+
messageCount: a.messageCount || 0,
|
|
141
|
+
utilization: a.utilization || 0,
|
|
142
|
+
}));
|
|
143
|
+
const entry = {
|
|
144
|
+
swarmId,
|
|
145
|
+
topology: merged.topology || '—',
|
|
146
|
+
consensus: merged.consensus || '—',
|
|
147
|
+
strategy: merged.strategy || '—',
|
|
148
|
+
status: merged.status,
|
|
149
|
+
agents,
|
|
150
|
+
messages: merged.messages || [],
|
|
151
|
+
errors: merged.errors || [],
|
|
152
|
+
findings: merged.findings || [],
|
|
153
|
+
taskCount: merged.taskCount || 0,
|
|
154
|
+
completedTasks: merged.completedTasks || 0,
|
|
155
|
+
failedTasks: merged.failedTasks || 0,
|
|
156
|
+
startedAt: merged.startedAt || merged.createdAt || new Date().toISOString(),
|
|
157
|
+
endedAt: merged.stoppedAt || merged.endedAt || new Date().toISOString(),
|
|
158
|
+
durationMs: 0,
|
|
159
|
+
};
|
|
160
|
+
if (entry.startedAt && entry.endedAt) {
|
|
161
|
+
entry.durationMs = new Date(entry.endedAt).getTime() - new Date(entry.startedAt).getTime();
|
|
162
|
+
}
|
|
163
|
+
try { appendSwarmHistory(projectDir, entry); } catch {}
|
|
164
|
+
}
|
|
165
|
+
|
|
124
166
|
return {
|
|
125
|
-
state:
|
|
167
|
+
state: merged,
|
|
126
168
|
activity: readJSON(path.join(base, 'metrics', 'swarm-activity.json')) || {},
|
|
127
169
|
suggestion: {},
|
|
128
|
-
config: readJSON(path.join(base, 'swarm-config.json')) || {}
|
|
170
|
+
config: readJSON(path.join(base, 'swarm-config.json')) || {},
|
|
129
171
|
};
|
|
130
172
|
}
|
|
131
173
|
|
|
174
|
+
function collectSwarmHistory(projectDir) {
|
|
175
|
+
const historyPath = path.join(projectDir, '.monomind', 'swarm', 'history.jsonl');
|
|
176
|
+
return readJSONL(historyPath).reverse(); // newest-first
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function appendSwarmHistory(projectDir, entry) {
|
|
180
|
+
const dir = path.join(projectDir, '.monomind', 'swarm');
|
|
181
|
+
if (!fs.existsSync(dir)) {
|
|
182
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
183
|
+
}
|
|
184
|
+
const historyPath = path.join(dir, 'history.jsonl');
|
|
185
|
+
fs.appendFileSync(historyPath, JSON.stringify(entry) + '\n');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function collectSwarmEvents(projectDir, opts = {}) {
|
|
189
|
+
const eventsPath = path.join(projectDir, '.monomind', 'swarm', 'events.jsonl');
|
|
190
|
+
const events = readJSONL(eventsPath, opts.last || null);
|
|
191
|
+
if (opts.swarmId) return events.filter(e => e.swarmId === opts.swarmId);
|
|
192
|
+
if (opts.agentId) return events.filter(e => e.agentId === opts.agentId);
|
|
193
|
+
return events;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function getSwarmDataSize(projectDir) {
|
|
197
|
+
const dir = path.join(projectDir, '.monomind', 'swarm');
|
|
198
|
+
let totalBytes = 0;
|
|
199
|
+
let fileCount = 0;
|
|
200
|
+
const files = ['history.jsonl', 'events.jsonl'];
|
|
201
|
+
for (const f of files) {
|
|
202
|
+
const stat = fileStat(path.join(dir, f));
|
|
203
|
+
if (stat) { totalBytes += stat.size; fileCount++; }
|
|
204
|
+
}
|
|
205
|
+
return { totalBytes, fileCount, humanSize: totalBytes < 1024 ? totalBytes + ' B' : totalBytes < 1048576 ? (totalBytes / 1024).toFixed(1) + ' KB' : (totalBytes / 1048576).toFixed(1) + ' MB' };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function cleanSwarmData(projectDir) {
|
|
209
|
+
const dir = path.join(projectDir, '.monomind', 'swarm');
|
|
210
|
+
const files = ['history.jsonl', 'events.jsonl'];
|
|
211
|
+
let removed = 0;
|
|
212
|
+
for (const f of files) {
|
|
213
|
+
const fp = path.join(dir, f);
|
|
214
|
+
try { fs.unlinkSync(fp); removed++; } catch {}
|
|
215
|
+
}
|
|
216
|
+
_appendedSwarmIds.clear();
|
|
217
|
+
return { removed, files };
|
|
218
|
+
}
|
|
219
|
+
|
|
132
220
|
function collectAgents(projectDir) {
|
|
133
221
|
const base = path.join(projectDir, '.monomind');
|
|
134
222
|
const regsDir = path.join(base, 'agents', 'registrations');
|
|
@@ -420,12 +508,12 @@ export function collectAllProjects() {
|
|
|
420
508
|
// Last activity = most recent session mtime
|
|
421
509
|
const lastActivity = sessions.length ? sessions[0].mtime : null;
|
|
422
510
|
|
|
423
|
-
//
|
|
424
|
-
let
|
|
511
|
+
// Count auto-memory files from ~/.claude/projects/<slug>/memory/
|
|
512
|
+
let memoryCount = 0;
|
|
425
513
|
try {
|
|
426
|
-
const
|
|
427
|
-
if (fs.existsSync(
|
|
428
|
-
|
|
514
|
+
const memDir = path.join(os.homedir(), '.claude', 'projects', slug, 'memory');
|
|
515
|
+
if (fs.existsSync(memDir)) {
|
|
516
|
+
memoryCount = fs.readdirSync(memDir).filter(f => f.endsWith('.md') && f !== 'MEMORY.md').length;
|
|
429
517
|
}
|
|
430
518
|
} catch {}
|
|
431
519
|
|
|
@@ -437,7 +525,7 @@ export function collectAllProjects() {
|
|
|
437
525
|
sessionCount: sessions.length,
|
|
438
526
|
sessions: sessions.slice(0, 5), // top 5 most recent
|
|
439
527
|
lastActivity,
|
|
440
|
-
|
|
528
|
+
memoryCount,
|
|
441
529
|
totalSize: sessions.reduce((sum, s) => sum + (s.size || 0), 0),
|
|
442
530
|
});
|
|
443
531
|
}
|
|
@@ -473,7 +561,7 @@ export function collectAll(projectDir) {
|
|
|
473
561
|
};
|
|
474
562
|
}
|
|
475
563
|
|
|
476
|
-
export { collectProject, collectSessions, collectSwarm, collectAgents, collectTokens, collectHooks, collectKnowledge, collectMetrics, collectMemory, collectMemoryFiles, collectSystem };
|
|
564
|
+
export { collectProject, collectSessions, collectSwarm, collectSwarmHistory, appendSwarmHistory, collectSwarmEvents, getSwarmDataSize, cleanSwarmData, collectAgents, collectTokens, collectHooks, collectKnowledge, collectMetrics, collectMemory, collectMemoryFiles, collectSystem };
|
|
477
565
|
|
|
478
566
|
export function getWatchPaths(projectDir) {
|
|
479
567
|
const resolvedDir = path.resolve(projectDir);
|
|
@@ -483,6 +571,7 @@ export function getWatchPaths(projectDir) {
|
|
|
483
571
|
return [
|
|
484
572
|
// Swarm
|
|
485
573
|
path.join(m, 'swarm', 'swarm-state.json'),
|
|
574
|
+
path.join(m, 'swarm', 'history.jsonl'),
|
|
486
575
|
path.join(m, 'swarm-config.json'),
|
|
487
576
|
// Metrics
|
|
488
577
|
path.join(m, 'metrics', 'swarm-activity.json'),
|