@dollhousemcp/mcp-server 2.0.17 → 2.0.19

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 (78) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/elements/BaseElement.d.ts +1 -0
  3. package/dist/elements/BaseElement.d.ts.map +1 -1
  4. package/dist/elements/BaseElement.js +7 -1
  5. package/dist/elements/agents/AgentManager.js +2 -2
  6. package/dist/elements/base/ElementFileOperations.js +2 -2
  7. package/dist/elements/ensembles/EnsembleManager.js +3 -3
  8. package/dist/elements/memories/MemoryManager.js +2 -2
  9. package/dist/elements/skills/SkillManager.js +2 -2
  10. package/dist/elements/templates/TemplateManager.js +2 -2
  11. package/dist/generated/version.d.ts +2 -2
  12. package/dist/generated/version.js +3 -3
  13. package/dist/handlers/ElementCRUDHandler.d.ts +30 -0
  14. package/dist/handlers/ElementCRUDHandler.d.ts.map +1 -1
  15. package/dist/handlers/ElementCRUDHandler.js +142 -2
  16. package/dist/handlers/mcp-aql/MCPAQLHandler.d.ts +4 -0
  17. package/dist/handlers/mcp-aql/MCPAQLHandler.d.ts.map +1 -1
  18. package/dist/handlers/mcp-aql/MCPAQLHandler.js +132 -14
  19. package/dist/handlers/mcp-aql/OperationRouter.d.ts.map +1 -1
  20. package/dist/handlers/mcp-aql/OperationRouter.js +6 -1
  21. package/dist/handlers/mcp-aql/OperationSchema.d.ts.map +1 -1
  22. package/dist/handlers/mcp-aql/OperationSchema.js +17 -1
  23. package/dist/handlers/mcp-aql/policies/AgentToolPolicyTranslator.d.ts.map +1 -1
  24. package/dist/handlers/mcp-aql/policies/AgentToolPolicyTranslator.js +2 -1
  25. package/dist/handlers/mcp-aql/policies/ElementPolicies.d.ts +9 -1
  26. package/dist/handlers/mcp-aql/policies/ElementPolicies.d.ts.map +1 -1
  27. package/dist/handlers/mcp-aql/policies/ElementPolicies.js +64 -4
  28. package/dist/handlers/mcp-aql/policies/OperationPolicies.d.ts.map +1 -1
  29. package/dist/handlers/mcp-aql/policies/OperationPolicies.js +6 -1
  30. package/dist/handlers/mcp-aql/policies/ToolClassification.d.ts.map +1 -1
  31. package/dist/handlers/mcp-aql/policies/ToolClassification.js +2 -1
  32. package/dist/handlers/strategies/AgentActivationStrategy.d.ts.map +1 -1
  33. package/dist/handlers/strategies/AgentActivationStrategy.js +5 -1
  34. package/dist/handlers/strategies/BaseActivationStrategy.d.ts +1 -0
  35. package/dist/handlers/strategies/BaseActivationStrategy.d.ts.map +1 -1
  36. package/dist/handlers/strategies/BaseActivationStrategy.js +15 -1
  37. package/dist/handlers/strategies/EnsembleActivationStrategy.d.ts.map +1 -1
  38. package/dist/handlers/strategies/EnsembleActivationStrategy.js +5 -1
  39. package/dist/handlers/strategies/MemoryActivationStrategy.d.ts.map +1 -1
  40. package/dist/handlers/strategies/MemoryActivationStrategy.js +5 -1
  41. package/dist/handlers/strategies/PersonaActivationStrategy.d.ts.map +1 -1
  42. package/dist/handlers/strategies/PersonaActivationStrategy.js +5 -1
  43. package/dist/handlers/strategies/SkillActivationStrategy.d.ts.map +1 -1
  44. package/dist/handlers/strategies/SkillActivationStrategy.js +5 -1
  45. package/dist/handlers/strategies/TemplateActivationStrategy.d.ts.map +1 -1
  46. package/dist/handlers/strategies/TemplateActivationStrategy.js +7 -2
  47. package/dist/persona/PersonaElement.js +2 -2
  48. package/dist/server/tools/MCPAQLTools.js +2 -1
  49. package/dist/services/SerializationService.d.ts.map +1 -1
  50. package/dist/services/SerializationService.js +7 -1
  51. package/dist/types/elements/IElement.d.ts +9 -0
  52. package/dist/types/elements/IElement.d.ts.map +1 -1
  53. package/dist/types/elements/IElement.js +1 -1
  54. package/dist/web/console/IngestRoutes.d.ts +6 -0
  55. package/dist/web/console/IngestRoutes.d.ts.map +1 -1
  56. package/dist/web/console/IngestRoutes.js +38 -9
  57. package/dist/web/console/LeaderElection.d.ts +39 -0
  58. package/dist/web/console/LeaderElection.d.ts.map +1 -1
  59. package/dist/web/console/LeaderElection.js +147 -29
  60. package/dist/web/console/LeaderForwardingSink.d.ts.map +1 -1
  61. package/dist/web/console/LeaderForwardingSink.js +5 -1
  62. package/dist/web/console/PromotionManager.d.ts.map +1 -1
  63. package/dist/web/console/PromotionManager.js +3 -11
  64. package/dist/web/public/app.js +62 -1
  65. package/dist/web/public/index.html +19 -17
  66. package/dist/web/public/permissions.css +190 -2
  67. package/dist/web/public/permissions.js +171 -30
  68. package/dist/web/public/sessions.js +111 -0
  69. package/dist/web/public/setup.js +131 -60
  70. package/dist/web/routes/permissionRoutes.d.ts.map +1 -1
  71. package/dist/web/routes/permissionRoutes.js +77 -5
  72. package/dist/web/routes/setupRoutes.d.ts.map +1 -1
  73. package/dist/web/routes/setupRoutes.js +16 -2
  74. package/dist/web/server.d.ts.map +1 -1
  75. package/dist/web/server.js +12 -10
  76. package/package.json +1 -1
  77. package/scripts/pretooluse-dollhouse.sh +39 -1
  78. package/server.json +2 -2
