@dollhousemcp/mcp-server 2.0.0-rc.6 → 2.0.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 (238) hide show
  1. package/CHANGELOG.md +52 -61
  2. package/README.github.md +2 -2
  3. package/README.md +2 -2
  4. package/README.md.backup +284 -224
  5. package/README.npm.md +2 -2
  6. package/dist/cache/LRUCache.d.ts +3 -0
  7. package/dist/cache/LRUCache.d.ts.map +1 -1
  8. package/dist/cache/LRUCache.js +36 -26
  9. package/dist/config/env.d.ts +14 -4
  10. package/dist/config/env.d.ts.map +1 -1
  11. package/dist/config/env.js +20 -6
  12. package/dist/di/Container.d.ts +21 -0
  13. package/dist/di/Container.d.ts.map +1 -1
  14. package/dist/di/Container.js +250 -53
  15. package/dist/elements/BaseElement.d.ts.map +1 -1
  16. package/dist/elements/BaseElement.js +5 -10
  17. package/dist/elements/base/BaseElementManager.d.ts +22 -0
  18. package/dist/elements/base/BaseElementManager.d.ts.map +1 -1
  19. package/dist/elements/base/BaseElementManager.js +47 -7
  20. package/dist/elements/memories/Memory.d.ts +1 -0
  21. package/dist/elements/memories/Memory.d.ts.map +1 -1
  22. package/dist/elements/memories/Memory.js +12 -8
  23. package/dist/elements/memories/MemoryManager.d.ts.map +1 -1
  24. package/dist/elements/memories/MemoryManager.js +23 -42
  25. package/dist/elements/memories/MemorySearchIndex.js +2 -2
  26. package/dist/generated/version.d.ts +2 -2
  27. package/dist/generated/version.d.ts.map +1 -1
  28. package/dist/generated/version.js +3 -3
  29. package/dist/handlers/EnhancedIndexHandler.js +6 -6
  30. package/dist/handlers/element-crud/listElements.d.ts +2 -0
  31. package/dist/handlers/element-crud/listElements.d.ts.map +1 -1
  32. package/dist/handlers/element-crud/listElements.js +3 -1
  33. package/dist/handlers/mcp-aql/Gatekeeper.d.ts.map +1 -1
  34. package/dist/handlers/mcp-aql/Gatekeeper.js +23 -17
  35. package/dist/handlers/mcp-aql/MCPAQLHandler.d.ts +14 -0
  36. package/dist/handlers/mcp-aql/MCPAQLHandler.d.ts.map +1 -1
  37. package/dist/handlers/mcp-aql/MCPAQLHandler.js +110 -14
  38. package/dist/handlers/mcp-aql/OperationRouter.d.ts.map +1 -1
  39. package/dist/handlers/mcp-aql/OperationRouter.js +13 -1
  40. package/dist/handlers/mcp-aql/OperationSchema.d.ts +7 -0
  41. package/dist/handlers/mcp-aql/OperationSchema.d.ts.map +1 -1
  42. package/dist/handlers/mcp-aql/OperationSchema.js +52 -1
  43. package/dist/handlers/mcp-aql/evaluatePermission.d.ts +53 -0
  44. package/dist/handlers/mcp-aql/evaluatePermission.d.ts.map +1 -0
  45. package/dist/handlers/mcp-aql/evaluatePermission.js +132 -0
  46. package/dist/handlers/mcp-aql/policies/ToolClassification.d.ts.map +1 -1
  47. package/dist/handlers/mcp-aql/policies/ToolClassification.js +2 -1
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +3 -3
  50. package/dist/logging/LogHooks.js +11 -11
  51. package/dist/logging/LogManager.d.ts +0 -2
  52. package/dist/logging/LogManager.d.ts.map +1 -1
  53. package/dist/logging/LogManager.js +1 -3
  54. package/dist/logging/sinks/MemoryLogSink.d.ts +2 -0
  55. package/dist/logging/sinks/MemoryLogSink.d.ts.map +1 -1
  56. package/dist/logging/sinks/MemoryLogSink.js +12 -3
  57. package/dist/logging/types.d.ts +0 -2
  58. package/dist/logging/types.d.ts.map +1 -1
  59. package/dist/logging/types.js +1 -1
  60. package/dist/metrics/GatekeeperMetricsTracker.d.ts +32 -0
  61. package/dist/metrics/GatekeeperMetricsTracker.d.ts.map +1 -0
  62. package/dist/metrics/GatekeeperMetricsTracker.js +42 -0
  63. package/dist/metrics/MetricsManager.d.ts +47 -0
  64. package/dist/metrics/MetricsManager.d.ts.map +1 -0
  65. package/dist/metrics/MetricsManager.js +232 -0
  66. package/dist/metrics/OperationMetricsTracker.d.ts +32 -0
  67. package/dist/metrics/OperationMetricsTracker.d.ts.map +1 -0
  68. package/dist/metrics/OperationMetricsTracker.js +53 -0
  69. package/dist/metrics/collectors/DefaultElementProviderCollector.d.ts +27 -0
  70. package/dist/metrics/collectors/DefaultElementProviderCollector.d.ts.map +1 -0
  71. package/dist/metrics/collectors/DefaultElementProviderCollector.js +69 -0
  72. package/dist/metrics/collectors/FileLockManagerCollector.d.ts +16 -0
  73. package/dist/metrics/collectors/FileLockManagerCollector.d.ts.map +1 -0
  74. package/dist/metrics/collectors/FileLockManagerCollector.js +51 -0
  75. package/dist/metrics/collectors/GatekeeperMetricsCollector.d.ts +16 -0
  76. package/dist/metrics/collectors/GatekeeperMetricsCollector.d.ts.map +1 -0
  77. package/dist/metrics/collectors/GatekeeperMetricsCollector.js +76 -0
  78. package/dist/metrics/collectors/LRUCacheCollector.d.ts +18 -0
  79. package/dist/metrics/collectors/LRUCacheCollector.d.ts.map +1 -0
  80. package/dist/metrics/collectors/LRUCacheCollector.js +95 -0
  81. package/dist/metrics/collectors/OperationMetricsCollector.d.ts +16 -0
  82. package/dist/metrics/collectors/OperationMetricsCollector.d.ts.map +1 -0
  83. package/dist/metrics/collectors/OperationMetricsCollector.js +80 -0
  84. package/dist/metrics/collectors/OperationalTelemetryCollector.d.ts +17 -0
  85. package/dist/metrics/collectors/OperationalTelemetryCollector.d.ts.map +1 -0
  86. package/dist/metrics/collectors/OperationalTelemetryCollector.js +26 -0
  87. package/dist/metrics/collectors/PerformanceMonitorCollector.d.ts +14 -0
  88. package/dist/metrics/collectors/PerformanceMonitorCollector.d.ts.map +1 -0
  89. package/dist/metrics/collectors/PerformanceMonitorCollector.js +141 -0
  90. package/dist/metrics/collectors/SecurityMonitorCollector.d.ts +21 -0
  91. package/dist/metrics/collectors/SecurityMonitorCollector.d.ts.map +1 -0
  92. package/dist/metrics/collectors/SecurityMonitorCollector.js +56 -0
  93. package/dist/metrics/collectors/SecurityTelemetryCollector.d.ts +15 -0
  94. package/dist/metrics/collectors/SecurityTelemetryCollector.d.ts.map +1 -0
  95. package/dist/metrics/collectors/SecurityTelemetryCollector.js +112 -0
  96. package/dist/metrics/collectors/TriggerMetricsTrackerCollector.d.ts +16 -0
  97. package/dist/metrics/collectors/TriggerMetricsTrackerCollector.d.ts.map +1 -0
  98. package/dist/metrics/collectors/TriggerMetricsTrackerCollector.js +26 -0
  99. package/dist/metrics/collectors/index.d.ts +11 -0
  100. package/dist/metrics/collectors/index.d.ts.map +1 -0
  101. package/dist/metrics/collectors/index.js +11 -0
  102. package/dist/metrics/sinks/MemoryMetricsSink.d.ts +22 -0
  103. package/dist/metrics/sinks/MemoryMetricsSink.d.ts.map +1 -0
  104. package/dist/metrics/sinks/MemoryMetricsSink.js +121 -0
  105. package/dist/metrics/types.d.ts +98 -0
  106. package/dist/metrics/types.d.ts.map +1 -0
  107. package/dist/metrics/types.js +24 -0
  108. package/dist/portfolio/DefaultElementProvider.d.ts.map +1 -1
  109. package/dist/portfolio/DefaultElementProvider.js +1 -7
  110. package/dist/portfolio/EnhancedIndexManager.d.ts.map +1 -1
  111. package/dist/portfolio/EnhancedIndexManager.js +18 -18
  112. package/dist/portfolio/NLPScoringManager.d.ts.map +1 -1
  113. package/dist/portfolio/NLPScoringManager.js +5 -9
  114. package/dist/portfolio/PortfolioIndexManager.js +2 -2
  115. package/dist/portfolio/RelationshipManager.js +2 -2
  116. package/dist/portfolio/VerbTriggerManager.d.ts.map +1 -1
  117. package/dist/portfolio/VerbTriggerManager.js +5 -19
  118. package/dist/portfolio/config/IndexConfig.d.ts.map +1 -1
  119. package/dist/portfolio/config/IndexConfig.js +1 -12
  120. package/dist/portfolio/enhanced-index/ElementDefinitionBuilder.d.ts.map +1 -1
  121. package/dist/portfolio/enhanced-index/ElementDefinitionBuilder.js +3 -15
  122. package/dist/portfolio/enhanced-index/SemanticRelationshipService.d.ts.map +1 -1
  123. package/dist/portfolio/enhanced-index/SemanticRelationshipService.js +2 -16
  124. package/dist/portfolio/types/RelationshipTypes.d.ts.map +1 -1
  125. package/dist/portfolio/types/RelationshipTypes.js +3 -17
  126. package/dist/security/audit/config/suppressions.d.ts.map +1 -1
  127. package/dist/security/audit/config/suppressions.js +36 -8
  128. package/dist/security/constants.d.ts.map +1 -1
  129. package/dist/security/constants.js +10 -6
  130. package/dist/security/fileLockManager.d.ts.map +1 -1
  131. package/dist/security/fileLockManager.js +8 -6
  132. package/dist/security/secureYamlParser.d.ts.map +1 -1
  133. package/dist/security/secureYamlParser.js +1 -13
  134. package/dist/security/securityMonitor.d.ts +2 -1
  135. package/dist/security/securityMonitor.d.ts.map +1 -1
  136. package/dist/security/securityMonitor.js +14 -3
  137. package/dist/security/telemetry/SecurityTelemetry.d.ts +16 -0
  138. package/dist/security/telemetry/SecurityTelemetry.d.ts.map +1 -1
  139. package/dist/security/telemetry/SecurityTelemetry.js +30 -2
  140. package/dist/security/tokenManager.d.ts +3 -0
  141. package/dist/security/tokenManager.d.ts.map +1 -1
  142. package/dist/security/tokenManager.js +13 -5
  143. package/dist/security/validation/BackgroundValidator.d.ts.map +1 -1
  144. package/dist/security/validation/BackgroundValidator.js +7 -7
  145. package/dist/server/startup.d.ts.map +1 -1
  146. package/dist/server/startup.js +8 -24
  147. package/dist/server/tools/MCPAQLTools.js +4 -1
  148. package/dist/services/ActivationStore.d.ts.map +1 -1
  149. package/dist/services/ActivationStore.js +9 -3
  150. package/dist/services/FileWatchService.d.ts +1 -0
  151. package/dist/services/FileWatchService.d.ts.map +1 -1
  152. package/dist/services/FileWatchService.js +83 -48
  153. package/dist/services/MetadataService.d.ts.map +1 -1
  154. package/dist/services/MetadataService.js +7 -2
  155. package/dist/services/query/ElementQueryService.d.ts.map +1 -1
  156. package/dist/services/query/ElementQueryService.js +1 -41
  157. package/dist/services/query/PaginationService.d.ts.map +1 -1
  158. package/dist/services/query/PaginationService.js +1 -14
  159. package/dist/services/query/SortService.d.ts.map +1 -1
  160. package/dist/services/query/SortService.js +1 -6
  161. package/dist/services/validation/ValidationService.d.ts.map +1 -1
  162. package/dist/services/validation/ValidationService.js +3 -8
  163. package/dist/storage/ElementStorageLayer.d.ts.map +1 -1
  164. package/dist/storage/ElementStorageLayer.js +5 -2
  165. package/dist/storage/MemoryStorageLayer.d.ts.map +1 -1
  166. package/dist/storage/MemoryStorageLayer.js +5 -2
  167. package/dist/telemetry/OperationalTelemetry.js +2 -2
  168. package/dist/utils/EventDeduplicator.d.ts +44 -0
  169. package/dist/utils/EventDeduplicator.d.ts.map +1 -0
  170. package/dist/utils/EventDeduplicator.js +93 -0
  171. package/dist/utils/FileLock.d.ts.map +1 -1
  172. package/dist/utils/FileLock.js +1 -9
  173. package/dist/utils/PerformanceMonitor.d.ts.map +1 -1
  174. package/dist/utils/PerformanceMonitor.js +5 -5
  175. package/dist/utils/SlidingWindowRateLimiter.d.ts +13 -0
  176. package/dist/utils/SlidingWindowRateLimiter.d.ts.map +1 -0
  177. package/dist/utils/SlidingWindowRateLimiter.js +23 -0
  178. package/dist/web/console/IngestRoutes.d.ts +84 -0
  179. package/dist/web/console/IngestRoutes.d.ts.map +1 -0
  180. package/dist/web/console/IngestRoutes.js +252 -0
  181. package/dist/web/console/LeaderElection.d.ts +89 -0
  182. package/dist/web/console/LeaderElection.d.ts.map +1 -0
  183. package/dist/web/console/LeaderElection.js +205 -0
  184. package/dist/web/console/LeaderForwardingSink.d.ts +61 -0
  185. package/dist/web/console/LeaderForwardingSink.d.ts.map +1 -0
  186. package/dist/web/console/LeaderForwardingSink.js +197 -0
  187. package/dist/web/console/SessionNames.d.ts +46 -0
  188. package/dist/web/console/SessionNames.d.ts.map +1 -0
  189. package/dist/web/console/SessionNames.js +257 -0
  190. package/dist/web/console/UnifiedConsole.d.ts +64 -0
  191. package/dist/web/console/UnifiedConsole.d.ts.map +1 -0
  192. package/dist/web/console/UnifiedConsole.js +119 -0
  193. package/dist/web/contentPipeline.d.ts +58 -0
  194. package/dist/web/contentPipeline.d.ts.map +1 -0
  195. package/dist/web/contentPipeline.js +112 -0
  196. package/dist/web/portDiscovery.d.ts +58 -0
  197. package/dist/web/portDiscovery.d.ts.map +1 -0
  198. package/dist/web/portDiscovery.js +143 -0
  199. package/dist/web/public/app.js +148 -60
  200. package/dist/web/public/logs.js +638 -0
  201. package/dist/web/public/metrics.js +682 -0
  202. package/dist/web/public/permissions.js +394 -0
  203. package/dist/web/public/sessions.js +369 -0
  204. package/dist/web/routes/healthRoutes.d.ts +16 -0
  205. package/dist/web/routes/healthRoutes.d.ts.map +1 -0
  206. package/dist/web/routes/healthRoutes.js +29 -0
  207. package/dist/web/routes/logRoutes.d.ts +18 -0
  208. package/dist/web/routes/logRoutes.d.ts.map +1 -0
  209. package/dist/web/routes/logRoutes.js +126 -0
  210. package/dist/web/routes/metricsRoutes.d.ts +17 -0
  211. package/dist/web/routes/metricsRoutes.d.ts.map +1 -0
  212. package/dist/web/routes/metricsRoutes.js +90 -0
  213. package/dist/web/routes/permissionRoutes.d.ts +16 -0
  214. package/dist/web/routes/permissionRoutes.d.ts.map +1 -0
  215. package/dist/web/routes/permissionRoutes.js +133 -0
  216. package/dist/web/routes.d.ts.map +1 -1
  217. package/dist/web/routes.js +309 -339
  218. package/dist/web/server.d.ts +21 -1
  219. package/dist/web/server.d.ts.map +1 -1
  220. package/dist/web/server.js +42 -4
  221. package/dist/web/sinks/WebSSELogSink.d.ts +15 -0
  222. package/dist/web/sinks/WebSSELogSink.d.ts.map +1 -0
  223. package/dist/web/sinks/WebSSELogSink.js +22 -0
  224. package/dist/web/sinks/WebSSEMetricsSink.d.ts +16 -0
  225. package/dist/web/sinks/WebSSEMetricsSink.d.ts.map +1 -0
  226. package/dist/web/sinks/WebSSEMetricsSink.js +23 -0
  227. package/package.json +2 -2
  228. package/server.json +2 -2
  229. package/dist/constants/version.d.ts +0 -3
  230. package/dist/constants/version.d.ts.map +0 -1
  231. package/dist/constants/version.js +0 -4
  232. package/dist/logging/sinks/SSELogSink.d.ts +0 -35
  233. package/dist/logging/sinks/SSELogSink.d.ts.map +0 -1
  234. package/dist/logging/sinks/SSELogSink.js +0 -181
  235. package/dist/logging/viewer/viewerHtml.d.ts +0 -8
  236. package/dist/logging/viewer/viewerHtml.d.ts.map +0 -1
  237. package/dist/logging/viewer/viewerHtml.js +0 -204
  238. package/dist/web/public/public/app.js +0 -1878
