@monoes/monomindcli 1.11.13 → 1.12.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 (427) 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 +13 -6
  20. package/.claude/helpers/handlers/session-handler.cjs +11 -4
  21. package/.claude/helpers/handlers/session-restore-handler.cjs +21 -11
  22. package/.claude/helpers/handlers/task-handler.cjs +13 -5
  23. package/.claude/helpers/intelligence.cjs +7 -2
  24. package/.claude/helpers/loop-tracker.cjs +15 -3
  25. package/.claude/helpers/memory.cjs +6 -1
  26. package/.claude/helpers/router.cjs +5 -2
  27. package/.claude/helpers/session.cjs +2 -0
  28. package/.claude/helpers/statusline.cjs +10 -2
  29. package/.claude/helpers/utils/micro-agents.cjs +20 -4
  30. package/.claude/skills/mastermind/_protocol.md +25 -15
  31. package/.claude/skills/mastermind/architect.md +3 -3
  32. package/.claude/skills/mastermind/autodev.md +4 -2
  33. package/.claude/skills/mastermind/idea.md +10 -0
  34. package/.claude/skills/mastermind/ops.md +3 -3
  35. package/.claude/skills/mastermind/runorg.md +153 -86
  36. package/dist/src/agents/registry-builder.d.ts.map +1 -1
  37. package/dist/src/agents/registry-builder.js +2 -0
  38. package/dist/src/agents/registry-builder.js.map +1 -1
  39. package/dist/src/autopilot-state.d.ts.map +1 -1
  40. package/dist/src/autopilot-state.js +10 -5
  41. package/dist/src/autopilot-state.js.map +1 -1
  42. package/dist/src/benchmarks/benchmark-runner.d.ts.map +1 -1
  43. package/dist/src/benchmarks/benchmark-runner.js +13 -0
  44. package/dist/src/benchmarks/benchmark-runner.js.map +1 -1
  45. package/dist/src/benchmarks/metric-evaluators.d.ts.map +1 -1
  46. package/dist/src/benchmarks/metric-evaluators.js +20 -9
  47. package/dist/src/benchmarks/metric-evaluators.js.map +1 -1
  48. package/dist/src/browser/actions.d.ts.map +1 -1
  49. package/dist/src/browser/actions.js +10 -3
  50. package/dist/src/browser/actions.js.map +1 -1
  51. package/dist/src/browser/browser.d.ts.map +1 -1
  52. package/dist/src/browser/browser.js +12 -2
  53. package/dist/src/browser/browser.js.map +1 -1
  54. package/dist/src/browser/cdp.d.ts.map +1 -1
  55. package/dist/src/browser/cdp.js +21 -3
  56. package/dist/src/browser/cdp.js.map +1 -1
  57. package/dist/src/browser/har.d.ts.map +1 -1
  58. package/dist/src/browser/har.js +27 -5
  59. package/dist/src/browser/har.js.map +1 -1
  60. package/dist/src/commands/agent.d.ts.map +1 -1
  61. package/dist/src/commands/agent.js +11 -8
  62. package/dist/src/commands/agent.js.map +1 -1
  63. package/dist/src/commands/analyze.d.ts.map +1 -1
  64. package/dist/src/commands/analyze.js +36 -21
  65. package/dist/src/commands/analyze.js.map +1 -1
  66. package/dist/src/commands/autopilot.d.ts.map +1 -1
  67. package/dist/src/commands/autopilot.js +12 -4
  68. package/dist/src/commands/autopilot.js.map +1 -1
  69. package/dist/src/commands/benchmark.d.ts.map +1 -1
  70. package/dist/src/commands/benchmark.js +51 -8
  71. package/dist/src/commands/benchmark.js.map +1 -1
  72. package/dist/src/commands/browse.d.ts.map +1 -1
  73. package/dist/src/commands/browse.js +5 -2
  74. package/dist/src/commands/browse.js.map +1 -1
  75. package/dist/src/commands/claims.d.ts.map +1 -1
  76. package/dist/src/commands/claims.js +29 -11
  77. package/dist/src/commands/claims.js.map +1 -1
  78. package/dist/src/commands/cleanup.d.ts.map +1 -1
  79. package/dist/src/commands/cleanup.js +25 -5
  80. package/dist/src/commands/cleanup.js.map +1 -1
  81. package/dist/src/commands/config.d.ts.map +1 -1
  82. package/dist/src/commands/config.js +15 -7
  83. package/dist/src/commands/config.js.map +1 -1
  84. package/dist/src/commands/daemon.d.ts.map +1 -1
  85. package/dist/src/commands/daemon.js +6 -0
  86. package/dist/src/commands/daemon.js.map +1 -1
  87. package/dist/src/commands/deployment.d.ts.map +1 -1
  88. package/dist/src/commands/deployment.js +34 -19
  89. package/dist/src/commands/deployment.js.map +1 -1
  90. package/dist/src/commands/doctor.d.ts.map +1 -1
  91. package/dist/src/commands/doctor.js +97 -20
  92. package/dist/src/commands/doctor.js.map +1 -1
  93. package/dist/src/commands/guidance.d.ts.map +1 -1
  94. package/dist/src/commands/guidance.js +15 -2
  95. package/dist/src/commands/guidance.js.map +1 -1
  96. package/dist/src/commands/hive-mind.d.ts.map +1 -1
  97. package/dist/src/commands/hive-mind.js +37 -14
  98. package/dist/src/commands/hive-mind.js.map +1 -1
  99. package/dist/src/commands/hooks.d.ts.map +1 -1
  100. package/dist/src/commands/hooks.js +42 -25
  101. package/dist/src/commands/hooks.js.map +1 -1
  102. package/dist/src/commands/init.d.ts.map +1 -1
  103. package/dist/src/commands/init.js +9 -4
  104. package/dist/src/commands/init.js.map +1 -1
  105. package/dist/src/commands/issues.d.ts.map +1 -1
  106. package/dist/src/commands/issues.js +29 -26
  107. package/dist/src/commands/issues.js.map +1 -1
  108. package/dist/src/commands/mcp.d.ts.map +1 -1
  109. package/dist/src/commands/mcp.js +11 -5
  110. package/dist/src/commands/mcp.js.map +1 -1
  111. package/dist/src/commands/memory.d.ts.map +1 -1
  112. package/dist/src/commands/memory.js +10 -0
  113. package/dist/src/commands/memory.js.map +1 -1
  114. package/dist/src/commands/migrate.js +5 -5
  115. package/dist/src/commands/migrate.js.map +1 -1
  116. package/dist/src/commands/monograph.d.ts.map +1 -1
  117. package/dist/src/commands/monograph.js +18 -5
  118. package/dist/src/commands/monograph.js.map +1 -1
  119. package/dist/src/commands/monovector/backup.d.ts.map +1 -1
  120. package/dist/src/commands/monovector/backup.js +8 -2
  121. package/dist/src/commands/monovector/backup.js.map +1 -1
  122. package/dist/src/commands/monovector/benchmark.d.ts.map +1 -1
  123. package/dist/src/commands/monovector/benchmark.js +20 -7
  124. package/dist/src/commands/monovector/benchmark.js.map +1 -1
  125. package/dist/src/commands/monovector/import.d.ts.map +1 -1
  126. package/dist/src/commands/monovector/import.js +15 -0
  127. package/dist/src/commands/monovector/import.js.map +1 -1
  128. package/dist/src/commands/monovector/migrate.d.ts.map +1 -1
  129. package/dist/src/commands/monovector/migrate.js +4 -1
  130. package/dist/src/commands/monovector/migrate.js.map +1 -1
  131. package/dist/src/commands/monovector/optimize.d.ts.map +1 -1
  132. package/dist/src/commands/monovector/optimize.js +11 -0
  133. package/dist/src/commands/monovector/optimize.js.map +1 -1
  134. package/dist/src/commands/monovector/setup.d.ts.map +1 -1
  135. package/dist/src/commands/monovector/setup.js +11 -1
  136. package/dist/src/commands/monovector/setup.js.map +1 -1
  137. package/dist/src/commands/neural.js +1 -1
  138. package/dist/src/commands/neural.js.map +1 -1
  139. package/dist/src/commands/performance.d.ts.map +1 -1
  140. package/dist/src/commands/performance.js +20 -7
  141. package/dist/src/commands/performance.js.map +1 -1
  142. package/dist/src/commands/platforms.d.ts.map +1 -1
  143. package/dist/src/commands/platforms.js +90 -8
  144. package/dist/src/commands/platforms.js.map +1 -1
  145. package/dist/src/commands/plugins.d.ts.map +1 -1
  146. package/dist/src/commands/plugins.js +12 -5
  147. package/dist/src/commands/plugins.js.map +1 -1
  148. package/dist/src/commands/process.d.ts.map +1 -1
  149. package/dist/src/commands/process.js +33 -10
  150. package/dist/src/commands/process.js.map +1 -1
  151. package/dist/src/commands/progress.d.ts.map +1 -1
  152. package/dist/src/commands/progress.js +5 -3
  153. package/dist/src/commands/progress.js.map +1 -1
  154. package/dist/src/commands/providers.js +5 -5
  155. package/dist/src/commands/providers.js.map +1 -1
  156. package/dist/src/commands/replay.d.ts.map +1 -1
  157. package/dist/src/commands/replay.js +8 -2
  158. package/dist/src/commands/replay.js.map +1 -1
  159. package/dist/src/commands/route.d.ts.map +1 -1
  160. package/dist/src/commands/route.js +27 -7
  161. package/dist/src/commands/route.js.map +1 -1
  162. package/dist/src/commands/security.d.ts.map +1 -1
  163. package/dist/src/commands/security.js +4 -0
  164. package/dist/src/commands/security.js.map +1 -1
  165. package/dist/src/commands/session.d.ts.map +1 -1
  166. package/dist/src/commands/session.js +12 -1
  167. package/dist/src/commands/session.js.map +1 -1
  168. package/dist/src/commands/start.d.ts.map +1 -1
  169. package/dist/src/commands/start.js +11 -4
  170. package/dist/src/commands/start.js.map +1 -1
  171. package/dist/src/commands/status.d.ts.map +1 -1
  172. package/dist/src/commands/status.js +7 -4
  173. package/dist/src/commands/status.js.map +1 -1
  174. package/dist/src/commands/swarm.d.ts.map +1 -1
  175. package/dist/src/commands/swarm.js +27 -13
  176. package/dist/src/commands/swarm.js.map +1 -1
  177. package/dist/src/commands/task.d.ts.map +1 -1
  178. package/dist/src/commands/task.js +26 -11
  179. package/dist/src/commands/task.js.map +1 -1
  180. package/dist/src/commands/tokens.d.ts.map +1 -1
  181. package/dist/src/commands/tokens.js +7 -2
  182. package/dist/src/commands/tokens.js.map +1 -1
  183. package/dist/src/commands/transfer-store.d.ts.map +1 -1
  184. package/dist/src/commands/transfer-store.js +36 -22
  185. package/dist/src/commands/transfer-store.js.map +1 -1
  186. package/dist/src/commands/update.d.ts.map +1 -1
  187. package/dist/src/commands/update.js +15 -3
  188. package/dist/src/commands/update.js.map +1 -1
  189. package/dist/src/commands/workflow.d.ts.map +1 -1
  190. package/dist/src/commands/workflow.js +39 -6
  191. package/dist/src/commands/workflow.js.map +1 -1
  192. package/dist/src/consensus/audit-writer.d.ts.map +1 -1
  193. package/dist/src/consensus/audit-writer.js +18 -7
  194. package/dist/src/consensus/audit-writer.js.map +1 -1
  195. package/dist/src/consensus/vote-signer.d.ts.map +1 -1
  196. package/dist/src/consensus/vote-signer.js +25 -8
  197. package/dist/src/consensus/vote-signer.js.map +1 -1
  198. package/dist/src/index.d.ts.map +1 -1
  199. package/dist/src/index.js +7 -3
  200. package/dist/src/index.js.map +1 -1
  201. package/dist/src/init/executor.d.ts.map +1 -1
  202. package/dist/src/init/executor.js +14 -11
  203. package/dist/src/init/executor.js.map +1 -1
  204. package/dist/src/init/shared-instructions-generator.d.ts.map +1 -1
  205. package/dist/src/init/shared-instructions-generator.js +20 -4
  206. package/dist/src/init/shared-instructions-generator.js.map +1 -1
  207. package/dist/src/init/statusline-generator.d.ts.map +1 -1
  208. package/dist/src/init/statusline-generator.js +36 -15
  209. package/dist/src/init/statusline-generator.js.map +1 -1
  210. package/dist/src/mcp-tools/a2a-tools.d.ts.map +1 -1
  211. package/dist/src/mcp-tools/a2a-tools.js +98 -13
  212. package/dist/src/mcp-tools/a2a-tools.js.map +1 -1
  213. package/dist/src/mcp-tools/agent-tools.d.ts.map +1 -1
  214. package/dist/src/mcp-tools/agent-tools.js +16 -3
  215. package/dist/src/mcp-tools/agent-tools.js.map +1 -1
  216. package/dist/src/mcp-tools/analyze-tools.d.ts.map +1 -1
  217. package/dist/src/mcp-tools/analyze-tools.js +80 -17
  218. package/dist/src/mcp-tools/analyze-tools.js.map +1 -1
  219. package/dist/src/mcp-tools/browser-tools.d.ts.map +1 -1
  220. package/dist/src/mcp-tools/browser-tools.js +84 -22
  221. package/dist/src/mcp-tools/browser-tools.js.map +1 -1
  222. package/dist/src/mcp-tools/claims-tools.d.ts.map +1 -1
  223. package/dist/src/mcp-tools/claims-tools.js +35 -7
  224. package/dist/src/mcp-tools/claims-tools.js.map +1 -1
  225. package/dist/src/mcp-tools/config-tools.d.ts.map +1 -1
  226. package/dist/src/mcp-tools/config-tools.js +82 -17
  227. package/dist/src/mcp-tools/config-tools.js.map +1 -1
  228. package/dist/src/mcp-tools/coordination-tools.d.ts.map +1 -1
  229. package/dist/src/mcp-tools/coordination-tools.js +37 -4
  230. package/dist/src/mcp-tools/coordination-tools.js.map +1 -1
  231. package/dist/src/mcp-tools/daa-tools.d.ts.map +1 -1
  232. package/dist/src/mcp-tools/daa-tools.js +49 -7
  233. package/dist/src/mcp-tools/daa-tools.js.map +1 -1
  234. package/dist/src/mcp-tools/embeddings-tools.d.ts.map +1 -1
  235. package/dist/src/mcp-tools/embeddings-tools.js +45 -18
  236. package/dist/src/mcp-tools/embeddings-tools.js.map +1 -1
  237. package/dist/src/mcp-tools/github-tools.d.ts.map +1 -1
  238. package/dist/src/mcp-tools/github-tools.js +75 -25
  239. package/dist/src/mcp-tools/github-tools.js.map +1 -1
  240. package/dist/src/mcp-tools/guidance-tools.d.ts.map +1 -1
  241. package/dist/src/mcp-tools/guidance-tools.js +32 -10
  242. package/dist/src/mcp-tools/guidance-tools.js.map +1 -1
  243. package/dist/src/mcp-tools/hive-mind-tools.d.ts.map +1 -1
  244. package/dist/src/mcp-tools/hive-mind-tools.js +91 -20
  245. package/dist/src/mcp-tools/hive-mind-tools.js.map +1 -1
  246. package/dist/src/mcp-tools/hooks-tools.d.ts.map +1 -1
  247. package/dist/src/mcp-tools/hooks-tools.js +188 -29
  248. package/dist/src/mcp-tools/hooks-tools.js.map +1 -1
  249. package/dist/src/mcp-tools/memory-tools.d.ts.map +1 -1
  250. package/dist/src/mcp-tools/memory-tools.js +25 -7
  251. package/dist/src/mcp-tools/memory-tools.js.map +1 -1
  252. package/dist/src/mcp-tools/monograph-compat.d.ts.map +1 -1
  253. package/dist/src/mcp-tools/monograph-compat.js +11 -2
  254. package/dist/src/mcp-tools/monograph-compat.js.map +1 -1
  255. package/dist/src/mcp-tools/monograph-tools.d.ts.map +1 -1
  256. package/dist/src/mcp-tools/monograph-tools.js +148 -26
  257. package/dist/src/mcp-tools/monograph-tools.js.map +1 -1
  258. package/dist/src/mcp-tools/neural-tools.d.ts.map +1 -1
  259. package/dist/src/mcp-tools/neural-tools.js +44 -9
  260. package/dist/src/mcp-tools/neural-tools.js.map +1 -1
  261. package/dist/src/mcp-tools/performance-tools.d.ts.map +1 -1
  262. package/dist/src/mcp-tools/performance-tools.js +45 -10
  263. package/dist/src/mcp-tools/performance-tools.js.map +1 -1
  264. package/dist/src/mcp-tools/progress-tools.d.ts.map +1 -1
  265. package/dist/src/mcp-tools/progress-tools.js +7 -4
  266. package/dist/src/mcp-tools/progress-tools.js.map +1 -1
  267. package/dist/src/mcp-tools/request-tracker.d.ts.map +1 -1
  268. package/dist/src/mcp-tools/request-tracker.js +15 -1
  269. package/dist/src/mcp-tools/request-tracker.js.map +1 -1
  270. package/dist/src/mcp-tools/security-tools.d.ts.map +1 -1
  271. package/dist/src/mcp-tools/security-tools.js +61 -9
  272. package/dist/src/mcp-tools/security-tools.js.map +1 -1
  273. package/dist/src/mcp-tools/session-tools.d.ts.map +1 -1
  274. package/dist/src/mcp-tools/session-tools.js +45 -14
  275. package/dist/src/mcp-tools/session-tools.js.map +1 -1
  276. package/dist/src/mcp-tools/swarm-tools.d.ts.map +1 -1
  277. package/dist/src/mcp-tools/swarm-tools.js +15 -3
  278. package/dist/src/mcp-tools/swarm-tools.js.map +1 -1
  279. package/dist/src/mcp-tools/system-tools.d.ts.map +1 -1
  280. package/dist/src/mcp-tools/system-tools.js +14 -7
  281. package/dist/src/mcp-tools/system-tools.js.map +1 -1
  282. package/dist/src/mcp-tools/task-tools.d.ts.map +1 -1
  283. package/dist/src/mcp-tools/task-tools.js +52 -10
  284. package/dist/src/mcp-tools/task-tools.js.map +1 -1
  285. package/dist/src/mcp-tools/terminal-tools.d.ts.map +1 -1
  286. package/dist/src/mcp-tools/terminal-tools.js +40 -6
  287. package/dist/src/mcp-tools/terminal-tools.js.map +1 -1
  288. package/dist/src/mcp-tools/transfer-tools.d.ts.map +1 -1
  289. package/dist/src/mcp-tools/transfer-tools.js +37 -4
  290. package/dist/src/mcp-tools/transfer-tools.js.map +1 -1
  291. package/dist/src/mcp-tools/workflow-tools.d.ts.map +1 -1
  292. package/dist/src/mcp-tools/workflow-tools.js +29 -6
  293. package/dist/src/mcp-tools/workflow-tools.js.map +1 -1
  294. package/dist/src/memory/ewc-consolidation.d.ts.map +1 -1
  295. package/dist/src/memory/ewc-consolidation.js +26 -10
  296. package/dist/src/memory/ewc-consolidation.js.map +1 -1
  297. package/dist/src/memory/intelligence.d.ts.map +1 -1
  298. package/dist/src/memory/intelligence.js +80 -19
  299. package/dist/src/memory/intelligence.js.map +1 -1
  300. package/dist/src/memory/memory-bridge.d.ts.map +1 -1
  301. package/dist/src/memory/memory-bridge.js +21 -2
  302. package/dist/src/memory/memory-bridge.js.map +1 -1
  303. package/dist/src/memory/memory-initializer.d.ts.map +1 -1
  304. package/dist/src/memory/memory-initializer.js +67 -3
  305. package/dist/src/memory/memory-initializer.js.map +1 -1
  306. package/dist/src/memory/sona-optimizer.d.ts.map +1 -1
  307. package/dist/src/memory/sona-optimizer.js +14 -4
  308. package/dist/src/memory/sona-optimizer.js.map +1 -1
  309. package/dist/src/monovector/command-outcomes.d.ts.map +1 -1
  310. package/dist/src/monovector/command-outcomes.js +43 -7
  311. package/dist/src/monovector/command-outcomes.js.map +1 -1
  312. package/dist/src/monovector/coverage-router.d.ts.map +1 -1
  313. package/dist/src/monovector/coverage-router.js +8 -4
  314. package/dist/src/monovector/coverage-router.js.map +1 -1
  315. package/dist/src/monovector/coverage-tools.d.ts.map +1 -1
  316. package/dist/src/monovector/coverage-tools.js +6 -3
  317. package/dist/src/monovector/coverage-tools.js.map +1 -1
  318. package/dist/src/monovector/diff-classifier.d.ts.map +1 -1
  319. package/dist/src/monovector/diff-classifier.js +13 -0
  320. package/dist/src/monovector/diff-classifier.js.map +1 -1
  321. package/dist/src/monovector/route-outcomes.d.ts +2 -1
  322. package/dist/src/monovector/route-outcomes.d.ts.map +1 -1
  323. package/dist/src/monovector/route-outcomes.js +46 -4
  324. package/dist/src/monovector/route-outcomes.js.map +1 -1
  325. package/dist/src/plugins/manager.d.ts.map +1 -1
  326. package/dist/src/plugins/manager.js +8 -3
  327. package/dist/src/plugins/manager.js.map +1 -1
  328. package/dist/src/plugins/store/discovery.d.ts.map +1 -1
  329. package/dist/src/plugins/store/discovery.js +46 -2
  330. package/dist/src/plugins/store/discovery.js.map +1 -1
  331. package/dist/src/plugins/store/search.d.ts.map +1 -1
  332. package/dist/src/plugins/store/search.js +5 -4
  333. package/dist/src/plugins/store/search.js.map +1 -1
  334. package/dist/src/production/circuit-breaker.d.ts.map +1 -1
  335. package/dist/src/production/circuit-breaker.js +17 -3
  336. package/dist/src/production/circuit-breaker.js.map +1 -1
  337. package/dist/src/production/error-handler.d.ts.map +1 -1
  338. package/dist/src/production/error-handler.js +3 -0
  339. package/dist/src/production/error-handler.js.map +1 -1
  340. package/dist/src/production/monitoring.d.ts.map +1 -1
  341. package/dist/src/production/monitoring.js +20 -3
  342. package/dist/src/production/monitoring.js.map +1 -1
  343. package/dist/src/production/rate-limiter.d.ts.map +1 -1
  344. package/dist/src/production/rate-limiter.js +13 -4
  345. package/dist/src/production/rate-limiter.js.map +1 -1
  346. package/dist/src/production/retry.d.ts.map +1 -1
  347. package/dist/src/production/retry.js +17 -9
  348. package/dist/src/production/retry.js.map +1 -1
  349. package/dist/src/routing/embed-worker.js +6 -2
  350. package/dist/src/routing/embed-worker.js.map +1 -1
  351. package/dist/src/routing/embedder.d.ts.map +1 -1
  352. package/dist/src/routing/embedder.js +0 -0
  353. package/dist/src/routing/embedder.js.map +1 -1
  354. package/dist/src/routing/llm-caller.d.ts.map +1 -1
  355. package/dist/src/routing/llm-caller.js +13 -2
  356. package/dist/src/routing/llm-caller.js.map +1 -1
  357. package/dist/src/routing/route-layer-factory.d.ts.map +1 -1
  358. package/dist/src/routing/route-layer-factory.js +18 -3
  359. package/dist/src/routing/route-layer-factory.js.map +1 -1
  360. package/dist/src/services/claim-service.d.ts +1 -0
  361. package/dist/src/services/claim-service.d.ts.map +1 -1
  362. package/dist/src/services/claim-service.js +8 -0
  363. package/dist/src/services/claim-service.js.map +1 -1
  364. package/dist/src/services/config-file-manager.d.ts.map +1 -1
  365. package/dist/src/services/config-file-manager.js +14 -2
  366. package/dist/src/services/config-file-manager.js.map +1 -1
  367. package/dist/src/services/headless-worker-executor.d.ts.map +1 -1
  368. package/dist/src/services/headless-worker-executor.js +18 -2
  369. package/dist/src/services/headless-worker-executor.js.map +1 -1
  370. package/dist/src/services/worker-daemon.d.ts.map +1 -1
  371. package/dist/src/services/worker-daemon.js +53 -12
  372. package/dist/src/services/worker-daemon.js.map +1 -1
  373. package/dist/src/transfer/anonymization/index.d.ts +0 -3
  374. package/dist/src/transfer/anonymization/index.d.ts.map +1 -1
  375. package/dist/src/transfer/anonymization/index.js +16 -1
  376. package/dist/src/transfer/anonymization/index.js.map +1 -1
  377. package/dist/src/transfer/export.d.ts.map +1 -1
  378. package/dist/src/transfer/export.js +8 -0
  379. package/dist/src/transfer/export.js.map +1 -1
  380. package/dist/src/transfer/ipfs/upload.d.ts.map +1 -1
  381. package/dist/src/transfer/ipfs/upload.js +33 -3
  382. package/dist/src/transfer/ipfs/upload.js.map +1 -1
  383. package/dist/src/transfer/serialization/cfp.d.ts.map +1 -1
  384. package/dist/src/transfer/serialization/cfp.js +9 -3
  385. package/dist/src/transfer/serialization/cfp.js.map +1 -1
  386. package/dist/src/transfer/storage/gcs.d.ts.map +1 -1
  387. package/dist/src/transfer/storage/gcs.js +37 -3
  388. package/dist/src/transfer/storage/gcs.js.map +1 -1
  389. package/dist/src/transfer/store/discovery.d.ts.map +1 -1
  390. package/dist/src/transfer/store/discovery.js +45 -3
  391. package/dist/src/transfer/store/discovery.js.map +1 -1
  392. package/dist/src/transfer/store/download.d.ts.map +1 -1
  393. package/dist/src/transfer/store/download.js +5 -0
  394. package/dist/src/transfer/store/download.js.map +1 -1
  395. package/dist/src/transfer/store/publish.d.ts.map +1 -1
  396. package/dist/src/transfer/store/publish.js +13 -1
  397. package/dist/src/transfer/store/publish.js.map +1 -1
  398. package/dist/src/transfer/store/registry.d.ts +8 -0
  399. package/dist/src/transfer/store/registry.d.ts.map +1 -1
  400. package/dist/src/transfer/store/registry.js +30 -5
  401. package/dist/src/transfer/store/registry.js.map +1 -1
  402. package/dist/src/transfer/store/search.d.ts.map +1 -1
  403. package/dist/src/transfer/store/search.js +20 -5
  404. package/dist/src/transfer/store/search.js.map +1 -1
  405. package/dist/src/ui/collector.mjs +39 -5
  406. package/dist/src/ui/dashboard.html +926 -1268
  407. package/dist/src/ui/orgs.html +722 -12
  408. package/dist/src/ui/server.mjs +573 -134
  409. package/dist/src/update/checker.d.ts.map +1 -1
  410. package/dist/src/update/checker.js +59 -7
  411. package/dist/src/update/checker.js.map +1 -1
  412. package/dist/src/update/executor.d.ts.map +1 -1
  413. package/dist/src/update/executor.js +50 -3
  414. package/dist/src/update/executor.js.map +1 -1
  415. package/dist/src/update/index.d.ts.map +1 -1
  416. package/dist/src/update/index.js +18 -1
  417. package/dist/src/update/index.js.map +1 -1
  418. package/dist/src/update/rate-limiter.d.ts +6 -0
  419. package/dist/src/update/rate-limiter.d.ts.map +1 -1
  420. package/dist/src/update/rate-limiter.js +79 -7
  421. package/dist/src/update/rate-limiter.js.map +1 -1
  422. package/dist/src/update/validator.d.ts.map +1 -1
  423. package/dist/src/update/validator.js +52 -1
  424. package/dist/src/update/validator.js.map +1 -1
  425. package/dist/tsconfig.tsbuildinfo +1 -1
  426. package/package.json +2 -3
  427. package/dist/src/ui/data/mastermind-events.jsonl +0 -59
