monomind 1.14.6 → 1.15.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 (314) hide show
  1. package/.claude/agents/reengineer-squad/boss.md +113 -0
  2. package/.claude/agents/reengineer-squad/critic-architect.md +132 -0
  3. package/.claude/agents/reengineer-squad/git-manager.md +145 -0
  4. package/.claude/agents/reengineer-squad/idea-generator.md +95 -0
  5. package/.claude/agents/reengineer-squad/implementer.md +112 -0
  6. package/.claude/agents/reengineer-squad/integration-planner.md +112 -0
  7. package/.claude/agents/reengineer-squad/source-analyst.md +103 -0
  8. package/.claude/agents/reengineer-squad/target-analyst.md +118 -0
  9. package/.claude/agents/reengineer-squad/tester.md +105 -0
  10. package/.claude/commands/mastermind/master.md +35 -14
  11. package/.claude/helpers/handlers/capture-handler.cjs +155 -18
  12. package/.claude/helpers/monolean-activate.cjs +20 -0
  13. package/.claude/helpers/monolean-config.cjs +76 -0
  14. package/.claude/helpers/monolean-instructions.cjs +109 -0
  15. package/.claude/helpers/monolean-propagate.cjs +9 -0
  16. package/.claude/helpers/monolean-tracker.cjs +18 -0
  17. package/.claude/helpers/skill-registry.json +2 -2
  18. package/.claude/settings.json +34 -2
  19. package/.claude/skills/agent-browser-testing/SKILL.md +301 -18
  20. package/.claude/skills/mastermind/runorg.md +69 -23
  21. package/.claude/skills/monodesign/SKILL.md +32 -1
  22. package/.claude/skills/monodesign/adapt.md +53 -0
  23. package/.claude/skills/monodesign/agents/monodesign-asset-producer.md +100 -0
  24. package/.claude/skills/monodesign/animate.md +65 -0
  25. package/.claude/skills/monodesign/audit.md +89 -0
  26. package/.claude/skills/monodesign/bolder.md +50 -0
  27. package/.claude/skills/monodesign/clarify.md +64 -0
  28. package/.claude/skills/monodesign/colorize.md +68 -0
  29. package/.claude/skills/monodesign/craft.md +51 -0
  30. package/.claude/skills/monodesign/critique.md +66 -0
  31. package/.claude/skills/monodesign/delight.md +47 -0
  32. package/.claude/skills/monodesign/distill.md +56 -0
  33. package/.claude/skills/monodesign/document.md +80 -0
  34. package/.claude/skills/monodesign/extract.md +74 -0
  35. package/.claude/skills/monodesign/harden.md +65 -0
  36. package/.claude/skills/monodesign/live.md +59 -0
  37. package/.claude/skills/monodesign/onboard.md +50 -0
  38. package/.claude/skills/monodesign/optimize.md +64 -0
  39. package/.claude/skills/monodesign/overdrive.md +56 -0
  40. package/.claude/skills/monodesign/polish.md +68 -0
  41. package/.claude/skills/monodesign/quieter.md +57 -0
  42. package/.claude/skills/monodesign/reference/antipatterns-catalog.md +248 -76
  43. package/.claude/skills/monodesign/reference/codex.md +107 -0
  44. package/.claude/skills/monodesign/reference/craft.md +3 -0
  45. package/.claude/skills/monodesign/reference/hooks.md +99 -0
  46. package/.claude/skills/monodesign/reference/image-prompts.md +12 -0
  47. package/.claude/skills/monodesign/shape.md +71 -0
  48. package/.claude/skills/monodesign/teach.md +69 -0
  49. package/.claude/skills/monodesign/typeset.md +59 -0
  50. package/.claude/skills/monolean/SKILL.md +118 -0
  51. package/.claude/skills/monolean-audit/SKILL.md +41 -0
  52. package/.claude/skills/monolean-debt/SKILL.md +46 -0
  53. package/.claude/skills/monolean-help/SKILL.md +60 -0
  54. package/.claude/skills/monolean-review/SKILL.md +57 -0
  55. package/package.json +8 -2
  56. package/packages/@monomind/cli/bin/cli.js +3 -1
  57. package/packages/@monomind/cli/dist/dashboard/server.js +137 -0
  58. package/packages/@monomind/cli/dist/src/__tests__/browse-adapters.test.d.ts +2 -0
  59. package/packages/@monomind/cli/dist/src/__tests__/browse-adapters.test.js +51 -0
  60. package/packages/@monomind/cli/dist/src/__tests__/browse-analyzer.test.d.ts +2 -0
  61. package/packages/@monomind/cli/dist/src/__tests__/browse-analyzer.test.js +68 -0
  62. package/packages/@monomind/cli/dist/src/__tests__/browse-builtin-handlers.test.d.ts +2 -0
  63. package/packages/@monomind/cli/dist/src/__tests__/browse-builtin-handlers.test.js +139 -0
  64. package/packages/@monomind/cli/dist/src/__tests__/browse-cdp.test.d.ts +2 -0
  65. package/packages/@monomind/cli/dist/src/__tests__/browse-cdp.test.js +169 -0
  66. package/packages/@monomind/cli/dist/src/__tests__/browse-dashboard.test.d.ts +2 -0
  67. package/packages/@monomind/cli/dist/src/__tests__/browse-dashboard.test.js +179 -0
  68. package/packages/@monomind/cli/dist/src/__tests__/browse-engine.test.d.ts +2 -0
  69. package/packages/@monomind/cli/dist/src/__tests__/browse-engine.test.js +122 -0
  70. package/packages/@monomind/cli/dist/src/__tests__/browse-expression.test.d.ts +2 -0
  71. package/packages/@monomind/cli/dist/src/__tests__/browse-expression.test.js +54 -0
  72. package/packages/@monomind/cli/dist/src/__tests__/browse-store.test.d.ts +2 -0
  73. package/packages/@monomind/cli/dist/src/__tests__/browse-store.test.js +99 -0
  74. package/packages/@monomind/cli/dist/src/__tests__/browse-workflow-types.test.d.ts +2 -0
  75. package/packages/@monomind/cli/dist/src/__tests__/browse-workflow-types.test.js +33 -0
  76. package/packages/@monomind/cli/dist/src/browser/action-builder/analyzer.d.ts +11 -0
  77. package/packages/@monomind/cli/dist/src/browser/action-builder/analyzer.js +71 -0
  78. package/packages/@monomind/cli/dist/src/browser/action-builder/types.d.ts +47 -0
  79. package/packages/@monomind/cli/dist/src/browser/action-builder/types.js +2 -0
  80. package/packages/@monomind/cli/dist/src/browser/adapters/gemini.d.ts +3 -0
  81. package/packages/@monomind/cli/dist/src/browser/adapters/gemini.js +16 -0
  82. package/packages/@monomind/cli/dist/src/browser/adapters/google.d.ts +3 -0
  83. package/packages/@monomind/cli/dist/src/browser/adapters/google.js +17 -0
  84. package/packages/@monomind/cli/dist/src/browser/adapters/index.d.ts +19 -0
  85. package/packages/@monomind/cli/dist/src/browser/adapters/index.js +23 -0
  86. package/packages/@monomind/cli/dist/src/browser/adapters/instagram.d.ts +3 -0
  87. package/packages/@monomind/cli/dist/src/browser/adapters/instagram.js +17 -0
  88. package/packages/@monomind/cli/dist/src/browser/adapters/linkedin.d.ts +3 -0
  89. package/packages/@monomind/cli/dist/src/browser/adapters/linkedin.js +19 -0
  90. package/packages/@monomind/cli/dist/src/browser/adapters/microsoft.d.ts +3 -0
  91. package/packages/@monomind/cli/dist/src/browser/adapters/microsoft.js +16 -0
  92. package/packages/@monomind/cli/dist/src/browser/adapters/x.d.ts +3 -0
  93. package/packages/@monomind/cli/dist/src/browser/adapters/x.js +19 -0
  94. package/packages/@monomind/cli/dist/src/browser/dashboard/api-types.d.ts +50 -0
  95. package/packages/@monomind/cli/dist/src/browser/dashboard/api-types.js +14 -0
  96. package/packages/@monomind/cli/dist/src/browser/dashboard/server.d.ts +9 -0
  97. package/packages/@monomind/cli/dist/src/browser/dashboard/server.js +62 -0
  98. package/packages/@monomind/cli/dist/src/browser/dashboard/ui.html +1811 -0
  99. package/packages/@monomind/cli/dist/src/browser/workflow/builtin-handlers.d.ts +3 -0
  100. package/packages/@monomind/cli/dist/src/browser/workflow/builtin-handlers.js +343 -0
  101. package/packages/@monomind/cli/dist/src/browser/workflow/engine.d.ts +15 -0
  102. package/packages/@monomind/cli/dist/src/browser/workflow/engine.js +127 -0
  103. package/packages/@monomind/cli/dist/src/browser/workflow/expression.d.ts +4 -0
  104. package/packages/@monomind/cli/dist/src/browser/workflow/expression.js +64 -0
  105. package/packages/@monomind/cli/dist/src/browser/workflow/store.d.ts +24 -0
  106. package/packages/@monomind/cli/dist/src/browser/workflow/store.js +145 -0
  107. package/packages/@monomind/cli/dist/src/browser/workflow/types.d.ts +48 -0
  108. package/packages/@monomind/cli/dist/src/browser/workflow/types.js +2 -0
  109. package/packages/@monomind/cli/dist/src/commands/browse-action.d.ts +4 -0
  110. package/packages/@monomind/cli/dist/src/commands/browse-action.js +151 -0
  111. package/packages/@monomind/cli/dist/src/commands/browse-platform.d.ts +4 -0
  112. package/packages/@monomind/cli/dist/src/commands/browse-platform.js +117 -0
  113. package/packages/@monomind/cli/dist/src/commands/browse-workflow.d.ts +4 -0
  114. package/packages/@monomind/cli/dist/src/commands/browse-workflow.js +153 -0
  115. package/packages/@monomind/cli/dist/src/commands/browse.d.ts +10 -6
  116. package/packages/@monomind/cli/dist/src/commands/browse.js +11 -2154
  117. package/packages/@monomind/cli/dist/src/commands/design-detect.d.ts +21 -0
  118. package/packages/@monomind/cli/dist/src/commands/design-detect.js +127 -0
  119. package/packages/@monomind/cli/dist/src/commands/design-palette.d.ts +22 -0
  120. package/packages/@monomind/cli/dist/src/commands/design-palette.js +539 -0
  121. package/packages/@monomind/cli/dist/src/commands/hooks-core-commands.d.ts +10 -0
  122. package/packages/@monomind/cli/dist/src/commands/hooks-core-commands.js +377 -0
  123. package/packages/@monomind/cli/dist/src/commands/hooks-coverage-commands.d.ts +12 -0
  124. package/packages/@monomind/cli/dist/src/commands/hooks-coverage-commands.js +1217 -0
  125. package/packages/@monomind/cli/dist/src/commands/hooks-coverage-utils.d.ts +42 -0
  126. package/packages/@monomind/cli/dist/src/commands/hooks-coverage-utils.js +220 -0
  127. package/packages/@monomind/cli/dist/src/commands/hooks-extended-commands.d.ts +14 -0
  128. package/packages/@monomind/cli/dist/src/commands/hooks-extended-commands.js +579 -0
  129. package/packages/@monomind/cli/dist/src/commands/hooks-formatting.d.ts +13 -0
  130. package/packages/@monomind/cli/dist/src/commands/hooks-formatting.js +42 -0
  131. package/packages/@monomind/cli/dist/src/commands/hooks-routing-commands.d.ts +15 -0
  132. package/packages/@monomind/cli/dist/src/commands/hooks-routing-commands.js +723 -0
  133. package/packages/@monomind/cli/dist/src/commands/hooks-workers.d.ts +9 -0
  134. package/packages/@monomind/cli/dist/src/commands/hooks-workers.js +782 -0
  135. package/packages/@monomind/cli/dist/src/commands/hooks.d.ts +8 -0
  136. package/packages/@monomind/cli/dist/src/commands/hooks.js +179 -4103
  137. package/packages/@monomind/cli/dist/src/commands/index.d.ts +1 -0
  138. package/packages/@monomind/cli/dist/src/commands/index.js +6 -0
  139. package/packages/@monomind/cli/dist/src/commands/org.js +14 -15
  140. package/packages/@monomind/cli/dist/src/commands/tokens.js +77 -1
  141. package/packages/@monomind/cli/dist/src/graph/enrich.mjs +362 -0
  142. package/packages/@monomind/cli/dist/src/init/executor.js +18 -8
  143. package/packages/@monomind/cli/dist/src/init/settings-generator.js +39 -5
  144. package/packages/@monomind/cli/dist/src/init/statusline-generator.js +25 -5
  145. package/packages/@monomind/cli/dist/src/mcp-tools/browser-tools.d.ts +3 -5
  146. package/packages/@monomind/cli/dist/src/mcp-tools/browser-tools.js +619 -326
  147. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-embedding.d.ts +161 -0
  148. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-embedding.js +506 -0
  149. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-intelligence.d.ts +26 -0
  150. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-intelligence.js +1328 -0
  151. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-routing.d.ts +27 -0
  152. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-routing.js +1591 -0
  153. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-tools.d.ts +3 -38
  154. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-tools.js +5 -3393
  155. package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +24 -14
  156. package/packages/@monomind/cli/dist/src/mcp-tools/workflow-tools.js +54 -1
  157. package/packages/@monomind/cli/dist/src/memory/embedding-operations.d.ts +58 -0
  158. package/packages/@monomind/cli/dist/src/memory/embedding-operations.js +299 -0
  159. package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.js +37 -3
  160. package/packages/@monomind/cli/dist/src/memory/hnsw-operations.d.ts +130 -0
  161. package/packages/@monomind/cli/dist/src/memory/hnsw-operations.js +400 -0
  162. package/packages/@monomind/cli/dist/src/memory/intelligence.js +42 -23
  163. package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +52 -8
  164. package/packages/@monomind/cli/dist/src/memory/memory-crud.d.ts +67 -0
  165. package/packages/@monomind/cli/dist/src/memory/memory-crud.js +415 -0
  166. package/packages/@monomind/cli/dist/src/memory/memory-initializer.d.ts +9 -322
  167. package/packages/@monomind/cli/dist/src/memory/memory-initializer.js +17 -1794
  168. package/packages/@monomind/cli/dist/src/memory/memory-migrations.d.ts +30 -0
  169. package/packages/@monomind/cli/dist/src/memory/memory-migrations.js +134 -0
  170. package/packages/@monomind/cli/dist/src/memory/memory-read.d.ts +78 -0
  171. package/packages/@monomind/cli/dist/src/memory/memory-read.js +331 -0
  172. package/packages/@monomind/cli/dist/src/memory/memory-schema.d.ts +13 -0
  173. package/packages/@monomind/cli/dist/src/memory/memory-schema.js +167 -0
  174. package/packages/@monomind/cli/dist/src/memory/sona-optimizer.js +37 -4
  175. package/packages/@monomind/cli/dist/src/monovector/route-outcomes.js +16 -6
  176. package/packages/@monomind/cli/dist/src/pricing/model-pricing.d.ts +41 -0
  177. package/packages/@monomind/cli/dist/src/pricing/model-pricing.js +61 -0
  178. package/packages/@monomind/cli/dist/src/ui/.monomind/capture/active-run.json +1 -0
  179. package/packages/@monomind/cli/dist/src/ui/collector.mjs +799 -0
  180. package/packages/@monomind/cli/dist/src/ui/dashboard.html +13986 -0
  181. package/packages/@monomind/cli/dist/src/ui/data/agent-avatars.html +763 -0
  182. package/packages/@monomind/cli/dist/src/ui/data/agent-avatars.json +966 -0
  183. package/packages/@monomind/cli/dist/src/ui/data/avatars/account-strategist.svg +58 -0
  184. package/packages/@monomind/cli/dist/src/ui/data/avatars/accounts-payable.svg +54 -0
  185. package/packages/@monomind/cli/dist/src/ui/data/avatars/adaptive-coordinator.svg +55 -0
  186. package/packages/@monomind/cli/dist/src/ui/data/avatars/adaptive-coordinator2.svg +54 -0
  187. package/packages/@monomind/cli/dist/src/ui/data/avatars/ai-citation.svg +57 -0
  188. package/packages/@monomind/cli/dist/src/ui/data/avatars/ai-engineer.svg +61 -0
  189. package/packages/@monomind/cli/dist/src/ui/data/avatars/analytics-reporter.svg +53 -0
  190. package/packages/@monomind/cli/dist/src/ui/data/avatars/api-tester.svg +53 -0
  191. package/packages/@monomind/cli/dist/src/ui/data/avatars/architecture.svg +54 -0
  192. package/packages/@monomind/cli/dist/src/ui/data/avatars/automation-governance.svg +55 -0
  193. package/packages/@monomind/cli/dist/src/ui/data/avatars/backend-dev.svg +53 -0
  194. package/packages/@monomind/cli/dist/src/ui/data/avatars/benchmarker.svg +54 -0
  195. package/packages/@monomind/cli/dist/src/ui/data/avatars/blockchain-auditor.svg +53 -0
  196. package/packages/@monomind/cli/dist/src/ui/data/avatars/byzantine-coord.svg +57 -0
  197. package/packages/@monomind/cli/dist/src/ui/data/avatars/case-analyst.svg +57 -0
  198. package/packages/@monomind/cli/dist/src/ui/data/avatars/cicd-engineer.svg +55 -0
  199. package/packages/@monomind/cli/dist/src/ui/data/avatars/cloud-architect.svg +54 -0
  200. package/packages/@monomind/cli/dist/src/ui/data/avatars/code-review-swarm.svg +57 -0
  201. package/packages/@monomind/cli/dist/src/ui/data/avatars/coder-v119.svg +57 -0
  202. package/packages/@monomind/cli/dist/src/ui/data/avatars/coder.svg +58 -0
  203. package/packages/@monomind/cli/dist/src/ui/data/avatars/collective-coord.svg +54 -0
  204. package/packages/@monomind/cli/dist/src/ui/data/avatars/compliance-auditor.svg +58 -0
  205. package/packages/@monomind/cli/dist/src/ui/data/avatars/consensus-coordinator.svg +54 -0
  206. package/packages/@monomind/cli/dist/src/ui/data/avatars/content-creator.svg +54 -0
  207. package/packages/@monomind/cli/dist/src/ui/data/avatars/crdt-synchronizer.svg +53 -0
  208. package/packages/@monomind/cli/dist/src/ui/data/avatars/cro-specialist.svg +58 -0
  209. package/packages/@monomind/cli/dist/src/ui/data/avatars/data-consolidator.svg +54 -0
  210. package/packages/@monomind/cli/dist/src/ui/data/avatars/data-engineer.svg +53 -0
  211. package/packages/@monomind/cli/dist/src/ui/data/avatars/database-optimizer.svg +61 -0
  212. package/packages/@monomind/cli/dist/src/ui/data/avatars/deal-strategist.svg +54 -0
  213. package/packages/@monomind/cli/dist/src/ui/data/avatars/defender.svg +53 -0
  214. package/packages/@monomind/cli/dist/src/ui/data/avatars/devops-automator.svg +56 -0
  215. package/packages/@monomind/cli/dist/src/ui/data/avatars/discovery-coach.svg +54 -0
  216. package/packages/@monomind/cli/dist/src/ui/data/avatars/email-marketing.svg +57 -0
  217. package/packages/@monomind/cli/dist/src/ui/data/avatars/embedded-firmware.svg +61 -0
  218. package/packages/@monomind/cli/dist/src/ui/data/avatars/evidence-collector.svg +57 -0
  219. package/packages/@monomind/cli/dist/src/ui/data/avatars/experiment-tracker.svg +53 -0
  220. package/packages/@monomind/cli/dist/src/ui/data/avatars/feedback-synthesizer.svg +54 -0
  221. package/packages/@monomind/cli/dist/src/ui/data/avatars/finance-tracker.svg +54 -0
  222. package/packages/@monomind/cli/dist/src/ui/data/avatars/frontend-developer.svg +54 -0
  223. package/packages/@monomind/cli/dist/src/ui/data/avatars/game-audio-engineer.svg +59 -0
  224. package/packages/@monomind/cli/dist/src/ui/data/avatars/game-designer.svg +54 -0
  225. package/packages/@monomind/cli/dist/src/ui/data/avatars/gossip-coordinator.svg +54 -0
  226. package/packages/@monomind/cli/dist/src/ui/data/avatars/hierarchical-coord.svg +54 -0
  227. package/packages/@monomind/cli/dist/src/ui/data/avatars/incident-commander.svg +57 -0
  228. package/packages/@monomind/cli/dist/src/ui/data/avatars/infrastructure.svg +54 -0
  229. package/packages/@monomind/cli/dist/src/ui/data/avatars/input-validator.svg +53 -0
  230. package/packages/@monomind/cli/dist/src/ui/data/avatars/ios-developer.svg +54 -0
  231. package/packages/@monomind/cli/dist/src/ui/data/avatars/issue-tracker.svg +53 -0
  232. package/packages/@monomind/cli/dist/src/ui/data/avatars/judge.svg +55 -0
  233. package/packages/@monomind/cli/dist/src/ui/data/avatars/launch-strategist.svg +54 -0
  234. package/packages/@monomind/cli/dist/src/ui/data/avatars/legal-compliance.svg +53 -0
  235. package/packages/@monomind/cli/dist/src/ui/data/avatars/level-designer.svg +53 -0
  236. package/packages/@monomind/cli/dist/src/ui/data/avatars/load-balancer.svg +57 -0
  237. package/packages/@monomind/cli/dist/src/ui/data/avatars/mcp-builder.svg +53 -0
  238. package/packages/@monomind/cli/dist/src/ui/data/avatars/memory-coordinator.svg +55 -0
  239. package/packages/@monomind/cli/dist/src/ui/data/avatars/mesh-coordinator.svg +55 -0
  240. package/packages/@monomind/cli/dist/src/ui/data/avatars/ml-developer.svg +58 -0
  241. package/packages/@monomind/cli/dist/src/ui/data/avatars/mobile-app-builder.svg +53 -0
  242. package/packages/@monomind/cli/dist/src/ui/data/avatars/mobile-dev.svg +54 -0
  243. package/packages/@monomind/cli/dist/src/ui/data/avatars/model-qa.svg +58 -0
  244. package/packages/@monomind/cli/dist/src/ui/data/avatars/narrative-designer.svg +58 -0
  245. package/packages/@monomind/cli/dist/src/ui/data/avatars/outbound-strategist.svg +55 -0
  246. package/packages/@monomind/cli/dist/src/ui/data/avatars/path-validator.svg +54 -0
  247. package/packages/@monomind/cli/dist/src/ui/data/avatars/payment-agent.svg +53 -0
  248. package/packages/@monomind/cli/dist/src/ui/data/avatars/perf-analyzer.svg +58 -0
  249. package/packages/@monomind/cli/dist/src/ui/data/avatars/pipeline-analyst.svg +54 -0
  250. package/packages/@monomind/cli/dist/src/ui/data/avatars/planner.svg +55 -0
  251. package/packages/@monomind/cli/dist/src/ui/data/avatars/pr-manager.svg +54 -0
  252. package/packages/@monomind/cli/dist/src/ui/data/avatars/pricing-strategist.svg +54 -0
  253. package/packages/@monomind/cli/dist/src/ui/data/avatars/product-manager.svg +54 -0
  254. package/packages/@monomind/cli/dist/src/ui/data/avatars/production-validator.svg +54 -0
  255. package/packages/@monomind/cli/dist/src/ui/data/avatars/project-shepherd.svg +54 -0
  256. package/packages/@monomind/cli/dist/src/ui/data/avatars/proposal-strategist.svg +54 -0
  257. package/packages/@monomind/cli/dist/src/ui/data/avatars/prosecutor.svg +57 -0
  258. package/packages/@monomind/cli/dist/src/ui/data/avatars/pseudocode.svg +53 -0
  259. package/packages/@monomind/cli/dist/src/ui/data/avatars/queen-coordinator.svg +55 -0
  260. package/packages/@monomind/cli/dist/src/ui/data/avatars/quorum-manager.svg +53 -0
  261. package/packages/@monomind/cli/dist/src/ui/data/avatars/raft-manager.svg +53 -0
  262. package/packages/@monomind/cli/dist/src/ui/data/avatars/reality-checker.svg +58 -0
  263. package/packages/@monomind/cli/dist/src/ui/data/avatars/recruitment.svg +58 -0
  264. package/packages/@monomind/cli/dist/src/ui/data/avatars/refinement.svg +53 -0
  265. package/packages/@monomind/cli/dist/src/ui/data/avatars/release-manager.svg +54 -0
  266. package/packages/@monomind/cli/dist/src/ui/data/avatars/repo-architect.svg +54 -0
  267. package/packages/@monomind/cli/dist/src/ui/data/avatars/researcher.svg +58 -0
  268. package/packages/@monomind/cli/dist/src/ui/data/avatars/resource-allocator.svg +53 -0
  269. package/packages/@monomind/cli/dist/src/ui/data/avatars/reviewer.svg +53 -0
  270. package/packages/@monomind/cli/dist/src/ui/data/avatars/safe-executor.svg +53 -0
  271. package/packages/@monomind/cli/dist/src/ui/data/avatars/sales-coach.svg +53 -0
  272. package/packages/@monomind/cli/dist/src/ui/data/avatars/sales-engineer.svg +58 -0
  273. package/packages/@monomind/cli/dist/src/ui/data/avatars/scout-explorer.svg +58 -0
  274. package/packages/@monomind/cli/dist/src/ui/data/avatars/security-architect.svg +54 -0
  275. package/packages/@monomind/cli/dist/src/ui/data/avatars/security-auditor.svg +55 -0
  276. package/packages/@monomind/cli/dist/src/ui/data/avatars/senior-developer.svg +58 -0
  277. package/packages/@monomind/cli/dist/src/ui/data/avatars/senior-pm.svg +58 -0
  278. package/packages/@monomind/cli/dist/src/ui/data/avatars/seo-specialist.svg +57 -0
  279. package/packages/@monomind/cli/dist/src/ui/data/avatars/social-media.svg +54 -0
  280. package/packages/@monomind/cli/dist/src/ui/data/avatars/solidity-engineer.svg +58 -0
  281. package/packages/@monomind/cli/dist/src/ui/data/avatars/sparc-coder.svg +58 -0
  282. package/packages/@monomind/cli/dist/src/ui/data/avatars/sparc-coord.svg +56 -0
  283. package/packages/@monomind/cli/dist/src/ui/data/avatars/specification.svg +57 -0
  284. package/packages/@monomind/cli/dist/src/ui/data/avatars/sprint-prioritizer.svg +53 -0
  285. package/packages/@monomind/cli/dist/src/ui/data/avatars/sre.svg +54 -0
  286. package/packages/@monomind/cli/dist/src/ui/data/avatars/studio-operations.svg +53 -0
  287. package/packages/@monomind/cli/dist/src/ui/data/avatars/studio-producer.svg +55 -0
  288. package/packages/@monomind/cli/dist/src/ui/data/avatars/support-responder.svg +56 -0
  289. package/packages/@monomind/cli/dist/src/ui/data/avatars/system-architect.svg +54 -0
  290. package/packages/@monomind/cli/dist/src/ui/data/avatars/task-orchestrator.svg +56 -0
  291. package/packages/@monomind/cli/dist/src/ui/data/avatars/technical-artist.svg +53 -0
  292. package/packages/@monomind/cli/dist/src/ui/data/avatars/technical-writer.svg +59 -0
  293. package/packages/@monomind/cli/dist/src/ui/data/avatars/tester.svg +53 -0
  294. package/packages/@monomind/cli/dist/src/ui/data/avatars/threat-detection.svg +61 -0
  295. package/packages/@monomind/cli/dist/src/ui/data/avatars/trend-researcher.svg +54 -0
  296. package/packages/@monomind/cli/dist/src/ui/data/avatars/trial-director.svg +55 -0
  297. package/packages/@monomind/cli/dist/src/ui/data/avatars/unity-architect.svg +54 -0
  298. package/packages/@monomind/cli/dist/src/ui/data/avatars/visionos-engineer.svg +57 -0
  299. package/packages/@monomind/cli/dist/src/ui/data/avatars/worker-specialist.svg +55 -0
  300. package/packages/@monomind/cli/dist/src/ui/data/avatars/workflow-architect.svg +57 -0
  301. package/packages/@monomind/cli/dist/src/ui/data/avatars/workflow-automation.svg +54 -0
  302. package/packages/@monomind/cli/dist/src/ui/data/avatars/zk-steward.svg +54 -0
  303. package/packages/@monomind/cli/dist/src/ui/data/known-projects.json +1 -0
  304. package/packages/@monomind/cli/dist/src/ui/data/mastermind-sessions.json +1 -0
  305. package/packages/@monomind/cli/dist/src/ui/data/sessions/_index.json +1 -0
  306. package/packages/@monomind/cli/dist/src/ui/orgs.html +2215 -0
  307. package/packages/@monomind/cli/dist/src/ui/server.mjs +6175 -0
  308. package/packages/@monomind/cli/dist/src/ui/sse-manager.mjs +119 -0
  309. package/packages/@monomind/cli/dist/src/update/checker.js +1 -1
  310. package/packages/@monomind/cli/dist/workflow/builtin-handlers.js +321 -0
  311. package/packages/@monomind/cli/dist/workflow/engine.js +253 -0
  312. package/packages/@monomind/cli/dist/workflow/expression.js +98 -0
  313. package/packages/@monomind/cli/dist/workflow/types.js +2 -0
  314. package/packages/@monomind/cli/package.json +8 -5
