@monoes/monomindcli 2.9.1 → 2.9.3

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 (150) hide show
  1. package/.claude/helpers/statusline.cjs +45 -0
  2. package/.claude/helpers/token-tracker.cjs +13 -1
  3. package/.claude/settings.json +0 -1
  4. package/.claude/skills/github-toolkit/SKILL.md +60 -0
  5. package/.claude/skills/mastermind-createorg/SKILL.md +8 -2
  6. package/.claude/skills/memory-toolkit/SKILL.md +75 -0
  7. package/.claude/skills/monodesign/scripts/detector/rules/checks.mjs +24 -1
  8. package/README.md +1 -1
  9. package/dist/src/agents/registry-builder.d.ts.map +1 -1
  10. package/dist/src/agents/registry-builder.js +94 -1
  11. package/dist/src/agents/registry-builder.js.map +1 -1
  12. package/dist/src/capabilities/cap-documents.js +7 -7
  13. package/dist/src/capabilities/cap-documents.js.map +1 -1
  14. package/dist/src/commands/completions.js +2 -2
  15. package/dist/src/commands/completions.js.map +1 -1
  16. package/dist/src/commands/doctor-env-checks.d.ts.map +1 -1
  17. package/dist/src/commands/doctor-env-checks.js +14 -2
  18. package/dist/src/commands/doctor-env-checks.js.map +1 -1
  19. package/dist/src/commands/index.d.ts +27 -41
  20. package/dist/src/commands/index.d.ts.map +1 -1
  21. package/dist/src/commands/index.js +113 -207
  22. package/dist/src/commands/index.js.map +1 -1
  23. package/dist/src/commands/init-wizard.d.ts.map +1 -1
  24. package/dist/src/commands/init-wizard.js +35 -0
  25. package/dist/src/commands/init-wizard.js.map +1 -1
  26. package/dist/src/commands/init.d.ts.map +1 -1
  27. package/dist/src/commands/init.js +43 -0
  28. package/dist/src/commands/init.js.map +1 -1
  29. package/dist/src/commands/neural-optimize.d.ts.map +1 -1
  30. package/dist/src/commands/neural-optimize.js +12 -8
  31. package/dist/src/commands/neural-optimize.js.map +1 -1
  32. package/dist/src/commands/org-observe.js +2 -2
  33. package/dist/src/commands/org-observe.js.map +1 -1
  34. package/dist/src/index.d.ts.map +1 -1
  35. package/dist/src/index.js +19 -9
  36. package/dist/src/index.js.map +1 -1
  37. package/dist/src/init/claudemd-generator.js +2 -2
  38. package/dist/src/init/claudemd-generator.js.map +1 -1
  39. package/dist/src/init/executor.d.ts.map +1 -1
  40. package/dist/src/init/executor.js +21 -4
  41. package/dist/src/init/executor.js.map +1 -1
  42. package/dist/src/init/settings-generator.d.ts.map +1 -1
  43. package/dist/src/init/settings-generator.js +16 -37
  44. package/dist/src/init/settings-generator.js.map +1 -1
  45. package/dist/src/init/statusline-generator.d.ts.map +1 -1
  46. package/dist/src/init/statusline-generator.js +45 -0
  47. package/dist/src/init/statusline-generator.js.map +1 -1
  48. package/dist/src/init/types.d.ts +8 -0
  49. package/dist/src/init/types.d.ts.map +1 -1
  50. package/dist/src/init/types.js.map +1 -1
  51. package/dist/src/init/upgrade.d.ts.map +1 -1
  52. package/dist/src/init/upgrade.js +3 -9
  53. package/dist/src/init/upgrade.js.map +1 -1
  54. package/dist/src/knowledge/document-pipeline.d.ts.map +1 -1
  55. package/dist/src/knowledge/document-pipeline.js +1 -0
  56. package/dist/src/knowledge/document-pipeline.js.map +1 -1
  57. package/dist/src/mcp-tools/memory-tools.d.ts.map +1 -1
  58. package/dist/src/mcp-tools/memory-tools.js +7 -15
  59. package/dist/src/mcp-tools/memory-tools.js.map +1 -1
  60. package/dist/src/memory/bm25-index.d.ts +17 -0
  61. package/dist/src/memory/bm25-index.d.ts.map +1 -1
  62. package/dist/src/memory/bm25-index.js +17 -0
  63. package/dist/src/memory/bm25-index.js.map +1 -1
  64. package/dist/src/memory/memory-bridge.d.ts +4 -0
  65. package/dist/src/memory/memory-bridge.d.ts.map +1 -1
  66. package/dist/src/memory/memory-bridge.js +128 -22
  67. package/dist/src/memory/memory-bridge.js.map +1 -1
  68. package/dist/src/memory/memory-initializer.d.ts.map +1 -1
  69. package/dist/src/memory/memory-initializer.js +0 -50
  70. package/dist/src/memory/memory-initializer.js.map +1 -1
  71. package/dist/src/memory/memory-kg.d.ts.map +1 -1
  72. package/dist/src/memory/memory-kg.js +21 -5
  73. package/dist/src/memory/memory-kg.js.map +1 -1
  74. package/dist/src/monovector/diff-classifier.d.ts.map +1 -1
  75. package/dist/src/monovector/diff-classifier.js +11 -20
  76. package/dist/src/monovector/diff-classifier.js.map +1 -1
  77. package/dist/src/orgrt/antigravity-runner.d.ts +9 -0
  78. package/dist/src/orgrt/antigravity-runner.d.ts.map +1 -0
  79. package/dist/src/orgrt/antigravity-runner.js +250 -0
  80. package/dist/src/orgrt/antigravity-runner.js.map +1 -0
  81. package/dist/src/orgrt/codex-runner.d.ts +9 -0
  82. package/dist/src/orgrt/codex-runner.d.ts.map +1 -0
  83. package/dist/src/orgrt/codex-runner.js +216 -0
  84. package/dist/src/orgrt/codex-runner.js.map +1 -0
  85. package/dist/src/orgrt/cross-org.d.ts.map +1 -1
  86. package/dist/src/orgrt/cross-org.js +8 -0
  87. package/dist/src/orgrt/cross-org.js.map +1 -1
  88. package/dist/src/orgrt/daemon.d.ts +14 -7
  89. package/dist/src/orgrt/daemon.d.ts.map +1 -1
  90. package/dist/src/orgrt/daemon.js +72 -12
  91. package/dist/src/orgrt/daemon.js.map +1 -1
  92. package/dist/src/orgrt/fence.d.ts +39 -0
  93. package/dist/src/orgrt/fence.d.ts.map +1 -0
  94. package/dist/src/orgrt/fence.js +75 -0
  95. package/dist/src/orgrt/fence.js.map +1 -0
  96. package/dist/src/orgrt/kimicode-runner.d.ts.map +1 -1
  97. package/dist/src/orgrt/kimicode-runner.js +27 -1
  98. package/dist/src/orgrt/kimicode-runner.js.map +1 -1
  99. package/dist/src/orgrt/provider.d.ts.map +1 -1
  100. package/dist/src/orgrt/provider.js +26 -0
  101. package/dist/src/orgrt/provider.js.map +1 -1
  102. package/dist/src/orgrt/session.d.ts +11 -1
  103. package/dist/src/orgrt/session.d.ts.map +1 -1
  104. package/dist/src/orgrt/session.js +60 -3
  105. package/dist/src/orgrt/session.js.map +1 -1
  106. package/dist/src/orgrt/tool-fence.d.ts +7 -2
  107. package/dist/src/orgrt/tool-fence.d.ts.map +1 -1
  108. package/dist/src/orgrt/tool-fence.js +18 -2
  109. package/dist/src/orgrt/tool-fence.js.map +1 -1
  110. package/dist/src/orgrt/types.d.ts +222 -0
  111. package/dist/src/orgrt/types.d.ts.map +1 -1
  112. package/dist/src/orgrt/types.js +34 -3
  113. package/dist/src/orgrt/types.js.map +1 -1
  114. package/dist/src/orgrt/vercel-providers.d.ts +29 -0
  115. package/dist/src/orgrt/vercel-providers.d.ts.map +1 -0
  116. package/dist/src/orgrt/vercel-providers.js +162 -0
  117. package/dist/src/orgrt/vercel-providers.js.map +1 -0
  118. package/dist/src/orgrt/vercel-runner.d.ts +39 -0
  119. package/dist/src/orgrt/vercel-runner.d.ts.map +1 -0
  120. package/dist/src/orgrt/vercel-runner.js +121 -0
  121. package/dist/src/orgrt/vercel-runner.js.map +1 -0
  122. package/dist/src/orgrt/vercel-session-store.d.ts +12 -0
  123. package/dist/src/orgrt/vercel-session-store.d.ts.map +1 -0
  124. package/dist/src/orgrt/vercel-session-store.js +49 -0
  125. package/dist/src/orgrt/vercel-session-store.js.map +1 -0
  126. package/dist/src/pricing/model-pricing.d.ts +10 -1
  127. package/dist/src/pricing/model-pricing.d.ts.map +1 -1
  128. package/dist/src/pricing/model-pricing.js +18 -1
  129. package/dist/src/pricing/model-pricing.js.map +1 -1
  130. package/dist/src/services/crash-reporter.d.ts +6 -5
  131. package/dist/src/services/crash-reporter.d.ts.map +1 -1
  132. package/dist/src/services/crash-reporter.js +6 -81
  133. package/dist/src/services/crash-reporter.js.map +1 -1
  134. package/dist/src/ui/collector.mjs +16 -82
  135. package/dist/src/ui/dashboard.html +176 -545
  136. package/dist/src/ui/data/mastermind-sessions.json +1 -0
  137. package/dist/src/ui/markdown.js +273 -0
  138. package/dist/src/ui/server.mjs +99 -189
  139. package/dist/src/utils/input-guards.d.ts +4 -1
  140. package/dist/src/utils/input-guards.d.ts.map +1 -1
  141. package/dist/src/utils/input-guards.js +6 -4
  142. package/dist/src/utils/input-guards.js.map +1 -1
  143. package/dist/src/utils/redaction.d.ts +24 -0
  144. package/dist/src/utils/redaction.d.ts.map +1 -0
  145. package/dist/src/utils/redaction.js +190 -0
  146. package/dist/src/utils/redaction.js.map +1 -0
  147. package/dist/tsconfig.tsbuildinfo +1 -1
  148. package/package.json +20 -5
  149. package/scripts/check-workspace-deps.mjs +45 -0
  150. package/scripts/publish.sh +24 -33