@@ -1978,12 +1978,70 @@ globalThis.DollhouseConsoleUI.clearBanner = function(bannerId) {
1978
1978
 
1979
1979
  const TAB_KEY = 'dollhousemcp-active-tab';
1980
1980
  const SETUP_SEEN_KEY = 'dollhousemcp-setup-seen';
1981
+ const FORCED_RELOAD_KEY = 'dollhousemcp-last-forced-reload';
1981
1982
  // Server version injected at request time — used to show Setup tab once per version
1982
1983
  // so upgraders automatically see it on each new release (not just first-ever visit).
1983
1984
  // Validate format (semver-like) before trusting the value; malformed falls back to
1984
1985
  // 'unknown' which safely triggers setup on every load rather than silently skipping.
1985
1986
  const _rawVersion = document.querySelector('meta[name="dollhouse-server-version"]')?.content || '';
1986
1987
  const currentServerVersion = /^\d+\.\d+\.\d+/.test(_rawVersion) ? _rawVersion : 'unknown';
1988
+ const _rawAssetVersion = document.querySelector('meta[name="dollhouse-console-asset-version"]')?.content || '';
1989
+ const currentAssetVersion = /^\d+\.\d+\.\d+/.test(_rawAssetVersion) ? _rawAssetVersion : currentServerVersion;
1990
+ let forcedReloadInFlight = false;
1991
+
1992
+ function normalizeReloadVersion(version) {
1993
+ return typeof version === 'string' && /^\d+\.\d+\.\d+/.test(version)
1994
+ ? version
1995
+ : (currentAssetVersion || currentServerVersion || 'unknown');
1996
+ }
1997
+
1998
+ function shouldThrottleForcedReload(targetVersion) {
1999
+ try {
2000
+ const raw = sessionStorage.getItem(FORCED_RELOAD_KEY);
2001
+ if (!raw) return false;
2002
+ const parsed = JSON.parse(raw);
2003
+ return parsed
2004
+ && parsed.version === targetVersion
2005
+ && typeof parsed.at === 'number'
2006
+ && Date.now() - parsed.at < 60_000;
2007
+ } catch {
2008
+ return false;
2009
+ }
2010
+ }
2011
+
2012
+ function rememberForcedReload(targetVersion, reason) {
2013
+ try {
2014
+ sessionStorage.setItem(FORCED_RELOAD_KEY, JSON.stringify({
2015
+ version: targetVersion,
2016
+ reason: reason || 'manual',
2017
+ at: Date.now(),
2018
+ }));
2019
+ } catch {
2020
+ // Ignore storage failures — reload still proceeds.
2021
+ }
2022
+ }
2023
+
2024
+ function buildCacheBustedConsoleUrl(targetVersion, reason) {
2025
+ const url = new URL(globalThis.location.href);
2026
+ url.searchParams.set('dollhouse_bust', targetVersion + '-' + Date.now());
2027
+ url.searchParams.set('dollhouse_asset_version', targetVersion);
2028
+ if (reason) {
2029
+ url.searchParams.set('dollhouse_reload_reason', reason);
2030
+ }
2031
+ return url.toString();
2032
+ }
2033
+
2034
+ function forceConsoleReload(reason, targetVersion) {
2035
+ const normalizedTargetVersion = normalizeReloadVersion(targetVersion);
2036
+ if (forcedReloadInFlight || shouldThrottleForcedReload(normalizedTargetVersion)) {
2037
+ return false;
2038
+ }
2039
+ forcedReloadInFlight = true;
2040
+ rememberForcedReload(normalizedTargetVersion, reason);
2041
+ const reloadUrl = buildCacheBustedConsoleUrl(normalizedTargetVersion, reason);
2042
+ globalThis.location.replace(reloadUrl);
2043
+ return true;
2044
+ }
1987
2045
 
1988
2046
  // Determine which tab to show on load:
1989
2047
  // 1. URL hash (deep link)
@@ -2022,6 +2080,9 @@ globalThis.DollhouseConsoleUI.clearBanner = function(bannerId) {
2022
2080
  // Expose for other scripts (logs.js, metrics.js, permissions.js)
2023
2081
  globalThis.DollhouseConsole = globalThis.DollhouseConsole || {};
2024
2082
  globalThis.DollhouseConsole.getUrlParams = () => getTabAndParams().params;
2083
+ globalThis.DollhouseConsole.currentServerVersion = currentServerVersion;
2084
+ globalThis.DollhouseConsole.currentAssetVersion = currentAssetVersion;
2085
+ globalThis.DollhouseConsole.forceReload = forceConsoleReload;
2025
2086
 
2026
2087
  /**
2027
2088
  * Apply URL params to the portfolio tab.
@@ -2175,7 +2236,7 @@ globalThis.DollhouseConsoleUI.clearBanner = function(bannerId) {
2175
2236
  toast.innerHTML = 'Console session token changed\u2009\u2014\u2009'
2176
2237
  + '<button style="background:#fff;color:#b91c1c;border:none;padding:6px 16px;'
2177
2238
  + 'border-radius:4px;cursor:pointer;font-weight:600;font-size:14px"'
2178
- + ' onclick="location.reload()">Reload</button>';
2239
+ + ' onclick="window.DollhouseConsole.forceReload(\'session-expired\')">Reload</button>';
2179
2240
  document.body.appendChild(toast);
2180
2241
  });
2181
2242
 
@@ -14,14 +14,16 @@
14
14
  <meta name="dollhouse-console-token" content="{{CONSOLE_TOKEN}}">
15
15
  <!-- Server version — injected at request time for version-aware UI behaviour (e.g. setup-seen per version). -->
16
16
  <meta name="dollhouse-server-version" content="{{DOLLHOUSE_VERSION}}">
17
- <link rel="stylesheet" href="fonts.css">
18
- <link rel="stylesheet" href="styles.css">
19
- <link rel="stylesheet" href="logs.css">
20
- <link rel="stylesheet" href="metrics.css">
21
- <link rel="stylesheet" href="permissions.css">
22
- <link rel="stylesheet" href="sessions.css">
23
- <link rel="stylesheet" href="setup.css">
24
- <link rel="stylesheet" href="security.css">
17
+ <!-- Asset version — injected at request time for cache-busting local CSS/JS/img files. -->
18
+ <meta name="dollhouse-console-asset-version" content="{{DOLLHOUSE_ASSET_VERSION}}">
19
+ <link rel="stylesheet" href="fonts.css?v={{DOLLHOUSE_ASSET_VERSION}}">
20
+ <link rel="stylesheet" href="styles.css?v={{DOLLHOUSE_ASSET_VERSION}}">
21
+ <link rel="stylesheet" href="logs.css?v={{DOLLHOUSE_ASSET_VERSION}}">
22
+ <link rel="stylesheet" href="metrics.css?v={{DOLLHOUSE_ASSET_VERSION}}">
23
+ <link rel="stylesheet" href="permissions.css?v={{DOLLHOUSE_ASSET_VERSION}}">
24
+ <link rel="stylesheet" href="sessions.css?v={{DOLLHOUSE_ASSET_VERSION}}">
25
+ <link rel="stylesheet" href="setup.css?v={{DOLLHOUSE_ASSET_VERSION}}">
26
+ <link rel="stylesheet" href="security.css?v={{DOLLHOUSE_ASSET_VERSION}}">
25
27
  <!-- uPlot for metrics time-series charts -->
26
28
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uplot@1.6.30/dist/uPlot.min.css" integrity="sha384-IfV0B7MIOYuO95kO9G5ySKPz/85zqFNOAs8iy4tkK5zd9izhJAB8b7lHrwYqqmYE" crossorigin="anonymous">
27
29
  </head>
@@ -31,7 +33,7 @@
31
33
 
32
34
  <header class="site-header">
33
35
  <div class="header-brand">
34
- <img src="dollhouse-logo.png" alt="DollhouseMCP" class="header-logo" width="32" height="32">
36
+ <img src="dollhouse-logo.png?v={{DOLLHOUSE_ASSET_VERSION}}" alt="DollhouseMCP" class="header-logo" width="32" height="32">
35
37
  <div class="header-brand-text">
36
38
  <h1 class="site-title">DollhouseMCP</h1>
37
39
  <p class="site-tagline">Management Console</p>
@@ -598,13 +600,13 @@ npm install @dollhousemcp/mcp-server</code></pre>
598
600
  <script src="https://cdn.jsdelivr.net/npm/uplot@1.6.30/dist/uPlot.iife.min.js" integrity="sha384-1NEYi76CBpge3gahk4+X4M4JzdOV3WYq84RnByqYdAd5SdvJBTNCPFh/nsoHfN6i" crossorigin="anonymous"></script>
599
601
  <!-- Console auth helper must load first — it reads the token meta tag and
600
602
  exposes window.DollhouseAuth for all subsequent scripts (#1780). -->
601
- <script src="consoleAuth.js"></script>
602
- <script src="setup.js"></script>
603
- <script src="app.js"></script>
604
- <script src="logs.js"></script>
605
- <script src="metrics.js"></script>
606
- <script src="permissions.js"></script>
607
- <script src="sessions.js"></script>
608
- <script src="security.js"></script>
603
+ <script src="consoleAuth.js?v={{DOLLHOUSE_ASSET_VERSION}}"></script>
604
+ <script src="setup.js?v={{DOLLHOUSE_ASSET_VERSION}}"></script>
605
+ <script src="app.js?v={{DOLLHOUSE_ASSET_VERSION}}"></script>
606
+ <script src="logs.js?v={{DOLLHOUSE_ASSET_VERSION}}"></script>
607
+ <script src="metrics.js?v={{DOLLHOUSE_ASSET_VERSION}}"></script>
608
+ <script src="permissions.js?v={{DOLLHOUSE_ASSET_VERSION}}"></script>
609
+ <script src="sessions.js?v={{DOLLHOUSE_ASSET_VERSION}}"></script>
610
+ <script src="security.js?v={{DOLLHOUSE_ASSET_VERSION}}"></script>
609
611
  </body>
610
612
  </html>
@@ -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);
@@ -217,16 +217,14 @@
217
217
  const selectedSessionId = selectedData?.sessionId;
218
218
  if (elements.length === 0) {
219
219
  list.innerHTML = '<li class="perm-pattern-empty">No active elements with policies</li>';
220
+ renderInvalidPolicySummary('perm-all-invalid-policy-summary', []);
220
221
  return;
221
222
  }
222
223
 
223
- list.innerHTML = elements.map(el => `
224
- <li class="perm-source-item${elementMatchesSelected(el, selectedSessionId) ? ' perm-source-item--selected' : ''}">
225
- <span class="perm-source-type">${esc(el.type)}</span>
226
- <span class="perm-source-name">${esc(el.element_name || el.name || '')}</span>
227
- ${el.description ? `<span style="color:var(--ink-400);font-size:0.75rem;margin-left:auto">${esc(el.description)}</span>` : ''}
228
- </li>
229
- `).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('');
230
228
  }
231
229
 
232
230
  function renderSelectedSessionDetail(selectedData) {
@@ -267,15 +265,10 @@
267
265
  }
268
266
 
269
267
  const elements = selectedData.elements || [];
268
+ renderInvalidPolicySummary('perm-selected-invalid-policy-summary', elements);
270
269
  sourceList.innerHTML = elements.length === 0
271
270
  ? '<li class="perm-pattern-empty">No policy-bearing elements found for this session</li>'
272
- : elements.map(el => `
273
- <li class="perm-source-item perm-source-item--detail">
274
- <span class="perm-source-type">${esc(el.type)}</span>
275
- <span class="perm-source-name">${esc(el.element_name || el.name || '')}</span>
276
- ${el.description ? `<span style="color:var(--ink-400);font-size:0.75rem;margin-left:auto">${esc(el.description)}</span>` : ''}
277
- </li>
278
- `).join('');
271
+ : elements.map(el => renderPolicySourceItem(el, ' perm-source-item--detail')).join('');
279
272
 
280
273
  renderPatternList('perm-selected-deny-list', selectedData.denyRules || [], 'deny');
281
274
  renderPatternList('perm-selected-allow-list', selectedData.allowRules || [], 'allow');
@@ -331,29 +324,152 @@
331
324
  if (latestId === lastDecisionId) return; // no change
332
325
  lastDecisionId = latestId;
333
326
 
334
- const html = decisions.map(d => {
335
- const time = new Date(d.timestamp).toLocaleTimeString();
336
- const toolDisplay = d.tool_name === 'Bash'
337
- ? `Bash: ${esc(truncate(d.command || '', 60))}`
338
- : esc(d.tool_name);
339
-
340
- return `
341
- <div class="perm-feed-row">
342
- <span class="perm-feed-time">${time}</span>
343
- <span class="perm-feed-decision perm-feed-decision--${d.decision}">${d.decision.toUpperCase()}</span>
344
- <span class="perm-feed-tool" title="${esc(d.command || d.tool_name)}">${toolDisplay}</span>
345
- <span class="perm-feed-reason" title="${esc(d.reason || '')}">${esc(d.reason || '')}</span>
346
- </div>
347
- `;
348
- }).join('');
327
+ const html = decisions.map(renderCompactDecisionRow).join('');
349
328
 
350
329
  feed.innerHTML = html;
351
- if (modalFeed) modalFeed.innerHTML = html;
330
+ if (modalFeed) modalFeed.innerHTML = renderAuditModal(decisions);
352
331
  if (modalCount) {
353
332
  modalCount.textContent = `${decisions.length} captured ${decisions.length === 1 ? 'entry' : 'entries'}`;
354
333
  }
355
334
  }
356
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
+
357
473
  function deriveSelectedSessionData(aggregateData, sessionId) {
358
474
  if (!sessionId) return null;
359
475
 
@@ -375,6 +491,8 @@
375
491
  type: element.type,
376
492
  element_name: element.element_name,
377
493
  description: element.description,
494
+ invalidGatekeeperPolicy: !!element.invalidGatekeeperPolicy,
495
+ invalidGatekeeperMessage: element.invalidGatekeeperMessage,
378
496
  };
379
497
  }),
380
498
  permissionPromptActive: !!aggregateData?.permissionPromptActive,
@@ -501,6 +619,7 @@
501
619
  <div>
502
620
  <div class="perm-selected-title" id="perm-selected-title">Selected Session</div>
503
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>
504
623
  </div>
505
624
  <span class="perm-selected-badge" id="perm-selected-badge" hidden>Persisted Policy State (Debug Info)</span>
506
625
  </div>
@@ -544,6 +663,7 @@
544
663
  <div>
545
664
  <div class="perm-selected-title">All Sessions</div>
546
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>
547
667
  </div>
548
668
  </div>
549
669
 
@@ -697,4 +817,25 @@
697
817
  return '<span id="perm-all-sessions-advisory" class="perm-inline-advisory" hidden></span>';
698
818
  }
699
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
+
700
841
  })();