monomind 1.11.14 → 1.13.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/.claude/agents/generated/channel-intelligence-director.md +87 -0
- package/.claude/agents/generated/chief-growth-officer.md +88 -0
- package/.claude/agents/generated/content-seo-strategist.md +90 -0
- package/.claude/agents/generated/developer-community-strategist.md +91 -0
- package/.claude/agents/generated/outreach-partnership-strategist.md +90 -0
- package/.claude/agents/generated/social-media-strategist.md +91 -0
- package/.claude/agents/generated/video-visual-strategist.md +90 -0
- package/.claude/commands/mastermind/master.md +1 -1
- package/.claude/helpers/auto-memory-hook.mjs +13 -4
- package/.claude/helpers/control-start.cjs +5 -0
- package/.claude/helpers/event-logger.cjs +114 -0
- package/.claude/helpers/handlers/adr-draft-handler.cjs +19 -5
- package/.claude/helpers/handlers/agent-start-handler.cjs +13 -4
- package/.claude/helpers/handlers/compact-handler.cjs +2 -0
- package/.claude/helpers/handlers/edit-handler.cjs +1 -1
- package/.claude/helpers/handlers/gates-handler.cjs +3 -0
- package/.claude/helpers/handlers/graph-status-handler.cjs +14 -8
- package/.claude/helpers/handlers/loops-status-handler.cjs +5 -2
- package/.claude/helpers/handlers/route-handler.cjs +24 -10
- package/.claude/helpers/handlers/session-handler.cjs +11 -4
- package/.claude/helpers/handlers/session-restore-handler.cjs +35 -19
- package/.claude/helpers/handlers/task-handler.cjs +13 -5
- package/.claude/helpers/hook-handler.cjs +40 -0
- package/.claude/helpers/intelligence.cjs +130 -53
- package/.claude/helpers/loop-tracker.cjs +15 -3
- package/.claude/helpers/memory-palace.cjs +461 -0
- package/.claude/helpers/memory.cjs +138 -14
- package/.claude/helpers/metrics-db.mjs +87 -0
- package/.claude/helpers/router.cjs +300 -42
- package/.claude/helpers/session.cjs +89 -30
- package/.claude/helpers/statusline.cjs +148 -4
- package/.claude/helpers/toggle-statusline.cjs +73 -0
- package/.claude/helpers/token-tracker.cjs +934 -0
- package/.claude/helpers/utils/micro-agents.cjs +20 -4
- package/.claude/helpers/utils/monograph.cjs +39 -4
- package/.claude/helpers/utils/telemetry.cjs +3 -3
- package/.claude/scheduled_tasks.lock +1 -1
- package/.claude/settings.json +92 -1
- package/.claude/skills/mastermind/_protocol.md +25 -15
- package/.claude/skills/mastermind/architect.md +3 -3
- package/.claude/skills/mastermind/autodev.md +4 -2
- package/.claude/skills/mastermind/idea.md +10 -0
- package/.claude/skills/mastermind/ops.md +3 -3
- package/.claude/skills/mastermind/runorg.md +153 -86
- package/package.json +20 -3
- package/packages/@monomind/cli/dist/src/agents/registry-builder.js +2 -0
- package/packages/@monomind/cli/dist/src/autopilot-state.js +10 -5
- package/packages/@monomind/cli/dist/src/benchmarks/benchmark-runner.js +13 -0
- package/packages/@monomind/cli/dist/src/benchmarks/metric-evaluators.js +20 -9
- package/packages/@monomind/cli/dist/src/browser/actions.js +10 -3
- package/packages/@monomind/cli/dist/src/browser/browser.js +12 -2
- package/packages/@monomind/cli/dist/src/browser/cdp.js +21 -3
- package/packages/@monomind/cli/dist/src/browser/har.js +27 -5
- package/packages/@monomind/cli/dist/src/commands/agent.js +11 -8
- package/packages/@monomind/cli/dist/src/commands/analyze.js +36 -21
- package/packages/@monomind/cli/dist/src/commands/autopilot.js +12 -4
- package/packages/@monomind/cli/dist/src/commands/benchmark.js +51 -8
- package/packages/@monomind/cli/dist/src/commands/browse.js +5 -2
- package/packages/@monomind/cli/dist/src/commands/claims.js +29 -11
- package/packages/@monomind/cli/dist/src/commands/cleanup.js +25 -5
- package/packages/@monomind/cli/dist/src/commands/config.js +15 -7
- package/packages/@monomind/cli/dist/src/commands/daemon.js +6 -0
- package/packages/@monomind/cli/dist/src/commands/deployment.js +34 -19
- package/packages/@monomind/cli/dist/src/commands/doctor.js +192 -23
- package/packages/@monomind/cli/dist/src/commands/guidance.js +15 -2
- package/packages/@monomind/cli/dist/src/commands/hive-mind.js +37 -14
- package/packages/@monomind/cli/dist/src/commands/hooks.js +42 -25
- package/packages/@monomind/cli/dist/src/commands/init.js +9 -4
- package/packages/@monomind/cli/dist/src/commands/issues.js +29 -26
- package/packages/@monomind/cli/dist/src/commands/mcp.js +11 -5
- package/packages/@monomind/cli/dist/src/commands/memory.js +10 -0
- package/packages/@monomind/cli/dist/src/commands/migrate.js +5 -5
- package/packages/@monomind/cli/dist/src/commands/monograph.js +18 -5
- package/packages/@monomind/cli/dist/src/commands/monovector/backup.js +8 -2
- package/packages/@monomind/cli/dist/src/commands/monovector/benchmark.js +20 -7
- package/packages/@monomind/cli/dist/src/commands/monovector/import.js +15 -0
- package/packages/@monomind/cli/dist/src/commands/monovector/migrate.js +4 -1
- package/packages/@monomind/cli/dist/src/commands/monovector/optimize.js +11 -0
- package/packages/@monomind/cli/dist/src/commands/monovector/setup.js +11 -1
- package/packages/@monomind/cli/dist/src/commands/neural.js +1 -1
- package/packages/@monomind/cli/dist/src/commands/performance.js +20 -7
- package/packages/@monomind/cli/dist/src/commands/platforms.js +90 -8
- package/packages/@monomind/cli/dist/src/commands/plugins.js +12 -5
- package/packages/@monomind/cli/dist/src/commands/process.js +33 -10
- package/packages/@monomind/cli/dist/src/commands/progress.js +5 -3
- package/packages/@monomind/cli/dist/src/commands/providers.js +5 -5
- package/packages/@monomind/cli/dist/src/commands/replay.js +8 -2
- package/packages/@monomind/cli/dist/src/commands/route.js +27 -7
- package/packages/@monomind/cli/dist/src/commands/security.js +4 -0
- package/packages/@monomind/cli/dist/src/commands/session.js +12 -1
- package/packages/@monomind/cli/dist/src/commands/start.js +11 -4
- package/packages/@monomind/cli/dist/src/commands/status.js +7 -4
- package/packages/@monomind/cli/dist/src/commands/swarm.js +27 -13
- package/packages/@monomind/cli/dist/src/commands/task.js +26 -11
- package/packages/@monomind/cli/dist/src/commands/tokens.js +7 -2
- package/packages/@monomind/cli/dist/src/commands/transfer-store.js +36 -22
- package/packages/@monomind/cli/dist/src/commands/update.js +15 -3
- package/packages/@monomind/cli/dist/src/commands/workflow.js +39 -6
- package/packages/@monomind/cli/dist/src/consensus/audit-writer.js +18 -7
- package/packages/@monomind/cli/dist/src/consensus/vote-signer.js +25 -8
- package/packages/@monomind/cli/dist/src/index.js +7 -3
- package/packages/@monomind/cli/dist/src/init/executor.js +14 -11
- package/packages/@monomind/cli/dist/src/init/shared-instructions-generator.js +20 -4
- package/packages/@monomind/cli/dist/src/init/statusline-generator.js +36 -15
- package/packages/@monomind/cli/dist/src/mcp-tools/a2a-tools.js +98 -13
- package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +16 -3
- package/packages/@monomind/cli/dist/src/mcp-tools/analyze-tools.js +80 -17
- package/packages/@monomind/cli/dist/src/mcp-tools/browser-tools.js +84 -22
- package/packages/@monomind/cli/dist/src/mcp-tools/claims-tools.js +35 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/config-tools.js +82 -17
- package/packages/@monomind/cli/dist/src/mcp-tools/coordination-tools.js +37 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/daa-tools.js +49 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/embeddings-tools.js +45 -18
- package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +75 -25
- package/packages/@monomind/cli/dist/src/mcp-tools/guidance-tools.js +32 -10
- package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +91 -20
- package/packages/@monomind/cli/dist/src/mcp-tools/hooks-tools.js +188 -29
- package/packages/@monomind/cli/dist/src/mcp-tools/memory-tools.js +25 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-compat.js +11 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +476 -62
- package/packages/@monomind/cli/dist/src/mcp-tools/neural-tools.js +44 -9
- package/packages/@monomind/cli/dist/src/mcp-tools/performance-tools.js +45 -10
- package/packages/@monomind/cli/dist/src/mcp-tools/progress-tools.js +7 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/request-tracker.js +15 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/security-tools.js +61 -9
- package/packages/@monomind/cli/dist/src/mcp-tools/session-tools.js +45 -14
- package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +15 -3
- package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +14 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.js +52 -10
- package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.js +40 -6
- package/packages/@monomind/cli/dist/src/mcp-tools/transfer-tools.js +37 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/workflow-tools.js +29 -6
- package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.js +26 -10
- package/packages/@monomind/cli/dist/src/memory/intelligence.js +80 -19
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +21 -2
- package/packages/@monomind/cli/dist/src/memory/memory-initializer.js +67 -3
- package/packages/@monomind/cli/dist/src/memory/sona-optimizer.js +14 -4
- package/packages/@monomind/cli/dist/src/monovector/command-outcomes.js +43 -7
- package/packages/@monomind/cli/dist/src/monovector/coverage-router.js +8 -4
- package/packages/@monomind/cli/dist/src/monovector/coverage-tools.js +6 -3
- package/packages/@monomind/cli/dist/src/monovector/diff-classifier.js +13 -0
- package/packages/@monomind/cli/dist/src/monovector/route-outcomes.d.ts +2 -1
- package/packages/@monomind/cli/dist/src/monovector/route-outcomes.js +46 -4
- package/packages/@monomind/cli/dist/src/plugins/manager.js +8 -3
- package/packages/@monomind/cli/dist/src/plugins/store/discovery.js +46 -2
- package/packages/@monomind/cli/dist/src/plugins/store/search.js +5 -4
- package/packages/@monomind/cli/dist/src/production/circuit-breaker.js +17 -3
- package/packages/@monomind/cli/dist/src/production/error-handler.js +3 -0
- package/packages/@monomind/cli/dist/src/production/monitoring.js +20 -3
- package/packages/@monomind/cli/dist/src/production/rate-limiter.js +13 -4
- package/packages/@monomind/cli/dist/src/production/retry.js +17 -9
- package/packages/@monomind/cli/dist/src/routing/embed-worker.js +6 -2
- package/packages/@monomind/cli/dist/src/routing/embedder.js +0 -0
- package/packages/@monomind/cli/dist/src/routing/llm-caller.js +13 -2
- package/packages/@monomind/cli/dist/src/routing/route-layer-factory.js +18 -3
- package/packages/@monomind/cli/dist/src/services/claim-service.d.ts +1 -0
- package/packages/@monomind/cli/dist/src/services/claim-service.js +8 -0
- package/packages/@monomind/cli/dist/src/services/config-file-manager.js +14 -2
- package/packages/@monomind/cli/dist/src/services/headless-worker-executor.js +18 -2
- package/packages/@monomind/cli/dist/src/services/worker-daemon.js +348 -17
- package/packages/@monomind/cli/dist/src/transfer/anonymization/index.d.ts +0 -3
- package/packages/@monomind/cli/dist/src/transfer/anonymization/index.js +16 -1
- package/packages/@monomind/cli/dist/src/transfer/export.js +8 -0
- package/packages/@monomind/cli/dist/src/transfer/ipfs/upload.js +33 -3
- package/packages/@monomind/cli/dist/src/transfer/serialization/cfp.js +8 -2
- package/packages/@monomind/cli/dist/src/transfer/storage/gcs.js +37 -3
- package/packages/@monomind/cli/dist/src/transfer/store/discovery.js +45 -3
- package/packages/@monomind/cli/dist/src/transfer/store/download.js +5 -0
- package/packages/@monomind/cli/dist/src/transfer/store/publish.js +13 -1
- package/packages/@monomind/cli/dist/src/transfer/store/registry.d.ts +8 -0
- package/packages/@monomind/cli/dist/src/transfer/store/registry.js +30 -5
- package/packages/@monomind/cli/dist/src/transfer/store/search.js +20 -5
- package/packages/@monomind/cli/dist/src/update/checker.js +59 -7
- package/packages/@monomind/cli/dist/src/update/executor.js +50 -3
- package/packages/@monomind/cli/dist/src/update/index.js +18 -1
- package/packages/@monomind/cli/dist/src/update/rate-limiter.d.ts +6 -0
- package/packages/@monomind/cli/dist/src/update/rate-limiter.js +79 -7
- package/packages/@monomind/cli/dist/src/update/validator.js +52 -1
- package/packages/@monomind/cli/package.json +2 -3
|
@@ -244,9 +244,10 @@ runId = new Date().toISOString()
|
|
|
244
244
|
|
|
245
245
|
Also validate that every role has non-empty `id`, `title`, `agent_type`, and `responsibilities` (array). Abort with `status: blocked` listing any malformed role — do this before Step 3 initializes infrastructure.
|
|
246
246
|
|
|
247
|
-
After the conceptual derivations above,
|
|
247
|
+
After the conceptual derivations above, **run the ENTIRE Step 2 + Step 3 setup as a SINGLE Bash tool call** so all variables persist through infrastructure creation. Copy the full script below into one Bash call — do NOT split it across multiple calls:
|
|
248
248
|
|
|
249
249
|
```bash
|
|
250
|
+
# ── STEP 2+3 COMBINED (must run as one Bash call — variables must persist) ──
|
|
250
251
|
orgFile=".monomind/orgs/${org_name}.json"
|
|
251
252
|
orgName=$(jq -r '.name' "$orgFile")
|
|
252
253
|
goal="${task:-$(jq -r '.goal' "$orgFile")}"
|
|
@@ -266,71 +267,73 @@ board_id=$(jq -r '.board_id // empty' "$orgFile")
|
|
|
266
267
|
todo_col=$(jq -r '.todo_col_id // empty' "$orgFile")
|
|
267
268
|
doing_col=$(jq -r '.doing_col_id // empty' "$orgFile")
|
|
268
269
|
done_col=$(jq -r '.done_col_id // empty' "$orgFile")
|
|
269
|
-
runId
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
---
|
|
273
|
-
|
|
274
|
-
## Step 3 — Initialize Org Infrastructure
|
|
275
|
-
|
|
276
|
-
**Validate board IDs from org config:**
|
|
277
|
-
|
|
278
|
-
The board and column IDs were written into the org config by `createorg`. If missing, the org was not created correctly.
|
|
270
|
+
runId="run-$(date -u +%Y%m%dT%H%M%S)"
|
|
279
271
|
|
|
280
|
-
|
|
281
|
-
[ -z "$board_id" ]
|
|
282
|
-
[ -z "$todo_col" ] && { echo "ERROR: org config missing todo_col_id — re-run /mastermind:createorg --name ${orgName}."; exit 1; }
|
|
283
|
-
[ -z "$doing_col" ] && { echo "ERROR: org config missing doing_col_id — re-run /mastermind:createorg --name ${orgName}."; exit 1; }
|
|
284
|
-
[ -z "$done_col" ] && { echo "ERROR: org config missing done_col_id — re-run /mastermind:createorg --name ${orgName}."; exit 1; }
|
|
285
|
-
```
|
|
272
|
+
# Validate board IDs (warning only — boss can still scan/fix code without a board)
|
|
273
|
+
[ -z "$board_id" ] && echo "WARN: board_id missing from org config — task board commands will be skipped by boss"
|
|
286
274
|
|
|
287
|
-
|
|
288
|
-
```bash
|
|
275
|
+
# Remove any stale stop file (MUST happen before boss spawns)
|
|
289
276
|
mkdir -p .monomind/orgs/.stops
|
|
290
|
-
|
|
277
|
+
rm -f "$stopFile"
|
|
291
278
|
|
|
292
|
-
|
|
293
|
-
```bash
|
|
279
|
+
# Initialize org state file
|
|
294
280
|
stateFile=".monomind/orgs/${orgName}-state.json"
|
|
295
281
|
if [ ! -f "$stateFile" ]; then
|
|
296
|
-
jq -n \
|
|
297
|
-
|
|
298
|
-
--arg runId "$runId" \
|
|
299
|
-
'{org:$org,run_id:$runId,started_at:(now|todate),agents:{}}' \
|
|
300
|
-
> "$stateFile"
|
|
282
|
+
jq -n --arg org "$orgName" --arg runId "$runId" \
|
|
283
|
+
'{org:$org,run_id:$runId,started_at:(now|todate),agents:{}}' > "$stateFile"
|
|
301
284
|
else
|
|
302
|
-
# Update run_id and started_at for this run
|
|
303
285
|
tmp="${stateFile}.tmp"
|
|
304
|
-
jq --arg runId "$runId" \
|
|
305
|
-
|
|
306
|
-
"$stateFile" > "$tmp" && mv "$tmp" "$stateFile"
|
|
286
|
+
jq --arg runId "$runId" '.run_id = $runId | .started_at = (now|todate)' \
|
|
287
|
+
"$stateFile" > "$tmp" && mv "$tmp" "$stateFile"
|
|
307
288
|
fi
|
|
308
|
-
```
|
|
309
289
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
290
|
+
# Resolve git-safe monomind directory
|
|
291
|
+
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
|
292
|
+
CTRL_URL=$(jq -r '.url // "http://localhost:4242"' "$REPO_ROOT/.monomind/control.json" 2>/dev/null || echo "http://localhost:4242")
|
|
293
|
+
GIT_COMMON=$(git rev-parse --git-common-dir 2>/dev/null || echo ".git")
|
|
294
|
+
if [[ "$GIT_COMMON" != /* ]]; then GIT_COMMON="$REPO_ROOT/$GIT_COMMON"; fi
|
|
295
|
+
GIT_COMMON="${GIT_COMMON%/}"
|
|
296
|
+
MONO_DIR="${GIT_COMMON}/monomind"
|
|
297
|
+
|
|
298
|
+
# Create run file and write run:start
|
|
299
|
+
runDir="${MONO_DIR}/orgs/${orgName}/runs"
|
|
300
|
+
mkdir -p "$runDir"
|
|
301
|
+
runFile="${runDir}/${runId}.jsonl"
|
|
302
|
+
jq -cn \
|
|
303
|
+
--arg runId "$runId" --arg org "$orgName" --arg goal "$goal" \
|
|
304
|
+
--arg bossRole "$bossRole_id" --arg proj "$REPO_ROOT" \
|
|
305
|
+
'{type:"run:start",runId:$runId,org:$org,orgName:$org,goal:$goal,bossRole:$bossRole,project:$proj,ts:(now*1000|floor)}' \
|
|
306
|
+
>> "$runFile"
|
|
307
|
+
|
|
308
|
+
# Print resolved values so Step 4 can embed them in the boss prompt
|
|
309
|
+
echo "ORG_VARS: orgName=${orgName} runId=${runId} sessionId=${sessionId} goal=${goal} bossRole_id=${bossRole_id} bossRole_title=${bossRole_title} bossRole_agent_type=${bossRole_agent_type} board_id=${board_id} todo_col=${todo_col} doing_col=${doing_col} done_col=${done_col} checkpointMin=${checkpointMin} memNs=${memNs} CTRL_URL=${CTRL_URL} MONO_DIR=${MONO_DIR} runFile=${runFile} REPO_ROOT=${REPO_ROOT}"
|
|
313
310
|
```
|
|
314
311
|
|
|
315
|
-
|
|
312
|
+
After running the script above, read the `ORG_VARS:` line from its output and hold those values for Step 4 (embed them into the boss prompt as literals — **do not re-run bash to look them up**).
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Step 3 — Emit org:start (separate Bash call, re-derives from output)
|
|
317
|
+
|
|
318
|
+
After the combined Step 2+3 script runs, emit `org:start` using the values printed by `ORG_VARS:`. Replace each `<X>` with the literal value from that line:
|
|
319
|
+
|
|
316
320
|
```bash
|
|
317
|
-
|
|
318
|
-
CTRL_URL=$(jq -r '.url // "http://localhost:4242"' "$REPO_ROOT/.monomind/control.json" 2>/dev/null || echo "http://localhost:4242")
|
|
319
|
-
curl -s -X POST "${CTRL_URL}/api/mastermind/event" \
|
|
321
|
+
curl -s -X POST "<CTRL_URL>/api/mastermind/event" \
|
|
320
322
|
-H "Content-Type: application/json" \
|
|
321
323
|
-d "$(jq -cn \
|
|
322
|
-
--arg session "
|
|
323
|
-
--arg org "
|
|
324
|
-
--arg
|
|
325
|
-
--arg
|
|
326
|
-
|
|
324
|
+
--arg session "<sessionId>" \
|
|
325
|
+
--arg org "<orgName>" \
|
|
326
|
+
--arg runId "<runId>" \
|
|
327
|
+
--arg goal "<goal>" \
|
|
328
|
+
--arg proj "<REPO_ROOT>" \
|
|
329
|
+
'{type:"org:start",session:$session,org:$org,runId:$runId,goal:$goal,project:$proj,ts:(now*1000|floor)}')" || true
|
|
327
330
|
```
|
|
328
331
|
|
|
329
332
|
---
|
|
330
333
|
|
|
331
334
|
## Step 4 — Spawn Boss Agent
|
|
332
335
|
|
|
333
|
-
Using the variables
|
|
336
|
+
Using the literal values from the `ORG_VARS:` output (not shell variables — they don't persist), spawn the boss agent via Task tool:
|
|
334
337
|
|
|
335
338
|
```javascript
|
|
336
339
|
Task({
|
|
@@ -351,9 +354,9 @@ FULL COMMUNICATION TOPOLOGY:
|
|
|
351
354
|
${orgConfig.communication.map(e => `${e.from} → ${e.to} (${e.type})`).join('\n')}
|
|
352
355
|
|
|
353
356
|
SHARED INFRASTRUCTURE:
|
|
354
|
-
- Task board (monotask): board_id
|
|
355
|
-
Columns: Todo
|
|
356
|
-
- Memory namespace:
|
|
357
|
+
- Task board (monotask): board_id=<board_id>
|
|
358
|
+
Columns: Todo=<todo_col> Doing=<doing_col> Done=<done_col>
|
|
359
|
+
- Memory namespace: <memNs> (use: npx monomind@latest memory store/search --namespace <memNs>)
|
|
357
360
|
- Dashboard events: POST to mastermind control server via curl — see CTRL_URL resolution in DASHBOARD EVENTS section below
|
|
358
361
|
- Session ID for all events: ${sessionId}
|
|
359
362
|
|
|
@@ -429,52 +432,117 @@ APPROVAL GATE (when governance.policy is "board" or "strict"):
|
|
|
429
432
|
sleep 30
|
|
430
433
|
done
|
|
431
434
|
|
|
435
|
+
DASHBOARD EVENT HELPER — resolve once at startup, reuse in every step:
|
|
436
|
+
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
|
437
|
+
CTRL_URL=$(jq -r '.url // "http://localhost:4242"' "$REPO_ROOT/.monomind/control.json" 2>/dev/null || echo "http://localhost:4242")
|
|
438
|
+
# Git-safe monomind dir — shared across all worktrees, survives branch switches:
|
|
439
|
+
GIT_COMMON=$(git rev-parse --git-common-dir 2>/dev/null || echo ".git")
|
|
440
|
+
# Resolve to absolute path (git returns relative ".git" for main checkout)
|
|
441
|
+
if [[ "$GIT_COMMON" != /* ]]; then GIT_COMMON="$REPO_ROOT/$GIT_COMMON"; fi
|
|
442
|
+
GIT_COMMON="${GIT_COMMON%/}" # strip trailing slash if any
|
|
443
|
+
MONO_DIR="${GIT_COMMON}/monomind"
|
|
444
|
+
runFile="${MONO_DIR}/orgs/${orgName}/runs/${runId}.jsonl"
|
|
445
|
+
|
|
446
|
+
⚠️ DASHBOARD EVENTS ARE MANDATORY — run every curl below exactly as written.
|
|
447
|
+
Do NOT skip, defer, or summarize. These keep the human-visible dashboard current.
|
|
448
|
+
Use Bash tool for all curl calls. Never use WebFetch (it doesn't support POST).
|
|
449
|
+
EVERY event MUST include runId:"${runId}" and org:"${orgName}" so the server routes it to the run file.
|
|
450
|
+
|
|
432
451
|
STOP DETECTION (check this FIRST in every loop iteration):
|
|
433
452
|
[ -f "${stopFile}" ] && echo STOP_REQUESTED
|
|
434
|
-
If the file exists: emit org:complete
|
|
453
|
+
If the file exists: write run:complete to run file, emit org:complete via curl, then "rm -f ${stopFile}", then exit.
|
|
454
|
+
# REQUIRED — write run:complete directly to run file:
|
|
455
|
+
jq -cn --arg runId "${runId}" --arg org "${orgName}" \
|
|
456
|
+
'{type:"run:complete",runId:$runId,org:$org,status:"stopped",ts:(now*1000|floor)}' >> "${runFile}" || true
|
|
457
|
+
# REQUIRED — emit org:complete via curl (includes runId so it also gets appended):
|
|
458
|
+
curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "Content-Type: application/json" \
|
|
459
|
+
-d "$(jq -cn --arg s "${sessionId}" --arg o "${orgName}" --arg rid "${runId}" --arg p "$REPO_ROOT" \
|
|
460
|
+
'{type:"org:complete",session:$s,org:$o,runId:$rid,project:$p,ts:(now*1000|floor)}')" || true
|
|
435
461
|
|
|
436
462
|
OPERATING LOOP:
|
|
437
|
-
1. Check for stop signal (
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
463
|
+
1. Check for stop signal (above). Exit with run:complete + org:complete events if found.
|
|
464
|
+
|
|
465
|
+
2. List unclaimed Todo cards:
|
|
466
|
+
monotask card list ${board_id} --col ${todo_col} --json | jq '[.[] | select(.labels | index("claimed") | not)]'
|
|
467
|
+
|
|
468
|
+
3. For each unclaimed Todo card, assign to the appropriate role based on its "role:<id>" label.
|
|
469
|
+
Skip cards whose prerequisites are not yet in the Done column.
|
|
470
|
+
|
|
471
|
+
4. For EACH role agent you are about to spawn — do these steps in order:
|
|
472
|
+
a) Move card to Doing and mark claimed:
|
|
473
|
+
monotask card move ${board_id} $CARD_ID ${doing_col}
|
|
474
|
+
monotask card label add ${board_id} $CARD_ID "claimed"
|
|
475
|
+
b) REQUIRED — emit org:comms to dashboard (include runId):
|
|
476
|
+
curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "Content-Type: application/json" \
|
|
477
|
+
-d "$(jq -cn --arg s "${sessionId}" --arg o "${orgName}" --arg rid "${runId}" --arg p "$REPO_ROOT" \
|
|
478
|
+
--arg role "<role_id>" --arg task "<card title>" \
|
|
479
|
+
'{type:"org:comms",session:$s,org:$o,runId:$rid,from:"boss",to:$role,msg:("Assigning: "+$task),project:$p,ts:(now*1000|floor)}')" || true
|
|
480
|
+
c) REQUIRED — emit org:agent:online before spawning (include runId):
|
|
481
|
+
curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "Content-Type: application/json" \
|
|
482
|
+
-d "$(jq -cn --arg s "${sessionId}" --arg o "${orgName}" --arg rid "${runId}" --arg p "$REPO_ROOT" \
|
|
483
|
+
--arg role "<role_id>" --arg title "<role title>" --arg at "<agent_type>" \
|
|
484
|
+
'{type:"org:agent:online",session:$s,org:$o,runId:$rid,role:$role,title:$title,agent_type:$at,project:$p,ts:(now*1000|floor)}')" || true
|
|
485
|
+
d) Spawn the agent via Task tool (run_in_background: true) with:
|
|
486
|
+
- Full role briefing (title, responsibilities, who they report to, memory namespace)
|
|
487
|
+
- The specific card title and card_id to work on
|
|
488
|
+
- Report output via: npx monomind@latest memory store --key "${memNs}:report:<card_id>" --namespace "${memNs}"
|
|
489
|
+
- When complete, move card to Done: monotask card move ${board_id} $CARD_ID ${done_col}
|
|
490
|
+
- REQUIRED: Include the following DASHBOARD EVENTS block verbatim in every specialist prompt so
|
|
491
|
+
they can emit their own events to the dashboard:
|
|
492
|
+
|
|
493
|
+
DASHBOARD EVENTS — emit these at the start and end of your work (REQUIRED):
|
|
494
|
+
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
|
495
|
+
CTRL_URL=$(jq -r '.url // "http://localhost:4242"' "$REPO_ROOT/.monomind/control.json" 2>/dev/null || echo "http://localhost:4242")
|
|
496
|
+
GIT_COMMON=$(git rev-parse --git-common-dir 2>/dev/null || echo ".git")
|
|
497
|
+
if [[ "$GIT_COMMON" != /* ]]; then GIT_COMMON="$REPO_ROOT/$GIT_COMMON"; fi
|
|
498
|
+
GIT_COMMON="${GIT_COMMON%/}"
|
|
499
|
+
MONO_DIR="${GIT_COMMON}/monomind"
|
|
500
|
+
runFile="${MONO_DIR}/orgs/${orgName}/runs/${runId}.jsonl"
|
|
501
|
+
# On start — announce you are working:
|
|
502
|
+
curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "Content-Type: application/json" \
|
|
503
|
+
-d "$(jq -cn --arg s "${sessionId}" --arg o "${orgName}" --arg rid "${runId}" \
|
|
504
|
+
--arg from "<role_id>" --arg msg "Starting: <card title>" \
|
|
505
|
+
'{type:"org:comms",session:$s,org:$o,runId:$rid,from:$from,to:"boss",msg:$msg,ts:(now*1000|floor)}')" || true
|
|
506
|
+
# On completion — report back:
|
|
507
|
+
curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "Content-Type: application/json" \
|
|
508
|
+
-d "$(jq -cn --arg s "${sessionId}" --arg o "${orgName}" --arg rid "${runId}" \
|
|
509
|
+
--arg from "<role_id>" --arg msg "Completed: <one-sentence summary of output>" \
|
|
510
|
+
'{type:"org:comms",session:$s,org:$o,runId:$rid,from:$from,to:"boss",msg:$msg,ts:(now*1000|floor)}')" || true
|
|
511
|
+
|
|
512
|
+
Fill in the literal values for orgName="${orgName}", runId="${runId}", sessionId="${sessionId}" — embed them
|
|
513
|
+
directly in the prompt string so the specialist doesn't need to resolve them.
|
|
514
|
+
|
|
448
515
|
5. Collect completed results from memory (search "${memNs}:report:")
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
516
|
+
|
|
517
|
+
6. Synthesize reports, make decisions, create new cards in Todo column based on progress toward goal.
|
|
518
|
+
REQUIRED — after each decision, emit org:comms (include runId):
|
|
519
|
+
curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "Content-Type: application/json" \
|
|
520
|
+
-d "$(jq -cn --arg s "${sessionId}" --arg o "${orgName}" --arg rid "${runId}" --arg p "$REPO_ROOT" \
|
|
521
|
+
--arg msg "<one-sentence summary of decision or finding>" \
|
|
522
|
+
'{type:"org:comms",session:$s,org:$o,runId:$rid,from:"boss",to:"all",msg:$msg,project:$p,ts:(now*1000|floor)}')" || true
|
|
523
|
+
|
|
524
|
+
7. Write cycle-complete entry (directly to run file AND to activity log):
|
|
453
525
|
pending_count=$(monotask card list ${board_id} --col ${todo_col} --json 2>/dev/null | jq '[.[] | select(.labels | index("claimed") | not)] | length' 2>/dev/null || echo 0)
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
526
|
+
jq -cn --arg runId "${runId}" --arg org "${orgName}" --argjson pend "${pending_count:-0}" \
|
|
527
|
+
'{type:"run:cycle:complete",runId:$runId,org:$org,pending:$pend,ts:(now*1000|floor)}' >> "${runFile}" || true
|
|
528
|
+
activityFile=".monomind/orgs/${orgName}-activity.jsonl"
|
|
529
|
+
echo "{\"type\":\"run:cycle:complete\",\"org\":\"${orgName}\",\"runId\":\"${runId}\",\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"pending\":${pending_count:-0}}" >> "$activityFile" 2>/dev/null || true
|
|
530
|
+
|
|
531
|
+
8. REQUIRED — emit org:checkpoint with a one-sentence progress summary (include runId):
|
|
532
|
+
curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "Content-Type: application/json" \
|
|
533
|
+
-d "$(jq -cn --arg s "${sessionId}" --arg o "${orgName}" --arg rid "${runId}" --arg p "$REPO_ROOT" \
|
|
534
|
+
--arg prog "<one sentence: what was done this cycle and what is next>" \
|
|
535
|
+
--argjson pend "${pending_count:-0}" \
|
|
536
|
+
'{type:"org:checkpoint",session:$s,org:$o,runId:$rid,progress:$prog,pending_tasks:$pend,project:$p,ts:(now*1000|floor)}')" || true
|
|
537
|
+
|
|
538
|
+
9. Wait ${checkpointMin} minutes, then loop back to step 1.
|
|
458
539
|
|
|
459
540
|
AGENT TYPES FOR YOUR TEAM:
|
|
460
541
|
${orgConfig.roles.filter(r => r.id !== bossRole.id).map(r =>
|
|
461
542
|
`• ${r.title}: subagent_type="${r.agent_type}"`
|
|
462
543
|
).join('\n')}
|
|
463
544
|
|
|
464
|
-
|
|
465
|
-
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
|
466
|
-
CTRL_URL=$(jq -r '.url // "http://localhost:4242"' "$REPO_ROOT/.monomind/control.json" 2>/dev/null || echo "http://localhost:4242")
|
|
467
|
-
curl -s -X POST "${CTRL_URL}/api/mastermind/event" \
|
|
468
|
-
-H "Content-Type: application/json" \
|
|
469
|
-
-d "$(jq -cn --arg type TYPE --arg session SESSION --arg org ORG --arg proj "$REPO_ROOT" '{type:$type,session:$session,org:$org,project:$proj,ts:(now*1000|floor)}')"
|
|
470
|
-
Payloads:
|
|
471
|
-
- org:agent:online → add fields: role, title, agent_type
|
|
472
|
-
- org:comms → add fields: from, to, msg
|
|
473
|
-
- org:checkpoint → add fields: progress, pending_tasks
|
|
474
|
-
- org:complete → no additional fields
|
|
475
|
-
Note: always include project:$REPO_ROOT in all event payloads for correct multi-project SSE filtering
|
|
476
|
-
|
|
477
|
-
START NOW: check for stop signal, assess the board, create initial tasks if none exist, then begin the operating loop.`
|
|
545
|
+
START NOW: resolve CTRL_URL, check for stop signal, assess the board, create initial tasks if none exist, then begin the operating loop.`
|
|
478
546
|
})
|
|
479
547
|
```
|
|
480
548
|
|
|
@@ -485,21 +553,20 @@ START NOW: check for stop signal, assess the board, create initial tasks if none
|
|
|
485
553
|
Emit `org:agent:online` for the boss role (team member events are emitted by the boss itself):
|
|
486
554
|
|
|
487
555
|
```bash
|
|
488
|
-
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
|
489
|
-
CTRL_URL=$(jq -r '.url // "http://localhost:4242"' "$REPO_ROOT/.monomind/control.json" 2>/dev/null || echo "http://localhost:4242")
|
|
490
556
|
curl -s -X POST "${CTRL_URL}/api/mastermind/event" \
|
|
491
557
|
-H "Content-Type: application/json" \
|
|
492
558
|
-d "$(jq -cn \
|
|
493
559
|
--arg session "$sessionId" \
|
|
494
560
|
--arg org "$orgName" \
|
|
561
|
+
--arg runId "$runId" \
|
|
495
562
|
--arg role "$bossRole_id" \
|
|
496
563
|
--arg title "$bossRole_title" \
|
|
497
564
|
--arg agent_type "$bossRole_agent_type" \
|
|
498
565
|
--arg proj "$REPO_ROOT" \
|
|
499
|
-
'{type:"org:agent:online",session:$session,org:$org,role:$role,title:$title,agent_type:$agent_type,project:$proj,ts:(now*1000|floor)}')" || true
|
|
566
|
+
'{type:"org:agent:online",session:$session,org:$org,runId:$runId,role:$role,title:$title,agent_type:$agent_type,project:$proj,ts:(now*1000|floor)}')" || true
|
|
500
567
|
```
|
|
501
568
|
|
|
502
|
-
(Use the scalar string variables `$bossRole_id`, `$bossRole_title`, `$bossRole_agent_type` derived by extracting fields from `bossRole` before constructing the curl call.)
|
|
569
|
+
(Use the scalar string variables `$bossRole_id`, `$bossRole_title`, `$bossRole_agent_type` derived by extracting fields from `bossRole` before constructing the curl call. `CTRL_URL`, `MONO_DIR`, and `runId` were resolved in Step 3 — reuse those variables.)
|
|
503
570
|
|
|
504
571
|
---
|
|
505
572
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "monomind",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "Monomind - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -56,12 +56,29 @@
|
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"semver": "^7.6.0",
|
|
59
|
-
"@monoes/monograph": "^1.
|
|
59
|
+
"@monoes/monograph": "^1.1.0"
|
|
60
60
|
},
|
|
61
61
|
"overrides": {
|
|
62
|
-
"hono": ">=4.
|
|
62
|
+
"hono": ">=4.12.21",
|
|
63
|
+
"ws": ">=8.20.1",
|
|
64
|
+
"axios": ">=1.16.0",
|
|
65
|
+
"qs": ">=6.15.2",
|
|
66
|
+
"esbuild": ">=0.28.1",
|
|
67
|
+
"protobufjs": ">=7.5.8",
|
|
68
|
+
"@protobufjs/utf8": ">=1.1.1",
|
|
69
|
+
"@grpc/grpc-js": ">=1.14.4"
|
|
63
70
|
},
|
|
64
71
|
"pnpm": {
|
|
72
|
+
"overrides": {
|
|
73
|
+
"hono": ">=4.12.21",
|
|
74
|
+
"ws": ">=8.20.1",
|
|
75
|
+
"axios": ">=1.16.0",
|
|
76
|
+
"qs": ">=6.15.2",
|
|
77
|
+
"esbuild": ">=0.28.1",
|
|
78
|
+
"protobufjs": ">=7.5.8",
|
|
79
|
+
"@protobufjs/utf8": ">=1.1.1",
|
|
80
|
+
"@grpc/grpc-js": ">=1.14.4"
|
|
81
|
+
},
|
|
65
82
|
"peerDependencyRules": {
|
|
66
83
|
"allowedVersions": {
|
|
67
84
|
"tree-sitter": "0.22.4",
|
|
@@ -160,6 +160,8 @@ export function buildUnifiedRegistry(roots, outputPath) {
|
|
|
160
160
|
for (const file of files) {
|
|
161
161
|
let content;
|
|
162
162
|
try {
|
|
163
|
+
if (statSync(file).size > 512 * 1024)
|
|
164
|
+
continue; // skip files > 512 KB
|
|
163
165
|
content = readFileSync(file, 'utf-8');
|
|
164
166
|
}
|
|
165
167
|
catch {
|
|
@@ -19,6 +19,8 @@ export const LOG_FILE = `${STATE_DIR}/autopilot-log.json`;
|
|
|
19
19
|
const MAX_HISTORY_ENTRIES = 50;
|
|
20
20
|
/** Maximum entries kept in the event log */
|
|
21
21
|
const MAX_LOG_ENTRIES = 1000;
|
|
22
|
+
/** Maximum bytes before refusing to load a state/log JSON file (10 MB) */
|
|
23
|
+
const MAX_STATE_FILE_BYTES = 10 * 1024 * 1024;
|
|
22
24
|
/** Allowlist for valid task sources */
|
|
23
25
|
export const VALID_TASK_SOURCES = new Set(['team-tasks', 'swarm-tasks', 'file-checklist']);
|
|
24
26
|
/** Terminal task statuses */
|
|
@@ -91,7 +93,7 @@ export function loadState() {
|
|
|
91
93
|
const filePath = path.resolve(STATE_FILE);
|
|
92
94
|
const defaults = getDefaultState();
|
|
93
95
|
try {
|
|
94
|
-
if (fs.existsSync(filePath)) {
|
|
96
|
+
if (fs.existsSync(filePath) && fs.statSync(filePath).size <= MAX_STATE_FILE_BYTES) {
|
|
95
97
|
const raw = safeJsonParse(fs.readFileSync(filePath, 'utf-8'));
|
|
96
98
|
const merged = { ...defaults, ...raw };
|
|
97
99
|
// Re-validate fields that could be tampered with
|
|
@@ -190,7 +192,7 @@ function compactLog(filePath) {
|
|
|
190
192
|
export function loadLog() {
|
|
191
193
|
const filePath = path.resolve(LOG_FILE);
|
|
192
194
|
try {
|
|
193
|
-
if (fs.existsSync(filePath)) {
|
|
195
|
+
if (fs.existsSync(filePath) && fs.statSync(filePath).size <= MAX_STATE_FILE_BYTES) {
|
|
194
196
|
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
195
197
|
// Backward compatible: support both old JSON-array form and the new
|
|
196
198
|
// append-only NDJSON form. Prefer NDJSON if the file looks line-based.
|
|
@@ -236,7 +238,10 @@ export function discoverTasks(sources) {
|
|
|
236
238
|
const files = fs.readdirSync(teamDir).filter((f) => f.endsWith('.json'));
|
|
237
239
|
for (const file of files) {
|
|
238
240
|
try {
|
|
239
|
-
const
|
|
241
|
+
const taskFilePath = path.join(teamDir, file);
|
|
242
|
+
if (fs.statSync(taskFilePath).size > MAX_STATE_FILE_BYTES)
|
|
243
|
+
continue;
|
|
244
|
+
const data = safeJsonParse(fs.readFileSync(taskFilePath, 'utf-8'));
|
|
240
245
|
tasks.push({
|
|
241
246
|
id: String(data.id || file.replace('.json', '')),
|
|
242
247
|
subject: String(data.subject || data.title || file),
|
|
@@ -254,7 +259,7 @@ export function discoverTasks(sources) {
|
|
|
254
259
|
if (source === 'swarm-tasks') {
|
|
255
260
|
const swarmFile = path.resolve('.monomind/swarm-tasks.json');
|
|
256
261
|
try {
|
|
257
|
-
if (fs.existsSync(swarmFile)) {
|
|
262
|
+
if (fs.existsSync(swarmFile) && fs.statSync(swarmFile).size <= MAX_STATE_FILE_BYTES) {
|
|
258
263
|
const data = safeJsonParse(fs.readFileSync(swarmFile, 'utf-8'));
|
|
259
264
|
const swarmTasks = Array.isArray(data) ? data : (data.tasks || []);
|
|
260
265
|
for (const t of swarmTasks) {
|
|
@@ -275,7 +280,7 @@ export function discoverTasks(sources) {
|
|
|
275
280
|
if (source === 'file-checklist') {
|
|
276
281
|
const checklistFile = path.resolve('.monomind/data/checklist.json');
|
|
277
282
|
try {
|
|
278
|
-
if (fs.existsSync(checklistFile)) {
|
|
283
|
+
if (fs.existsSync(checklistFile) && fs.statSync(checklistFile).size <= MAX_STATE_FILE_BYTES) {
|
|
279
284
|
const data = safeJsonParse(fs.readFileSync(checklistFile, 'utf-8'));
|
|
280
285
|
const items = Array.isArray(data) ? data : (data.items || []);
|
|
281
286
|
for (const item of items) {
|
|
@@ -23,9 +23,22 @@ export class BenchmarkRunner {
|
|
|
23
23
|
if (!fs.existsSync(resolved)) {
|
|
24
24
|
return benchmarks;
|
|
25
25
|
}
|
|
26
|
+
const MAX_BENCH_FILE_SIZE = 1 * 1024 * 1024; // 1 MB per benchmark file
|
|
26
27
|
const files = fs.readdirSync(resolved).filter((f) => f.endsWith('.json'));
|
|
27
28
|
for (const file of files) {
|
|
28
29
|
const filePath = path.join(resolved, file);
|
|
30
|
+
// Guard against OOM: reject symlinks and oversized files before reading
|
|
31
|
+
let stat;
|
|
32
|
+
try {
|
|
33
|
+
stat = fs.lstatSync(filePath);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (stat.isSymbolicLink())
|
|
39
|
+
continue;
|
|
40
|
+
if (stat.size > MAX_BENCH_FILE_SIZE)
|
|
41
|
+
continue;
|
|
29
42
|
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
30
43
|
try {
|
|
31
44
|
const parsed = JSON.parse(raw);
|
|
@@ -6,15 +6,17 @@
|
|
|
6
6
|
* Checks whether the output contains the expected substring.
|
|
7
7
|
*/
|
|
8
8
|
export function containsExpected(output, config) {
|
|
9
|
-
|
|
9
|
+
// Cap expected to prevent huge strings from inflating returned objects
|
|
10
|
+
const expected = typeof config.expected === 'string' ? config.expected.slice(0, 200) : '';
|
|
11
|
+
const found = output.includes(expected);
|
|
10
12
|
return {
|
|
11
13
|
type: 'contains_expected',
|
|
12
14
|
passed: found,
|
|
13
|
-
actual: found ?
|
|
14
|
-
expected
|
|
15
|
+
actual: found ? expected : null,
|
|
16
|
+
expected,
|
|
15
17
|
message: found
|
|
16
|
-
? `Output contains expected string "${
|
|
17
|
-
: `Output missing expected string "${
|
|
18
|
+
? `Output contains expected string "${expected}"`
|
|
19
|
+
: `Output missing expected string "${expected}"`,
|
|
18
20
|
};
|
|
19
21
|
}
|
|
20
22
|
/**
|
|
@@ -38,16 +40,20 @@ export function lengthRange(output, config) {
|
|
|
38
40
|
*/
|
|
39
41
|
export function noHallucination(output, config) {
|
|
40
42
|
const lowerOutput = output.toLowerCase();
|
|
41
|
-
|
|
43
|
+
// Cap forbidden array to 200 entries to prevent unbounded O(n) scan
|
|
44
|
+
const forbidden = Array.isArray(config.forbidden) ? config.forbidden.slice(0, 200) : [];
|
|
45
|
+
const found = forbidden.filter((word) => typeof word === 'string' && lowerOutput.includes(word.toLowerCase()));
|
|
42
46
|
const passed = found.length === 0;
|
|
47
|
+
// Truncate reflected words to avoid inflated messages
|
|
48
|
+
const truncatedFound = found.map((w) => w.slice(0, 50));
|
|
43
49
|
return {
|
|
44
50
|
type: 'no_hallucination',
|
|
45
51
|
passed,
|
|
46
|
-
actual: found.length > 0 ?
|
|
52
|
+
actual: found.length > 0 ? truncatedFound : null,
|
|
47
53
|
expected: null,
|
|
48
54
|
message: passed
|
|
49
55
|
? 'No forbidden words found in output'
|
|
50
|
-
: `Forbidden words found: ${
|
|
56
|
+
: `Forbidden words found: ${truncatedFound.join(', ')}`,
|
|
51
57
|
};
|
|
52
58
|
}
|
|
53
59
|
/**
|
|
@@ -56,8 +62,13 @@ export function noHallucination(output, config) {
|
|
|
56
62
|
export function jsonValid(output) {
|
|
57
63
|
let passed = false;
|
|
58
64
|
let parsedType = null;
|
|
65
|
+
// Cap output before JSON.parse to prevent OOM on huge strings
|
|
66
|
+
const MAX_JSON_BYTES = 1 * 1024 * 1024; // 1 MB
|
|
67
|
+
const bounded = typeof output === 'string' && output.length > MAX_JSON_BYTES
|
|
68
|
+
? output.slice(0, MAX_JSON_BYTES)
|
|
69
|
+
: output;
|
|
59
70
|
try {
|
|
60
|
-
const parsed = JSON.parse(
|
|
71
|
+
const parsed = JSON.parse(bounded);
|
|
61
72
|
passed = true;
|
|
62
73
|
parsedType = typeof parsed;
|
|
63
74
|
}
|
|
@@ -20,7 +20,8 @@ export async function clickElement(client, sessionId, ref, options = {}) {
|
|
|
20
20
|
}
|
|
21
21
|
export async function clickPoint(client, sessionId, x, y, options = {}) {
|
|
22
22
|
const button = options.button ?? 'left';
|
|
23
|
-
|
|
23
|
+
// Cap clickCount to prevent unbounded loop DoS
|
|
24
|
+
const clickCount = Math.min(Math.max(1, Math.floor(options.clickCount ?? 1)), 100);
|
|
24
25
|
const modifiers = options.modifiers ?? 0;
|
|
25
26
|
const shared = { x, y, button, modifiers };
|
|
26
27
|
const buttonsMask = button === 'right' ? 2 : button === 'middle' ? 4 : 1;
|
|
@@ -69,7 +70,9 @@ export async function fillElement(client, sessionId, ref, value) {
|
|
|
69
70
|
await typeText(client, sessionId, value);
|
|
70
71
|
}
|
|
71
72
|
export async function typeText(client, sessionId, text) {
|
|
72
|
-
|
|
73
|
+
// Cap to 100 KB to prevent OOM in CDP message serializer
|
|
74
|
+
const safeText = text.length > 102_400 ? text.slice(0, 102_400) : text;
|
|
75
|
+
await client.send('Input.insertText', { text: safeText }, sessionId);
|
|
73
76
|
}
|
|
74
77
|
export async function pressKeyCombo(client, sessionId, key, modifiers) {
|
|
75
78
|
const { text: _text, ...keyInfo } = resolveKey(key);
|
|
@@ -184,6 +187,8 @@ function resolveKey(key) {
|
|
|
184
187
|
return { key, code: key };
|
|
185
188
|
}
|
|
186
189
|
export async function scrollElement(client, sessionId, direction, amount = 300, ref) {
|
|
190
|
+
// Cap scroll amount to prevent extreme delta values
|
|
191
|
+
amount = Math.min(Math.max(1, Math.floor(amount)), 100_000);
|
|
187
192
|
let x = 0;
|
|
188
193
|
let y = 0;
|
|
189
194
|
let deltaX = 0;
|
|
@@ -351,7 +356,9 @@ export async function readClipboard(client, sessionId) {
|
|
|
351
356
|
return result;
|
|
352
357
|
}
|
|
353
358
|
export async function writeClipboard(client, sessionId, text) {
|
|
354
|
-
|
|
359
|
+
// Cap to 100 KB to prevent OOM when serializing the CDP expression
|
|
360
|
+
const safeText = text.length > 102_400 ? text.slice(0, 102_400) : text;
|
|
361
|
+
await evaluateJs(client, sessionId, `navigator.clipboard.writeText(${JSON.stringify(safeText)})`);
|
|
355
362
|
}
|
|
356
363
|
export async function pushState(client, sessionId, url) {
|
|
357
364
|
// Try Next.js router first, then fallback to history.pushState
|
|
@@ -41,7 +41,12 @@ export async function isPortOpen(port) {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
export async function launchBrowser(config = {}) {
|
|
44
|
-
const
|
|
44
|
+
const rawPort = config.port ?? DEFAULT_PORT;
|
|
45
|
+
// Validate port is in a safe range for localhost CDP debugging
|
|
46
|
+
if (!Number.isInteger(rawPort) || rawPort < 1024 || rawPort > 65535) {
|
|
47
|
+
throw new Error(`Invalid port: ${rawPort}. Must be an integer between 1024 and 65535.`);
|
|
48
|
+
}
|
|
49
|
+
const port = rawPort;
|
|
45
50
|
if (await isPortOpen(port)) {
|
|
46
51
|
return port;
|
|
47
52
|
}
|
|
@@ -69,7 +74,9 @@ export async function launchBrowser(config = {}) {
|
|
|
69
74
|
if (config.headless !== false) {
|
|
70
75
|
defaultArgs.push('--headless=new');
|
|
71
76
|
}
|
|
72
|
-
|
|
77
|
+
// Cap caller-supplied args to prevent memory exhaustion via huge argument arrays
|
|
78
|
+
const callerArgs = (config.args ?? []).slice(0, 50);
|
|
79
|
+
const args = [...defaultArgs, ...callerArgs];
|
|
73
80
|
const child = spawn(chromePath, args, {
|
|
74
81
|
detached: true,
|
|
75
82
|
stdio: 'ignore',
|
|
@@ -122,6 +129,9 @@ export async function connectToTarget(port, targetId) {
|
|
|
122
129
|
return { client, target, sessionId };
|
|
123
130
|
}
|
|
124
131
|
export async function openUrl(client, sessionId, url) {
|
|
132
|
+
// Cap to 2 MB to prevent OOM in CDP message serializer (e.g. data: URI attacks)
|
|
133
|
+
if (url.length > 2_097_152)
|
|
134
|
+
throw new Error('URL exceeds 2 MB limit');
|
|
125
135
|
await client.send('Page.navigate', { url }, sessionId);
|
|
126
136
|
await waitForNetworkIdle(client, sessionId, 500, 30_000);
|
|
127
137
|
}
|