@@ -173,11 +173,19 @@ function safeParseYaml(content) {
173
173
 
174
174
  // ── Type filter chips ──────────────────────────────────────────────────────
175
175
 
176
+ /** Elements filtered by source toggle only (no type/topic/search). */
177
+ function getSourceFilteredElements() {
178
+ if (activeSource === 'collection') return allElements.filter(el => !el._local);
179
+ if (activeSource === 'portfolio') return allElements.filter(el => el._local);
180
+ return allElements;
181
+ }
182
+
176
183
  function renderTypeFilters() {
177
184
  const container = document.getElementById('type-filters');
178
185
  if (!container) return;
179
186
 
180
- const typeCounts = allElements.reduce((acc, el) => {
187
+ const sourceFiltered = getSourceFilteredElements();
188
+ const typeCounts = sourceFiltered.reduce((acc, el) => {
181
189
  acc[el.type] = (acc[el.type] || 0) + 1;
182
190
  return acc;
183
191
  }, {});
@@ -186,7 +194,7 @@ function safeParseYaml(content) {
186
194
 
187
195
  const isAllActive = activeTypes.size === 0;
188
196
  container.innerHTML = types.map(type => {
189
- const count = type === 'all' ? allElements.length : typeCounts[type];
197
+ const count = type === 'all' ? sourceFiltered.length : typeCounts[type];
190
198
  const isActive = type === 'all' ? isAllActive : activeTypes.has(type);
191
199
  return `<button
192
200
  class="type-filter${isActive ? ' active' : ''}"
@@ -253,8 +261,9 @@ function safeParseYaml(content) {
253
261
  const container = document.getElementById('topic-filters');
254
262
  if (!container) return;
255
263
 
264
+ const sourceFiltered = getSourceFilteredElements();
256
265
  const topicCounts = {};
257
- allElements.forEach(el => {
266
+ sourceFiltered.forEach(el => {
258
267
  const topic = getTopicForElement(el);
259
268
  if (topic) topicCounts[topic] = (topicCounts[topic] || 0) + 1;
260
269
  });
@@ -264,7 +273,7 @@ function safeParseYaml(content) {
264
273
 
265
274
  container.hidden = false;
266
275
  container.innerHTML = topics.map(topic => {
267
- const count = topic === 'all' ? allElements.length : topicCounts[topic];
276
+ const count = topic === 'all' ? sourceFiltered.length : topicCounts[topic];
268
277
  const isActive = topic === activeTopic;
269
278
  return `<button
270
279
  class="topic-filter${isActive ? ' active' : ''}"
@@ -356,9 +365,10 @@ function safeParseYaml(content) {
356
365
  const pageItems = filteredElements.slice(pageStart, pageEnd);
357
366
 
358
367
  if (countEl) {
359
- const base = total === allElements.length
360
- ? `${allElements.length} elements`
361
- : `${total} of ${allElements.length} elements`;
368
+ const sourceTotal = getSourceFilteredElements().length;
369
+ const base = total === sourceTotal
370
+ ? `${sourceTotal} elements`
371
+ : `${total} of ${sourceTotal} elements`;
362
372
  const pageNote = totalPages > 1 ? ` · page ${currentPage} of ${totalPages}` : '';
363
373
  countEl.textContent = base + pageNote;
364
374
  }
@@ -708,19 +718,25 @@ function safeParseYaml(content) {
708
718
  document.body.classList.add('modal-open');
709
719
  body.focus(); // focus body so arrow/Page/Home/End keys scroll content natively
710
720
 
711
- // Fetch full content as raw text — local API for portfolio, GitHub for collection
721
+ // Fetch element content — structured JSON for local, raw text for collection
712
722
  try {
713
- let content;
723
+ let content; // raw file content (for Raw view, Copy, Download)
724
+ let structured; // { metadata, body, type, validation } when available
725
+
714
726
  if (element._content) {
715
727
  content = element._content;
716
728
  } else if (element._local) {
717
729
  const res = await fetch(`/api/elements/${element.path}`);
718
730
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
719
- content = await res.text();
731
+ const data = await res.json();
732
+ structured = data;
733
+ content = data.raw;
720
734
  } else {
721
- const res = await fetch(`https://raw.githubusercontent.com/DollhouseMCP/collection/main/${element.path}`);
735
+ const res = await fetch(`/api/collection/content/${element.path}`);
722
736
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
723
- content = await res.text();
737
+ const data = await res.json();
738
+ structured = data;
739
+ content = data.raw;
724
740
  }
725
741
 
726
742
  const renderBtn = modal.querySelector('#btn-render');
@@ -729,6 +745,11 @@ function safeParseYaml(content) {
729
745
  if (modalShowRaw) {
730
746
  body.innerHTML = `<pre class="element-source"><code class="element-code language-yaml">${escapeHtml(content)}</code></pre>`;
731
747
  if (globalThis.hljs) body.querySelectorAll('pre code').forEach(b => hljs.highlightElement(b));
748
+ } else if (structured) {
749
+ body.innerHTML = renderStructuredDetail(structured);
750
+ body.querySelectorAll('pre code').forEach(b => {
751
+ if (globalThis.hljs) hljs.highlightElement(b);
752
+ });
732
753
  } else {
733
754
  body.innerHTML = renderDetailView(content, element.type);
734
755
  body.querySelectorAll('pre code').forEach(b => {
@@ -1304,27 +1325,15 @@ function safeParseYaml(content) {
1304
1325
  return html;
1305
1326
  }
1306
1327
 
1307
- function renderDetailView(content, type) { // NOSONAR - sequential independent metadata sections; complexity score is inflated by &&-guards on array existence checks
1308
- if (type === 'memory') {
1309
- // Portfolio memories are pure YAML — jsyaml.load succeeds.
1310
- // Collection memories are markdown-with-frontmatter — jsyaml.load throws on the
1311
- // second `---` document marker, so we fall through to the standard renderer below.
1312
- try {
1313
- const parsed = safeParseYaml(content);
1314
- if (parsed && typeof parsed === 'object') return renderMemoryView(content);
1315
- } catch { /* not pure YAML — fall through */ }
1316
- }
1317
-
1318
- const { frontmatter: fm, body } = parseFrontmatter(content);
1328
+ /** Render common metadata sections (created, author, tags, etc.) shared by both renderers */
1329
+ function renderCommonMetadata(fm, type) {
1319
1330
  let html = '';
1320
1331
 
1321
- // ── Created date — prominent header line ──
1322
1332
  const createdVal = fm.created || fm.created_date;
1323
1333
  if (createdVal) {
1324
1334
  html += `<div class="detail-created"><span class="detail-created-label">Created</span><span class="detail-created-value">${escapeHtml(formatDate(createdVal))}</span></div>`;
1325
1335
  }
1326
1336
 
1327
- // ── Core metadata ──
1328
1337
  const coreFields = [
1329
1338
  detailField('Author', fm.author),
1330
1339
  detailField('Version', fm.version ? `v${fm.version}` : null),
@@ -1335,68 +1344,103 @@ function safeParseYaml(content) {
1335
1344
  ].filter(Boolean).join('');
1336
1345
  if (coreFields) html += detailSection('Details', coreFields);
1337
1346
 
1338
- // ── Tags ──
1339
1347
  if (Array.isArray(fm.tags) && fm.tags.length) {
1340
1348
  html += detailSection('Tags', detailPillList(fm.tags, 'pill-tag'));
1341
1349
  }
1342
-
1343
- // ── Triggers (personas) ──
1344
1350
  if (Array.isArray(fm.triggers) && fm.triggers.length) {
1345
1351
  html += detailSection('Trigger words', detailPillList(fm.triggers, 'pill-trigger'));
1346
1352
  }
1347
1353
 
1348
- // ── Components (ensembles) ──
1349
- const compTypes = ['personas','skills','tools','templates','prompts','memories'];
1350
- const compEntries = compTypes
1351
- .filter(k => Array.isArray(fm[k]) && fm[k].length)
1352
- .map(k => `<div class="detail-field"><span class="detail-label">${capitalize(k)}</span><span class="detail-value">${detailPillList(fm[k])}</span></div>`)
1353
- .join('');
1354
- if (compEntries) html += detailSection('Components', compEntries);
1355
-
1356
- // ── Ensemble coordination ──
1354
+ html += renderComponentsSections(fm);
1357
1355
  if (fm.coordination_strategy) html += detailSection('Coordination', `<p class="detail-prose">${escapeHtml(fm.coordination_strategy)}</p>`);
1356
+ html += renderUseCases(fm);
1358
1357
 
1359
- // ── Use cases ──
1360
- if (Array.isArray(fm.use_cases) && fm.use_cases.length) {
1361
- const useCaseItems = fm.use_cases.map(u => `<li>${escapeHtml(u)}</li>`).join('');
1362
- html += detailSection('Use cases', `<ul class="detail-list">${useCaseItems}</ul>`);
1363
- }
1364
-
1365
- // ── Instructions (all element types) ──
1366
1358
  if (fm.instructions && type !== 'agent' && type !== 'ensemble') {
1367
- // Agents and ensembles render instructions in their own sections
1368
1359
  html += detailSection('Instructions', renderInstructions(fm.instructions));
1369
1360
  }
1370
-
1371
- // ── Gatekeeper (all element types) ──
1372
1361
  if (fm.gatekeeper && typeof fm.gatekeeper === 'object' && type !== 'agent' && type !== 'ensemble') {
1373
1362
  html += renderGatekeeperSection(fm.gatekeeper);
1374
1363
  }
1375
1364
 
1376
- // ── Parameters (skills/tools) ──
1377
- html += renderDetailParameters(fm);
1365
+ return html;
1366
+ }
1378
1367
 
1379
- // ── Variables (templates) ──
1380
- html += renderDetailVariables(fm);
1368
+ /** Render components section (ensembles) */
1369
+ function renderComponentsSections(fm) {
1370
+ const compTypes = ['personas','skills','tools','templates','prompts','memories'];
1371
+ const compEntries = compTypes
1372
+ .filter(k => Array.isArray(fm[k]) && fm[k].length)
1373
+ .map(k => `<div class="detail-field"><span class="detail-label">${capitalize(k)}</span><span class="detail-value">${detailPillList(fm[k])}</span></div>`)
1374
+ .join('');
1375
+ return compEntries ? detailSection('Components', compEntries) : '';
1376
+ }
1377
+
1378
+ /** Render use cases section */
1379
+ function renderUseCases(fm) {
1380
+ if (!Array.isArray(fm.use_cases) || !fm.use_cases.length) return '';
1381
+ const items = fm.use_cases.map(u => `<li>${escapeHtml(u)}</li>`).join('');
1382
+ return detailSection('Use cases', `<ul class="detail-list">${items}</ul>`);
1383
+ }
1381
1384
 
1382
- // ── Proficiency levels (skills) ──
1385
+ /** Render type-specific sections (parameters, agent, ensemble, proficiency) */
1386
+ function renderTypeSpecificSections(fm, type) {
1387
+ let html = '';
1388
+ html += renderDetailParameters(fm);
1389
+ html += renderDetailVariables(fm);
1383
1390
  if (fm.proficiency_levels && typeof fm.proficiency_levels === 'object') {
1384
1391
  const levels = Object.entries(fm.proficiency_levels)
1385
1392
  .map(([lvl, desc]) => detailField(capitalize(lvl), desc)).join('');
1386
1393
  if (levels) html += detailSection('Proficiency levels', levels);
1387
1394
  }
1395
+ if (type === 'agent') html += renderAgentSection(fm);
1396
+ if (type === 'ensemble') html += renderEnsembleSection(fm);
1397
+ return html;
1398
+ }
1388
1399
 
1389
- // ── Agent fields ──
1390
- if (type === 'agent') {
1391
- html += renderAgentSection(fm);
1400
+ /**
1401
+ * Render element detail from pre-parsed structured JSON.
1402
+ * The server has already validated and parsed the YAML — no client-side
1403
+ * re-parsing needed. Uses the same rendering helpers as renderDetailView.
1404
+ */
1405
+ function renderStructuredDetail(data) {
1406
+ const { metadata: fm, body, type } = data;
1407
+ if (!fm || typeof fm !== 'object') {
1408
+ return `<pre class="element-source"><code class="element-code">${escapeHtml(data.raw || JSON.stringify(data))}</code></pre>`;
1409
+ }
1410
+
1411
+ if (type === 'memory') {
1412
+ return renderMemoryView(data.raw);
1392
1413
  }
1393
1414
 
1394
- // ── Ensemble fields ──
1395
- if (type === 'ensemble') {
1396
- html += renderEnsembleSection(fm);
1415
+ let html = '';
1416
+
1417
+ if (data.validation?.status === 'warn') {
1418
+ html += `<div class="detail-validation-warn">Security scan: ${escapeHtml(data.validation?.reason || 'warning')}</div>`;
1419
+ }
1420
+
1421
+ html += renderCommonMetadata(fm, type);
1422
+ html += renderTypeSpecificSections(fm, type);
1423
+ html += renderDetailExtra(fm, body || '');
1424
+
1425
+ return html || `<pre class="element-source"><code class="element-code">${escapeHtml(data.raw || '')}</code></pre>`;
1426
+ }
1427
+
1428
+ function renderDetailView(content, type) { // NOSONAR - sequential independent metadata sections; complexity score is inflated by &&-guards on array existence checks
1429
+ if (type === 'memory') {
1430
+ // Portfolio memories are pure YAML — jsyaml.load succeeds.
1431
+ // Collection memories are markdown-with-frontmatter — jsyaml.load throws on the
1432
+ // second `---` document marker, so we fall through to the standard renderer below.
1433
+ try {
1434
+ const parsed = safeParseYaml(content);
1435
+ if (parsed && typeof parsed === 'object') return renderMemoryView(content);
1436
+ } catch { /* not pure YAML — fall through */ }
1397
1437
  }
1398
1438
 
1399
- // ── Catch-all + body ──
1439
+ const { frontmatter: fm, body } = parseFrontmatter(content);
1440
+ let html = '';
1441
+
1442
+ html += renderCommonMetadata(fm, type);
1443
+ html += renderTypeSpecificSections(fm, type);
1400
1444
  html += renderDetailExtra(fm, body);
1401
1445
 
1402
1446
  return html || `<pre class="element-source"><code class="element-code">${escapeHtml(content)}</code></pre>`;
@@ -1865,6 +1909,8 @@ function safeParseYaml(content) {
1865
1909
  b.classList.toggle('active', on);
1866
1910
  b.setAttribute('aria-pressed', on);
1867
1911
  });
1912
+ renderTypeFilters();
1913
+ renderTopicFilters();
1868
1914
  applyFilters();
1869
1915
  });
1870
1916
  }
@@ -1872,6 +1918,48 @@ function safeParseYaml(content) {
1872
1918
  // Portfolio button
1873
1919
  document.getElementById('btn-portfolio')?.addEventListener('click', loadLocalPortfolio);
1874
1920
 
1921
+ // ── Tab switching ─────────────────────────────────────────────────────────
1922
+ const consoleTabs = document.getElementById('console-tabs');
1923
+ const tabInits = { logs: false, metrics: false, permissions: false };
1924
+
1925
+ if (consoleTabs) {
1926
+ consoleTabs.addEventListener('click', (e) => {
1927
+ const btn = e.target.closest('.console-tab');
1928
+ if (!btn) return;
1929
+ const tab = btn.dataset.tab;
1930
+ if (!tab) return;
1931
+
1932
+ // Update active tab button
1933
+ consoleTabs.querySelectorAll('.console-tab').forEach(b => b.classList.remove('active'));
1934
+ btn.classList.add('active');
1935
+
1936
+ // Show/hide panels
1937
+ document.querySelectorAll('.tab-panel').forEach(p => {
1938
+ p.hidden = p.id !== 'tab-' + tab;
1939
+ if (p.id === 'tab-' + tab) p.classList.add('active');
1940
+ else p.classList.remove('active');
1941
+ });
1942
+
1943
+ lazyInitTab(tab, tabInits);
1944
+ });
1945
+ }
1946
+
1947
+ function lazyInitTab(tab, tabInits) {
1948
+ const dc = globalThis.DollhouseConsole;
1949
+ if (tab === 'logs' && dc?.logs) {
1950
+ if (!tabInits.logs) { tabInits.logs = true; dc.logs.init(); }
1951
+ else if (dc.logs.refresh) { dc.logs.refresh(); }
1952
+ }
1953
+ if (tab === 'metrics' && dc?.metrics) {
1954
+ if (!tabInits.metrics) { tabInits.metrics = true; dc.metrics.init(); }
1955
+ else if (dc.metrics.refresh) { dc.metrics.refresh(); }
1956
+ }
1957
+ if (tab === 'permissions' && dc?.permissions) {
1958
+ if (!tabInits.permissions) { tabInits.permissions = true; dc.permissions.init(); }
1959
+ else if (dc.permissions.refresh) { dc.permissions.refresh(); }
1960
+ }
1961
+ }
1962
+
1875
1963
  init();
1876
1964
  });
1877
1965