@dollhousemcp/mcp-server 2.0.27 → 2.0.28

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.
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Best-effort MCP client platform detection for session metadata.
3
+ *
4
+ * This is intentionally conservative: when we cannot identify the host
5
+ * confidently, we return null rather than guessing from a session ID.
6
+ */
7
+ const CLIENT_PLATFORM_LABELS = {
8
+ 'claude-code': 'Claude Code',
9
+ 'claude-desktop': 'Claude Desktop',
10
+ codex: 'Codex',
11
+ cursor: 'Cursor',
12
+ vscode: 'VS Code',
13
+ windsurf: 'Windsurf',
14
+ 'gemini-cli': 'Gemini CLI',
15
+ cline: 'Cline',
16
+ lmstudio: 'LM Studio',
17
+ 'web-console': 'Web Console',
18
+ };
19
+ function normalizeText(value) {
20
+ return typeof value === 'string' ? value.trim().toLowerCase() : '';
21
+ }
22
+ function includesAny(value, needles) {
23
+ return needles.some((needle) => value.includes(needle));
24
+ }
25
+ function matchesAnySource(values, needles) {
26
+ return values.some((value) => includesAny(value, needles));
27
+ }
28
+ const TEXT_PLATFORM_MATCHERS = [
29
+ { platform: 'cursor', needles: ['cursor'] },
30
+ { platform: 'windsurf', needles: ['windsurf'] },
31
+ { platform: 'gemini-cli', needles: ['gemini'] },
32
+ { platform: 'cline', needles: ['cline'] },
33
+ { platform: 'lmstudio', needles: ['lmstudio', 'lm studio'] },
34
+ { platform: 'claude-desktop', needles: ['claude desktop'] },
35
+ { platform: 'claude-code', needles: ['claude code'] },
36
+ { platform: 'codex', needles: ['codex'] },
37
+ ];
38
+ export function normalizeSessionClientPlatformId(value) {
39
+ const normalized = normalizeText(value ?? undefined);
40
+ if (!normalized) {
41
+ return null;
42
+ }
43
+ if (normalized === 'gemini') {
44
+ return 'gemini-cli';
45
+ }
46
+ if (normalized in CLIENT_PLATFORM_LABELS) {
47
+ return normalized;
48
+ }
49
+ return null;
50
+ }
51
+ export function getSessionClientPlatformLabel(platform) {
52
+ return platform ? CLIENT_PLATFORM_LABELS[platform] ?? '' : '';
53
+ }
54
+ export function detectSessionClientPlatformId(env = process.env, argv = process.argv, execPath = process.execPath, title = process.title) {
55
+ const termProgram = normalizeText(env.TERM_PROGRAM);
56
+ const argvText = normalizeText(argv.join(' '));
57
+ const execPathText = normalizeText(execPath);
58
+ const titleText = normalizeText(title);
59
+ const textSources = [argvText, execPathText, titleText];
60
+ if (env.CLAUDE_DESKTOP === 'true' || env.CLAUDE_DESKTOP_VERSION) {
61
+ return 'claude-desktop';
62
+ }
63
+ if (env.CLAUDE_CODE === 'true' || termProgram === 'claude-code') {
64
+ return 'claude-code';
65
+ }
66
+ if (env.VSCODE_CWD ||
67
+ env.VSCODE_PID ||
68
+ env.VSCODE_IPC_HOOK ||
69
+ env.VSCODE_NLS_CONFIG ||
70
+ termProgram === 'vscode') {
71
+ return 'vscode';
72
+ }
73
+ if (env.CODEX_HOME || termProgram === 'codex') {
74
+ return 'codex';
75
+ }
76
+ for (const matcher of TEXT_PLATFORM_MATCHERS) {
77
+ if (matchesAnySource(textSources, matcher.needles)) {
78
+ return matcher.platform;
79
+ }
80
+ }
81
+ return null;
82
+ }
83
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2Vzc2lvbkNsaWVudFBsYXRmb3JtLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL3dlYi9jb25zb2xlL3Nlc3Npb25DbGllbnRQbGF0Zm9ybS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7R0FLRztBQWNILE1BQU0sc0JBQXNCLEdBQTRDO0lBQ3RFLGFBQWEsRUFBRSxhQUFhO0lBQzVCLGdCQUFnQixFQUFFLGdCQUFnQjtJQUNsQyxLQUFLLEVBQUUsT0FBTztJQUNkLE1BQU0sRUFBRSxRQUFRO0lBQ2hCLE1BQU0sRUFBRSxTQUFTO0lBQ2pCLFFBQVEsRUFBRSxVQUFVO0lBQ3BCLFlBQVksRUFBRSxZQUFZO0lBQzFCLEtBQUssRUFBRSxPQUFPO0lBQ2QsUUFBUSxFQUFFLFdBQVc7SUFDckIsYUFBYSxFQUFFLGFBQWE7Q0FDN0IsQ0FBQztBQUVGLFNBQVMsYUFBYSxDQUFDLEtBQXlCO0lBQzlDLE9BQU8sT0FBTyxLQUFLLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsSUFBSSxFQUFFLENBQUMsV0FBVyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztBQUNyRSxDQUFDO0FBRUQsU0FBUyxXQUFXLENBQUMsS0FBYSxFQUFFLE9BQTBCO0lBQzVELE9BQU8sT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLE1BQU0sRUFBRSxFQUFFLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDO0FBQzFELENBQUM7QUFFRCxTQUFTLGdCQUFnQixDQUFDLE1BQXlCLEVBQUUsT0FBMEI7SUFDN0UsT0FBTyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxXQUFXLENBQUMsS0FBSyxFQUFFLE9BQU8sQ0FBQyxDQUFDLENBQUM7QUFDN0QsQ0FBQztBQUVELE1BQU0sc0JBQXNCLEdBR3ZCO0lBQ0gsRUFBRSxRQUFRLEVBQUUsUUFBUSxFQUFFLE9BQU8s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LHNCQUFzQixFQUFFLENBQUM7UUFDN0MsSUFBSSxnQkFBZ0IsQ0FBQyxXQUFXLEVBQUUsT0FBTyxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7WUFDbkQsT0FBTyxPQUFPLENBQUMsUUFBUSxDQUFDO1FBQzFCLENBQUM7SUFDSCxDQUFDO0lBRUQsT0FBTyxJQUFJLENBQUM7QUFDZCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBCZXN0LWVmZm9ydCBNQ1AgY2xpZW50IHBsYXRmb3JtIGRldGVjdGlvbiBmb3Igc2Vzc2lvbiBtZXRhZGF0YS5cbiAqXG4gKiBUaGlzIGlzIGludGVudGlvbmFsbHkgY29uc2VydmF0aXZlOiB3aGVuIHdlIGNhbm5vdCBpZGVudGlmeSB0aGUgaG9zdFxuICogY29uZmlkZW50bHksIHdlIHJldHVybiBudWxsIHJhdGhlciB0aGFuIGd1ZXNzaW5nIGZyb20gYSBzZXNzaW9uIElELlxuICovXG5cbmV4cG9ydCB0eXBlIFNlc3Npb25DbGllbnRQbGF0Zm9ybUlkID1cbiAgfCAnY2xhdWRlLWNvZGUnXG4gIHwgJ2NsYXVkZS1kZXNrdG9wJ1xuICB8ICdjb2RleCdcbiAgfCAnY3Vyc29yJ1xuICB8ICd2c2NvZGUnXG4gIHwgJ3dpbmRzdXJmJ1xuICB8ICdnZW1pbmktY2xpJ1xuICB8ICdjbGluZSdcbiAgfCAnbG1zdHVkaW8nXG4gIHwgJ3dlYi1jb25zb2xlJztcblxuY29uc3QgQ0xJRU5UX1BMQVRGT1JNX0xBQkVMUzogUmVjb3JkPFNlc3Npb25DbGllbnRQbGF0Zm9ybUlkLCBzdHJpbmc+ID0ge1xuICAnY2xhdWRlLWNvZGUnOiAnQ2xhdWRlIENvZGUnLFxuICAnY2xhdWRlLWRlc2t0b3AnOiAnQ2xhdWRlIERlc2t0b3AnLFxuICBjb2RleDogJ0NvZGV4JyxcbiAgY3Vyc29yOiAnQ3Vyc29yJyxcbiAgdnNjb2RlOiAnVlMgQ29kZScsXG4gIHdpbmRzdXJmOiAnV2luZHN1cmYnLFxuICAnZ2VtaW5pLWNsaSc6ICdHZW1pbmkgQ0xJJyxcbiAgY2xpbmU6ICdDbGluZScsXG4gIGxtc3R1ZGlvOiAnTE0gU3R1ZGlvJyxcbiAgJ3dlYi1jb25zb2xlJzogJ1dlYiBDb25zb2xlJyxcbn07XG5cbmZ1bmN0aW9uIG5vcm1hbGl6ZVRleHQodmFsdWU6IHN0cmluZyB8IHVuZGVmaW5lZCk6IHN0cmluZyB7XG4gIHJldHVybiB0eXBlb2YgdmFsdWUgPT09ICdzdHJpbmcnID8gdmFsdWUudHJpbSgpLnRvTG93ZXJDYXNlKCkgOiAnJztcbn1cblxuZnVuY3Rpb24gaW5jbHVkZXNBbnkodmFsdWU6IHN0cmluZywgbmVlZGxlczogcmVhZG9ubHkgc3RyaW5nW10pOiBib29sZWFuIHtcbiAgcmV0dXJuIG5lZWRsZXMuc29tZSgobmVlZGxlKSA9PiB2YWx1ZS5pbmNsdWRlcyhuZWVkbGUpKTtcbn1cblxuZnVuY3Rpb24gbWF0Y2hlc0FueVNvdXJjZSh2YWx1ZXM6IHJlYWRvbmx5IHN0cmluZ1tdLCBuZWVkbGVzOiByZWFkb25seSBzdHJpbmdbXSk6IGJvb2xlYW4ge1xuICByZXR1cm4gdmFsdWVzLnNvbWUoKHZhbHVlKSA9PiBpbmNsdWRlc0FueSh2YWx1ZSwgbmVlZGxlcykpO1xufVxuXG5jb25zdCBURVhUX1BMQVRGT1JNX01BVENIRVJTOiBSZWFkb25seUFycmF5PHtcbiAgcGxhdGZvcm06IFNlc3Npb25DbGllbnRQbGF0Zm9ybUlkO1xuICBuZWVkbGVzOiByZWFkb25seSBzdHJpbmdbXTtcbn0+ID0gW1xuICB7IHBsYXRmb3JtOiAnY3Vyc29yJywgbmVlZGxlczogWydjdXJzb3InXSB9LFxuICB7IHBsYXRmb3JtOiAnd2luZHN1cmYnLCBuZWVkbGVzOiBbJ3dpbmRzdXJmJ10gfSxcbiAgeyBwbGF0Zm9ybTogJ2dlbWluaS1jbGknLCBuZWVkbGVzOiBbJ2dlbWluaSddIH0sXG4gIHsgcGxhdGZvcm06ICdjbGluZScsIG5lZWRsZXM6IFsnY2xpbmUnXSB9LFxuICB7IHBsYXRmb3JtOiAnbG1zdHVkaW8nLCBuZWVkbGVzOiBbJ2xtc3R1ZGlvJywgJ2xtIHN0dWRpbyddIH0sXG4gIHsgcGxhdGZvcm06ICdjbGF1ZGUtZGVza3RvcCcsIG5lZWRsZXM6IFsnY2xhdWRlIGRlc2t0b3AnXSB9LFxuICB7IHBsYXRmb3JtOiAnY2xhdWRlLWNvZGUnLCBuZWVkbGVzOiBbJ2NsYXVkZSBjb2RlJ10gfSxcbiAgeyBwbGF0Zm9ybTogJ2NvZGV4JywgbmVlZGxlczogWydjb2RleCddIH0sXG5dO1xuXG5leHBvcnQgZnVuY3Rpb24gbm9ybWFsaXplU2Vzc2lvbkNsaWVudFBsYXRmb3JtSWQoXG4gIHZhbHVlOiBzdHJpbmcgfCBudWxsIHwgdW5kZWZpbmVkLFxuKTogU2Vzc2lvbkNsaWVudFBsYXRmb3JtSWQgfCBudWxsIHtcbiAgY29uc3Qgbm9ybWFsaXplZCA9IG5vcm1hbGl6ZVRleHQodmFsdWUgPz8gdW5kZWZpbmVkKTtcbiAgaWYgKCFub3JtYWxpemVkKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICBpZiAobm9ybWFsaXplZCA9PT0gJ2dlbWluaScpIHtcbiAgICByZXR1cm4gJ2dlbWluaS1jbGknO1xuICB9XG5cbiAgaWYgKG5vcm1hbGl6ZWQgaW4gQ0xJRU5UX1BMQVRGT1JNX0xBQkVMUykge1xuICAgIHJldHVybiBub3JtYWxpemVkIGFzIFNlc3Npb25DbGllbnRQbGF0Zm9ybUlkO1xuICB9XG5cbiAgcmV0dXJuIG51bGw7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBnZXRTZXNzaW9uQ2xpZW50UGxhdGZvcm1MYWJlbChcbiAgcGxhdGZvcm06IFNlc3Npb25DbGllbnRQbGF0Zm9ybUlkIHwgbnVsbCB8IHVuZGVmaW5lZCxcbik6IHN0cmluZyB7XG4gIHJldHVybiBwbGF0Zm9ybSA/IENMSUVOVF9QTEFURk9STV9MQUJFTFNbcGxhdGZvcm1dID8/ICcnIDogJyc7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBkZXRlY3RTZXNzaW9uQ2xpZW50UGxhdGZvcm1JZChcbiAgZW52OiBOb2RlSlMuUHJvY2Vzc0VudiA9IHByb2Nlc3MuZW52LFxuICBhcmd2OiByZWFkb25seSBzdHJpbmdbXSA9IHByb2Nlc3MuYXJndixcbiAgZXhlY1BhdGg6IHN0cmluZyA9IHByb2Nlc3MuZXhlY1BhdGgsXG4gIHRpdGxlOiBzdHJpbmcgPSBwcm9jZXNzLnRpdGxlLFxuKTogU2Vzc2lvbkNsaWVudFBsYXRmb3JtSWQgfCBudWxsIHtcbiAgY29uc3QgdGVybVByb2dyYW0gPSBub3JtYWxpemVUZXh0KGVudi5URVJNX1BST0dSQU0pO1xuICBjb25zdCBhcmd2VGV4dCA9IG5vcm1hbGl6ZVRleHQoYXJndi5qb2luKCcgJykpO1xuICBjb25zdCBleGVjUGF0aFRleHQgPSBub3JtYWxpemVUZXh0KGV4ZWNQYXRoKTtcbiAgY29uc3QgdGl0bGVUZXh0ID0gbm9ybWFsaXplVGV4dCh0aXRsZSk7XG4gIGNvbnN0IHRleHRTb3VyY2VzID0gW2FyZ3ZUZXh0LCBleGVjUGF0aFRleHQsIHRpdGxlVGV4dF07XG5cbiAgaWYgKGVudi5DTEFVREVfREVTS1RPUCA9PT0gJ3RydWUnIHx8IGVudi5DTEFVREVfREVTS1RPUF9WRVJTSU9OKSB7XG4gICAgcmV0dXJuICdjbGF1ZGUtZGVza3RvcCc7XG4gIH1cblxuICBpZiAoZW52LkNMQVVERV9DT0RFID09PSAndHJ1ZScgfHwgdGVybVByb2dyYW0gPT09ICdjbGF1ZGUtY29kZScpIHtcbiAgICByZXR1cm4gJ2NsYXVkZS1jb2RlJztcbiAgfVxuXG4gIGlmIChcbiAgICBlbnYuVlNDT0RFX0NXRCB8fFxuICAgIGVudi5WU0NPREVfUElEIHx8XG4gICAgZW52LlZTQ09ERV9JUENfSE9PSyB8fFxuICAgIGVudi5WU0NPREVfTkxTX0NPTkZJRyB8fFxuICAgIHRlcm1Qcm9ncmFtID09PSAndnNjb2RlJ1xuICApIHtcbiAgICByZXR1cm4gJ3ZzY29kZSc7XG4gIH1cblxuICBpZiAoZW52LkNPREVYX0hPTUUgfHwgdGVybVByb2dyYW0gPT09ICdjb2RleCcpIHtcbiAgICByZXR1cm4gJ2NvZGV4JztcbiAgfVxuXG4gIGZvciAoY29uc3QgbWF0Y2hlciBvZiBURVhUX1BMQVRGT1JNX01BVENIRVJTKSB7XG4gICAgaWYgKG1hdGNoZXNBbnlTb3VyY2UodGV4dFNvdXJjZXMsIG1hdGNoZXIubmVlZGxlcykpIHtcbiAgICAgIHJldHVybiBtYXRjaGVyLnBsYXRmb3JtO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiBudWxsO1xufVxuIl19
@@ -66,7 +66,8 @@
66
66
  position: absolute;
