@dollhousemcp/mcp-server 2.0.16 → 2.0.18

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 (74) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md.backup +18 -0
  3. package/dist/elements/BaseElement.d.ts +1 -0
  4. package/dist/elements/BaseElement.d.ts.map +1 -1
  5. package/dist/elements/BaseElement.js +7 -1
  6. package/dist/elements/agents/AgentManager.js +2 -2
  7. package/dist/elements/base/BaseElementManager.d.ts.map +1 -1
  8. package/dist/elements/base/BaseElementManager.js +17 -1
  9. package/dist/elements/base/ElementFileOperations.js +2 -2
  10. package/dist/elements/ensembles/EnsembleManager.js +3 -3
  11. package/dist/elements/memories/MemoryManager.d.ts.map +1 -1
  12. package/dist/elements/memories/MemoryManager.js +14 -3
  13. package/dist/elements/skills/SkillManager.js +2 -2
  14. package/dist/elements/templates/TemplateManager.js +2 -2
  15. package/dist/generated/version.d.ts +2 -2
  16. package/dist/generated/version.js +3 -3
  17. package/dist/handlers/ElementCRUDHandler.d.ts.map +1 -1
  18. package/dist/handlers/ElementCRUDHandler.js +3 -2
  19. package/dist/handlers/element-crud/createElement.d.ts.map +1 -1
  20. package/dist/handlers/element-crud/createElement.js +6 -2
  21. package/dist/handlers/element-crud/editElement.d.ts.map +1 -1
  22. package/dist/handlers/element-crud/editElement.js +6 -2
  23. package/dist/handlers/element-crud/helpers.d.ts +2 -0
  24. package/dist/handlers/element-crud/helpers.d.ts.map +1 -1
  25. package/dist/handlers/element-crud/helpers.js +21 -2
  26. package/dist/handlers/mcp-aql/IntrospectionResolver.d.ts.map +1 -1
  27. package/dist/handlers/mcp-aql/IntrospectionResolver.js +34 -7
  28. package/dist/handlers/mcp-aql/MCPAQLHandler.d.ts +1 -0
  29. package/dist/handlers/mcp-aql/MCPAQLHandler.d.ts.map +1 -1
  30. package/dist/handlers/mcp-aql/MCPAQLHandler.js +50 -14
  31. package/dist/handlers/mcp-aql/OperationSchema.d.ts.map +1 -1
  32. package/dist/handlers/mcp-aql/OperationSchema.js +3 -2
  33. package/dist/handlers/mcp-aql/evaluatePermission.d.ts.map +1 -1
  34. package/dist/handlers/mcp-aql/evaluatePermission.js +2 -1
  35. package/dist/handlers/mcp-aql/policies/ElementPolicies.d.ts +17 -1
  36. package/dist/handlers/mcp-aql/policies/ElementPolicies.d.ts.map +1 -1
  37. package/dist/handlers/mcp-aql/policies/ElementPolicies.js +88 -4
  38. package/dist/handlers/strategies/AgentActivationStrategy.d.ts.map +1 -1
  39. package/dist/handlers/strategies/AgentActivationStrategy.js +5 -1
  40. package/dist/handlers/strategies/BaseActivationStrategy.d.ts +1 -0
  41. package/dist/handlers/strategies/BaseActivationStrategy.d.ts.map +1 -1
  42. package/dist/handlers/strategies/BaseActivationStrategy.js +15 -1
  43. package/dist/handlers/strategies/EnsembleActivationStrategy.d.ts.map +1 -1
  44. package/dist/handlers/strategies/EnsembleActivationStrategy.js +5 -1
  45. package/dist/handlers/strategies/MemoryActivationStrategy.d.ts.map +1 -1
  46. package/dist/handlers/strategies/MemoryActivationStrategy.js +5 -1
  47. package/dist/handlers/strategies/PersonaActivationStrategy.d.ts.map +1 -1
  48. package/dist/handlers/strategies/PersonaActivationStrategy.js +5 -1
  49. package/dist/handlers/strategies/SkillActivationStrategy.d.ts.map +1 -1
  50. package/dist/handlers/strategies/SkillActivationStrategy.js +5 -1
  51. package/dist/handlers/strategies/TemplateActivationStrategy.d.ts.map +1 -1
  52. package/dist/handlers/strategies/TemplateActivationStrategy.js +7 -2
  53. package/dist/persona/PersonaElement.js +2 -2
  54. package/dist/services/SerializationService.d.ts.map +1 -1
  55. package/dist/services/SerializationService.js +7 -1
  56. package/dist/types/elements/IElement.d.ts +9 -0
  57. package/dist/types/elements/IElement.d.ts.map +1 -1
  58. package/dist/types/elements/IElement.js +1 -1
  59. package/dist/utils/permissionHooks.d.ts +39 -3
  60. package/dist/utils/permissionHooks.d.ts.map +1 -1
  61. package/dist/utils/permissionHooks.js +651 -74
  62. package/dist/web/public/permissions.css +190 -2
  63. package/dist/web/public/permissions.js +209 -56
  64. package/dist/web/public/setup.js +452 -108
  65. package/dist/web/routes/permissionRoutes.d.ts.map +1 -1
  66. package/dist/web/routes/permissionRoutes.js +108 -17
  67. package/dist/web/routes/setupRoutes.d.ts +1 -0
  68. package/dist/web/routes/setupRoutes.d.ts.map +1 -1
  69. package/dist/web/routes/setupRoutes.js +128 -42
  70. package/package.json +3 -1
  71. package/scripts/pretooluse-dollhouse.sh +39 -1
  72. package/scripts/pretooluse-vscode.sh +163 -0
  73. package/scripts/pretooluse-windsurf.sh +166 -4
  74. package/server.json +2 -2