@@ -215,12 +215,6 @@ html, body { height: 100%; background: var(--bg); color: var(--text-hi); font-fa
215
215
  .sr-compact-badge { display: inline-block; font-size: 10px; padding: 1px 6px; background: oklch(72% 0.18 75 / 0.12); color: var(--accent); border-radius: 8px; margin-left: 6px; white-space: nowrap; }
216
216
  .sr-meta { font-size: 11px; color: var(--text-lo); margin-top: 3px; }
217
217
 
218
- /* swarm tab */
219
- .swarm-run-row { padding: 7px 8px; cursor: pointer; border-radius: 5px; margin-bottom: 3px; transition: background 0.1s; }
220
- .swarm-run-row:hover { background: var(--surface-hi); }
221
- .swarm-run-row.active { background: oklch(72% 0.18 75 / 0.1); }
222
- .swarm-topo-pill { font-size: 10px; padding: 1px 7px; border-radius: 8px; background: var(--surface-hi); color: var(--text-lo); font-family: var(--mono); }
223
- .swarm-live { font-size: 10px; color: var(--green); font-weight: 600; margin-left: 6px; }
224
218
  /* chunks tab */
225
219
  .chunk-stats-bar { display: flex; gap: 20px; margin-bottom: 12px; padding-bottom: 10px; border-bottom: 1px solid var(--border); align-items: center; }
226
220
  .chunk-stat { display: flex; flex-direction: column; gap: 2px; }
@@ -377,18 +371,15 @@ html, body { height: 100%; background: var(--bg); color: var(--text-hi); font-fa
377
371
  .cv-excerpt-item { font-size:10px; color:var(--text-lo); font-family:var(--mono); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:100%; }
378
372
  .cv-excerpt-item .cv-ex-tag { font-size:8px; color:var(--text-xs); margin-right:5px; display:inline-block; min-width:30px; }
379
373
 
380
- /* agent detail drawer (slides in from right within the org detail pane) */
381
- #org-agent-drawer {
382
- position: absolute; top: 0; right: 0; height: 100%;
383
- width: min(420px, 82%);
384
- background: var(--surface); border-left: 1px solid var(--border);
385
- box-shadow: -16px 0 44px -14px oklch(0% 0 0 / 0.5);
386
- transform: translateX(101%);
387
- transition: transform 0.32s cubic-bezier(0.16, 1, 0.3, 1);
388
- display: flex; flex-direction: column; z-index: 40; min-width: 0;
389
- }
390
- #org-agent-drawer.open { transform: translateX(0); }
391
- @media (prefers-reduced-motion: reduce) { #org-agent-drawer { transition: none; } }
374
+ /* agent detail modal */
375
+ #org-agent-drawer { display: none; position: fixed; inset: 0; z-index: 3000; background: oklch(0% 0 0 / 0.65); align-items: center; justify-content: center; }
376
+ #org-agent-drawer.open { display: flex; }
377
+ #oad-box {
378
+ background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
379
+ box-shadow: 0 20px 60px -10px oklch(0% 0 0 / 0.5);
380
+ width: min(560px, 92vw); max-height: 88vh;
381
+ display: flex; flex-direction: column; overflow: hidden; min-width: 0;
382
+ }
392
383
  #oad-head { display: flex; align-items: center; gap: 11px; padding: 16px 16px 14px; border-bottom: 1px solid var(--border); flex-shrink: 0; }
393
384
  .oad-avatar { width: 46px; height: 46px; border-radius: 50%; flex-shrink: 0; background: var(--surface-hi); object-fit: cover; }
394
385
  .oad-avatar-wrap { position: relative; width: 46px; height: 46px; flex-shrink: 0; cursor: pointer; }
@@ -436,12 +427,23 @@ html, body { height: 100%; background: var(--bg); color: var(--text-hi); font-fa
436
427
  .role-v2:hover { transform: translateY(-1px); }
437
428
  .rv2-more { margin-top: 9px; font-size: 10.5px; color: var(--accent); opacity: 0.85; }
438
429
 
439
- /* org meta strip */
440
- .org-meta-strip { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; margin-bottom: 16px; }
430
+ /* org meta strip (cells reused inside #org-meta-modal) */
441
431
  .oms-cell { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); padding: 10px 12px; }
442
432
  .oms-lbl { font-size: 10px; text-transform: uppercase; letter-spacing: 0.07em; color: var(--text-lo); margin-bottom: 4px; }
443
433
  .oms-val { font-size: 12px; color: var(--text-hi); word-break: break-word; line-height: 1.4; }
444
434
 
435
+ /* org meta — condensed one-line summary, click for full detail */
436
+ .org-meta-line { display: flex; align-items: center; gap: 10px; padding: 9px 12px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); margin-bottom: 16px; cursor: pointer; overflow: hidden; }
437
+ .org-meta-line:hover { background: var(--surface-hi); }
438
+ .oml-item { display: flex; align-items: baseline; gap: 5px; min-width: 0; flex-shrink: 0; }
439
+ .oml-item.goal { flex: 1 1 auto; min-width: 0; }
440
+ .oml-lbl { font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-lo); flex-shrink: 0; }
441
+ .oml-val { font-size: 12px; color: var(--text-hi); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
442
+ .oml-sep { color: var(--text-lo); opacity: 0.5; flex-shrink: 0; }
443
+ #org-meta-modal { display: none; position: fixed; inset: 0; z-index: 3000; background: oklch(0% 0 0 / 0.65); align-items: center; justify-content: center; }
444
+ #org-meta-modal.open { display: flex; }
445
+ #org-meta-modal-box { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; width: min(520px, 90vw); padding: 20px; display: flex; flex-direction: column; gap: 10px; max-height: 85vh; overflow-y: auto; }
446
+
445
447
  /* SVG org chart */
446
448
  .org-chart-wrap { position: relative; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); overflow: hidden; margin-bottom: 12px; }
447
449
  .org-chart-svg { width: 100%; display: block; }
@@ -1417,6 +1419,13 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
1417
1419
  #mem-modal-ta { width: 100%; height: 280px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 10px; font-family: var(--mono); font-size: 12px; color: var(--text-hi); resize: vertical; outline: none; overflow-y: auto; }
1418
1420
  #mem-modal-ta:focus { border-color: var(--accent); }
1419
1421
  .mem-modal-btns { display: flex; justify-content: flex-end; gap: 8px; }
1422
+ /* docs reader modal */
1423
+ #docs-modal { display: none; position: fixed; inset: 0; z-index: 3000; background: oklch(0% 0 0 / 0.65); align-items: center; justify-content: center; }
1424
+ #docs-modal.open { display: flex; }
1425
+ #docs-modal-box { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; width: min(820px, 94vw); padding: 20px; display: flex; flex-direction: column; gap: 12px; max-height: 88vh; }
1426
+ #docs-modal-title { font-size: 13px; font-weight: 600; color: var(--text-hi); font-family: var(--mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1427
+ #docs-modal-body { overflow-y: auto; }
1428
+ .docs-modal-btns { display: flex; justify-content: flex-end; gap: 8px; }
1420
1429
 
1421
1430
  /* ── playbook view tabs ────────────────────────────────────── */
1422
1431
  .wf-pane { display: none; }
@@ -1970,9 +1979,8 @@ select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
1970
1979
  <button class="odt-btn" data-memtab="routing" onclick="switchMemTab('routing')">Routing</button>
1971
1980
  <button class="odt-btn" data-memtab="usage" onclick="switchMemTab('usage')">Usage</button>
1972
1981
  <button class="odt-btn" data-memtab="adrs" onclick="switchMemTab('adrs')">ADRs</button>
1973
- <button class="odt-btn" data-memtab="swarm" onclick="switchMemTab('swarm')">Swarm</button>
1974
1982
  <button class="odt-btn" data-memtab="chunks" onclick="switchMemTab('chunks')">Chunks</button>
1975
- <button class="odt-btn" data-memtab="agent-graph" onclick="switchMemTab('agent-graph')">Agent Graph</button>
1983
+ <button class="odt-btn" data-memtab="docs" onclick="switchMemTab('docs')">Docs</button>
1976
1984
  <button class="odt-btn" data-memtab="store" onclick="switchMemTab('store')">Store</button>
1977
1985
  </div>
1978
1986
  <div id="mem-tab-memories">
@@ -1994,15 +2002,6 @@ select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
1994
2002
  </div>
1995
2003
  <div id="adr-content"><div class="loading-txt">Loading…</div></div>
1996
2004
  </div>
1997
- <div id="mem-tab-swarm" style="display:none">
1998
- <div style="display:flex;height:380px;gap:0">
1999
- <div id="swarm-run-list" style="width:200px;flex-shrink:0;border-right:1px solid var(--border);overflow-y:auto;padding:6px"></div>
2000
- <div id="swarm-run-detail" style="flex:1;padding:14px;overflow-y:auto">
2001
- <div style="color:var(--text-lo);font-size:13px">Select a swarm run</div>
2002
- </div>
2003
- </div>
2004
- <button class="btn" style="margin-top:10px;color:var(--red);border-color:var(--red)" onclick="cleanSwarmData()">&#x232B; Clean Data</button>
2005
- </div>
2006
2005
  <div id="mem-tab-chunks" style="display:none">