67
67
  top: calc(100% + 0.4rem);
68
68
  right: 0;
69
- min-width: 480px;
69
+ width: min(34rem, calc(100vw - 1rem));
70
+ box-sizing: border-box;
70
71
  background: var(--paper-strong, #fff);
71
72
  border: 1px solid var(--line, #c8d5e9);
72
73
  border-radius: var(--radius-md, 0.85rem);
@@ -184,10 +185,11 @@
184
185
 
185
186
  .session-dropdown-item {
186
187
  display: grid;
187
- grid-template-columns: 1rem 8px 1fr 62px 72px 3.5rem 1.2rem;
188
+ grid-template-columns: 1rem 8px minmax(0, 1fr) 5.5rem 6.75rem 4.5rem 1.2rem;
188
189
  align-items: center;
189
- gap: 0.4rem;
190
- padding: 0.45rem 0.75rem;
190
+ column-gap: 0.7rem;
191
+ row-gap: 0.3rem;
192
+ padding: 0.55rem 0.9rem;
191
193
  font-size: var(--step--1, 0.82rem);
192
194
  cursor: pointer;
193
195
  transition: background 0.1s;
@@ -235,12 +237,51 @@
235
237
  white-space: nowrap;
236
238
  }
237
239
 
240
+ .session-dropdown-primary {
241
+ min-width: 0;
242
+ display: flex;
243
+ flex-direction: column;
244
+ gap: 0.18rem;
245
+ align-self: center;
246
+ }
247
+
248
+ .session-dropdown-meta {
249
+ display: flex;
250
+ align-items: center;
251
+ gap: 0.35rem;
252
+ min-width: 0;
253
+ flex-wrap: wrap;
254
+ }
255
+
256
+ .session-dropdown-version {
257
+ font-size: 0.7rem;
258
+ font-family: var(--font-mono, monospace);
259
+ color: var(--ink-500, #677893);
260
+ white-space: nowrap;
261
+ }
262
+
263
+ .session-dropdown-update {
264
+ display: inline-flex;
265
+ align-items: center;
266
+ padding: 1px 5px;
267
+ border-radius: 999px;
268
+ font-size: 0.62rem;
269
+ font-weight: 700;
270
+ letter-spacing: 0.04em;
271
+ background: #ecfdf5;
272
+ color: #166534;
273
+ border: 1px solid #86efac;
274
+ white-space: nowrap;
275
+ }
276
+
238
277
  .session-dropdown-uptime {
239
278
  font-size: 0.7rem;
240
279
  font-family: var(--font-mono, monospace);
241
280
  color: var(--ink-500, #677893);
242
281
  white-space: nowrap;
243
282
  text-align: right;
283
+ min-width: 4.5rem;
284
+ justify-self: end;
244
285
  }
245
286
 
246
287
  .session-dropdown-role {
@@ -287,6 +328,16 @@
287
328
  color: var(--ink-900, #e0e8f0);
288
329
  }
289
330
 
331
+ [data-theme="dark"] .session-dropdown-version {
332
+ color: var(--ink-500, #8899bb);
333
+ }
334
+
335
+ [data-theme="dark"] .session-dropdown-update {
336
+ background: #052e16;
337
+ color: #bbf7d0;
338
+ border-color: #166534;
339
+ }
340
+
290
341
  [data-theme="dark"] .session-dropdown-toggle-label {
291
342
  color: var(--ink-500, #8899bb);
292
343
  }
@@ -363,15 +414,40 @@
363
414
  * - Color (blue=positive, orange=negative — colorblind-safe pair)
364
415
  */
365
416
  .session-status-badge {
366
- display: inline-block;
367
- padding: 1px 5px;
368
- border-radius: 3px;
369
- font-size: 9px;
417
+ display: inline-flex;
418
+ align-items: center;
419
+ justify-content: center;
420
+ padding: 2px 7px;
421
+ border-radius: 5px;
422
+ font-size: 10px;
370
423
  font-weight: 600;
371
424
  letter-spacing: 0.04em;
372
425
  white-space: nowrap;
373
426
  text-align: center;
374
- min-width: 52px;
427
+ min-width: 4.75rem;
428
+ box-sizing: border-box;
429
+ }
430
+
431
+ .session-dropdown-item > .session-status-badge {
432
+ justify-self: center;
433
+ }
434
+
435
+ .session-dropdown-badge-stack {
436
+ display: flex;
437
+ flex-direction: column;
438
+ align-items: center;
439
+ justify-self: center;
440
+ gap: 0.24rem;
441
+ width: 100%;
442
+ max-width: 6.75rem;
443
+ }
444
+
445
+ .session-dropdown-client-label {
446
+ font-size: 0.62rem;
447
+ line-height: 1.15;
448
+ color: var(--ink-500, #677893);
449
+ text-align: center;
450
+ width: 100%;
375
451
  }
376
452
 
377
453
  .session-status-badge[data-status="positive"] {
@@ -398,6 +474,10 @@
398
474
  border-color: #9a3412;
399
475
  }
400
476
 
477
+ [data-theme="dark"] .session-dropdown-client-label {
478
+ color: var(--ink-500, #8899bb);
479
+ }
480
+
401
481
  /* Session filter in the log viewer */
402
482
  #log-session-filter {
403
483
  min-width: 120px;
@@ -33,6 +33,18 @@
33
33
  var lastReloadTargetVersion = '';
34
34
  var pendingLeaderReloadTimer = null;
35
35
  var showPolicySessions = loadPolicyDebugVisibility();
36
+ var CLIENT_PLATFORM_LABELS = {
37
+ 'claude-code': 'Claude Code',
38
+ 'claude-desktop': 'Claude Desktop',
39
+ 'codex': 'Codex',
40
+ 'cursor': 'Cursor',
41
+ 'vscode': 'VS Code',
42
+ 'windsurf': 'Windsurf',
43
+ 'gemini-cli': 'Gemini CLI',
44
+ 'cline': 'Cline',
45
+ 'lmstudio': 'LM Studio',
46
+ 'web-console': 'Web Console'
47
+ };
36
48
 
37
49
  function loadPolicyDebugVisibility() {
38
50
  try {
@@ -176,6 +188,55 @@
176
188
  /** NFC-normalize a string safely */
177
189
  function nfc(s) { try { return s.normalize('NFC'); } catch(e) { return s; } }
178
190
 
191
+ function normalizeClientPlatform(platform) {
192
+ if (typeof platform !== 'string') return '';
193
+ var normalized = nfc(platform).trim().toLowerCase();
194
+ if (!normalized) return '';
195
+ if (normalized === 'gemini') return 'gemini-cli';
196
+ return Object.prototype.hasOwnProperty.call(CLIENT_PLATFORM_LABELS, normalized) ? normalized : '';
197
+ }
198
+
199
+ function displayPlatform(session) {
200
+ if (!session || typeof session !== 'object') return '';
201
+ if (typeof session.clientPlatformLabel === 'string' && session.clientPlatformLabel.trim()) {
202
+ return nfc(session.clientPlatformLabel.trim());
203
+ }
204
+ var platform = normalizeClientPlatform(session.clientPlatform);
205
+ return platform ? CLIENT_PLATFORM_LABELS[platform] || '' : '';
206
+ }
207
+
208
+ function displayVersion(session) {
209
+ if (!session || typeof session !== 'object') return '';
210
+ var normalized = normalizeSemver(session.serverVersion);
211
+ if (!normalized && typeof session.serverVersion === 'string') {
212
+ normalized = nfc(session.serverVersion).trim();
213
+ }
214
+ if (!normalized) return '';
215
+ return normalized.charAt(0) === 'v' ? normalized : ('v' + normalized);
216
+ }
217
+
218
+ function getNewestKnownSessionVersion(list) {
219
+ if (!Array.isArray(list)) return '';
220
+ var newest = '';
221
+ for (var i = 0; i < list.length; i++) {
222
+ var session = list[i];
223
+ if (!session || isPolicyOnlySession(session) || session.status !== 'active') continue;
224
+ var version = normalizeSemver(session.serverVersion);
225
+ if (!version) continue;
226
+ if (!newest || compareSemver(version, newest) > 0) {
227
+ newest = version;
228
+ }
229
+ }
230
+ return newest;
231
+ }
232
+
233
+ function sessionHasUpdateAvailable(session, newestVersion) {
234
+ if (!session || !newestVersion || isPolicyOnlySession(session)) return false;
235
+ var version = normalizeSemver(session.serverVersion);
236
+ if (!version) return false;
237
+ return compareSemver(version, newestVersion) < 0;
238
+ }
239
+
179
240
  function isPolicyOnlySession(session) {
180
241
  return !!(session && session.isPolicyOnly);
181
242
  }
@@ -231,6 +292,9 @@
231
292
  isLeader: false,
232
293
  authenticated: false,
233
294
  kind: 'policy',
295
+ serverVersion: '',
296
+ clientPlatform: '',
297
+ clientPlatformLabel: '',
234
298
  isPolicyOnly: true,
235
299
  });
236
300
  }
@@ -254,12 +318,63 @@
254
318
  // Build a key from current sessions to detect changes
255
319
  function sessionListKey(list) {
256
320
  return list.map(function(s) {
257
- return s.sessionId + ':' + s.status + ':' + (isPolicyOnlySession(s) ? 'policy' : 'live');
321
+ return [
322
+ s.sessionId,
323
+ s.status,
324
+ s.displayName || '',
325
+ s.serverVersion || '',
326
+ s.clientPlatform || '',
327
+ s.clientPlatformLabel || '',
328
+ s.isLeader ? 'leader' : 'member',
329
+ s.authenticated ? 'auth' : 'noauth',
330
+ isPolicyOnlySession(s) ? 'policy' : 'live'
331
+ ].join(':');
258
332
  }).join(',')
259
333
  + '|policyDebug:' + (showPolicySessions ? 'on' : 'off')
260
334
  + '|knownPolicy:' + policySessions.map(function(session) { return session.sessionId; }).join(',');
261
335
  }
262
336
 
337
+ function normalizeLiveSessions(list) {
338
+ if (!Array.isArray(list)) return [];
339
+ var normalized = [];
340
+ var seen = new Set();
341
+
342
+ for (var i = 0; i < list.length; i++) {
343
+ var item = list[i];
344
+ if (!item || typeof item.sessionId !== 'string') continue;
345
+ var sessionId = nfc(item.sessionId).trim();
346
+ if (!sessionId || seen.has(sessionId)) continue;
347
+ seen.add(sessionId);
348
+
349
+ var clientPlatform = normalizeClientPlatform(item.clientPlatform);
350
+ var clientPlatformLabel = '';
351
+ if (typeof item.clientPlatformLabel === 'string' && item.clientPlatformLabel.trim()) {
352
+ clientPlatformLabel = nfc(item.clientPlatformLabel).trim();
353
+ } else if (clientPlatform) {
354
+ clientPlatformLabel = CLIENT_PLATFORM_LABELS[clientPlatform] || '';
355
+ }
356
+
357
+ normalized.push({
358
+ sessionId: sessionId,
359
+ displayName: nfc(typeof item.displayName === 'string' && item.displayName ? item.displayName : sessionId),
360
+ color: typeof item.color === 'string' ? item.color : '',
361
+ pid: typeof item.pid === 'number' ? item.pid : 0,
362
+ startedAt: typeof item.startedAt === 'string' ? item.startedAt : '',
363
+ lastHeartbeat: typeof item.lastHeartbeat === 'string' ? item.lastHeartbeat : '',
364
+ status: item.status === 'ended' ? 'ended' : 'active',
365
+ isLeader: !!item.isLeader,
366
+ authenticated: !!item.authenticated,
367
+ kind: typeof item.kind === 'string' ? item.kind : 'mcp',
368
+ serverVersion: normalizeSemver(item.serverVersion) || (typeof item.serverVersion === 'string' ? nfc(item.serverVersion).trim() : ''),
369
+ consoleProtocolVersion: typeof item.consoleProtocolVersion === 'number' ? item.consoleProtocolVersion : 0,
370
+ clientPlatform: clientPlatform,
371
+ clientPlatformLabel: clientPlatformLabel,
372
+ });
373
+ }
374
+
375
+ return normalized;
376
+ }
377
+
263
378
  function setPolicyDebugVisibility(nextVisible, keepDropdownOpen) {
264
379
  var normalized = !!nextVisible;
265
380
  if (showPolicySessions === normalized) return;
@@ -513,6 +628,7 @@
513
628
  if (!a.isLeader && b.isLeader) return 1;
514
629
  return 0;
515
630
  });
631
+ var newestKnownVersion = getNewestKnownSessionVersion(sorted);
516
632
 
517
633
  function appendSessionItem(s) {
518
634
  var item = document.createElement('div');
@@ -529,11 +645,37 @@
529
645
  if (s.color) dot.style.background = s.color;
530
646
  item.appendChild(dot);
531
647
 
648
+ var nameWrap = document.createElement('div');
649
+ nameWrap.className = 'session-dropdown-primary';
650
+
532
651
  var nameEl = document.createElement('span');
533
652
  nameEl.className = 'session-dropdown-name';
534
653
  nameEl.textContent = displayName(s);
535
654
  if (s.color) nameEl.style.color = s.color;
536
- item.appendChild(nameEl);
655
+ nameWrap.appendChild(nameEl);
656
+
657
+ var versionText = displayVersion(s);
658
+ if (versionText) {
659
+ var metaRow = document.createElement('div');
660
+ metaRow.className = 'session-dropdown-meta';
661
+
662
+ var versionEl = document.createElement('span');
663
+ versionEl.className = 'session-dropdown-version';
664
+ versionEl.textContent = versionText;
665
+ metaRow.appendChild(versionEl);
666
+
667
+ if (sessionHasUpdateAvailable(s, newestKnownVersion)) {
668
+ var updateBadge = document.createElement('span');
669
+ updateBadge.className = 'session-dropdown-update';
670
+ updateBadge.textContent = 'Update available';
671
+ updateBadge.title = 'A newer local DollhouseMCP session version is active.';
672
+ metaRow.appendChild(updateBadge);
673
+ }
674
+
675
+ nameWrap.appendChild(metaRow);
676
+ }
677
+
678
+ item.appendChild(nameWrap);
537
679
 
538
680
  // Session status badges (#1805) — for persisted policy sessions we
539
681
  // switch from "live/authenticated" semantics to "saved/no client".
@@ -554,6 +696,9 @@
554
696
  }
555
697
  item.appendChild(authBadge);
556
698
 
699
+ var clientWrap = document.createElement('div');
700
+ clientWrap.className = 'session-dropdown-badge-stack';
701
+
557
702
  var clientBadge = document.createElement('span');
558
703
  clientBadge.className = 'session-status-badge';
559
704
  if (isPolicyOnlySession(s)) {
@@ -569,7 +714,17 @@
569
714
  clientBadge.dataset.status = 'negative';
570
715
  clientBadge.title = 'No MCP client attached';
571
716
  }
572
- item.appendChild(clientBadge);
717
+ clientWrap.appendChild(clientBadge);
718
+
719
+ var platformLabel = displayPlatform(s);
720
+ if (platformLabel) {
721
+ var clientLabel = document.createElement('span');
722
+ clientLabel.className = 'session-dropdown-client-label';
723
+ clientLabel.textContent = platformLabel;
724
+ clientWrap.appendChild(clientLabel);
725
+ }
726
+
727
+ item.appendChild(clientWrap);
573
728
 
574
729
  var uptimeEl = document.createElement('span');
575
730
  uptimeEl.className = 'session-dropdown-uptime';
@@ -727,7 +882,7 @@
727
882
  return res.json();
728
883
  }).then(function(data) {
729
884
  if (data && data.sessions) {
730
- sessions = data.sessions;
885
+ sessions = normalizeLiveSessions(data.sessions);
731
886
  maybeForceReloadForNewLeader(sessions);
732
887
  updateSessionIndicator();
733
888
  updateSessionFilterOptions();
@@ -743,6 +898,7 @@
743
898
  window.DollhouseSessions = {
744
899
  getFilterSessionId: function() { return filterSessionId; },
745
900
  displayName: displayName,
901
+ displayPlatform: displayPlatform,
746
902
  getSessions: function() { return sessions; },
747
903
  getLiveSessions: getLiveSessions,
748
904
  getSelectableSessions: getSelectableSessions,
@@ -13,6 +13,9 @@
13
13
  const PKG = '@dollhousemcp/mcp-server';
14
14
  const HOOKS_DIR = '~/.dollhouse/hooks';
15
15
  const HOOK_BASE_SCRIPT_PATH = `${HOOKS_DIR}/pretooluse-dollhouse.sh`;
16
+ const HOOK_CONTRACT_DOC_URL = 'https://github.com/DollhouseMCP/mcp-server/blob/main/docs/architecture/permission-hook-platform-contracts.md';
17
+ // Keep hook entrypoints and output expectations in sync with
18
+ // docs/architecture/permission-hook-platform-contracts.md.
16
19
 
17
20
  /** Platform registry — drives config generation AND panel rendering */
18
21
  const PLATFORMS = [
@@ -1591,6 +1594,7 @@ codex_hooks = true`;
1591
1594
  intro.innerHTML = `<div class="setup-permissions-note">
1592
1595
  <strong>Permissions &amp; Security</strong>
1593
1596
  <p>Use this mode to turn on permission enforcement for supported clients. Claude Code is fully guided in this release. Gemini CLI, Cursor, VS Code, Windsurf, and Codex have native partial support, while Cline and LM Studio stay in the MCP and fallback lane for now. Other clients will be marked as coming soon.</p>
1597
+ <p class="setup-hint">Need the advanced client-by-client hook contract details? <a href="${HOOK_CONTRACT_DOC_URL}" target="_blank" rel="noopener noreferrer">Read the platform contract reference</a>.</p>
1594
1598
  </div>`;
1595
1599
  };
1596
1600
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dollhousemcp/mcp-server",
3
- "version": "2.0.27",
3
+ "version": "2.0.28",
4
4
  "description": "DollhouseMCP - A Model Context Protocol (MCP) server that enables dynamic AI persona management from markdown files, allowing Claude and other compatible AI assistants to activate and switch between different behavioral personas.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -20,11 +20,14 @@ RUN_DIR="$HOME/.dollhouse/run"
20
20
  PORT_FILE="$RUN_DIR/permission-server.port"
21
21
  AUTHORITY_FILE="$RUN_DIR/permission-authority.json"
22
22
  AUTHORITY_CACHE_TTL_SECONDS=2
23
- MAX_RETRIES=2
24
- INITIAL_TIMEOUT=5
23
+ MAX_RETRIES="${DOLLHOUSE_HOOK_MAX_RETRIES:-2}"
24
+ INITIAL_TIMEOUT="${DOLLHOUSE_HOOK_INITIAL_TIMEOUT:-5}"
25
25
  HOOK_PLATFORM="${DOLLHOUSE_HOOK_PLATFORM:-claude_code}"
26
26
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
27
27
 
28
+ [[ "$MAX_RETRIES" =~ ^[0-9]+$ ]] || MAX_RETRIES=2
29
+ [[ "$INITIAL_TIMEOUT" =~ ^[0-9]+$ ]] || INITIAL_TIMEOUT=5
30
+
28
31
  # Debug logging helper — writes to stderr so it doesn't pollute stdout
29
32
  debug() {
30
33
  if [[ "${DOLLHOUSE_HOOK_DEBUG:-0}" == "1" ]]; then
@@ -114,6 +117,37 @@ normalize_response() {
114
117
  end
115
118
  ' 2>/dev/null
116
119
  ;;
120
+ codex)
121
+ echo "$response" | jq -c '
122
+ if type == "object" and (keys | length) == 0 then
123
+ {
124
+ hookSpecificOutput: {
125
+ hookEventName: "PreToolUse",
126
+ permissionDecision: "allow",
127
+ permissionDecisionReason: ""
128
+ }
129
+ }
130
+ elif (.hookSpecificOutput.permissionDecision? | type) == "string" then
131
+ {
132
+ hookSpecificOutput: {
133
+ hookEventName: "PreToolUse",
134
+ permissionDecision: (if .hookSpecificOutput.permissionDecision == "allow" then "allow" else "deny" end),
135
+ permissionDecisionReason: (.hookSpecificOutput.permissionDecisionReason // .hookSpecificOutput.reason // .reason // .message // "")
136
+ }
137
+ }
138
+ elif (.decision? | type) == "string" and (.decision | IN("allow", "deny", "ask")) then
139
+ {
140
+ hookSpecificOutput: {
141
+ hookEventName: "PreToolUse",
142
+ permissionDecision: (if .decision == "allow" then "allow" else "deny" end),
143
+ permissionDecisionReason: (.reason // .message // "")
144
+ }
145
+ }
146
+ else
147
+ empty
148
+ end
149
+ ' 2>/dev/null
150
+ ;;
117
151
  *)
118
152
  echo "$response"
119
153
  ;;
@@ -7,11 +7,14 @@
7
7
 
8
8
  RUN_DIR="$HOME/.dollhouse/run"
9
9
  PORT_FILE="$RUN_DIR/permission-server.port"
10
- MAX_RETRIES=2
11
- INITIAL_TIMEOUT=5
10
+ MAX_RETRIES="${DOLLHOUSE_HOOK_MAX_RETRIES:-2}"
11
+ INITIAL_TIMEOUT="${DOLLHOUSE_HOOK_INITIAL_TIMEOUT:-5}"
12
12
  HOOK_PLATFORM="vscode"
13
13
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
14
 
15
+ [[ "$MAX_RETRIES" =~ ^[0-9]+$ ]] || MAX_RETRIES=2
16
+ [[ "$INITIAL_TIMEOUT" =~ ^[0-9]+$ ]] || INITIAL_TIMEOUT=5
17
+
15
18
  debug() {
16
19
  if [[ "${DOLLHOUSE_HOOK_DEBUG:-0}" == "1" ]]; then
17
20
  echo "[pretooluse-vscode] $*" >&2
@@ -7,11 +7,14 @@
7
7
 
8
8
  RUN_DIR="$HOME/.dollhouse/run"
9
9
  PORT_FILE="$RUN_DIR/permission-server.port"
10
- MAX_RETRIES=2
11
- INITIAL_TIMEOUT=5
10
+ MAX_RETRIES="${DOLLHOUSE_HOOK_MAX_RETRIES:-2}"
11
+ INITIAL_TIMEOUT="${DOLLHOUSE_HOOK_INITIAL_TIMEOUT:-5}"
12
12
  HOOK_PLATFORM="windsurf"
13
13
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
14
 
15
+ [[ "$MAX_RETRIES" =~ ^[0-9]+$ ]] || MAX_RETRIES=2
16
+ [[ "$INITIAL_TIMEOUT" =~ ^[0-9]+$ ]] || INITIAL_TIMEOUT=5
17
+
15
18
  debug() {
16
19
  if [[ "${DOLLHOUSE_HOOK_DEBUG:-0}" == "1" ]]; then
17
20
  echo "[pretooluse-windsurf] $*" >&2
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.DollhouseMCP/mcp-server",
4
4
  "title": "DollhouseMCP",
5
5
  "description": "OSS to create Personas, Skills, Templates, Agents, and Memories to customize your AI experience.",
6
- "version": "2.0.27",
6
+ "version": "2.0.28",
7
7
  "homepage": "https://dollhousemcp.com",
8
8
  "repository": {
9
9
  "type": "git",
@@ -29,7 +29,7 @@
29
29
  {
30
30
  "registryType": "npm",
31
31
  "identifier": "@dollhousemcp/mcp-server",
32
- "version": "2.0.27",
32
+ "version": "2.0.28",
33
33
  "transport": {
34
34
  "type": "stdio"
35
35
  }