@dollhousemcp/mcp-server 2.0.24 → 2.0.26
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.
- package/CHANGELOG.md +8 -0
- package/dist/auto-dollhouse/portDiscovery.d.ts.map +1 -1
- package/dist/auto-dollhouse/portDiscovery.js +7 -4
- package/dist/di/Container.d.ts.map +1 -1
- package/dist/di/Container.js +4 -2
- package/dist/generated/version.d.ts +2 -2
- package/dist/generated/version.js +3 -3
- package/dist/handlers/mcp-aql/MCPAQLHandler.d.ts.map +1 -1
- package/dist/handlers/mcp-aql/MCPAQLHandler.js +18 -1
- package/dist/handlers/mcp-aql/OperationRouter.d.ts.map +1 -1
- package/dist/handlers/mcp-aql/OperationRouter.js +6 -1
- package/dist/handlers/mcp-aql/OperationSchema.d.ts.map +1 -1
- package/dist/handlers/mcp-aql/OperationSchema.js +16 -1
- package/dist/handlers/mcp-aql/SchemaDispatcher.d.ts.map +1 -1
- package/dist/handlers/mcp-aql/SchemaDispatcher.js +2 -1
- package/dist/index.js +14 -2
- package/dist/server/tools/BuildInfoTools.d.ts +1 -0
- package/dist/server/tools/BuildInfoTools.d.ts.map +1 -1
- package/dist/server/tools/BuildInfoTools.js +2 -1
- package/dist/server/tools/MCPAQLTools.js +3 -1
- package/dist/services/ActivationStore.d.ts +8 -0
- package/dist/services/ActivationStore.d.ts.map +1 -1
- package/dist/services/ActivationStore.js +28 -29
- package/dist/services/BuildInfoService.d.ts +3 -0
- package/dist/services/BuildInfoService.d.ts.map +1 -1
- package/dist/services/BuildInfoService.js +18 -1
- package/dist/services/sessionIdentity.d.ts +24 -0
- package/dist/services/sessionIdentity.d.ts.map +1 -0
- package/dist/services/sessionIdentity.js +42 -0
- package/dist/utils/permissionAuthority.d.ts +38 -0
- package/dist/utils/permissionAuthority.d.ts.map +1 -0
- package/dist/utils/permissionAuthority.js +341 -0
- package/dist/utils/permissionHooks.d.ts.map +1 -1
- package/dist/utils/permissionHooks.js +10 -1
- package/dist/web/console/UnifiedConsole.d.ts +2 -0
- package/dist/web/console/UnifiedConsole.d.ts.map +1 -1
- package/dist/web/console/UnifiedConsole.js +3 -1
- package/dist/web/portDiscovery.d.ts +7 -0
- package/dist/web/portDiscovery.d.ts.map +1 -1
- package/dist/web/portDiscovery.js +35 -4
- package/dist/web/public/app.js +28 -4
- package/dist/web/public/index.html +2 -0
- package/dist/web/public/permissions.css +456 -0
- package/dist/web/public/permissions.js +629 -12
- package/dist/web/public/sessions.css +119 -0
- package/dist/web/public/sessions.js +95 -9
- package/dist/web/public/setup.js +67 -7
- package/dist/web/public/styles.css +21 -2
- package/dist/web/routes/permissionRoutes.d.ts +4 -1
- package/dist/web/routes/permissionRoutes.d.ts.map +1 -1
- package/dist/web/routes/permissionRoutes.js +118 -6
- package/dist/web/routes/setupRoutes.d.ts +18 -0
- package/dist/web/routes/setupRoutes.d.ts.map +1 -1
- package/dist/web/routes/setupRoutes.js +129 -49
- package/dist/web/server.d.ts +4 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +19 -1
- package/package.json +4 -3
- package/scripts/pretooluse-dollhouse.sh +78 -12
- package/scripts/pretooluse-vscode.sh +6 -9
- package/scripts/pretooluse-windsurf.sh +6 -9
- package/server.json +2 -2
|
@@ -18,6 +18,34 @@
|
|
|
18
18
|
let latestAggregateData = null;
|
|
19
19
|
let latestSelectedData = null;
|
|
20
20
|
let latestPollRequestId = 0;
|
|
21
|
+
const AUTHORITY_MODE_REQUEST_STATES = {
|
|
22
|
+
idle: 'idle',
|
|
23
|
+
saving: 'saving',
|
|
24
|
+
};
|
|
25
|
+
const AUTHORITY_AUTHORITATIVE_HOSTS = new Set(['claude-code']);
|
|
26
|
+
// Authority modes are intentionally phrased in human terms in the UI:
|
|
27
|
+
// - off => host-controlled permissions
|
|
28
|
+
// - shared => both Dollhouse and the host participate
|
|
29
|
+
// - authoritative => Dollhouse is the source of truth
|
|
30
|
+
const authorityUiState = {
|
|
31
|
+
selectedHost: 'claude-code',
|
|
32
|
+
selectedMode: 'shared',
|
|
33
|
+
draftReason: '',
|
|
34
|
+
dirty: false,
|
|
35
|
+
requestState: AUTHORITY_MODE_REQUEST_STATES.idle,
|
|
36
|
+
feedback: '',
|
|
37
|
+
feedbackKind: 'info',
|
|
38
|
+
};
|
|
39
|
+
const AUTHORITY_HOST_META = {
|
|
40
|
+
'claude-code': { label: 'Claude Code', shortLabel: 'CC', tone: 'claude' },
|
|
41
|
+
'codex': { label: 'Codex', shortLabel: 'CX', tone: 'codex' },
|
|
42
|
+
'cursor': { label: 'Cursor', shortLabel: 'CU', tone: 'cursor' },
|
|
43
|
+
'vscode': { label: 'VS Code', shortLabel: 'VS', tone: 'vscode' },
|
|
44
|
+
'windsurf': { label: 'Windsurf', shortLabel: 'WS', tone: 'windsurf' },
|
|
45
|
+
'gemini': { label: 'Gemini CLI', shortLabel: 'GM', tone: 'gemini' },
|
|
46
|
+
'cline': { label: 'Cline', shortLabel: 'CL', tone: 'cline' },
|
|
47
|
+
'lmstudio': { label: 'LM Studio', shortLabel: 'LM', tone: 'lmstudio' },
|
|
48
|
+
};
|
|
21
49
|
|
|
22
50
|
async function fetchPermissionStatus(sessionId) {
|
|
23
51
|
const query = sessionId ? `?sessionId=${encodeURIComponent(sessionId)}` : '';
|
|
@@ -26,6 +54,17 @@
|
|
|
26
54
|
return res.json();
|
|
27
55
|
}
|
|
28
56
|
|
|
57
|
+
async function updatePermissionAuthority(payload) {
|
|
58
|
+
const res = await DollhouseAuth.apiFetch('/api/permissions/authority', {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: { 'Content-Type': 'application/json' },
|
|
61
|
+
body: JSON.stringify(payload),
|
|
62
|
+
});
|
|
63
|
+
const data = await res.json().catch(function () { return {}; });
|
|
64
|
+
if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
|
|
65
|
+
return data;
|
|
66
|
+
}
|
|
67
|
+
|
|
29
68
|
// ── Public API ─────────────────────────────────────────────
|
|
30
69
|
|
|
31
70
|
window.DollhouseConsole = window.DollhouseConsole || {};
|
|
@@ -43,15 +82,31 @@
|
|
|
43
82
|
if (tabs) {
|
|
44
83
|
tabs.addEventListener('click', function (e) {
|
|
45
84
|
const btn = e.target.closest('.console-tab');
|
|
46
|
-
if (btn
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
85
|
+
if (!btn || btn.dataset.tab !== 'permissions') {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const dc = window.DollhouseConsole;
|
|
90
|
+
if (!dc || !dc.permissions) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!initialized) {
|
|
95
|
+
dc.permissions.init();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (dc.permissions.refresh) {
|
|
100
|
+
dc.permissions.refresh();
|
|
52
101
|
}
|
|
53
102
|
});
|
|
54
103
|
}
|
|
104
|
+
|
|
105
|
+
window.addEventListener('dollhouse:policy-debug-visibility-changed', function () {
|
|
106
|
+
if (initialized) {
|
|
107
|
+
renderFromCache();
|
|
108
|
+
}
|
|
109
|
+
});
|
|
55
110
|
});
|
|
56
111
|
|
|
57
112
|
// ── Initialization ─────────────────────────────────────────
|
|
@@ -65,6 +120,7 @@
|
|
|
65
120
|
|
|
66
121
|
root.innerHTML = buildDashboardHTML();
|
|
67
122
|
attachCardToggles();
|
|
123
|
+
attachAuthorityControls();
|
|
68
124
|
poll(); // immediate first fetch
|
|
69
125
|
pollTimer = setInterval(poll, POLL_INTERVAL_MS);
|
|
70
126
|
}
|
|
@@ -87,13 +143,14 @@
|
|
|
87
143
|
return;
|
|
88
144
|
}
|
|
89
145
|
|
|
146
|
+
const visibleAggregateData = getVisibleAggregateData(aggregateData);
|
|
90
147
|
const currentSessionId = window.DollhouseSessions?.getFilterSessionId?.() || '';
|
|
91
|
-
const selectedData = deriveSelectedSessionData(
|
|
148
|
+
const selectedData = deriveSelectedSessionData(visibleAggregateData, currentSessionId);
|
|
92
149
|
|
|
93
150
|
latestAggregateData = aggregateData;
|
|
94
151
|
latestSelectedData = selectedData;
|
|
95
152
|
window.DollhouseSessions?.setPolicySessions?.(aggregateData.knownSessions || []);
|
|
96
|
-
render(
|
|
153
|
+
render(visibleAggregateData, selectedData);
|
|
97
154
|
} catch (err) {
|
|
98
155
|
renderError(err.message);
|
|
99
156
|
}
|
|
@@ -106,15 +163,16 @@
|
|
|
106
163
|
}
|
|
107
164
|
|
|
108
165
|
const sessionId = window.DollhouseSessions?.getFilterSessionId?.() || '';
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
166
|
+
const visibleAggregateData = getVisibleAggregateData(latestAggregateData);
|
|
167
|
+
latestSelectedData = deriveSelectedSessionData(visibleAggregateData, sessionId);
|
|
168
|
+
render(visibleAggregateData, latestSelectedData);
|
|
112
169
|
}
|
|
113
170
|
|
|
114
171
|
// ── Rendering ──────────────────────────────────────────────
|
|
115
172
|
|
|
116
173
|
function render(data, selectedData) {
|
|
117
174
|
renderStatusBar(data);
|
|
175
|
+
renderAuthorityMode(data);
|
|
118
176
|
renderSummaryStats(data);
|
|
119
177
|
renderAdvisory(data);
|
|
120
178
|
renderPolicySources(data, selectedData);
|
|
@@ -196,6 +254,119 @@
|
|
|
196
254
|
setText('perm-stat-asked', asked);
|
|
197
255
|
}
|
|
198
256
|
|
|
257
|
+
function renderAuthorityMode(data) {
|
|
258
|
+
const card = document.getElementById('perm-authority-card');
|
|
259
|
+
const saveButton = document.getElementById('perm-authority-save-btn');
|
|
260
|
+
const message = document.getElementById('perm-authority-message');
|
|
261
|
+
const currentHostList = document.getElementById('perm-authority-current-host-list');
|
|
262
|
+
const selectedHostHeading = document.getElementById('perm-authority-selected-host');
|
|
263
|
+
const reasonInput = document.getElementById('perm-authority-reason');
|
|
264
|
+
const note = document.getElementById('perm-authority-note');
|
|
265
|
+
const authoritativeNote = document.getElementById('perm-authority-authoritative-note');
|
|
266
|
+
const dirtyState = document.getElementById('perm-authority-dirty-state');
|
|
267
|
+
const saveCopy = document.getElementById('perm-authority-save-copy');
|
|
268
|
+
const saveShell = document.getElementById('perm-authority-save-shell');
|
|
269
|
+
|
|
270
|
+
if (!card || !saveButton || !message || !currentHostList || !selectedHostHeading || !reasonInput || !note || !authoritativeNote || !dirtyState || !saveCopy || !saveShell) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const supportedHosts = Array.isArray(data?.authoritySupportedHosts)
|
|
275
|
+
? data.authoritySupportedHosts
|
|
276
|
+
: ['claude-code'];
|
|
277
|
+
const authority = data?.authority || { defaultMode: 'shared', hosts: {} };
|
|
278
|
+
|
|
279
|
+
if (supportedHosts.length === 0) {
|
|
280
|
+
currentHostList.innerHTML = '<div class="perm-pattern-empty">No installed permission hosts detected yet.</div>';
|
|
281
|
+
selectedHostHeading.textContent = 'No installed hosts';
|
|
282
|
+
note.textContent = 'Install Dollhouse permission hooks for a host before changing permission authority mode here.';
|
|
283
|
+
authoritativeNote.hidden = true;
|
|
284
|
+
authoritativeNote.textContent = '';
|
|
285
|
+
dirtyState.hidden = true;
|
|
286
|
+
dirtyState.textContent = '';
|
|
287
|
+
message.hidden = true;
|
|
288
|
+
message.textContent = '';
|
|
289
|
+
message.dataset.kind = 'info';
|
|
290
|
+
saveCopy.textContent = 'Once a host is installed and configured, it will appear on the left for editing.';
|
|
291
|
+
saveShell.dataset.dirty = 'false';
|
|
292
|
+
saveShell.dataset.busy = 'false';
|
|
293
|
+
card.dataset.authorityDirty = 'false';
|
|
294
|
+
card.setAttribute('aria-busy', 'false');
|
|
295
|
+
reasonInput.value = authorityUiState.draftReason;
|
|
296
|
+
setAuthorityRadioState('perm-authority-mode-off', false, true);
|
|
297
|
+
setAuthorityRadioState('perm-authority-mode-shared', false, true);
|
|
298
|
+
setAuthorityRadioState('perm-authority-mode-authoritative', false, true);
|
|
299
|
+
saveButton.disabled = true;
|
|
300
|
+
saveButton.textContent = 'No Installed Hosts Yet';
|
|
301
|
+
saveButton.setAttribute('aria-busy', 'false');
|
|
302
|
+
card.hidden = false;
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!supportedHosts.includes(authorityUiState.selectedHost)) {
|
|
307
|
+
authorityUiState.selectedHost = supportedHosts[0];
|
|
308
|
+
authorityUiState.dirty = false;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const serverMode = getAuthorityModeForHost(authority, authorityUiState.selectedHost);
|
|
312
|
+
if (!authorityUiState.dirty) {
|
|
313
|
+
authorityUiState.selectedMode = serverMode;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
reasonInput.value = authorityUiState.draftReason;
|
|
317
|
+
|
|
318
|
+
const authoritativeSupported = AUTHORITY_AUTHORITATIVE_HOSTS.has(authorityUiState.selectedHost);
|
|
319
|
+
const desiredMode = authoritativeSupported ? authorityUiState.selectedMode : fallbackAuthorityMode(authorityUiState.selectedMode);
|
|
320
|
+
const dirty = desiredMode !== serverMode;
|
|
321
|
+
|
|
322
|
+
setAuthorityRadioState('perm-authority-mode-off', desiredMode === 'off', false);
|
|
323
|
+
setAuthorityRadioState('perm-authority-mode-shared', desiredMode === 'shared', false);
|
|
324
|
+
setAuthorityRadioState('perm-authority-mode-authoritative', desiredMode === 'authoritative', !authoritativeSupported);
|
|
325
|
+
|
|
326
|
+
currentHostList.innerHTML = renderAuthorityCurrentHostList(authority, supportedHosts, authorityUiState.selectedHost);
|
|
327
|
+
selectedHostHeading.textContent = formatAuthorityHost(authorityUiState.selectedHost);
|
|
328
|
+
note.textContent = 'Human-only control. AI can read authority mode but cannot change it through MCP.';
|
|
329
|
+
authoritativeNote.hidden = authoritativeSupported;
|
|
330
|
+
authoritativeNote.textContent = authoritativeSupported
|
|
331
|
+
? ''
|
|
332
|
+
: 'Claude Code only for now. Other hosts can use Host-Controlled or Shared Permissioning mode.';
|
|
333
|
+
dirtyState.hidden = !dirty;
|
|
334
|
+
dirtyState.textContent = dirty
|
|
335
|
+
? `Unsaved change: ${formatAuthorityHost(authorityUiState.selectedHost)} will move from ${formatAuthorityMode(serverMode)} to ${formatAuthorityMode(desiredMode)} after you save.`
|
|
336
|
+
: '';
|
|
337
|
+
saveCopy.textContent = buildAuthoritySaveCopy({
|
|
338
|
+
host: authorityUiState.selectedHost,
|
|
339
|
+
currentMode: serverMode,
|
|
340
|
+
desiredMode,
|
|
341
|
+
dirty,
|
|
342
|
+
saving: authorityUiState.requestState === AUTHORITY_MODE_REQUEST_STATES.saving,
|
|
343
|
+
});
|
|
344
|
+
saveShell.dataset.dirty = dirty ? 'true' : 'false';
|
|
345
|
+
saveShell.dataset.busy = authorityUiState.requestState === AUTHORITY_MODE_REQUEST_STATES.saving ? 'true' : 'false';
|
|
346
|
+
card.dataset.authorityDirty = dirty ? 'true' : 'false';
|
|
347
|
+
card.setAttribute('aria-busy', authorityUiState.requestState === AUTHORITY_MODE_REQUEST_STATES.saving ? 'true' : 'false');
|
|
348
|
+
|
|
349
|
+
if (authorityUiState.feedback) {
|
|
350
|
+
message.hidden = false;
|
|
351
|
+
message.textContent = authorityUiState.feedback;
|
|
352
|
+
message.dataset.kind = authorityUiState.feedbackKind;
|
|
353
|
+
} else {
|
|
354
|
+
message.hidden = true;
|
|
355
|
+
message.textContent = '';
|
|
356
|
+
message.dataset.kind = 'info';
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
saveButton.disabled = authorityUiState.requestState === AUTHORITY_MODE_REQUEST_STATES.saving || !dirty;
|
|
360
|
+
saveButton.textContent = authorityUiState.requestState === AUTHORITY_MODE_REQUEST_STATES.saving
|
|
361
|
+
? `Saving ${formatAuthorityMode(desiredMode)}...`
|
|
362
|
+
: dirty
|
|
363
|
+
? `Save ${formatAuthorityMode(desiredMode)} Mode for ${formatAuthorityHost(authorityUiState.selectedHost)}`
|
|
364
|
+
: `Saved for ${formatAuthorityHost(authorityUiState.selectedHost)}`;
|
|
365
|
+
saveButton.dataset.dirty = dirty ? 'true' : 'false';
|
|
366
|
+
saveButton.setAttribute('aria-busy', authorityUiState.requestState === AUTHORITY_MODE_REQUEST_STATES.saving ? 'true' : 'false');
|
|
367
|
+
card.hidden = false;
|
|
368
|
+
}
|
|
369
|
+
|
|
199
370
|
function renderAdvisory(data) {
|
|
200
371
|
const advisory = document.getElementById('perm-all-sessions-advisory');
|
|
201
372
|
if (!advisory) return;
|
|
@@ -500,6 +671,60 @@
|
|
|
500
671
|
};
|
|
501
672
|
}
|
|
502
673
|
|
|
674
|
+
/**
|
|
675
|
+
* Persisted policy state rows are primarily a debugging aid, so the
|
|
676
|
+
* dashboard treats them as opt-in and mirrors the explicit sessions UI flag.
|
|
677
|
+
*/
|
|
678
|
+
function shouldShowPersistedPolicyDebug() {
|
|
679
|
+
return window.DollhouseSessions?.isPolicyDebugVisible?.() === true;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function getVisibleAggregateData(data) {
|
|
683
|
+
if (!data || shouldShowPersistedPolicyDebug()) {
|
|
684
|
+
return data;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const hiddenPolicySessions = Array.isArray(data.knownSessions) ? data.knownSessions : [];
|
|
688
|
+
if (hiddenPolicySessions.length === 0) {
|
|
689
|
+
return data;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const hiddenPolicySessionIds = new Set(
|
|
693
|
+
hiddenPolicySessions
|
|
694
|
+
.map(function (session) { return session && typeof session.sessionId === 'string' ? session.sessionId : ''; })
|
|
695
|
+
.filter(Boolean),
|
|
696
|
+
);
|
|
697
|
+
|
|
698
|
+
const filteredElements = ((data.elements || []).filter(function (element) {
|
|
699
|
+
const sessionIds = Array.isArray(element?.sessionIds) ? element.sessionIds.filter(function (sessionId) {
|
|
700
|
+
return typeof sessionId === 'string' && sessionId !== '';
|
|
701
|
+
}) : [];
|
|
702
|
+
if (sessionIds.length === 0) {
|
|
703
|
+
return true;
|
|
704
|
+
}
|
|
705
|
+
return sessionIds.some(function (sessionId) {
|
|
706
|
+
return !hiddenPolicySessionIds.has(sessionId);
|
|
707
|
+
});
|
|
708
|
+
}));
|
|
709
|
+
|
|
710
|
+
return {
|
|
711
|
+
...data,
|
|
712
|
+
activeElementCount: filteredElements.length,
|
|
713
|
+
hasAllowlist: filteredElements.some(function (element) {
|
|
714
|
+
return (Array.isArray(element.allowRules) && element.allowRules.length > 0)
|
|
715
|
+
|| (Array.isArray(element.allowPatterns) && element.allowPatterns.length > 0);
|
|
716
|
+
}),
|
|
717
|
+
elements: filteredElements,
|
|
718
|
+
knownSessions: [],
|
|
719
|
+
denyPatterns: flattenElementPatterns(filteredElements, 'denyPatterns'),
|
|
720
|
+
allowPatterns: flattenElementPatterns(filteredElements, 'allowPatterns'),
|
|
721
|
+
confirmPatterns: flattenElementPatterns(filteredElements, 'confirmPatterns'),
|
|
722
|
+
denyRules: flattenElementPatterns(filteredElements, 'denyRules'),
|
|
723
|
+
allowRules: flattenElementPatterns(filteredElements, 'allowRules'),
|
|
724
|
+
confirmRules: flattenElementPatterns(filteredElements, 'confirmRules'),
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
|
|
503
728
|
function flattenElementPatterns(elements, key) {
|
|
504
729
|
return elements.flatMap(function (element) {
|
|
505
730
|
return Array.isArray(element[key]) ? element[key] : [];
|
|
@@ -608,6 +833,80 @@
|
|
|
608
833
|
</div>
|
|
609
834
|
</div>
|
|
610
835
|
|
|
836
|
+
<div class="perm-card perm-card--full" data-collapsed="false" id="perm-authority-card">
|
|
837
|
+
<div class="perm-card-header" role="button" tabindex="0" aria-expanded="true">
|
|
838
|
+
<h3 class="perm-card-title">Permission Authority Mode</h3>
|
|
839
|
+
<span class="perm-card-toggle" aria-hidden="true">▾</span>
|
|
840
|
+
</div>
|
|
841
|
+
<div class="perm-card-body">
|
|
842
|
+
<div class="perm-selected-header perm-selected-header--compact">
|
|
843
|
+
<div>
|
|
844
|
+
<div class="perm-selected-title">Human-Only Permission Authority</div>
|
|
845
|
+
<div class="perm-selected-subtitle">Choose whether the host's native permission system or DollhouseMCP is the final authority for tool approvals.</div>
|
|
846
|
+
</div>
|
|
847
|
+
</div>
|
|
848
|
+
|
|
849
|
+
<div class="perm-selected-grid">
|
|
850
|
+
<div class="perm-selected-panel">
|
|
851
|
+
<h4 class="perm-selected-panel-title">Current Permission State</h4>
|
|
852
|
+
<div class="perm-authority-current-list" id="perm-authority-current-host-list"></div>
|
|
853
|
+
</div>
|
|
854
|
+
|
|
855
|
+
<div class="perm-selected-panel">
|
|
856
|
+
<h4 class="perm-selected-panel-title">Change Permission Mode</h4>
|
|
857
|
+
<div class="perm-authority-selected-host" id="perm-authority-selected-host">Claude Code</div>
|
|
858
|
+
<div class="perm-selected-subtitle">Choose how this host should handle permission decisions.</div>
|
|
859
|
+
|
|
860
|
+
<div class="perm-authority-options" role="radiogroup" aria-label="Authority mode">
|
|
861
|
+
<label class="perm-authority-option" id="perm-authority-option-off">
|
|
862
|
+
<span class="perm-authority-option-main">
|
|
863
|
+
<input type="radio" name="perm-authority-mode" id="perm-authority-mode-off" value="off">
|
|
864
|
+
<span class="perm-authority-option-copy">
|
|
865
|
+
<span class="perm-authority-option-title">Host-Controlled Permissions</span>
|
|
866
|
+
<span class="perm-authority-option-description">Dollhouse steps out of the way. The host's own permission system handles approvals by itself.</span>
|
|
867
|
+
</span>
|
|
868
|
+
</span>
|
|
869
|
+
</label>
|
|
870
|
+
<label class="perm-authority-option" id="perm-authority-option-shared">
|
|
871
|
+
<span class="perm-authority-option-main">
|
|
872
|
+
<input type="radio" name="perm-authority-mode" id="perm-authority-mode-shared" value="shared">
|
|
873
|
+
<span class="perm-authority-option-copy">
|
|
874
|
+
<span class="perm-authority-option-title">Shared Permissioning</span>
|
|
875
|
+
<span class="perm-authority-option-description">Dollhouse stays active, but the host permission system can still be more restrictive.</span>
|
|
876
|
+
</span>
|
|
877
|
+
</span>
|
|
878
|
+
</label>
|
|
879
|
+
<label class="perm-authority-option perm-authority-option--authoritative" id="perm-authority-option-authoritative">
|
|
880
|
+
<span class="perm-authority-option-main">
|
|
881
|
+
<input type="radio" name="perm-authority-mode" id="perm-authority-mode-authoritative" value="authoritative">
|
|
882
|
+
<span class="perm-authority-option-copy">
|
|
883
|
+
<span class="perm-authority-option-title-row">
|
|
884
|
+
<span class="perm-authority-option-title">Dollhouse-Controlled Permissions</span>
|
|
885
|
+
<span class="perm-authority-inline-note" id="perm-authority-authoritative-note" hidden></span>
|
|
886
|
+
</span>
|
|
887
|
+
<span class="perm-authority-option-description">Dollhouse becomes the permission authority. It syncs Dollhouse allow, ask, and deny rules into the host so Dollhouse decides conflicts instead of the host's own approval flow.</span>
|
|
888
|
+
</span>
|
|
889
|
+
</span>
|
|
890
|
+
</label>
|
|
891
|
+
</div>
|
|
892
|
+
|
|
893
|
+
<label class="perm-selected-subtitle perm-authority-field-label" for="perm-authority-reason">Reason (optional)</label>
|
|
894
|
+
<input id="perm-authority-reason" class="perm-authority-reason-input" type="text" maxlength="200" placeholder="Why are you changing the permission authority mode?">
|
|
895
|
+
|
|
896
|
+
<p class="perm-selected-subtitle perm-authority-human-note" id="perm-authority-note"></p>
|
|
897
|
+
<div class="perm-authority-save-shell" id="perm-authority-save-shell" data-dirty="false">
|
|
898
|
+
<div class="perm-authority-dirty-state" id="perm-authority-dirty-state" hidden></div>
|
|
899
|
+
<div class="perm-inline-warning" id="perm-authority-message" hidden></div>
|
|
900
|
+
<div class="perm-authority-actions">
|
|
901
|
+
<div class="perm-authority-save-copy" id="perm-authority-save-copy"></div>
|
|
902
|
+
<button type="button" class="perm-panel-action perm-authority-save-btn" id="perm-authority-save-btn">Save Authority Mode</button>
|
|
903
|
+
</div>
|
|
904
|
+
</div>
|
|
905
|
+
</div>
|
|
906
|
+
</div>
|
|
907
|
+
</div>
|
|
908
|
+
</div>
|
|
909
|
+
|
|
611
910
|
<!-- Selected Session Detail -->
|
|
612
911
|
<div class="perm-card perm-card--full" data-collapsed="false" id="perm-selected-card" hidden>
|
|
613
912
|
<div class="perm-card-header" role="button" tabindex="0" aria-expanded="true">
|
|
@@ -710,7 +1009,10 @@
|
|
|
710
1009
|
<span>Aggregate decision log across all sessions</span>
|
|
711
1010
|
<span id="perm-audit-modal-count">0 captured entries</span>
|
|
712
1011
|
</div>
|
|
713
|
-
<
|
|
1012
|
+
<div class="modal-header-actions">
|
|
1013
|
+
<button type="button" class="modal-action-btn" id="perm-audit-copy-btn">Copy Markdown</button>
|
|
1014
|
+
<button type="button" class="modal-close" id="perm-audit-modal-close" aria-label="Close audit view">✕</button>
|
|
1015
|
+
</div>
|
|
714
1016
|
</header>
|
|
715
1017
|
<div class="modal-body">
|
|
716
1018
|
<div class="perm-feed perm-feed--modal" id="perm-audit-modal-feed" role="log" aria-live="polite" aria-label="Full permission decision audit feed">
|
|
@@ -741,6 +1043,7 @@
|
|
|
741
1043
|
const expandBtn = document.getElementById('perm-feed-expand-btn');
|
|
742
1044
|
const auditModal = document.getElementById('perm-audit-modal');
|
|
743
1045
|
const closeBtn = document.getElementById('perm-audit-modal-close');
|
|
1046
|
+
const copyBtn = document.getElementById('perm-audit-copy-btn');
|
|
744
1047
|
if (expandBtn && auditModal) {
|
|
745
1048
|
expandBtn.addEventListener('click', function (e) {
|
|
746
1049
|
e.stopPropagation();
|
|
@@ -752,6 +1055,12 @@
|
|
|
752
1055
|
closeBtn.addEventListener('click', closeAuditModal);
|
|
753
1056
|
}
|
|
754
1057
|
|
|
1058
|
+
if (copyBtn) {
|
|
1059
|
+
copyBtn.addEventListener('click', function () {
|
|
1060
|
+
copyAuditViewAsMarkdown(copyBtn);
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
|
|
755
1064
|
if (auditModal) {
|
|
756
1065
|
auditModal.addEventListener('click', function (e) {
|
|
757
1066
|
if (e.target === auditModal || e.target.hasAttribute('data-close-audit-modal')) {
|
|
@@ -767,6 +1076,52 @@
|
|
|
767
1076
|
}
|
|
768
1077
|
}
|
|
769
1078
|
|
|
1079
|
+
function attachAuthorityControls() {
|
|
1080
|
+
const currentHostList = document.getElementById('perm-authority-current-host-list');
|
|
1081
|
+
const reasonInput = document.getElementById('perm-authority-reason');
|
|
1082
|
+
const saveButton = document.getElementById('perm-authority-save-btn');
|
|
1083
|
+
|
|
1084
|
+
if (currentHostList) {
|
|
1085
|
+
currentHostList.addEventListener('click', function (event) {
|
|
1086
|
+
const row = event.target.closest('.perm-authority-current-host[data-host]');
|
|
1087
|
+
if (!row) {
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
const host = row.getAttribute('data-host');
|
|
1091
|
+
if (!host || host === authorityUiState.selectedHost) {
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
authorityUiState.selectedHost = host;
|
|
1095
|
+
authorityUiState.feedback = '';
|
|
1096
|
+
authorityUiState.feedbackKind = 'info';
|
|
1097
|
+
authorityUiState.dirty = false;
|
|
1098
|
+
renderAuthorityMode(latestAggregateData);
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
document.querySelectorAll('input[name="perm-authority-mode"]').forEach(function (input) {
|
|
1103
|
+
input.addEventListener('change', function (event) {
|
|
1104
|
+
authorityUiState.selectedMode = event.target.value;
|
|
1105
|
+
authorityUiState.feedback = '';
|
|
1106
|
+
authorityUiState.feedbackKind = 'info';
|
|
1107
|
+
authorityUiState.dirty = true;
|
|
1108
|
+
renderAuthorityMode(latestAggregateData);
|
|
1109
|
+
});
|
|
1110
|
+
});
|
|
1111
|
+
|
|
1112
|
+
if (reasonInput) {
|
|
1113
|
+
reasonInput.addEventListener('input', function (event) {
|
|
1114
|
+
authorityUiState.draftReason = event.target.value;
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
if (saveButton) {
|
|
1119
|
+
saveButton.addEventListener('click', function () {
|
|
1120
|
+
saveAuthorityMode();
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
770
1125
|
function openAuditModal() {
|
|
771
1126
|
const auditModal = document.getElementById('perm-audit-modal');
|
|
772
1127
|
if (!auditModal) return;
|
|
@@ -789,6 +1144,75 @@
|
|
|
789
1144
|
document.body.classList.remove('modal-open');
|
|
790
1145
|
}
|
|
791
1146
|
|
|
1147
|
+
async function copyAuditViewAsMarkdown(button) {
|
|
1148
|
+
const decisions = latestAggregateData?.recentDecisions ?? [];
|
|
1149
|
+
const markdown = buildAuditMarkdown(decisions);
|
|
1150
|
+
const originalLabel = button.textContent;
|
|
1151
|
+
|
|
1152
|
+
try {
|
|
1153
|
+
await copyTextToClipboard(markdown);
|
|
1154
|
+
button.textContent = 'Copied!';
|
|
1155
|
+
window.setTimeout(function () {
|
|
1156
|
+
button.textContent = originalLabel;
|
|
1157
|
+
}, 1500);
|
|
1158
|
+
} catch {
|
|
1159
|
+
button.textContent = 'Copy failed';
|
|
1160
|
+
window.setTimeout(function () {
|
|
1161
|
+
button.textContent = originalLabel;
|
|
1162
|
+
}, 1500);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
async function copyTextToClipboard(text) {
|
|
1167
|
+
if (navigator.clipboard?.writeText) {
|
|
1168
|
+
try {
|
|
1169
|
+
await navigator.clipboard.writeText(text);
|
|
1170
|
+
return;
|
|
1171
|
+
} catch {
|
|
1172
|
+
// Fall through to the user-gesture fallback below. Some embedded browsers
|
|
1173
|
+
// expose the clipboard API but still reject writes in modal contexts.
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
copyTextWithSelectionFallback(text);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
function copyTextWithSelectionFallback(text) {
|
|
1181
|
+
const textarea = document.createElement('textarea');
|
|
1182
|
+
let handled = false;
|
|
1183
|
+
|
|
1184
|
+
function handleCopy(event) {
|
|
1185
|
+
if (!event.clipboardData) {
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
event.clipboardData.setData('text/plain', text);
|
|
1189
|
+
event.preventDefault();
|
|
1190
|
+
handled = true;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
textarea.value = text;
|
|
1194
|
+
textarea.setAttribute('readonly', '');
|
|
1195
|
+
textarea.setAttribute('aria-hidden', 'true');
|
|
1196
|
+
textarea.style.position = 'fixed';
|
|
1197
|
+
textarea.style.top = '-9999px';
|
|
1198
|
+
textarea.style.left = '-9999px';
|
|
1199
|
+
textarea.style.opacity = '0';
|
|
1200
|
+
document.body.appendChild(textarea);
|
|
1201
|
+
document.addEventListener('copy', handleCopy, true);
|
|
1202
|
+
textarea.focus();
|
|
1203
|
+
textarea.select();
|
|
1204
|
+
textarea.setSelectionRange(0, textarea.value.length);
|
|
1205
|
+
|
|
1206
|
+
try {
|
|
1207
|
+
if (!document.execCommand || !document.execCommand('copy') || !handled) {
|
|
1208
|
+
throw new Error('Clipboard copy command unavailable');
|
|
1209
|
+
}
|
|
1210
|
+
} finally {
|
|
1211
|
+
document.removeEventListener('copy', handleCopy, true);
|
|
1212
|
+
textarea.remove();
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
792
1216
|
function setText(id, value) {
|
|
793
1217
|
const el = document.getElementById(id);
|
|
794
1218
|
if (el) el.textContent = String(value);
|
|
@@ -813,10 +1237,203 @@
|
|
|
813
1237
|
return str.length > len ? str.slice(0, len) + '...' : str;
|
|
814
1238
|
}
|
|
815
1239
|
|
|
1240
|
+
function getAuthorityModeForHost(authority, host) {
|
|
1241
|
+
return authority?.hosts?.[host]?.mode || authority?.defaultMode || 'shared';
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
function formatAuthorityHost(host) {
|
|
1245
|
+
const meta = AUTHORITY_HOST_META[String(host || '')];
|
|
1246
|
+
if (meta?.label) {
|
|
1247
|
+
return meta.label;
|
|
1248
|
+
}
|
|
1249
|
+
return String(host || '')
|
|
1250
|
+
.split('-')
|
|
1251
|
+
.map(function (part) { return part ? part.charAt(0).toUpperCase() + part.slice(1) : part; })
|
|
1252
|
+
.join(' ');
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
function formatAuthorityMode(mode) {
|
|
1256
|
+
return mode === 'off'
|
|
1257
|
+
? 'Host-Controlled Permissions'
|
|
1258
|
+
: mode === 'authoritative'
|
|
1259
|
+
? 'Dollhouse-Controlled Permissions'
|
|
1260
|
+
: 'Shared Permissioning';
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
function buildAuthorityExplanation(mode, authoritativeSupported) {
|
|
1264
|
+
if (mode === 'off') {
|
|
1265
|
+
return 'Dollhouse is not participating in approvals here. The host handles permissions on its own.';
|
|
1266
|
+
}
|
|
1267
|
+
if (mode === 'authoritative') {
|
|
1268
|
+
return 'Dollhouse is the source of truth for permissions here. The host follows Dollhouse-synced allow, ask, and deny rules, while user-authored entries outside Dollhouse-managed settings are still preserved.';
|
|
1269
|
+
}
|
|
1270
|
+
return authoritativeSupported
|
|
1271
|
+
? 'Dollhouse participates in permission checks, but the host can still be more restrictive.'
|
|
1272
|
+
: 'This host currently supports shared/advisory mode while authoritative settings sync is still being added.';
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
function fallbackAuthorityMode(mode) {
|
|
1276
|
+
return mode === 'authoritative' ? 'shared' : mode;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
function setAuthorityRadioState(id, checked, disabled) {
|
|
1280
|
+
const radio = document.getElementById(id);
|
|
1281
|
+
if (!radio) return;
|
|
1282
|
+
radio.checked = checked;
|
|
1283
|
+
radio.disabled = disabled;
|
|
1284
|
+
const option = radio.closest('.perm-authority-option');
|
|
1285
|
+
if (option) {
|
|
1286
|
+
option.dataset.checked = checked ? 'true' : 'false';
|
|
1287
|
+
option.dataset.disabled = disabled ? 'true' : 'false';
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
function buildAuthoritySaveCopy(state) {
|
|
1292
|
+
if (state.saving) {
|
|
1293
|
+
return `Applying ${formatAuthorityMode(state.desiredMode)} mode for ${formatAuthorityHost(state.host)}...`;
|
|
1294
|
+
}
|
|
1295
|
+
if (state.dirty) {
|
|
1296
|
+
return `Review the change and save to apply ${formatAuthorityMode(state.desiredMode)} mode for ${formatAuthorityHost(state.host)}.`;
|
|
1297
|
+
}
|
|
1298
|
+
return `${formatAuthorityHost(state.host)} is currently saved in ${formatAuthorityMode(state.currentMode)} mode.`;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
function renderAuthorityCurrentHostList(authority, supportedHosts, selectedHost) {
|
|
1302
|
+
const explicitHosts = Object.keys(authority?.hosts || {});
|
|
1303
|
+
const hostIds = Array.from(new Set(explicitHosts.concat(supportedHosts || [])));
|
|
1304
|
+
const orderedHosts = hostIds.sort(function (left, right) {
|
|
1305
|
+
return formatAuthorityHost(left).localeCompare(formatAuthorityHost(right));
|
|
1306
|
+
});
|
|
1307
|
+
|
|
1308
|
+
if (orderedHosts.length === 0) {
|
|
1309
|
+
return '<div class="perm-pattern-empty">No host authority settings saved yet.</div>';
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
return orderedHosts.map(function (host) {
|
|
1313
|
+
const mode = getAuthorityModeForHost(authority, host);
|
|
1314
|
+
const meta = AUTHORITY_HOST_META[host] || { shortLabel: formatAuthorityHost(host).slice(0, 2).toUpperCase(), tone: 'generic' };
|
|
1315
|
+
const selectedAttr = host === selectedHost ? 'true' : 'false';
|
|
1316
|
+
return `
|
|
1317
|
+
<button type="button" class="perm-authority-current-host" data-selected="${selectedAttr}" data-host="${esc(host)}" aria-pressed="${selectedAttr}">
|
|
1318
|
+
<span class="perm-authority-host-mark perm-authority-host-mark--${esc(meta.tone || 'generic')}" aria-hidden="true">${esc(meta.shortLabel || 'DH')}</span>
|
|
1319
|
+
<span class="perm-authority-current-host-copy">
|
|
1320
|
+
<span class="perm-authority-current-host-name">${esc(formatAuthorityHost(host))}</span>
|
|
1321
|
+
<span class="perm-authority-current-host-mode">${esc(formatAuthorityMode(mode))}</span>
|
|
1322
|
+
</span>
|
|
1323
|
+
</button>
|
|
1324
|
+
`;
|
|
1325
|
+
}).join('');
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
async function saveAuthorityMode() {
|
|
1329
|
+
if (!latestAggregateData || authorityUiState.requestState === AUTHORITY_MODE_REQUEST_STATES.saving) {
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
const authority = latestAggregateData.authority || { defaultMode: 'shared', hosts: {} };
|
|
1334
|
+
const currentMode = getAuthorityModeForHost(authority, authorityUiState.selectedHost);
|
|
1335
|
+
const requestedMode = AUTHORITY_AUTHORITATIVE_HOSTS.has(authorityUiState.selectedHost)
|
|
1336
|
+
? authorityUiState.selectedMode
|
|
1337
|
+
: fallbackAuthorityMode(authorityUiState.selectedMode);
|
|
1338
|
+
|
|
1339
|
+
if (requestedMode === currentMode) {
|
|
1340
|
+
authorityUiState.feedback = 'No authority-mode change to save.';
|
|
1341
|
+
authorityUiState.feedbackKind = 'info';
|
|
1342
|
+
renderAuthorityMode(latestAggregateData);
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
const confirmMessage = [
|
|
1347
|
+
`Change ${formatAuthorityHost(authorityUiState.selectedHost)} from ${formatAuthorityMode(currentMode)} to ${formatAuthorityMode(requestedMode)}?`,
|
|
1348
|
+
'',
|
|
1349
|
+
requestedMode === 'authoritative'
|
|
1350
|
+
? 'Dollhouse will take a backup and write managed host permission settings.'
|
|
1351
|
+
: requestedMode === 'off'
|
|
1352
|
+
? 'Dollhouse hooks will no-op and the host permission system will become the only gate.'
|
|
1353
|
+
: 'Dollhouse will stay active, but the host will remain authoritative on conflicts.',
|
|
1354
|
+
].join('\n');
|
|
1355
|
+
|
|
1356
|
+
if (!window.confirm(confirmMessage)) {
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
authorityUiState.requestState = AUTHORITY_MODE_REQUEST_STATES.saving;
|
|
1361
|
+
authorityUiState.feedback = '';
|
|
1362
|
+
renderAuthorityMode(latestAggregateData);
|
|
1363
|
+
|
|
1364
|
+
try {
|
|
1365
|
+
const response = await updatePermissionAuthority({
|
|
1366
|
+
host: authorityUiState.selectedHost,
|
|
1367
|
+
mode: requestedMode,
|
|
1368
|
+
reason: authorityUiState.draftReason.trim() || undefined,
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1371
|
+
latestAggregateData = {
|
|
1372
|
+
...latestAggregateData,
|
|
1373
|
+
authority: response.authority,
|
|
1374
|
+
};
|
|
1375
|
+
authorityUiState.selectedMode = requestedMode;
|
|
1376
|
+
authorityUiState.dirty = false;
|
|
1377
|
+
authorityUiState.feedback = `Saved ${formatAuthorityMode(requestedMode)} mode for ${formatAuthorityHost(authorityUiState.selectedHost)}.`;
|
|
1378
|
+
authorityUiState.feedbackKind = 'success';
|
|
1379
|
+
} catch (error) {
|
|
1380
|
+
authorityUiState.feedback = error instanceof Error ? error.message : 'Failed to update permission authority.';
|
|
1381
|
+
authorityUiState.feedbackKind = 'error';
|
|
1382
|
+
} finally {
|
|
1383
|
+
authorityUiState.requestState = AUTHORITY_MODE_REQUEST_STATES.idle;
|
|
1384
|
+
renderAuthorityMode(latestAggregateData);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
|
|
816
1388
|
function dataAdvisoryPlaceholder() {
|
|
817
1389
|
return '<span id="perm-all-sessions-advisory" class="perm-inline-advisory" hidden></span>';
|
|
818
1390
|
}
|
|
819
1391
|
|
|
1392
|
+
function buildAuditMarkdown(decisions) {
|
|
1393
|
+
const lines = [
|
|
1394
|
+
'# DollhouseMCP Permissions Audit',
|
|
1395
|
+
'',
|
|
1396
|
+
`Generated: ${formatExactTimestamp(new Date().toISOString())}`,
|
|
1397
|
+
`Entries: ${decisions.length}`,
|
|
1398
|
+
'Scope: Aggregate decision log across all sessions',
|
|
1399
|
+
'',
|
|
1400
|
+
];
|
|
1401
|
+
|
|
1402
|
+
if (!decisions.length) {
|
|
1403
|
+
lines.push('No permission decisions recorded yet.');
|
|
1404
|
+
return lines.join('\n');
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
decisions.forEach(function (decision, index) {
|
|
1408
|
+
const entryLabel = decision.tool_name === 'Bash' && decision.command
|
|
1409
|
+
? `Bash: ${decision.command}`
|
|
1410
|
+
: (decision.tool_name || 'Unknown tool');
|
|
1411
|
+
lines.push(`## ${index + 1}. ${entryLabel}`);
|
|
1412
|
+
lines.push(`- Decision: ${decision.decision || 'unknown'}`);
|
|
1413
|
+
lines.push(`- Timestamp: ${formatExactTimestamp(decision.timestamp)}`);
|
|
1414
|
+
|
|
1415
|
+
const compactContext = getCompactContext(decision);
|
|
1416
|
+
if (compactContext) {
|
|
1417
|
+
lines.push(`- Context: ${compactContext}`);
|
|
1418
|
+
}
|
|
1419
|
+
if (decision.reason) {
|
|
1420
|
+
lines.push(`- Reason: ${decision.reason}`);
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
const detailRows = Array.isArray(decision.details) ? decision.details : [];
|
|
1424
|
+
if (detailRows.length) {
|
|
1425
|
+
lines.push('- Details:');
|
|
1426
|
+
detailRows.forEach(function (detail) {
|
|
1427
|
+
lines.push(` - ${detail.label}: ${detail.value}`);
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
lines.push('');
|
|
1432
|
+
});
|
|
1433
|
+
|
|
1434
|
+
return lines.join('\n');
|
|
1435
|
+
}
|
|
1436
|
+
|
|
820
1437
|
function renderInvalidPolicySummary(elementId, elements) {
|
|
821
1438
|
const banner = document.getElementById(elementId);
|
|
822
1439
|
if (!banner) return;
|