2007
2006
  <div class="chunk-stats-bar" id="chunk-stats-bar">
2008
2007
  <div class="chunk-stat"><div class="chunk-stat-val" id="chunk-count-val">—</div><div class="chunk-stat-lbl">Chunks</div></div>
@@ -2028,14 +2027,13 @@ select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
2028
2027
  </div>
2029
2028
  <div id="chunks-grid"></div>
2030
2029
  </div>
2031
- <div id="mem-tab-agent-graph" style="display:none">
2032
- <div id="ag-summary-bar" style="display:flex;gap:20px;flex-wrap:wrap;margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid var(--border)"></div>
2033
- <div style="display:flex;gap:0;min-height:280px">
2034
- <div id="ag-sess-list" style="width:190px;flex-shrink:0;overflow-y:auto;border-right:1px solid var(--border);padding-right:8px"></div>
2035
- <div id="ag-detail" style="flex:1;padding:0 14px;overflow-y:auto">
2036
- <div style="color:var(--text-lo);font-size:13px;padding:20px 0">Select a session</div>
2037
- </div>
2030
+ <div id="mem-tab-docs" style="display:none">
2031
+ <div id="docs-stats-bar" style="display:flex;gap:20px;flex-wrap:wrap;margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid var(--border)"></div>
2032
+ <div class="filter-bar">
2033
+ <input class="filter-input" type="text" id="docs-filter" placeholder="Filter by path…" oninput="filterDocs(this.value)" />
2034
+ <button class="btn" onclick="_docsLoaded=false;loadDocsTab()" title="Refresh">↺</button>
2038
2035
  </div>
2036
+ <div id="docs-grid"></div>
2039
2037
  </div>
2040
2038
  <div id="mem-tab-store" style="display:none">
2041
2039
  <div class="filter-bar" style="margin-bottom:6px;display:flex;gap:6px;align-items:center">
@@ -2129,9 +2127,11 @@ select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
2129
2127
  </div>
2130
2128
  </div>
2131
2129
  <div id="org-import-input-wrap" style="display:none"><input type="file" id="org-import-file" accept=".json" style="display:none"></div>
2132
- <div id="org-agent-drawer" aria-hidden="true">
2133
- <div id="oad-head"></div>
2134
- <div id="oad-body"></div>
2130
+ <div id="org-agent-drawer" aria-hidden="true" role="dialog" aria-modal="true" aria-label="Agent details" onclick="if(event.target===this)v2CloseAgent()">
2131
+ <div id="oad-box">
2132
+ <div id="oad-head"></div>
2133
+ <div id="oad-body"></div>
2134
+ </div>
2135
2135
  </div>
2136
2136
  </div>
2137
2137
  </div>
@@ -2359,6 +2359,27 @@ select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
2359
2359
  </div>
2360
2360
  </div>
2361
2361
  </div>
2362
+ <!-- docs reader modal -->
2363
+ <div id="docs-modal" onclick="if(event.target===this)closeDocModal()">
2364
+ <div id="docs-modal-box">
2365
+ <div id="docs-modal-title">Document</div>
2366
+ <div id="docs-modal-body" class="gd-md"></div>
2367
+ <div class="docs-modal-btns">
2368
+ <button class="btn" onclick="closeDocModal()">Close</button>
2369
+ </div>
2370
+ </div>
2371
+ </div>
2372
+ <!-- org meta detail modal -->
2373
+ <div id="org-meta-modal" onclick="if(event.target===this)v2CloseMetaModal()">
2374
+ <div id="org-meta-modal-box">
2375
+ <div class="oms-cell"><div class="oms-lbl">Goal</div><div class="oms-val" id="omm-goal" style="color:var(--text-mid)"></div></div>
2376
+ <div class="oms-cell"><div class="oms-lbl">Topology</div><div class="oms-val" id="omm-topo"></div></div>
2377
+ <div class="oms-cell"><div class="oms-lbl">Created</div><div class="oms-val" id="omm-created" style="color:var(--text-mid)"></div></div>
2378
+ <div class="docs-modal-btns">
2379
+ <button class="btn" onclick="v2CloseMetaModal()">Close</button>
2380
+ </div>
2381
+ </div>
2382
+ </div>
2362
2383
  <div id="toast-rack"></div>
2363
2384
 
2364
2385
  <!-- f56: report card modal -->
@@ -2413,6 +2434,7 @@ select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
2413
2434
  </div>
2414
2435
  </div>
2415
2436
 
2437
+ <script src="markdown.js"></script>
2416
2438
  <script>
2417
2439
  // ── auth: attach this session's dashboard credential to every fetch() and
2418
2440
  // EventSource connection ──────────────────────────────────────────────────
