@monster-orange/den 0.4.23 → 0.4.25
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 +51 -0
- package/docs/distributions.md +47 -0
- package/package.json +1 -1
- package/service/install.sh +5 -3
- package/src/shared/agents/den-claude.ts +13 -2
- package/src/shared/agents/runtime.ts +24 -2
- package/src/shared/hub.ts +345 -0
- package/src/ui/cli/agent-ops.ts +171 -3
- package/src/ui/cli/doctor-command.ts +62 -1
- package/src/ui/cli/doctor-where.ts +60 -13
- package/src/ui/server/agent-routes.ts +24 -1
- package/src/ui/server/index.ts +13 -0
- package/src/ui/server/public/combobox.js +150 -0
- package/src/ui/server/public/error-page.js +26 -14
- package/src/ui/server/public/i18n/es.js +4 -2
- package/src/ui/server/public/i18n/ja.js +4 -2
- package/src/ui/server/public/settings-registry.js +218 -25
- package/src/ui/server/public/settings.html +149 -1
- package/src/ui/server/public/shell.js +21 -14
- package/src/ui/server/public/svc-panel.js +54 -15
- package/src/ui/server/route-classes.ts +4 -0
- package/src/ui/server/ui-assets.ts +2 -0
|
@@ -451,9 +451,115 @@
|
|
|
451
451
|
min-width: 0;
|
|
452
452
|
max-width: 100%;
|
|
453
453
|
}
|
|
454
|
+
/* A select never cuts its own words: it wraps to its own line. */
|
|
455
|
+
.rot-f select {
|
|
456
|
+
min-width: min(100%, max-content);
|
|
457
|
+
}
|
|
454
458
|
.rot-add {
|
|
455
459
|
margin-top: 10px;
|
|
456
460
|
}
|
|
461
|
+
/* HUB-1.5: Settings › Agents › Hub. */
|
|
462
|
+
.hub-st {
|
|
463
|
+
display: grid;
|
|
464
|
+
grid-template-columns: max-content 1fr;
|
|
465
|
+
gap: 4px 12px;
|
|
466
|
+
margin: 0;
|
|
467
|
+
}
|
|
468
|
+
.hub-st dt {
|
|
469
|
+
color: var(--muted);
|
|
470
|
+
}
|
|
471
|
+
.hub-st dd {
|
|
472
|
+
margin: 0;
|
|
473
|
+
min-width: 0;
|
|
474
|
+
overflow-wrap: anywhere;
|
|
475
|
+
}
|
|
476
|
+
.hub-tools {
|
|
477
|
+
display: flex;
|
|
478
|
+
gap: 8px;
|
|
479
|
+
align-items: center;
|
|
480
|
+
margin-top: 10px;
|
|
481
|
+
}
|
|
482
|
+
/* D30: the add form: each control with its label, wrapping. */
|
|
483
|
+
.rot-pick {
|
|
484
|
+
display: inline-flex;
|
|
485
|
+
align-items: center;
|
|
486
|
+
gap: 6px;
|
|
487
|
+
min-width: 0;
|
|
488
|
+
flex: 1 1 200px;
|
|
489
|
+
flex-wrap: wrap;
|
|
490
|
+
}
|
|
491
|
+
.rot-pick[hidden] {
|
|
492
|
+
display: none;
|
|
493
|
+
}
|
|
494
|
+
.rot-pick input[type="number"] {
|
|
495
|
+
width: 6em;
|
|
496
|
+
flex: 0 0 auto;
|
|
497
|
+
}
|
|
498
|
+
.rot-never {
|
|
499
|
+
display: inline-flex;
|
|
500
|
+
align-items: center;
|
|
501
|
+
gap: 4px;
|
|
502
|
+
}
|
|
503
|
+
.rot-gone {
|
|
504
|
+
color: var(--muted);
|
|
505
|
+
}
|
|
506
|
+
/* The shared picker (combobox.js). */
|
|
507
|
+
.den-combo {
|
|
508
|
+
display: inline-flex;
|
|
509
|
+
align-items: center;
|
|
510
|
+
gap: 6px;
|
|
511
|
+
min-width: 0;
|
|
512
|
+
flex: 1 1 200px;
|
|
513
|
+
flex-wrap: wrap;
|
|
514
|
+
}
|
|
515
|
+
.den-combo-box {
|
|
516
|
+
position: relative;
|
|
517
|
+
flex: 1 1 160px;
|
|
518
|
+
min-width: 0;
|
|
519
|
+
}
|
|
520
|
+
.den-combo-box input {
|
|
521
|
+
width: 100%;
|
|
522
|
+
box-sizing: border-box;
|
|
523
|
+
}
|
|
524
|
+
.den-combo-list {
|
|
525
|
+
position: absolute;
|
|
526
|
+
z-index: 20;
|
|
527
|
+
left: 0;
|
|
528
|
+
right: 0;
|
|
529
|
+
top: calc(100% + 2px);
|
|
530
|
+
max-height: 240px;
|
|
531
|
+
overflow-y: auto;
|
|
532
|
+
margin: 0;
|
|
533
|
+
padding: 4px;
|
|
534
|
+
list-style: none;
|
|
535
|
+
background: var(--surface);
|
|
536
|
+
border: 1px solid var(--border);
|
|
537
|
+
border-radius: 8px;
|
|
538
|
+
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
|
|
539
|
+
}
|
|
540
|
+
.den-combo-list[hidden] {
|
|
541
|
+
display: none;
|
|
542
|
+
}
|
|
543
|
+
.den-combo-list li {
|
|
544
|
+
display: flex;
|
|
545
|
+
flex-wrap: wrap;
|
|
546
|
+
justify-content: space-between;
|
|
547
|
+
gap: 2px 8px;
|
|
548
|
+
padding: 5px 8px;
|
|
549
|
+
border-radius: 6px;
|
|
550
|
+
cursor: pointer;
|
|
551
|
+
}
|
|
552
|
+
.den-combo-list li[aria-selected="true"] {
|
|
553
|
+
background: var(--sunken);
|
|
554
|
+
}
|
|
555
|
+
.den-combo-list li[aria-disabled="true"] {
|
|
556
|
+
color: var(--muted);
|
|
557
|
+
cursor: default;
|
|
558
|
+
}
|
|
559
|
+
.den-combo-s {
|
|
560
|
+
color: var(--muted);
|
|
561
|
+
font-size: 12px;
|
|
562
|
+
}
|
|
457
563
|
.rot-ov caption {
|
|
458
564
|
text-align: left;
|
|
459
565
|
color: var(--muted);
|
|
@@ -464,7 +570,49 @@
|
|
|
464
570
|
.rot-ov td {
|
|
465
571
|
padding: 4px 10px 4px 0;
|
|
466
572
|
text-align: left;
|
|
467
|
-
|
|
573
|
+
/* Break a key at its "_", never mid-word. */
|
|
574
|
+
overflow-wrap: normal;
|
|
575
|
+
word-break: normal;
|
|
576
|
+
}
|
|
577
|
+
/* Narrow: each override is a card of label/value lines, so no
|
|
578
|
+
column is squeezed into breaking words mid-letter. */
|
|
579
|
+
@media (max-width: 560px) {
|
|
580
|
+
.rot-ov thead {
|
|
581
|
+
position: absolute;
|
|
582
|
+
width: 1px;
|
|
583
|
+
height: 1px;
|
|
584
|
+
overflow: hidden;
|
|
585
|
+
clip: rect(0 0 0 0);
|
|
586
|
+
}
|
|
587
|
+
.rot-ov,
|
|
588
|
+
.rot-ov tbody,
|
|
589
|
+
.rot-ov tr,
|
|
590
|
+
.rot-ov td {
|
|
591
|
+
display: block;
|
|
592
|
+
width: 100%;
|
|
593
|
+
}
|
|
594
|
+
.rot-ov caption {
|
|
595
|
+
display: block;
|
|
596
|
+
}
|
|
597
|
+
.rot-ov tr[data-rot-row] {
|
|
598
|
+
padding: 6px 0;
|
|
599
|
+
border-top: 1px solid var(--border);
|
|
600
|
+
}
|
|
601
|
+
.rot-ov td {
|
|
602
|
+
display: flex;
|
|
603
|
+
gap: 8px;
|
|
604
|
+
padding: 2px 0;
|
|
605
|
+
overflow-wrap: anywhere;
|
|
606
|
+
}
|
|
607
|
+
.rot-ov td[data-l]::before {
|
|
608
|
+
content: attr(data-l);
|
|
609
|
+
flex: 0 0 64px;
|
|
610
|
+
color: var(--muted);
|
|
611
|
+
font-family: inherit;
|
|
612
|
+
}
|
|
613
|
+
.rot-ov td.rot-act {
|
|
614
|
+
padding-top: 4px;
|
|
615
|
+
}
|
|
468
616
|
}
|
|
469
617
|
.rt-fl {
|
|
470
618
|
flex-basis: 100%;
|
|
@@ -426,9 +426,10 @@
|
|
|
426
426
|
"err.signin.claimOrg":
|
|
427
427
|
"Members of the {org} GitHub org can claim it, in a browser on {host}.",
|
|
428
428
|
"err.signin.claimHere": "This is my computer: connect GitHub",
|
|
429
|
+
"err.signin.claimHereOn": "On {host} itself: connect GitHub",
|
|
430
|
+
"err.signin.claimOrgOnly":
|
|
431
|
+
"Members of the {org} GitHub org can claim it, from a browser on {host}. Open this page on {host}, or set web.owner_github to sign in from anywhere.",
|
|
429
432
|
"err.signin.claimAs": "Sign in as @{login}",
|
|
430
|
-
"err.signin.claimHereNote":
|
|
431
|
-
"This is my computer works only in a browser on {host}. Anywhere else it opens nothing.",
|
|
432
433
|
"err.signin.claimChecking": "Checking this is {host}…",
|
|
433
434
|
"err.signin.githubRefused":
|
|
434
435
|
"@{login} isn't the owner of this {name}. Only the owner's account can use Sign in with GitHub. Use Approve this computer instead.",
|
|
@@ -855,6 +856,7 @@
|
|
|
855
856
|
"ver.usedLatest": "{n} now tracks the latest version",
|
|
856
857
|
"set.agents.personas": "Personas",
|
|
857
858
|
"set.agents.rotation": "Rotation",
|
|
859
|
+
"set.agents.hub": "Hub",
|
|
858
860
|
// The Agents tab (AG-4a-term, agents-page.js).
|
|
859
861
|
"n.agents": "Agents",
|
|
860
862
|
"kind.agent": "Agent",
|
|
@@ -2320,28 +2322,31 @@
|
|
|
2320
2322
|
var down = Object.prototype.hasOwnProperty.call(off, id)
|
|
2321
2323
|
? !!off[id]
|
|
2322
2324
|
: state.health[id] === "down";
|
|
2325
|
+
// D27: a real link (never popup-blocked, as window.open was in Safari).
|
|
2326
|
+
var live = !down && /^https?:\/\//i.test(url);
|
|
2323
2327
|
return (
|
|
2324
|
-
'<
|
|
2328
|
+
'<a class="den-pvisit" tabindex="-1" data-visit="' +
|
|
2325
2329
|
esc(id) +
|
|
2326
2330
|
'" data-url="' +
|
|
2327
2331
|
esc(url) +
|
|
2328
|
-
'"
|
|
2332
|
+
'"' +
|
|
2333
|
+
(live
|
|
2334
|
+
? ' href="' + esc(url) + '" target="_blank" rel="noopener noreferrer"'
|
|
2335
|
+
: ' role="link" aria-disabled="true"') +
|
|
2336
|
+
' aria-label="' +
|
|
2329
2337
|
esc(t("act.visitX", { s: id })) +
|
|
2330
2338
|
'" title="' +
|
|
2331
2339
|
esc(down ? t("svp.notRunning") : url) +
|
|
2332
|
-
'"'
|
|
2333
|
-
(down ? ' aria-disabled="true"' : "") +
|
|
2334
|
-
'><span aria-hidden="true">↗</span></button>'
|
|
2340
|
+
'"><span aria-hidden="true">↗</span></a>'
|
|
2335
2341
|
);
|
|
2336
2342
|
}
|
|
2343
|
+
/** A pin's Visit ↗ clicked: true when the link opens by itself. */
|
|
2337
2344
|
function visitPin(b) {
|
|
2338
|
-
if (b.getAttribute("aria-disabled") === "true") {
|
|
2345
|
+
if (b.getAttribute("aria-disabled") === "true" || !b.hasAttribute("href")) {
|
|
2339
2346
|
toast(t("svp.notRunning"));
|
|
2340
|
-
return;
|
|
2347
|
+
return false;
|
|
2341
2348
|
}
|
|
2342
|
-
|
|
2343
|
-
if (url && /^https?:\/\//i.test(url))
|
|
2344
|
-
window.open(url, "_blank", "noopener");
|
|
2349
|
+
return true;
|
|
2345
2350
|
}
|
|
2346
2351
|
// 0.4.18: the service panel (svc-panel.js registers it).
|
|
2347
2352
|
var servicePanel = null;
|
|
@@ -2653,8 +2658,7 @@
|
|
|
2653
2658
|
var tgt = e.target;
|
|
2654
2659
|
var v = tgt && tgt.closest ? tgt.closest("[data-visit]") : null;
|
|
2655
2660
|
if (v) {
|
|
2656
|
-
e.preventDefault();
|
|
2657
|
-
visitPin(v);
|
|
2661
|
+
if (!visitPin(v)) e.preventDefault();
|
|
2658
2662
|
return;
|
|
2659
2663
|
}
|
|
2660
2664
|
// 0.4.18: a plain click (or Enter) on a pinned row opens its panel;
|
|
@@ -5018,6 +5022,9 @@
|
|
|
5018
5022
|
".den-btn.primary { background: var(--accent-fill); border-color: var(--accent-fill); color: var(--on-accent); }",
|
|
5019
5023
|
".den-btn.sm { padding: 2px 9px; font-size: 12.5px; }",
|
|
5020
5024
|
".den-btn:disabled { opacity: .6; cursor: default; }",
|
|
5025
|
+
// D27: Visit is a link styled as a button.
|
|
5026
|
+
"a.den-btn, a.den-pvisit { text-decoration: none; display: inline-flex; align-items: center; justify-content: center; }",
|
|
5027
|
+
"a.den-pvisit { display: grid; }",
|
|
5021
5028
|
// Status drawer (.scrim, .drawer, .prob-card, .grp, .svl: R15)
|
|
5022
5029
|
".den-scrim { position: fixed; inset: 0; background: rgb(0 0 0 / 0.35); z-index: 40; }",
|
|
5023
5030
|
".den-drawer { position: fixed; top: 0; right: 0; bottom: 0; width: min(420px, 100%); z-index: 41;",
|
|
@@ -67,6 +67,9 @@ const S = {
|
|
|
67
67
|
testing: new Set(),
|
|
68
68
|
/** The open panel: { id, el, opener, scrim, timer }. */
|
|
69
69
|
open: null,
|
|
70
|
+
/** D27: a pointer is down inside the panel: repaint after it is up. */
|
|
71
|
+
pressed: false,
|
|
72
|
+
syncLater: false,
|
|
70
73
|
};
|
|
71
74
|
|
|
72
75
|
/** Start or Restart from here: "starting…" until health says, or the timeout. */
|
|
@@ -228,14 +231,21 @@ function testsHtml(s) {
|
|
|
228
231
|
return `<span class="den-svp-${ok ? "ok" : "bad"}">${esc(t(ok ? "svp.testsOk" : "svp.testsBad", { t: at }))}</span>`;
|
|
229
232
|
}
|
|
230
233
|
|
|
231
|
-
/**
|
|
234
|
+
/**
|
|
235
|
+
* Visit: a real link, styled as a button (D27). A native target=_blank link
|
|
236
|
+
* is never popup-blocked (Safari blocked window.open from the delegated
|
|
237
|
+
* click) and works with no script. Off (down, starting): no href,
|
|
238
|
+
* aria-disabled, and a click says why (visit()).
|
|
239
|
+
*/
|
|
232
240
|
export function visitHtml(s, cls, label) {
|
|
233
241
|
if (!s.url) return "";
|
|
234
|
-
const on = visitable(s);
|
|
242
|
+
const on = visitable(s) && /^https?:\/\//i.test(s.url);
|
|
235
243
|
return (
|
|
236
|
-
`<
|
|
237
|
-
|
|
238
|
-
|
|
244
|
+
`<a class="${cls}" data-visit="${esc(s.id)}" data-url="${esc(s.url)}"` +
|
|
245
|
+
(on
|
|
246
|
+
? ` href="${esc(s.url)}" target="_blank" rel="noopener noreferrer"`
|
|
247
|
+
: ` role="link" tabindex="0" aria-disabled="true"`) +
|
|
248
|
+
` aria-label="${esc(t("act.visitX", { s: s.id }))}" title="${esc(on ? s.url : t("svp.notRunning"))}">${label}</a>`
|
|
239
249
|
);
|
|
240
250
|
}
|
|
241
251
|
|
|
@@ -335,6 +345,12 @@ function sync() {
|
|
|
335
345
|
publishUrls();
|
|
336
346
|
const o = S.open;
|
|
337
347
|
if (!o) return;
|
|
348
|
+
// D27: never repaint under a press: the button pressed would be replaced
|
|
349
|
+
// between pointerdown and click, and the click would land on the panel.
|
|
350
|
+
if (S.pressed) {
|
|
351
|
+
S.syncLater = true;
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
338
354
|
const a = document.activeElement;
|
|
339
355
|
const mine = a && o.el.contains(a) && a !== o.el;
|
|
340
356
|
const key = mine
|
|
@@ -451,15 +467,20 @@ export function panelOpen() {
|
|
|
451
467
|
const env = () => S.deps.currentEnv?.() ?? "local";
|
|
452
468
|
const runs = () => window.denShell?.runs;
|
|
453
469
|
|
|
454
|
-
/**
|
|
470
|
+
/**
|
|
471
|
+
* Visit, clicked: a live link opens by itself (the browser follows it, so
|
|
472
|
+
* true: leave the click alone); an off one says why (false: the caller
|
|
473
|
+
* prevents the click).
|
|
474
|
+
*/
|
|
455
475
|
export function visit(btn) {
|
|
456
|
-
if (
|
|
476
|
+
if (
|
|
477
|
+
btn.getAttribute("aria-disabled") === "true" ||
|
|
478
|
+
!btn.hasAttribute("href")
|
|
479
|
+
) {
|
|
457
480
|
window.denShell?.toast?.(t("svp.notRunning"));
|
|
458
|
-
return;
|
|
481
|
+
return false;
|
|
459
482
|
}
|
|
460
|
-
|
|
461
|
-
if (!url || !/^https?:\/\//i.test(url)) return;
|
|
462
|
-
window.open(url, "_blank", "noopener");
|
|
483
|
+
return true;
|
|
463
484
|
}
|
|
464
485
|
|
|
465
486
|
/** A stream into a runs-dock tab: Start, Stop, Restart (exec), Logs, tests. */
|
|
@@ -635,6 +656,27 @@ export function initServicePanel(deps = {}) {
|
|
|
635
656
|
sync();
|
|
636
657
|
});
|
|
637
658
|
window.addEventListener("den:pins", () => sync());
|
|
659
|
+
// D27: a press inside the open panel holds its repaint until it ends.
|
|
660
|
+
document.addEventListener(
|
|
661
|
+
"pointerdown",
|
|
662
|
+
(e) => {
|
|
663
|
+
if (S.open?.el.contains(e.target)) S.pressed = true;
|
|
664
|
+
},
|
|
665
|
+
true,
|
|
666
|
+
);
|
|
667
|
+
const released = () => {
|
|
668
|
+
if (!S.pressed) return;
|
|
669
|
+
S.pressed = false;
|
|
670
|
+
// After the click this press makes, then the repaint it held.
|
|
671
|
+
setTimeout(() => {
|
|
672
|
+
if (S.syncLater) {
|
|
673
|
+
S.syncLater = false;
|
|
674
|
+
sync();
|
|
675
|
+
}
|
|
676
|
+
}, 0);
|
|
677
|
+
};
|
|
678
|
+
document.addEventListener("pointerup", released, true);
|
|
679
|
+
document.addEventListener("pointercancel", released, true);
|
|
638
680
|
document.addEventListener(
|
|
639
681
|
"click",
|
|
640
682
|
(e) => {
|
|
@@ -643,10 +685,7 @@ export function initServicePanel(deps = {}) {
|
|
|
643
685
|
// The panel's Visit here; the rail's ↗ is shell.js's (the same visit()).
|
|
644
686
|
const v = tgt.closest?.("[data-visit]");
|
|
645
687
|
if (v) {
|
|
646
|
-
if (o?.el.contains(v))
|
|
647
|
-
e.preventDefault();
|
|
648
|
-
visit(v);
|
|
649
|
-
}
|
|
688
|
+
if (o?.el.contains(v) && !visit(v)) e.preventDefault();
|
|
650
689
|
return;
|
|
651
690
|
}
|
|
652
691
|
const opener = tgt.closest?.("[data-svpanel-open]");
|
|
@@ -388,6 +388,10 @@ export const ROUTES: readonly RouteEntry[] = [
|
|
|
388
388
|
"/api/agents/rotation",
|
|
389
389
|
"D28: rotation settings, the overrides and an agent's effective values",
|
|
390
390
|
),
|
|
391
|
+
R(
|
|
392
|
+
"/api/hub",
|
|
393
|
+
"HUB-1.5: Den Hub: its URL, connected or not, the team and the install's label (never the token)",
|
|
394
|
+
),
|
|
391
395
|
M(
|
|
392
396
|
["PUT"],
|
|
393
397
|
"/api/agents/rotation",
|
|
@@ -52,6 +52,8 @@ export const UI_MODULES: readonly string[] = [
|
|
|
52
52
|
"/server-agent-page.js",
|
|
53
53
|
// 0.4.18: the service panel (rail pins, Graph ⋯, Services names).
|
|
54
54
|
"/svc-panel.js",
|
|
55
|
+
// D30: the shared searchable picker (Settings › Agents › Rotation).
|
|
56
|
+
"/combobox.js",
|
|
55
57
|
// 0.4.18: where a service's live log comes from (Logs page and panel).
|
|
56
58
|
"/log-source.js",
|
|
57
59
|
];
|