monomind 2.3.2 → 2.3.4
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/README.md +37 -4
- package/package.json +1 -1
- package/packages/@monomind/cli/.claude/commands/mastermind/_repeat.md +9 -9
- package/packages/@monomind/cli/.claude/commands/mastermind/{approve.md → approvev1.md} +14 -11
- package/packages/@monomind/cli/.claude/commands/mastermind/architect.md +3 -3
- package/packages/@monomind/cli/.claude/commands/mastermind/createorg.md +4 -4
- package/packages/@monomind/cli/.claude/commands/mastermind/help.md +18 -0
- package/packages/@monomind/cli/.claude/commands/mastermind/master.md +6 -6
- package/packages/@monomind/cli/.claude/commands/mastermind/repeat.md +5 -5
- package/packages/@monomind/cli/.claude/commands/mastermind/runorg.md +20 -108
- package/packages/@monomind/cli/.claude/commands/mastermind/runorgv1.md +159 -0
- package/packages/@monomind/cli/.claude/commands/mastermind/stoporg.md +1 -1
- package/packages/@monomind/cli/.claude/helpers/control-start.cjs +52 -4
- package/packages/@monomind/cli/.claude/helpers/event-logger.cjs +9 -1
- package/packages/@monomind/cli/.claude/helpers/handlers/capture-handler.cjs +9 -1
- package/packages/@monomind/cli/.claude/helpers/handlers/route-handler.cjs +88 -15
- package/packages/@monomind/cli/.claude/helpers/handlers/session-restore-handler.cjs +25 -18
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/_protocol.md +1 -1
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/_repeat.md +3 -3
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/adapters.md +1 -1
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/agents.md +6 -2
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/{approve.md → approvev1.md} +8 -5
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/architect.md +8 -8
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/backup.md +1 -1
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/bootstrap.md +1 -1
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/costs.md +1 -1
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/env.md +7 -2
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/{heartbeat.md → heartbeatv1.md} +9 -6
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/idea.md +1 -1
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/inbox.md +14 -2
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/instance.md +3 -1
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/monitor.md +2 -2
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/org-settings.md +5 -2
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/orgs.md +14 -13
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/orgstatus.md +19 -8
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/plugins.md +1 -1
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/projects.md +3 -0
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/runorg.md +37 -719
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/runorgv1.md +731 -0
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/stoporg.md +12 -5
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/tasks.md +4 -1
- package/packages/@monomind/cli/README.md +37 -4
- package/packages/@monomind/cli/dist/src/commands/cleanup.js +30 -8
- package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.js +23 -10
- package/packages/@monomind/cli/dist/src/commands/org-observe.js +30 -26
- package/packages/@monomind/cli/dist/src/commands/org.js +52 -2
- package/packages/@monomind/cli/dist/src/knowledge/document-pipeline.js +0 -0
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +31 -12
- package/packages/@monomind/cli/dist/src/orgrt/daemon.js +45 -9
- package/packages/@monomind/cli/dist/src/orgrt/mailbox.d.ts +23 -0
- package/packages/@monomind/cli/dist/src/orgrt/mailbox.js +33 -1
- package/packages/@monomind/cli/dist/src/orgrt/migrate.d.ts +26 -0
- package/packages/@monomind/cli/dist/src/orgrt/migrate.js +111 -0
- package/packages/@monomind/cli/dist/src/orgrt/reporting.js +8 -1
- package/packages/@monomind/cli/dist/src/orgrt/session.js +8 -2
- package/packages/@monomind/cli/dist/src/ui/dashboard.html +44 -0
- package/packages/@monomind/cli/dist/src/ui/orgs.html +2 -2
- package/packages/@monomind/cli/dist/src/ui/server.mjs +86 -0
- package/packages/@monomind/cli/package.json +2 -2
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
<!-- LEGACY-ORG-V1 — Start a saved org as a persistent, prompt-orchestrated agent organization. The boss agent coordinates all roles; agents pick up tasks from a shared board and run until stopped. Reach it only via /mastermind:runorgv1. -->
|
|
2
|
+
|
|
3
|
+
> **LEGACY-ORG-V1 (2026-07): superseded by `monomind org run <name>` (SDK org runtime v2), delegated to by default via `/mastermind:runorg`.**
|
|
4
|
+
> This prompt-orchestrated path has no delivery guarantees and no ground-truth event stream.
|
|
5
|
+
> It remains only for orgs not yet migrated. New orgs MUST use the daemon. Reach this legacy path only via `/mastermind:runorgv1`.
|
|
6
|
+
|
|
7
|
+
**If $ARGUMENTS is empty:** List saved orgs and display the following.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
**MASTERMIND: RUN ORG**
|
|
12
|
+
|
|
13
|
+
Running an org starts an autonomous agent organization. There are two modes:
|
|
14
|
+
|
|
15
|
+
**Persistent org** (no schedule): a boss agent loads the org definition, assigns work to specialists from a shared board, and loops indefinitely. Stop with `/mastermind:stoporg --org <name>`.
|
|
16
|
+
|
|
17
|
+
**Scheduled org** (created with `--schedule`): sets the org to `active` and runs the first iteration immediately. Subsequent iterations are self-scheduled via ScheduleWakeup. The loop stops cleanly within one interval after `/mastermind:stoporg --org <name>`.
|
|
18
|
+
|
|
19
|
+
**Your saved orgs:**
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx -y monomind@latest org list 2>/dev/null \
|
|
23
|
+
|| ls .monomind/orgs/*.json 2>/dev/null | grep -vE -- '-(state|goals|threads|activity|approvals|members|secrets|budgets|routines|issues|projects|workspaces|worktrees|environments|plugins|adapters|join-requests|bootstrap|project-workspaces|approval-comments|skills)\.json$' | xargs -I{} basename {} .json 2>/dev/null \
|
|
24
|
+
|| echo "(none — run /mastermind:createorg to define one)"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Usage:**
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
/mastermind:runorgv1 --org <name>
|
|
31
|
+
|
|
32
|
+
/mastermind:runorgv1 --org content-team --task "Publish the Q2 product roundup post by Friday"
|
|
33
|
+
|
|
34
|
+
/mastermind:runorgv1 --org research-pod "Focus this week on competitor pricing changes"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Options:**
|
|
38
|
+
`--org <name>` — which saved org to start (required; prompted if omitted)
|
|
39
|
+
`--task <task>` — override the org's default goal for this run
|
|
40
|
+
Any remaining text is passed as additional context to the boss agent.
|
|
41
|
+
|
|
42
|
+
No orgs yet? Run `/mastermind:createorg` to define one.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
**If $ARGUMENTS is non-empty:** Execute the flow below.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
**STEP 0 — Extract loop-control flags (do this FIRST, before any other parsing)**
|
|
51
|
+
|
|
52
|
+
Scan `$ARGUMENTS` for these flags and store their values. Remove them from the argument string before Step 1:
|
|
53
|
+
|
|
54
|
+
| Flag | Variable | Default |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| `--rep <N>` | `current_rep = N` | absent |
|
|
57
|
+
| `--loop <id>` | `LOOP_ID = id` | absent |
|
|
58
|
+
| `--tillend` | `tillend_mode = true` | false |
|
|
59
|
+
| `--maxruns <N>` | `tillend_maxruns = N` | 50 |
|
|
60
|
+
| `--wait <N>` | `wait_seconds = N` | 60 |
|
|
61
|
+
| `--repeat <N>` | `repeat_count = N` | 0 |
|
|
62
|
+
|
|
63
|
+
⚠️ **CRITICAL — CONTINUATION RUNS DO NOT SKIP WORK.** When `--rep N` is present, this is a scheduled continuation triggered by ScheduleWakeup. The org's FULL work cycle MUST still execute every time: session variables → session:start event → Skill("mastermind-skills:runorgv1") → session:complete event. NEVER short-circuit or skip the org work because `--rep` is present. The `--rep` / `--loop` flags are only consumed by `Skill("mastermind-skills:_repeat")` at the end.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
**STEP 1 — Parse org-specific flags**
|
|
68
|
+
|
|
69
|
+
From the remaining `$ARGUMENTS` (after loop flags removed in Step 0):
|
|
70
|
+
- `--org <name>` → org_name = <name>
|
|
71
|
+
- `--task <task>` → task_override = <task> (if omitted, task_override = null — the skill uses org's stored goal)
|
|
72
|
+
- Remaining text = additional context passed to the boss agent
|
|
73
|
+
|
|
74
|
+
If `--org` is not provided, list saved orgs and ask which to run:
|
|
75
|
+
```bash
|
|
76
|
+
orgs=$(ls .monomind/orgs/*.json 2>/dev/null | xargs -I{} basename {} .json 2>/dev/null)
|
|
77
|
+
if [ -z "$orgs" ]; then
|
|
78
|
+
echo "No saved orgs found. Run /mastermind:createorg first."
|
|
79
|
+
exit 1
|
|
80
|
+
fi
|
|
81
|
+
echo "$orgs"
|
|
82
|
+
```
|
|
83
|
+
Ask: "Which org would you like to start? Available: <list from above>"
|
|
84
|
+
|
|
85
|
+
Verify the org file exists before proceeding:
|
|
86
|
+
```bash
|
|
87
|
+
[ -f ".monomind/orgs/${org_name}.json" ] || { echo "Org '${org_name}' not found."; exit 1; }
|
|
88
|
+
```
|
|
89
|
+
If the file does not exist, stop and suggest running `/mastermind:createorg --name ${org_name}`.
|
|
90
|
+
|
|
91
|
+
Load brain context for the `ops` domain (follow _protocol.md Brain Load Procedure, namespace: `ops`).
|
|
92
|
+
|
|
93
|
+
Resolve session ID and project root:
|
|
94
|
+
```bash
|
|
95
|
+
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
|
96
|
+
CTRL_URL=$(jq -r '.url // "http://localhost:4242"' "$REPO_ROOT/.monomind/control.json" 2>/dev/null || echo "http://localhost:4242")
|
|
97
|
+
# Reuse the loop's original sessionId for continuation runs (keeps all reps under one session)
|
|
98
|
+
if [ -n "${LOOP_ID:-}" ] && [ -f ".monomind/loops/${LOOP_ID}.json" ]; then
|
|
99
|
+
session_id=$(jq -r '.sessionId // empty' ".monomind/loops/${LOOP_ID}.json" 2>/dev/null)
|
|
100
|
+
fi
|
|
101
|
+
session_id="${session_id:-mm-$(date -u +%Y%m%dT%H%M%S)}"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Emit `session:start` to dashboard:
|
|
105
|
+
```bash
|
|
106
|
+
curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" \
|
|
107
|
+
-H "Content-Type: application/json" \
|
|
108
|
+
-d "$(jq -cn \
|
|
109
|
+
--arg session "$session_id" \
|
|
110
|
+
--arg org "$org_name" \
|
|
111
|
+
--arg proj "$REPO_ROOT" \
|
|
112
|
+
'{type:"session:start",session:$session,domain:"ops",prompt:("Running org: "+$org),mode:"auto",project:$proj,ts:(now*1000|floor)}')" || true
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Emit `domain:dispatch`:
|
|
116
|
+
```bash
|
|
117
|
+
curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" \
|
|
118
|
+
-H "Content-Type: application/json" \
|
|
119
|
+
-d "$(jq -cn \
|
|
120
|
+
--arg session "$session_id" \
|
|
121
|
+
--arg org "$org_name" \
|
|
122
|
+
--arg proj "$REPO_ROOT" \
|
|
123
|
+
'{type:"domain:dispatch",session:$session,domain:"ops",cmd:("Starting org "+$org+" as persistent daemon"),project:$proj,ts:(now*1000|floor)}')" || true
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Invoke `Skill("mastermind-skills:runorgv1")` passing: brain_context, org_name: `$org_name`, session_id: `$session_id`, task: task_override, caller: "command".
|
|
127
|
+
|
|
128
|
+
After the skill spawns the boss agent and returns: note the status. Emit `session:complete`:
|
|
129
|
+
```bash
|
|
130
|
+
curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" \
|
|
131
|
+
-H "Content-Type: application/json" \
|
|
132
|
+
-d "$(jq -cn \
|
|
133
|
+
--arg session "$session_id" \
|
|
134
|
+
--arg status "<status>" \
|
|
135
|
+
--arg proj "$REPO_ROOT" \
|
|
136
|
+
'{type:"session:complete",session:$session,domain:"ops",status:$status,domains:["ops"],project:$proj,ts:(now*1000|floor)}')" || true
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Follow _protocol.md Brain Write Procedure for domain `ops`.
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
**MANDATORY — invoke `Skill("mastermind-skills:_repeat")` now.** This is required regardless of how the skill above completed, regardless of whether you think the work is done, regardless of whether you plan to end your response. For `--repeat N`: the count is non-negotiable — all N runs must happen. For `--tillend`: only a verified empty round (confirmed by git diff) stops the loop. Do not end your response without invoking this skill.
|
|
143
|
+
|
|
144
|
+
After the REPEAT POSTAMBLE completes, if a loop was started or continued (LOOP_ID is set), write the org name into the loop state file so the dashboard can detect running status:
|
|
145
|
+
```bash
|
|
146
|
+
if [ -n "${LOOP_ID:-}" ]; then
|
|
147
|
+
LOOP_FILE=".monomind/loops/${LOOP_ID}.json"
|
|
148
|
+
if [ -f "$LOOP_FILE" ]; then
|
|
149
|
+
python3 -c "
|
|
150
|
+
import json, sys
|
|
151
|
+
f = sys.argv[1]; org = sys.argv[2]
|
|
152
|
+
d = json.load(open(f))
|
|
153
|
+
if 'orgName' not in d:
|
|
154
|
+
d['orgName'] = org
|
|
155
|
+
open(f, 'w').write(json.dumps(d, indent=2))
|
|
156
|
+
" "$LOOP_FILE" "${org_name}" 2>/dev/null || true
|
|
157
|
+
fi
|
|
158
|
+
fi
|
|
159
|
+
```
|
|
@@ -66,7 +66,7 @@ Invoke `Skill("mastermind-skills:stoporg")` passing: org_name: `$org_name`, sess
|
|
|
66
66
|
|
|
67
67
|
After skill returns: emit `session:complete`:
|
|
68
68
|
```bash
|
|
69
|
-
curl -s -X POST "${CTRL_URL}/api/mastermind/event" \
|
|
69
|
+
curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" \
|
|
70
70
|
-H "Content-Type: application/json" \
|
|
71
71
|
-d "$(jq -cn \
|
|
72
72
|
--arg session "$session_id" \
|
|
@@ -172,11 +172,16 @@ async function main() {
|
|
|
172
172
|
? [...args, String(DEFAULT_PORT)]
|
|
173
173
|
: [...args, 'ui', '--no-open', '--port', String(DEFAULT_PORT)];
|
|
174
174
|
|
|
175
|
+
// The child writes its ACTUAL bound port here — the only identity-proof
|
|
176
|
+
// signal. An HTTP probe alone can be answered by another project's server
|
|
177
|
+
// already holding the port (which then leaves control.json lying about
|
|
178
|
+
// where THIS project's events should go).
|
|
179
|
+
const BOUND_REPORT = path.join(CWD, '.monomind', `.bound-report-${Date.now()}.json`);
|
|
175
180
|
const child = spawn(cmd, allArgs, {
|
|
176
181
|
detached: true,
|
|
177
182
|
stdio: 'ignore',
|
|
178
183
|
cwd: CWD,
|
|
179
|
-
env: { ...process.env, CLAUDE_PROJECT_DIR: CWD },
|
|
184
|
+
env: { ...process.env, CLAUDE_PROJECT_DIR: CWD, MONOMIND_BOUND_REPORT: BOUND_REPORT },
|
|
180
185
|
});
|
|
181
186
|
|
|
182
187
|
child.unref();
|
|
@@ -189,11 +194,20 @@ async function main() {
|
|
|
189
194
|
// If port 4242 was in use, server.mjs auto-increments (up to +10).
|
|
190
195
|
// Poll a few ports to find where it actually bound and update control.json.
|
|
191
196
|
const http = require('http');
|
|
197
|
+
// Resolves to the responder's pid (number), null for "answers but pid
|
|
198
|
+
// unreadable" (auth-walled or old server), or false for "no answer".
|
|
192
199
|
function probePort(p) {
|
|
193
200
|
return new Promise((resolve) => {
|
|
194
201
|
const req = http.get({ hostname: 'localhost', port: p, path: '/api/status', timeout: 1000 }, (res) => {
|
|
195
|
-
|
|
196
|
-
|
|
202
|
+
let body = '';
|
|
203
|
+
res.on('data', (c) => { if (body.length < 4096) body += c; });
|
|
204
|
+
res.on('end', () => {
|
|
205
|
+
if (res.statusCode >= 500) return resolve(false);
|
|
206
|
+
try {
|
|
207
|
+
const pid = JSON.parse(body).pid;
|
|
208
|
+
resolve(typeof pid === 'number' ? pid : null);
|
|
209
|
+
} catch { resolve(null); }
|
|
210
|
+
});
|
|
197
211
|
});
|
|
198
212
|
req.on('error', () => resolve(false));
|
|
199
213
|
req.on('timeout', () => { req.destroy(); resolve(false); });
|
|
@@ -201,20 +215,54 @@ async function main() {
|
|
|
201
215
|
}
|
|
202
216
|
|
|
203
217
|
// Give the server up to ~10 s to start, then confirm the actual port.
|
|
218
|
+
// Identity-first: the child's bound-report file (written by server.mjs) is
|
|
219
|
+
// authoritative; the HTTP probe only confirms a port when the responder's
|
|
220
|
+
// pid matches our child. A foreign server answering on DEFAULT_PORT must
|
|
221
|
+
// NOT be mistaken for ours — that lie misroutes every event emitter in
|
|
222
|
+
// this project (root cause of orgs running invisibly).
|
|
204
223
|
async function confirmPort() {
|
|
224
|
+
let sawForeignOnDefault = false;
|
|
205
225
|
for (let attempt = 0; attempt < 20; attempt++) {
|
|
206
226
|
await new Promise(r => setTimeout(r, 500));
|
|
227
|
+
// 1) Authoritative: child self-reported its bound port
|
|
228
|
+
try {
|
|
229
|
+
const rep = JSON.parse(fs.readFileSync(BOUND_REPORT, 'utf8'));
|
|
230
|
+
if (rep && rep.pid === child.pid && typeof rep.port === 'number') {
|
|
231
|
+
try { fs.unlinkSync(BOUND_REPORT); } catch { /* ignore */ }
|
|
232
|
+
if (rep.port !== DEFAULT_PORT) {
|
|
233
|
+
writeStatus(child.pid, rep.port);
|
|
234
|
+
process.stdout.write(`[control] server bound to port ${rep.port} (updated control.json)\n`);
|
|
235
|
+
}
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
} catch { /* not written yet or old server — fall through to probe */ }
|
|
239
|
+
// 2) Fallback: pid-matched HTTP probe (old servers without report support)
|
|
207
240
|
for (let delta = 0; delta <= 10; delta++) {
|
|
208
241
|
const p = DEFAULT_PORT + delta;
|
|
209
|
-
|
|
242
|
+
const responderPid = await probePort(p);
|
|
243
|
+
if (responderPid === false) continue;
|
|
244
|
+
if (responderPid === child.pid) {
|
|
210
245
|
if (p !== DEFAULT_PORT) {
|
|
211
246
|
writeStatus(child.pid, p);
|
|
212
247
|
process.stdout.write(`[control] server bound to port ${p} (updated control.json)\n`);
|
|
213
248
|
}
|
|
214
249
|
return;
|
|
215
250
|
}
|
|
251
|
+
if (p === DEFAULT_PORT && typeof responderPid === 'number') sawForeignOnDefault = true;
|
|
252
|
+
// pid mismatch or unreadable — not provably ours, keep scanning
|
|
216
253
|
}
|
|
217
254
|
}
|
|
255
|
+
try { fs.unlinkSync(BOUND_REPORT); } catch { /* ignore */ }
|
|
256
|
+
if (sawForeignOnDefault) {
|
|
257
|
+
// Another project's server owns DEFAULT_PORT and our child never proved
|
|
258
|
+
// its own port — point control.json at the live server instead of lying,
|
|
259
|
+
// and kill our redundant child.
|
|
260
|
+
try { process.kill(child.pid, 'SIGTERM'); } catch { /* already gone */ }
|
|
261
|
+
process.stdout.write(`[control] port ${DEFAULT_PORT} is served by another project's control server — reusing it (killed redundant child)\n`);
|
|
262
|
+
const foreignPid = await probePort(DEFAULT_PORT);
|
|
263
|
+
writeStatus(typeof foreignPid === 'number' ? foreignPid : 0, DEFAULT_PORT);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
218
266
|
// Server never became reachable on any expected port — kill the child
|
|
219
267
|
// rather than leave an orphan bound to some port nothing will ever read.
|
|
220
268
|
// The next session-start simply retries.
|
|
@@ -66,12 +66,20 @@ function forwardToDashboard(entry) {
|
|
|
66
66
|
const rawPort = Number(ctrl.port);
|
|
67
67
|
const port = (Number.isInteger(rawPort) && rawPort >= 1024 && rawPort <= 65535) ? rawPort : 4242;
|
|
68
68
|
const body = JSON.stringify({ ...entry, hookCaptured: true });
|
|
69
|
+
// Ingest is default-deny: attach the dashboard credential (written by
|
|
70
|
+
// server.mjs next to control.json) or the POST is silently rejected.
|
|
71
|
+
let authHdr = '';
|
|
72
|
+
try { authHdr = fs.readFileSync(path.join(CWD, '.monomind', 'dashboard-token'), 'utf8').trim(); } catch {}
|
|
69
73
|
const req = http.request({
|
|
70
74
|
method: 'POST',
|
|
71
75
|
hostname: 'localhost',
|
|
72
76
|
port,
|
|
73
77
|
path: '/api/mastermind/event',
|
|
74
|
-
headers: {
|
|
78
|
+
headers: {
|
|
79
|
+
'Content-Type': 'application/json',
|
|
80
|
+
'Content-Length': Buffer.byteLength(body),
|
|
81
|
+
...(authHdr ? { 'x-monomind-token': authHdr } : {}),
|
|
82
|
+
},
|
|
75
83
|
timeout: 400,
|
|
76
84
|
});
|
|
77
85
|
req.on('error', () => {});
|
|
@@ -201,12 +201,20 @@ function emitEvent(event) {
|
|
|
201
201
|
}
|
|
202
202
|
const u = new URL(baseUrl);
|
|
203
203
|
const body = JSON.stringify(event);
|
|
204
|
+
// Ingest is default-deny: attach the dashboard credential (written by
|
|
205
|
+
// server.mjs next to control.json) or the POST is silently rejected.
|
|
206
|
+
let authHdr = '';
|
|
207
|
+
try { authHdr = fs.readFileSync(path.join(CWD, '.monomind', 'dashboard-token'), 'utf8').trim(); } catch {}
|
|
204
208
|
const req = http.request({
|
|
205
209
|
hostname: u.hostname,
|
|
206
210
|
port: parseInt(u.port || '4242', 10),
|
|
207
211
|
path: '/api/mastermind/event',
|
|
208
212
|
method: 'POST',
|
|
209
|
-
headers: {
|
|
213
|
+
headers: {
|
|
214
|
+
'Content-Type': 'application/json',
|
|
215
|
+
'Content-Length': Buffer.byteLength(body),
|
|
216
|
+
...(authHdr ? { 'x-monomind-token': authHdr } : {}),
|
|
217
|
+
},
|
|
210
218
|
}, () => {
|
|
211
219
|
try { fs.unlinkSync(spoolFile); } catch {}
|
|
212
220
|
resolve();
|
|
@@ -319,32 +319,105 @@ module.exports = {
|
|
|
319
319
|
// ── Second Brain: per-request knowledge injection ──────────────────
|
|
320
320
|
// When the project has an indexed knowledge base, surface the most
|
|
321
321
|
// relevant excerpts for THIS prompt so Claude has them without having
|
|
322
|
-
// to decide to call knowledge_search.
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
//
|
|
327
|
-
// endpoint holding the embedding model for semantic per-prompt retrieval.
|
|
322
|
+
// to decide to call knowledge_search. Semantic-first via the dashboard
|
|
323
|
+
// server's warm /api/knowledge/search (it holds the local embedding
|
|
324
|
+
// model hot — a hook subprocess can't afford a 1-3s model load per
|
|
325
|
+
// prompt); tokenized keyword scoring over chunks.jsonl as the fallback
|
|
326
|
+
// when the server is down or still warming.
|
|
328
327
|
try {
|
|
329
328
|
var sbPrompt = String(prompt || '');
|
|
330
|
-
// Skip slash commands and
|
|
331
|
-
|
|
329
|
+
// Skip slash commands and low-content prompts — injection would be
|
|
330
|
+
// noise. A prompt earns injection only when it carries at least two
|
|
331
|
+
// substantive terms ("what is next" / "ok go ahead" / "thanks" carry
|
|
332
|
+
// zero-to-one and must stay quiet; a real question about the project
|
|
333
|
+
// clears this easily).
|
|
334
|
+
var _SB_FILLER = new Set(['what', 'whats', 'is', 'are', 'was', 'were', 'the', 'this', 'that', 'these', 'those',
|
|
335
|
+
'next', 'now', 'then', 'and', 'but', 'for', 'not', 'you', 'your', 'can', 'could', 'should', 'would', 'will',
|
|
336
|
+
'lets', 'let', 'make', 'made', 'making', 'please', 'okay', 'yes', 'yeah', 'sure', 'thanks', 'thank',
|
|
337
|
+
'ahead', 'continue', 'proceed', 'more', 'again', 'how', 'why', 'when', 'where', 'who', 'which',
|
|
338
|
+
'with', 'about', 'from', 'into', 'onto', 'over', 'under', 'all', 'any', 'some', 'one', 'two', 'just',
|
|
339
|
+
'like', 'want', 'need', 'get', 'got', 'here', 'there', 'still', 'also', 'too', 'very', 'really',
|
|
340
|
+
'them', 'they', 'their', 'theirs', 'its', 'our', 'ours', 'him', 'her', 'his', 'hers',
|
|
341
|
+
'order', 'done', 'doing', 'did', 'does', 'goes', 'going', 'went', 'come', 'came', 'good', 'great', 'nice', 'fine']);
|
|
342
|
+
var _sbSubstantive = sbPrompt.toLowerCase().split(/[^a-z0-9]+/).filter(function(t) {
|
|
343
|
+
return t.length >= 3 && !_SB_FILLER.has(t);
|
|
344
|
+
});
|
|
345
|
+
if (_sbSubstantive.length >= 2 && sbPrompt.charAt(0) !== '/') {
|
|
332
346
|
var sbKnowledgeDir = path.join(CWD, '.monomind', 'knowledge');
|
|
333
347
|
if (fs.existsSync(path.join(sbKnowledgeDir, 'chunks.jsonl'))) {
|
|
334
|
-
var
|
|
335
|
-
var
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
348
|
+
var sbHits = null;
|
|
349
|
+
var sbMethod = 'keyword';
|
|
350
|
+
|
|
351
|
+
// Semantic path: warm control-server endpoint (strictly local traffic;
|
|
352
|
+
// sbAuth is the local dashboard session value from .monomind, same one
|
|
353
|
+
// every other hook event POST attaches — not a checked-in secret).
|
|
354
|
+
try {
|
|
355
|
+
var sbCtrlUrl = 'http://localhost:4242';
|
|
356
|
+
try {
|
|
357
|
+
var sbCtl = JSON.parse(fs.readFileSync(path.join(CWD, '.monomind', 'control.json'), 'utf-8'));
|
|
358
|
+
if (sbCtl.url) sbCtrlUrl = sbCtl.url;
|
|
359
|
+
} catch (_) {}
|
|
360
|
+
var sbAuth = '';
|
|
361
|
+
try { sbAuth = fs.readFileSync(path.join(CWD, '.monomind', 'dashboard-token'), 'utf-8').trim(); } catch (_) {}
|
|
362
|
+
var sbResp = await fetch(sbCtrlUrl + '/api/knowledge/search', {
|
|
363
|
+
method: 'POST',
|
|
364
|
+
headers: { 'Content-Type': 'application/json', 'x-monomind-token': sbAuth },
|
|
365
|
+
body: JSON.stringify({ query: sbPrompt, namespace: 'knowledge:shared', limit: 3 }),
|
|
366
|
+
signal: AbortSignal.timeout(900),
|
|
367
|
+
});
|
|
368
|
+
if (sbResp.ok) {
|
|
369
|
+
var sbData = await sbResp.json();
|
|
370
|
+
if (sbData && Array.isArray(sbData.results) && sbData.results.length > 0) {
|
|
371
|
+
sbHits = sbData.results.map(function(r) { return { key: r.key, value: r.content, score: r.score, metadata: {} }; });
|
|
372
|
+
sbMethod = sbData.method === 'semantic' ? 'semantic' : 'keyword';
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
} catch (_) { /* server down or warming — fall back below */ }
|
|
376
|
+
|
|
377
|
+
if (!sbHits) {
|
|
378
|
+
var sbSearch = hCtx._buildKnowledgeSearchFn(sbKnowledgeDir);
|
|
379
|
+
sbHits = await hCtx.runWithTimeout(
|
|
380
|
+
function() { return sbSearch(sbPrompt, { namespace: 'knowledge:shared', limit: 3, minScore: 0.45 }); },
|
|
381
|
+
'second-brain-inject'
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
// Relevance floor: injecting weak matches pollutes every prompt's
|
|
385
|
+
// context — below 0.35 the excerpt is more likely noise than help.
|
|
386
|
+
if (sbHits) sbHits = sbHits.filter(function(h) { return (h.score || 0) >= 0.35; });
|
|
387
|
+
|
|
388
|
+
// Telemetry: append one JSONL line per evaluated prompt so the
|
|
389
|
+
// thresholds above can be tuned from real usage (and misses can
|
|
390
|
+
// seed the golden-set eval). Prompt text is NOT logged.
|
|
391
|
+
try {
|
|
392
|
+
var sbMetricsDir = path.join(CWD, '.monomind', 'metrics');
|
|
393
|
+
fs.mkdirSync(sbMetricsDir, { recursive: true });
|
|
394
|
+
var sbMetricsFile = path.join(sbMetricsDir, 'second-brain.jsonl');
|
|
395
|
+
try {
|
|
396
|
+
if (fs.existsSync(sbMetricsFile) && fs.statSync(sbMetricsFile).size > 512 * 1024) {
|
|
397
|
+
var sbOld = fs.readFileSync(sbMetricsFile, 'utf-8').trim().split('\n');
|
|
398
|
+
fs.writeFileSync(sbMetricsFile, sbOld.slice(-500).join('\n') + '\n', 'utf-8');
|
|
399
|
+
}
|
|
400
|
+
} catch (_) {}
|
|
401
|
+
fs.appendFileSync(sbMetricsFile, JSON.stringify({
|
|
402
|
+
ts: Date.now(),
|
|
403
|
+
method: sbMethod,
|
|
404
|
+
hits: sbHits ? sbHits.length : 0,
|
|
405
|
+
topScore: sbHits && sbHits[0] ? Number((sbHits[0].score || 0).toFixed(3)) : null,
|
|
406
|
+
promptLen: sbPrompt.length,
|
|
407
|
+
terms: _sbSubstantive.length,
|
|
408
|
+
injected: !!(sbHits && sbHits.length > 0),
|
|
409
|
+
}) + '\n', 'utf-8');
|
|
410
|
+
} catch (_) { /* telemetry never blocks */ }
|
|
411
|
+
|
|
339
412
|
if (sbHits && sbHits.length > 0) {
|
|
340
|
-
var sbLines = ['[SECOND_BRAIN] ' + sbHits.length + ' relevant excerpt(s) from the project knowledge base:'];
|
|
413
|
+
var sbLines = ['[SECOND_BRAIN] ' + sbHits.length + ' relevant excerpt(s) (' + sbMethod + ') from the project knowledge base:'];
|
|
341
414
|
for (var sbI = 0; sbI < sbHits.length; sbI++) {
|
|
342
415
|
var sbH = sbHits[sbI];
|
|
343
416
|
var sbSrc = (sbH.metadata && sbH.metadata.filePath) ? String(sbH.metadata.filePath).split('/').slice(-2).join('/') : sbH.key;
|
|
344
417
|
var sbText = String(sbH.value || '').replace(/\s+/g, ' ').slice(0, 240);
|
|
345
418
|
sbLines.push(' • [' + sbSrc + '] ' + sbText);
|
|
346
419
|
}
|
|
347
|
-
sbLines.push(' (deeper
|
|
420
|
+
sbLines.push(' (deeper lookup: mcp__monomind__knowledge_search or `monomind doc search -q "..."`)');
|
|
348
421
|
console.log(sbLines.join('\n'));
|
|
349
422
|
}
|
|
350
423
|
}
|
|
@@ -358,6 +358,17 @@ module.exports = {
|
|
|
358
358
|
var _kbLastCheck = 0;
|
|
359
359
|
try { _kbLastCheck = JSON.parse(fs.readFileSync(_kbMarkerPath, 'utf-8')).ts || 0; } catch (_) {}
|
|
360
360
|
if (Date.now() - _kbLastCheck > 30 * 60 * 1000) {
|
|
361
|
+
// Marker BEFORE the scan (not only when dirty): the 2000-stat walk
|
|
362
|
+
// itself is what needs rate-limiting — on a clean tree it used to
|
|
363
|
+
// re-run on every single session start. Fail-closed: skip when the
|
|
364
|
+
// marker can't be written, so an unwritable disk can't cause
|
|
365
|
+
// per-session scans either.
|
|
366
|
+
var _kbMarkerOk = false;
|
|
367
|
+
try {
|
|
368
|
+
fs.writeFileSync(_kbMarkerPath, JSON.stringify({ ts: Date.now() }), 'utf-8');
|
|
369
|
+
_kbMarkerOk = true;
|
|
370
|
+
} catch (_) {}
|
|
371
|
+
if (!_kbMarkerOk) throw new Error('reindex marker unwritable — skipping scan');
|
|
361
372
|
// Cheap bounded scan: any document changed since the last ingest?
|
|
362
373
|
var _DOC_EXT = { '.md': 1, '.txt': 1, '.pdf': 1, '.docx': 1 };
|
|
363
374
|
var _kbDirty = false;
|
|
@@ -380,24 +391,20 @@ module.exports = {
|
|
|
380
391
|
};
|
|
381
392
|
_kbWalk(CWD, 0);
|
|
382
393
|
if (_kbDirty) {
|
|
383
|
-
var
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
});
|
|
398
|
-
_kbChild.unref();
|
|
399
|
-
console.log('[KNOWLEDGE_REINDEX] changed documents detected — re-ingesting in background');
|
|
400
|
-
}
|
|
394
|
+
var _kbSpawn = require('child_process').spawn;
|
|
395
|
+
var _kbChild = _kbSpawn('npx', ['-y', 'monomind@latest', 'doc', 'ingest', '.'], {
|
|
396
|
+
cwd: CWD,
|
|
397
|
+
detached: true,
|
|
398
|
+
stdio: 'ignore',
|
|
399
|
+
env: process.env,
|
|
400
|
+
shell: process.platform === 'win32',
|
|
401
|
+
windowsHide: true,
|
|
402
|
+
});
|
|
403
|
+
// Without an error listener, a missing npx binary emits an
|
|
404
|
+
// unhandled 'error' event and crashes the whole session-start hook.
|
|
405
|
+
_kbChild.on('error', function () { /* npx unavailable — reindex on a later session */ });
|
|
406
|
+
_kbChild.unref();
|
|
407
|
+
console.log('[KNOWLEDGE_REINDEX] changed documents detected — re-ingesting in background');
|
|
401
408
|
}
|
|
402
409
|
}
|
|
403
410
|
}
|
|
@@ -230,7 +230,7 @@ Resolve `CTRL_URL` at runtime: `jq -r '.url // "http://localhost:4242"' "$REPO_R
|
|
|
230
230
|
```bash
|
|
231
231
|
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
|
232
232
|
CTRL_URL=$(jq -r '.url // "http://localhost:4242"' "$REPO_ROOT/.monomind/control.json" 2>/dev/null || echo "http://localhost:4242")
|
|
233
|
-
curl -s -o /dev/null -X POST "${CTRL_URL}/api/mastermind/event" \
|
|
233
|
+
curl -s -o /dev/null -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" \
|
|
234
234
|
-H "Content-Type: application/json" \
|
|
235
235
|
-d "$(jq -cn \
|
|
236
236
|
--arg sid "$SESSION_ID" \
|
|
@@ -73,7 +73,7 @@ Set `TILLEND_EMPTY=true` **only if git shows no changes AND your self-assessment
|
|
|
73
73
|
```bash
|
|
74
74
|
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
|
75
75
|
CTRL_URL=$(jq -r '.url // "http://localhost:4242"' "$REPO_ROOT/.monomind/control.json" 2>/dev/null || echo "http://localhost:4242")
|
|
76
|
-
curl -s -X POST "${CTRL_URL}/api/mastermind/event" \
|
|
76
|
+
curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" \
|
|
77
77
|
-H "Content-Type: application/json" \
|
|
78
78
|
-d "{\"type\":\"loop:complete\",\"loopId\":\"${LOOP_ID}\",\"command\":\"/<command>\",\"mode\":\"tillend\",\"ranReps\":<current_rep>,\"reason\":\"empty-round\",\"project\":\"${REPO_ROOT}\",\"ts\":$(date +%s)000}" || true
|
|
79
79
|
```
|
|
@@ -103,7 +103,7 @@ If `current_rep >= repeat_count`:
|
|
|
103
103
|
```bash
|
|
104
104
|
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
|
105
105
|
CTRL_URL=$(jq -r '.url // "http://localhost:4242"' "$REPO_ROOT/.monomind/control.json" 2>/dev/null || echo "http://localhost:4242")
|
|
106
|
-
curl -s -X POST "${CTRL_URL}/api/mastermind/event" \
|
|
106
|
+
curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" \
|
|
107
107
|
-H "Content-Type: application/json" \
|
|
108
108
|
-d "{\"type\":\"loop:complete\",\"loopId\":\"${LOOP_ID}\",\"command\":\"/<command>\",\"ranReps\":<repeat_count>,\"project\":\"${REPO_ROOT}\",\"ts\":$(date +%s)000}" || true
|
|
109
109
|
```
|
|
@@ -151,7 +151,7 @@ Emit `loop:tick`:
|
|
|
151
151
|
```bash
|
|
152
152
|
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
|
153
153
|
CTRL_URL=$(jq -r '.url // "http://localhost:4242"' "$REPO_ROOT/.monomind/control.json" 2>/dev/null || echo "http://localhost:4242")
|
|
154
|
-
curl -s -X POST "${CTRL_URL}/api/mastermind/event" \
|
|
154
|
+
curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" \
|
|
155
155
|
-H "Content-Type: application/json" \
|
|
156
156
|
-d "{\"type\":\"loop:tick\",\"loopId\":\"${LOOP_ID}\",\"command\":\"/<command>\",\"completedRep\":<current_rep>,\"nextRep\":<next_rep>,\"nextAt\":${NEXT_AT},\"project\":\"${REPO_ROOT}\",\"ts\":$(date +%s)000}" || true
|
|
157
157
|
```
|
|
@@ -139,7 +139,7 @@ Emit `org:adapter:installed` event:
|
|
|
139
139
|
```bash
|
|
140
140
|
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
|
141
141
|
CTRL_URL=$(jq -r '.url // "http://localhost:4242"' "$REPO_ROOT/.monomind/control.json" 2>/dev/null || echo "http://localhost:4242")
|
|
142
|
-
curl -s -X POST "${CTRL_URL}/api/mastermind/event" \
|
|
142
|
+
curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" \
|
|
143
143
|
-H "Content-Type: application/json" \
|
|
144
144
|
-d "$(jq -cn --arg org "$org_name" --arg type "$adapter_type" --arg pkg "$package_name" \
|
|
145
145
|
'{type:"org:adapter:installed",org:$org,adapter:$type,package:$pkg,ts:(now*1000|floor)}')" || true
|
|
@@ -51,12 +51,15 @@ stateFile=".monomind/orgs/${org_name}-state.json"
|
|
|
51
51
|
|
|
52
52
|
jq -r '(.roles // [])[] | "• [\(.id)] \(.title) agent=\(.agent_type) reports_to=\(.reports_to // "none")"' "$orgFile"
|
|
53
53
|
|
|
54
|
+
# LEGACY-ORG-V1: the state file's per-agent heartbeat tracking is a v1 concept —
|
|
55
|
+
# v2 orgs report live status via `monomind org status`/`org report` instead.
|
|
54
56
|
# Overlay runtime status from state file if present
|
|
55
57
|
if [ -f "$stateFile" ]; then
|
|
56
58
|
echo ""
|
|
57
59
|
echo "RUNTIME STATUS:"
|
|
58
60
|
jq -r '.agents // {} | to_entries[] | " \(.key): \(.value.status // "unknown") last_beat=\(.value.last_heartbeat // "never")"' "$stateFile" 2>/dev/null || true
|
|
59
61
|
fi
|
|
62
|
+
# end LEGACY-ORG-V1
|
|
60
63
|
```
|
|
61
64
|
|
|
62
65
|
Render as table:
|
|
@@ -126,7 +129,7 @@ Then emit `org:agent:hired` event to dashboard:
|
|
|
126
129
|
```bash
|
|
127
130
|
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
|
128
131
|
CTRL_URL=$(jq -r '.url // "http://localhost:4242"' "$REPO_ROOT/.monomind/control.json" 2>/dev/null || echo "http://localhost:4242")
|
|
129
|
-
curl -s -X POST "${CTRL_URL}/api/mastermind/event" \
|
|
132
|
+
curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" \
|
|
130
133
|
-H "Content-Type: application/json" \
|
|
131
134
|
-d "$(jq -cn --arg org "$org_name" --arg role "$agent_id" --arg title "$title" \
|
|
132
135
|
'{type:"org:agent:hired",org:$org,role:$role,title:$title,ts:(now*1000|floor)}')" || true
|
|
@@ -134,6 +137,7 @@ curl -s -X POST "${CTRL_URL}/api/mastermind/event" \
|
|
|
134
137
|
|
|
135
138
|
### pause / resume
|
|
136
139
|
|
|
140
|
+
<!-- LEGACY-ORG-V1: the agent state/heartbeat file this pauses is a v1 concept. -->
|
|
137
141
|
Update state file:
|
|
138
142
|
|
|
139
143
|
```bash
|
|
@@ -169,7 +173,7 @@ org: <org_name>
|
|
|
169
173
|
agents_count: <N>
|
|
170
174
|
```
|
|
171
175
|
|
|
172
|
-
Print summary and any suggested next actions (e.g. "Run /mastermind:
|
|
176
|
+
Print summary and any suggested next actions (e.g. "Run /mastermind:heartbeatv1 to trigger a manual heartbeat for this agent").
|
|
173
177
|
|
|
174
178
|
---
|
|
175
179
|
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: mastermind-
|
|
3
|
-
description: Mastermind
|
|
2
|
+
name: mastermind-approvev1
|
|
3
|
+
description: "LEGACY-ORG-V1 (2026-07): v2 approvals arrive as question events in the dashboard Human Input tab — no skill needed. Mastermind approvev1 — review and action pending approval requests from agents in a running v1 (prompt-orchestrated) org. Agents can request human approval before proceeding with sensitive actions. Reach it only via `/mastermind:approvev1`."
|
|
4
4
|
type: domain-skill
|
|
5
5
|
default_mode: confirm
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
# Mastermind Approve
|
|
8
|
+
# Mastermind Approve (v1, legacy)
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
> **LEGACY-ORG-V1 — file-based approval queue for prompt-orchestrated v1 orgs. Reach it only via `/mastermind:approvev1`.**
|
|
11
|
+
> v2 approvals arrive as `question` events in the dashboard Human Input tab — there is no v2 approve skill, the dashboard UI is the approval surface. This skill remains only for orgs not yet migrated off the v1 `board_id`/`topology` config shape.
|
|
12
|
+
|
|
13
|
+
This skill is invoked by `mastermind:approvev1` or directly via `/mastermind:approvev1`.
|
|
11
14
|
|
|
12
15
|
---
|
|
13
16
|
|
|
@@ -150,7 +153,7 @@ jq --arg id "$approval_id" \
|
|
|
150
153
|
|
|
151
154
|
2. Emit `org:approval:requested` event to dashboard (so the human is notified).
|
|
152
155
|
|
|
153
|
-
3. Poll for the approval decision — check the approvals file first (authoritative, updated by both the dashboard UI and `/mastermind:
|
|
156
|
+
3. Poll for the approval decision — check the approvals file first (authoritative, updated by both the dashboard UI and `/mastermind:approvev1`), then fall back to memory:
|
|
154
157
|
```bash
|
|
155
158
|
approvalsFile=".monomind/orgs/${orgName}-approvals.json"
|
|
156
159
|
while true; do
|