@@ -2698,8 +2720,7 @@ function switchProject(path) {
2698
2720
  DIR = path;
2699
2721
  // Reset cached tab data on project change
2700
2722
  _chunksLoaded = false;
2701
- _agData = null;
2702
- _swarmRuns = [];
2723
+ _docsLoaded = false;
2703
2724
  _memFiles = [];
2704
2725
  _selMemFilename = null;
2705
2726
  _editMemFilename = null;
@@ -3469,7 +3490,7 @@ function switchMemTab(tab) {
3469
3490
  _currentMemTab = tab;
3470
3491
  document.querySelectorAll('.mem-tab-bar .odt-btn').forEach(b =>
3471
3492
  b.classList.toggle('active', b.dataset.memtab === tab));
3472
- ['memories','routing','usage','adrs','swarm','chunks','agent-graph','store'].forEach(t => {
3493
+ ['memories','routing','usage','adrs','chunks','docs','store'].forEach(t => {
3473
3494
  const el = document.getElementById('mem-tab-' + t);
3474
3495
  if (el) el.style.display = t === tab ? '' : 'none';
3475
3496
  });
@@ -3477,9 +3498,8 @@ function switchMemTab(tab) {
3477
3498
  else if (tab === 'routing') loadMemRouting();
3478
3499
  else if (tab === 'usage') loadMemUsage();
3479
3500
  else if (tab === 'adrs') loadMemADRs();
3480
- else if (tab === 'swarm') loadSwarmTab();
3481
3501
  else if (tab === 'chunks') loadChunksTab();
3482
- else if (tab === 'agent-graph') loadAgentGraphTab();
3502
+ else if (tab === 'docs') loadDocsTab();
3483
3503
  else if (tab === 'store') loadStoreEntries();
3484
3504
  }
3485
3505
 
@@ -4359,227 +4379,8 @@ async function gdCopyPath(p) {
4359
4379
  catch { showToast('Copy failed', 'Clipboard unavailable', 'warn'); }
4360
4380
  }
4361
4381
 
4362
- // ── High-fidelity markdown renderer for the document viewer ──────────────────
4363
- // Handles: headings (h1-h6) with anchors, bold/italic/strikethrough, inline
4364
- // code and fenced code blocks (with language label + copy button), unordered/
4365
- // ordered/nested/task lists, GFM tables, nested blockquotes, horizontal rules,
4366
- // links (external get rel=noopener + target=_blank), images, line breaks.
4367
- // HTML-escaped at the boundary; nothing the source contains can inject markup.
4368
- function renderDocMarkdown(md) {
4369
- if (!md) return '';
4370
- const esc = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
4371
-
4372
- // 1. Strip a leading YAML frontmatter block (mastermind plan/spec files have one).
4373
- let src = String(md).replace(/^\uFEFF?---[\s\S]*?\n---\s*\n/, '');
4374
-
4375
- // 2. Extract fenced code blocks first so their contents are never reinterpreted.
4376
- const codeBlocks = [];
4377
- src = src.replace(/```([\w+-]*)\n?([\s\S]*?)```/g, (_m, lang, code) => {
4378
- const clean = code.replace(/\n$/, '');
4379
- codeBlocks.push({ lang: (lang || '').toLowerCase(), code: clean });
4380
- return '\u0000CB' + (codeBlocks.length - 1) + '\u0000';
4381
- });
4382
-
4383
- // 3. Extract inline HTML blocks (multiline <div>…</div> etc.) — pass through
4384
- // verbatim after escaping, so user-authored HTML in a plan still renders
4385
- // structurally without enabling script injection (we'll re-strip <script>
4386
- // and on* handlers at the end).
4387
- const htmlBlocks = [];
4388
- src = src.replace(/(^|\n)(<(\w+)[^>\n]*>[\s\S]*?<\/\3>)(?=\n|$)/g, (_m, lead, html) => {
4389
- htmlBlocks.push(html);
4390
- return (lead || '') + '\u0000HB' + (htmlBlocks.length - 1) + '\u0000';
4391
- });
4392
-
4393
- // 4. Escape everything that remains.
4394
- src = esc(src).replace(/\u0000CB(\d+)\u0000/g, 'CB$1').replace(/\u0000HB(\d+)\u0000/g, 'HB$1');
4395
-
4396
- // 5. Block-level transforms: tables, headings, hr, blockquotes, lists.
4397
- // Operate line-by-line so nesting and paragraph grouping stay coherent.
4398
- const lines = src.split('\n');
4399
- const out = [];
4400
- let i = 0;
4401
- let inUl = false, inOl = false, inTask = false, bqBuf = [], paraBuf = [];
4402
-
4403
- const closeLists = () => {
4404
- if (inUl) { out.push('</ul>'); inUl = false; }
4405
- if (inOl) { out.push('</ol>'); inOl = false; }
4406
- if (inTask) { out.push('</ul>'); inTask = false; }
4407
- };
4408
- const flushPara = () => { if (paraBuf.length) { out.push('<p>' + paraBuf.join(' ') + '</p>'); paraBuf = []; } };
4409
- const flushBQ = () => {
4410
- if (!bqBuf.length) return;
4411
- // Recursively render the inner block so nested formatting works.
4412
- out.push('<blockquote>' + renderDocMarkdown(bqBuf.join('\n')) + '</blockquote>');
4413
- bqBuf = [];
4414
- };
4415
- const slugify = (s) => String(s).toLowerCase().replace(/<[^>]+>/g, '').replace(/[^\w\s-]/g, '').trim().replace(/\s+/g, '-');
4416
-
4417
- while (i < lines.length) {
4418
- let line = lines[i];
4419
-
4420
- // Block placeholders for code/html blocks (they sit on their own line).
4421
- const cbMatch = line.match(/^CB(\d+)$/);
4422
- if (cbMatch) { closeLists(); flushPara(); flushBQ(); out.push(renderCodeBlock(codeBlocks[+cbMatch[1]])); i++; continue; }
4423
- const hbMatch = line.match(/^HB(\d+)$/);
4424
- if (hbMatch) { closeLists(); flushPara(); flushBQ(); out.push(sanitizeHtmlBlock(htmlBlocks[+hbMatch[1]])); i++; continue; }
4425
-
4426
- // Blank line: paragraph + list breaks.
4427
- if (line.trim() === '') { closeLists(); flushPara(); flushBQ(); i++; continue; }
4428
-
4429
- // Headings (with anchor).
4430
- const h = line.match(/^(#{1,6})\s+(.+?)(?:\s+#+\s*)?$/);
4431
- if (h) { closeLists(); flushPara(); flushBQ(); const lvl = h[1].length; const txt = inlineMd(h[2]); const id = slugify(h[2]); out.push('<h' + lvl + ' id="' + id + '">' + txt + '</h' + lvl + '>'); i++; continue; }
4432
-
4433
- // Horizontal rule.
4434
- if (/^(\*\s*){3,}$/.test(line) || /^(-\s*){3,}$/.test(line) || /^(_\s*){3,}$/.test(line)) { closeLists(); flushPara(); flushBQ(); out.push('<hr>'); i++; continue; }
4435
-
4436
- // GFM table — a block of consecutive lines where line 0 and line 1 look
4437
- // like | a | b | / |---|---|. Scan forward to collect the full table.
4438
- if (/^\s*\|.+\|\s*$/.test(line) && i + 1 < lines.length && /^\s*\|?[\s:-]+\|[\s:|-]+\|?\s*$/.test(lines[i + 1])) {
4439
- closeLists(); flushPara(); flushBQ();
4440
- const headerCells = splitTableRow(line);
4441
- const aligns = parseTableAligns(lines[i + 1]);
4442
- i += 2;
4443
- const rows = [];
4444
- while (i < lines.length && /^\s*\|.+\|\s*$/.test(lines[i])) { rows.push(splitTableRow(lines[i])); i++; }
4445
- out.push(renderTable(headerCells, aligns, rows));
4446
- continue;
4447
- }
4448
-
4449
- // Blockquote.
4450
- const bq = line.match(/^>\s?(.*)$/);
4451
- if (bq) { closeLists(); flushPara(); bqBuf.push(bq[1]); i++; continue; }
4452
- if (bqBuf.length) { flushBQ(); }
4453
-
4454
- // Task list item.
4455
- const task = line.match(/^(\s*)([-*])\s+\[( |x|X)\]\s+(.+)$/);
4456
- if (task) { flushPara(); if (inUl) { out.push('</ul>'); inUl = false; } if (inOl) { out.push('</ol>'); inOl = false; } if (!inTask) { out.push('<ul class="md-tasklist">'); inTask = true; } const checked = task[3].toLowerCase() === 'x'; out.push('<li><input type="checkbox" disabled' + (checked ? ' checked' : '') + '> ' + inlineMd(task[4]) + '</li>'); i++; continue; }
4457
-
4458
- // Unordered list item.
4459
- const ul = line.match(/^(\s*)([-*+])\s+(.+)$/);
4460
- if (ul && !task) {
4461
- flushPara();
4462
- if (inOl) { out.push('</ol>'); inOl = false; }
4463
- if (inTask) { out.push('</ul>'); inTask = false; }
4464
- if (!inUl) { out.push('<ul>'); inUl = true; }
4465
- const indent = ul[1].length;
4466
- if (indent === 0) out.push('<li>' + inlineMd(ul[3]) + '</li>');
4467
- else {
4468
- // Nested list — close current top item and open a nested <ul>.
4469
- out.push('<li>' + inlineMd(ul[3]) + '</li>');
4470
- }
4471
- i++; continue;
4472
- }
4473
- // Ordered list item.
4474
- const ol = line.match(/^(\s*)(\d+)\.\s+(.+)$/);
4475
- if (ol) {
4476
- flushPara();
4477
- if (inUl) { out.push('</ul>'); inUl = false; }
4478
- if (inTask) { out.push('</ul>'); inTask = false; }
4479
- if (!inOl) { out.push('<ol>'); inOl = true; }
4480
- out.push('<li>' + inlineMd(ol[3]) + '</li>');
4481
- i++; continue;
4482
- }
4483
-
4484
- // Otherwise: paragraph text. Buffer until a blank line / structural break.
4485
- closeLists();
4486
- flushBQ();
4487
- paraBuf.push(inlineMd(line));
4488
- i++;
4489
- }
4490
- closeLists(); flushPara(); flushBQ();
4491
-
4492
- let html = out.join('\n');
4493
- // 6. Restore code/html block placeholders INSIDE paragraphs (when a CB/HB
4494
- // token got buffered as paragraph text instead of sitting alone).
4495
- html = html.replace(/CB(\d+)/g, (_m, idx) => renderCodeBlock(codeBlocks[+idx]))
4496
- .replace(/HB(\d+)/g, (_m, idx) => sanitizeHtmlBlock(htmlBlocks[+idx]));
4497
- return html;
4498
-
4499
- // ── helpers ──
4500
- function inlineMd(s) {
4501
- // Inline code first (so its contents aren't reformatted).
4502
- const codes = [];
4503
- s = s.replace(/`([^`\n]+)`/g, (_m, c) => { codes.push(c); return '\u0001C' + (codes.length - 1) + '\u0001'; });
4504
- // Images: ![alt](src "title")
4505
- s = s.replace(/!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g,
4506
- (_m, alt, src, title) => '<img src="' + src + '" alt="' + esc(alt) + '"' + (title ? ' title="' + esc(title) + '"' : '') + ' loading="lazy" style="max-width:100%;height:auto;border-radius:6px">');
4507
- // Links: [text](href "title")
4508
- s = s.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g,
4509
- (_m, txt, href, title) => {
4510
- const external = /^(https?:|mailto:|tel:)/i.test(href);
4511
- const safe = external ? ' target="_blank" rel="noopener noreferrer"' : '';
4512
- return '<a href="' + href + '"' + (title ? ' title="' + esc(title) + '"' : '') + safe + '>' + txt + '</a>';
4513
- });
4514
- // Bold.
4515
- s = s.replace(/\*\*([^*\n]+)\*\*/g, '<strong>$1</strong>');
4516
- s = s.replace(/__([^_\n]+)__/g, '<strong>$1</strong>');
4517
- // Italic.
4518
- s = s.replace(/\*([^*\n]+)\*/g, '<em>$1</em>');
4519
- s = s.replace(/(^|[^_])_([^_\n]+)_/g, '$1<em>$2</em>');
4520
- // Strikethrough.
4521
- s = s.replace(/~~([^~\n]+)~~/g, '<del>$1</del>');
4522
- // Restore inline code.
4523
- s = s.replace(/\u0001C(\d+)\u0001/g, (_m, idx) => '<code>' + esc(codes[+idx]) + '</code>');
4524
- return s;
4525
- }
4526
-
4527
- function renderCodeBlock(block) {
4528
- const lang = block?.lang || '';
4529
- const code = block?.code ?? '';
4530
- const labelHtml = lang ? '<span class="gd-code-lang">' + esc(lang) + '</span>' : '';
4531
- return '<div class="gd-code-block">' +
4532
- '<div class="gd-code-bar"><button class="gd-code-copy" onclick="gdCopyCode(this)">copy</button>' + labelHtml + '</div>' +
4533
- '<pre><code' + (lang ? ' class="language-' + esc(lang) + '"' : '') + '>' + esc(code) + '</code></pre>' +
4534
- '</div>';
4535
- }
4536
-
4537
- function sanitizeHtmlBlock(html) {
4538
- // Strip <script> entirely, drop event-handler attributes (onclick=, onload=, on*=),
4539
- // and javascript: URLs. Keep everything else as-is so user HTML renders.
4540
- return String(html)
4541
- .replace(/<script[\s\S]*?<\/script>/gi, '')
4542
- .replace(/\son\w+\s*=\s*"[^"]*"/gi, '')
4543
- .replace(/\son\w+\s*=\s*'[^']*'/gi, '')
4544
- .replace(/\son\w+\s*=\s*[^\s>]+/gi, '')
4545
- .replace(/(href|src)\s*=\s*"javascript:[^"]*"/gi, '$1="#"');
4546
- }
4547
-
4548
- function splitTableRow(line) {
4549
- return line.trim().replace(/^\||\|$/g, '').split('|').map(c => c.trim());
4550
- }
4551
- function parseTableAligns(line) {
4552
- return splitTableRow(line).map(cell => {
4553
- if (/^:\s*-+:$/.test(cell)) return 'center';
4554
- if (/^-*:$/.test(cell)) return 'left';
4555
- if (/^-+:$/.test(cell)) return 'right';
4556
- return null;
4557
- });
4558
- }
4559
- function renderTable(headerCells, aligns, rows) {
4560
- const alignAttr = (i) => aligns[i] ? ' style="text-align:' + aligns[i] + '"' : '';
4561
- let html = '<div class="gd-table-wrap"><table><thead><tr>';
4562
- headerCells.forEach((c, i) => { html += '<th' + alignAttr(i) + '>' + inlineMd(c) + '</th>'; });
4563
- html += '</tr></thead><tbody>';
4564
- for (const r of rows) {
4565
- html += '<tr>';
4566
- r.forEach((c, i) => { html += '<td' + alignAttr(i) + '>' + inlineMd(c) + '</td>'; });
4567
- html += '</tr>';
4568
- }
4569
- html += '</tbody></table></div>';
4570
- return html;
4571
- }
4572
- }
4573
-
4574
- function gdCopyCode(btn) {
4575
- const codeEl = btn.closest('.gd-code-block')?.querySelector('code');
4576
- if (!codeEl) return;
4577
- const text = codeEl.textContent;
4578
- navigator.clipboard.writeText(text).then(
4579
- () => { const old = btn.textContent; btn.textContent = 'copied'; setTimeout(() => { btn.textContent = old; }, 1200); },
4580
- () => { btn.textContent = 'failed'; },
4581
- );
4582
- }
4382
+ // renderDocMarkdown() and gdCopyCode() moved to markdown.js (#124) — loaded via
4383
+ // <script src="markdown.js"> above; both remain plain globals.
4583
4384
 
4584
4385
  let _hiEntries = [];
4585
4386
  let _hiTab = 'pending';
@@ -6122,6 +5923,23 @@ function v2ToggleChartExpand(open) {
6122
5923
  }
6123
5924
  }
6124
5925
 
5926
+ // Recomputed from _v2OrgData at click time (not cached) so the modal never
5927
+ // shows a stale goal/topology/created after switching orgs.
5928
+ function v2OpenMetaModal() {
5929
+ const d = _v2OrgData;
5930
+ if (!d) return;
5931
+ const goalText = d.goal || '—';
5932
+ const topo = (d.topology || 'hierarchical').toLowerCase();
5933
+ const created = d.created_at ? new Date(d.created_at).toLocaleDateString('en', {month:'short',day:'numeric',year:'numeric'}) : '—';
5934
+ document.getElementById('omm-goal').textContent = goalText;
5935
+ document.getElementById('omm-topo').textContent = topo;
5936
+ document.getElementById('omm-created').textContent = created;
5937
+ document.getElementById('org-meta-modal').classList.add('open');
5938
+ }
5939
+ function v2CloseMetaModal() {
5940
+ document.getElementById('org-meta-modal').classList.remove('open');
5941
+ }
5942
+
6125
5943
  function v2RenderOrgChart() {
6126
5944
  if (!_v2SelOrg) return;
6127
5945
  const d = _v2OrgData;
@@ -6180,6 +5998,11 @@ function v2RenderOrgChart() {
6180
5998
  // a node center) or edge-column bubbles clip against the chart border.
6181
5999
  const PAD_X = (_v2ChartExpanded || _crowded) ? Math.max(R + 20, 78) : R + 20;
6182
6000
  const PAD_Y = (_v2ChartExpanded || _crowded) ? R + 96 : R + 24;
6001
+ // Space a node's label needs below its center before the viewBox clips it:
6002
+ // the label rect is y=(R+14-11), height=26 when a subtype line is present,
6003
+ // so its bottom edge sits at R+29 — plus a small cushion so labels never
6004
+ // sit flush against the chart's bottom edge.
6005
+ const LBL_BELOW = R + 29 + 8;
6183
6006
  const roleIds = new Set(roles.map(r => r.id));
6184
6007
  // all leaders at depth 0 (supports co-lead orgs)
6185
6008
  const leaders = roles.filter(_v2OrgIsLeader);
@@ -6205,7 +6028,7 @@ function v2RenderOrgChart() {
6205
6028
  pos[r.id] = { x, y: subY };
6206
6029
  });
6207
6030
  const _hasBothRows = leaders.length > 0 && subs.length > 0;
6208
- _layoutH = _hasBothRows ? PAD_Y + 220 + R + 8 : PAD_Y + R + 8;
6031
+ _layoutH = _hasBothRows ? PAD_Y + 220 + LBL_BELOW : PAD_Y + LBL_BELOW;
6209
6032
  };
6210
6033
 
6211
6034
  if (!roles.length) {
@@ -6222,7 +6045,7 @@ function v2RenderOrgChart() {
6222
6045
  const a = (2 * Math.PI * i / n) - Math.PI / 2;
6223
6046
  pos[r.id] = { x: cx + rad * Math.cos(a), y: cy + rad * Math.sin(a) };
6224
6047
  });
6225
- _layoutH = Math.round(cy + rad + PAD_Y);
6048
+ _layoutH = Math.round(cy + rad + LBL_BELOW);
6226
6049
  } else if (topo === 'ring' || topo === 'pipeline') {
6227
6050
  // linear sequence
6228
6051
  const usableW = W - PAD_X * 2;
@@ -6261,7 +6084,7 @@ function v2RenderOrgChart() {
6261
6084
  roles.forEach(r => layerOf[depth[r.id]].push(r));
6262
6085
 
6263
6086
  const LAYER_H = 110;
6264
- _layoutH = PAD_Y + maxDepth * LAYER_H + R + 24; // bottom row center + radius + margin
6087
+ _layoutH = PAD_Y + maxDepth * LAYER_H + LBL_BELOW; // bottom row center + label space
6265
6088
 
6266
6089
  for (let d2 = 0; d2 <= maxDepth; d2++) {
6267
6090
  const layer = layerOf[d2];
@@ -6297,7 +6120,7 @@ function v2RenderOrgChart() {
6297
6120
  });
6298
6121
  const rows = Math.ceil(seq.length / perRow);
6299
6122
  W = PAD_X * 2 + (perRow - 1) * COL_W;
6300
- _layoutH = PAD_Y + (rows - 1) * ROW_H + R + 28;
6123
+ _layoutH = PAD_Y + (rows - 1) * ROW_H + LBL_BELOW;
6301
6124
  }
6302
6125
  }
6303
6126
 
@@ -6406,10 +6229,12 @@ function v2RenderOrgChart() {
6406
6229
  // ── assemble HTML ─────────────────────────────────────
6407
6230
  // Expanded view: the chart owns the space — meta cards stay in the tab.
6408
6231
  pane.innerHTML = `
6409
- ${_v2ChartExpanded ? '' : `<div class="org-meta-strip">
6410
- <div class="oms-cell"><div class="oms-lbl">Goal</div><div class="oms-val" style="color:var(--text-mid);font-size:11px;line-height:1.4">${esc(goalText)}</div></div>
6411
- <div class="oms-cell"><div class="oms-lbl">Topology</div><div class="oms-val">${esc(topo)}</div></div>
6412
- <div class="oms-cell"><div class="oms-lbl">Created</div><div class="oms-val" style="color:var(--text-mid)">${esc(created)}</div></div>
6232
+ ${_v2ChartExpanded ? '' : `<div class="org-meta-line" onclick="v2OpenMetaModal()" title="Click for full details">
6233
+ <span class="oml-item goal"><span class="oml-lbl">Goal</span><span class="oml-val">${esc(goalText)}</span></span>
6234
+ <span class="oml-sep">·</span>
6235
+ <span class="oml-item"><span class="oml-lbl">Topology</span><span class="oml-val">${esc(topo)}</span></span>
6236
+ <span class="oml-sep">·</span>
6237
+ <span class="oml-item"><span class="oml-lbl">Created</span><span class="oml-val">${esc(created)}</span></span>
6413
6238
  </div>`}
6414
6239
  <div class="org-chart-wrap">
6415
6240
  ${_v2ChartExpanded ? '' : `<button class="v2-chart-expand" onclick="v2ToggleChartExpand(true)" title="Expand chart to full page" aria-label="Expand chart to full page">⛶</button>`}
@@ -8998,7 +8823,12 @@ function _ofMdToHtml(md) {
8998
8823
  .replace(/!\[([^\]]*)\]\(([^)]+)\)/g, function(_m, alt, url) {
8999
8824
  return '<img src="'+String(url).replace(/"/g,'&quot;')+'" alt="'+String(alt).replace(/"/g,'&quot;')+'" loading="lazy" style="max-width:100%;border-radius:4px;margin:8px 0;display:block">';
9000
8825
  })
9001
- .replace(/\[([^\]]+)\]\(([^)]+)\)/g,'<a href="$2" style="color:var(--accent);text-decoration:underline" target="_blank" rel="noopener">$1</a>')
8826
+ .replace(/\[([^\]]+)\]\(([^)]+)\)/g, function(_m, txt, url) {
8827
+ // #124: previously interpolated the href raw ('<a href="$2" ...>') — a
8828
+ // URL containing a `"` could break out of the attribute. Matches the
8829
+ // quote-escaping the image handler right above already does.
8830
+ return '<a href="'+String(url).replace(/"/g,'&quot;')+'" style="color:var(--accent);text-decoration:underline" target="_blank" rel="noopener">'+txt+'</a>';
8831
+ })
9002
8832
  .replace(/\n\n+/g,'</p><p style="margin:0 0 8px">')
9003
8833
  .replace(/^(?!<[hupoli]|<pre|<hr)(.+)$/gm, '$1')
9004
8834
  .replace(/\n/g,'<br>');
@@ -10068,7 +9898,7 @@ document.addEventListener('keydown', e => {
10068
9898
  if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
10069
9899
  if (document.getElementById('cmd-palette').classList.contains('open')) return;
10070
9900
 
10071
- if (e.key === 'Escape') { closeDetail(); closeCmdPalette(); closeShortcutHelp(); closeMastermind(); closeBudgetModal(); if (document.getElementById('app').classList.contains('ambient')) toggleAmbient(); }
9901
+ if (e.key === 'Escape') { closeDetail(); closeCmdPalette(); closeShortcutHelp(); closeMastermind(); closeBudgetModal(); closeDocModal(); v2CloseAgent(); v2CloseMetaModal(); if (document.getElementById('app').classList.contains('ambient')) toggleAmbient(); }
10072
9902
  if (e.key === '?') { e.preventDefault(); openShortcutHelp(); return; }
10073
9903
  if (e.key === 'm' || e.key === 'M') { e.preventDefault(); openMastermind(); return; }
10074
9904
  const _vkeys = { '1':'now','2':'sessions','3':'projects','4':'loops','5':'tokens','6':'memory','7':'orgs','8':'monograph','9':'global' };
@@ -13763,28 +13593,28 @@ async function loadStoreEntries() {
13763
13593
  if (ns) url += '&namespace=' + encodeURIComponent(ns);
13764
13594
  const data = await apiFetch(url);
13765
13595
  const entries = data.entries || [];
13766
- if (!entries.length) { grid.innerHTML = '<div class="empty">No entries' + (ns ? ' in namespace "' + escHtml(ns) + '"' : '') + '</div>'; return; }
13596
+ if (!entries.length) { grid.innerHTML = '<div class="empty">No entries' + (ns ? ' in namespace "' + esc(ns) + '"' : '') + '</div>'; return; }
13767
13597
  let html = '<table class="tbl" style="width:100%;font-size:12px"><thead><tr><th>Key</th><th>Namespace</th><th>Content</th><th>Tags</th><th>Updated</th><th></th></tr></thead><tbody>';
13768
13598
  for (const e of entries) {
13769
- const preview = escHtml((e.content || '').slice(0, 80)) + (e.content?.length > 80 ? '…' : '');
13770
- const tags = (e.tags || []).map(t => '<span style="background:var(--bg-hi);padding:1px 4px;border-radius:3px;font-size:10px">' + escHtml(t) + '</span>').join(' ');
13599
+ const preview = esc((e.content || '').slice(0, 80)) + (e.content?.length > 80 ? '…' : '');
13600
+ const tags = (e.tags || []).map(t => '<span style="background:var(--bg-hi);padding:1px 4px;border-radius:3px;font-size:10px">' + esc(t) + '</span>').join(' ');
13771
13601
  const updated = e.updatedAt ? new Date(e.updatedAt).toLocaleDateString() : '';
13772
13602
  const emb = e.hasEmbedding ? ' title="Has embedding"' : '';
13773
13603
  html += '<tr>';
13774
- html += '<td style="font-weight:500;white-space:nowrap"' + emb + '>' + (e.hasEmbedding ? '&#x25C9; ' : '') + escHtml(e.key) + '</td>';
13775
- html += '<td style="color:var(--text-lo)">' + escHtml(e.namespace) + '</td>';
13604
+ html += '<td style="font-weight:500;white-space:nowrap"' + emb + '>' + (e.hasEmbedding ? '&#x25C9; ' : '') + esc(e.key) + '</td>';
13605
+ html += '<td style="color:var(--text-lo)">' + esc(e.namespace) + '</td>';
13776
13606
  html += '<td style="max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + preview + '</td>';
13777
13607
  html += '<td>' + tags + '</td>';
13778
13608
  html += '<td style="color:var(--text-lo);white-space:nowrap">' + updated + '</td>';
13779
- html += '<td style="white-space:nowrap"><button class="btn" style="font-size:10px;padding:1px 6px" onclick="viewStoreEntry(\'' + escHtml(e.key) + '\',\'' + escHtml(e.namespace) + '\')">View</button>';
13780
- html += ' <button class="btn" style="font-size:10px;padding:1px 6px;color:var(--red);border-color:var(--red)" onclick="deleteStoreEntry(\'' + escHtml(e.id || '') + '\',\'' + escHtml(e.key) + '\',\'' + escHtml(e.namespace) + '\')">Del</button></td>';
13609
+ html += '<td style="white-space:nowrap"><button class="btn" style="font-size:10px;padding:1px 6px" onclick="viewStoreEntry(\'' + esc(e.key) + '\',\'' + esc(e.namespace) + '\')">View</button>';
13610
+ html += ' <button class="btn" style="font-size:10px;padding:1px 6px;color:var(--red);border-color:var(--red)" onclick="deleteStoreEntry(\'' + esc(e.id || '') + '\',\'' + esc(e.key) + '\',\'' + esc(e.namespace) + '\')">Del</button></td>';
13781
13611
  html += '</tr>';
13782
13612
  }
13783
13613
  html += '</tbody></table>';
13784
13614
  grid.innerHTML = html;
13785
13615
  _storeLoaded = true;
13786
13616
  } catch (e) {
13787
- grid.innerHTML = '<div class="empty" style="color:var(--red)">Error: ' + escHtml(e.message) + '</div>';
13617
+ grid.innerHTML = '<div class="empty" style="color:var(--red)">Error: ' + esc(e.message) + '</div>';
13788
13618
  }
13789
13619
  }
13790
13620
 
@@ -13801,20 +13631,20 @@ async function storeSearch() {
13801
13631
  if (ns) url += '&namespace=' + encodeURIComponent(ns);
13802
13632
  const data = await apiFetch(url);
13803
13633
  const results = data.results || [];
13804
- if (!results.length) { res.innerHTML = '<div class="empty">No results for "' + escHtml(q) + '"</div>'; return true; }
13634
+ if (!results.length) { res.innerHTML = '<div class="empty">No results for "' + esc(q) + '"</div>'; return true; }
13805
13635
  let html = '<div class="m-group-title" style="margin-bottom:4px">Search results (' + results.length + ') <span style="font-size:10px;color:var(--text-xs);font-weight:400">' + (data.searchMethod || '') + ', ' + (data.searchTime || 0) + 'ms</span></div>';
13806
13636
  html += '<table class="tbl" style="width:100%;font-size:12px"><thead><tr><th>Key</th><th>Score</th><th>NS</th><th>Content</th></tr></thead><tbody>';
13807
13637
  for (const r of results) {
13808
- const preview = escHtml((r.content || '').slice(0, 100)) + (r.content?.length > 100 ? '…' : '');
13809
- html += '<tr><td style="font-weight:500">' + escHtml(r.key) + '</td>';
13638
+ const preview = esc((r.content || '').slice(0, 100)) + (r.content?.length > 100 ? '…' : '');
13639
+ html += '<tr><td style="font-weight:500">' + esc(r.key) + '</td>';
13810
13640
  html += '<td style="color:var(--text-lo)">' + (r.score?.toFixed(3) || '') + '</td>';
13811
- html += '<td style="color:var(--text-lo)">' + escHtml(r.namespace) + '</td>';
13641
+ html += '<td style="color:var(--text-lo)">' + esc(r.namespace) + '</td>';
13812
13642
  html += '<td style="max-width:400px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + preview + '</td></tr>';
13813
13643
  }
13814
13644
  html += '</tbody></table>';
13815
13645
  res.innerHTML = html;
13816
13646
  } catch (e) {
13817
- res.innerHTML = '<div class="empty" style="color:var(--red)">Search error: ' + escHtml(e.message) + '</div>';
13647
+ res.innerHTML = '<div class="empty" style="color:var(--red)">Search error: ' + esc(e.message) + '</div>';
13818
13648
  }
13819
13649
  return true;
13820
13650
  }
@@ -13865,167 +13695,6 @@ function openStoreNewModal() {
13865
13695
  }).catch(e => showToast('Error', e.message, 'err'));
13866
13696
  }
13867
13697
 
13868
- // ── swarm memory tab ───────────────────────────────────────
13869
- let _swarmRuns = [];
13870
-
13871
- async function loadSwarmTab() {
13872
- const list = document.getElementById('swarm-run-list');
13873
- if (!list) return;
13874
- list.innerHTML = '<div class="loading-txt" style="padding:8px">Loading…</div>';
13875
- try {
13876
- const data = await apiFetch('/api/swarm-history?dir=' + enc(DIR));
13877
- _swarmRuns = Array.isArray(data) ? data : (data.runs || data.history || []);
13878
- _renderSwarmRunList();
13879
- } catch (e) {
13880
- list.innerHTML = '<div class="empty" style="padding:8px">Failed: ' + esc(e.message) + '</div>';
13881
- }
13882
- }
13883
-
13884
- function _renderSwarmRunList() {
13885
- const list = document.getElementById('swarm-run-list');
13886
- if (!_swarmRuns.length) {
13887
- list.innerHTML = '<div style="padding:8px;color:var(--text-lo);font-size:12px">No swarm runs found</div>';
13888
- return;
13889
- }
13890
- list.innerHTML = _swarmRuns.map((r, i) => {
13891
- const topo = r.topology || 'hierarchical';
13892
- const live = r.status === 'running' || r.active;
13893
- return '<div class="swarm-run-row" onclick="selectSwarmRun(' + i + ')">' +
13894
- '<div style="display:flex;align-items:center;gap:5px">' +
13895
- '<span class="swarm-topo-pill">' + esc(topo) + '</span>' +
13896
- (live ? '<span class="swarm-live">⬤ LIVE</span>' : '') +
13897
- '</div>' +
13898
- '<div style="font-size:11px;color:var(--text-lo);margin-top:2px">' + (r.agentCount || 0) + ' agents · ' + relTime(r.startedAt || r.created_at) + '</div>' +
13899
- '</div>';
13900
- }).join('');
13901
- }
13902
-
13903
- async function selectSwarmRun(idx) {
13904
- const run = _swarmRuns[idx];
13905
- if (!run) return;
13906
- document.querySelectorAll('.swarm-run-row').forEach((el, i) => el.classList.toggle('active', i === idx));
13907
- const detail = document.getElementById('swarm-run-detail');
13908
- if (!detail) return;
13909
- detail.innerHTML =
13910
- '<div style="margin-bottom:10px">' +
13911
- '<div style="font-size:13px;font-weight:600;color:var(--text-hi)">' + esc((run.swarmId || run.id || '—').toString().slice(0, 14)) + '</div>' +
13912
- '<div style="font-size:11px;color:var(--text-lo);margin-top:3px">' + esc(run.topology || '—') + ' · ' + (run.consensus ? esc(run.consensus) + ' vote-threshold' : '—') + ' · ' + (run.agentCount || 0) + ' agents</div>' +
13913
- '</div>' +
13914
- '<canvas id="swarm-topo-canvas" style="width:100%;max-width:380px;height:190px;display:block;border:1px solid var(--border);border-radius:6px;margin-bottom:12px"></canvas>' +
13915
- '<div id="swarm-agent-list" style="margin-bottom:10px"></div>' +
13916
- '<div id="swarm-event-log" style="font-size:11px;font-family:var(--mono);max-height:150px;overflow-y:auto"></div>';
13917
- _drawSwarmTopology(run);
13918
- _renderSwarmAgents(run);
13919
- _loadSwarmEvents(run.swarmId || run.id);
13920
- }
13921
-
13922
- function _drawSwarmTopology(run) {
13923
- const canvas = document.getElementById('swarm-topo-canvas');
13924
- if (!canvas) return;
13925
- const dpr = window.devicePixelRatio || 1;
13926
- const W = canvas.offsetWidth || 380, H = 190;
13927
- canvas.width = W * dpr; canvas.height = H * dpr;
13928
- canvas.style.width = W + 'px'; canvas.style.height = H + 'px';
13929
- const ctx = canvas.getContext('2d');
13930
- ctx.scale(dpr, dpr);
13931
- const agents = run.agents || Array.from({ length: Math.min(run.agentCount || 4, 12) }, (_, i) => ({ id: 'agent-' + i, type: 'worker' }));
13932
- const n = agents.length;
13933
- if (!n) return;
13934
- const topo = (run.topology || 'hierarchical').toLowerCase();
13935
- const cx = W / 2, cy = H / 2;
13936
- const pos = [];
13937
- if (topo.includes('hierarchical') || topo.includes('centralized')) {
13938
- pos.push({ x: cx, y: 28 });
13939
- const workers = agents.slice(1);
13940
- workers.forEach((_, i) => {
13941
- const spread = Math.min(W * 0.42, 160);
13942
- const x = cx + (workers.length > 1 ? (i / (workers.length - 1) - 0.5) * spread * 2 : 0);
13943
- pos.push({ x: x, y: H - 32 });
13944
- });
13945
- } else {
13946
- agents.forEach((_, i) => {
13947
- const a = (i / n) * Math.PI * 2 - Math.PI / 2;
13948
- const r = Math.min(W, H) * 0.36;
13949
- pos.push({ x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r });
13950
- });
13951
- }
13952
- ctx.strokeStyle = 'oklch(40% 0.006 75 / 0.35)'; ctx.lineWidth = 1;
13953
- if (topo.includes('hierarchical') || topo.includes('centralized')) {
13954
- for (let i = 1; i < pos.length; i++) {
13955
- ctx.beginPath(); ctx.moveTo(pos[0].x, pos[0].y); ctx.lineTo(pos[i].x, pos[i].y); ctx.stroke();
13956
- }
13957
- } else {
13958
- let drawn = 0;
13959
- for (let i = 0; i < n && drawn < 30; i++)
13960
- for (let j = i + 1; j < n && drawn < 30; j++, drawn++) {
13961
- ctx.beginPath(); ctx.moveTo(pos[i].x, pos[i].y); ctx.lineTo(pos[j].x, pos[j].y); ctx.stroke();
13962
- }
13963
- }
13964
- agents.forEach((ag, i) => {
13965
- const { x, y } = pos[i];
13966
- const isQ = i === 0 && (topo.includes('hierarchical') || topo.includes('centralized'));
13967
- const r = isQ ? 9 : 5;
13968
- ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI * 2);
13969
- ctx.fillStyle = isQ ? 'oklch(72% 0.18 75)' : 'oklch(52% 0.008 75)';
13970
- ctx.fill();
13971
- ctx.fillStyle = isQ ? 'oklch(11% 0.009 55)' : 'oklch(90% 0.005 75)';
13972
- ctx.font = isQ ? 'bold 9px sans-serif' : '8px sans-serif';
13973
- ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
13974
- ctx.fillText(isQ ? 'Q' : String(i), x, y);
13975
- if (!isQ) {
13976
- ctx.fillStyle = 'oklch(50% 0.006 75)';
13977
- ctx.font = '9px sans-serif';
13978
- ctx.fillText((ag.type || '').slice(0, 5), x, y + 14);
13979
- }
13980
- });
13981
- }
13982
-
13983
- function _renderSwarmAgents(run) {
13984
- const el = document.getElementById('swarm-agent-list');
13985
- if (!el) return;
13986
- const agents = run.agents || [];
13987
- if (!agents.length) { el.innerHTML = ''; return; }
13988
- el.innerHTML = '<div class="m-group-title" style="margin-bottom:4px">Agents</div>' +
13989
- agents.slice(0, 15).map(a =>
13990
- '<div style="display:flex;gap:10px;padding:3px 0;font-size:11px;border-bottom:1px solid var(--border)">' +
13991
- '<span style="color:var(--text-lo);font-family:var(--mono);min-width:70px">' + esc((a.id || '').toString().slice(0, 10)) + '</span>' +
13992
- '<span style="color:var(--text-hi)">' + esc(a.type || a.role || 'worker') + '</span>' +
13993
- '<span style="margin-left:auto;color:var(--text-xs)">' + esc(a.status || '—') + '</span>' +
13994
- '</div>'
13995
- ).join('');
13996
- }
13997
-
13998
- async function _loadSwarmEvents(swarmId) {
13999
- if (!swarmId) return;
14000
- const el = document.getElementById('swarm-event-log');
14001
- if (!el) return;
14002
- try {
14003
- const data = await apiFetch('/api/swarm-events?agentId=' + enc(swarmId) + '&dir=' + enc(DIR));
14004
- const events = Array.isArray(data) ? data : (data.events || []);
14005
- if (!events.length) { el.innerHTML = ''; return; }
14006
- el.innerHTML = '<div class="m-group-title" style="margin-bottom:3px">Events</div>' +
14007
- events.slice(-40).map(e =>
14008
- '<div style="color:var(--text-lo);padding:2px 0;border-bottom:1px solid var(--border)">' +
14009
- esc(relTime(e.ts || e.timestamp)) + ' <span style="color:var(--text-mid)">' + esc(e.type || e.kind || '?') + '</span> ' +
14010
- esc((e.message || e.data || '').toString().slice(0, 70)) +
14011
- '</div>'
14012
- ).join('');
14013
- el.scrollTop = el.scrollHeight;
14014
- } catch (_) {}
14015
- }
14016
-
14017
- async function cleanSwarmData() {
14018
- if (!confirm('Delete all swarm run data? This cannot be undone.')) return;
14019
- try {
14020
- await fetch('/api/swarm-clean?dir=' + enc(DIR), { method: 'DELETE' });
14021
- showToast('Cleaned', 'Swarm data deleted', 'ok');
14022
- _swarmRuns = [];
14023
- _renderSwarmRunList();
14024
- const detail = document.getElementById('swarm-run-detail');
14025
- if (detail) detail.innerHTML = '<div style="color:var(--text-lo);font-size:13px">Select a swarm run</div>';
14026
- } catch (e) { showToast('Error', e.message, 'err'); }
14027
- }
14028
-
14029
13698
  // ── chunks memory tab ──────────────────────────────────────
14030
13699
  let _chunks = [], _chunksLoaded = false, _editChunkId = null;
14031
13700
 
@@ -14244,113 +13913,75 @@ async function deleteChunk(id) {
14244
13913
  } catch (e) { showToast('Error', e.message, 'err'); }
14245
13914
  }
14246
13915
 
14247
- // ── agent graph memory tab ─────────────────────────────────
14248
- let _agData = null;
13916
+ // ── docs memory tab ─────────────────────────────────────────
13917
+ let _docsFiles = [], _docsLoaded = false;
14249
13918
 
14250
- async function loadAgentGraphTab() {
14251
- if (_agData) { _renderAgSummary(); _renderAgSessList(); return; }
14252
- const bar = document.getElementById('ag-summary-bar');
13919
+ async function loadDocsTab() {
13920
+ if (_docsLoaded) { _renderDocs(); return; }
13921
+ const bar = document.getElementById('docs-stats-bar');
13922
+ const grid = document.getElementById('docs-grid');
14253
13923
  if (bar) bar.innerHTML = '<div class="loading-txt">Loading…</div>';
14254
13924
  try {
14255
- const data = await apiFetch('/api/graph?dir=' + enc(DIR));
14256
- // /api/graph returns {nodes, edges} — session nodes + agenttype nodes.
14257
- // Transform into the summary/sessions shape this tab renders.
14258
- const nodes = Array.isArray(data && data.nodes) ? data.nodes : [];
14259
- const sessNodes = nodes.filter(n => n.type === 'session').sort((a, b) => (b.mtime || 0) - (a.mtime || 0));
14260
- const agNodes = nodes.filter(n => n.type === 'agenttype');
14261
- const sessions = sessNodes.map(n => ({
14262
- id: n.id,
14263
- turns: n.turns || 0,
14264
- spawnCount: Object.values(n.agentSpawns || {}).reduce((a, b) => a + b, 0),
14265
- toolCount: n.totalTools || 0,
14266
- tools: n.toolCounts || {},
14267
- agentTypes: n.agentSpawns || {},
14268
- cost: n.cost,
14269
- }));
14270
- _agData = {
14271
- sessions,
14272
- sessionCount: sessions.length,
14273
- agentTypes: agNodes.length,
14274
- totalSpawns: agNodes.reduce((a, n) => a + (n.totalSpawns || 0), 0),
14275
- totalToolCalls: sessions.reduce((a, s) => a + (s.toolCount || 0), 0),
14276
- totalCost: sessions.reduce((a, s) => a + (Number(s.cost) || 0), 0),
14277
- };
14278
- _renderAgSummary();
14279
- _renderAgSessList();
13925
+ const data = await apiFetch('/api/docs?dir=' + enc(DIR));
13926
+ _docsFiles = Array.isArray(data && data.files) ? data.files : [];
13927
+ _docsLoaded = true;
13928
+ _renderDocs();
14280
13929
  } catch (e) {
13930
+ if (grid) grid.innerHTML = '';
14281
13931
  if (bar) bar.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>';
14282
13932
  }
14283
13933
  }
14284
13934
 
14285
- function _renderAgSummary() {
14286
- const el = document.getElementById('ag-summary-bar');
14287
- if (!_agData || !el) return;
14288
- const d = _agData;
14289
- const stats = [
14290
- { l: 'Sessions', v: d.sessionCount != null ? d.sessionCount : (d.sessions && d.sessions.length != null ? d.sessions.length : '—') },
14291
- { l: 'Agent Types', v: d.agentTypes != null ? d.agentTypes : '—' },
14292
- { l: 'Spawns', v: d.totalSpawns != null ? d.totalSpawns : '—' },
14293
- { l: 'Tool Calls', v: d.totalToolCalls != null ? Number(d.totalToolCalls).toLocaleString() : '—' },
14294
- { l: 'Total Cost', v: d.totalCost != null ? '$' + Number(d.totalCost).toFixed(3) : '—' },
14295
- ];
14296
- el.innerHTML = stats.map(s =>
13935
+ function _renderDocs() {
13936
+ const bar = document.getElementById('docs-stats-bar');
13937
+ const grid = document.getElementById('docs-grid');
13938
+ if (bar) bar.innerHTML =
14297
13939
  '<div>' +
14298
- '<div style="font-size:10px;color:var(--text-lo);text-transform:uppercase;letter-spacing:0.07em">' + esc(s.l) + '</div>' +
14299
- '<div style="font-size:16px;font-weight:700;color:var(--text-hi);font-family:var(--mono)">' + esc(String(s.v)) + '</div>' +
14300
- '</div>'
14301
- ).join('');
13940
+ '<div style="font-size:10px;color:var(--text-lo);text-transform:uppercase;letter-spacing:0.07em">Markdown Files</div>' +
13941
+ '<div style="font-size:16px;font-weight:700;color:var(--text-hi);font-family:var(--mono)">' + _docsFiles.length + '</div>' +
13942
+ '</div>';
13943
+ if (!grid) return;
13944
+ if (!_docsFiles.length) {
13945
+ grid.innerHTML = '<div class="empty">No markdown files found.</div>';
13946
+ return;
13947
+ }
13948
+ grid.innerHTML = _docsFiles.map(f => {
13949
+ const sizeKb = ((f.size || 0) / 1024).toFixed(1) + ' KB';
13950
+ return '<div class="chunk-card" style="cursor:pointer" data-search="' + esc(f.path.toLowerCase()) + '" onclick="openDocModal(' + JSON.stringify(f.path).replace(/"/g, '&quot;') + ')">' +
13951
+ '<div class="chunk-src">' + esc(f.path) + '</div>' +
13952
+ '<div class="chunk-footer" style="margin-top:3px">' +
13953
+ '<span class="chunk-ns">' + esc(sizeKb) + '</span>' +
13954
+ '<span style="font-size:11px;color:var(--text-lo)">' + esc(relTime(f.mtime)) + '</span>' +
13955
+ '</div>' +
13956
+ '</div>';
13957
+ }).join('');
14302
13958
  }
14303
13959
 
14304
- function _renderAgSessList() {
14305
- const el = document.getElementById('ag-sess-list');
14306
- if (!_agData || !el) return;
14307
- const sessions = _agData.sessions || [];
14308
- if (!sessions.length) { el.innerHTML = '<div class="empty" style="font-size:12px">No sessions</div>'; return; }
14309
- el.innerHTML = sessions.map((s, i) =>
14310
- '<div class="sess-row" style="margin-bottom:4px" onclick="selectAgSession(' + i + ')">' +
14311
- '<div class="sr-top">' +
14312
- '<div class="sr-prompt" style="font-size:11px">' + esc((s.id || s.file || '').slice(-16)) + '</div>' +
14313
- '</div>' +
14314
- '<div style="font-size:10px;color:var(--text-lo);margin-top:1px">' + (s.spawnCount || 0) + ' spawns · ' + (s.toolCount || 0) + ' tools</div>' +
14315
- '</div>'
14316
- ).join('');
13960
+ function filterDocs(q) {
13961
+ const lq = q.toLowerCase();
13962
+ document.querySelectorAll('#docs-grid .chunk-card').forEach(el => {
13963
+ el.style.display = (!lq || (el.dataset.search || '').includes(lq)) ? '' : 'none';
13964
+ });
14317
13965
  }
14318
13966
 
14319
- function selectAgSession(idx) {
14320
- const s = (_agData && _agData.sessions || [])[idx];
14321
- if (!s) return;
14322
- document.querySelectorAll('#ag-sess-list .sess-row').forEach((el, i) => el.classList.toggle('active', i === idx));
14323
- const detail = document.getElementById('ag-detail');
14324
- if (!detail) return;
14325
- const agArr = Object.entries(s.agentTypes || {}).sort((a, b) => b[1] - a[1]).slice(0, 12);
14326
- const toolArr = Object.entries(s.tools || {}).sort((a, b) => b[1] - a[1]).slice(0, 15);
14327
- const maxAg = agArr.length ? Math.max(...agArr.map(x => x[1])) : 1;
14328
- const maxTool = toolArr.length ? Math.max(...toolArr.map(x => x[1])) : 1;
14329
- detail.innerHTML =
14330
- '<div style="display:flex;gap:16px;flex-wrap:wrap;margin-bottom:10px;font-size:12px">' +
14331
- '<span>Turns: <b>' + (s.turns || 0) + '</b></span>' +
14332
- '<span>Spawns: <b>' + (s.spawnCount || 0) + '</b></span>' +
14333
- '<span>Tools: <b>' + (s.toolCount || 0) + '</b></span>' +
14334
- (s.cost != null ? '<span style="color:var(--accent)">$' + Number(s.cost).toFixed(4) + '</span>' : '') +
14335
- '</div>' +
14336
- (agArr.length ? '<div class="m-group-title" style="margin-bottom:5px">Agent Types</div>' +
14337
- agArr.map(function(entry) {
14338
- var type = entry[0], count = entry[1];
14339
- return '<div style="display:flex;align-items:center;gap:8px;padding:3px 0;font-size:12px">' +
14340
- '<div style="width:110px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-hi)">' + esc(type) + '</div>' +
14341
- '<div style="flex:1;height:7px;background:var(--border);border-radius:2px;overflow:hidden"><div style="width:' + Math.round(count/maxAg*100) + '%;height:100%;background:var(--accent);border-radius:2px"></div></div>' +
14342
- '<div style="width:22px;text-align:right;color:var(--text-lo);font-size:11px;font-family:var(--mono)">' + count + '</div>' +
14343
- '</div>';
14344
- }).join('') : '') +
14345
- (toolArr.length ? '<div class="m-group-title" style="margin-bottom:5px;margin-top:14px">Top Tools</div>' +
14346
- toolArr.map(function(entry) {
14347
- var tool = entry[0], count = entry[1];
14348
- return '<div style="display:flex;align-items:center;gap:8px;padding:3px 0;font-size:12px">' +
14349
- '<div style="width:110px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-hi);font-family:var(--mono);font-size:11px">' + esc(tool) + '</div>' +
14350
- '<div style="flex:1;height:7px;background:var(--border);border-radius:2px;overflow:hidden"><div style="width:' + Math.round(count/maxTool*100) + '%;height:100%;background:oklch(62% 0.12 195);border-radius:2px"></div></div>' +
14351
- '<div style="width:22px;text-align:right;color:var(--text-lo);font-size:11px;font-family:var(--mono)">' + count + '</div>' +
14352
- '</div>';
14353
- }).join('') : '');
13967
+ async function openDocModal(relPath) {
13968
+ const modal = document.getElementById('docs-modal');
13969
+ const title = document.getElementById('docs-modal-title');
13970
+ const body = document.getElementById('docs-modal-body');
13971
+ title.textContent = relPath;
13972
+ body.innerHTML = '<div class="loading-txt">Loading…</div>';
13973
+ modal.classList.add('open');
13974
+ try {
13975
+ const data = await apiFetch('/api/doc-read?dir=' + enc(DIR) + '&path=' + enc(relPath));
13976
+ body.innerHTML = data && data.body ? renderDocMarkdown(data.body) : '<div class="empty">Empty document</div>';
13977
+ body.scrollTop = 0;
13978
+ } catch (e) {
13979
+ body.innerHTML = '<div class="empty">Failed to load: ' + esc(e.message) + '</div>';
13980
+ }
13981
+ }
13982
+
13983
+ function closeDocModal() {
13984
+ document.getElementById('docs-modal').classList.remove('open');
14354
13985
  }
14355
13986
 
14356
13987
  // ── Mastermind overlay ─────────────────────────────────────