@@ -11,6 +11,7 @@ const buildDocsState = new Map();
11
11
 
12
12
  // Pricing per token (mirrors token-tracker.cjs FALLBACK_PRICING)
13
13
  const _SJ_PRICING = {
14
+ 'claude-opus-4-8': { in: 5e-6, out: 25e-6, cw: 6.25e-6, cr: 0.5e-6 },
14
15
  'claude-opus-4-6': { in: 5e-6, out: 25e-6, cw: 6.25e-6, cr: 0.5e-6 },
15
16
  'claude-opus-4-5': { in: 5e-6, out: 25e-6, cw: 6.25e-6, cr: 0.5e-6 },
16
17
  'claude-opus-4': { in: 15e-6, out: 75e-6, cw: 18.75e-6, cr: 1.5e-6 },
@@ -20,13 +21,16 @@ const _SJ_PRICING = {
20
21
  'claude-3-7-sonnet': { in: 3e-6, out: 15e-6, cw: 3.75e-6, cr: 0.3e-6 },
21
22
  'claude-3-5-sonnet': { in: 3e-6, out: 15e-6, cw: 3.75e-6, cr: 0.3e-6 },
22
23
  'claude-haiku-4-5': { in: 1e-6, out: 5e-6, cw: 1.25e-6, cr: 0.1e-6 },
24
+ 'claude-haiku-4': { in: 0.8e-6, out: 4e-6, cw: 1e-6, cr: 0.08e-6 },
23
25
  'claude-3-5-haiku': { in: 0.8e-6, out: 4e-6, cw: 1e-6, cr: 0.08e-6 },
24
26
  'gpt-4o': { in: 2.5e-6, out: 10e-6, cw: 2.5e-6, cr: 1.25e-6 },
25
27
  'gpt-4o-mini': { in: 0.15e-6, out: 0.6e-6, cw: 0.15e-6, cr: 0.075e-6 },
26
28
  'gemini-2.5-pro': { in: 1.25e-6, out: 10e-6, cw: 1.25e-6, cr: 0.315e-6 },
27
29
  };
28
30
  function _sjGetPricing(model) {
29
- const canonical = (model || '').replace(/@.*$/, '').replace(/-\d{8}$/, '');
31
+ const _ALIAS = { 'haiku': 'claude-haiku-4-5', 'opus': 'claude-opus-4-6', 'sonnet': 'claude-sonnet-4-6' };
32
+ let canonical = (model || '').replace(/@.*$/, '').replace(/-\d{8}$/, '');
33
+ canonical = _ALIAS[canonical] || canonical;
30
34
  if (_SJ_PRICING[canonical]) return _SJ_PRICING[canonical];
31
35
  for (const k of Object.keys(_SJ_PRICING)) { if (canonical.startsWith(k) || canonical.includes(k)) return _SJ_PRICING[k]; }
32
36
  return null;
@@ -51,7 +55,7 @@ function categorizeTool(name) {
51
55
  if (['Read','Write','Edit','MultiEdit','Glob','Grep','LS'].includes(name)) return 'file';
52
56
  if (name === 'Bash') return 'bash';
53
57
  if (['Agent','Task'].includes(name)) return 'agent';
54
- if (name.startsWith('mcp__monobrain__memory') || name.startsWith('mcp__monobrain__agentdb')) return 'memory';
58
+ if (name.startsWith('mcp__monomind__memory') || name.startsWith('mcp__monomind__agentdb')) return 'memory';
55
59
  if (['WebFetch','WebSearch'].includes(name)) return 'web';
56
60
  if (name === 'TodoWrite' || name === 'TodoRead') return 'task';
57
61
  if (name === 'Skill') return 'skill';
@@ -131,8 +135,8 @@ function buildToolLabel(name, input) {
131
135
  if (name === 'WebFetch') return `Fetch ${(input.url || '').slice(0, 50)}`;
132
136
  if (name === 'WebSearch') return `Search ${(input.query || '').slice(0, 40)}`;
133
137
  if (name === 'Skill') return `Skill: ${input.skill || '?'}`;
134
- if (name.startsWith('mcp__monobrain__memory')) return name.replace('mcp__monobrain__memory_', 'mem:');
135
- if (name.startsWith('mcp__')) return name.replace('mcp__monobrain__', '⬡ ').replace('mcp__', '⬡ ').slice(0, 40);
138
+ if (name.startsWith('mcp__monomind__memory')) return name.replace('mcp__monomind__memory_', 'mem:');
139
+ if (name.startsWith('mcp__')) return name.replace('mcp__monomind__', '⬡ ').replace('mcp__', '⬡ ').slice(0, 40);
136
140
  return name.slice(0, 40);
137
141
  }
138
142
 
@@ -179,6 +183,41 @@ function pathToSections(filename) {
179
183
  const sseClients = new Set();
180
184
  // Mastermind real-time event stream clients
181
185
  const mmSseClients = new Set();
186
+ // Active org run tracking: org -> runId (enables event routing for orgs without runId in payload)
187
+ const activeOrgRuns = new Map();
188
+
189
+ // Returns the shared git directory parent so run files survive branch switches and
190
+ // are shared across all worktrees. In a worktree, .git is a FILE pointing to the
191
+ // shared .git dir (e.g. /main/.git/worktrees/feat); we navigate up two levels to
192
+ // reach /main/.git, then up one more to /main/ for the monomind data root.
193
+ // Falls back to the working directory if git isn't available.
194
+ const _gitMonomindCache = new Map();
195
+ function _getGitMonomindDir(workDir) {
196
+ if (!workDir) return null;
197
+ if (_gitMonomindCache.has(workDir)) return _gitMonomindCache.get(workDir);
198
+ let result = null;
199
+ try {
200
+ const gitEntry = path.join(workDir, '.git');
201
+ const st = fs.statSync(gitEntry);
202
+ if (st.isDirectory()) {
203
+ // Regular repo: .git is a directory
204
+ result = path.join(gitEntry, 'monomind');
205
+ } else if (st.isFile()) {
206
+ // Worktree: .git is a text file "gitdir: /main/.git/worktrees/name"
207
+ const m = fs.readFileSync(gitEntry, 'utf8').trim().match(/^gitdir:\s*(.+)/);
208
+ if (m) {
209
+ // Resolve relative paths (gitdir can be relative to the worktree root)
210
+ const worktreeDir = path.resolve(workDir, m[1].trim());
211
+ // /main/.git/worktrees/name -> /main/.git -> /main/.git/monomind
212
+ const commonGitDir = path.dirname(path.dirname(worktreeDir));
213
+ result = path.join(commonGitDir, 'monomind');
214
+ }
215
+ }
216
+ } catch {}
217
+ if (!result) result = path.join(workDir, '.monomind'); // fallback
218
+ _gitMonomindCache.set(workDir, result);
219
+ return result;
220
+ }
182
221
 
183
222
  // Server state
184
223
  let running = false;
@@ -410,7 +449,23 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
410
449
  return;
411
450
  }
412
451
  try {
413
- const raw = fs.readFileSync(file, 'utf8');
452
+ // Security: validate that the requested file stays within the user's
453
+ // home directory. Without this, ?file=/etc/passwd discloses arbitrary
454
+ // system files to any process that can reach localhost:4242.
455
+ const _resolvedFile = path.resolve(file);
456
+ const _homeDir = os.homedir();
457
+ if (!_resolvedFile.startsWith(_homeDir + path.sep) && !_resolvedFile.startsWith(_homeDir)) {
458
+ res.writeHead(403, { 'Content-Type': 'application/json' });
459
+ res.end(JSON.stringify({ error: 'Access denied: file must be within the home directory' }));
460
+ return;
461
+ }
462
+ // Only allow JSONL files (session logs).
463
+ if (!_resolvedFile.endsWith('.jsonl')) {
464
+ res.writeHead(403, { 'Content-Type': 'application/json' });
465
+ res.end(JSON.stringify({ error: 'Access denied: only .jsonl files are permitted' }));
466
+ return;
467
+ }
468
+ const raw = fs.readFileSync(_resolvedFile, 'utf8');
414
469
  const allLines = raw.split('\n').filter(Boolean);
415
470
  const lines = allLines.slice(-limit);
416
471
  const events = parseSessionLines(lines);
@@ -450,7 +505,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
450
505
  for (const { f, mtime } of sessionFiles) {
451
506
  const fp = path.join(projectClaudeDir, f);
452
507
  const id = f.replace('.jsonl', '');
453
- let lastPrompt = '', summaries = [], totalDurationMs = 0, totalMessages = 0, firstTs = null, lastTs = null, totalCost = 0, toolCalls = 0, userMessages = 0, cacheReadTokens = 0, totalInputTokens = 0;
508
+ let lastPrompt = '', summaries = [], totalDurationMs = 0, totalMessages = 0, firstTs = null, lastTs = null, totalCost = 0, toolCalls = 0, userMessages = 0, cacheReadTokens = 0, totalInputTokens = 0, errorCount = 0;
454
509
  const modelBreakdown = {};
455
510
  const filesTouchedSet = new Set();
456
511
  try {
@@ -460,7 +515,12 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
460
515
  let e; try { e = JSON.parse(line); } catch { continue; }
461
516
  if (e.timestamp) { if (!firstTs) firstTs = e.timestamp; lastTs = e.timestamp; }
462
517
  if (e.type === 'last-prompt' && e.lastPrompt) lastPrompt = e.lastPrompt;
463
- if (e.type === 'user') userMessages++;
518
+ if (e.type === 'user') {
519
+ userMessages++;
520
+ for (const b of (e.message?.content || [])) {
521
+ if (b && b.type === 'tool_result' && b.is_error) errorCount++;
522
+ }
523
+ }
464
524
  if (e.type === 'system' && e.subtype === 'compact_boundary') pendingCompact = true;
465
525
  if (pendingCompact && e.type === 'user') {
466
526
  const msg = e.message || {};
@@ -502,7 +562,9 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
502
562
  }
503
563
  } catch {}
504
564
  const filesTouched = [...filesTouchedSet].slice(0, 20);
505
- sessions.push({ id, mtime, firstTs, lastTs, lastPrompt, summaries, totalDurationMs, totalMessages, totalCost, toolCalls, userMessages, cacheReadTokens, totalInputTokens, modelBreakdown, filesTouched, file: fp });
565
+ const compactCount = summaries.length;
566
+ const summary = summaries.length ? summaries[summaries.length - 1].text : null;
567
+ sessions.push({ id, mtime, firstTs, lastTs, lastPrompt, summaries, summary, compactCount, errorCount, totalDurationMs, totalMessages, totalCost, toolCalls, userMessages, cacheReadTokens, totalInputTokens, modelBreakdown, filesTouched, file: fp });
506
568
  }
507
569
  res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
508
570
  res.end(JSON.stringify({ sessions }));
@@ -891,7 +953,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
891
953
  // ------------------------------------------------------- PUT /api/memory-file
892
954
  if (req.method === 'PUT' && url === '/api/memory-file') {
893
955
  let body = '';
894
- req.on('data', chunk => { body += chunk; });
956
+ req.on('data', chunk => { body += chunk; if (body.length > 2097152) { req.destroy(); return; } });
895
957
  req.on('end', () => {
896
958
  try {
897
959
  const qs = new URL(req.url, 'http://localhost').searchParams;
@@ -925,7 +987,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
925
987
  // ------------------------------------------------------- DELETE /api/memory-file
926
988
  if (req.method === 'DELETE' && url === '/api/memory-file') {
927
989
  let body = '';
928
- req.on('data', chunk => { body += chunk; });
990
+ req.on('data', chunk => { body += chunk; if (body.length > 2097152) { req.destroy(); return; } });
929
991
  req.on('end', () => {
930
992
  try {
931
993
  const qs = new URL(req.url, 'http://localhost').searchParams;
@@ -1060,8 +1122,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1060
1122
  let alive = false;
1061
1123
  try { process.kill(pid, 0); alive = true; } catch {}
1062
1124
  const alreadyTracked = loops.some(l => l.id === sessionId || l.sessionId === sessionId);
1063
- const hasRepeatLoops = loops.some(l => l.source === '_repeat.md');
1064
- if (alive && sessionId && !alreadyTracked && !hasRepeatLoops && !stopFiles.has(sessionId)) {
1125
+ if (alive && sessionId && !alreadyTracked && !stopFiles.has(sessionId)) {
1065
1126
  // Try to extract ScheduleWakeup context from session JSONL
1066
1127
  let loopEntry = null;
1067
1128
  try {
@@ -1144,6 +1205,10 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1144
1205
  }
1145
1206
  } catch {}
1146
1207
 
1208
+ // Dedup: suppress scheduled_tasks_lock noise when real repeat loops exist
1209
+ const hasRepeatLoops = loops.some(l => l.source !== 'scheduled_tasks_lock' && l.source !== 'schedule_wakeup_hook');
1210
+ if (hasRepeatLoops) loops = loops.filter(l => l.source !== 'scheduled_tasks_lock' && l.source !== 'schedule_wakeup_hook');
1211
+
1147
1212
  loops.sort((a, b) => (b.startedAt || 0) - (a.startedAt || 0));
1148
1213
  res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
1149
1214
  res.end(JSON.stringify({ loops }));
@@ -1154,13 +1219,14 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1154
1219
  // ---------------------------------------------------------- POST /api/loops/stop
1155
1220
  if (req.method === 'POST' && url === '/api/loops/stop') {
1156
1221
  let body = '';
1157
- req.on('data', chunk => { body += chunk; });
1222
+ req.on('data', chunk => { body += chunk; if (body.length > 2097152) { req.destroy(); return; } });
1158
1223
  req.on('end', () => {
1159
1224
  try {
1160
- const stopQs = new URL(req.url, 'http://localhost').searchParams;
1161
1225
  const { id } = JSON.parse(body);
1162
1226
  if (!id) { res.writeHead(400); res.end(JSON.stringify({ error: 'id required' })); return; }
1163
- const loopsDir = path.join(stopQs.get('dir') || projectDir || process.cwd(), '.monomind', 'loops');
1227
+ const _stopQs = new URL(req.url, 'http://localhost').searchParams;
1228
+ const _stopDir = path.resolve(_stopQs.get('dir') || projectDir || process.cwd());
1229
+ const loopsDir = path.join(_stopDir, '.monomind', 'loops');
1164
1230
  fs.mkdirSync(loopsDir, { recursive: true });
1165
1231
  fs.writeFileSync(path.join(loopsDir, `${id}.stop`), `stop-requested-${Date.now()}`);
1166
1232
  res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
@@ -1173,17 +1239,27 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1173
1239
  // ---------------------------------------------------------- POST /api/loops/create
1174
1240
  if (req.method === 'POST' && url === '/api/loops/create') {
1175
1241
  let body = '';
1176
- req.on('data', chunk => { body += chunk; });
1242
+ req.on('data', chunk => { body += chunk; if (body.length > 2097152) { req.destroy(); return; } });
1177
1243
  req.on('end', () => {
1178
1244
  try {
1179
- const createQs = new URL(req.url, 'http://localhost').searchParams;
1180
- const { name, prompt, interval, maxReps } = JSON.parse(body);
1245
+ const _qs = new URL(req.url, 'http://localhost').searchParams;
1246
+ const { name: _rawName, prompt: _rawPrompt, interval: _rawInterval, maxReps: _rawMaxReps } = JSON.parse(body);
1247
+ // Cap field sizes to prevent individual large-field disk inflation.
1248
+ // The 2MB body cap already limits total payload, but a single field
1249
+ // near 2MB would produce a multi-MB loop config file per request.
1250
+ const MAX_LOOP_PROMPT_LEN = 64 * 1024; // 64 KB
1251
+ const MAX_LOOP_NAME_LEN = 512;
1252
+ const MAX_LOOP_INTERVAL_LEN = 64;
1253
+ const prompt = typeof _rawPrompt === 'string' ? _rawPrompt.slice(0, MAX_LOOP_PROMPT_LEN) : null;
1254
+ const name = typeof _rawName === 'string' ? _rawName.slice(0, MAX_LOOP_NAME_LEN) : null;
1255
+ const interval = typeof _rawInterval === 'string' ? _rawInterval.slice(0, MAX_LOOP_INTERVAL_LEN) : null;
1256
+ const maxReps = typeof _rawMaxReps === 'number' && Number.isFinite(_rawMaxReps) ? Math.max(1, Math.min(Math.floor(_rawMaxReps), 10000)) : null;
1181
1257
  if (!prompt) { res.writeHead(400); res.end(JSON.stringify({ error: 'prompt required' })); return; }
1182
- const loopsDir = path.join(createQs.get('dir') || projectDir || process.cwd(), '.monomind', 'loops');
1258
+ const loopsDir = path.join(path.resolve(_qs.get('dir') || projectDir || process.cwd()), '.monomind', 'loops');
1183
1259
  fs.mkdirSync(loopsDir, { recursive: true });
1184
1260
  const id = `loop-${Date.now()}-${Math.random().toString(36).slice(2,7)}`;
1185
1261
  const nowMs = Date.now();
1186
- const loop = { id, type: 'repeat', name: name || prompt.slice(0, 40), prompt, interval: interval || '1h', maxReps: maxReps || null, status: 'active', currentRep: 0, startedAt: nowMs, lastRunAt: null };
1262
+ const loop = { id, type: 'repeat', name: name || prompt.slice(0, 40), prompt, interval: interval || '1h', maxReps, status: 'active', currentRep: 0, startedAt: nowMs, lastRunAt: null };
1187
1263
  fs.writeFileSync(path.join(loopsDir, `${id}.json`), JSON.stringify(loop, null, 2));
1188
1264
  res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
1189
1265
  res.end(JSON.stringify({ ok: true, id }));
@@ -1196,7 +1272,10 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1196
1272
  if (req.method === 'GET' && url === '/api/session-errors') {
1197
1273
  const qs = new URL(req.url, 'http://localhost').searchParams;
1198
1274
  const d = path.resolve(qs.get('dir') || projectDir || process.cwd());
1199
- const sessionId = qs.get('id') || '';
1275
+ // Cap sessionId to prevent O(n×m) DoS via f.includes(sessionId) substring
1276
+ // match against every filename when sessionId is a very long string.
1277
+ const _rawSessId = qs.get('id') || '';
1278
+ const sessionId = _rawSessId.slice(0, 256);
1200
1279
  const slug = d.replace(/\//g, '-');
1201
1280
  const projectClaudeDir = path.join(os.homedir(), '.claude', 'projects', slug);
1202
1281
  try {
@@ -1267,7 +1346,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1267
1346
  // ------------------------------------------------------- DELETE /api/knowledge-chunk
1268
1347
  if (req.method === 'DELETE' && url === '/api/knowledge-chunk') {
1269
1348
  let body = '';
1270
- req.on('data', chunk => { body += chunk; });
1349
+ req.on('data', chunk => { body += chunk; if (body.length > 2097152) { req.destroy(); return; } });
1271
1350
  req.on('end', () => {
1272
1351
  try {
1273
1352
  const qs = new URL(req.url, 'http://localhost').searchParams;
@@ -1306,7 +1385,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1306
1385
  // ------------------------------------------------------- PUT /api/knowledge-chunk
1307
1386
  if (req.method === 'PUT' && url === '/api/knowledge-chunk') {
1308
1387
  let body = '';
1309
- req.on('data', chunk => { body += chunk; });
1388
+ req.on('data', chunk => { body += chunk; if (body.length > 2097152) { req.destroy(); return; } });
1310
1389
  req.on('end', () => {
1311
1390
  try {
1312
1391
  const qs = new URL(req.url, 'http://localhost').searchParams;
@@ -1802,7 +1881,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1802
1881
  try {
1803
1882
  const qs = new URL(req.url, 'http://localhost').searchParams;
1804
1883
  const dir = qs.get('dir') || projectDir || process.cwd();
1805
- const q = (qs.get('q') || '').trim();
1884
+ // Cap ?q= to prevent DoS via megabyte FTS query strings.
1885
+ const q = (qs.get('q') || '').trim().slice(0, 4096);
1806
1886
  const limit = Math.min(100, parseInt(qs.get('limit') || '50', 10));
1807
1887
  const d = path.resolve(dir || process.cwd());
1808
1888
  const dbPath = path.join(d, '.monomind', 'monograph.db');
@@ -1936,7 +2016,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1936
2016
  try {
1937
2017
  const qs = new URL(req.url, 'http://localhost').searchParams;
1938
2018
  const dir = qs.get('dir') || projectDir || process.cwd();
1939
- const q = qs.get('q') || '';
2019
+ const q = (qs.get('q') || '').trim().slice(0, 4096);
1940
2020
  const d = path.resolve(dir || process.cwd());
1941
2021
  const dbPath = path.join(d, '.monomind', 'monograph.db');
1942
2022
  if (!q) { res.writeHead(400); res.end(JSON.stringify({ error: 'Missing ?q= parameter' })); return; }
@@ -1973,7 +2053,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1973
2053
  try {
1974
2054
  const qs = new URL(req.url, 'http://localhost').searchParams;
1975
2055
  const dir = qs.get('dir') || projectDir || process.cwd();
1976
- const nodeQ = qs.get('node') || '';
2056
+ const nodeQ = (qs.get('node') || '').trim().slice(0, 4096);
1977
2057
  const d = path.resolve(dir || process.cwd());
1978
2058
  const dbPath = path.join(d, '.monomind', 'monograph.db');
1979
2059
  if (!nodeQ) { res.writeHead(400); res.end(JSON.stringify({ error: 'Missing ?node= parameter' })); return; }
@@ -2015,8 +2095,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
2015
2095
  try {
2016
2096
  const qs = new URL(req.url, 'http://localhost').searchParams;
2017
2097
  const dir = qs.get('dir') || projectDir || process.cwd();
2018
- const from = qs.get('from') || '';
2019
- const to = qs.get('to') || '';
2098
+ const from = (qs.get('from') || '').trim().slice(0, 4096);
2099
+ const to = (qs.get('to') || '').trim().slice(0, 4096);
2020
2100
  const d = path.resolve(dir || process.cwd());
2021
2101
  const dbPath = path.join(d, '.monomind', 'monograph.db');
2022
2102
  if (!from || !to) { res.writeHead(400); res.end(JSON.stringify({ error: 'Missing ?from= and ?to= parameters' })); return; }
@@ -2136,7 +2216,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
2136
2216
  // -------------------------------------------------- POST /api/mcp/call
2137
2217
  if (req.method === 'POST' && url === '/api/mcp/call') {
2138
2218
  let body = '';
2139
- req.on('data', c => body += c);
2219
+ req.on('data', c => { body += c; if (body.length > 2097152) { req.destroy(); return; } });
2140
2220
  req.on('end', async () => {
2141
2221
  const json = res => { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); };
2142
2222
  const ok = (data) => { json(res); res.end(JSON.stringify({ content: [{ type: 'text', text: typeof data === 'string' ? data : JSON.stringify(data, null, 2) }] })); };
@@ -2184,7 +2264,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
2184
2264
  ok(`nodes: ${n}\nedges: ${e}`);
2185
2265
  } else if (tool === 'monograph_cypher') {
2186
2266
  // Translate basic MATCH (n:Label) queries to SQL
2187
- const q = (input.query || '').trim();
2267
+ const q = (String(input.query || '')).trim().slice(0, 4096);
2188
2268
  const labelMatch = q.match(/MATCH\s+\(n:(\w+)\)/i);
2189
2269
  if (labelMatch) {
2190
2270
  const label = labelMatch[1];
@@ -2242,12 +2322,13 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
2242
2322
  } else if (tool === 'monograph_diff') {
2243
2323
  ok('Graph diff: compare two snapshots using monograph snapshot + monograph diff commands');
2244
2324
  } else if (tool === 'monograph_rename') {
2245
- const sym = input.symbolName || '';
2325
+ // Cap sym to prevent O(n) FTS scan DoS via oversized query string.
2326
+ const sym = String(input.symbolName || '').slice(0, 4096);
2246
2327
  if (!sym) { ok('Provide symbolName to rename'); return; }
2247
2328
  const hits = ftsSearch(db2, sym, 20);
2248
2329
  ok(`Found ${hits.length} occurrences of "${sym}":\n` + hits.map(h => ` ${h.filePath || '?'}:${h.startLine || '?'} — ${h.name}`).join('\n'));
2249
2330
  } else if (tool === 'monograph_impact') {
2250
- const target = input.target || '';
2331
+ const target = String(input.target || '').slice(0, 4096);
2251
2332
  const dir3 = input.direction || 'both';
2252
2333
  const depth = input.maxDepth || 4;
2253
2334
  const hits = ftsSearch(db2, target, 5);
@@ -2274,7 +2355,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
2274
2355
  }
2275
2356
  ok(`Impact of "${hits[0].name}" (${dir3}, depth=${depth}):\n` + (results.join('\n') || ' (no dependencies found)'));
2276
2357
  } else if (tool === 'monograph_context') {
2277
- const id = input.id || '';
2358
+ const id = String(input.id || '').slice(0, 4096);
2278
2359
  const hits = ftsSearch(db2, id, 5);
2279
2360
  if (!hits.length) { ok(`Node not found: ${id}`); return; }
2280
2361
  const node = hits[0];
@@ -2282,7 +2363,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
2282
2363
  const inEdges = db2.prepare('SELECT e.relation, n.name FROM edges e JOIN nodes n ON n.id = e.source_id WHERE e.target_id = ? LIMIT 20').all(node.id);
2283
2364
  ok(`# ${node.name} (${node.label})\nFile: ${node.filePath || '?'}\n\n**Imports / depends on (${outEdges.length}):**\n${outEdges.map(e => ` → ${e.name} [${e.relation}]`).join('\n') || ' (none)'}\n\n**Used by / depended on by (${inEdges.length}):**\n${inEdges.map(e => ` ← ${e.name} [${e.relation}]`).join('\n') || ' (none)'}`);
2284
2365
  } else if (tool === 'monograph_query' || tool === 'monograph_suggest') {
2285
- const q2 = input.query || input.task || '';
2366
+ const q2 = String(input.query || input.task || '').slice(0, 4096);
2286
2367
  const hits2 = ftsSearch(db2, q2, 20);
2287
2368
  ok(hits2.map(h => `${h.name} (${h.label}) — ${h.filePath || '?'}:${h.startLine || '?'}`).join('\n') || 'No results');
2288
2369
 
@@ -2631,7 +2712,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
2631
2712
  if (['Read','Write','Edit','MultiEdit','Glob','Grep','LS'].includes(name)) return 'file';
2632
2713
  if (name === 'Bash') return 'bash';
2633
2714
  if (['Agent','Task'].includes(name)) return 'agent';
2634
- if (name.startsWith('mcp__monobrain__memory') || name.startsWith('mcp__monobrain__agentdb')) return 'memory';
2715
+ if (name.startsWith('mcp__monomind__memory') || name.startsWith('mcp__monomind__agentdb')) return 'memory';
2635
2716
  if (['WebFetch','WebSearch'].includes(name)) return 'web';
2636
2717
  if (name === 'Skill') return 'skill';
2637
2718
  return 'other';
@@ -2648,9 +2729,35 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
2648
2729
  try { stat = fs.statSync(fp); } catch { continue; }
2649
2730
 
2650
2731
  // Skip files over size cap to avoid memory spikes on large sessions
2732
+ // But still do a lightweight scan for agent spawns (tool_use blocks named Agent/Task)
2651
2733
  if (stat.size > JSONL_SIZE_CAP) {
2734
+ const truncSpawns = {};
2735
+ try {
2736
+ const raw = fs.readFileSync(fp, 'utf8');
2737
+ for (const line of raw.split('\n')) {
2738
+ if (!line.includes('"tool_use"') || (!line.includes('"Agent"') && !line.includes('"Task"'))) continue;
2739
+ let e; try { e = JSON.parse(line); } catch { continue; }
2740
+ if (e.type !== 'assistant') continue;
2741
+ for (const block of (e.message?.content || [])) {
2742
+ if (!block || block.type !== 'tool_use') continue;
2743
+ if (block.name !== 'Agent' && block.name !== 'Task') continue;
2744
+ const sub = block.input?.subagent_type || block.input?.description || '?';
2745
+ truncSpawns[sub] = (truncSpawns[sub] || 0) + 1;
2746
+ }
2747
+ }
2748
+ } catch {}
2652
2749
  nodes.push({ id: sid, type: 'session', label: sid.slice(0,8), turns: 0, totalTools: 0,
2653
- toolCounts: {}, cost: 0, mtime: stat.mtimeMs, size: stat.size, agentSpawns: {}, truncated: true });
2750
+ toolCounts: {}, cost: 0, mtime: stat.mtimeMs, size: stat.size, agentSpawns: truncSpawns, truncated: true });
2751
+ for (const [subType, count] of Object.entries(truncSpawns)) {
2752
+ const nodeId = 'agent::' + subType;
2753
+ if (!agentTypeNodes[subType]) {
2754
+ agentTypeNodes[subType] = true;
2755
+ nodes.push({ id: nodeId, type: 'agenttype', label: subType, totalSpawns: 0 });
2756
+ }
2757
+ const aNode = nodes.find(n => n.id === nodeId);
2758
+ if (aNode) aNode.totalSpawns = (aNode.totalSpawns || 0) + count;
2759
+ edges.push({ source: sid, target: nodeId, weight: count, label: String(count) });
2760
+ }
2654
2761
  continue;
2655
2762
  }
2656
2763
 
@@ -2663,7 +2770,12 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
2663
2770
  const lines = raw.split('\n').filter(Boolean);
2664
2771
  for (const line of lines) {
2665
2772
  let e; try { e = JSON.parse(line); } catch { continue; }
2666
- if (e.type === 'user') turns++;
2773
+ if (e.type === 'user') {
2774
+ // Only count actual human turns, not tool-result responses
2775
+ const ct = e.message?.content;
2776
+ const isToolResult = Array.isArray(ct) && ct.length > 0 && ct.every(b => b && b.type === 'tool_result');
2777
+ if (!isToolResult) turns++;
2778
+ }
2667
2779
  if (e.type === 'assistant') {
2668
2780
  for (const block of (e.message?.content || [])) {
2669
2781
  if (!block || block.type !== 'tool_use') continue;
@@ -2674,8 +2786,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
2674
2786
  agentSpawns[sub] = (agentSpawns[sub] || 0) + 1;
2675
2787
  }
2676
2788
  }
2789
+ if (e.message?.usage) totalCost += _sjCalcCost(e.message.model || '', e.message.usage);
2677
2790
  }
2678
- if (e.costUSD) totalCost += e.costUSD;
2679
2791
  }
2680
2792
  } catch {}
2681
2793
 
@@ -2733,8 +2845,13 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
2733
2845
  try {
2734
2846
  const qs = new URL(req.url, 'http://localhost').searchParams;
2735
2847
  const dir = qs.get('dir') || projectDir || process.cwd();
2736
- const swarmId = qs.get('swarmId') || undefined;
2737
- const agentId = qs.get('agentId') || undefined;
2848
+ // Cap swarmId and agentId to prevent O(n×m) DoS: filter() compares
2849
+ // each event against the query string, so a megabyte-scale ID causes
2850
+ // O(events × m) string comparisons.
2851
+ const _rawSwarmId = qs.get('swarmId') || undefined;
2852
+ const _rawAgentId = qs.get('agentId') || undefined;
2853
+ const swarmId = typeof _rawSwarmId === 'string' ? _rawSwarmId.slice(0, 256) : undefined;
2854
+ const agentId = typeof _rawAgentId === 'string' ? _rawAgentId.slice(0, 256) : undefined;
2738
2855
  const last = qs.get('last') ? parseInt(qs.get('last')) : undefined;
2739
2856
  const events = collectSwarmEvents(path.resolve(dir), { swarmId, agentId, last });
2740
2857
  res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
@@ -2778,47 +2895,71 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
2778
2895
  if (req.method === 'GET' && url.startsWith('/api/token-usage')) {
2779
2896
  try {
2780
2897
  const qs = new URL(req.url, 'http://localhost').searchParams;
2781
- const period = qs.get('period') || 'today';
2898
+ const period = ['today','week','30days','month'].includes(qs.get('period')) ? qs.get('period') : 'today';
2782
2899
  const dir = path.resolve(qs.get('dir') || projectDir || process.cwd());
2783
-
2784
- // Determine date window
2785
- const now = new Date();
2786
- const todayStr = now.toISOString().slice(0, 10);
2787
- let fromStr;
2788
- let daysBack;
2789
- if (period === 'today') {
2790
- fromStr = todayStr; daysBack = 1;
2791
- } else if (period === 'week') {
2792
- const d = new Date(now); d.setDate(d.getDate() - 6);
2793
- fromStr = d.toISOString().slice(0, 10); daysBack = 7;
2794
- } else if (period === '30d' || period === '30days') {
2795
- const d = new Date(now); d.setDate(d.getDate() - 29);
2796
- fromStr = d.toISOString().slice(0, 10); daysBack = 30;
2797
- } else { // month
2798
- fromStr = now.toISOString().slice(0, 7) + '-01'; daysBack = 32;
2799
- }
2800
-
2801
- const raw = collectTokens(dir, Math.max(daysBack + 1, 14));
2802
- // Filter daily to the requested window
2803
- const daily = (raw.daily || []).filter(d => d.date >= fromStr);
2804
- // Aggregate summary for the period
2805
- let cost = 0, calls = 0, tokensIn = 0, tokensOut = 0;
2806
- for (const d of daily) { cost += d.cost; calls += d.calls; tokensIn += d.tokensIn; tokensOut += d.tokensOut; }
2807
- // Filter rows to sessions that had activity in the window
2808
- const rows = (raw.rows || []).filter(r => {
2809
- // rows don't have dates yet, just include top ones by cost
2810
- return true;
2811
- }).slice(0, 30);
2812
-
2813
- const summary = {
2814
- todayCost: cost,
2815
- todayCalls: calls,
2816
- cost, calls,
2817
- totalTokens: tokensIn + tokensOut,
2818
- modelCount: Object.keys(raw.summary?.modelBreakdown || {}).length || null,
2900
+ const trackerPath = path.join(dir, '.claude', 'helpers', 'token-tracker.cjs');
2901
+ const fallback = () => {
2902
+ const summary = (() => { try { return JSON.parse(fs.readFileSync(path.join(dir, '.monomind', 'metrics', 'token-summary.json'), 'utf8')); } catch { return {}; } })();
2903
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
2904
+ const fbSum = { todayCost: summary.todayCost || 0, cost: summary.todayCost || 0, todayCalls: summary.todayCalls || 0, calls: summary.todayCalls || 0, totalTokens: 0, totalTokensIn: 0, totalTokensOut: 0, cacheTokens: 0, modelCount: 0 };
2905
+ res.end(JSON.stringify({ summary: fbSum, totalCost: summary.todayCost || 0, totalCalls: summary.todayCalls || 0, totalIn: 0, totalOut: 0, totalCR: 0, totalCW: 0, rows: [], models: [], categories: [], tools: [], mcpServers: [], projects: [], modelBreakdown: {}, categoryBreakdown: {}, toolBreakdown: {}, mcpBreakdown: {}, periodLabel: period }));
2819
2906
  };
2820
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
2821
- res.end(JSON.stringify({ summary, daily, rows, periodLabel: period }));
2907
+ if (!fs.existsSync(trackerPath)) { fallback(); return; }
2908
+ try {
2909
+ const _req = createRequire(import.meta.url);
2910
+ const tracker = _req(trackerPath);
2911
+ const range = tracker.getDateRange(period);
2912
+ const projects = tracker.parseAllSessions(range.start, range.end);
2913
+ let totalCost = 0, totalIn = 0, totalOut = 0, totalCR = 0, totalCW = 0, totalCalls = 0;
2914
+ const modelBreakdown = {}, categoryBreakdown = {}, toolBreakdown = {}, mcpBreakdown = {};
2915
+ for (const p of projects) {
2916
+ totalCost += p.totalCost || 0;
2917
+ for (const s of (p.sessions || [])) {
2918
+ totalIn += s.totalInputTokens || 0;
2919
+ totalOut += s.totalOutputTokens || 0;
2920
+ totalCR += s.totalCacheRead || 0;
2921
+ totalCW += s.totalCacheWrite || 0;
2922
+ totalCalls += s.apiCalls || 0;
2923
+ for (const [mn, m] of Object.entries(s.modelBreakdown || {})) {
2924
+ if (!modelBreakdown[mn]) modelBreakdown[mn] = { calls: 0, cost: 0, tokens: 0 };
2925
+ modelBreakdown[mn].calls += m.calls || 0;
2926
+ modelBreakdown[mn].cost += m.cost || 0;
2927
+ modelBreakdown[mn].tokens += m.tokens || 0;
2928
+ }
2929
+ for (const [cat, c] of Object.entries(s.categoryBreakdown || {})) {
2930
+ if (!categoryBreakdown[cat]) categoryBreakdown[cat] = { turns: 0, cost: 0 };
2931
+ categoryBreakdown[cat].turns += c.turns || 0;
2932
+ categoryBreakdown[cat].cost += c.cost || 0;
2933
+ }
2934
+ for (const [tool, t] of Object.entries(s.toolBreakdown || {})) {
2935
+ if (!toolBreakdown[tool]) toolBreakdown[tool] = { calls: 0 };
2936
+ toolBreakdown[tool].calls += t.calls || 0;
2937
+ }
2938
+ for (const [srv, m] of Object.entries(s.mcpBreakdown || {})) {
2939
+ if (!mcpBreakdown[srv]) mcpBreakdown[srv] = { calls: 0 };
2940
+ mcpBreakdown[srv].calls += m.calls || 0;
2941
+ }
2942
+ }
2943
+ }
2944
+ // Build client-friendly arrays from breakdown dicts
2945
+ const models = Object.entries(modelBreakdown).map(([model, m]) => ({ model, cost: m.cost, calls: m.calls, tokens: m.tokens })).sort((a, b) => b.cost - a.cost);
2946
+ const categories = Object.entries(categoryBreakdown).map(([category, c]) => ({ category, turns: c.turns, cost: c.cost })).sort((a, b) => b.turns - a.turns);
2947
+ const tools = Object.entries(toolBreakdown).map(([tool, t]) => ({ tool, count: t.calls })).sort((a, b) => b.count - a.count);
2948
+ const mcpServers = Object.entries(mcpBreakdown).map(([server, m]) => ({ server, count: m.calls })).sort((a, b) => b.count - a.count);
2949
+ const projectRows = projects.map(p => ({ project: p.name || p.slug || p.dir || '?', cost: p.totalCost || 0 })).sort((a, b) => b.cost - a.cost);
2950
+ // Build rows array from sessions for per-session table
2951
+ const rows = [];
2952
+ for (const p of projects) {
2953
+ for (const s of (p.sessions || [])) {
2954
+ rows.push({ id: s.id || '', session: s.lastPrompt || s.id || '', calls: s.apiCalls || 0, cost: s.totalCost || 0, tokens: (s.totalInputTokens || 0) + (s.totalOutputTokens || 0) });
2955
+ }
2956
+ }
2957
+ rows.sort((a, b) => b.cost - a.cost);
2958
+ // Summary object matching client expectations
2959
+ const summary = { todayCost: totalCost, cost: totalCost, todayCalls: totalCalls, calls: totalCalls, totalTokens: totalIn + totalOut, totalTokensIn: totalIn, totalTokensOut: totalOut, cacheTokens: totalCR, modelCount: models.length };
2960
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
2961
+ res.end(JSON.stringify({ summary, totalCost, totalCalls, totalIn, totalOut, totalCR, totalCW, rows, models, categories, tools, mcpServers, projects: projectRows, modelBreakdown, categoryBreakdown, toolBreakdown, mcpBreakdown, periodLabel: period }));
2962
+ } catch (e) { fallback(); }
2822
2963
  } catch (err) {
2823
2964
  res.writeHead(500, { 'Content-Type': 'application/json' });
2824
2965
  res.end(JSON.stringify({ error: err.message }));
@@ -2972,7 +3113,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
2972
3113
  const orgsDir = path.join(path.resolve(_orgsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
2973
3114
  let orgs = [];
2974
3115
  if (fs.existsSync(orgsDir)) {
2975
- const _sidecarSuffixRe = /-(approvals|state|activity|goals|routines|projects|members|issues|workspaces|worktrees|environments|plugins|adapters|bootstrap|threads|budgets|project-workspaces|approval-comments|secrets)\.json$/;
3116
+ const _sidecarSuffixRe = /-(approvals|state|activity|goals|routines|projects|members|issues|workspaces|worktrees|environments|plugins|adapters|bootstrap|threads|budgets|project-workspaces|approval-comments|secrets|join-requests|skills)\.json$/;
2976
3117
  const files = fs.readdirSync(orgsDir).filter(f => f.endsWith('.json') && !_sidecarSuffixRe.test(f));
2977
3118
  // Read events file once, outside the per-org loop
2978
3119
  let recentLines = [];
@@ -3003,7 +3144,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3003
3144
  const stopTs = lastStop ? (JSON.parse(lastStop).ts || 0) : 0;
3004
3145
  running = startTs > stopTs;
3005
3146
  }
3006
- orgs.push({ name: cfg.name, goal: cfg.goal, roles: cfg.roles?.length || 0, topology: cfg.topology, created_at: cfg.created_at, running, status: cfg.status, loop: cfg.loop ? { poll_interval_minutes: cfg.loop.poll_interval_minutes, last_run: cfg.loop.last_run, next_run: cfg.loop.next_run } : undefined });
3147
+ orgs.push({ name: cfg.name, goal: cfg.goal, roles: Array.isArray(cfg.roles) ? cfg.roles : [], topology: cfg.topology, created_at: cfg.created_at, running, status: cfg.status, loop: cfg.loop ? { poll_interval_minutes: cfg.loop.poll_interval_minutes, last_run: cfg.loop.last_run, next_run: cfg.loop.next_run } : undefined });
3007
3148
  } catch(_) {}
3008
3149
  }
3009
3150
  }
@@ -3013,12 +3154,65 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3013
3154
  return;
3014
3155
  }
3015
3156
 
3157
+ // POST /api/orgs/:name/import — import an org config by name (orgs.html upload flow)
3158
+ if (req.method === 'POST' && /^\/api\/orgs\/[a-z0-9][a-z0-9_-]{0,63}\/import$/i.test(url)) {
3159
+ let body = '';
3160
+ req.on('data', c => { body += c; if (body.length > 2e6) req.destroy(); });
3161
+ req.on('end', () => {
3162
+ try {
3163
+ const urlParts = url.split('/');
3164
+ const orgName = decodeURIComponent(urlParts[3]);
3165
+ if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Invalid org name' })); return; }
3166
+ const cfg = JSON.parse(body);
3167
+ const _importQs = new URL(req.url, 'http://localhost').searchParams;
3168
+ const dir = path.resolve(_importQs.get('dir') || projectDir || process.cwd());
3169
+ const orgsDir = path.join(dir, '.monomind', 'orgs');
3170
+ fs.mkdirSync(orgsDir, { recursive: true });
3171
+ const destFile = path.join(orgsDir, `${orgName}.json`);
3172
+ fs.writeFileSync(destFile, JSON.stringify({ ...cfg, name: orgName }, null, 2), 'utf8');
3173
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
3174
+ res.end(JSON.stringify({ ok: true, name: orgName, file: destFile }));
3175
+ } catch (e) {
3176
+ res.writeHead(400, { 'Content-Type': 'application/json' });
3177
+ res.end(JSON.stringify({ error: e.message }));
3178
+ }
3179
+ });
3180
+ return;
3181
+ }
3182
+
3183
+ // POST /api/orgs — import / create org from JSON body
3184
+ if (req.method === 'POST' && url === '/api/orgs') {
3185
+ let body = '';
3186
+ req.on('data', c => { body += c; if (body.length > 2e6) req.destroy(); });
3187
+ req.on('end', () => {
3188
+ try {
3189
+ const cfg = JSON.parse(body);
3190
+ const qs = new URL(req.url, 'http://localhost').searchParams;
3191
+ const dir = qs.get('dir') || cfg.dir || projectDir || process.cwd();
3192
+ const name = (cfg.name || '').toLowerCase().replace(/[^a-z0-9_-]/g, '-').replace(/^-+|-+$/g, '').slice(0, 64);
3193
+ if (!name) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Invalid org name' })); return; }
3194
+ const orgsDir = path.join(path.resolve(dir), '.monomind', 'orgs');
3195
+ fs.mkdirSync(orgsDir, { recursive: true });
3196
+ const destFile = path.join(orgsDir, `${name}.json`);
3197
+ const cleanCfg = Object.fromEntries(Object.entries({ ...cfg, name }).filter(([k]) => !k.startsWith('_')));
3198
+ fs.writeFileSync(destFile, JSON.stringify(cleanCfg, null, 2), 'utf8');
3199
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
3200
+ res.end(JSON.stringify({ ok: true, name, file: destFile }));
3201
+ } catch (e) {
3202
+ res.writeHead(400, { 'Content-Type': 'application/json' });
3203
+ res.end(JSON.stringify({ error: e.message }));
3204
+ }
3205
+ });
3206
+ return;
3207
+ }
3208
+
3016
3209
  // GET /api/orgs/:name — get specific org config (exact path: /api/orgs/<slug>)
3017
3210
  if (req.method === 'GET' && /^\/api\/orgs\/[a-z0-9][a-z0-9_-]{0,63}$/i.test(url)) {
3018
3211
  try {
3019
3212
  const orgName = decodeURIComponent(url.slice('/api/orgs/'.length));
3020
3213
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
3021
- const f = path.join(projectDir || process.cwd(), '.monomind', 'orgs', `${orgName}.json`);
3214
+ const _orgsOneQs = new URL(req.url, 'http://localhost').searchParams;
3215
+ const f = path.join(path.resolve(_orgsOneQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}.json`);
3022
3216
  if (!fs.existsSync(f)) { res.writeHead(404); res.end('{"error":"not found"}'); return; }
3023
3217
  res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
3024
3218
  res.end(fs.readFileSync(f, 'utf8'));
@@ -3050,8 +3244,17 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3050
3244
  const stopFile = path.join(orgsDir, '.stops', `${orgName}.stop`);
3051
3245
  const running = !fs.existsSync(stopFile) && Object.values(state.agents || {}).some(a => a.status === 'running');
3052
3246
 
3247
+ // Read real tasks from the task store and group by status column
3248
+ const taskStoreData = readJsonSafe(path.join(d, '.monomind', 'tasks', 'store.json'));
3249
+ const allTasks = taskStoreData ? Object.values(taskStoreData.tasks || {}) : [];
3250
+ const tasks = {
3251
+ todo: allTasks.filter(t => t.status === 'pending').map(t => ({ id: t.taskId, description: t.description, status: 'todo', ts: t.createdAt })),
3252
+ doing: allTasks.filter(t => t.status === 'in_progress').map(t => ({ id: t.taskId, description: t.description, status: 'doing', ts: t.startedAt || t.createdAt })),
3253
+ done: allTasks.filter(t => t.status === 'completed' || t.status === 'failed' || t.status === 'cancelled').map(t => ({ id: t.taskId, description: t.description, status: t.status, ts: t.completedAt || t.createdAt })),
3254
+ };
3255
+
3053
3256
  const result = { config, state, goals: goalsData.goals, routines: routinesData.routines,
3054
- approvals: approvalsData.approvals, running, tasks: { todo: [], doing: [], done: [] } };
3257
+ approvals: approvalsData.approvals, running, tasks };
3055
3258
 
3056
3259
  res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
3057
3260
  res.end(JSON.stringify(result));
@@ -3115,7 +3318,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3115
3318
  const parts = url.split('/');
3116
3319
  const orgName = decodeURIComponent(parts[3]);
3117
3320
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('[]'); return; }
3118
- const d = projectDir || process.cwd();
3321
+ const _projsQs = new URL(req.url, 'http://localhost').searchParams;
3322
+ const d = path.resolve(_projsQs.get('dir') || projectDir || process.cwd());
3119
3323
  const projFile = path.join(d, '.monomind', 'orgs', `${orgName}-projects.json`);
3120
3324
  if (!fs.existsSync(projFile)) { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end('[]'); return; }
3121
3325
  const data = JSON.parse(fs.readFileSync(projFile, 'utf8'));
@@ -3131,7 +3335,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3131
3335
  const parts = url.split('/');
3132
3336
  const orgName = decodeURIComponent(parts[3]);
3133
3337
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('{}'); return; }
3134
- const d = projectDir || process.cwd();
3338
+ const _membersQs = new URL(req.url, 'http://localhost').searchParams;
3339
+ const d = path.resolve(_membersQs.get('dir') || projectDir || process.cwd());
3135
3340
  const membersFile = path.join(d, '.monomind', 'orgs', `${orgName}-members.json`);
3136
3341
  if (!fs.existsSync(membersFile)) {
3137
3342
  res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
@@ -3151,7 +3356,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3151
3356
  const parts = url.split('/');
3152
3357
  const orgName = decodeURIComponent(parts[3]);
3153
3358
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('{}'); return; }
3154
- const d = projectDir || process.cwd();
3359
+ const _adaptersQs = new URL(req.url, 'http://localhost').searchParams;
3360
+ const d = path.resolve(_adaptersQs.get('dir') || projectDir || process.cwd());
3155
3361
  const adaptersFile = path.join(d, '.monomind', 'orgs', `${orgName}-adapters.json`);
3156
3362
  if (!fs.existsSync(adaptersFile)) {
3157
3363
  // Return defaults derived from org config if available
@@ -3179,7 +3385,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3179
3385
  const parts = url.split('/');
3180
3386
  const orgName = decodeURIComponent(parts[3]);
3181
3387
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('{}'); return; }
3182
- const d = projectDir || process.cwd();
3388
+ const _skillsQs = new URL(req.url, 'http://localhost').searchParams;
3389
+ const d = path.resolve(_skillsQs.get('dir') || projectDir || process.cwd());
3183
3390
  const skillsDir = path.join(d, '.claude', 'skills');
3184
3391
  const orgFile = path.join(d, '.monomind', 'orgs', `${orgName}.json`);
3185
3392
 
@@ -3286,13 +3493,13 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3286
3493
  // GET /api/org/:name/search?q=<query> — fuzzy search across org data
3287
3494
  if (req.method === 'GET' && /^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/search(\?.*)?$/i.test(url)) {
3288
3495
  try {
3289
- const urlObj = new URL(`http://x${url}`);
3496
+ const urlObj = new URL(`http://x${req.url}`);
3290
3497
  const orgName = decodeURIComponent(urlObj.pathname.split('/')[3]);
3291
3498
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('{}'); return; }
3292
3499
  const q = (urlObj.searchParams.get('q') || '').toLowerCase().trim();
3293
3500
  if (!q || q.length < 2) { res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }); res.end('{"hits":[]}'); return; }
3294
3501
 
3295
- const d = projectDir || process.cwd();
3502
+ const d = path.resolve(urlObj.searchParams.get('dir') || projectDir || process.cwd());
3296
3503
  const orgsDir = path.join(d, '.monomind', 'orgs');
3297
3504
  const readJ = (f) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch(_) { return null; } };
3298
3505
 
@@ -3310,8 +3517,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3310
3517
  // Goals
3311
3518
  const goals = readJ(path.join(orgsDir, `${orgName}-goals.json`));
3312
3519
  for (const g of (goals?.goals || [])) {
3313
- if (match(g.title) || match(g.description)) {
3314
- hits.push({ type: 'goal', id: g.id, title: g.title, meta: g.status || 'open' });
3520
+ if (match(g.title) || match(g.text) || match(g.goal) || match(g.description)) {
3521
+ hits.push({ type: 'goal', id: g.id, title: g.title || g.text || g.goal, meta: g.status || 'open' });
3315
3522
  }
3316
3523
  }
3317
3524
 
@@ -3339,6 +3546,14 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3339
3546
  }
3340
3547
  }
3341
3548
 
3549
+ // Issues
3550
+ const issuesData = readJ(path.join(orgsDir, `${orgName}-issues.json`));
3551
+ for (const i of (issuesData?.issues || [])) {
3552
+ if (match(i.title) || match(i.description) || match(i.slug)) {
3553
+ hits.push({ type: 'issue', id: i.id || i.slug, title: i.title || i.slug, meta: i.status || 'open' });
3554
+ }
3555
+ }
3556
+
3342
3557
  // Recent activity events
3343
3558
  const eventsFile = path.join(d, 'data', 'mastermind-events.jsonl');
3344
3559
  if (fs.existsSync(eventsFile)) {
@@ -3365,13 +3580,17 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3365
3580
  try {
3366
3581
  const orgName = decodeURIComponent(url.split('/')[3]);
3367
3582
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
3368
- const issuesPath = path.join(projectDir || process.cwd(), '.monomind', 'orgs', `${orgName}-issues.json`);
3583
+ const _issuesQs = new URL(req.url, 'http://localhost').searchParams;
3584
+ const _issuesDir = path.resolve(_issuesQs.get('dir') || projectDir || process.cwd());
3585
+ const issuesPath = path.join(_issuesDir, '.monomind', 'orgs', `${orgName}-issues.json`);
3369
3586
  let payload = { issues: [] };
3370
3587
  try {
3371
3588
  const raw = JSON.parse(fs.readFileSync(issuesPath, 'utf8'));
3372
3589
  payload.issues = (raw.issues || []).map(i => ({
3373
- id: i.id, slug: i.slug, title: i.title, status: i.status || 'open',
3590
+ id: i.id, slug: i.slug, title: i.title, description: i.description || null,
3591
+ status: i.status || 'open',
3374
3592
  priority: i.priority || 'medium', assignee_id: i.assignee_id || null,
3593
+ assignee: i.assignee || i.assignee_id || null,
3375
3594
  project_id: i.project_id || null, parent_id: i.parent_id || null,
3376
3595
  created_at: i.created_at, updated_at: i.updated_at
3377
3596
  }));
@@ -3422,11 +3641,12 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3422
3641
  try {
3423
3642
  const actPath = path.join(base, `${orgName}-activity.jsonl`);
3424
3643
  const lines = fs.readFileSync(actPath, 'utf8').split('\n').filter(Boolean);
3425
- const cutoff = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
3644
+ const cutoffMs = Date.now() - 7 * 24 * 60 * 60 * 1000;
3426
3645
  lines.forEach(line => {
3427
3646
  try {
3428
3647
  const ev = JSON.parse(line);
3429
- if (!ev.ts || ev.ts < cutoff) return;
3648
+ const evMs = typeof ev.ts === 'number' ? ev.ts : (ev.ts ? Date.parse(ev.ts) : 0);
3649
+ if (!evMs || evMs < cutoffMs) return;
3430
3650
  totalRuns++;
3431
3651
  if (ev.type && ev.type.includes('complete')) successRuns++;
3432
3652
  } catch(_) {}
@@ -3460,7 +3680,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3460
3680
  try {
3461
3681
  const orgName = decodeURIComponent(url.split('/')[3]);
3462
3682
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
3463
- const envsPath = path.join(projectDir || process.cwd(), '.monomind', 'orgs', `${orgName}-environments.json`);
3683
+ const _envsQs = new URL(req.url, 'http://localhost').searchParams;
3684
+ const envsPath = path.join(path.resolve(_envsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}-environments.json`);
3464
3685
  let payload = { environments: [], default_env: null };
3465
3686
  try {
3466
3687
  const raw = JSON.parse(fs.readFileSync(envsPath, 'utf8'));
@@ -3486,7 +3707,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3486
3707
  try {
3487
3708
  const orgName = decodeURIComponent(url.split('/')[3]);
3488
3709
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
3489
- const base = path.join(projectDir || process.cwd(), '.monomind', 'orgs');
3710
+ const _wsQs = new URL(req.url, 'http://localhost').searchParams;
3711
+ const base = path.join(path.resolve(_wsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
3490
3712
  let payload = { workspaces: [] };
3491
3713
  try {
3492
3714
  const wsRaw = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-workspaces.json`), 'utf8'));
@@ -3509,17 +3731,18 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3509
3731
  }
3510
3732
 
3511
3733
  // GET /api/org/:name/invites — active invites + pending join requests
3512
- if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/invites$/i)) {
3734
+ if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/invites(\?.*)?$/i)) {
3513
3735
  try {
3514
- const orgName = decodeURIComponent(url.split('/')[3]);
3736
+ const orgName = decodeURIComponent(url.split('/')[3].split('?')[0]);
3515
3737
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
3516
- const base = path.join(projectDir || process.cwd(), '.monomind', 'orgs');
3738
+ const _invitesQs = new URL(req.url, 'http://localhost').searchParams;
3739
+ const base = path.join(path.resolve(_invitesQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
3517
3740
  let payload = { invites: [], join_requests: [] };
3518
3741
  try {
3519
3742
  const raw = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-members.json`), 'utf8'));
3520
3743
  const all = raw.join_requests || [];
3521
3744
  payload.invites = all.filter(r => r.type === 'invite' && r.status === 'pending')
3522
- .map(r => ({ id: r.id, token: r.token ? r.token.slice(0, 8) + '…' : r.id, role: r.role || 'operator', createdAt: r.createdAt || null, status: r.status }));
3745
+ .map(r => ({ id: r.id, token: r.token ? r.token.slice(0, 8) + '…' : r.id, role: r.role || 'operator', createdAt: r.createdAt || null, expiresAt: r.expiresAt || null, status: r.status }));
3523
3746
  payload.join_requests = all.filter(r => r.type !== 'invite' && r.status === 'pending_approval')
3524
3747
  .map(r => ({ id: r.id, requestType: r.requestType || 'human', role: r.role || 'viewer', createdAt: r.createdAt || null, message: r.message || '' }));
3525
3748
  } catch(_) { /* members file missing */ }
@@ -3534,7 +3757,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3534
3757
  try {
3535
3758
  const orgName = decodeURIComponent(url.split('/')[3]);
3536
3759
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
3537
- const base = path.join(projectDir || process.cwd(), '.monomind');
3760
+ const _pluginsQs = new URL(req.url, 'http://localhost').searchParams;
3761
+ const base = path.join(path.resolve(_pluginsQs.get('dir') || projectDir || process.cwd()), '.monomind');
3538
3762
  let plugins = [];
3539
3763
  try {
3540
3764
  const reg = JSON.parse(fs.readFileSync(path.join(base, 'plugins', 'registry.json'), 'utf8'));
@@ -3572,7 +3796,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3572
3796
  try {
3573
3797
  const orgName = decodeURIComponent(url.split('/')[3]);
3574
3798
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
3575
- const base = path.join(projectDir || process.cwd(), '.monomind', 'orgs');
3799
+ const _myIssuesQs = new URL(req.url, 'http://localhost').searchParams;
3800
+ const base = path.join(path.resolve(_myIssuesQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
3576
3801
  let payload = { issues: [] };
3577
3802
  try {
3578
3803
  const raw = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-issues.json`), 'utf8'));
@@ -3582,12 +3807,15 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3582
3807
  .map(i => ({
3583
3808
  id: i.id,
3584
3809
  title: i.title || null,
3810
+ description: i.description || null,
3585
3811
  status: i.status || 'open',
3586
3812
  priority: i.priority || 'medium',
3587
3813
  assigneeId: i.assigneeId || i.assigned_to || null,
3588
3814
  projectId: i.projectId || i.project_id || null,
3589
3815
  createdAt: i.createdAt || null,
3590
3816
  lastActivityAt: i.lastActivityAt || null,
3817
+ updated_at: i.updated_at || i.lastActivityAt || i.updatedAt || i.ts || null,
3818
+ ts: i.ts || i.updated_at || i.lastActivityAt || null,
3591
3819
  }));
3592
3820
  } catch(_) { /* issues file missing */ }
3593
3821
  res.writeHead(200, { 'Content-Type': 'application/json' });
@@ -3601,7 +3829,9 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3601
3829
  try {
3602
3830
  const orgName = decodeURIComponent(url.split('/')[3]);
3603
3831
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
3604
- const base = path.join(projectDir || process.cwd(), '.monomind', 'orgs');
3832
+ const _agentsQs = new URL(req.url, 'http://localhost').searchParams;
3833
+ const d = path.resolve(_agentsQs.get('dir') || projectDir || process.cwd());
3834
+ const base = path.join(d, '.monomind', 'orgs');
3605
3835
  const readJsonSafe = (f) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch(_) { return null; } };
3606
3836
  const config = readJsonSafe(path.join(base, `${orgName}.json`)) || {};
3607
3837
  const stateData = readJsonSafe(path.join(base, `${orgName}-state.json`)) || {};
@@ -3614,8 +3844,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3614
3844
  return {
3615
3845
  id: r.id,
3616
3846
  title: r.title || r.id,
3617
- adapterType: (r.adapter && r.adapter.type) || null,
3618
- adapterModel: (r.adapter && r.adapter.model) || null,
3847
+ adapterType: r.agent_type || r.type || null,
3848
+ adapterModel: (r.adapter_config && r.adapter_config.model) || (r.adapter && r.adapter.model) || null,
3619
3849
  governance: r.governance || null,
3620
3850
  reportsTo: r.reports_to || null,
3621
3851
  status: s.status || 'idle',
@@ -3636,7 +3866,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3636
3866
  try {
3637
3867
  const orgName = decodeURIComponent(url.split('/')[3].split('?')[0]);
3638
3868
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
3639
- const base = path.join(projectDir || process.cwd(), '.monomind', 'orgs');
3869
+ const _approvalsQs = new URL(req.url, 'http://localhost').searchParams;
3870
+ const base = path.join(path.resolve(_approvalsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
3640
3871
  const readJsonSafe = (f) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch(_) { return null; } };
3641
3872
  const data = readJsonSafe(path.join(base, `${orgName}-approvals.json`)) || { approvals: [] };
3642
3873
  const approvals = (data.approvals || [])
@@ -3671,7 +3902,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3671
3902
  // Body: { action: "approve" | "reject" | "revision_requested" }
3672
3903
  if (req.method === 'POST' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/approvals\/[^/]+$/i)) {
3673
3904
  let body = '';
3674
- for await (const chunk of req) body += chunk;
3905
+ for await (const chunk of req) { body += chunk; if (body.length > 2097152) { req.destroy(); break; } }
3675
3906
  try {
3676
3907
  const parts = url.split('/');
3677
3908
  const orgName = decodeURIComponent(parts[3]);
@@ -3683,7 +3914,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3683
3914
  if (!['approve', 'reject', 'revision_requested'].includes(action)) {
3684
3915
  res.writeHead(400); res.end('{"error":"action must be approve, reject, or revision_requested"}'); return;
3685
3916
  }
3686
- const base = path.join(projectDir || process.cwd(), '.monomind', 'orgs');
3917
+ const _postApprovalsQs = new URL(req.url, 'http://localhost').searchParams;
3918
+ const base = path.join(path.resolve(_postApprovalsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
3687
3919
  const approvalsFile = path.join(base, `${orgName}-approvals.json`);
3688
3920
  let data = { approvals: [] };
3689
3921
  try { data = JSON.parse(fs.readFileSync(approvalsFile, 'utf8')); } catch(_) {}
@@ -3701,7 +3933,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3701
3933
  fs.renameSync(tmp, approvalsFile);
3702
3934
  // Emit org:approval:resolved event so boss agent unblocks
3703
3935
  const event = { type: 'org:approval:resolved', org: orgName, approval_id: approvalId, status, ts: Date.now() };
3704
- try { fs.appendFileSync(path.join(projectDir || process.cwd(), 'data', 'mastermind-events.jsonl'), JSON.stringify(event) + '\n'); } catch(_) {}
3936
+ try { fs.appendFileSync(path.join(path.resolve(_postApprovalsQs.get('dir') || projectDir || process.cwd()), 'data', 'mastermind-events.jsonl'), JSON.stringify(event) + '\n'); } catch(_) {}
3705
3937
  const msg = `data: ${JSON.stringify(event)}\n\n`;
3706
3938
  for (const c of mmSseClients) { try { c.write(msg); } catch(_) { mmSseClients.delete(c); } }
3707
3939
  res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
@@ -3715,7 +3947,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3715
3947
  try {
3716
3948
  const orgName = decodeURIComponent(url.split('/')[3]);
3717
3949
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
3718
- const base = path.join(projectDir || process.cwd(), '.monomind', 'orgs');
3950
+ const _secretsQs = new URL(req.url, 'http://localhost').searchParams;
3951
+ const base = path.join(path.resolve(_secretsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
3719
3952
  const secretsDir = path.join(base, '.secrets');
3720
3953
  const readJsonSafe = (f) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch(_) { return null; } };
3721
3954
  // Read secrets index — NEVER expose actual values
@@ -3723,6 +3956,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3723
3956
  const data = readJsonSafe(indexFile) || { secrets: [] };
3724
3957
  const secrets = (data.secrets || []).map(s => ({
3725
3958
  name: s.name,
3959
+ purpose: s.purpose || null,
3726
3960
  maskedRef: s.maskedRef || `${(s.name||'').substring(0,4)}***`,
3727
3961
  status: s.status || 'active',
3728
3962
  createdAt: s.createdAt || null,
@@ -3742,7 +3976,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3742
3976
  try {
3743
3977
  const orgName = decodeURIComponent(url.split('/')[3]);
3744
3978
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
3745
- const base = path.join(projectDir || process.cwd(), '.monomind', 'orgs');
3979
+ const _budgetsQs = new URL(req.url, 'http://localhost').searchParams;
3980
+ const base = path.join(path.resolve(_budgetsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
3746
3981
  let budgetData = { org_budget: {}, agent_budgets: {}, period: 'monthly', currency: 'USD' };
3747
3982
  try { budgetData = JSON.parse(fs.readFileSync(path.join(base, `${orgName}-budgets.json`), 'utf8')); } catch(_) {}
3748
3983
  // Enrich with per-agent spend from state file.
@@ -3785,12 +4020,17 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3785
4020
  try {
3786
4021
  const orgName = decodeURIComponent(url.split('/')[3]);
3787
4022
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
3788
- const threadsFile = path.join(projectDir || process.cwd(), '.monomind', 'orgs', `${orgName}-threads.jsonl`);
4023
+ const _threadsQs = new URL(req.url, 'http://localhost').searchParams;
4024
+ const threadsFile = path.join(path.resolve(_threadsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}-threads.jsonl`);
3789
4025
  let threads = [];
3790
4026
  try {
3791
4027
  const lines = fs.readFileSync(threadsFile, 'utf8').split('\n').filter(l => l.trim());
3792
4028
  threads = lines.map(l => { try { return JSON.parse(l); } catch(_) { return null; } }).filter(Boolean);
3793
- threads = threads.filter(t => t.type === 'thread' || !t.type);
4029
+ threads = threads.filter(t => t.type === 'thread' || !t.type).map(t => ({
4030
+ ...t,
4031
+ author: t.author || t.authorName || t.createdBy || t.authorId || null,
4032
+ messageCount: t.messageCount != null ? t.messageCount : (Array.isArray(t.messages) ? t.messages.length : (typeof t.messages === 'number' ? t.messages : null)),
4033
+ }));
3794
4034
  } catch(_) {}
3795
4035
  res.writeHead(200, { 'Content-Type': 'application/json' });
3796
4036
  res.end(JSON.stringify({ threads }));
@@ -3804,7 +4044,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3804
4044
  try {
3805
4045
  const orgName = decodeURIComponent(url.split('/')[3].split('?')[0]);
3806
4046
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
3807
- const joinFile = path.join(projectDir || process.cwd(), '.monomind', 'orgs', `${orgName}-join-requests.json`);
4047
+ const _joinQs = new URL(req.url, 'http://localhost').searchParams;
4048
+ const joinFile = path.join(path.resolve(_joinQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}-join-requests.json`);
3808
4049
  let requests = [];
3809
4050
  try {
3810
4051
  const raw = fs.readFileSync(joinFile, 'utf8');
@@ -3831,7 +4072,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3831
4072
  try {
3832
4073
  const orgName = decodeURIComponent(url.split('/')[3]);
3833
4074
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
3834
- const goalsFile = path.join(projectDir || process.cwd(), '.monomind', 'orgs', `${orgName}-goals.json`);
4075
+ const _goalsQs = new URL(req.url, 'http://localhost').searchParams;
4076
+ const goalsFile = path.join(path.resolve(_goalsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}-goals.json`);
3835
4077
  let data = { goals: [] };
3836
4078
  try { data = JSON.parse(fs.readFileSync(goalsFile, 'utf8')); } catch(_) {}
3837
4079
  res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
@@ -3845,7 +4087,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3845
4087
  try {
3846
4088
  const orgName = decodeURIComponent(url.split('/')[3]);
3847
4089
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
3848
- const routinesFile = path.join(projectDir || process.cwd(), '.monomind', 'orgs', `${orgName}-routines.json`);
4090
+ const _routinesQs = new URL(req.url, 'http://localhost').searchParams;
4091
+ const routinesFile = path.join(path.resolve(_routinesQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}-routines.json`);
3849
4092
  let data = { routines: [] };
3850
4093
  try { data = JSON.parse(fs.readFileSync(routinesFile, 'utf8')); } catch(_) {}
3851
4094
  res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
@@ -3858,13 +4101,14 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3858
4101
  // Body: { goals: [{id, title, description, status, priority, assignee_id, created_at}] }
3859
4102
  if (req.method === 'POST' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/goals$/i)) {
3860
4103
  let body = '';
3861
- for await (const chunk of req) body += chunk;
4104
+ for await (const chunk of req) { body += chunk; if (body.length > 2097152) { req.destroy(); break; } }
3862
4105
  try {
3863
4106
  const orgName = decodeURIComponent(url.split('/')[3]);
3864
4107
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
3865
4108
  const parsed = JSON.parse(body);
3866
4109
  if (!parsed || !Array.isArray(parsed.goals)) { res.writeHead(400); res.end('{"error":"goals array required"}'); return; }
3867
- const goalsFile = path.join(projectDir || process.cwd(), '.monomind', 'orgs', `${orgName}-goals.json`);
4110
+ const _postGoalsQs = new URL(req.url, 'http://localhost').searchParams;
4111
+ const goalsFile = path.join(path.resolve(_postGoalsQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}-goals.json`);
3868
4112
  const tmp = `${goalsFile}.tmp`;
3869
4113
  const payload = { org: orgName, updated_at: new Date().toISOString(), goals: parsed.goals };
3870
4114
  fs.writeFileSync(tmp, JSON.stringify(payload, null, 2), 'utf-8');
@@ -3879,13 +4123,14 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3879
4123
  // Body: { routines: [{name, description, schedule, enabled, last_run, next_run}] }
3880
4124
  if (req.method === 'POST' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/routines$/i)) {
3881
4125
  let body = '';
3882
- for await (const chunk of req) body += chunk;
4126
+ for await (const chunk of req) { body += chunk; if (body.length > 2097152) { req.destroy(); break; } }
3883
4127
  try {
3884
4128
  const orgName = decodeURIComponent(url.split('/')[3]);
3885
4129
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
3886
4130
  const parsed = JSON.parse(body);
3887
4131
  if (!parsed || !Array.isArray(parsed.routines)) { res.writeHead(400); res.end('{"error":"routines array required"}'); return; }
3888
- const routinesFile = path.join(projectDir || process.cwd(), '.monomind', 'orgs', `${orgName}-routines.json`);
4132
+ const _postRoutinesQs = new URL(req.url, 'http://localhost').searchParams;
4133
+ const routinesFile = path.join(path.resolve(_postRoutinesQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs', `${orgName}-routines.json`);
3889
4134
  const tmp = `${routinesFile}.tmp`;
3890
4135
  const payload = { org: orgName, updated_at: new Date().toISOString(), routines: parsed.routines };
3891
4136
  fs.writeFileSync(tmp, JSON.stringify(payload, null, 2), 'utf-8');
@@ -3896,16 +4141,95 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3896
4141
  return;
3897
4142
  }
3898
4143
 
3899
- // DELETE /api/orgs/:name — delete an org config and all associated data files
3900
- if (req.method === 'DELETE' && url.match(/^\/api\/orgs\/[a-z0-9][a-z0-9_-]{0,63}$/i)) {
4144
+ // GET /api/org/:name/filesall files related to an org
4145
+ if (req.method === 'GET' && url.match(/^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/files$/i)) {
3901
4146
  try {
3902
4147
  const orgName = decodeURIComponent(url.split('/')[3]);
4148
+ if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('{"error":"Invalid org name"}'); return; }
4149
+ const _filesQs = new URL(req.url, 'http://localhost').searchParams;
4150
+ const d = path.resolve(_filesQs.get('dir') || projectDir || process.cwd());
4151
+ const orgsDir = path.join(d, '.monomind', 'orgs');
4152
+ const files = [];
4153
+ const seen = new Set();
4154
+ const addFile = (fp, type) => {
4155
+ if (seen.has(fp)) return; seen.add(fp);
4156
+ try { const st = fs.statSync(fp); files.push({ name: path.basename(fp), path: fp, type, size: st.size, mtime: st.mtime.toISOString() }); } catch (_) {}
4157
+ };
4158
+ addFile(path.join(orgsDir, orgName + '.json'), 'config');
4159
+ for (const s of ['-state','-approvals','-goals','-routines','-projects','-members','-issues','-threads','-budgets']) {
4160
+ const fp = path.join(orgsDir, orgName + s + '.json');
4161
+ if (fs.existsSync(fp)) addFile(fp, s.slice(1));
4162
+ }
4163
+ const walkDir = (dir, depth) => {
4164
+ if (depth > 3) return;
4165
+ let entries; try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; }
4166
+ for (const e of entries) {
4167
+ if (e.name.startsWith('.')) continue;
4168
+ const fp = path.join(dir, e.name);
4169
+ if (e.isDirectory()) walkDir(fp, depth + 1);
4170
+ else addFile(fp, 'generated');
4171
+ }
4172
+ };
4173
+ const orgWorkDir = path.join(orgsDir, orgName);
4174
+ if (fs.existsSync(orgWorkDir)) walkDir(orgWorkDir, 0);
4175
+ let orgCfg = null;
4176
+ try { orgCfg = JSON.parse(fs.readFileSync(path.join(orgsDir, orgName + '.json'), 'utf8')); } catch (_) {}
4177
+ if (orgCfg && Array.isArray(orgCfg.roles)) {
4178
+ const agentsDir = path.join(d, '.claude', 'agents');
4179
+ const walkAgents = (dir) => {
4180
+ let entries; try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_) { return; }
4181
+ for (const e of entries) {
4182
+ if (e.isDirectory()) { walkAgents(path.join(dir, e.name)); continue; }
4183
+ if (!e.name.endsWith('.md')) continue;
4184
+ const fp = path.join(dir, e.name);
4185
+ const base = e.name.replace('.md', '').toLowerCase();
4186
+ if (orgCfg.roles.some(r => base === (r.id||'').toLowerCase() || base === (r.agent_type||'').toLowerCase() || (r.instructions_file||'').endsWith(e.name))) addFile(fp, 'agent-definition');
4187
+ }
4188
+ };
4189
+ if (fs.existsSync(agentsDir)) walkAgents(agentsDir);
4190
+ }
4191
+ files.sort((a, b) => new Date(b.mtime) - new Date(a.mtime));
4192
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
4193
+ res.end(JSON.stringify(files));
4194
+ } catch (e) { res.writeHead(500); res.end(JSON.stringify({ error: e.message })); }
4195
+ return;
4196
+ }
4197
+
4198
+ // GET /api/file-content — return raw text content of a .monomind file
4199
+ if (req.method === 'GET' && url === '/api/file-content') {
4200
+ try {
4201
+ const _fcQs = new URL(req.url, 'http://localhost').searchParams;
4202
+ const rawPath = _fcQs.get('path');
4203
+ const baseDir = path.resolve(_fcQs.get('dir') || projectDir || process.cwd());
4204
+ if (!rawPath) { res.writeHead(400); res.end('Missing path'); return; }
4205
+ const resolved = path.resolve(rawPath);
4206
+ // Security: must be inside .monomind of the project dir
4207
+ const monomindDir = path.join(baseDir, '.monomind');
4208
+ if (!resolved.startsWith(monomindDir + path.sep) && resolved !== monomindDir) {
4209
+ res.writeHead(403); res.end('Forbidden'); return;
4210
+ }
4211
+ if (!fs.existsSync(resolved)) { res.writeHead(404); res.end('Not found'); return; }
4212
+ const stat = fs.statSync(resolved);
4213
+ if (!stat.isFile()) { res.writeHead(400); res.end('Not a file'); return; }
4214
+ if (stat.size > 524288) { res.writeHead(413); res.end('File too large'); return; }
4215
+ const content = fs.readFileSync(resolved, 'utf8');
4216
+ res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8', 'Access-Control-Allow-Origin': '*' });
4217
+ res.end(content);
4218
+ } catch(_) { res.writeHead(500); res.end('Internal error'); }
4219
+ return;
4220
+ }
4221
+
4222
+ // DELETE /api/orgs/:name — delete an org config and all associated data files
4223
+ if (req.method === 'DELETE' && url.match(/^\/api\/orgs\/[a-z0-9][a-z0-9_-]{0,63}(\?.*)?$/i)) {
4224
+ try {
4225
+ const orgName = decodeURIComponent(url.split('/')[3].split('?')[0]);
3903
4226
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
3904
- const orgsDir = path.join(projectDir || process.cwd(), '.monomind', 'orgs');
4227
+ const _delOrgQs = new URL(req.url, 'http://localhost').searchParams;
4228
+ const orgsDir = path.join(path.resolve(_delOrgQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
3905
4229
  const configFile = path.join(orgsDir, `${orgName}.json`);
3906
4230
  if (!fs.existsSync(configFile)) { res.writeHead(404); res.end('{"error":"org not found"}'); return; }
3907
4231
  // Remove all org-associated files (config + state + data)
3908
- const suffixes = ['', '-state', '-goals', '-routines', '-approvals', '-activity', '-issues', '-members', '-projects', '-workspaces', '-worktrees', '-environments', '-plugins', '-adapters'];
4232
+ const suffixes = ['', '-state', '-goals', '-routines', '-approvals', '-activity', '-issues', '-members', '-projects', '-workspaces', '-worktrees', '-environments', '-plugins', '-adapters', '-budgets', '-threads', '-secrets', '-join-requests', '-bootstrap', '-project-workspaces', '-approval-comments', '-skills'];
3909
4233
  for (const suf of suffixes) {
3910
4234
  const f = path.join(orgsDir, `${orgName}${suf}.json`);
3911
4235
  try { if (fs.existsSync(f)) fs.unlinkSync(f); } catch(_) {}
@@ -3914,6 +4238,18 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3914
4238
  }
3915
4239
  // Remove stop file if present
3916
4240
  try { fs.unlinkSync(path.join(orgsDir, '.stops', `${orgName}.stop`)); } catch(_) {}
4241
+ // Remove org subdirectory under .monomind/orgs/ (legacy flat-file location)
4242
+ try { const orgWorkDir = path.join(orgsDir, orgName); if (fs.existsSync(orgWorkDir)) fs.rmSync(orgWorkDir, { recursive: true, force: true }); } catch(_) {}
4243
+ // Remove org subdirectory under git-safe location (.git/monomind/orgs/<name>/) so run
4244
+ // files written by the worktree-aware path (feat 880f034e) are also cleaned up on delete
4245
+ try {
4246
+ const _delWorkDir = path.resolve(_delOrgQs.get('dir') || projectDir || process.cwd());
4247
+ const _delGitMonoDir = _getGitMonomindDir(_delWorkDir);
4248
+ if (_delGitMonoDir) {
4249
+ const gitOrgDir = path.join(_delGitMonoDir, 'orgs', orgName);
4250
+ if (fs.existsSync(gitOrgDir)) fs.rmSync(gitOrgDir, { recursive: true, force: true });
4251
+ }
4252
+ } catch(_) {}
3917
4253
  // Remove loop prompt file if present (created for scheduled orgs by createorg)
3918
4254
  try { const lpf = path.join(path.resolve(projectDir || process.cwd()), '.monomind', 'loops', `${orgName}.md`); if (fs.existsSync(lpf)) fs.unlinkSync(lpf); } catch(_) {}
3919
4255
  // Emit org:delete event
@@ -3932,13 +4268,15 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3932
4268
  try {
3933
4269
  const orgName = decodeURIComponent(url.split('/')[3]);
3934
4270
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end('Invalid org name'); return; }
4271
+ const _stopOrgQs = new URL(req.url, 'http://localhost').searchParams;
4272
+ const _stopOrgBase = path.resolve(_stopOrgQs.get('dir') || projectDir || process.cwd());
3935
4273
  const stopEvent = { type: 'org:stop', org: orgName, ts: Date.now() };
3936
- const dataDir = path.join(projectDir || process.cwd(), 'data');
4274
+ const dataDir = path.join(_stopOrgBase, 'data');
3937
4275
  try { fs.mkdirSync(dataDir, { recursive: true }); } catch(_) {}
3938
4276
  try { fs.appendFileSync(path.join(dataDir, 'mastermind-events.jsonl'), JSON.stringify(stopEvent) + '\n'); } catch(_) {}
3939
4277
  // Write stop marker file for boss agent to detect
3940
4278
  try {
3941
- const stopDir = path.join(projectDir || process.cwd(), '.monomind', 'orgs', '.stops');
4279
+ const stopDir = path.join(_stopOrgBase, '.monomind', 'orgs', '.stops');
3942
4280
  fs.mkdirSync(stopDir, { recursive: true });
3943
4281
  fs.writeFileSync(path.join(stopDir, `${orgName}.stop`), String(Date.now()));
3944
4282
  } catch(_) {}
@@ -3953,7 +4291,7 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3953
4291
  // POST /api/orgs/:name/copy — copy org config to another project directory
3954
4292
  if (req.method === 'POST' && url.match(/^\/api\/orgs\/[a-z0-9][a-z0-9_-]{0,63}\/copy$/i)) {
3955
4293
  let body = '';
3956
- for await (const chunk of req) body += chunk;
4294
+ for await (const chunk of req) { body += chunk; if (body.length > 2097152) { req.destroy(); break; } }
3957
4295
  try {
3958
4296
  const orgName = decodeURIComponent(url.split('/')[3]);
3959
4297
  if (orgName.length > 64 || !/^[a-z0-9][a-z0-9_-]*$/i.test(orgName)) { res.writeHead(400); res.end(JSON.stringify({ error: 'Invalid org name' })); return; }
@@ -3962,7 +4300,8 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3962
4300
  const destination = payload.destination ? String(payload.destination).trim() : '';
3963
4301
  if (!destination) { res.writeHead(400); res.end(JSON.stringify({ error: 'destination is required' })); return; }
3964
4302
  if (!path.isAbsolute(destination)) { res.writeHead(400); res.end(JSON.stringify({ error: 'destination must be an absolute path' })); return; }
3965
- const srcOrgsDir = path.join(projectDir || process.cwd(), '.monomind', 'orgs');
4303
+ const _copyOrgQs = new URL(req.url, 'http://localhost').searchParams;
4304
+ const srcOrgsDir = path.join(path.resolve(_copyOrgQs.get('dir') || projectDir || process.cwd()), '.monomind', 'orgs');
3966
4305
  const srcFile = path.join(srcOrgsDir, `${orgName}.json`);
3967
4306
  if (!fs.existsSync(srcFile)) { res.writeHead(404); res.end(JSON.stringify({ error: 'org not found' })); return; }
3968
4307
  const destOrgsDir = path.join(path.resolve(destination), '.monomind', 'orgs');
@@ -3975,16 +4314,87 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3975
4314
  return;
3976
4315
  }
3977
4316
 
4317
+ // GET /api/org/:name/runs — list structured run files for an org
4318
+ if (req.method === 'GET' && /^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/runs(\?.*)?$/i.test(url)) {
4319
+ try {
4320
+ const _rQs = new URL(req.url, 'http://localhost').searchParams;
4321
+ const _rOrgName = decodeURIComponent(url.split('/')[3] || '');
4322
+ const _rWorkDir = path.resolve(_rQs.get('dir') || projectDir || process.cwd());
4323
+ const _rMonoDir = _getGitMonomindDir(_rWorkDir) || path.join(_rWorkDir, '.monomind');
4324
+ const _rDir = path.join(_rMonoDir, 'orgs', _rOrgName, 'runs');
4325
+ const runs = [];
4326
+ if (fs.existsSync(_rDir)) {
4327
+ const files = fs.readdirSync(_rDir).filter(f => f.endsWith('.jsonl')).sort().reverse();
4328
+ for (const f of files.slice(0, 50)) {
4329
+ try {
4330
+ const raw = fs.readFileSync(path.join(_rDir, f), 'utf8');
4331
+ const allLines = raw.split('\n').filter(Boolean);
4332
+ const eventCount = allLines.length;
4333
+ const parse = l => { try { return JSON.parse(l); } catch { return null; } };
4334
+ const headEvents = allLines.slice(0, 5).map(parse).filter(Boolean);
4335
+ const tailEvents = allLines.slice(-5).map(parse).filter(Boolean);
4336
+ const first = headEvents.find(e => e.type === 'run:start') || headEvents[0];
4337
+ const last = tailEvents.slice().reverse().find(e => e.type === 'run:complete' || e.type === 'org:complete');
4338
+ const cycles = allLines.filter(l => l.includes('"org:checkpoint"')).length;
4339
+ const lastEvent = tailEvents[tailEvents.length - 1] || headEvents[headEvents.length - 1];
4340
+ const ageMs = lastEvent?.ts ? Date.now() - lastEvent.ts : Infinity;
4341
+ const isStale = !last && ageMs > 30 * 60 * 1000;
4342
+ runs.push({ runId: f.replace('.jsonl', ''), startedAt: first?.ts || 0, endedAt: last?.ts || 0,
4343
+ status: last ? 'complete' : isStale ? 'stale' : 'running',
4344
+ eventCount, cycleCount: cycles, goal: first?.goal || '', bossRole: first?.bossRole || '' });
4345
+ } catch (_) {}
4346
+ }
4347
+ }
4348
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
4349
+ res.end(JSON.stringify(runs));
4350
+ } catch (_) { res.writeHead(500); res.end('[]'); }
4351
+ return;
4352
+ }
4353
+
4354
+ // GET /api/org/:name/runs/:runId — get all events for a specific run
4355
+ if (req.method === 'GET' && /^\/api\/org\/[a-z0-9][a-z0-9_-]{0,63}\/runs\/[a-z0-9][a-z0-9_-]{0,79}(\?.*)?$/i.test(url)) {
4356
+ try {
4357
+ const _rvQs = new URL(req.url, 'http://localhost').searchParams;
4358
+ const _rvParts = url.replace(/\?.*$/, '').split('/');
4359
+ const _rvOrgName = decodeURIComponent(_rvParts[3] || '');
4360
+ const _rvRunId = decodeURIComponent(_rvParts[5] || '');
4361
+ const _rvWorkDir = path.resolve(_rvQs.get('dir') || projectDir || process.cwd());
4362
+ const _rvMonoDir = _getGitMonomindDir(_rvWorkDir) || path.join(_rvWorkDir, '.monomind');
4363
+ const _rvFile = path.join(_rvMonoDir, 'orgs', _rvOrgName, 'runs', `${_rvRunId}.jsonl`);
4364
+ if (!fs.existsSync(_rvFile)) { res.writeHead(404); res.end('{"error":"run not found"}'); return; }
4365
+ const events = fs.readFileSync(_rvFile, 'utf8').split('\n').filter(Boolean)
4366
+ .map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
4367
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache' });
4368
+ res.end(JSON.stringify(events));
4369
+ } catch (_) { res.writeHead(500); res.end('[]'); }
4370
+ return;
4371
+ }
4372
+
3978
4373
  // ------------------------------------------------- Mastermind event system
3979
4374
  // POST /api/mastermind/event — ingest event from mastermind skill
3980
4375
  if (req.method === 'POST' && url === '/api/mastermind/event') {
3981
4376
  let body = '';
3982
- for await (const chunk of req) body += chunk;
4377
+ for await (const chunk of req) { body += chunk; if (body.length > 2097152) { req.destroy(); break; } }
3983
4378
  let event = {};
3984
4379
  try { event = JSON.parse(body); } catch (_) {}
3985
4380
  event.ts = event.ts || Date.now();
3986
- // Use project path from event if provided (multi-project support)
3987
- const eventProject = event.project && path.isAbsolute(event.project) ? event.project : null;
4381
+ // Use project path from event if provided (multi-project support).
4382
+ // Security: path.isAbsolute() alone is insufficient — an attacker can
4383
+ // supply event.project="/etc" and cause writes to system directories.
4384
+ // Only accept paths that resolve to an existing directory AND are not
4385
+ // the filesystem root (/), AND are not obviously system paths.
4386
+ // Cap to 4096 chars to prevent OOM from huge path strings.
4387
+ const _rawProject = event.project;
4388
+ let eventProject = null;
4389
+ if (typeof _rawProject === 'string' && _rawProject.length > 0 && _rawProject.length <= 4096
4390
+ && path.isAbsolute(_rawProject)) {
4391
+ // Reject filesystem root and common system directories
4392
+ const _norm = path.resolve(_rawProject);
4393
+ const _systemPaths = ['/', '/etc', '/usr', '/bin', '/sbin', '/lib', '/lib64', '/boot', '/dev', '/sys', '/proc', '/tmp'];
4394
+ if (!_systemPaths.includes(_norm) && !_systemPaths.some(p => _norm.startsWith(p + '/'))) {
4395
+ eventProject = _norm;
4396
+ }
4397
+ }
3988
4398
  const root = eventProject || projectDir || process.cwd();
3989
4399
  const dataDir = path.join(root, 'data');
3990
4400
  try { fs.mkdirSync(dataDir, { recursive: true }); } catch (_) {}
@@ -3998,6 +4408,28 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
3998
4408
  } catch (_) {}
3999
4409
  }
4000
4410
  try { fs.appendFileSync(path.join(dataDir, 'mastermind-events.jsonl'), JSON.stringify(event) + '\n'); } catch (_) {}
4411
+ // Track active runs and route org events to run files
4412
+ if (event.org) {
4413
+ const _orgKey = String(event.org).trim();
4414
+ // Any event with both org+runId updates the active run map (run:start written directly to file so org:start is first via curl)
4415
+ if (event.runId) activeOrgRuns.set(_orgKey, String(event.runId).trim());
4416
+ else if (activeOrgRuns.has(_orgKey)) event.runId = activeOrgRuns.get(_orgKey);
4417
+ if (event.type === 'run:complete' || event.type === 'org:complete') activeOrgRuns.delete(_orgKey);
4418
+ }
4419
+ // Persist to git-safe run file (survives branch switches + shared across worktrees)
4420
+ if (event.org && event.runId) {
4421
+ try {
4422
+ const _orn = String(event.org).trim();
4423
+ const _rid = String(event.runId).trim();
4424
+ if (_orn.length > 0 && _orn.length <= 64 && /^[a-z0-9][a-z0-9_-]*$/i.test(_orn)
4425
+ && _rid.length > 0 && _rid.length <= 80 && /^[a-z0-9][a-z0-9_-]*$/i.test(_rid)) {
4426
+ const _monoDir = _getGitMonomindDir(root) || path.join(root, '.monomind');
4427
+ const _runDir = path.join(_monoDir, 'orgs', _orn, 'runs');
4428
+ fs.mkdirSync(_runDir, { recursive: true });
4429
+ fs.appendFileSync(path.join(_runDir, `${_rid}.jsonl`), JSON.stringify(event) + '\n');
4430
+ }
4431
+ } catch (_) {}
4432
+ }
4001
4433
  // Persist session
4002
4434
  try {
4003
4435
  const sessFile = path.join(dataDir, 'mastermind-sessions.json');
@@ -4016,12 +4448,19 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
4016
4448
  }
4017
4449
  }
4018
4450
  fs.writeFileSync(sessFile, JSON.stringify(sessions.slice(0, 50), null, 2));
4019
- // Also write individual session file for direct traceability
4451
+ // Also write individual session file for direct traceability.
4452
+ // Security: validate event.session before using it as a filename to
4453
+ // prevent path traversal (e.g. "../../../etc/cron.d/payload").
4020
4454
  const sessionObj = sessions.find(s => s.id === event.session);
4021
4455
  if (sessionObj) {
4022
4456
  const sessDir = path.join(dataDir, 'sessions');
4023
4457
  try { fs.mkdirSync(sessDir, { recursive: true }); } catch (_) {}
4024
- try { fs.writeFileSync(path.join(sessDir, `${event.session}.json`), JSON.stringify(sessionObj, null, 2)); } catch (_) {}
4458
+ try {
4459
+ const _sid = String(event.session || '').trim();
4460
+ if (_sid.length > 0 && _sid.length <= 128 && /^[a-zA-Z0-9_.-]+$/.test(_sid)) {
4461
+ fs.writeFileSync(path.join(sessDir, `${_sid}.json`), JSON.stringify(sessionObj, null, 2));
4462
+ }
4463
+ } catch (_) {}
4025
4464
  }
4026
4465
  } catch (_) {}
4027
4466
  // For org:stop events, write a stop marker the boss agent can detect