@monster-orange/den 0.4.3 → 0.4.4

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 (58) hide show
  1. package/CHANGELOG.md +179 -0
  2. package/README.md +9 -9
  3. package/docs/distributions.md +215 -65
  4. package/package.json +1 -1
  5. package/src/shared/agents/catalog-runtime.ts +178 -0
  6. package/src/shared/agents/catalog.ts +343 -0
  7. package/src/shared/agents/den-claude.ts +14 -18
  8. package/src/shared/agents/runtime-search.ts +142 -15
  9. package/src/shared/agents/runtime.ts +18 -1
  10. package/src/shared/agents/runtimes.ts +23 -1
  11. package/src/shared/auth/sudo-vault.ts +16 -0
  12. package/src/shared/den-config.ts +57 -3
  13. package/src/shared/edge/status.ts +134 -0
  14. package/src/shared/environment-manager/health.ts +82 -8
  15. package/src/shared/environment-manager/workspaces.ts +54 -0
  16. package/src/shared/health/not-checked.ts +42 -0
  17. package/src/shared/health/rollup.ts +4 -0
  18. package/src/shared/process/spawn-streaming.ts +8 -3
  19. package/src/shared/sessions/client.ts +5 -1
  20. package/src/shared/sessions/host.ts +29 -1
  21. package/src/shared/sessions/protocol.ts +6 -1
  22. package/src/shared/sessions/types.ts +6 -0
  23. package/src/shared/stacks/status.ts +24 -0
  24. package/src/shared/strategies/registry.ts +5 -0
  25. package/src/shared/types/index.ts +6 -0
  26. package/src/ui/cli/agent-ops.ts +210 -19
  27. package/src/ui/cli/agents-command.ts +50 -21
  28. package/src/ui/cli/edge-command.ts +16 -3
  29. package/src/ui/cli/health-display.ts +4 -1
  30. package/src/ui/mcp/scope.ts +23 -3
  31. package/src/ui/mcp/server.ts +31 -7
  32. package/src/ui/mcp/tools.ts +327 -20
  33. package/src/ui/mcp/views.ts +450 -0
  34. package/src/ui/server/agent-routes.ts +118 -28
  35. package/src/ui/server/index.ts +77 -6
  36. package/src/ui/server/public/agent-start.js +46 -7
  37. package/src/ui/server/public/agents-page.js +232 -97
  38. package/src/ui/server/public/agents-section.js +3 -5
  39. package/src/ui/server/public/agents.html +12 -28
  40. package/src/ui/server/public/graph-model.js +2 -0
  41. package/src/ui/server/public/header.js +2 -3
  42. package/src/ui/server/public/health-rollup.js +20 -8
  43. package/src/ui/server/public/i18n/es.js +15 -13
  44. package/src/ui/server/public/i18n/ja.js +14 -13
  45. package/src/ui/server/public/index.html +17 -4
  46. package/src/ui/server/public/local-dev.html +28 -17
  47. package/src/ui/server/public/services.html +21 -5
  48. package/src/ui/server/public/settings-page.js +3 -2
  49. package/src/ui/server/public/settings-registry.js +225 -0
  50. package/src/ui/server/public/settings.html +90 -1
  51. package/src/ui/server/public/shell.js +28 -23
  52. package/src/ui/server/public/topbar.js +166 -206
  53. package/src/ui/server/readonly-routes.ts +29 -5
  54. package/src/ui/server/request-guard.ts +1 -1
  55. package/src/ui/server/route-classes.ts +14 -2
  56. package/src/ui/server/session-routes.ts +9 -1
  57. package/src/ui/server/sudo-store.ts +170 -6
  58. package/src/ui/server/workspace-routes.ts +10 -3
@@ -64,6 +64,13 @@ export const DEN_SETTING_CATEGORIES = [
64
64
  description:
65
65
  "Settings for one workspace. They follow the Workspace you view in the top bar.",
66
66
  },