@@ -0,0 +1,2215 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>MASTERMIND ORGS</title>
7
+ <style>
8
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
9
+
10
+ :root {
11
+ --bg: oklch(7% 0.010 186);
12
+ --bg-panel: oklch(9% 0.012 186);
13
+ --bg-hover: oklch(62% 0.20 186 / 0.06);
14
+ --bg-active: oklch(62% 0.20 186 / 0.11);
15
+ --teal: oklch(62% 0.20 186);
16
+ --teal-dim: oklch(62% 0.20 186 / 0.18);
17
+ --teal-glow: oklch(62% 0.20 186 / 0.08);
18
+ --indigo: oklch(68% 0.18 252);
19
+ --green: oklch(68% 0.20 150);
20
+ --green-glow:oklch(68% 0.20 150 / 0.25);
21
+ --amber: oklch(78% 0.18 80);
22
+ --red: oklch(62% 0.22 25);
23
+ --text: oklch(80% 0.012 186);
24
+ --muted: oklch(52% 0.010 186);
25
+ --dim: oklch(36% 0.008 186);
26
+ --border: oklch(62% 0.20 186 / 0.12);
27
+ --border-hi: oklch(62% 0.20 186 / 0.35);
28
+ --mono: 'Azeret Mono', 'Space Mono', 'Courier New', monospace;
29
+ }
30
+
31
+ html, body {
32
+ width: 100%; height: 100%; overflow: hidden;
33
+ background: var(--bg);
34
+ font-family: var(--mono);
35
+ color: var(--text);
36
+ font-size: 13px;
37
+ line-height: 1.5;
38
+ user-select: none;
39
+ }
40
+
41
+ /* ── Layout ─────────────────────────────────────────────────── */
42
+ #app { display: flex; height: 100vh; }
43
+
44
+ #sidebar {
45
+ width: 260px; flex-shrink: 0;
46
+ background: var(--bg-panel);
47
+ border-right: 1px solid var(--border);
48
+ display: flex; flex-direction: column;
49
+ overflow: hidden;
50
+ }
51
+
52
+ #main {
53
+ flex: 1; display: flex; flex-direction: column;
54
+ overflow: hidden; min-width: 0;
55
+ }
56
+
57
+ /* ── Sidebar ─────────────────────────────────────────────────── */
58
+ #sb-header {
59
+ padding: 14px 14px 12px;
60
+ border-bottom: 1px solid var(--border);
61
+ flex-shrink: 0;
62
+ }
63
+
64
+ #sb-wordmark {
65
+ display: flex; align-items: center; gap: 8px; margin-bottom: 10px;
66
+ }
67
+
68
+ #sb-back {
69
+ font-size: 9px; letter-spacing: 1px; color: var(--dim);
70
+ text-decoration: none; padding: 3px 7px;
71
+ border: 1px solid var(--border); border-radius: 3px;
72
+ transition: color 0.12s, border-color 0.12s;
73
+ }
74
+ #sb-back:hover { color: var(--teal); border-color: var(--teal-dim); }
75
+
76
+ #sb-title {
77
+ font-size: 9px; letter-spacing: 4px; color: var(--teal);
78
+ opacity: 0.8;
79
+ }
80
+
81
+ #sb-status-row {
82
+ display: flex; align-items: center; gap: 6px;
83
+ }
84
+
85
+ .live-dot {
86
+ width: 6px; height: 6px; border-radius: 50%;
87
+ background: var(--dim); flex-shrink: 0;
88
+ transition: background 0.4s, box-shadow 0.4s;
89
+ }
90
+ .live-dot.on {
91
+ background: var(--green);
92
+ box-shadow: 0 0 6px var(--green-glow);
93
+ }
94
+ @media (prefers-reduced-motion: no-preference) {
95
+ .live-dot.on { animation: livepulse 2.2s ease-in-out infinite; }
96
+ }
97
+ @keyframes livepulse { 0%,100%{opacity:1} 50%{opacity:0.45} }
98
+
99
+ #sb-status-text { font-size: 9px; letter-spacing: 2px; color: var(--dim); }
100
+ #sb-running-count { font-size: 8px; color: var(--green); margin-left: auto; letter-spacing: 1px; }
101
+
102
+ #sb-list {
103
+ flex: 1; overflow-y: auto;
104
+ padding: 6px 0;
105
+ scrollbar-width: thin;
106
+ scrollbar-color: var(--teal-dim) transparent;
107
+ }
108
+ #sb-list::-webkit-scrollbar { width: 3px; }
109
+ #sb-list::-webkit-scrollbar-thumb { background: var(--teal-dim); border-radius: 2px; }
110
+
111
+ .org-item {
112
+ padding: 10px 14px;
113
+ cursor: pointer;
114
+ border-left: 2px solid transparent;
115
+ transition: background 0.12s, border-color 0.12s;
116
+ position: relative;
117
+ }
118
+ .org-item:hover { background: var(--bg-hover); }
119
+ .org-item.active {
120
+ background: var(--bg-active);
121
+ border-left-color: var(--teal);
122
+ }
123
+ .org-item.running { border-left-color: var(--green); }
124
+ .org-item.active.running { border-left-color: var(--teal); }
125
+
126
+ .oi-header { display: flex; align-items: center; gap: 7px; margin-bottom: 3px; }
127
+
128
+ .oi-dot {
129
+ width: 5px; height: 5px; border-radius: 50%;
130
+ background: var(--dim); flex-shrink: 0;
131
+ }
132
+ .oi-dot.running {
133
+ background: var(--green);
134
+ box-shadow: 0 0 4px var(--green);
135
+ }
136
+ @media (prefers-reduced-motion: no-preference) {
137
+ .oi-dot.running { animation: livepulse 2s ease-in-out infinite; }
138
+ }
139
+
140
+ .oi-name {
141
+ font-size: 11px; color: var(--text); font-weight: 500;
142
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
143
+ flex: 1;
144
+ }
145
+
146
+ .oi-badge {
147
+ font-size: 7px; padding: 1px 6px; border-radius: 2px; flex-shrink: 0;
148
+ text-transform: uppercase; letter-spacing: 0.06em;
149
+ }
150
+ .oi-badge.running {
151
+ background: oklch(68% 0.20 150 / 0.12);
152
+ color: var(--green);
153
+ border: 1px solid oklch(68% 0.20 150 / 0.28);
154
+ }
155
+ .oi-badge.quiet {
156
+ background: oklch(75% 0.18 80 / 0.10);
157
+ color: oklch(75% 0.18 80);
158
+ border: 1px solid oklch(75% 0.18 80 / 0.30);
159
+ }
160
+ .oi-badge.stale {
161
+ background: oklch(60% 0.20 25 / 0.10);
162
+ color: oklch(65% 0.20 25);
163
+ border: 1px solid oklch(60% 0.20 25 / 0.30);
164
+ }
165
+ .oi-badge.idle {
166
+ background: transparent; color: var(--dim);
167
+ border: 1px solid var(--border);
168
+ }
169
+
170
+ .oi-goal {
171
+ font-size: 9px; color: var(--muted); line-height: 1.5;
172
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
173
+ padding-left: 12px;
174
+ }
175
+
176
+ .oi-meta {
177
+ display: flex; gap: 8px; padding-left: 12px; margin-top: 3px;
178
+ }
179
+ .oi-chip {
180
+ font-size: 8px; color: var(--dim); letter-spacing: 0.5px;
181
+ }
182
+ .oi-chip span { color: var(--muted); }
183
+
184
+ #sb-empty {
185
+ padding: 28px 16px; text-align: center;
186
+ color: var(--dim); font-size: 9px; line-height: 1.8; letter-spacing: 0.5px;
187
+ }
188
+ #sb-empty .empty-icon {
189
+ font-size: 24px; display: block; margin-bottom: 10px; opacity: 0.35;
190
+ }
191
+ #sb-empty .empty-cmd {
192
+ display: inline-block; margin-top: 10px;
193
+ color: var(--teal); border: 1px solid var(--teal-dim);
194
+ padding: 3px 10px; border-radius: 3px; font-size: 8px; letter-spacing: 1px;
195
+ }
196
+
197
+ /* ── Main area ─────────────────────────────────────────────────── */
198
+ #main-header {
199
+ padding: 12px 20px 11px;
200
+ border-bottom: 1px solid var(--border);
201
+ display: flex; align-items: center; gap: 12px;
202
+ flex-shrink: 0;
203
+ }
204
+
205
+ #org-label {
206
+ font-size: 9px; letter-spacing: 4px; color: var(--teal);
207
+ opacity: 0.7; text-transform: uppercase;
208
+ }
209
+
210
+ #org-name {
211
+ font-size: 15px; letter-spacing: 2px; color: var(--text);
212
+ font-weight: 600; text-transform: uppercase;
213
+ }
214
+
215
+ #org-status-badge {
216
+ font-size: 8px; padding: 2px 8px; border-radius: 2px;
217
+ text-transform: uppercase; letter-spacing: 0.1em; margin-left: 4px;
218
+ }
219
+ #org-status-badge.running {
220
+ background: oklch(68% 0.20 150 / 0.12);
221
+ color: var(--green); border: 1px solid oklch(68% 0.20 150 / 0.25);
222
+ }
223
+ #org-status-badge.quiet {
224
+ background: oklch(75% 0.18 80 / 0.10);
225
+ color: oklch(75% 0.18 80); border: 1px solid oklch(75% 0.18 80 / 0.30);
226
+ }
227
+ #org-status-badge.stale {
228
+ background: oklch(60% 0.20 25 / 0.10);
229
+ color: oklch(65% 0.20 25); border: 1px solid oklch(60% 0.20 25 / 0.30);
230
+ }
231
+ #org-status-badge.idle {
232
+ background: transparent; color: var(--dim);
233
+ border: 1px solid var(--border);
234
+ }
235
+
236
+ .hdr-chip {
237
+ font-size: 8px; color: var(--dim); letter-spacing: 0.5px;
238
+ padding: 2px 8px; border: 1px solid var(--border); border-radius: 2px;
239
+ }
240
+ .hdr-chip span { color: var(--muted); }
241
+
242
+ #main-tabs {
243
+ display: flex; gap: 0; border-bottom: 1px solid var(--border);
244
+ flex-shrink: 0; padding: 0 20px;
245
+ }
246
+
247
+ .tab-btn {
248
+ font-size: 9px; letter-spacing: 2px; color: var(--dim);
249
+ padding: 8px 14px; background: none; border: none; cursor: pointer;
250
+ font-family: var(--mono); text-transform: uppercase;
251
+ border-bottom: 2px solid transparent; margin-bottom: -1px;
252
+ transition: color 0.12s, border-color 0.12s;
253
+ }
254
+ .tab-btn:hover { color: var(--muted); }
255
+ .tab-btn.active { color: var(--teal); border-bottom-color: var(--teal); }
256
+
257
+ #main-body { flex: 1; overflow-y: auto; overflow-x: hidden; }
258
+ #main-body::-webkit-scrollbar { width: 4px; }
259
+ #main-body::-webkit-scrollbar-thumb { background: var(--teal-dim); border-radius: 2px; }
260
+
261
+ .tab-pane { display: none; }
262
+ .tab-pane.active { display: block; }
263
+
264
+ /* ── Empty / no-selection state ─────────────────────────────────── */
265
+ #no-org-state {
266
+ display: flex; flex-direction: column; align-items: center;
267
+ justify-content: center; height: 100%; gap: 10px; opacity: 0.5;
268
+ padding: 40px;
269
+ }
270
+ #no-org-state .ns-icon { font-size: 48px; opacity: 0.25; }
271
+ #no-org-state .ns-text {
272
+ font-size: 9px; letter-spacing: 3px; color: var(--dim);
273
+ text-align: center;
274
+ }
275
+
276
+ /* ── Org Chart ──────────────────────────────────────────────────── */
277
+ #chart-pane {
278
+ padding: 20px;
279
+ display: flex; flex-direction: column; gap: 16px;
280
+ }
281
+
282
+ #chart-svg-wrap {
283
+ background: oklch(8% 0.01 186);
284
+ border: 1px solid var(--border);
285
+ border-radius: 4px;
286
+ overflow: hidden; position: relative;
287
+ }
288
+
289
+ #org-chart-svg {
290
+ width: 100%; display: block;
291
+ }
292
+
293
+ /* ── Info grid (goal / topology / created) ─────────────────────── */
294
+ #org-meta-grid {
295
+ display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1px;
296
+ background: var(--border); border: 1px solid var(--border);
297
+ border-radius: 3px; overflow: hidden;
298
+ }
299
+ .meta-cell {
300
+ background: var(--bg-panel);
301
+ padding: 10px 14px;
302
+ }
303
+ .meta-cell-label { font-size: 8px; letter-spacing: 2px; color: var(--dim); margin-bottom: 4px; }
304
+ .meta-cell-value { font-size: 11px; color: var(--text); }
305
+
306
+ /* ── Live status strip ─────────────────────────────────────────── */
307
+ #live-strip {
308
+ display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
309
+ background: oklch(8% 0.01 186); border: 1px solid var(--border);
310
+ border-radius: 4px; padding: 8px 12px; font-size: 10px; color: var(--dim);
311
+ letter-spacing: 0.5px;
312
+ }
313
+ #ls-state { display: flex; align-items: center; gap: 5px; }
314
+ .ls-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--dim); flex-shrink: 0; }
315
+ .ls-dot.running { background: oklch(65% 0.20 150); }
316
+ @media (prefers-reduced-motion: no-preference) { .ls-dot.running { animation: ls-pulse 2s ease-in-out infinite; } }
317
+ @keyframes ls-pulse { 0%,100%{opacity:1} 50%{opacity:0.3} }
318
+ #ls-label { font-size: 9px; letter-spacing: 2px; }
319
+ .ls-metric { font-size: 10px; color: var(--muted); }
320
+ .ls-sparkline-wrap { display: flex; align-items: flex-end; gap: 1px; height: 20px; margin-left: auto; }
321
+ .ls-bar { width: 6px; border-radius: 1px; background: oklch(62% 0.20 186 / 0.4); min-height: 2px; }
322
+ .ls-bar.active { background: oklch(62% 0.20 186 / 0.8); }
323
+ #clf-wrap {
324
+ background: oklch(8% 0.01 186); border: 1px solid var(--border);
325
+ border-radius: 4px; overflow: hidden;
326
+ }
327
+ #clf-header {
328
+ font-size: 8px; letter-spacing: 3px; color: var(--dim); padding: 6px 12px;
329
+ border-bottom: 1px solid var(--border);
330
+ }
331
+ #clf-rows { min-height: 48px; max-height: 160px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: var(--teal-dim) transparent; }
332
+ .clf-row {
333
+ display: grid; grid-template-columns: 80px 70px 1fr;
334
+ gap: 8px; padding: 5px 12px;
335
+ font-size: 10px; border-bottom: 1px solid oklch(100% 0 0 / 0.03);
336
+ }
337
+ .clf-row.new { animation: clf-fadein 0.3s ease; }
338
+ @keyframes clf-fadein { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
339
+ .clf-time { color: var(--dim); font-variant-numeric: tabular-nums; }
340
+ .clf-type { color: var(--accent); }
341
+ .clf-msg { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
342
+
343
+ /* ── Roles list ─────────────────────────────────────────────────── */
344
+ #roles-pane { padding: 20px; }
345
+
346
+ .roles-grid {
347
+ display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
348
+ gap: 10px;
349
+ }
350
+
351
+ .role-block {
352
+ background: var(--bg-panel);
353
+ border: 1px solid var(--border);
354
+ border-radius: 3px; padding: 12px 14px;
355
+ transition: border-color 0.12s;
356
+ }
357
+ .role-block:hover { border-color: var(--teal-dim); }
358
+
359
+ .rb-header { display: flex; align-items: flex-start; gap: 8px; margin-bottom: 6px; }
360
+ .rb-dot {
361
+ width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; margin-top: 4px;
362
+ }
363
+ .rb-title { font-size: 12px; color: var(--text); font-weight: 500; line-height: 1.3; }
364
+ .rb-agent {
365
+ margin-left: auto; flex-shrink: 0;
366
+ font-size: 8px; padding: 1px 7px; border-radius: 2px;
367
+ border: 1px solid var(--teal-dim); color: var(--teal); opacity: 0.85;
368
+ letter-spacing: 0.5px;
369
+ }
370
+ .rb-id { font-size: 9px; color: var(--dim); margin-bottom: 5px; padding-left: 15px; }
371
+ .rb-reports { font-size: 9px; color: var(--muted); padding-left: 15px; margin-bottom: 6px; }
372
+ .rb-reports span { color: var(--indigo); }
373
+
374
+ .rb-resps { padding-left: 15px; }
375
+ .rb-resp-item {
376
+ font-size: 9px; color: var(--dim); line-height: 1.6;
377
+ display: flex; gap: 6px; align-items: flex-start;
378
+ }
379
+ .rb-resp-item::before {
380
+ content: '—'; color: oklch(36% 0.008 186 / 0.5); flex-shrink: 0; margin-top: 1px;
381
+ }
382
+
383
+ /* ── Activity / event log ───────────────────────────────────────── */
384
+ #activity-pane { padding: 20px; }
385
+ #activity-log { display: flex; flex-direction: column; gap: 0; }
386
+
387
+ .act-row {
388
+ display: grid;
389
+ grid-template-columns: 60px 90px 1fr;
390
+ gap: 8px; padding: 5px 0;
391
+ border-bottom: 1px solid oklch(62% 0.20 186 / 0.05);
392
+ align-items: baseline; font-size: 10px;
393
+ }
394
+ .act-row:last-child { border-bottom: none; }
395
+
396
+ .act-time { color: var(--dim); font-variant-numeric: tabular-nums; font-size: 9px; }
397
+ .act-type { color: var(--teal); font-weight: 500; letter-spacing: 0.5px; }
398
+ .act-msg { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
399
+
400
+ .act-empty { color: var(--dim); font-size: 9px; padding: 16px 0; letter-spacing: 1px; }
401
+
402
+ /* ── Health pane ─────────────────────────────────────────────────── */
403
+ #health-pane { padding: 20px; }
404
+
405
+ .health-grid {
406
+ display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
407
+ gap: 10px; margin-bottom: 16px;
408
+ }
409
+
410
+ .health-cell {
411
+ background: var(--bg-panel);
412
+ border: 1px solid var(--border); border-radius: 3px;
413
+ padding: 12px 14px;
414
+ }
415
+ .hc-label { font-size: 8px; letter-spacing: 2px; color: var(--dim); margin-bottom: 4px; }
416
+ .hc-value { font-size: 18px; color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
417
+ .hc-sub { font-size: 9px; color: var(--muted); margin-top: 2px; }
418
+ .hc-value.green { color: var(--green); }
419
+ .hc-value.amber { color: var(--amber); }
420
+ .hc-value.red { color: var(--red); }
421
+
422
+ /* ── Chat tab ───────────────────────────────────────────────────── */
423
+ #chat-pane {
424
+ display: flex; flex-direction: column; height: 100%;
425
+ overflow: hidden;
426
+ }
427
+ #chat-session-bar {
428
+ display: flex; align-items: center; gap: 8px;
429
+ padding: 10px 20px 8px;
430
+ border-bottom: 1px solid var(--border);
431
+ flex-shrink: 0; flex-wrap: wrap;
432
+ }
433
+ #chat-sess-label { font-size: 8px; letter-spacing: 2px; color: var(--dim); }
434
+ #chat-sess-select {
435
+ background: var(--bg-panel); color: var(--muted);
436
+ border: 1px solid var(--border); border-radius: 3px;
437
+ font-size: 9px; font-family: var(--mono); letter-spacing: 0.5px;
438
+ padding: 3px 6px; cursor: pointer; max-width: 260px;
439
+ }
440
+ #chat-sess-select:focus { outline: none; border-color: var(--teal-dim); }
441
+ #chat-live-dot {
442
+ width: 5px; height: 5px; border-radius: 50%;
443
+ background: var(--dim); margin-left: auto; flex-shrink: 0;
444
+ transition: background 0.4s;
445
+ }
446
+ #chat-live-dot.on { background: var(--green); animation: livepulse 2.2s ease-in-out infinite; }
447
+ #chat-live-label { font-size: 8px; color: var(--dim); }
448
+ /* Phase 3: render mode toggle */
449
+ #chat-mode-bar {
450
+ display: flex; gap: 3px; margin-left: 8px;
451
+ }
452
+ .chat-mode-btn {
453
+ font-size: 7px; padding: 2px 7px; border-radius: 2px;
454
+ border: 1px solid var(--border); color: var(--dim);
455
+ background: transparent; cursor: pointer; font-family: var(--mono);
456
+ letter-spacing: 0.5px; text-transform: uppercase; transition: all 0.15s;
457
+ }
458
+ .chat-mode-btn:hover { border-color: var(--teal-dim); color: var(--muted); }
459
+ .chat-mode-btn.active { border-color: var(--teal); color: var(--teal); background: oklch(62% 0.20 186 / 0.07); }
460
+
461
+ #chat-feed-wrap {
462
+ flex: 1; overflow-y: auto; padding: 12px 20px;
463
+ display: flex; flex-direction: column; gap: 5px;
464
+ scrollbar-width: thin;
465
+ scrollbar-color: var(--teal-dim) transparent;
466
+ }
467
+ #chat-feed-wrap::-webkit-scrollbar { width: 4px; }
468
+ #chat-feed-wrap::-webkit-scrollbar-thumb { background: var(--teal-dim); border-radius: 2px; }
469
+
470
+ #chat-feed-empty {
471
+ font-size: 10px; color: var(--dim); text-align: center;
472
+ padding: 30px 0; line-height: 2;
473
+ }
474
+ #chat-agent-bar {
475
+ display: flex; align-items: center; gap: 6px;
476
+ padding: 6px 20px 7px;
477
+ border-bottom: 1px solid var(--border);
478
+ flex-shrink: 0; flex-wrap: wrap;
479
+ }
480
+ #chat-agent-label { font-size: 8px; letter-spacing: 2px; color: var(--dim); flex-shrink: 0; }
481
+ .chat-agent-pill {
482
+ font-size: 8px; padding: 2px 8px; border-radius: 10px;
483
+ border: 1px solid var(--border); color: var(--muted);
484
+ background: transparent; cursor: pointer; letter-spacing: 0.3px;
485
+ font-family: var(--mono); transition: border-color 0.15s, color 0.15s;
486
+ }
487
+ .chat-agent-pill:hover { border-color: var(--teal-dim); color: var(--text); }
488
+ .chat-agent-pill.active { border-color: var(--teal); color: var(--teal); background: oklch(62% 0.20 186 / 0.07); }
489
+
490
+ .cmsg { display: flex; flex-direction: column; max-width: 92%; }
491
+ .cmsg.csys { align-self: center; max-width: 100%; }
492
+ .cmsg.cagent { align-self: flex-start; }
493
+ .cmsg.cic { align-self: flex-start; }
494
+
495
+ @keyframes cmsg-in { from{opacity:0;transform:translateY(3px)} to{opacity:1;transform:none} }
496
+ .cmsg.new { animation: cmsg-in 0.18s ease-out; }
497
+
498
+ .cmsg.csys .cbubble {
499
+ background: oklch(10% 0.008 186);
500
+ border: 1px solid var(--border);
501
+ border-radius: 6px; padding: 4px 12px;
502
+ font-size: 9px; color: var(--dim); letter-spacing: 0.3px;
503
+ text-align: center;
504
+ }
505
+ .cmsg.cagent .cbubble {
506
+ background: oklch(11% 0.016 186);
507
+ border: 1px solid var(--teal-dim);
508
+ border-radius: 2px 8px 8px 8px;
509
+ padding: 7px 11px;
510
+ color: oklch(70% 0.010 186);
511
+ font-size: 11px; line-height: 1.6;
512
+ word-break: break-word; white-space: pre-wrap;
513
+ }
514
+ .cmsg.cic .cbubble {
515
+ background: oklch(10% 0.013 295);
516
+ border: 1px solid oklch(68% 0.18 295 / 0.22);
517
+ border-radius: 2px 8px 8px 8px;
518
+ padding: 7px 11px;
519
+ color: oklch(68% 0.010 295);
520
+ font-size: 11px; line-height: 1.6;
521
+ word-break: break-word; white-space: pre-wrap;
522
+ }
523
+ .cmsg-meta {
524
+ display: flex; align-items: center; gap: 5px;
525
+ margin-bottom: 3px; flex-wrap: wrap;
526
+ }
527
+ .cmsg.csys .cmsg-meta { display: none; }
528
+ .ctag {
529
+ font-size: 8px; padding: 1px 6px; border-radius: 10px;
530
+ border: 1px solid var(--teal-dim); color: var(--teal);
531
+ letter-spacing: 0.4px; flex-shrink: 0;
532
+ }
533
+ .ctag.sender { border-color: oklch(68% 0.18 295 / 0.35); color: oklch(68% 0.14 295); }
534
+ .ctag.receiver{ border-color: oklch(78% 0.18 80 / 0.35); color: var(--amber); }
535
+ .cic-arrow { font-size: 9px; color: var(--dim); }
536
+ .cts { font-size: 8px; color: var(--dim); margin-left: auto; }
537
+ .cevtype {
538
+ font-size: 7px; padding: 1px 4px; border-radius: 2px;
539
+ background: oklch(62% 0.20 186 / 0.07);
540
+ border: 1px solid var(--border); color: var(--dim);
541
+ letter-spacing: 0.3px;
542
+ }
543
+
544
+ /* ── Communication edges (SVG in chart) ─────────────────────────── */
545
+ .oc-edge { stroke: var(--teal); stroke-opacity: 0.25; stroke-width: 1; fill: none; }
546
+ .oc-edge.command { stroke: var(--teal); stroke-opacity: 0.4; }
547
+ .oc-edge.report { stroke: var(--indigo); stroke-opacity: 0.35; stroke-dasharray: 4 3; }
548
+ .oc-edge.feedback { stroke: var(--amber); stroke-opacity: 0.25; stroke-dasharray: 2 4; }
549
+ .oc-edge.handoff { stroke: var(--green); stroke-opacity: 0.3; }
550
+
551
+ .oc-node-bg { fill: oklch(8% 0.01 186); stroke-width: 1.5; }
552
+ .oc-node-bg.boss { stroke: var(--teal); }
553
+ .oc-node-bg.peer { stroke: oklch(68% 0.18 252); }
554
+ .oc-node-text {
555
+ fill: var(--text); font-size: 10px;
556
+ font-family: 'Azeret Mono', 'Space Mono', monospace;
557
+ text-anchor: middle; dominant-baseline: middle;
558
+ pointer-events: none;
559
+ }
560
+ .oc-node-sub {
561
+ fill: var(--muted); font-size: 8px;
562
+ font-family: 'Azeret Mono', 'Space Mono', monospace;
563
+ text-anchor: middle; dominant-baseline: middle;
564
+ pointer-events: none;
565
+ }
566
+ .oc-node-pulse { fill: none; stroke-width: 1; opacity: 0; }
567
+ @media (prefers-reduced-motion: no-preference) {
568
+ .oc-node-pulse.running {
569
+ animation: nodepulse 2.5s ease-in-out infinite;
570
+ stroke: var(--green); opacity: 0.35;
571
+ }
572
+ }
573
+ @keyframes nodepulse { 0%,100%{r:26;opacity:0.2} 50%{r:30;opacity:0.55} }
574
+
575
+ /* Arrow markers are inline in the SVG */
576
+
577
+ /* ── Copy / Export / Import buttons on org items ────────────────── */
578
+ .oi-copy-btn, .oi-export-btn, .oi-import-btn {
579
+ display: none;
580
+ flex-shrink: 0;
581
+ background: none;
582
+ border: 1px solid var(--border);
583
+ border-radius: 2px;
584
+ color: var(--dim);
585
+ font-family: var(--mono);
586
+ font-size: 7px;
587
+ letter-spacing: 0.5px;
588
+ padding: 1px 5px;
589
+ cursor: pointer;
590
+ transition: color 0.12s, border-color 0.12s, background 0.12s;
591
+ line-height: 1.4;
592
+ white-space: nowrap;
593
+ }
594
+ .org-item:hover .oi-copy-btn,
595
+ .org-item:hover .oi-export-btn,
596
+ .org-item:hover .oi-import-btn { display: inline-block; }
597
+ .oi-copy-btn:hover {
598
+ color: var(--teal);
599
+ border-color: var(--teal-dim);
600
+ background: var(--teal-glow);
601
+ }
602
+ .oi-export-btn:hover {
603
+ color: var(--green, #4ade80);
604
+ border-color: oklch(60% 0.15 145 / 0.4);
605
+ background: oklch(60% 0.15 145 / 0.08);
606
+ }
607
+ .oi-import-btn:hover {
608
+ color: var(--amber, #facc15);
609
+ border-color: oklch(70% 0.15 85 / 0.4);
610
+ background: oklch(70% 0.15 85 / 0.08);
611
+ }
612
+
613
+ /* ── Copy modal ─────────────────────────────────────────────────── */
614
+ #copy-project-list {
615
+ max-height: 180px;
616
+ overflow-y: auto;
617
+ border: 1px solid var(--border-hi);
618
+ border-radius: 4px;
619
+ margin: 4px 0 10px;
620
+ background: oklch(6% 0.008 186);
621
+ }
622
+ .copy-proj-row {
623
+ padding: 7px 10px;
624
+ font-size: 12px;
625
+ font-family: var(--mono);
626
+ color: var(--text-md);
627
+ cursor: pointer;
628
+ border-bottom: 1px solid var(--border);
629
+ white-space: nowrap;
630
+ overflow: hidden;
631
+ text-overflow: ellipsis;
632
+ }
633
+ .copy-proj-row:last-child { border-bottom: none; }
634
+ .copy-proj-row:hover { background: var(--bg-hover); color: var(--text-hi); }
635
+ .copy-proj-row.selected { background: color-mix(in srgb, var(--teal) 15%, transparent); color: var(--teal); border-left: 2px solid var(--teal); padding-left: 8px; }
636
+ .copy-proj-loading { padding: 10px; font-size: 11px; color: var(--dim); text-align: center; }
637
+ #copy-modal-overlay {
638
+ display: none;
639
+ position: fixed; inset: 0;
640
+ background: oklch(5% 0.008 186 / 0.82);
641
+ backdrop-filter: blur(2px);
642
+ z-index: 200;
643
+ align-items: center; justify-content: center;
644
+ }
645
+ #copy-modal-overlay.open { display: flex; }
646
+
647
+ #copy-modal {
648
+ background: var(--bg-panel);
649
+ border: 1px solid var(--border-hi);
650
+ border-radius: 4px;
651
+ padding: 22px 24px 18px;
652
+ width: 440px;
653
+ max-width: calc(100vw - 32px);
654
+ box-shadow: 0 8px 40px oklch(0% 0 0 / 0.5);
655
+ }
656
+
657
+ #copy-modal-title {
658
+ font-size: 9px; letter-spacing: 3px; color: var(--teal);
659
+ margin-bottom: 4px;
660
+ }
661
+ #copy-modal-org {
662
+ font-size: 14px; font-weight: 600; color: var(--text);
663
+ letter-spacing: 1px; margin-bottom: 16px;
664
+ }
665
+
666
+ .copy-modal-label {
667
+ font-size: 8px; letter-spacing: 2px; color: var(--dim);
668
+ margin-bottom: 6px;
669
+ }
670
+
671
+ #copy-dest-input {
672
+ width: 100%;
673
+ background: oklch(6% 0.008 186);
674
+ border: 1px solid var(--border-hi);
675
+ border-radius: 3px;
676
+ color: var(--text);
677
+ font-family: var(--mono);
678
+ font-size: 12px;
679
+ padding: 8px 10px;
680
+ outline: none;
681
+ transition: border-color 0.12s;
682
+ margin-bottom: 6px;
683
+ }
684
+ #copy-dest-input:focus { border-color: var(--teal); }
685
+ #copy-dest-input::placeholder { color: var(--dim); }
686
+
687
+ #copy-modal-hint {
688
+ font-size: 9px; color: var(--dim); margin-bottom: 16px; line-height: 1.5;
689
+ }
690
+
691
+ #copy-modal-status {
692
+ font-size: 9px; min-height: 16px; margin-bottom: 12px;
693
+ letter-spacing: 0.5px;
694
+ }
695
+ #copy-modal-status.ok { color: var(--green); }
696
+ #copy-modal-status.err { color: var(--red); }
697
+
698
+ .copy-modal-actions {
699
+ display: flex; gap: 8px; justify-content: flex-end;
700
+ }
701
+
702
+ .cm-btn {
703
+ background: none;
704
+ border: 1px solid var(--border-hi);
705
+ border-radius: 3px;
706
+ color: var(--muted);
707
+ font-family: var(--mono);
708
+ font-size: 9px; letter-spacing: 1px;
709
+ padding: 5px 14px;
710
+ cursor: pointer;
711
+ text-transform: uppercase;
712
+ transition: color 0.12s, border-color 0.12s, background 0.12s;
713
+ }
714
+ .cm-btn:hover { color: var(--text); border-color: var(--muted); }
715
+ .cm-btn.primary {
716
+ color: var(--teal); border-color: var(--teal-dim);
717
+ }
718
+ .cm-btn.primary:hover { background: var(--teal-glow); border-color: var(--teal); }
719
+ .cm-btn:disabled { opacity: 0.4; cursor: not-allowed; }
720
+ </style>
721
+ </head>
722
+ <body>
723
+ <div id="app">
724
+
725
+ <!-- ── Sidebar: org list ── -->
726
+ <div id="sidebar">
727
+ <div id="sb-header">
728
+ <div id="sb-wordmark">
729
+ <a id="sb-back" href="/">← CONTROL</a>
730
+ <span id="sb-title">ORGS</span>
731
+ </div>
732
+ <div id="sb-status-row">
733
+ <div class="live-dot" id="sb-live-dot"></div>
734
+ <span id="sb-status-text">LOADING</span>
735
+ <span id="sb-running-count"></span>
736
+ </div>
737
+ </div>
738
+ <div id="sb-list">
739
+ <div id="sb-empty" style="display:none">
740
+ <span class="empty-icon">⬡</span>
741
+ NO ORGS FOUND<br><br>
742
+ Create a named agent team<br>that coordinates across sessions.
743
+ <span class="empty-cmd">/mastermind:createorg</span>
744
+ </div>
745
+ </div>
746
+ </div>
747
+
748
+ <!-- ── Main content ── -->
749
+ <div id="main">
750
+
751
+ <!-- No-selection splash -->
752
+ <div id="no-org-state">
753
+ <div class="ns-icon">⬡</div>
754
+ <div class="ns-text">SELECT AN ORG</div>
755
+ </div>
756
+
757
+ <!-- Org detail (hidden until org selected) -->
758
+ <div id="org-detail" style="display:none; height:100%; flex-direction:column; overflow:hidden;">
759
+
760
+ <div id="main-header">
761
+ <div>
762
+ <div id="org-label">MASTERMIND ORG</div>
763
+ <div style="display:flex; align-items:center; gap:8px; margin-top:2px;">
764
+ <div id="org-name">—</div>
765
+ <span id="org-status-badge" class="idle">IDLE</span>
766
+ </div>
767
+ </div>
768
+ <div style="margin-left:auto; display:flex; gap:6px; flex-wrap:wrap; justify-content:flex-end;">
769
+ <div class="hdr-chip">ROLES <span id="hdr-roles">0</span></div>
770
+ <div class="hdr-chip">TOPOLOGY <span id="hdr-topology">—</span></div>
771
+ <div class="hdr-chip" id="hdr-created-wrap" style="display:none">SINCE <span id="hdr-created">—</span></div>
772
+ </div>
773
+ <button id="stop-btn" onclick="stopCurrentOrg()" style="display:none;
774
+ font-size:9px; font-family:var(--mono); letter-spacing:1px; color:var(--red);
775
+ border:1px solid oklch(62% 0.22 25 / 0.35); background:none; padding:4px 12px;
776
+ border-radius:3px; cursor:pointer; text-transform:uppercase; transition:background 0.12s;"
777
+ onmouseover="this.style.background='oklch(62% 0.22 25 / 0.08)'"
778
+ onmouseout="this.style.background='none'">STOP</button>
779
+ </div>
780
+
781
+ <div id="main-tabs">
782
+ <button class="tab-btn active" onclick="switchTab('chart')">ORG CHART</button>
783
+ <button class="tab-btn" onclick="switchTab('roles')">ROLES</button>
784
+ <button class="tab-btn" onclick="switchTab('activity')">ACTIVITY</button>
785
+ <button class="tab-btn" onclick="switchTab('health')">HEALTH</button>
786
+ <button class="tab-btn" onclick="switchTab('chat')">CHAT</button>
787
+ </div>
788
+
789
+ <div id="main-body">
790
+
791
+ <!-- Chart tab -->
792
+ <div class="tab-pane active" id="tab-chart">
793
+ <div id="chart-pane">
794
+ <div id="org-meta-grid">
795
+ <div class="meta-cell">
796
+ <div class="meta-cell-label">GOAL</div>
797
+ <div class="meta-cell-value" id="meta-goal" style="font-size:10px;line-height:1.5;white-space:normal;color:var(--muted)">—</div>
798
+ </div>
799
+ <div class="meta-cell">
800
+ <div class="meta-cell-label">TOPOLOGY</div>
801
+ <div class="meta-cell-value" id="meta-topology">—</div>
802
+ </div>
803
+ <div class="meta-cell">
804
+ <div class="meta-cell-label">CREATED</div>
805
+ <div class="meta-cell-value" id="meta-created" style="font-size:10px;color:var(--muted)">—</div>
806
+ </div>
807
+ </div>
808
+ <div id="chart-svg-wrap">
809
+ <svg id="org-chart-svg" viewBox="0 0 720 320">
810
+ <defs>
811
+ <marker id="arr-teal" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto">
812
+ <path d="M0,0 L0,6 L6,3 z" fill="oklch(62% 0.20 186 / 0.5)"/>
813
+ </marker>
814
+ <marker id="arr-indigo" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto">
815
+ <path d="M0,0 L0,6 L6,3 z" fill="oklch(68% 0.18 252 / 0.45)"/>
816
+ </marker>
817
+ <marker id="arr-amber" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto">
818
+ <path d="M0,0 L0,6 L6,3 z" fill="oklch(78% 0.18 80 / 0.35)"/>
819
+ </marker>
820
+ <marker id="arr-green" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto">
821
+ <path d="M0,0 L0,6 L6,3 z" fill="oklch(68% 0.20 150 / 0.4)"/>
822
+ </marker>
823
+ <filter id="node-glow" x="-50%" y="-50%" width="200%" height="200%">
824
+ <feGaussianBlur in="SourceGraphic" stdDeviation="3" result="b"/>
825
+ <feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
826
+ </filter>
827
+ </defs>
828
+ <rect width="720" height="320" fill="oklch(7% 0.008 186)"/>
829
+ <g id="oc-edges"></g>
830
+ <g id="oc-nodes"></g>
831
+ </svg>
832
+ </div>
833
+
834
+ <!-- Live status strip -->
835
+ <div id="live-strip">
836
+ <div id="ls-state">
837
+ <span id="ls-dot" class="ls-dot"></span>
838
+ <span id="ls-label">IDLE</span>
839
+ </div>
840
+ <div id="ls-uptime" class="ls-metric" title="Uptime">—</div>
841
+ <div id="ls-last" class="ls-metric" title="Last event">—</div>
842
+ <div id="ls-epm" class="ls-metric" title="Events / 5 min">—</div>
843
+ <div id="ls-sparkline" class="ls-sparkline-wrap"></div>
844
+ </div>
845
+ <!-- Live event feed -->
846
+ <div id="clf-wrap">
847
+ <div id="clf-header">LIVE EVENTS</div>
848
+ <div id="clf-rows"></div>
849
+ </div>
850
+
851
+ <!-- Legend -->
852
+ <div style="display:flex; gap:16px; flex-wrap:wrap; padding:0 2px;">
853
+ <div style="display:flex; align-items:center; gap:5px; font-size:8px; color:var(--dim);">
854
+ <svg width="24" height="8"><line x1="0" y1="4" x2="24" y2="4" stroke="oklch(62% 0.20 186)" stroke-opacity="0.5" stroke-width="1.5" marker-end="url(#arr-teal)"/></svg>
855
+ COMMAND
856
+ </div>
857
+ <div style="display:flex; align-items:center; gap:5px; font-size:8px; color:var(--dim);">
858
+ <svg width="24" height="8"><line x1="0" y1="4" x2="24" y2="4" stroke="oklch(68% 0.18 252)" stroke-opacity="0.45" stroke-width="1" stroke-dasharray="4 3" marker-end="url(#arr-indigo)"/></svg>
859
+ REPORT
860
+ </div>
861
+ <div style="display:flex; align-items:center; gap:5px; font-size:8px; color:var(--dim);">
862
+ <svg width="24" height="8"><line x1="0" y1="4" x2="24" y2="4" stroke="oklch(78% 0.18 80)" stroke-opacity="0.35" stroke-width="1" stroke-dasharray="2 4" marker-end="url(#arr-amber)"/></svg>
863
+ FEEDBACK
864
+ </div>
865
+ <div style="display:flex; align-items:center; gap:5px; font-size:8px; color:var(--dim);">
866
+ <svg width="24" height="8"><line x1="0" y1="4" x2="24" y2="4" stroke="oklch(68% 0.20 150)" stroke-opacity="0.4" stroke-width="1.5" marker-end="url(#arr-green)"/></svg>
867
+ HANDOFF
868
+ </div>
869
+ </div>
870
+ </div>
871
+ </div>
872
+
873
+ <!-- Roles tab -->
874
+ <div class="tab-pane" id="tab-roles">
875
+ <div id="roles-pane">
876
+ <div id="roles-grid" class="roles-grid"></div>
877
+ </div>
878
+ </div>
879
+
880
+ <!-- Activity tab -->
881
+ <div class="tab-pane" id="tab-activity">
882
+ <div id="activity-pane">
883
+ <div id="activity-log"></div>
884
+ </div>
885
+ </div>
886
+
887
+ <!-- Health tab -->
888
+ <div class="tab-pane" id="tab-health">
889
+ <div id="health-pane">
890
+ <div id="health-grid" class="health-grid"></div>
891
+ <div id="health-detail"></div>
892
+ </div>
893
+ </div>
894
+
895
+ <!-- Chat tab -->
896
+ <div class="tab-pane" id="tab-chat">
897
+ <div id="chat-pane">
898
+ <div id="chat-session-bar">
899
+ <span id="chat-sess-label">SESSION</span>
900
+ <select id="chat-sess-select" onchange="selectChatSession(this.value)">
901
+ <option value="">— select a session —</option>
902
+ </select>
903
+ <div id="chat-mode-bar">
904
+ <button class="chat-mode-btn active" id="mode-summary" onclick="setChatMode('summary')">SUMMARY</button>
905
+ <button class="chat-mode-btn" id="mode-detailed" onclick="setChatMode('detailed')">DETAILED</button>
906
+ <button class="chat-mode-btn" id="mode-raw" onclick="setChatMode('raw')">RAW</button>
907
+ </div>
908
+ <div id="chat-live-dot"></div>
909
+ <span id="chat-live-label">OFFLINE</span>
910
+ </div>
911
+ <div id="chat-agent-bar" style="display:none">
912
+ <span id="chat-agent-label">AGENT</span>
913
+ </div>
914
+ <div id="chat-feed-wrap">
915
+ <div id="chat-feed-empty">Select a session above to see agent communications.</div>
916
+ </div>
917
+ </div>
918
+ </div>
919
+
920
+ </div><!-- end main-body -->
921
+ </div><!-- end org-detail -->
922
+ </div><!-- end main -->
923
+ </div><!-- end app -->
924
+
925
+ <!-- ── Copy-to-project modal ── -->
926
+ <div id="copy-modal-overlay" onclick="if(event.target===this)closeCopyModal()">
927
+ <div id="copy-modal">
928
+ <div id="copy-modal-title">COPY ORG TO PROJECT</div>
929
+ <div id="copy-modal-org">—</div>
930
+ <div class="copy-modal-label">SELECT DESTINATION PROJECT</div>
931
+ <div id="copy-project-list"><div class="copy-proj-loading">Loading projects…</div></div>
932
+ <div class="copy-modal-label" style="margin-top:4px">OR ENTER PATH MANUALLY</div>
933
+ <input id="copy-dest-input" type="text" placeholder="/absolute/path/to/project"
934
+ onkeydown="if(event.key==='Enter')executeCopy(); if(event.key==='Escape')closeCopyModal()"
935
+ oninput="document.querySelectorAll('.copy-proj-row').forEach(r=>r.classList.remove('selected'))">
936
+ <div id="copy-modal-hint">
937
+ Writes to <span style="color:var(--teal)">&lt;destination&gt;/.monomind/orgs/&lt;name&gt;.json</span>
938
+ </div>
939
+ <div id="copy-modal-status"></div>
940
+ <div class="copy-modal-actions">
941
+ <button class="cm-btn" onclick="closeCopyModal()">CANCEL</button>
942
+ <button class="cm-btn primary" id="copy-confirm-btn" onclick="executeCopy()">COPY</button>
943
+ </div>
944
+ </div>
945
+ </div>
946
+
947
+ <script>
948
+ 'use strict';
949
+
950
+ // ── State ─────────────────────────────────────────────────────────
951
+ let allOrgs = [];
952
+ let selectedOrg = null;
953
+ let orgDetailData = null;
954
+ let currentTab = 'chart';
955
+ // Phase 3: Chat render mode (Summary / Detailed / Raw)
956
+ let chatRenderMode = localStorage.getItem('chatRenderMode') || 'summary';
957
+
958
+ // Phase 3: Chat render mode — Summary / Detailed / Raw
959
+ const CHAT_SUMMARY_TYPES = new Set(['session:start','session:complete','domain:dispatch','domain:complete','agent:spawn','agent:complete','agent:edit','org:comms','loop:start','loop:complete','org:artifact','agent:message','intercom']);
960
+ const CHAT_DETAILED_TYPES = new Set([...CHAT_SUMMARY_TYPES,'agent:bash','agent:bash:result','agent:browse','agent:read','agent:read:batch']);
961
+ function setChatMode(mode) {
962
+ chatRenderMode = mode;
963
+ localStorage.setItem('chatRenderMode', mode);
964
+ ['summary','detailed','raw'].forEach(m => {
965
+ const btn = document.getElementById('mode-' + m);
966
+ if (btn) btn.className = 'chat-mode-btn' + (m === mode ? ' active' : '');
967
+ });
968
+ // Re-render chat feed with new mode
969
+ const currentSessId = document.getElementById('chat-sess-select')?.value;
970
+ if (currentSessId) selectChatSession(currentSessId);
971
+ }
972
+ // Apply saved mode on load
973
+ (function() {
974
+ ['summary','detailed','raw'].forEach(m => {
975
+ const btn = document.getElementById('mode-' + m);
976
+ if (btn) btn.className = 'chat-mode-btn' + (m === chatRenderMode ? ' active' : '');
977
+ });
978
+ })();
979
+
980
+ // ── Utilities ──────────────────────────────────────────────────────
981
+ function esc(s) {
982
+ return String(s ?? '')
983
+ .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
984
+ .replace(/"/g,'&quot;').replace(/'/g,'&#39;');
985
+ }
986
+ function fmtDate(ts) {
987
+ if (!ts) return '—';
988
+ const d = new Date(typeof ts === 'number' ? ts : Date.parse(ts));
989
+ if (isNaN(d)) return ts;
990
+ return d.toLocaleDateString('en', {month:'short', day:'numeric', year:'numeric'});
991
+ }
992
+ function fmtTime(ts) {
993
+ if (!ts) return '';
994
+ const d = new Date(typeof ts === 'number' ? ts : Date.parse(ts));
995
+ if (isNaN(d)) return '';
996
+ return d.toLocaleTimeString('en', {hour12:false, hour:'2-digit', minute:'2-digit', second:'2-digit'});
997
+ }
998
+ function fmtEventType(t) {
999
+ const m = {
1000
+ 'org:start':'START','org:stop':'STOP','org:complete':'COMPLETE',
1001
+ 'org:create':'CREATE','org:heartbeat':'HB','org:agent:online':'AGENT ON',
1002
+ 'org:comms':'COMMS','org:checkpoint':'CHECKPOINT',
1003
+ };
1004
+ return m[t] || (t || '').replace(/^org:/,'').toUpperCase();
1005
+ }
1006
+
1007
+ // Role colors cycle
1008
+ const ROLE_COLORS = [
1009
+ 'oklch(62% 0.20 186)',
1010
+ 'oklch(68% 0.18 252)',
1011
+ 'oklch(68% 0.20 150)',
1012
+ 'oklch(78% 0.18 80)',
1013
+ 'oklch(62% 0.22 25)',
1014
+ 'oklch(74% 0.16 310)',
1015
+ ];
1016
+ function roleColor(i) { return ROLE_COLORS[i % ROLE_COLORS.length]; }
1017
+
1018
+ // ── Tab switching ─────────────────────────────────────────────────
1019
+ window.switchTab = function(tab) {
1020
+ currentTab = tab;
1021
+ document.querySelectorAll('.tab-btn').forEach(b => {
1022
+ b.classList.toggle('active', b.textContent.toLowerCase().includes(tab) ||
1023
+ (tab === 'chart' && b.textContent === 'ORG CHART') ||
1024
+ (tab === 'roles' && b.textContent === 'ROLES') ||
1025
+ (tab === 'activity' && b.textContent === 'ACTIVITY') ||
1026
+ (tab === 'health' && b.textContent === 'HEALTH') ||
1027
+ (tab === 'chat' && b.textContent === 'CHAT'));
1028
+ });
1029
+ document.querySelectorAll('.tab-pane').forEach(p => p.classList.remove('active'));
1030
+ const pane = document.getElementById('tab-' + tab);
1031
+ if (pane) pane.classList.add('active');
1032
+ if (tab === 'chat') renderChatTab();
1033
+ else if (orgDetailData) renderTab(tab);
1034
+ };
1035
+
1036
+ // ── Render all orgs in sidebar ────────────────────────────────────
1037
+ function renderSidebar() {
1038
+ const list = document.getElementById('sb-list');
1039
+ const empty = document.getElementById('sb-empty');
1040
+ const dot = document.getElementById('sb-live-dot');
1041
+ const statusText = document.getElementById('sb-status-text');
1042
+ const runCount = document.getElementById('sb-running-count');
1043
+
1044
+ // Clear existing items
1045
+ list.querySelectorAll('.org-item').forEach(el => el.remove());
1046
+
1047
+ const running = allOrgs.filter(o => o.running).length;
1048
+ if (running > 0) {
1049
+ dot.classList.add('on');
1050
+ statusText.textContent = 'LIVE';
1051
+ runCount.textContent = running + ' RUNNING';
1052
+ } else {
1053
+ dot.classList.remove('on');
1054
+ statusText.textContent = allOrgs.length ? 'IDLE' : 'EMPTY';
1055
+ runCount.textContent = '';
1056
+ }
1057
+
1058
+ if (!allOrgs.length) {
1059
+ empty.style.display = 'block';
1060
+ return;
1061
+ }
1062
+ empty.style.display = 'none';
1063
+
1064
+ allOrgs.forEach(org => {
1065
+ const item = document.createElement('div');
1066
+ item.className = 'org-item' +
1067
+ (org.running ? ' running' : '') +
1068
+ (selectedOrg === org.name ? ' active' : '');
1069
+ item.dataset.orgName = org.name;
1070
+
1071
+ const rolesCount = Array.isArray(org.roles) ? org.roles.length : (org.roles || 0);
1072
+ const topo = org.topology || 'hierarchical';
1073
+ const goalText = (org.goal || '').slice(0, 55) + ((org.goal || '').length > 55 ? '…' : '');
1074
+
1075
+ item.innerHTML = `
1076
+ <div class="oi-header">
1077
+ <div class="oi-dot ${org.running ? 'running' : ''}"></div>
1078
+ <div class="oi-name">${esc(org.name)}</div>
1079
+ <span class="oi-badge ${(()=>{if(!org.running)return 'idle';if(!org.lastEventAt)return 'quiet';const a=Date.now()-org.lastEventAt;return a<1800000?'running':a<7200000?'quiet':'stale';})()} ">${(()=>{if(!org.running)return 'IDLE';if(!org.lastEventAt)return '🟡 QUIET';const a=Date.now()-org.lastEventAt;return a<1800000?'🟢 LIVE':a<7200000?'🟡 QUIET':'🔴 STALE';})()}</span>
1080
+ <button class="oi-copy-btn" title="Copy org to another project">COPY</button>
1081
+ <button class="oi-export-btn" title="Export org as JSON file">EXPORT</button>
1082
+ <button class="oi-import-btn" title="Import org from JSON file">IMPORT</button>
1083
+ </div>
1084
+ ${goalText ? `<div class="oi-goal">${esc(goalText)}</div>` : ''}
1085
+ <div class="oi-meta">
1086
+ <span class="oi-chip">${rolesCount} <span>roles</span></span>
1087
+ <span class="oi-chip">${esc(topo)}</span>
1088
+ </div>`;
1089
+
1090
+ item.querySelector('.oi-copy-btn').addEventListener('click', e => {
1091
+ e.stopPropagation();
1092
+ openCopyModal(org.name);
1093
+ });
1094
+ item.querySelector('.oi-export-btn').addEventListener('click', e => {
1095
+ e.stopPropagation();
1096
+ exportOrg(org.name);
1097
+ });
1098
+ item.querySelector('.oi-import-btn').addEventListener('click', e => {
1099
+ e.stopPropagation();
1100
+ triggerImportOrg(org.name);
1101
+ });
1102
+ item.addEventListener('click', () => selectOrg(org.name));
1103
+ list.appendChild(item);
1104
+ });
1105
+ }
1106
+
1107
+ // ── Select and load an org ────────────────────────────────────────
1108
+ async function selectOrg(name) {
1109
+ selectedOrg = name;
1110
+ chatCurrentId = null; // reset chat session when org changes
1111
+ orgRunStartMs = null;
1112
+ if (lsUptimeTimer) { clearInterval(lsUptimeTimer); lsUptimeTimer = null; }
1113
+
1114
+ // Update sidebar active state
1115
+ document.querySelectorAll('.org-item').forEach(el => {
1116
+ el.classList.toggle('active', el.dataset.orgName === name);
1117
+ });
1118
+
1119
+ // Show detail area
1120
+ document.getElementById('no-org-state').style.display = 'none';
1121
+ const detail = document.getElementById('org-detail');
1122
+ detail.style.display = 'flex';
1123
+
1124
+ // Basic data from list
1125
+ const listOrg = allOrgs.find(o => o.name === name) || {};
1126
+ updateHeader(listOrg, null);
1127
+
1128
+ // Fetch full data
1129
+ orgDetailData = null;
1130
+ try {
1131
+ const [mainR, actR, healthR] = await Promise.all([
1132
+ fetch(`/api/org/${encodeURIComponent(name)}`),
1133
+ fetch(`/api/org/${encodeURIComponent(name)}/activity`).catch(() => null),
1134
+ fetch(`/api/org/${encodeURIComponent(name)}/health`).catch(() => null),
1135
+ ]);
1136
+ if (!mainR.ok) throw new Error('not found');
1137
+ const raw = await mainR.json();
1138
+ // API returns {config, state, goals, routines, approvals, running, tasks} — flatten config to top level
1139
+ orgDetailData = raw.config ? { ...raw.config, ...raw, running: raw.running } : raw;
1140
+ orgDetailData._activity = actR && actR.ok ? await actR.json() : [];
1141
+ orgDetailData._health = healthR && healthR.ok ? await healthR.json() : null;
1142
+ } catch (e) {
1143
+ orgDetailData = { ...listOrg, roles: listOrg.roles || [], communication: [] };
1144
+ orgDetailData._activity = [];
1145
+ orgDetailData._health = null;
1146
+ }
1147
+
1148
+ updateHeader(listOrg, orgDetailData);
1149
+
1150
+ // Pre-populate LIVE EVENTS from historical run data (so panel isn't empty on select)
1151
+ if (listOrg.projectDir) {
1152
+ try {
1153
+ const runsR = await fetch(`/api/orgs/${encodeURIComponent(name)}/runs/current?dir=${encodeURIComponent(listOrg.projectDir)}`);
1154
+ if (runsR.ok) {
1155
+ const runsData = await runsR.json();
1156
+ if (runsData.events && runsData.events.length) {
1157
+ orgEventLog[name] = runsData.events.map(ev => ({ ...ev, _ts: ev.ts || Date.now() }));
1158
+ }
1159
+ }
1160
+ } catch (_) {}
1161
+ }
1162
+
1163
+ renderTab(currentTab);
1164
+ }
1165
+
1166
+ function updateHeader(listOrg, data) {
1167
+ const name = listOrg.name || selectedOrg || '—';
1168
+ document.getElementById('org-name').textContent = name.toUpperCase();
1169
+
1170
+ const running = listOrg.running;
1171
+ const badge = document.getElementById('org-status-badge');
1172
+ // Phase 2: Three-state liveness — LIVE / QUIET / STALE / IDLE (Issue 6)
1173
+ let badgeState, badgeText;
1174
+ if (running) {
1175
+ if (!listOrg.lastEventAt) { badgeState = 'quiet'; badgeText = '🟡 QUIET'; }
1176
+ else {
1177
+ const _ageMs = Date.now() - listOrg.lastEventAt;
1178
+ if (_ageMs < 30 * 60 * 1000) { badgeState = 'running'; badgeText = '🟢 LIVE'; }
1179
+ else if (_ageMs < 2 * 60 * 60 * 1000) { badgeState = 'quiet'; badgeText = '🟡 QUIET'; }
1180
+ else { badgeState = 'stale'; badgeText = '🔴 STALE'; }
1181
+ }
1182
+ } else {
1183
+ badgeState = 'idle'; badgeText = 'IDLE';
1184
+ }
1185
+ badge.textContent = badgeText;
1186
+ badge.className = 'oi-badge ' + badgeState;
1187
+
1188
+ const rolesArr = data ? (data.roles || []) : [];
1189
+ const rolesCount = Array.isArray(rolesArr) ? rolesArr.length : (listOrg.roles || 0);
1190
+ document.getElementById('hdr-roles').textContent = rolesCount;
1191
+ document.getElementById('hdr-topology').textContent = (data?.topology || listOrg.topology || '—').toUpperCase();
1192
+
1193
+ const created = data?.created_at || listOrg.created_at;
1194
+ if (created) {
1195
+ document.getElementById('hdr-created').textContent = fmtDate(created);
1196
+ document.getElementById('hdr-created-wrap').style.display = '';
1197
+ }
1198
+
1199
+ document.getElementById('stop-btn').style.display = running ? '' : 'none';
1200
+ }
1201
+
1202
+ // ── Tab renderers ─────────────────────────────────────────────────
1203
+ function renderTab(tab) {
1204
+ if (!orgDetailData) return;
1205
+ if (tab === 'chart') { renderChart(); initLiveStrip(); }
1206
+ else if (tab === 'roles') renderRoles();
1207
+ else if (tab === 'activity') renderActivity();
1208
+ else if (tab === 'health') renderHealth();
1209
+ }
1210
+
1211
+ // ── CHART TAB ─────────────────────────────────────────────────────
1212
+ function renderChart() {
1213
+ const d = orgDetailData;
1214
+ const roles = Array.isArray(d.roles) ? d.roles : [];
1215
+ const comms = Array.isArray(d.communication) ? d.communication : [];
1216
+ const running = !!(allOrgs.find(o => o.name === selectedOrg) || {}).running;
1217
+
1218
+ // Meta bar
1219
+ document.getElementById('meta-goal').textContent = d.goal || '—';
1220
+ document.getElementById('meta-topology').textContent = (d.topology || '—').toUpperCase();
1221
+ document.getElementById('meta-created').textContent = fmtDate(d.created_at);
1222
+
1223
+ // Build SVG chart
1224
+ const svgEl = document.getElementById('org-chart-svg');
1225
+ const edgesG = document.getElementById('oc-edges');
1226
+ const nodesG = document.getElementById('oc-nodes');
1227
+ edgesG.innerHTML = '';
1228
+ nodesG.innerHTML = '';
1229
+
1230
+ if (!roles.length) {
1231
+ // Empty chart placeholder
1232
+ const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
1233
+ text.setAttribute('x', '360');
1234
+ text.setAttribute('y', '160');
1235
+ text.setAttribute('text-anchor', 'middle');
1236
+ text.setAttribute('fill', 'oklch(36% 0.008 186)');
1237
+ text.setAttribute('font-size', '11');
1238
+ text.setAttribute('font-family', "'Azeret Mono','Space Mono',monospace");
1239
+ text.setAttribute('letter-spacing', '2');
1240
+ text.textContent = 'NO ROLES DEFINED';
1241
+ nodesG.appendChild(text);
1242
+ return;
1243
+ }
1244
+
1245
+ // Layout: compute node positions
1246
+ const W = 720, H = 320;
1247
+ const topo = (d.topology || 'hierarchical').toLowerCase();
1248
+ const positions = computeLayout(roles, comms, topo, W, H);
1249
+
1250
+ const NS = 'http://www.w3.org/2000/svg';
1251
+
1252
+ // Draw edges first
1253
+ const markerMap = {
1254
+ command: 'arr-teal', report: 'arr-indigo', feedback: 'arr-amber', handoff: 'arr-green'
1255
+ };
1256
+ const strokeMap = {
1257
+ command: { color: 'oklch(62% 0.20 186)', opacity: 0.5, width: 1.5, dash: '' },
1258
+ report: { color: 'oklch(68% 0.18 252)', opacity: 0.4, width: 1, dash: '4 3' },
1259
+ feedback: { color: 'oklch(78% 0.18 80)', opacity: 0.3, width: 1, dash: '2 4' },
1260
+ handoff: { color: 'oklch(68% 0.20 150)', opacity: 0.45, width: 1.5, dash: '' },
1261
+ };
1262
+
1263
+ // Build set of edge pairs to detect anti-parallel edges (A→B + B→A)
1264
+ const edgePairSet = new Set(comms.map(c => `${c.from}|${c.to}`));
1265
+
1266
+ comms.forEach(edge => {
1267
+ const fromPos = positions[edge.from];
1268
+ const toPos = positions[edge.to];
1269
+ if (!fromPos || !toPos || edge.from === edge.to) return;
1270
+
1271
+ const style = strokeMap[edge.type] || strokeMap.command;
1272
+ const marker = markerMap[edge.type] || 'arr-teal';
1273
+
1274
+ // Nudge endpoints toward each edge center to avoid overlap with nodes
1275
+ const dx = toPos.x - fromPos.x, dy = toPos.y - fromPos.y;
1276
+ const len = Math.sqrt(dx*dx + dy*dy) || 1;
1277
+ const R = 24; // node radius to clear
1278
+
1279
+ // Perpendicular offset for anti-parallel pairs so both edges remain visible
1280
+ let px = 0, py = 0;
1281
+ if (edgePairSet.has(`${edge.to}|${edge.from}`)) {
1282
+ const [canonFrom, canonTo] = edge.from < edge.to ? [edge.from, edge.to] : [edge.to, edge.from];
1283
+ const cp = positions[canonFrom], ct = positions[canonTo];
1284
+ const cdx = ct.x - cp.x, cdy = ct.y - cp.y;
1285
+ const clen = Math.sqrt(cdx*cdx + cdy*cdy) || 1;
1286
+ const sign = edge.from === canonFrom ? 1 : -1;
1287
+ px = sign * (-cdy / clen) * 6;
1288
+ py = sign * ( cdx / clen) * 6;
1289
+ }
1290
+
1291
+ const x1 = fromPos.x + (dx/len)*R + px;
1292
+ const y1 = fromPos.y + (dy/len)*R + py;
1293
+ const x2 = toPos.x - (dx/len)*(R + 4) + px;
1294
+ const y2 = toPos.y - (dy/len)*(R + 4) + py;
1295
+
1296
+ const line = document.createElementNS(NS, 'line');
1297
+ line.setAttribute('x1', x1.toFixed(1));
1298
+ line.setAttribute('y1', y1.toFixed(1));
1299
+ line.setAttribute('x2', x2.toFixed(1));
1300
+ line.setAttribute('y2', y2.toFixed(1));
1301
+ line.setAttribute('stroke', style.color);
1302
+ line.setAttribute('stroke-opacity', style.opacity);
1303
+ line.setAttribute('stroke-width', style.width);
1304
+ if (style.dash) line.setAttribute('stroke-dasharray', style.dash);
1305
+ line.setAttribute('marker-end', `url(#${marker})`);
1306
+ edgesG.appendChild(line);
1307
+ });
1308
+
1309
+ // Draw nodes
1310
+ roles.forEach((role, i) => {
1311
+ const pos = positions[role.id];
1312
+ if (!pos) return;
1313
+ const isBoss = !role.reports_to || role.reports_to === role.id;
1314
+ const color = roleColor(i);
1315
+
1316
+ const g = document.createElementNS(NS, 'g');
1317
+ g.setAttribute('transform', `translate(${pos.x.toFixed(1)},${pos.y.toFixed(1)})`);
1318
+
1319
+ // Pulse ring (running state)
1320
+ if (running) {
1321
+ const pulse = document.createElementNS(NS, 'circle');
1322
+ pulse.setAttribute('r', '26');
1323
+ pulse.setAttribute('class', 'oc-node-pulse running');
1324
+ pulse.setAttribute('stroke', isBoss ? 'oklch(62% 0.20 186)' : 'oklch(68% 0.20 150)');
1325
+ g.appendChild(pulse);
1326
+ }
1327
+
1328
+ // Node circle
1329
+ const circle = document.createElementNS(NS, 'circle');
1330
+ circle.setAttribute('r', '22');
1331
+ circle.setAttribute('class', `oc-node-bg ${isBoss ? 'boss' : 'peer'}`);
1332
+ circle.setAttribute('stroke', color);
1333
+ circle.setAttribute('filter', 'url(#node-glow)');
1334
+ g.appendChild(circle);
1335
+
1336
+ // Role title — abbreviated to fit circle
1337
+ const titleText = document.createElementNS(NS, 'text');
1338
+ titleText.setAttribute('class', 'oc-node-text');
1339
+ titleText.setAttribute('y', '-4');
1340
+ const title = role.title || role.id;
1341
+ const shortTitle = title.length > 10 ? title.slice(0, 9) + '…' : title;
1342
+ titleText.textContent = shortTitle;
1343
+ titleText.style.fill = color;
1344
+ g.appendChild(titleText);
1345
+
1346
+ // Agent type
1347
+ if (role.agent_type) {
1348
+ const subText = document.createElementNS(NS, 'text');
1349
+ subText.setAttribute('class', 'oc-node-sub');
1350
+ subText.setAttribute('y', '10');
1351
+ const agent = role.agent_type.length > 12 ? role.agent_type.slice(0, 11) + '…' : role.agent_type;
1352
+ subText.textContent = agent;
1353
+ g.appendChild(subText);
1354
+ }
1355
+
1356
+ nodesG.appendChild(g);
1357
+ });
1358
+ }
1359
+
1360
+ function computeLayout(roles, comms, topology, W, H) {
1361
+ const positions = {};
1362
+ const n = roles.length;
1363
+ if (n === 0) return positions;
1364
+
1365
+ const cx = W / 2, cy = H / 2;
1366
+ const PAD = 60;
1367
+
1368
+ if (topology === 'mesh') {
1369
+ // All nodes in a circle
1370
+ roles.forEach((role, i) => {
1371
+ const angle = (2 * Math.PI * i / n) - Math.PI / 2;
1372
+ const r = Math.min((W - PAD*2) / 2, (H - PAD*2) / 2) * 0.8;
1373
+ positions[role.id] = {
1374
+ x: cx + r * Math.cos(angle),
1375
+ y: cy + r * Math.sin(angle),
1376
+ };
1377
+ });
1378
+
1379
+ } else if (topology === 'star') {
1380
+ // Boss in center, others around edge
1381
+ const boss = roles.find(r => !r.reports_to) || roles[0];
1382
+ const rest = roles.filter(r => r.id !== boss.id);
1383
+ positions[boss.id] = { x: cx, y: cy };
1384
+ const r2 = Math.min((W - PAD*2) / 2, (H - PAD*2) / 2) * 0.75;
1385
+ rest.forEach((role, i) => {
1386
+ const angle = (2 * Math.PI * i / rest.length) - Math.PI / 2;
1387
+ positions[role.id] = {
1388
+ x: cx + r2 * Math.cos(angle),
1389
+ y: cy + r2 * Math.sin(angle),
1390
+ };
1391
+ });
1392
+
1393
+ } else {
1394
+ // Hierarchical: layer by reports_to depth
1395
+ const depthMap = {};
1396
+ const childMap = {};
1397
+ roles.forEach(r => {
1398
+ if (!r.reports_to || r.reports_to === r.id) { depthMap[r.id] = 0; }
1399
+ const parent = r.reports_to;
1400
+ if (parent && parent !== r.id) {
1401
+ if (!childMap[parent]) childMap[parent] = [];
1402
+ childMap[parent].push(r.id);
1403
+ }
1404
+ });
1405
+ // BFS to assign depths
1406
+ let changed = true, iter = 0;
1407
+ while (changed && iter < 20) {
1408
+ changed = false; iter++;
1409
+ roles.forEach(r => {
1410
+ if (r.reports_to && r.reports_to !== r.id && depthMap[r.reports_to] !== undefined) {
1411
+ const newDepth = (depthMap[r.reports_to] || 0) + 1;
1412
+ if (depthMap[r.id] !== newDepth) { depthMap[r.id] = newDepth; changed = true; }
1413
+ }
1414
+ });
1415
+ }
1416
+ // Assign depth 0 to any unresolved
1417
+ roles.forEach(r => { if (depthMap[r.id] === undefined) depthMap[r.id] = 1; });
1418
+
1419
+ const maxDepth = Math.max(...Object.values(depthMap));
1420
+ const layerMap = {};
1421
+ Object.entries(depthMap).forEach(([id, depth]) => {
1422
+ if (!layerMap[depth]) layerMap[depth] = [];
1423
+ layerMap[depth].push(id);
1424
+ });
1425
+
1426
+ const rowH = maxDepth > 0 ? (H - PAD * 2) / (maxDepth + 1) : H - PAD * 2;
1427
+ Object.entries(layerMap).forEach(([depth, ids]) => {
1428
+ const y = PAD + Number(depth) * rowH + (maxDepth > 0 ? rowH / 2 : H / 2);
1429
+ ids.forEach((id, i) => {
1430
+ const colW = (W - PAD * 2) / ids.length;
1431
+ const x = PAD + colW * i + colW / 2;
1432
+ positions[id] = { x, y };
1433
+ });
1434
+ });
1435
+ }
1436
+
1437
+ return positions;
1438
+ }
1439
+
1440
+ // ── ROLES TAB ─────────────────────────────────────────────────────
1441
+ function renderRoles() {
1442
+ const d = orgDetailData;
1443
+ const roles = Array.isArray(d.roles) ? d.roles : [];
1444
+ const grid = document.getElementById('roles-grid');
1445
+ grid.innerHTML = '';
1446
+
1447
+ if (!roles.length) {
1448
+ grid.innerHTML = '<div style="color:var(--dim);font-size:9px;letter-spacing:1px;">NO ROLES DEFINED</div>';
1449
+ return;
1450
+ }
1451
+
1452
+ roles.forEach((role, i) => {
1453
+ const color = roleColor(i);
1454
+ const isBoss = !role.reports_to || role.reports_to === role.id;
1455
+ const resps = Array.isArray(role.responsibilities) ? role.responsibilities : [];
1456
+
1457
+ const block = document.createElement('div');
1458
+ block.className = 'role-block';
1459
+
1460
+ block.innerHTML = `
1461
+ <div class="rb-header">
1462
+ <div class="rb-dot" style="background:${color}; box-shadow:0 0 4px ${color.replace(')', ' / 0.31)')};"></div>
1463
+ <div class="rb-title">${esc(role.title || role.id)}</div>
1464
+ ${role.agent_type ? `<span class="rb-agent">${esc(role.agent_type)}</span>` : ''}
1465
+ </div>
1466
+ <div class="rb-id">id: <span style="color:var(--muted)">${esc(role.id)}</span>${isBoss ? ' <span style="color:var(--teal);font-size:8px;margin-left:4px">BOSS</span>' : ''}</div>
1467
+ ${role.reports_to && !isBoss ? `<div class="rb-reports">reports to: <span>${esc(role.reports_to)}</span></div>` : ''}
1468
+ ${resps.length ? `<div class="rb-resps">${resps.map(r => `<div class="rb-resp-item">${esc(r)}</div>`).join('')}</div>` : ''}
1469
+ `;
1470
+
1471
+ grid.appendChild(block);
1472
+ });
1473
+ }
1474
+
1475
+ // ── ACTIVITY TAB ─────────────────────────────────────────────────
1476
+ function renderActivity() {
1477
+ const log = document.getElementById('activity-log');
1478
+ const activity = orgDetailData._activity;
1479
+
1480
+ // Also inject org event log from SSE-captured events
1481
+ const orgEvents = orgEventLog[selectedOrg] || [];
1482
+
1483
+ // Merge and sort
1484
+ let events = [];
1485
+ if (Array.isArray(activity)) events = [...activity];
1486
+ events.push(...orgEvents);
1487
+ events.sort((a, b) => (b.ts || 0) - (a.ts || 0));
1488
+
1489
+ if (!events.length) {
1490
+ log.innerHTML = '<div class="act-empty">NO ACTIVITY RECORDED</div>';
1491
+ return;
1492
+ }
1493
+
1494
+ log.innerHTML = events.slice(0, 80).map(ev => {
1495
+ const t = fmtTime(ev.ts);
1496
+ const type = fmtEventType(ev.type);
1497
+ const detail = ev.role ? ev.role : ev.msg ? ev.msg : ev.agent ? ev.agent : '';
1498
+ return `<div class="act-row">
1499
+ <span class="act-time">${esc(t)}</span>
1500
+ <span class="act-type">${esc(type)}</span>
1501
+ <span class="act-msg">${esc(detail)}</span>
1502
+ </div>`;
1503
+ }).join('');
1504
+ }
1505
+
1506
+ // ── HEALTH TAB ─────────────────────────────────────────────────────
1507
+ function renderHealth() {
1508
+ const health = orgDetailData._health;
1509
+ const listOrg = allOrgs.find(o => o.name === selectedOrg) || {};
1510
+ const roles = Array.isArray(orgDetailData.roles) ? orgDetailData.roles : [];
1511
+ const running = listOrg.running;
1512
+
1513
+ const grid = document.getElementById('health-grid');
1514
+ const detail = document.getElementById('health-detail');
1515
+
1516
+ grid.innerHTML = `
1517
+ <div class="health-cell">
1518
+ <div class="hc-label">STATUS</div>
1519
+ <div class="hc-value ${running ? 'green' : ''}">${running ? 'LIVE' : 'IDLE'}</div>
1520
+ </div>
1521
+ <div class="health-cell">
1522
+ <div class="hc-label">ROLES</div>
1523
+ <div class="hc-value">${roles.length}</div>
1524
+ </div>
1525
+ <div class="health-cell">
1526
+ <div class="hc-label">TOPOLOGY</div>
1527
+ <div class="hc-value" style="font-size:13px">${esc((orgDetailData.topology || '—').toUpperCase())}</div>
1528
+ </div>
1529
+ ${health?.agents_active !== undefined ? `
1530
+ <div class="health-cell">
1531
+ <div class="hc-label">AGENTS</div>
1532
+ <div class="hc-value ${health.agents_active > 0 ? 'green' : ''}">${health.agents_active}</div>
1533
+ <div class="hc-sub">active</div>
1534
+ </div>` : ''}
1535
+ ${health?.tasks_pending !== undefined ? `
1536
+ <div class="health-cell">
1537
+ <div class="hc-label">TASKS</div>
1538
+ <div class="hc-value ${health.tasks_pending > 5 ? 'amber' : ''}">${health.tasks_pending}</div>
1539
+ <div class="hc-sub">pending</div>
1540
+ </div>` : ''}
1541
+ ${health?.run_success_rate_7d !== null && health?.run_success_rate_7d !== undefined ? `
1542
+ <div class="health-cell">
1543
+ <div class="hc-label">SUCCESS</div>
1544
+ <div class="hc-value ${health.run_success_rate_7d >= 80 ? 'green' : health.run_success_rate_7d >= 50 ? 'amber' : 'red'}">${health.run_success_rate_7d}%</div>
1545
+ <div class="hc-sub">7d rate (${health.total_runs_7d || 0} runs)</div>
1546
+ </div>` : ''}
1547
+ ${health?.budget_used_pct !== null && health?.budget_used_pct !== undefined ? `
1548
+ <div class="health-cell">
1549
+ <div class="hc-label">BUDGET</div>
1550
+ <div class="hc-value ${health.budget_used_pct > 90 ? 'red' : health.budget_used_pct > 70 ? 'amber' : ''}">${health.budget_used_pct}%</div>
1551
+ <div class="hc-sub">tokens used</div>
1552
+ </div>` : ''}
1553
+ `;
1554
+
1555
+ if (health?.errors && health.errors.length) {
1556
+ detail.innerHTML = `
1557
+ <div style="margin-top:16px">
1558
+ <div style="font-size:8px;letter-spacing:2px;color:var(--dim);margin-bottom:8px">RECENT ERRORS</div>
1559
+ ${health.errors.slice(0,5).map(e => `
1560
+ <div style="font-size:9px;color:var(--red);padding:5px 0;border-bottom:1px solid oklch(62% 0.22 25 / 0.1)">
1561
+ ${esc(e)}
1562
+ </div>`).join('')}
1563
+ </div>`;
1564
+ } else {
1565
+ detail.innerHTML = health ? `
1566
+ <div style="margin-top:16px;font-size:9px;color:var(--dim);letter-spacing:1px;">
1567
+ ${running ? 'ORG IS RUNNING — NO ERRORS DETECTED' : 'ORG IS IDLE'}
1568
+ </div>` : `
1569
+ <div style="margin-top:16px;font-size:9px;color:var(--dim);letter-spacing:1px;">
1570
+ HEALTH DATA UNAVAILABLE
1571
+ </div>`;
1572
+ }
1573
+ }
1574
+
1575
+ // ── CHAT TAB ──────────────────────────────────────────────────────
1576
+ let chatSessions = []; // all sessions from /api/mastermind/sessions
1577
+ let chatCurrentId = null; // currently viewed session id
1578
+ let chatCurrentAgent = 'all'; // 'all' or an agent/role name
1579
+ let chatScrollLocked = false;
1580
+ let mmSse = null;
1581
+
1582
+ async function loadChatSessions() {
1583
+ try {
1584
+ const r = await fetch('/api/mastermind/sessions');
1585
+ if (!r.ok) return;
1586
+ chatSessions = await r.json();
1587
+ populateChatSelect();
1588
+ } catch(_) {}
1589
+ }
1590
+
1591
+ function orgSessionMatch(s) {
1592
+ if (!selectedOrg) return true;
1593
+ const orgLc = selectedOrg.toLowerCase();
1594
+ const prompt = (s.prompt || '').toLowerCase();
1595
+ if (prompt.includes('running org: ' + orgLc) || prompt.includes('org: ' + orgLc)) return true;
1596
+ // Also check events for an org field
1597
+ return (s.events || []).some(ev => (ev.org || '').toLowerCase() === orgLc);
1598
+ }
1599
+
1600
+ function populateChatSelect() {
1601
+ const sel = document.getElementById('chat-sess-select');
1602
+ if (!sel) return;
1603
+ const prev = chatCurrentId || sel.value;
1604
+
1605
+ // Filter to sessions for this org; fall back to all if none match
1606
+ const orgSessions = chatSessions.filter(orgSessionMatch);
1607
+ const list = orgSessions.length ? orgSessions : chatSessions;
1608
+ const isFiltered = orgSessions.length > 0 && orgSessions.length < chatSessions.length;
1609
+
1610
+ const placeholder = isFiltered
1611
+ ? `— ${orgSessions.length} session${orgSessions.length !== 1 ? 's' : ''} for ${selectedOrg} —`
1612
+ : '— select a session —';
1613
+ sel.innerHTML = `<option value="">${placeholder}</option>`;
1614
+
1615
+ list.forEach(s => {
1616
+ const d = new Date(s.ts);
1617
+ const ts = d.toLocaleTimeString([],{hour:'2-digit',minute:'2-digit',second:'2-digit'});
1618
+ const date = d.toLocaleDateString([],{month:'short',day:'numeric'});
1619
+ const prompt = (s.prompt || '').replace(/^running org:\s*/i, '').slice(0, 40);
1620
+ const label = `${date} ${ts} ${prompt}${prompt.length >= 40 ? '…' : ''} [${s.status || '?'}]`;
1621
+ const opt = document.createElement('option');
1622
+ opt.value = s.id;
1623
+ opt.textContent = label;
1624
+ if (s.id === prev) opt.selected = true;
1625
+ sel.appendChild(opt);
1626
+ });
1627
+
1628
+ // Auto-select most recent running session for this org
1629
+ if (!chatCurrentId) {
1630
+ const running = list.find(s => s.status === 'running');
1631
+ if (running) { sel.value = running.id; selectChatSession(running.id); }
1632
+ }
1633
+ }
1634
+
1635
+ window.selectChatSession = function(id) {
1636
+ chatCurrentId = id;
1637
+ chatCurrentAgent = 'all';
1638
+ const feed = document.getElementById('chat-feed-wrap');
1639
+ const empty = document.getElementById('chat-feed-empty');
1640
+ const bar = document.getElementById('chat-agent-bar');
1641
+ if (!id) {
1642
+ feed.querySelectorAll('.cmsg').forEach(e=>e.remove());
1643
+ if (empty) empty.style.display = 'block';
1644
+ if (bar) bar.style.display = 'none';
1645
+ return;
1646
+ }
1647
+ const sess = chatSessions.find(s => s.id === id);
1648
+ if (!sess) return;
1649
+ feed.querySelectorAll('.cmsg').forEach(e=>e.remove());
1650
+ if (empty) empty.style.display = 'none';
1651
+ populateChatAgentBar(sess.events || []);
1652
+ (sess.events || []).forEach(ev => appendChatEvent(ev, false));
1653
+ chatScrollFeed();
1654
+ };
1655
+
1656
+ function chatAgentMatchesFilter(ev) {
1657
+ if (chatCurrentAgent === 'all') return true;
1658
+ return ev.agent === chatCurrentAgent || ev.from === chatCurrentAgent || ev.to === chatCurrentAgent;
1659
+ }
1660
+
1661
+ function populateChatAgentBar(events) {
1662
+ const bar = document.getElementById('chat-agent-bar');
1663
+ if (!bar) return;
1664
+ const agentSet = new Set();
1665
+ (events || []).forEach(ev => {
1666
+ if (ev.agent) agentSet.add(ev.agent);
1667
+ if (ev.from && ev.from !== '?') agentSet.add(ev.from);
1668
+ if (ev.to && ev.to !== '?') agentSet.add(ev.to);
1669
+ });
1670
+ // Add org roles even if not yet seen in events
1671
+ if (orgDetailData && Array.isArray(orgDetailData.roles)) {
1672
+ orgDetailData.roles.forEach(r => {
1673
+ const n = typeof r === 'string' ? r : (r.role || r.name || r.id || '');
1674
+ if (n) agentSet.add(n);
1675
+ });
1676
+ }
1677
+ const agents = [...agentSet].sort();
1678
+ if (!agents.length) { bar.style.display = 'none'; return; }
1679
+ bar.style.display = 'flex';
1680
+ bar.innerHTML = '<span id="chat-agent-label">AGENT</span>';
1681
+ ['all', ...agents].forEach(name => {
1682
+ const pill = document.createElement('button');
1683
+ pill.className = 'chat-agent-pill' + (chatCurrentAgent === name ? ' active' : '');
1684
+ pill.textContent = name === 'all' ? 'ALL' : name;
1685
+ pill.dataset.agent = name;
1686
+ pill.onclick = () => selectChatAgent(name);
1687
+ bar.appendChild(pill);
1688
+ });
1689
+ }
1690
+
1691
+ window.selectChatAgent = function(name) {
1692
+ chatCurrentAgent = name;
1693
+ document.querySelectorAll('#chat-agent-bar .chat-agent-pill').forEach(p => {
1694
+ p.classList.toggle('active', p.dataset.agent === name);
1695
+ });
1696
+ const feed = document.getElementById('chat-feed-wrap');
1697
+ const empty = document.getElementById('chat-feed-empty');
1698
+ const sess = chatSessions.find(s => s.id === chatCurrentId);
1699
+ feed.querySelectorAll('.cmsg').forEach(e => e.remove());
1700
+ if (!sess) return;
1701
+ const visible = (sess.events || []).filter(ev => chatAgentMatchesFilter(ev));
1702
+ if (!visible.length) {
1703
+ if (empty) { empty.style.display = 'block'; empty.textContent = name === 'all' ? 'No events.' : `No events for "${name}"`; }
1704
+ } else {
1705
+ if (empty) empty.style.display = 'none';
1706
+ visible.forEach(ev => appendChatEvent(ev, false));
1707
+ }
1708
+ chatScrollFeed();
1709
+ };
1710
+
1711
+ function appendChatEvent(ev, animate) {
1712
+ if (!chatAgentMatchesFilter(ev)) return;
1713
+ // Phase 3: Apply render mode filter
1714
+ if (chatRenderMode === 'summary' && !CHAT_SUMMARY_TYPES.has(ev.type)) return;
1715
+ if (chatRenderMode === 'detailed' && !CHAT_DETAILED_TYPES.has(ev.type)) return;
1716
+ // 'raw' mode: show everything
1717
+ const feed = document.getElementById('chat-feed-wrap');
1718
+ const empty = document.getElementById('chat-feed-empty');
1719
+ if (!feed) return;
1720
+ if (empty) empty.style.display = 'none';
1721
+ const ts = new Date(ev.ts || Date.now()).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit',second:'2-digit'});
1722
+ const DCOLS = {build:'#60a5fa',idea:'#fbbf24',marketing:'#f472b6',review:'#34d399',research:'#a78bfa',content:'#fb923c',release:'#22d3ee',sales:'#f87171',ops:'#4ade80',finance:'#fde68a',orgs:'#c084fc'};
1723
+ const dc = d => DCOLS[d] || 'oklch(62% 0.20 186)';
1724
+ let el = null;
1725
+
1726
+ if (ev.type === 'session:start') {
1727
+ el = mkSys(`🔵 Session started${ev.prompt ? ': ' + esc(ev.prompt.slice(0,80)) : ''}`, ts);
1728
+ } else if (ev.type === 'session:complete') {
1729
+ el = mkSys(`🏁 Session complete — ${esc((ev.domains||[]).join(', ') || 'done')}`, ts);
1730
+ } else if (ev.type === 'domain:dispatch') {
1731
+ el = mkSys(`<span style="color:${dc(ev.domain)}">⚙ [${esc((ev.domain||'').toUpperCase())}]</span> ${esc(ev.cmd||'dispatched')}`, ts);
1732
+ } else if (ev.type === 'domain:complete') {
1733
+ const arts = (ev.artifacts||[]).length ? ` · ${ev.artifacts.length} artifact${ev.artifacts.length!==1?'s':''}` : '';
1734
+ el = mkSys(`<span style="color:${dc(ev.domain)}">✓ [${esc((ev.domain||'').toUpperCase())}]</span> ${esc(ev.status||'done')}${arts}`, ts);
1735
+ } else if (ev.type === 'agent:spawn') {
1736
+ const col = dc(ev.domain);
1737
+ const name = ev.agentType || ev.from || ev.agent || 'agent';
1738
+ el = mkAgent(name, `spawned: ${esc(ev.task||'task')}`, ts, col, 'spawn');
1739
+ } else if (ev.type === 'agent:complete') {
1740
+ const name = ev.agentType || ev.role || ev.from || 'agent';
1741
+ const result = (ev.result || '').slice(0, 300);
1742
+ const cost = ev.cost_usd ? ` · $${parseFloat(ev.cost_usd).toFixed(4)}` : '';
1743
+ const tools = ev.toolCalls?.length ? ` · ${ev.toolCalls.slice(0,4).join(', ')}` : '';
1744
+ el = mkAgent(name, `${esc(result) || 'done'}${esc(tools)}${esc(cost)}`, ts, dc(ev.domain), 'complete');
1745
+ } else if (ev.type === 'org:comms') {
1746
+ el = mkIntercom(ev.from || 'boss', ev.to || 'agent', esc(ev.msg || ev.message || ''), ts);
1747
+ } else if (ev.type === 'agent:message') {
1748
+ el = mkAgent(ev.agent||'agent', esc(ev.text||''), ts, dc(ev.domain), null);
1749
+ } else if (ev.type === 'intercom') {
1750
+ el = mkIntercom(ev.from, ev.to, esc(ev.msg||''), ts);
1751
+ } else if (ev.type === 'loop:start') {
1752
+ el = mkSys(`🔁 Loop: ${esc(ev.command||'')} (${ev.repeat||'?'} runs)`, ts);
1753
+ } else if (ev.type === 'loop:complete') {
1754
+ el = mkSys(`🔁 Loop done: ${esc(ev.command||'')} — ${ev.ranReps||'?'} run(s)`, ts);
1755
+ } else if (ev.type === 'org:artifact' && ev.artifact) {
1756
+ el = mkArtifact(ev.artifact, ts);
1757
+ } else {
1758
+ el = mkSys(`<span style="opacity:0.4">${esc(ev.type)}${ev.domain?' ['+esc(ev.domain)+']':''}</span>`, ts);
1759
+ }
1760
+ if (!el) return;
1761
+ if (animate) el.classList.add('new');
1762
+ feed.appendChild(el);
1763
+ }
1764
+
1765
+ function mkSys(html, ts) {
1766
+ const el = document.createElement('div');
1767
+ el.className = 'cmsg csys';
1768
+ el.innerHTML = `<div class="cbubble">${html} <span style="color:var(--dim);margin-left:8px">${ts}</span></div>`;
1769
+ return el;
1770
+ }
1771
+ function mkAgent(name, text, ts, color, typeTag) {
1772
+ const el = document.createElement('div');
1773
+ el.className = 'cmsg cagent';
1774
+ const tagStyle = color ? `style="border-color:${color}44;color:${color}"` : '';
1775
+ const ttag = typeTag ? `<span class="cevtype">${esc(typeTag)}</span>` : '';
1776
+ el.innerHTML = `<div class="cmsg-meta"><span class="ctag" ${tagStyle}>${esc(name)}</span>${ttag}<span class="cts">${ts}</span></div><div class="cbubble">${text}</div>`;
1777
+ return el;
1778
+ }
1779
+ function mkIntercom(from, to, text, ts) {
1780
+ const el = document.createElement('div');
1781
+ el.className = 'cmsg cic';
1782
+ el.innerHTML = `<div class="cmsg-meta"><span class="ctag sender">${esc(from||'?')}</span><span class="cic-arrow">→</span><span class="ctag receiver">${esc(to||'?')}</span><span class="cevtype">intercom</span><span class="cts">${ts}</span></div><div class="cbubble">${text}</div>`;
1783
+ return el;
1784
+ }
1785
+ function mkArtifact(art, ts) {
1786
+ const el = document.createElement('div');
1787
+ el.className = 'cmsg csys';
1788
+ const isText = (art.mimeType || '').startsWith('text/') || (art.mimeType || '') === 'application/json';
1789
+ const labelRaw = art.label || (art.path || 'artifact').split('/').pop();
1790
+ const meta = [art.mimeType, art.size ? `${Math.round(art.size/1024*10)/10} KB` : null, art.path ? art.path.split('/').slice(-2).join('/') : null].filter(Boolean).join(' · ');
1791
+ const viewBtn = isText && art.path
1792
+ ? `<button onclick="viewArtifact(${JSON.stringify(art.path)},${JSON.stringify(labelRaw)})" style="background:#1a3a5a;color:#5d9fd9;border:1px solid #2980b944;border-radius:4px;padding:3px 9px;font-size:10px;font-weight:700;cursor:pointer;margin-left:auto">View</button>`
1793
+ : `<span style="font-size:10px;color:#555">Binary</span>`;
1794
+ el.innerHTML = `<div class="cbubble" style="padding:0"><div style="background:#1a1a2e;border:1px solid #3333aa44;border-radius:6px;padding:7px 10px;display:flex;align-items:center;gap:8px"><span style="font-size:16px">📄</span><div style="flex:1;min-width:0"><div style="font-size:12px;font-weight:700;color:#ccc;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${esc(labelRaw)}</div><div style="font-size:10px;color:#888">${esc(meta)}</div></div>${viewBtn}</div><span style="color:var(--dim);font-size:9px;padding:4px 6px 2px">${ts}</span></div>`;
1795
+ return el;
1796
+ }
1797
+
1798
+ async function viewArtifact(filePath, label) {
1799
+ const orgName = selectedOrg;
1800
+ if (!orgName || !filePath) return;
1801
+ const url = `/api/org/${encodeURIComponent(orgName)}/artifact?path=${encodeURIComponent(filePath)}`;
1802
+ try {
1803
+ const data = await fetch(url).then(r => r.json());
1804
+ if (data.binary) { console.warn('[orgs] Binary file — cannot display'); return; }
1805
+ if (data.error === 'file not found') { console.warn('[orgs] File no longer exists'); return; }
1806
+ if (data.error) { console.warn('[orgs] Artifact error:', data.error); return; }
1807
+ showArtifactPanel(label, data.content, data.mimeType);
1808
+ } catch(_) { console.warn('[orgs] Failed to load artifact', _); }
1809
+ }
1810
+
1811
+ function showArtifactPanel(label, content, mimeType) {
1812
+ let panel = document.getElementById('artifact-panel');
1813
+ if (!panel) {
1814
+ panel = document.createElement('div');
1815
+ panel.id = 'artifact-panel';
1816
+ panel.style.cssText = 'position:fixed;top:0;right:0;width:480px;height:100vh;background:#0a0a14;border-left:1px solid #333;z-index:1000;display:flex;flex-direction:column;overflow:hidden';
1817
+ document.body.appendChild(panel);
1818
+ }
1819
+ panel.innerHTML = `
1820
+ <div style="padding:12px 14px;border-bottom:1px solid #1a1a2a;display:flex;align-items:center;gap:10px">
1821
+ <span style="font-size:13px;font-weight:700;color:#ccc;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(label)}</span>
1822
+ <button onclick="document.getElementById('artifact-panel').remove()" style="background:none;border:none;color:#888;font-size:16px;cursor:pointer">✕</button>
1823
+ </div>
1824
+ <pre style="flex:1;overflow:auto;padding:14px;font-size:11px;line-height:1.6;color:#bbb;white-space:pre-wrap;word-break:break-word">${esc(content)}</pre>
1825
+ `;
1826
+ panel.style.display = 'flex';
1827
+ }
1828
+
1829
+ function chatScrollFeed() {
1830
+ const feed = document.getElementById('chat-feed-wrap');
1831
+ if (feed && !chatScrollLocked) feed.scrollTop = feed.scrollHeight;
1832
+ }
1833
+
1834
+ function renderChatTab() {
1835
+ if (chatSessions.length) {
1836
+ populateChatSelect(); // re-filter immediately with current selectedOrg
1837
+ }
1838
+ loadChatSessions(); // then refresh from server
1839
+ connectMmSSE();
1840
+ const feed = document.getElementById('chat-feed-wrap');
1841
+ if (feed) {
1842
+ feed.addEventListener('scroll', () => {
1843
+ chatScrollLocked = feed.scrollHeight - feed.scrollTop - feed.clientHeight > 80;
1844
+ }, { passive: true });
1845
+ }
1846
+ }
1847
+
1848
+ function connectMmSSE() {
1849
+ if (mmSse) return; // already connected
1850
+ mmSse = new EventSource('/api/mastermind-stream');
1851
+ mmSse.onopen = () => {
1852
+ const dot = document.getElementById('chat-live-dot');
1853
+ const lbl = document.getElementById('chat-live-label');
1854
+ if (dot) dot.classList.add('on');
1855
+ if (lbl) lbl.textContent = 'LIVE';
1856
+ };
1857
+ mmSse.onmessage = e => {
1858
+ try {
1859
+ const ev = JSON.parse(e.data);
1860
+ handleMmEvent(ev);
1861
+ } catch(_) {}
1862
+ };
1863
+ mmSse.onerror = () => {
1864
+ const dot = document.getElementById('chat-live-dot');
1865
+ const lbl = document.getElementById('chat-live-label');
1866
+ if (dot) dot.classList.remove('on');
1867
+ if (lbl) lbl.textContent = 'RECONNECTING';
1868
+ mmSse = null;
1869
+ setTimeout(connectMmSSE, 4000);
1870
+ };
1871
+ }
1872
+
1873
+ function handleMmEvent(ev) {
1874
+ // Update in-memory sessions list
1875
+ if (ev.type === 'session:start') {
1876
+ const existing = chatSessions.find(s => s.id === ev.session);
1877
+ if (!existing) {
1878
+ chatSessions.unshift({ id: ev.session, ts: ev.ts||Date.now(), prompt: ev.prompt||'', status:'running', domains:[], events:[ev] });
1879
+ }
1880
+ populateChatSelect();
1881
+ // Auto-select if chat tab is active, nothing selected, and session matches this org
1882
+ if (currentTab === 'chat' && !chatCurrentId) {
1883
+ const newSess = chatSessions.find(s => s.id === ev.session);
1884
+ if (newSess && orgSessionMatch(newSess)) {
1885
+ const sel = document.getElementById('chat-sess-select');
1886
+ if (sel) { sel.value = ev.session; selectChatSession(ev.session); }
1887
+ }
1888
+ }
1889
+ return;
1890
+ }
1891
+ const sess = chatSessions.find(s => s.id === ev.session);
1892
+ if (sess) {
1893
+ (sess.events = sess.events || []).push(ev);
1894
+ if (ev.type === 'domain:dispatch' && ev.domain && !sess.domains.includes(ev.domain)) sess.domains.push(ev.domain);
1895
+ if (ev.type === 'session:complete') { sess.status = ev.status || 'complete'; sess.endTs = ev.ts; populateChatSelect(); }
1896
+ // If this is the currently-viewed session and chat tab is visible, append bubble
1897
+ if (currentTab === 'chat' && chatCurrentId === ev.session) {
1898
+ const hasNewAgent = (ev.agent && !document.querySelector(`[data-agent="${CSS.escape(ev.agent)}"]`))
1899
+ || (ev.from && !document.querySelector(`[data-agent="${CSS.escape(ev.from)}"]`))
1900
+ || (ev.to && !document.querySelector(`[data-agent="${CSS.escape(ev.to)}"]`));
1901
+ if (hasNewAgent) populateChatAgentBar(sess.events);
1902
+ appendChatEvent(ev, true);
1903
+ chatScrollFeed();
1904
+ }
1905
+ }
1906
+ }
1907
+
1908
+ // ── Stop org action ────────────────────────────────────────────────
1909
+ window.stopCurrentOrg = async function() {
1910
+ if (!selectedOrg) return;
1911
+ try {
1912
+ await fetch(`/api/orgs/${encodeURIComponent(selectedOrg)}/stop`, { method: 'POST' });
1913
+ setTimeout(loadOrgs, 600);
1914
+ } catch (_) {}
1915
+ };
1916
+
1917
+ // ── SSE org events ────────────────────────────────────────────────
1918
+ const orgEventLog = {};
1919
+ let orgRunStartMs = null;
1920
+ let lsUptimeTimer = null;
1921
+
1922
+ let evtSource = null;
1923
+ function connectSSE() {
1924
+ if (evtSource) evtSource.close();
1925
+ evtSource = new EventSource('/api/mastermind-stream');
1926
+ evtSource.onmessage = e => {
1927
+ try {
1928
+ const ev = JSON.parse(e.data);
1929
+ if (ev && typeof ev.org === 'string' && ev.type && (ev.type.startsWith('org:') || ev.type.startsWith('agent:'))) {
1930
+ const name = ev.org;
1931
+ if (!orgEventLog[name]) orgEventLog[name] = [];
1932
+ orgEventLog[name].push({ ...ev, _ts: Date.now() });
1933
+ if (orgEventLog[name].length > 50) orgEventLog[name].shift();
1934
+
1935
+ // Refresh if this org is selected
1936
+ if (name === selectedOrg && currentTab === 'activity') {
1937
+ renderActivity();
1938
+ }
1939
+ // Reload org list if start/stop event
1940
+ if (ev.type === 'org:start' || ev.type === 'org:stop') {
1941
+ setTimeout(loadOrgs, 400);
1942
+ }
1943
+ // Update live strip for selected org on chart tab
1944
+ if (name === selectedOrg && currentTab === 'chart') {
1945
+ if (ev.type === 'org:start') orgRunStartMs = Date.now();
1946
+ if (ev.type === 'org:stop') orgRunStartMs = null;
1947
+ appendLiveFeedRow(ev, true);
1948
+ updateLiveStrip();
1949
+ }
1950
+ }
1951
+ } catch (_) {}
1952
+ };
1953
+ evtSource.onerror = () => {
1954
+ setTimeout(connectSSE, 5000);
1955
+ };
1956
+ }
1957
+
1958
+ function updateLiveStrip() {
1959
+ const events = orgEventLog[selectedOrg] || [];
1960
+ const running = orgRunStartMs !== null;
1961
+ const dot = document.getElementById('ls-dot');
1962
+ const label = document.getElementById('ls-label');
1963
+ if (!dot || !label) return;
1964
+ dot.className = 'ls-dot' + (running ? ' running' : '');
1965
+ label.textContent = running ? 'RUNNING' : 'IDLE';
1966
+
1967
+ // Uptime
1968
+ const uptimeEl = document.getElementById('ls-uptime');
1969
+ if (uptimeEl) {
1970
+ if (running && orgRunStartMs) {
1971
+ const s = Math.floor((Date.now() - orgRunStartMs) / 1000);
1972
+ const h = Math.floor(s / 3600), m = Math.floor((s % 3600) / 60), sec = s % 60;
1973
+ uptimeEl.textContent = h ? `up ${h}h ${m}m` : m ? `up ${m}m ${sec}s` : `up ${sec}s`;
1974
+ } else {
1975
+ uptimeEl.textContent = '—';
1976
+ }
1977
+ }
1978
+
1979
+ // Last event
1980
+ const lastEl = document.getElementById('ls-last');
1981
+ if (lastEl && events.length) {
1982
+ const last = events[events.length - 1];
1983
+ const ago = Math.round((Date.now() - (last._ts || Date.now())) / 1000);
1984
+ lastEl.textContent = ago < 5 ? 'just now' : ago < 60 ? ago + 's ago' : Math.floor(ago/60) + 'm ago';
1985
+ } else if (lastEl) {
1986
+ lastEl.textContent = '—';
1987
+ }
1988
+
1989
+ // Events per 5 min
1990
+ const now = Date.now();
1991
+ const epmEl = document.getElementById('ls-epm');
1992
+ if (epmEl) {
1993
+ const recent = events.filter(ev => ev._ts && now - ev._ts < 5 * 60 * 1000).length;
1994
+ epmEl.textContent = recent + ' / 5m';
1995
+ }
1996
+
1997
+ // 12-bar sparkline (25s buckets)
1998
+ renderLiveSparkline(events);
1999
+ }
2000
+
2001
+ function renderLiveSparkline(events) {
2002
+ const wrap = document.getElementById('ls-sparkline');
2003
+ if (!wrap) return;
2004
+ const now = Date.now();
2005
+ const buckets = Array.from({ length: 12 }, (_, i) => {
2006
+ const start = now - (12 - i) * 25000;
2007
+ return events.filter(ev => ev._ts && ev._ts >= start && ev._ts < start + 25000).length;
2008
+ });
2009
+ const max = Math.max(1, ...buckets);
2010
+ wrap.innerHTML = buckets.map((count, i) => {
2011
+ const h = Math.max(2, Math.round(count / max * 18));
2012
+ return `<div class="ls-bar${i === 11 ? ' active' : ''}" style="height:${h}px" title="${count} events"></div>`;
2013
+ }).join('');
2014
+ }
2015
+
2016
+ function appendLiveFeedRow(ev, isNew) {
2017
+ const rows = document.getElementById('clf-rows');
2018
+ if (!rows) return;
2019
+ const time = new Date().toLocaleTimeString('en', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });
2020
+ const type = (ev.type || '').replace('org:', '');
2021
+ const msg = ev.message || ev.msg || ev.task?.slice(0,60) || (ev.result||'').slice(0,60) || ev.agentType || ev.agent || ev.role || ev.status || '';
2022
+ const row = document.createElement('div');
2023
+ row.className = 'clf-row' + (isNew ? ' new' : '');
2024
+ row.title = msg;
2025
+ row.innerHTML = `<span class="clf-time">${time}</span><span class="clf-type">${type}</span><span class="clf-msg">${msg}</span>`;
2026
+ rows.appendChild(row);
2027
+ if (rows.children.length > 30) rows.removeChild(rows.firstChild);
2028
+ rows.scrollTop = rows.scrollHeight;
2029
+ }
2030
+
2031
+ function initLiveStrip() {
2032
+ orgRunStartMs = null;
2033
+ const rows = document.getElementById('clf-rows');
2034
+ if (rows) rows.innerHTML = '';
2035
+ const events = orgEventLog[selectedOrg] || [];
2036
+ // Seed from existing event history
2037
+ events.forEach(ev => appendLiveFeedRow(ev, false));
2038
+ // Detect running state from list data
2039
+ const listOrg = allOrgs.find(o => o.name === selectedOrg);
2040
+ if (listOrg && listOrg.running && !orgRunStartMs) {
2041
+ const acts = orgDetailData._activity || [];
2042
+ const startEv = acts.slice().reverse().find(e => e.type === 'org:start' || e.type === 'org:create');
2043
+ if (startEv && startEv.ts) orgRunStartMs = startEv.ts;
2044
+ else if (listOrg.running_since) orgRunStartMs = listOrg.running_since;
2045
+ else orgRunStartMs = Date.now(); // fallback
2046
+ } else if (listOrg && !listOrg.running) {
2047
+ orgRunStartMs = null;
2048
+ }
2049
+ updateLiveStrip();
2050
+ if (lsUptimeTimer) clearInterval(lsUptimeTimer);
2051
+ lsUptimeTimer = setInterval(updateLiveStrip, 1000);
2052
+ }
2053
+
2054
+ // ── Data loading ──────────────────────────────────────────────────
2055
+ async function loadOrgs() {
2056
+ try {
2057
+ const r = await fetch('/api/orgs');
2058
+ allOrgs = await r.json();
2059
+ if (!Array.isArray(allOrgs)) allOrgs = [];
2060
+ } catch (_) {
2061
+ allOrgs = [];
2062
+ }
2063
+ renderSidebar();
2064
+
2065
+ // Re-render detail if selected org changed running state
2066
+ if (selectedOrg && orgDetailData) {
2067
+ const listOrg = allOrgs.find(o => o.name === selectedOrg);
2068
+ if (listOrg) updateHeader(listOrg, orgDetailData);
2069
+ }
2070
+ }
2071
+
2072
+ // ── Copy org modal ────────────────────────────────────────────────
2073
+ let copyTargetOrg = null;
2074
+
2075
+ window.openCopyModal = function(name) {
2076
+ copyTargetOrg = name;
2077
+ document.getElementById('copy-modal-org').textContent = name.toUpperCase();
2078
+ document.getElementById('copy-dest-input').value = '';
2079
+ document.getElementById('copy-modal-status').textContent = '';
2080
+ document.getElementById('copy-modal-status').className = '';
2081
+ document.getElementById('copy-confirm-btn').disabled = false;
2082
+ document.getElementById('copy-modal-overlay').classList.add('open');
2083
+ // Load project list
2084
+ const listEl = document.getElementById('copy-project-list');
2085
+ listEl.innerHTML = '<div class="copy-proj-loading">Loading projects…</div>';
2086
+ fetch('/api/projects').then(r => r.json()).then(data => {
2087
+ const projects = data?.projects || [];
2088
+ if (!projects.length) { listEl.innerHTML = '<div class="copy-proj-loading">No other projects found</div>'; return; }
2089
+ listEl.innerHTML = projects.map(p => {
2090
+ const label = p.name || p.slug || p.path?.split('/').pop() || p.path;
2091
+ const isCurrentDir = p.path === (window._orgDir || '');
2092
+ return `<div class="copy-proj-row${isCurrentDir ? ' selected' : ''}" title="${p.path}" onclick="selectCopyProject('${p.path.replace(/'/g,"\\'")}', this)">
2093
+ <span style="color:var(--text-hi)">${label}</span>
2094
+ <span style="color:var(--dim);font-size:10px;margin-left:6px">${p.path.replace(/\/Users\/[^/]+\//, '~/')}</span>
2095
+ </div>`;
2096
+ }).join('');
2097
+ }).catch(() => { listEl.innerHTML = '<div class="copy-proj-loading">Could not load projects</div>'; });
2098
+ setTimeout(() => document.getElementById('copy-dest-input').focus(), 60);
2099
+ };
2100
+
2101
+ window.selectCopyProject = function(path, el) {
2102
+ document.querySelectorAll('.copy-proj-row').forEach(r => r.classList.remove('selected'));
2103
+ el.classList.add('selected');
2104
+ document.getElementById('copy-dest-input').value = path;
2105
+ };
2106
+
2107
+ window.closeCopyModal = function() {
2108
+ document.getElementById('copy-modal-overlay').classList.remove('open');
2109
+ copyTargetOrg = null;
2110
+ };
2111
+
2112
+ window.executeCopy = async function() {
2113
+ if (!copyTargetOrg) return;
2114
+ const destination = document.getElementById('copy-dest-input').value.trim();
2115
+ const status = document.getElementById('copy-modal-status');
2116
+ const btn = document.getElementById('copy-confirm-btn');
2117
+ if (!destination) {
2118
+ status.textContent = 'ENTER A DESTINATION PATH';
2119
+ status.className = 'err';
2120
+ return;
2121
+ }
2122
+ btn.disabled = true;
2123
+ status.textContent = 'COPYING…';
2124
+ status.className = '';
2125
+ try {
2126
+ const r = await fetch(`/api/orgs/${encodeURIComponent(copyTargetOrg)}/copy`, {
2127
+ method: 'POST',
2128
+ headers: { 'Content-Type': 'application/json' },
2129
+ body: JSON.stringify({ destination }),
2130
+ });
2131
+ const data = await r.json();
2132
+ if (r.ok && data.ok) {
2133
+ status.textContent = 'COPIED — ' + (data.destFile || destination);
2134
+ status.className = 'ok';
2135
+ setTimeout(closeCopyModal, 2000);
2136
+ } else {
2137
+ status.textContent = 'ERROR: ' + (data.error || r.statusText);
2138
+ status.className = 'err';
2139
+ btn.disabled = false;
2140
+ }
2141
+ } catch(e) {
2142
+ status.textContent = 'ERROR: ' + e.message;
2143
+ status.className = 'err';
2144
+ btn.disabled = false;
2145
+ }
2146
+ };
2147
+
2148
+ // ── Export org ────────────────────────────────────────────────────
2149
+ window.exportOrg = async function(name) {
2150
+ try {
2151
+ const r = await fetch(`/api/orgs/${encodeURIComponent(name)}`);
2152
+ if (!r.ok) throw new Error(r.statusText);
2153
+ const blob = await r.blob();
2154
+ const a = document.createElement('a');
2155
+ a.href = URL.createObjectURL(blob);
2156
+ a.download = `${name}.json`;
2157
+ a.click();
2158
+ setTimeout(() => URL.revokeObjectURL(a.href), 5000);
2159
+ } catch(e) {
2160
+ console.warn('[orgs] Export failed:', e.message);
2161
+ }
2162
+ };
2163
+
2164
+ // ── Import org ────────────────────────────────────────────────────
2165
+ let importTargetOrg = null;
2166
+
2167
+ window.triggerImportOrg = function(name) {
2168
+ importTargetOrg = name;
2169
+ let inp = document.getElementById('oi-import-file-input');
2170
+ if (!inp) {
2171
+ inp = document.createElement('input');
2172
+ inp.type = 'file';
2173
+ inp.accept = '.json,application/json';
2174
+ inp.id = 'oi-import-file-input';
2175
+ inp.style.display = 'none';
2176
+ document.body.appendChild(inp);
2177
+ inp.addEventListener('change', handleImportFile);
2178
+ }
2179
+ inp.value = '';
2180
+ inp.click();
2181
+ };
2182
+
2183
+ async function handleImportFile(e) {
2184
+ const file = e.target.files[0];
2185
+ if (!file || !importTargetOrg) return;
2186
+ try {
2187
+ const text = await file.text();
2188
+ let data;
2189
+ try { data = JSON.parse(text); } catch(_) { console.warn('[orgs] Import failed: invalid JSON'); return; }
2190
+ const r = await fetch(`/api/orgs/${encodeURIComponent(importTargetOrg)}/import`, {
2191
+ method: 'POST',
2192
+ headers: { 'Content-Type': 'application/json' },
2193
+ body: JSON.stringify(data),
2194
+ });
2195
+ const result = await r.json();
2196
+ if (r.ok && result.ok) {
2197
+ loadOrgs();
2198
+ } else {
2199
+ console.warn('[orgs] Import failed:', result.error || r.statusText);
2200
+ }
2201
+ } catch(e) {
2202
+ console.warn('[orgs] Import failed:', e.message);
2203
+ } finally {
2204
+ importTargetOrg = null;
2205
+ }
2206
+ }
2207
+
2208
+ // ── Bootstrap ─────────────────────────────────────────────────────
2209
+ fetch('/api/status').then(r => r.json()).then(d => { if (d.dir) window._orgDir = d.dir; }).catch(() => {});
2210
+ connectSSE();
2211
+ loadOrgs();
2212
+ setInterval(loadOrgs, 8000);
2213
+ </script>
2214
+ </body>
2215
+ </html>