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.
Files changed (179) hide show
  1. package/.claude/agents/generated/channel-intelligence-director.md +87 -0
  2. package/.claude/agents/generated/chief-growth-officer.md +88 -0
  3. package/.claude/agents/generated/content-seo-strategist.md +90 -0
  4. package/.claude/agents/generated/developer-community-strategist.md +91 -0
  5. package/.claude/agents/generated/outreach-partnership-strategist.md +90 -0
  6. package/.claude/agents/generated/social-media-strategist.md +91 -0
  7. package/.claude/agents/generated/video-visual-strategist.md +90 -0
  8. package/.claude/commands/mastermind/master.md +1 -1
  9. package/.claude/helpers/auto-memory-hook.mjs +13 -4
  10. package/.claude/helpers/control-start.cjs +5 -0
  11. package/.claude/helpers/event-logger.cjs +114 -0
  12. package/.claude/helpers/handlers/adr-draft-handler.cjs +19 -5
  13. package/.claude/helpers/handlers/agent-start-handler.cjs +13 -4
  14. package/.claude/helpers/handlers/compact-handler.cjs +2 -0
  15. package/.claude/helpers/handlers/edit-handler.cjs +1 -1
  16. package/.claude/helpers/handlers/gates-handler.cjs +3 -0
  17. package/.claude/helpers/handlers/graph-status-handler.cjs +14 -8
  18. package/.claude/helpers/handlers/loops-status-handler.cjs +5 -2
  19. package/.claude/helpers/handlers/route-handler.cjs +24 -10
  20. package/.claude/helpers/handlers/session-handler.cjs +11 -4
  21. package/.claude/helpers/handlers/session-restore-handler.cjs +35 -19
  22. package/.claude/helpers/handlers/task-handler.cjs +13 -5
  23. package/.claude/helpers/hook-handler.cjs +40 -0
  24. package/.claude/helpers/intelligence.cjs +130 -53
  25. package/.claude/helpers/loop-tracker.cjs +15 -3
  26. package/.claude/helpers/memory-palace.cjs +461 -0
  27. package/.claude/helpers/memory.cjs +138 -14
  28. package/.claude/helpers/metrics-db.mjs +87 -0
  29. package/.claude/helpers/router.cjs +300 -42
  30. package/.claude/helpers/session.cjs +89 -30
  31. package/.claude/helpers/statusline.cjs +148 -4
  32. package/.claude/helpers/toggle-statusline.cjs +73 -0
  33. package/.claude/helpers/token-tracker.cjs +934 -0
  34. package/.claude/helpers/utils/micro-agents.cjs +20 -4
  35. package/.claude/helpers/utils/monograph.cjs +39 -4
  36. package/.claude/helpers/utils/telemetry.cjs +3 -3
  37. package/.claude/scheduled_tasks.lock +1 -1
  38. package/.claude/settings.json +92 -1
  39. package/.claude/skills/mastermind/_protocol.md +25 -15
  40. package/.claude/skills/mastermind/architect.md +3 -3
  41. package/.claude/skills/mastermind/autodev.md +4 -2
  42. package/.claude/skills/mastermind/idea.md +10 -0
  43. package/.claude/skills/mastermind/ops.md +3 -3
  44. package/.claude/skills/mastermind/runorg.md +153 -86
  45. package/package.json +20 -3
  46. package/packages/@monomind/cli/dist/src/agents/registry-builder.js +2 -0
  47. package/packages/@monomind/cli/dist/src/autopilot-state.js +10 -5
  48. package/packages/@monomind/cli/dist/src/benchmarks/benchmark-runner.js +13 -0
  49. package/packages/@monomind/cli/dist/src/benchmarks/metric-evaluators.js +20 -9
  50. package/packages/@monomind/cli/dist/src/browser/actions.js +10 -3
  51. package/packages/@monomind/cli/dist/src/browser/browser.js +12 -2
  52. package/packages/@monomind/cli/dist/src/browser/cdp.js +21 -3
  53. package/packages/@monomind/cli/dist/src/browser/har.js +27 -5
  54. package/packages/@monomind/cli/dist/src/commands/agent.js +11 -8
  55. package/packages/@monomind/cli/dist/src/commands/analyze.js +36 -21
  56. package/packages/@monomind/cli/dist/src/commands/autopilot.js +12 -4
  57. package/packages/@monomind/cli/dist/src/commands/benchmark.js +51 -8
  58. package/packages/@monomind/cli/dist/src/commands/browse.js +5 -2
  59. package/packages/@monomind/cli/dist/src/commands/claims.js +29 -11
  60. package/packages/@monomind/cli/dist/src/commands/cleanup.js +25 -5
  61. package/packages/@monomind/cli/dist/src/commands/config.js +15 -7
  62. package/packages/@monomind/cli/dist/src/commands/daemon.js +6 -0
  63. package/packages/@monomind/cli/dist/src/commands/deployment.js +34 -19
  64. package/packages/@monomind/cli/dist/src/commands/doctor.js +192 -23
  65. package/packages/@monomind/cli/dist/src/commands/guidance.js +15 -2
  66. package/packages/@monomind/cli/dist/src/commands/hive-mind.js +37 -14
  67. package/packages/@monomind/cli/dist/src/commands/hooks.js +42 -25
  68. package/packages/@monomind/cli/dist/src/commands/init.js +9 -4
  69. package/packages/@monomind/cli/dist/src/commands/issues.js +29 -26
  70. package/packages/@monomind/cli/dist/src/commands/mcp.js +11 -5
  71. package/packages/@monomind/cli/dist/src/commands/memory.js +10 -0
  72. package/packages/@monomind/cli/dist/src/commands/migrate.js +5 -5
  73. package/packages/@monomind/cli/dist/src/commands/monograph.js +18 -5
  74. package/packages/@monomind/cli/dist/src/commands/monovector/backup.js +8 -2
  75. package/packages/@monomind/cli/dist/src/commands/monovector/benchmark.js +20 -7
  76. package/packages/@monomind/cli/dist/src/commands/monovector/import.js +15 -0
  77. package/packages/@monomind/cli/dist/src/commands/monovector/migrate.js +4 -1
  78. package/packages/@monomind/cli/dist/src/commands/monovector/optimize.js +11 -0
  79. package/packages/@monomind/cli/dist/src/commands/monovector/setup.js +11 -1
  80. package/packages/@monomind/cli/dist/src/commands/neural.js +1 -1
  81. package/packages/@monomind/cli/dist/src/commands/performance.js +20 -7
  82. package/packages/@monomind/cli/dist/src/commands/platforms.js +90 -8
  83. package/packages/@monomind/cli/dist/src/commands/plugins.js +12 -5
  84. package/packages/@monomind/cli/dist/src/commands/process.js +33 -10
  85. package/packages/@monomind/cli/dist/src/commands/progress.js +5 -3
  86. package/packages/@monomind/cli/dist/src/commands/providers.js +5 -5
  87. package/packages/@monomind/cli/dist/src/commands/replay.js +8 -2
  88. package/packages/@monomind/cli/dist/src/commands/route.js +27 -7
  89. package/packages/@monomind/cli/dist/src/commands/security.js +4 -0
  90. package/packages/@monomind/cli/dist/src/commands/session.js +12 -1
  91. package/packages/@monomind/cli/dist/src/commands/start.js +11 -4
  92. package/packages/@monomind/cli/dist/src/commands/status.js +7 -4
  93. package/packages/@monomind/cli/dist/src/commands/swarm.js +27 -13
  94. package/packages/@monomind/cli/dist/src/commands/task.js +26 -11
  95. package/packages/@monomind/cli/dist/src/commands/tokens.js +7 -2
  96. package/packages/@monomind/cli/dist/src/commands/transfer-store.js +36 -22
  97. package/packages/@monomind/cli/dist/src/commands/update.js +15 -3
  98. package/packages/@monomind/cli/dist/src/commands/workflow.js +39 -6
  99. package/packages/@monomind/cli/dist/src/consensus/audit-writer.js +18 -7
  100. package/packages/@monomind/cli/dist/src/consensus/vote-signer.js +25 -8
  101. package/packages/@monomind/cli/dist/src/index.js +7 -3
  102. package/packages/@monomind/cli/dist/src/init/executor.js +14 -11
  103. package/packages/@monomind/cli/dist/src/init/shared-instructions-generator.js +20 -4
  104. package/packages/@monomind/cli/dist/src/init/statusline-generator.js +36 -15
  105. package/packages/@monomind/cli/dist/src/mcp-tools/a2a-tools.js +98 -13
  106. package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +16 -3
  107. package/packages/@monomind/cli/dist/src/mcp-tools/analyze-tools.js +80 -17
  108. package/packages/@monomind/cli/dist/src/mcp-tools/browser-tools.js +84 -22
  109. package/packages/@monomind/cli/dist/src/mcp-tools/claims-tools.js +35 -7
  110. package/packages/@monomind/cli/dist/src/mcp-tools/config-tools.js +82 -17
  111. package/packages/@monomind/cli/dist/src/mcp-tools/coordination-tools.js +37 -4
  112. package/packages/@monomind/cli/dist/src/mcp-tools/daa-tools.js +49 -7
  113. package/packages/@monomind/cli/dist/src/mcp-tools/embeddings-tools.js +45 -18
  114. package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +75 -25
  115. package/packages/@monomind/cli/dist/src/mcp-tools/guidance-tools.js +32 -10
  116. package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +91 -20
  117. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-tools.js +188 -29
  118. package/packages/@monomind/cli/dist/src/mcp-tools/memory-tools.js +25 -7
  119. package/packages/@monomind/cli/dist/src/mcp-tools/monograph-compat.js +11 -2
  120. package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +476 -62
  121. package/packages/@monomind/cli/dist/src/mcp-tools/neural-tools.js +44 -9
  122. package/packages/@monomind/cli/dist/src/mcp-tools/performance-tools.js +45 -10
  123. package/packages/@monomind/cli/dist/src/mcp-tools/progress-tools.js +7 -4
  124. package/packages/@monomind/cli/dist/src/mcp-tools/request-tracker.js +15 -1
  125. package/packages/@monomind/cli/dist/src/mcp-tools/security-tools.js +61 -9
  126. package/packages/@monomind/cli/dist/src/mcp-tools/session-tools.js +45 -14
  127. package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +15 -3
  128. package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +14 -7
  129. package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.js +52 -10
  130. package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.js +40 -6
  131. package/packages/@monomind/cli/dist/src/mcp-tools/transfer-tools.js +37 -4
  132. package/packages/@monomind/cli/dist/src/mcp-tools/workflow-tools.js +29 -6
  133. package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.js +26 -10
  134. package/packages/@monomind/cli/dist/src/memory/intelligence.js +80 -19
  135. package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +21 -2
  136. package/packages/@monomind/cli/dist/src/memory/memory-initializer.js +67 -3
  137. package/packages/@monomind/cli/dist/src/memory/sona-optimizer.js +14 -4
  138. package/packages/@monomind/cli/dist/src/monovector/command-outcomes.js +43 -7
  139. package/packages/@monomind/cli/dist/src/monovector/coverage-router.js +8 -4
  140. package/packages/@monomind/cli/dist/src/monovector/coverage-tools.js +6 -3
  141. package/packages/@monomind/cli/dist/src/monovector/diff-classifier.js +13 -0
  142. package/packages/@monomind/cli/dist/src/monovector/route-outcomes.d.ts +2 -1
  143. package/packages/@monomind/cli/dist/src/monovector/route-outcomes.js +46 -4
  144. package/packages/@monomind/cli/dist/src/plugins/manager.js +8 -3
  145. package/packages/@monomind/cli/dist/src/plugins/store/discovery.js +46 -2
  146. package/packages/@monomind/cli/dist/src/plugins/store/search.js +5 -4
  147. package/packages/@monomind/cli/dist/src/production/circuit-breaker.js +17 -3
  148. package/packages/@monomind/cli/dist/src/production/error-handler.js +3 -0
  149. package/packages/@monomind/cli/dist/src/production/monitoring.js +20 -3
  150. package/packages/@monomind/cli/dist/src/production/rate-limiter.js +13 -4
  151. package/packages/@monomind/cli/dist/src/production/retry.js +17 -9
  152. package/packages/@monomind/cli/dist/src/routing/embed-worker.js +6 -2
  153. package/packages/@monomind/cli/dist/src/routing/embedder.js +0 -0
  154. package/packages/@monomind/cli/dist/src/routing/llm-caller.js +13 -2
  155. package/packages/@monomind/cli/dist/src/routing/route-layer-factory.js +18 -3
  156. package/packages/@monomind/cli/dist/src/services/claim-service.d.ts +1 -0
  157. package/packages/@monomind/cli/dist/src/services/claim-service.js +8 -0
  158. package/packages/@monomind/cli/dist/src/services/config-file-manager.js +14 -2
  159. package/packages/@monomind/cli/dist/src/services/headless-worker-executor.js +18 -2
  160. package/packages/@monomind/cli/dist/src/services/worker-daemon.js +348 -17
  161. package/packages/@monomind/cli/dist/src/transfer/anonymization/index.d.ts +0 -3
  162. package/packages/@monomind/cli/dist/src/transfer/anonymization/index.js +16 -1
  163. package/packages/@monomind/cli/dist/src/transfer/export.js +8 -0
  164. package/packages/@monomind/cli/dist/src/transfer/ipfs/upload.js +33 -3
  165. package/packages/@monomind/cli/dist/src/transfer/serialization/cfp.js +8 -2
  166. package/packages/@monomind/cli/dist/src/transfer/storage/gcs.js +37 -3
  167. package/packages/@monomind/cli/dist/src/transfer/store/discovery.js +45 -3
  168. package/packages/@monomind/cli/dist/src/transfer/store/download.js +5 -0
  169. package/packages/@monomind/cli/dist/src/transfer/store/publish.js +13 -1
  170. package/packages/@monomind/cli/dist/src/transfer/store/registry.d.ts +8 -0
  171. package/packages/@monomind/cli/dist/src/transfer/store/registry.js +30 -5
  172. package/packages/@monomind/cli/dist/src/transfer/store/search.js +20 -5
  173. package/packages/@monomind/cli/dist/src/update/checker.js +59 -7
  174. package/packages/@monomind/cli/dist/src/update/executor.js +50 -3
  175. package/packages/@monomind/cli/dist/src/update/index.js +18 -1
  176. package/packages/@monomind/cli/dist/src/update/rate-limiter.d.ts +6 -0
  177. package/packages/@monomind/cli/dist/src/update/rate-limiter.js +79 -7
  178. package/packages/@monomind/cli/dist/src/update/validator.js +52 -1
  179. 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, extract them as real shell variables so downstream bash blocks can reference `$orgName`, `$sessionId`, etc.:
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=$(date -u +%Y-%m-%dT%H:%M:%SZ)
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
- ```bash
281
- [ -z "$board_id" ] && { echo "ERROR: org config missing board_id re-run /mastermind:createorg --name ${orgName} to rebuild the org."; exit 1; }
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
- **Create stop-file and state directories:**
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
- **Initialize org state file** (tracks per-agent status, heartbeat timestamps, token usage):
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
- --arg org "$orgName" \
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
- '.run_id = $runId | .started_at = (now|todate)' \
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
- **Remove any stale stop file:**
311
- ```bash
312
- rm -f "$stopFile"
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
- **Emit `org:start` to dashboard:**
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
- REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
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 "$sessionId" \
323
- --arg org "$orgName" \
324
- --arg goal "$goal" \
325
- --arg proj "$(pwd)" \
326
- '{type:"org:start",session:$session,org:$org,goal:$goal,project:$proj,ts:(now*1000|floor)}')" || true
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 derived in Step 2, spawn the boss agent via Task tool:
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=${board_id}
355
- Columns: Todo=${todo_col} Doing=${doing_col} Done=${done_col}
356
- - Memory namespace: ${memNs} (use: npx monomind@latest memory store/search --namespace ${memNs})
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 event, clean up with "rm -f ${stopFile}", then exit.
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 (see above). Exit if found.
438
- 2. List unclaimed Todo cards: monotask card list ${board_id} --col ${todo_col} --json | jq '[.[] | select(.labels | index("claimed") | not)]'
439
- 3. For each Todo card without a "claimed" label, assign to the appropriate role based on its "role:<id>" label. Skip cards whose prerequisites (monotask card prerequisite list) are not yet in the Done column.
440
- 4. Spawn the assigned role agent via Task tool (run_in_background: true) with:
441
- - Full role briefing (title, responsibilities, who they report to, memory namespace)
442
- - The specific card title and card_id to work on
443
- - Move the card to Doing before spawning: monotask card move ${board_id} $CARD_ID ${doing_col}
444
- - Add label "claimed" to the card
445
- - Report output via: npx monomind@latest memory store --key "${memNs}:report:<card_id>" --namespace "${memNs}"
446
- - When complete, move card to Done: monotask card move ${board_id} $CARD_ID ${done_col}
447
- - Emit org:agent:online event to dashboard before starting (use curl, see below)
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
- 6. Synthesize reports, make decisions, create new cards in Todo column based on progress toward goal
450
- 7. Write a run-completion entry to the activity log (used by the health dashboard for 7-day success rate):
451
- activityFile=".monomind/orgs/${orgName}-activity.jsonl"
452
- # Count Todo cards not yet claimed as the pending_tasks metric
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
- echo "{\"type\":\"run:complete\",\"org\":\"${orgName}\",\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"pending\":${pending_count:-0}}" >> "$activityFile"
455
- (On error/exception, write: {"type":"run:error","ts":"...", "org":"..."})
456
- 8. Emit org:checkpoint event with progress summary
457
- 9. Wait ${checkpointMin} minutes, then loop back to step 1
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
- DASHBOARD EVENTS emit via curl (NOT WebFetch WebFetch does not support POST):
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.11.14",
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.2.0"
59
+ "@monoes/monograph": "^1.1.0"
60
60
  },
61
61
  "overrides": {
62
- "hono": ">=4.11.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 data = safeJsonParse(fs.readFileSync(path.join(teamDir, file), 'utf-8'));
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
- const found = output.includes(config.expected);
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 ? config.expected : null,
14
- expected: config.expected,
15
+ actual: found ? expected : null,
16
+ expected,
15
17
  message: found
16
- ? `Output contains expected string "${config.expected}"`
17
- : `Output missing expected string "${config.expected}"`,
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
- const found = config.forbidden.filter((word) => lowerOutput.includes(word.toLowerCase()));
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 ? found : null,
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: ${found.join(', ')}`,
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(output);
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
- const clickCount = options.clickCount ?? 1;
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
- await client.send('Input.insertText', { text }, sessionId);
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
- await evaluateJs(client, sessionId, `navigator.clipboard.writeText(${JSON.stringify(text)})`);
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 port = config.port ?? DEFAULT_PORT;
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
- const args = [...defaultArgs, ...(config.args ?? [])];
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
  }