67
+ {
68
+ id: "agents",
69
+ label: "Agents",
70
+ order: 55,
71
+ description:
72
+ "The runtimes agents run on, installed first. Paths you set are this machine's only: everywhere (config/local/user.yml) or for one workspace (config/local/workspaces.yml), which wins.",
73
+ },
67
74
  {
68
75
  id: "cli",
69
76
  label: "Command line",
@@ -416,6 +423,29 @@ export const DEN_SETTINGS = [
416
423
  write: (v, ctx) => ctx.saveWorkspace({ editor: v || null }),
417
424
  },
418
425
 
426
+ // ── Agents ─────────────────────────────────────────────────────────────
427
+ {
428
+ id: "agents.runtimes",
429
+ category: "agents",
430
+ label: "Runtimes",
431
+ description:
432
+ "Each runtime's status and version, the path in use and where it came from. Set path… runs its version command first and refuses a path that isn't that runtime.",
433
+ keywords: [
434
+ "runtime",
435
+ "claude",
436
+ "codex",
437
+ "gemini",
438
+ "grok",
439
+ "path",
440
+ "bin",
441
+ "cli",
442
+ "agent",
443
+ ],
444
+ scope: "user",
445
+ control: { type: "custom", render: renderRuntimes },
446
+ read: () => null,
447
+ },
448
+
419
449
  // ── Command line ───────────────────────────────────────────────────────
420
450
  {
421
451
  id: "cli.local_dev",
@@ -498,6 +528,53 @@ export const DEN_SETTINGS = [
498
528
  },
499
529
  read: () => null,
500
530
  },
531
+ {
532
+ // R8, LK-0: the admin password is for sudo, and nothing else. Den asks
533
+ // for it only when a command it runs needs sudo; here is whether it is
534
+ // saved, and Forget. Reading never wakes the vault.
535
+ id: "cli.sudo",
536
+ category: "cli",
537
+ label: "Admin password",
538
+ description:
539
+ "Used only when a command Den runs needs sudo, and asked for only then. It is no sign-in: nothing else needs it.",
540
+ keywords: ["admin", "password", "sudo", "forget", "vault", "1password"],
541
+ scope: "user",
542
+ control: {
543
+ type: "custom",
544
+ render(host, ctx) {
545
+ const draw = (st) => {
546
+ const saved = st?.saved;
547
+ const where =
548
+ saved === "vault"
549
+ ? `Saved in ${st.vault?.label || "the vault"}`
550
+ : saved === "session"
551
+ ? "Saved for this session"
552
+ : "Not saved";
553
+ host.innerHTML =
554
+ '<output class="set-ro" id="set-sudo"></output>' +
555
+ (saved
556
+ ? ' <button type="button" class="btn" id="set-sudo-forget">Forget</button>'
557
+ : "");
558
+ host.querySelector("output").textContent = where;
559
+ host
560
+ .querySelector("#set-sudo-forget")
561
+ ?.addEventListener("click", () =>
562
+ ctx.shell
563
+ .api("POST", "/api/sudo-password/forget", {})
564
+ .then(load)
565
+ .catch(load),
566
+ );
567
+ };
568
+ const load = () =>
569
+ fetch("/api/sudo-password/status", { cache: "no-store" })
570
+ .then((r) => (r.ok ? r.json() : null))
571
+ .then(draw)
572
+ .catch(() => draw(null));
573
+ void load();
574
+ },
575
+ },
576
+ read: () => null,
577
+ },
501
578
 
502
579
  // ── Distribution (read-only) ───────────────────────────────────────────
503
580
  distRow(
@@ -547,6 +624,154 @@ export const DEN_SETTINGS = [
547
624
  ),
548
625
  ];
549
626
 
627
+ // ── Settings › Agents › Runtimes (AG-0.5) ────────────────────────────────
628
+
629
+ const escR = (s) =>
630
+ String(s ?? "").replace(
631
+ /[&<>"']/g,
632
+ (c) =>
633
+ ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[
634
+ c
635
+ ],
636
+ );
637
+
638
+ /** Where a runtime's path came from, as the row says it. */
639
+ export const RUNTIME_SOURCE = {
640
+ workspace: "workspace",
641
+ setting: "global",
642
+ "login-shell": "detected · login shell",
643
+ PATH: "detected · PATH",
644
+ "well-known": "detected · install directory",
645
+ "interactive-shell": "detected · interactive shell",
646
+ };
647
+
648
+ /** One runtime's row (r: GET /api/agents/runtimes' entry). */
649
+ export function runtimeRowHtml(r, scopeWs) {
650
+ const id = escR(r.id);
651
+ const name =
652
+ `<b>${escR(r.label || r.id)}</b>` +
653
+ (r.vendor ? ` <span class="rt-v">${escR(r.vendor)}</span>` : "") +
654
+ (r.denTools === false
655
+ ? ` <span class="set-tag" data-den-tools="coming">Den tools: coming</span>`
656
+ : "");
657
+ const set = r.source === "workspace" || r.source === "setting";
658
+ const status = r.available
659
+ ? `<span class="rt-ok">✓ ${escR(r.version ? r.version : "installed")}</span>`
660
+ : `<span class="rt-bad">${escR(set ? r.why : "Not installed")}</span>`;
661
+ const where = r.path
662
+ ? `<span class="den-mono rt-p">${escR(r.path)}</span> <span class="set-tag" data-src="${escR(r.source || "")}">${escR(RUNTIME_SOURCE[r.source] || r.source || "")}</span>`
663
+ : set
664
+ ? `<span class="set-tag" data-src="${escR(r.source)}">${escR(RUNTIME_SOURCE[r.source])}</span>`
665
+ : "";
666
+ // A path set at the scope shown can be cleared from here.
667
+ const mine = scopeWs ? r.source === "workspace" : r.source === "setting";
668
+ return (
669
+ `<div class="rt" data-runtime="${id}" data-available="${r.available ? "true" : "false"}">` +
670
+ `<div class="rt-n">${name}</div>` +
671
+ `<div class="rt-s">${status}</div>` +
672
+ `<div class="rt-w">${where}</div>` +
673
+ `<div class="rt-a"><button type="button" class="btn sm" data-set-path="${id}">Set path…</button>` +
674
+ (mine
675
+ ? ` <button type="button" class="btn sm" data-clear-path="${id}">Clear</button>`
676
+ : "") +
677
+ `</div>` +
678
+ `<form class="rt-f" data-path-form="${id}" hidden>` +
679
+ `<label class="rt-fl" for="rt-in-${id}">Path to ${escR(r.label || r.id)}${scopeWs ? ` for ${escR(scopeWs)}` : " (every workspace)"}</label>` +
680
+ `<input type="text" id="rt-in-${id}" class="den-mono" spellcheck="false" autocomplete="off" placeholder="/absolute/path/to/${escR(r.id === "den-claude" ? "claude" : r.id)}" value="${escR(mine ? r.path || "" : "")}">` +
681
+ `<button type="submit" class="btn sm primary">Save</button> <button type="button" class="btn sm" data-cancel-path>Cancel</button>` +
682
+ `<div class="rt-err" role="alert" hidden></div></form>` +
683
+ `</div>`
684
+ );
685
+ }
686
+
687
+ /** The Runtimes row: the list, a workspace selector, Set path… and Clear. */
688
+ function renderRuntimes(host, ctx) {
689
+ const wss = ctx.workspaces || [];
690
+ let ws = "";
691
+ const draw = (rows, note) => {
692
+ host.innerHTML =
693
+ `<div class="rt-scope"><label for="rt-ws">Paths for</label> <select id="rt-ws" data-focus-key="rt-ws">` +
694
+ `<option value="">every workspace (user.yml)</option>` +
695
+ wss
696
+ .map(
697
+ (w) =>
698
+ `<option value="${escR(w)}"${w === ws ? " selected" : ""}>${escR(w)} (workspaces.yml)</option>`,
699
+ )
700
+ .join("") +
701
+ `</select></div>` +
702
+ (note ? `<div class="set-note">${escR(note)}</div>` : "") +
703
+ `<div class="rt-list" role="list">${rows.map((r) => runtimeRowHtml(r, ws)).join("")}</div>`;
704
+ };
705
+ const load = () => {
706
+ // Under an invalid config the runtimes answer 422: say why, ask nothing.
707
+ if (ctx.shell?.configInvalid?.() === true)
708
+ return draw([], "Fix the config first: the runtimes are read from it.");
709
+ return ctx.shell
710
+ .api(
711
+ "GET",
712
+ `/api/agents/runtimes${ws ? `?workspace=${encodeURIComponent(ws)}` : ""}`,
713
+ )
714
+ .then((b) =>
715
+ // Only what has a CLI to find: generic is the persona's argv.
716
+ draw((b?.runtimes || []).filter((r) => r.setting)),
717
+ )
718
+ .catch((e) => draw([], e?.message || "The runtimes did not load."));
719
+ };
720
+ host.addEventListener("change", (e) => {
721
+ if (e.target.id !== "rt-ws") return;
722
+ ws = e.target.value;
723
+ void load();
724
+ });
725
+ host.addEventListener("click", async (e) => {
726
+ const b = e.target.closest("button");
727
+ if (!b) return;
728
+ const row = b.closest(".rt");
729
+ const form = row?.querySelector(".rt-f");
730
+ if (b.dataset.setPath && form) {
731
+ form.hidden = false;
732
+ form.querySelector("input").focus();
733
+ } else if (b.hasAttribute("data-cancel-path") && form) {
734
+ form.hidden = true;
735
+ } else if (b.dataset.clearPath) {
736
+ await savePath(b.dataset.clearPath, null, form);
737
+ }
738
+ });
739
+ host.addEventListener("submit", async (e) => {
740
+ e.preventDefault();
741
+ const form = e.target.closest(".rt-f");
742
+ if (!form) return;
743
+ const bin = form.querySelector("input").value.trim();
744
+ if (!bin) return;
745
+ await savePath(form.dataset.pathForm, bin, form);
746
+ });
747
+ async function savePath(id, bin, form) {
748
+ const err = form?.querySelector(".rt-err");
749
+ const btns = form ? [...form.querySelectorAll("button")] : [];
750
+ btns.forEach((x) => (x.disabled = true));
751
+ try {
752
+ await ctx.shell.api(
753
+ "POST",
754
+ `/api/agents/runtimes/${encodeURIComponent(id)}/path`,
755
+ {
756
+ scope: ws ? "workspace" : "global",
757
+ ...(ws ? { workspace: ws } : {}),
758
+ bin,
759
+ },
760
+ );
761
+ await load();
762
+ } catch (x) {
763
+ // Its refusal is shown here, under the field, not as a toast.
764
+ if (err) {
765
+ err.textContent = x?.message || "Not saved";
766
+ err.hidden = false;
767
+ }
768
+ } finally {
769
+ btns.forEach((x) => (x.disabled = false));
770
+ }
771
+ }
772
+ void load();
773
+ }
774
+
550
775
  /** Register Den's categories and rows with the shell's SETTINGS registry. */
551
776
  export function registerDenSettings(shell) {
552
777
  for (const c of DEN_SETTING_CATEGORIES) shell.registerSettingsCategory(c);
@@ -95,7 +95,9 @@
95
95
  top: 0;
96
96
  z-index: 4;
97
97
  background: var(--bg);
98
- box-shadow: 0 -20px 0 0 var(--bg), 0 12px 0 0 var(--bg);
98
+ box-shadow:
99
+ 0 -20px 0 0 var(--bg),
100
+ 0 12px 0 0 var(--bg);
99
101
  }
100
102
  .ph .act {
101
103
  margin-left: auto;
@@ -352,6 +354,93 @@
352
354
  border-radius: 999px;
353
355
  padding: 0 6px;
354
356
  }
357
+ /* Settings › Agents › Runtimes (AG-0.5): one row per runtime. */
358
+ .set-custom {
359
+ flex: 1 1 100%;
360
+ min-width: 0;
361
+ }
362
+ .rt-scope {
363
+ display: flex;
364
+ align-items: center;
365
+ gap: 8px;
366
+ flex-wrap: wrap;
367
+ font-size: 13px;
368
+ color: var(--muted);
369
+ margin-bottom: 6px;
370
+ }
371
+ .rt-list {
372
+ display: grid;
373
+ border: 1px solid var(--border);
374
+ border-radius: var(--radius-button);
375
+ }
376
+ .rt {
377
+ display: grid;
378
+ grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr) minmax(
379
+ 0,
380
+ 2fr
381
+ ) auto;
382
+ align-items: center;
383
+ gap: 4px 12px;
384
+ padding: 8px 10px;
385
+ font-size: 13px;
386
+ }
387
+ .rt + .rt {
388
+ border-top: 1px solid var(--border);
389
+ }
390
+ .rt-v {
391
+ color: var(--muted);
392
+ }
393
+ .rt-n .set-tag {
394
+ white-space: nowrap;
395
+ }
396
+ .rt-ok {
397
+ color: var(--ok);
398
+ }
399
+ .rt-bad {
400
+ color: var(--muted);
401
+ overflow-wrap: anywhere;
402
+ }
403
+ .rt-w {
404
+ min-width: 0;
405
+ overflow-wrap: anywhere;
406
+ }
407
+ .rt-p {
408
+ font-size: 12px;
409
+ }
410
+ .rt-f {
411
+ grid-column: 1 / -1;
412
+ display: flex;
413
+ flex-wrap: wrap;
414
+ align-items: center;
415
+ gap: 6px;
416
+ }
417
+ .rt-f[hidden],
418
+ .rt-err[hidden] {
419
+ display: none;
420
+ }
421
+ .rt-fl {
422
+ flex-basis: 100%;
423
+ color: var(--muted);
424
+ font-size: 12.5px;
425
+ }
426
+ .rt-f input {
427
+ flex: 1 1 260px;
428
+ min-width: 0;
429
+ }
430
+ .rt-err {
431
+ flex-basis: 100%;
432
+ color: var(--bad);
433
+ font-size: 12.5px;
434
+ overflow-wrap: anywhere;
435
+ }
436
+ @media (max-width: 640px) {
437
+ .rt {
438
+ grid-template-columns: minmax(0, 1fr) auto;
439
+ }
440
+ .rt-w {
441
+ grid-column: 1 / -1;
442
+ }
443
+ }
355
444
  .set-langs {
356
445
  display: grid;
357
446
  gap: 4px;
@@ -22,10 +22,10 @@
22
22
  * each part an .ib button with a .pop menu (an ARIA menu: ↑/↓,
23
23
  * Home, End, Enter; Escape and a click outside close it) ·
24
24
  * the ⌘K search field (R5) · the status chip (R6) · the Runs
25
- * chip (R7) · the lock chip (R8) · What's new · ◐ ▾ (R10).
25
+ * chip (R7) · the sudo chip (R8, only while asked) · What's new · ◐ ▾ (R10).
26
26
  * shell.js draws them; topbar.js (through header.js) drives the
27
27
  * chips, the command palette (R17), the status drawer (R15) and
28
- * the lock popover; appearance.js the user menu (dev.20).
28
+ * the admin-password prompt; appearance.js the user menu (dev.20).
29
29
  * rail Pinned, then the NAV registry by GROUPS (Develop, Verify,
30
30
  * Ship, a distribution's own), then Settings and the collapse
31
31
  * toggle. One tab stop (the current page); ↑/↓, Home and End
@@ -251,24 +251,20 @@
251
251
  "dock.err": "failed",
252
252
  "dock.last": "Last run: {s} ↗",
253
253
  "dock.resize": "Drag to resize",
254
- // The lock chip and its popover (R8).
255
- "lock.locked": "Locked",
256
- "lock.unlocked": "Unlocked",
257
- "lock.title": "Unlock to start, stop and restart services and agents",
258
- "lock.nopw": "Passwordless sudo (NOPASSWD): no admin password needed",
259
- "lock.session": "Unlocked for this session",
260
- "lock.sessionDesc":
261
- "Unlocked for this session: start, stop and restart work on every page.",
254
+ // The admin password, asked only when a command needs sudo (R8, LK-0).
255
+ "lock.waiting": "Admin password needed",
256
+ "lock.needs": "{cmd} needs your admin password",
257
+ "lock.cmd": "A command",
262
258
  "lock.cfg":
263
259
  "Saving the password is off: set secrets.provider and secrets.refs.sudo in {file}.",
264
260
  "lock.update": "Update stored password",
265
261
  "lock.saveTo": "Save to {s}",
266
262
  "lock.pw": "Admin password",
267
- "lock.go": "Unlock",
263
+ "lock.go": "Continue",
268
264
  "lock.checking": "Checking…",
269
- "lock.note": "Den asks once per session. Agents can't unlock.",
270
- "lock.again": "Enter the password again…",
271
- "lock.saveFailed": "Unlocked, but saving to {s} failed: {err}",
265
+ "lock.note": "Den uses it only for sudo. Agents can't enter it.",
266
+ "lock.saved": "Admin password kept for this session",
267
+ "lock.saveFailed": "Kept for this session, but saving to {s} failed: {err}",
272
268
  "lock.wrong": "Incorrect password — try again",
273
269
  "lock.reqFailed": "Request failed — is the server running?",
274
270
  // What's new (R9) and the stale-server pill.
@@ -402,11 +398,13 @@
402
398
  "set.graph.density": "Cards",
403
399
  "set.graph.panelWidth": "Details panel",
404
400
  "set.workspace.editor": "IDE for this workspace",
401
+ "set.agents.runtimes": "Runtimes",
405
402
  "set.cli.local_dev": "Local environment",
406
403
  "set.cli.cli_tabs": "Terminal tabs",
407
404
  "set.cli.log_window_lines": "Log pane lines",
408
405
  "set.cli.web_port": "Your web port",
409
406
  "set.cli.signin": "Browser sign-in",
407
+ "set.cli.sudo": "Admin password",
410
408
  "set.distribution.brand": "Product name",
411
409
  "set.distribution.github_org": "GitHub org",
412
410
  "set.distribution.orgs": "Org labels",
@@ -495,6 +493,11 @@
495
493
  "ag.rt.fix":
496
494
  "Install it, or set {key} to its path in {file}, or run {cmd}.",
497
495
  "ag.rt.unknown": "No runtime {id} here.",
496
+ "ag.rt.denTools": "Den tools: coming",
497
+ "ag.rt.ownCli":
498
+ "It runs as its own CLI in the workspace: Den's tools and permission modes are coming for it.",
499
+ "ag.rt.notInstalled": "not installed",
500
+ "ag.rt.needsArgv": "the persona has no argv template",
498
501
  };
499
502
  // The languages the UI has a catalog for, each named in itself (a name is
500
503
  // never translated). The server's list is ui-language.ts's UI_LANGUAGES.
@@ -1347,9 +1350,9 @@
1347
1350
 
1348
1351
  // ── the frame ──────────────────────────────────────────────────────────
1349
1352
  // The top bar after the scope, in the prototype's order (dev.20): the ⌘K
1350
- // search field (R5), then .chips: status (R6), Runs (R7), the lock (R8),
1353
+ // search field (R5), then .chips: status (R6), Runs (R7), the sudo ask (R8),
1351
1354
  // What's new (R9) and ◐ ▾ (R10, appearance.js fills #den-user). The
1352
- // chips start neutral ("Checking…", "Runs", "Locked") and topbar.js
1355
+ // chips start neutral ("Checking…", "Runs"; the sudo chip hidden) and topbar.js
1353
1356
  // paints them: a cold load never shows a partial red.
1354
1357
  var chips =
1355
1358
  '<button type="button" class="den-search" id="den-search" aria-haspopup="dialog" aria-keyshortcuts="Meta+K Control+K" aria-label="' +
@@ -1366,10 +1369,10 @@
1366
1369
  '<span class="den-dot off" aria-hidden="true"></span><span class="den-n den-lbl-l">' +
1367
1370
  esc(t("runs.title")) +
1368
1371
  "</span></button>" +
1369
- '<button type="button" class="den-chip" id="den-lock" aria-haspopup="dialog" aria-expanded="false" title="' +
1370
- esc(t("lock.title")) +
1371
- '"><span class="den-lock-ic" aria-hidden="true">🔒</span><span class="den-lbl-l">' +
1372
- esc(t("lock.locked")) +
1372
+ // LK-0: shown only while a run waits for the admin password (topbar.js).
1373
+ '<button type="button" class="den-chip" id="den-lock" aria-haspopup="dialog" aria-expanded="false" hidden>' +
1374
+ '<span class="den-lock-ic" aria-hidden="true">🔑</span><span class="den-lbl-l">' +
1375
+ esc(t("lock.waiting")) +
1373
1376
  "</span></button>" +
1374
1377
  '<button class="den-ib v-pill" id="version-pill" onclick="openChangelog()" title="' +
1375
1378
  esc(t("v.title")) +
@@ -3853,7 +3856,7 @@
3853
3856
  " max-height: calc(100vh - 72px); overflow-y: auto; box-sizing: border-box;",
3854
3857
  " font: 14px/1.45 var(--sans); color: var(--text); }",
3855
3858
  ".den-pop:focus { outline: none; }",
3856
- // LK-1: the lock popover is a <dialog> in the top layer (a popover, or
3859
+ // LK-1: the admin-password prompt is a <dialog> in the top layer (a popover, or
3857
3860
  // a modal over another modal): placed by its top/left, no UA box.
3858
3861
  "dialog.den-pop { margin: 0; inset: auto; max-width: none; }",
3859
3862
  "dialog.den-pop::backdrop { background: transparent; }",
@@ -3909,6 +3912,8 @@
3909
3912
  ".den-chip .den-n { font-variant-numeric: tabular-nums; }",
3910
3913
  ".den-chip .den-dot, .den-drawer .den-dot, .den-pal .den-dot { margin: 0; }",
3911
3914
  ".den-chip[aria-expanded='true'] { border-color: var(--border-strong); }",
3915
+ ".den-chip[hidden] { display: none; }",
3916
+ "#den-lock { border-color: color-mix(in srgb, var(--warn) 55%, var(--border)); }",
3912
3917
  ".den-dot.run { background: var(--accent); animation: den-pulse 1.2s infinite; }",
3913
3918
  ".den-dot.pending { background: var(--faint); animation: den-pulse 1.2s infinite; }",
3914
3919
  "@keyframes den-pulse { 50% { opacity: .35; } }",
@@ -3973,7 +3978,7 @@
3973
3978
  ".den-pal li.den-pal-empty { cursor: default; color: var(--muted); }",
3974
3979
  ".den-pal .den-foot { display: flex; gap: 14px; padding: 8px 14px; border-top: 1px solid var(--border);",
3975
3980
  " color: var(--faint); font-size: 12px; flex-wrap: wrap; }",
3976
- // The lock popover (.pop form, .pop input, .note: R8)
3981
+ // The admin-password prompt (.pop form, .pop input, .note: R8, LK-0)
3977
3982
  ".den-pop form { display: grid; gap: 8px; padding: 6px 8px 8px; width: 260px; box-sizing: content-box; margin: 0; }",
3978
3983
  ".den-pop input:not([type='checkbox']) { border: 1px solid var(--border); background: var(--bg); color: var(--text);",
3979
3984
  " border-radius: var(--radius-button); padding: 6px 8px; font: inherit; font-size: 13.3333px; line-height: normal; }",
@@ -3982,7 +3987,7 @@
3982
3987
  ".den-pop .den-lock-save { display: flex; gap: 6px; align-items: center; color: var(--muted); font-size: 12.5px; }",
3983
3988
  ".den-pop .den-lock-save[hidden], .den-pop .den-lock-err[hidden], .den-mslot[hidden] { display: none; }",
3984
3989
  ".den-pop .den-lock-err { color: var(--bad); font-size: 12.5px; }",
3985
- ".den-pop .den-lock-desc { padding: 2px 8px 6px; color: var(--muted); font-size: 12.5px; max-width: 260px; }",
3990
+ ".den-pop #den-lock-t { overflow-wrap: anywhere; }",
3986
3991
  // What's new (.ib.whatsnew + .badge-dot)
3987
3992
  "#den-shell .v-pill { font: inherit; padding: 4px 8px; border: 1px solid transparent;",
3988
3993
  " border-radius: var(--radius-button); color: var(--muted); flex-shrink: 0; }",