@@ -355,9 +355,10 @@
355
355
  }
356
356
 
357
357
  .perm-feed--modal {
358
- height: calc(100vh - 11rem);
358
+ height: 100%;
359
359
  max-height: none;
360
360
  min-height: 0;
361
+ overflow: auto;
361
362
  }
362
363
 
363
364
  .perm-feed-empty {
@@ -398,6 +399,16 @@
398
399
  .perm-selected-grid {
399
400
  grid-template-columns: 1fr;
400
401
  }
402
+
403
+ .perm-audit-summary-row {
404
+ grid-template-columns: 1fr;
405
+ gap: 0.45rem;
406
+ }
407
+
408
+ .perm-audit-detail-row {
409
+ grid-template-columns: 1fr;
410
+ gap: 0.25rem;
411
+ }
401
412
  }
402
413
 
403
414
  .perm-feed-tool {
@@ -420,6 +431,133 @@
420
431
  width: min(82rem, 100%);
421
432
  }
422
433
 
434
+ .perm-audit-modal .modal-body {
435
+ display: flex;
436
+ min-height: 0;
437
+ padding: 0;
438
+ }
439
+
440
+ .perm-audit-modal .modal-meta {
441
+ row-gap: 0.35rem;
442
+ }
443
+
444
+ .perm-audit-modal .perm-feed--modal {
445
+ padding: 0.875rem 1rem 1rem;
446
+ }
447
+
448
+ .perm-audit-entry {
449
+ border: 1px solid var(--ink-100, #e2e8f0);
450
+ border-radius: 0.75rem;
451
+ background: var(--paper-strong, #fff);
452
+ margin-bottom: 0.75rem;
453
+ overflow: hidden;
454
+ }
455
+
456
+ .perm-audit-entry:last-child {
457
+ margin-bottom: 0;
458
+ }
459
+
460
+ .perm-audit-entry[open] {
461
+ box-shadow: 0 10px 24px color-mix(in srgb, var(--ink-900, #0f172a) 10%, transparent);
462
+ }
463
+
464
+ .perm-audit-summary-row {
465
+ display: grid;
466
+ grid-template-columns: minmax(6rem, auto) auto minmax(9rem, auto) minmax(0, 1fr);
467
+ gap: 0.75rem;
468
+ align-items: center;
469
+ padding: 0.875rem 1rem;
470
+ cursor: pointer;
471
+ list-style: none;
472
+ }
473
+
474
+ .perm-audit-summary-row::-webkit-details-marker {
475
+ display: none;
476
+ }
477
+
478
+ .perm-audit-summary-row:hover {
479
+ background: var(--ink-50, #f8fafc);
480
+ }
481
+
482
+ .perm-audit-time-group {
483
+ display: flex;
484
+ flex-direction: column;
485
+ gap: 0.15rem;
486
+ min-width: 0;
487
+ }
488
+
489
+ .perm-audit-time {
490
+ color: var(--ink-800, #1e293b);
491
+ font-size: 0.82rem;
492
+ font-weight: 700;
493
+ }
494
+
495
+ .perm-audit-date {
496
+ color: var(--ink-500, #64748b);
497
+ font-size: 0.72rem;
498
+ }
499
+
500
+ .perm-audit-tool {
501
+ color: var(--ink-800, #1e293b);
502
+ font-weight: 700;
503
+ min-width: 0;
504
+ overflow-wrap: anywhere;
505
+ }
506
+
507
+ .perm-audit-context {
508
+ color: var(--ink-600, #475569);
509
+ min-width: 0;
510
+ overflow-wrap: anywhere;
511
+ }
512
+
513
+ .perm-audit-entry-body {
514
+ border-top: 1px solid var(--ink-100, #e2e8f0);
515
+ padding: 0.95rem 1rem 1rem;
516
+ background: color-mix(in srgb, var(--paper-base, #f8fafc) 82%, white);
517
+ }
518
+
519
+ .perm-audit-reason-block {
520
+ margin-bottom: 0.85rem;
521
+ }
522
+
523
+ .perm-audit-reason-text {
524
+ margin: 0.3rem 0 0;
525
+ color: var(--ink-700, #334155);
526
+ line-height: 1.55;
527
+ }
528
+
529
+ .perm-audit-detail-list {
530
+ display: grid;
531
+ gap: 0.625rem;
532
+ margin: 0;
533
+ }
534
+
535
+ .perm-audit-detail-row {
536
+ display: grid;
537
+ grid-template-columns: minmax(8rem, 11rem) minmax(0, 1fr);
538
+ gap: 0.75rem;
539
+ align-items: start;
540
+ }
541
+
542
+ .perm-audit-meta-label {
543
+ color: var(--ink-500, #64748b);
544
+ font-family: var(--font-mono, 'IBM Plex Mono', monospace);
545
+ font-size: 0.74rem;
546
+ text-transform: uppercase;
547
+ letter-spacing: 0.05em;
548
+ }
549
+
550
+ .perm-audit-meta-value {
551
+ margin: 0;
552
+ color: var(--ink-800, #1e293b);
553
+ overflow-wrap: anywhere;
554
+ }
555
+
556
+ .perm-audit-detail-value--mono {
557
+ font-family: var(--font-mono, 'IBM Plex Mono', monospace);
558
+ font-size: 0.82rem;
559
+ }
560
+
423
561
  /* ── Source Elements ───────────────────────────────────────── */
424
562
 
425
563
  .perm-source-list {
@@ -464,6 +602,29 @@
464
602
  overflow-wrap: anywhere;
465
603
  }
466
604
 
605
+ .perm-source-warning {
606
+ display: inline-flex;
607
+ align-items: center;
608
+ padding: 0.125rem 0.375rem;
609
+ border-radius: 999px;
610
+ background: color-mix(in srgb, #f59e0b 16%, white);
611
+ color: #431407;
612
+ font-size: 0.6875rem;
613
+ font-weight: 700;
614
+ text-transform: uppercase;
615
+ }
616
+
617
+ .perm-inline-warning {
618
+ margin-top: 0.5rem;
619
+ padding: 0.625rem 0.75rem;
620
+ border: 1px solid color-mix(in srgb, #f59e0b 35%, white);
621
+ border-radius: 0.75rem;
622
+ background: color-mix(in srgb, #f59e0b 10%, white);
623
+ color: #431407;
624
+ font-size: 0.8125rem;
625
+ line-height: 1.45;
626
+ }
627
+
467
628
  /* ── Dark Mode ─────────────────────────────────────────────── */
468
629
 
469
630
  [data-theme="dark"] .perm-status-bar,
@@ -527,7 +688,10 @@
527
688
  [data-theme="dark"] .perm-selected-title,
528
689
  [data-theme="dark"] .perm-feed-tool,
529
690
  [data-theme="dark"] .perm-pattern-text,
530
- [data-theme="dark"] .perm-source-name {
691
+ [data-theme="dark"] .perm-source-name,
692
+ [data-theme="dark"] .perm-audit-time,
693
+ [data-theme="dark"] .perm-audit-tool,
694
+ [data-theme="dark"] .perm-audit-meta-value {
531
695
  color: var(--ink-200, #e2e8f0);
532
696
  }
533
697
 
@@ -537,6 +701,20 @@
537
701
  background: color-mix(in srgb, var(--paper) 36%, var(--surface-1));
538
702
  }
539
703
 
704
+ [data-theme="dark"] .perm-audit-entry {
705
+ background: color-mix(in srgb, var(--paper) 22%, var(--surface-1));
706
+ border-color: color-mix(in srgb, var(--line) 88%, transparent);
707
+ }
708
+
709
+ [data-theme="dark"] .perm-audit-summary-row:hover {
710
+ background: color-mix(in srgb, var(--surface-2) 72%, var(--paper-strong));
711
+ }
712
+
713
+ [data-theme="dark"] .perm-audit-entry-body {
714
+ background: color-mix(in srgb, var(--surface-1) 82%, var(--paper-strong));
715
+ border-color: color-mix(in srgb, var(--line) 92%, transparent);
716
+ }
717
+
540
718
  [data-theme="dark"] .perm-source-item,
541
719
  [data-theme="dark"] .perm-pattern-item,
542
720
  [data-theme="dark"] .perm-feed-row {
@@ -548,6 +726,16 @@
548
726
  background: color-mix(in srgb, var(--surface-2) 72%, var(--paper-strong));
549
727
  }
550
728
 
729
+ [data-theme="dark"] .perm-audit-context,
730
+ [data-theme="dark"] .perm-audit-reason-text {
731
+ color: var(--ink-300, #cbd5e1);
732
+ }
733
+
734
+ [data-theme="dark"] .perm-audit-date,
735
+ [data-theme="dark"] .perm-audit-meta-label {
736
+ color: var(--ink-500, #7b93a7);
737
+ }
738
+
551
739
  [data-theme="dark"] .perm-source-type {
552
740
  background: color-mix(in srgb, var(--surface-2) 90%, var(--paper-strong));
553
741
  color: var(--ink-900, #dce6f2);
@@ -152,12 +152,18 @@
152
152
  hasElements ? `${data.activeElementCount} elements` : 'No ensemble';
153
153
  }
154
154
  if (hookDot && hookLabel) {
155
- const hasPatterns = (data.denyPatterns?.length || 0)
155
+ const hasExternalRules = (data.denyPatterns?.length || 0)
156
156
  + (data.allowPatterns?.length || 0)
157
157
  + (data.confirmPatterns?.length || 0) > 0;
158
- if (!hasPatterns) {
158
+ const hasAnyRules = (data.denyRules?.length || 0)
159
+ + (data.allowRules?.length || 0)
160
+ + (data.confirmRules?.length || 0) > 0;
161
+ if (!hasAnyRules) {
159
162
  hookDot.dataset.status = 'inactive';
160
163
  hookLabel.textContent = 'No policies';
164
+ } else if (!hasExternalRules) {
165
+ hookDot.dataset.status = 'active';
166
+ hookLabel.textContent = 'MCP-AQL policies active';
161
167
  } else if (data.permissionPromptActive) {
162
168
  hookDot.dataset.status = 'active';
163
169
  hookLabel.textContent = 'Prompt tool active';
@@ -175,9 +181,9 @@
175
181
  }
176
182
 
177
183
  function renderSummaryStats(data) {
178
- setText('perm-stat-deny-count', getAggregatePatterns(data, 'denyPatterns').length);
179
- setText('perm-stat-allow-count', getAggregatePatterns(data, 'allowPatterns').length);
180
- setText('perm-stat-confirm-count', getAggregatePatterns(data, 'confirmPatterns').length);
184
+ setText('perm-stat-deny-count', getAggregateRules(data, 'denyRules').length);
185
+ setText('perm-stat-allow-count', getAggregateRules(data, 'allowRules').length);
186
+ setText('perm-stat-confirm-count', getAggregateRules(data, 'confirmRules').length);
181
187
  setText('perm-stat-decisions', data.recentDecisions?.length || 0);
182
188
 
183
189
  // Decision breakdown
@@ -211,16 +217,14 @@
211
217
  const selectedSessionId = selectedData?.sessionId;
212
218
  if (elements.length === 0) {
213
219
  list.innerHTML = '<li class="perm-pattern-empty">No active elements with policies</li>';
220
+ renderInvalidPolicySummary('perm-all-invalid-policy-summary', []);
214
221
  return;
215
222
  }
216
223
 
217
- list.innerHTML = elements.map(el => `
218
- <li class="perm-source-item${elementMatchesSelected(el, selectedSessionId) ? ' perm-source-item--selected' : ''}">
219
- <span class="perm-source-type">${esc(el.type)}</span>
220
- <span class="perm-source-name">${esc(el.element_name || el.name || '')}</span>
221
- ${el.description ? `<span style="color:var(--ink-400);font-size:0.75rem;margin-left:auto">${esc(el.description)}</span>` : ''}
222
- </li>
223
- `).join('');
224
+ renderInvalidPolicySummary('perm-all-invalid-policy-summary', elements);
225
+ list.innerHTML = elements.map(el =>
226
+ renderPolicySourceItem(el, elementMatchesSelected(el, selectedSessionId) ? ' perm-source-item--selected' : '')
227
+ ).join('');
224
228
  }
225
229
 
226
230
  function renderSelectedSessionDetail(selectedData) {
@@ -261,31 +265,26 @@
261
265
  }
262
266
 
263
267
  const elements = selectedData.elements || [];
268
+ renderInvalidPolicySummary('perm-selected-invalid-policy-summary', elements);
264
269
  sourceList.innerHTML = elements.length === 0
265
270
  ? '<li class="perm-pattern-empty">No policy-bearing elements found for this session</li>'
266
- : elements.map(el => `
267
- <li class="perm-source-item perm-source-item--detail">
268
- <span class="perm-source-type">${esc(el.type)}</span>
269
- <span class="perm-source-name">${esc(el.element_name || el.name || '')}</span>
270
- ${el.description ? `<span style="color:var(--ink-400);font-size:0.75rem;margin-left:auto">${esc(el.description)}</span>` : ''}
271
- </li>
272
- `).join('');
271
+ : elements.map(el => renderPolicySourceItem(el, ' perm-source-item--detail')).join('');
273
272
 
274
- renderPatternList('perm-selected-deny-list', selectedData.denyPatterns || [], 'deny');
275
- renderPatternList('perm-selected-allow-list', selectedData.allowPatterns || [], 'allow');
276
- renderPatternList('perm-selected-confirm-list', selectedData.confirmPatterns || [], 'confirm');
273
+ renderPatternList('perm-selected-deny-list', selectedData.denyRules || [], 'deny');
274
+ renderPatternList('perm-selected-allow-list', selectedData.allowRules || [], 'allow');
275
+ renderPatternList('perm-selected-confirm-list', selectedData.confirmRules || [], 'confirm');
277
276
  }
278
277
 
279
278
  function renderDenyPatterns(data) {
280
- renderPatternList('perm-deny-list', getAggregatePatterns(data, 'denyPatterns'), 'deny');
279
+ renderPatternList('perm-deny-list', getAggregateRules(data, 'denyRules'), 'deny');
281
280
  }
282
281
 
283
282
  function renderAllowPatterns(data) {
284
- renderPatternList('perm-allow-list', getAggregatePatterns(data, 'allowPatterns'), 'allow');
283
+ renderPatternList('perm-allow-list', getAggregateRules(data, 'allowRules'), 'allow');
285
284
  }
286
285
 
287
286
  function renderConfirmPatterns(data) {
288
- renderPatternList('perm-confirm-list', getAggregatePatterns(data, 'confirmPatterns'), 'confirm');
287
+ renderPatternList('perm-confirm-list', getAggregateRules(data, 'confirmRules'), 'confirm');
289
288
  }
290
289
 
291
290
  function renderPatternList(elementId, patterns, type) {
@@ -293,7 +292,7 @@
293
292
  if (!list) return;
294
293
 
295
294
  if (patterns.length === 0) {
296
- list.innerHTML = `<li class="perm-pattern-empty">No ${type} patterns active</li>`;
295
+ list.innerHTML = `<li class="perm-pattern-empty">No ${type} rules active</li>`;
297
296
  return;
298
297
  }
299
298
 
@@ -325,29 +324,152 @@
325
324
  if (latestId === lastDecisionId) return; // no change
326
325
  lastDecisionId = latestId;
327
326
 
328
- const html = decisions.map(d => {
329
- const time = new Date(d.timestamp).toLocaleTimeString();
330
- const toolDisplay = d.tool_name === 'Bash'
331
- ? `Bash: ${esc(truncate(d.command || '', 60))}`
332
- : esc(d.tool_name);
333
-
334
- return `
335
- <div class="perm-feed-row">
336
- <span class="perm-feed-time">${time}</span>
337
- <span class="perm-feed-decision perm-feed-decision--${d.decision}">${d.decision.toUpperCase()}</span>
338
- <span class="perm-feed-tool" title="${esc(d.command || d.tool_name)}">${toolDisplay}</span>
339
- <span class="perm-feed-reason" title="${esc(d.reason || '')}">${esc(d.reason || '')}</span>
340
- </div>
341
- `;
342
- }).join('');
327
+ const html = decisions.map(renderCompactDecisionRow).join('');
343
328
 
344
329
  feed.innerHTML = html;
345
- if (modalFeed) modalFeed.innerHTML = html;
330
+ if (modalFeed) modalFeed.innerHTML = renderAuditModal(decisions);
346
331
  if (modalCount) {
347
332
  modalCount.textContent = `${decisions.length} captured ${decisions.length === 1 ? 'entry' : 'entries'}`;
348
333
  }
349
334
  }
350
335
 
336
+ function renderCompactDecisionRow(decision) {
337
+ const toolDisplay = decision.tool_name === 'Bash'
338
+ ? `Bash: ${esc(truncate(decision.command || '', 60))}`
339
+ : esc(decision.tool_name);
340
+
341
+ return `
342
+ <div class="perm-feed-row">
343
+ <span class="perm-feed-time">${esc(formatShortTime(decision.timestamp))}</span>
344
+ <span class="perm-feed-decision perm-feed-decision--${decision.decision}">${esc(getDecisionLabel(decision.decision))}</span>
345
+ <span class="perm-feed-tool" title="${esc(decision.command || decision.tool_name)}">${toolDisplay}</span>
346
+ <span class="perm-feed-reason" title="${esc(decision.reason || '')}">${esc(decision.reason || '')}</span>
347
+ </div>
348
+ `;
349
+ }
350
+
351
+ function renderAuditModal(decisions) {
352
+ if (!decisions || decisions.length === 0) {
353
+ return '<div class="perm-feed-empty">No permission decisions yet. Waiting for tool calls...</div>';
354
+ }
355
+
356
+ return decisions.map(renderAuditDecisionEntry).join('');
357
+ }
358
+
359
+ function renderAuditDecisionEntry(decision) {
360
+ const compactContext = getCompactContext(decision);
361
+ const detailRows = Array.isArray(decision.details) ? decision.details : [];
362
+ const reasonBlock = decision.reason
363
+ ? `
364
+ <div class="perm-audit-reason-block">
365
+ <div class="perm-audit-meta-label">Reason</div>
366
+ <p class="perm-audit-reason-text">${esc(decision.reason)}</p>
367
+ </div>
368
+ `
369
+ : '';
370
+ const detailList = detailRows.length > 0
371
+ ? `
372
+ <dl class="perm-audit-detail-list">
373
+ ${detailRows.map(detail => `
374
+ <div class="perm-audit-detail-row">
375
+ <dt class="perm-audit-meta-label">${esc(detail.label)}</dt>
376
+ <dd class="perm-audit-meta-value${detail.monospace ? ' perm-audit-detail-value--mono' : ''}">${esc(detail.value)}</dd>
377
+ </div>
378
+ `).join('')}
379
+ <div class="perm-audit-detail-row">
380
+ <dt class="perm-audit-meta-label">Exact Time</dt>
381
+ <dd class="perm-audit-meta-value perm-audit-detail-value--mono">${esc(formatExactTimestamp(decision.timestamp))}</dd>
382
+ </div>
383
+ </dl>
384
+ `
385
+ : `
386
+ <dl class="perm-audit-detail-list">
387
+ <div class="perm-audit-detail-row">
388
+ <dt class="perm-audit-meta-label">Exact Time</dt>
389
+ <dd class="perm-audit-meta-value perm-audit-detail-value--mono">${esc(formatExactTimestamp(decision.timestamp))}</dd>
390
+ </div>
391
+ </dl>
392
+ `;
393
+
394
+ return `
395
+ <details class="perm-audit-entry">
396
+ <summary class="perm-audit-summary-row">
397
+ <span class="perm-audit-time-group">
398
+ <span class="perm-audit-time">${esc(formatShortTime(decision.timestamp))}</span>
399
+ <span class="perm-audit-date">${esc(formatShortDate(decision.timestamp))}</span>
400
+ </span>
401
+ <span class="perm-feed-decision perm-feed-decision--${decision.decision}">${esc(getDecisionLabel(decision.decision))}</span>
402
+ <span class="perm-audit-tool">${esc(decision.tool_name)}</span>
403
+ <span class="perm-audit-context">${esc(compactContext)}</span>
404
+ </summary>
405
+ <div class="perm-audit-entry-body">
406
+ ${reasonBlock}
407
+ ${detailList}
408
+ </div>
409
+ </details>
410
+ `;
411
+ }
412
+
413
+ function formatShortTime(timestamp) {
414
+ return new Date(timestamp).toLocaleTimeString([], {
415
+ hour: 'numeric',
416
+ minute: '2-digit',
417
+ second: '2-digit',
418
+ });
419
+ }
420
+
421
+ function formatShortDate(timestamp) {
422
+ return new Date(timestamp).toLocaleDateString([], {
423
+ month: 'short',
424
+ day: 'numeric',
425
+ });
426
+ }
427
+
428
+ function formatExactTimestamp(timestamp) {
429
+ const date = new Date(timestamp);
430
+ return date.toLocaleString([], {
431
+ year: 'numeric',
432
+ month: 'short',
433
+ day: 'numeric',
434
+ hour: 'numeric',
435
+ minute: '2-digit',
436
+ second: '2-digit',
437
+ timeZoneName: 'short',
438
+ });
439
+ }
440
+
441
+ function getDecisionLabel(decision) {
442
+ return String(decision || '').toUpperCase();
443
+ }
444
+
445
+ function getCompactContext(decision) {
446
+ if (decision.targetLabel && decision.target) {
447
+ return `${decision.targetLabel}: ${truncate(decision.target, 96)}`;
448
+ }
449
+ if (decision.command) {
450
+ return truncate(decision.command, 96);
451
+ }
452
+ return decision.reason || 'No extra context captured';
453
+ }
454
+
455
+ function renderPolicySourceItem(el, extraClass = '') {
456
+ const invalidBadge = el.invalidGatekeeperPolicy
457
+ ? `<span class="perm-source-warning" title="${esc(el.invalidGatekeeperMessage || '')}">policy invalid</span>`
458
+ : '';
459
+ const description = el.description
460
+ ? `<span style="color:var(--ink-400);font-size:0.75rem;margin-left:auto">${esc(el.description)}</span>`
461
+ : '';
462
+
463
+ return `
464
+ <li class="perm-source-item${extraClass}">
465
+ <span class="perm-source-type">${esc(el.type)}</span>
466
+ <span class="perm-source-name">${esc(el.element_name || el.name || '')}</span>
467
+ ${invalidBadge}
468
+ ${description}
469
+ </li>
470
+ `;
471
+ }
472
+
351
473
  function deriveSelectedSessionData(aggregateData, sessionId) {
352
474
  if (!sessionId) return null;
353
475
 
@@ -359,16 +481,18 @@
359
481
  sessionId: sessionId,
360
482
  activeElementCount: elements.length,
361
483
  hasAllowlist: elements.some(function (element) {
362
- return Array.isArray(element.allowPatterns) && element.allowPatterns.length > 0;
484
+ return Array.isArray(element.allowRules) && element.allowRules.length > 0;
363
485
  }),
364
- denyPatterns: flattenElementPatterns(elements, 'denyPatterns'),
365
- allowPatterns: flattenElementPatterns(elements, 'allowPatterns'),
366
- confirmPatterns: flattenElementPatterns(elements, 'confirmPatterns'),
486
+ denyRules: flattenElementPatterns(elements, 'denyRules'),
487
+ allowRules: flattenElementPatterns(elements, 'allowRules'),
488
+ confirmRules: flattenElementPatterns(elements, 'confirmRules'),
367
489
  elements: elements.map(function (element) {
368
490
  return {
369
491
  type: element.type,
370
492
  element_name: element.element_name,
371
493
  description: element.description,
494
+ invalidGatekeeperPolicy: !!element.invalidGatekeeperPolicy,
495
+ invalidGatekeeperMessage: element.invalidGatekeeperMessage,
372
496
  };
373
497
  }),
374
498
  permissionPromptActive: !!aggregateData?.permissionPromptActive,
@@ -388,6 +512,12 @@
388
512
  return Array.from(new Set(combined.concat(perElement)));
389
513
  }
390
514
 
515
+ function getAggregateRules(data, key) {
516
+ const combined = Array.isArray(data && data[key]) ? data[key] : [];
517
+ const perElement = flattenElementPatterns((data && data.elements) || [], key);
518
+ return Array.from(new Set(combined.concat(perElement)));
519
+ }
520
+
391
521
  // ── Dashboard HTML ─────────────────────────────────────────
392
522
 
393
523
  function buildDashboardHTML() {
@@ -448,15 +578,15 @@
448
578
  <div class="perm-stat-grid">
449
579
  <div class="perm-stat">
450
580
  <div class="perm-stat-value perm-stat-value--deny" id="perm-stat-deny-count">0</div>
451
- <div class="perm-stat-label">Deny Patterns</div>
581
+ <div class="perm-stat-label">Deny Rules</div>
452
582
  </div>
453
583
  <div class="perm-stat">
454
584
  <div class="perm-stat-value perm-stat-value--allow" id="perm-stat-allow-count">0</div>
455
- <div class="perm-stat-label">Allow Patterns</div>
585
+ <div class="perm-stat-label">Allow Rules</div>
456
586
  </div>
457
587
  <div class="perm-stat">
458
588
  <div class="perm-stat-value perm-stat-value--ask" id="perm-stat-confirm-count">0</div>
459
- <div class="perm-stat-label">Confirm Patterns</div>
589
+ <div class="perm-stat-label">Confirm Rules</div>
460
590
  </div>
461
591
  <div class="perm-stat">
462
592
  <div class="perm-stat-value" id="perm-stat-decisions">0</div>
@@ -489,6 +619,7 @@
489
619
  <div>
490
620
  <div class="perm-selected-title" id="perm-selected-title">Selected Session</div>
491
621
  <div class="perm-selected-subtitle" id="perm-selected-subtitle"></div>
622
+ <div class="perm-inline-warning" id="perm-selected-invalid-policy-summary" hidden></div>
492
623
  </div>
493
624
  <span class="perm-selected-badge" id="perm-selected-badge" hidden>Persisted Policy State (Debug Info)</span>
494
625
  </div>
@@ -501,19 +632,19 @@
501
632
  </ul>
502
633
  </div>
503
634
  <div class="perm-selected-panel">
504
- <h4 class="perm-selected-panel-title">Deny Patterns</h4>
635
+ <h4 class="perm-selected-panel-title">Deny Rules</h4>
505
636
  <ul class="perm-pattern-list" id="perm-selected-deny-list">
506
637
  <li class="perm-pattern-empty">Loading...</li>
507
638
  </ul>
508
639
  </div>
509
640
  <div class="perm-selected-panel">
510
- <h4 class="perm-selected-panel-title">Allow Patterns</h4>
641
+ <h4 class="perm-selected-panel-title">Allow Rules</h4>
511
642
  <ul class="perm-pattern-list" id="perm-selected-allow-list">
512
643
  <li class="perm-pattern-empty">Loading...</li>
513
644
  </ul>
514
645
  </div>
515
646
  <div class="perm-selected-panel">
516
- <h4 class="perm-selected-panel-title">Confirm Patterns</h4>
647
+ <h4 class="perm-selected-panel-title">Confirm Rules</h4>
517
648
  <ul class="perm-pattern-list" id="perm-selected-confirm-list">
518
649
  <li class="perm-pattern-empty">Loading...</li>
519
650
  </ul>
@@ -531,7 +662,8 @@
531
662
  <div class="perm-selected-header perm-selected-header--compact">
532
663
  <div>
533
664
  <div class="perm-selected-title">All Sessions</div>
534
- <div class="perm-selected-subtitle">${esc('Aggregate policy state across all live and persisted sessions. The decision feed below is currently aggregate, not selection-scoped.')}${dataAdvisoryPlaceholder()}</div>
665
+ <div class="perm-selected-subtitle">${esc('Aggregate policy state across all live and persisted sessions. Rules shown here include both Dollhouse operation policies and external tool restrictions.')}${dataAdvisoryPlaceholder()}</div>
666
+ <div class="perm-inline-warning" id="perm-all-invalid-policy-summary" hidden></div>
535
667
  </div>
536
668
  </div>
537
669
 
@@ -543,19 +675,19 @@
543
675
  </ul>
544
676
  </div>
545
677
  <div class="perm-selected-panel">
546
- <h4 class="perm-selected-panel-title">Deny Patterns</h4>
678
+ <h4 class="perm-selected-panel-title">Deny Rules</h4>
547
679
  <ul class="perm-pattern-list" id="perm-deny-list">
548
680
  <li class="perm-pattern-empty">Loading...</li>
549
681
  </ul>
550
682
  </div>
551
683
  <div class="perm-selected-panel">
552
- <h4 class="perm-selected-panel-title">Allow Patterns</h4>
684
+ <h4 class="perm-selected-panel-title">Allow Rules</h4>
553
685
  <ul class="perm-pattern-list" id="perm-allow-list">
554
686
  <li class="perm-pattern-empty">Loading...</li>
555
687
  </ul>
556
688
  </div>
557
689
  <div class="perm-selected-panel">
558
- <h4 class="perm-selected-panel-title">Confirm Patterns</h4>
690
+ <h4 class="perm-selected-panel-title">Confirm Rules</h4>
559
691
  <ul class="perm-pattern-list" id="perm-confirm-list">
560
692
  <li class="perm-pattern-empty">Loading...</li>
561
693
  </ul>
@@ -685,4 +817,25 @@
685
817
  return '<span id="perm-all-sessions-advisory" class="perm-inline-advisory" hidden></span>';
686
818
  }
687
819
 
820
+ function renderInvalidPolicySummary(elementId, elements) {
821
+ const banner = document.getElementById(elementId);
822
+ if (!banner) return;
823
+
824
+ const invalid = (elements || []).filter(function (element) {
825
+ return !!element.invalidGatekeeperPolicy;
826
+ });
827
+
828
+ if (invalid.length === 0) {
829
+ banner.hidden = true;
830
+ banner.textContent = '';
831
+ return;
832
+ }
833
+
834
+ const names = invalid.map(function (element) {
835
+ return element.element_name || element.name || 'unknown';
836
+ });
837
+ banner.hidden = false;
838
+ banner.textContent = `${invalid.length} active element${invalid.length === 1 ? '' : 's'} ha${invalid.length === 1 ? 's' : 've'} malformed gatekeeper policy. These elements remain active, but that policy is not being enforced: ${names.join(', ')}`;
839
+ }
840
+
688
841
  })();