@monster-orange/den 0.4.27 → 0.4.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.
- package/CHANGELOG.md +47 -0
- package/docs/distributions.md +73 -2
- package/package.json +1 -1
- package/src/shared/agents/claude-helpers.ts +623 -0
- package/src/shared/agents/den-claude.ts +54 -0
- package/src/shared/agents/helpers.ts +175 -0
- package/src/shared/agents/runtime.ts +13 -0
- package/src/shared/den-config.ts +42 -1
- package/src/shared/hub.ts +10 -0
- package/src/ui/cli/agent-ops.ts +144 -0
- package/src/ui/cli/agents-command.ts +127 -1
- package/src/ui/mcp/api.ts +8 -2
- package/src/ui/mcp/server.ts +13 -1
- package/src/ui/mcp/tools.ts +50 -0
- package/src/ui/server/agent-routes.ts +56 -2
- package/src/ui/server/hub-connect.ts +69 -3
- package/src/ui/server/index.ts +7 -1
- package/src/ui/server/public/agent-helpers.js +311 -0
- package/src/ui/server/public/agents-page.js +76 -9
- package/src/ui/server/public/i18n/es.js +39 -0
- package/src/ui/server/public/i18n/ja.js +37 -0
- package/src/ui/server/public/settings-registry.js +37 -1
- package/src/ui/server/public/shell.js +200 -0
- package/src/ui/server/public/transcript-view.js +202 -0
- package/src/ui/server/request-guard.ts +3 -1
- package/src/ui/server/route-classes.ts +16 -0
- package/src/ui/server/session-routes.ts +71 -1
- package/src/ui/server/ui-assets.ts +3 -0
|
@@ -84,6 +84,13 @@
|
|
|
84
84
|
* as a tag.
|
|
85
85
|
*/
|
|
86
86
|
import { readiness, runtimeName, runtimeOptions } from "/agent-start.js";
|
|
87
|
+
import { focusBack, refindKey } from "/transcript-view.js";
|
|
88
|
+
import {
|
|
89
|
+
helperBarButtonHtml,
|
|
90
|
+
helperRowsHtml,
|
|
91
|
+
openHelpers,
|
|
92
|
+
wireHelperRows,
|
|
93
|
+
} from "/agent-helpers.js";
|
|
87
94
|
import {
|
|
88
95
|
commandProblem,
|
|
89
96
|
filterBarHtml,
|
|
@@ -496,7 +503,8 @@ function stoppedHtml(r) {
|
|
|
496
503
|
`<span class="why">${esc(stopReason(r))}</span>` +
|
|
497
504
|
`</button>` +
|
|
498
505
|
`<div class="agacts">${callout}${cant}${resume}${fresh}${rename}${remove}</div>` +
|
|
499
|
-
`</div>`
|
|
506
|
+
`</div>` +
|
|
507
|
+
helperRowsHtml(r)
|
|
500
508
|
);
|
|
501
509
|
}
|
|
502
510
|
|
|
@@ -540,15 +548,49 @@ function rowHtml(r) {
|
|
|
540
548
|
// D28: a running agent whose rotation is due: its callout, with buttons
|
|
541
549
|
// (before, only a tag: nothing to press).
|
|
542
550
|
const callout = r.kind === "agent" ? rotCalloutHtml(r) : "";
|
|
543
|
-
|
|
551
|
+
// D32: the helpers it started in its own session, under it.
|
|
552
|
+
const helpers = r.kind === "agent" ? helperRowsHtml(r) : "";
|
|
553
|
+
if (!callout) return button + helpers;
|
|
544
554
|
return (
|
|
545
555
|
`<div class="agst" data-agent="${esc(r.agent)}" data-state="${esc(r.state)}" data-ws="${esc(r.workspace)}" data-rotation="${rot}">` +
|
|
546
556
|
button +
|
|
547
557
|
`<div class="agacts">${callout}</div>` +
|
|
548
|
-
`</div>`
|
|
558
|
+
`</div>` +
|
|
559
|
+
helpers
|
|
549
560
|
);
|
|
550
561
|
}
|
|
551
562
|
|
|
563
|
+
/** Where focus was in the list, as a selector that finds it again once
|
|
564
|
+
* the list is redrawn: a helper row or button, an agent's row, a link. */
|
|
565
|
+
function listFocusKey(el) {
|
|
566
|
+
const key = refindKey(el);
|
|
567
|
+
if (key) return key;
|
|
568
|
+
const q = (v) => CSS.escape(String(v ?? ""));
|
|
569
|
+
if (el.matches("button.agr[data-id]"))
|
|
570
|
+
return `#agl button.agr[data-id="${q(el.dataset.id)}"]`;
|
|
571
|
+
if (el.matches("[data-act]")) {
|
|
572
|
+
const row = el.closest(".agst[data-agent]");
|
|
573
|
+
const act = `[data-act="${q(el.dataset.act)}"]`;
|
|
574
|
+
return row
|
|
575
|
+
? `#agl .agst[data-agent="${q(row.dataset.agent)}"][data-ws="${q(row.dataset.ws)}"] ${act}`
|
|
576
|
+
: `#agl ${act}`;
|
|
577
|
+
}
|
|
578
|
+
return "";
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/** The list's HTML, written only when it changed; focus inside it is kept
|
|
582
|
+
* (the refresh poll redraws it every few seconds). */
|
|
583
|
+
function setList(el, html) {
|
|
584
|
+
if (el.denHtml === html) return;
|
|
585
|
+
el.denHtml = html;
|
|
586
|
+
const had = el.contains(document.activeElement)
|
|
587
|
+
? document.activeElement
|
|
588
|
+
: null;
|
|
589
|
+
const key = had ? listFocusKey(had) : "";
|
|
590
|
+
el.innerHTML = html;
|
|
591
|
+
if (had) focusBack(had, key);
|
|
592
|
+
}
|
|
593
|
+
|
|
552
594
|
function paintList() {
|
|
553
595
|
const el = st.root.querySelector("#agl");
|
|
554
596
|
el.removeAttribute("aria-busy");
|
|
@@ -562,7 +604,7 @@ function paintList() {
|
|
|
562
604
|
`<div class="agnote" data-note="backend">${esc(t("ag.noBackend", { cli: CLI }))}</div>`,
|
|
563
605
|
);
|
|
564
606
|
if (!st.data) {
|
|
565
|
-
el
|
|
607
|
+
setList(el, notes.join(""));
|
|
566
608
|
return;
|
|
567
609
|
}
|
|
568
610
|
// ON-6: the server agent's group, "Server", first, whatever the view.
|
|
@@ -615,10 +657,12 @@ function paintList() {
|
|
|
615
657
|
)
|
|
616
658
|
.join("")
|
|
617
659
|
: `<div class="none">${esc(t("ag.none.scope", { scope: st.scopeLabel }))}</div>`;
|
|
618
|
-
|
|
660
|
+
setList(
|
|
661
|
+
el,
|
|
619
662
|
notes.join("") +
|
|
620
|
-
|
|
621
|
-
|
|
663
|
+
body +
|
|
664
|
+
`<div class="aghint" id="agl-hint">${esc(t(`ag.hint.${shownLayout()}`))} ${esc(t("ag.hint.keys"))}</div>`,
|
|
665
|
+
);
|
|
622
666
|
const clear = st.root.querySelector("#ag-clear");
|
|
623
667
|
if (clear) clear.hidden = !visibleRows().some(isStopped);
|
|
624
668
|
// No such workspace: nothing to start an agent in.
|
|
@@ -697,6 +741,13 @@ async function refresh() {
|
|
|
697
741
|
/* the last list stays; the next ask may answer */
|
|
698
742
|
}
|
|
699
743
|
followAgents();
|
|
744
|
+
// D32: the rail's Agents badge: helpers running now.
|
|
745
|
+
window.denShell?.setState?.({
|
|
746
|
+
helpersRunning: (st.data?.sessions ?? []).reduce(
|
|
747
|
+
(n, r) => n + (r.helpers?.running ?? 0),
|
|
748
|
+
0,
|
|
749
|
+
),
|
|
750
|
+
});
|
|
700
751
|
st.scopeLabel = scopeLabelFor(viewWorkspaces());
|
|
701
752
|
paintList();
|
|
702
753
|
paintSub();
|
|
@@ -1098,7 +1149,7 @@ function paintBar(p) {
|
|
|
1098
1149
|
(r?.kind === "agent" && rotActionsHtml(r)
|
|
1099
1150
|
? `<span class="agrot" data-rot-bar title="${esc(rotMessage(r))}"><span class="tagx warn" data-rotation="${rotationOf(r)}">${esc(t(rotationOf(r) === "ready" ? "ag.rot.ready" : "ag.rot.due", { why: rotationWhy(r.rotation) }))}</span>${rotActionsHtml(r)}</span>`
|
|
1100
1151
|
: "") +
|
|
1101
|
-
`<span class="x">${reconnect}${stop}${resume}${more}<button type="button" class="den-btn sm" data-act="close" aria-label="${esc(t("ag.detachName", { id: name }))}" title="${esc(t("ag.detach"))}">×</button></span>`;
|
|
1152
|
+
`<span class="x">${r?.kind === "agent" ? helperBarButtonHtml(r) : ""}${reconnect}${stop}${resume}${more}<button type="button" class="den-btn sm" data-act="close" aria-label="${esc(t("ag.detachName", { id: name }))}" title="${esc(t("ag.detach"))}">×</button></span>`;
|
|
1102
1153
|
// Tabs: the tab's × closes it; the pane's bar has none.
|
|
1103
1154
|
const x = bar.querySelector('[data-act="close"]');
|
|
1104
1155
|
if (x) x.hidden = shownLayout() === "tabs";
|
|
@@ -2947,6 +2998,13 @@ export async function mountAgentsPage(root) {
|
|
|
2947
2998
|
startPrefill = { ws: q.get("ws") || "", folder: q.get("folder") || "" };
|
|
2948
2999
|
}
|
|
2949
3000
|
|
|
3001
|
+
// D32: helper rows open their transcript; "All helpers" the full list.
|
|
3002
|
+
wireHelperRows(root.querySelector("#agl"), (agent, ws) => {
|
|
3003
|
+
const r = visibleRows().find(
|
|
3004
|
+
(x) => x.agent === agent && x.workspace === ws,
|
|
3005
|
+
);
|
|
3006
|
+
return r ? displayName(r) : agent;
|
|
3007
|
+
});
|
|
2950
3008
|
root.querySelector("#agl").addEventListener("click", (e) => {
|
|
2951
3009
|
const act = e.target.closest("button[data-act]");
|
|
2952
3010
|
if (act) {
|
|
@@ -3015,7 +3073,16 @@ export async function mountAgentsPage(root) {
|
|
|
3015
3073
|
const r = rowOf(id);
|
|
3016
3074
|
if (r) void restartRow(r, "resume", b);
|
|
3017
3075
|
} else if (b.dataset.act === "reconnect") void connect(p);
|
|
3018
|
-
else if (b.dataset.act === "
|
|
3076
|
+
else if (b.dataset.act === "helpers") {
|
|
3077
|
+
const r = rowOf(id);
|
|
3078
|
+
if (r?.kind === "agent")
|
|
3079
|
+
void openHelpers({
|
|
3080
|
+
ws: r.workspace,
|
|
3081
|
+
agent: r.agent,
|
|
3082
|
+
name: paneName(r),
|
|
3083
|
+
opener: b,
|
|
3084
|
+
});
|
|
3085
|
+
} else if (b.dataset.act === "more") {
|
|
3019
3086
|
const r = rowOf(id);
|
|
3020
3087
|
if (!r) return;
|
|
3021
3088
|
window.denShell?.openMenu?.(b, {
|
|
@@ -758,6 +758,45 @@
|
|
|
758
758
|
"ag.sub.one": "{n} sesión · {w} en espera · {scope}",
|
|
759
759
|
"ag.all": "todos los espacios",
|
|
760
760
|
"ag.waiting": "en espera",
|
|
761
|
+
"tx.asked": "Pedido",
|
|
762
|
+
"tx.result": "Resultado",
|
|
763
|
+
"tx.error": "Error",
|
|
764
|
+
"tx.omitted": "{n} líneas anteriores no se muestran",
|
|
765
|
+
"tx.omitted.one": "{n} línea anterior no se muestra",
|
|
766
|
+
"tx.empty": "Nada todavía.",
|
|
767
|
+
"tx.close": "Cerrar",
|
|
768
|
+
"tx.loading": "Cargando…",
|
|
769
|
+
"tx.failed": "No se pudo leer: {err}",
|
|
770
|
+
"hp.title": "Ayudantes ({n})",
|
|
771
|
+
"hp.bar": "Ayudantes: {n}",
|
|
772
|
+
"hp.barRunning": "Ayudantes: {n}, {r} en marcha",
|
|
773
|
+
"hp.all": "Todos los ayudantes ({n})",
|
|
774
|
+
"hp.runningN": "{n} en marcha",
|
|
775
|
+
"hp.groupOf": "Ayudantes de {name}",
|
|
776
|
+
"hp.open": "{what}, {status}: abrir su transcripción",
|
|
777
|
+
"hp.dlg": "Ayudantes de {name}",
|
|
778
|
+
"hp.earlier": "Conversaciones anteriores ({n})",
|
|
779
|
+
"hp.none": "Sin ayudantes en esta conversación.",
|
|
780
|
+
"hp.unsupported":
|
|
781
|
+
"El runtime de este agente no puede decir qué ayudantes inició.",
|
|
782
|
+
"hp.workflow": "flujo {name}",
|
|
783
|
+
"hp.note":
|
|
784
|
+
"Los ayudantes corren dentro de la sesión del propio agente: Den los muestra, pero no puede detenerlos ni dirigirlos. Para cambiar lo que hacen, díselo al agente.",
|
|
785
|
+
"hp.st.running": "en marcha",
|
|
786
|
+
"hp.st.idle": "inactivo",
|
|
787
|
+
"hp.st.done": "terminado",
|
|
788
|
+
"hp.st.failed": "falló",
|
|
789
|
+
"hp.st.stopped": "detenido",
|
|
790
|
+
"b.helpers": "{n} ayudantes en marcha",
|
|
791
|
+
"b.helpers.one": "{n} ayudante en marcha",
|
|
792
|
+
"hub.rail": "Hub",
|
|
793
|
+
"hub.railTip": "{team} en Hub (se abre en otra pestaña)",
|
|
794
|
+
"hub.railTipPlain": "Hub (se abre en otra pestaña)",
|
|
795
|
+
"hub.railUnread": "{n} esperan tu respuesta",
|
|
796
|
+
"hub.railRejected":
|
|
797
|
+
"Hub no acepta el token de esta instalación. Vuelve a conectar en Ajustes › Hub.",
|
|
798
|
+
"hub.railForbidden":
|
|
799
|
+
"Hub no permite esta instalación. Pídeselo a un administrador de Hub (Ajustes › Hub).",
|
|
761
800
|
"ag.session": "sesión",
|
|
762
801
|
"ag.empty":
|
|
763
802
|
"Elige una sesión a la izquierda para abrir su terminal. Abre más para verlas en mosaico (hasta 4).",
|
|
@@ -739,6 +739,43 @@
|
|
|
739
739
|
"ag.sub": "{n} セッション · 承認待ち {w} · {scope}",
|
|
740
740
|
"ag.all": "すべてのワークスペース",
|
|
741
741
|
"ag.waiting": "承認待ち",
|
|
742
|
+
"tx.asked": "依頼",
|
|
743
|
+
"tx.result": "結果",
|
|
744
|
+
"tx.error": "エラー",
|
|
745
|
+
"tx.omitted": "以前の {n} 行は表示していません",
|
|
746
|
+
"tx.empty": "まだ何もありません。",
|
|
747
|
+
"tx.close": "閉じる",
|
|
748
|
+
"tx.loading": "読み込み中…",
|
|
749
|
+
"tx.failed": "読み取れませんでした: {err}",
|
|
750
|
+
"hp.title": "ヘルパー ({n})",
|
|
751
|
+
"hp.bar": "ヘルパー: {n}",
|
|
752
|
+
"hp.barRunning": "ヘルパー: {n}、実行中 {r}",
|
|
753
|
+
"hp.all": "すべてのヘルパー ({n})",
|
|
754
|
+
"hp.runningN": "実行中 {n}",
|
|
755
|
+
"hp.groupOf": "{name} のヘルパー",
|
|
756
|
+
"hp.open": "{what}、{status}: トランスクリプトを開く",
|
|
757
|
+
"hp.dlg": "{name} のヘルパー",
|
|
758
|
+
"hp.earlier": "以前の会話 ({n})",
|
|
759
|
+
"hp.none": "この会話にヘルパーはいません。",
|
|
760
|
+
"hp.unsupported":
|
|
761
|
+
"このエージェントのランタイムは、起動したヘルパーを示せません。",
|
|
762
|
+
"hp.workflow": "ワークフロー {name}",
|
|
763
|
+
"hp.note":
|
|
764
|
+
"ヘルパーはこのエージェント自身のセッションで動くため、Den は表示できますが、停止や指示はできません。変えたいときはエージェントに伝えてください。",
|
|
765
|
+
"hp.st.running": "実行中",
|
|
766
|
+
"hp.st.idle": "待機",
|
|
767
|
+
"hp.st.done": "完了",
|
|
768
|
+
"hp.st.failed": "失敗",
|
|
769
|
+
"hp.st.stopped": "停止",
|
|
770
|
+
"b.helpers": "実行中のヘルパー {n}",
|
|
771
|
+
"hub.rail": "Hub",
|
|
772
|
+
"hub.railTip": "Hub の {team}(新しいタブで開く)",
|
|
773
|
+
"hub.railTipPlain": "Hub(新しいタブで開く)",
|
|
774
|
+
"hub.railUnread": "対応待ち {n} 件",
|
|
775
|
+
"hub.railRejected":
|
|
776
|
+
"Hub がこのインストールのトークンを受け付けません。設定 › Hub から再接続してください。",
|
|
777
|
+
"hub.railForbidden":
|
|
778
|
+
"Hub がこのインストールを許可していません。Hub の管理者に依頼してください(設定 › Hub)。",
|
|
742
779
|
"ag.session": "セッション",
|
|
743
780
|
"ag.empty":
|
|
744
781
|
"左のセッションを選ぶとターミナルが開きます。複数開くと並べて表示します(最大 4)。",
|
|
@@ -1565,6 +1565,12 @@ export function hubHtml(b, flash) {
|
|
|
1565
1565
|
: "") +
|
|
1566
1566
|
`<button type="button" class="btn sm" data-hub-action="disconnect">Disconnect</button>` +
|
|
1567
1567
|
`</div>` +
|
|
1568
|
+
// Disconnect asks first, saying what it does (Hub revokes the key).
|
|
1569
|
+
`<div class="hub-confirm" data-hub-confirm hidden>` +
|
|
1570
|
+
`<p class="set-note set-warn">This revokes this Den's Hub key on Hub. Agents lose Hub until you Connect again.</p>` +
|
|
1571
|
+
`<button type="button" class="btn sm primary" data-hub-action="disconnect-yes">Disconnect</button> ` +
|
|
1572
|
+
`<button type="button" class="btn sm" data-hub-action="disconnect-no">Cancel</button>` +
|
|
1573
|
+
`</div>` +
|
|
1568
1574
|
`<div class="rt-err" role="alert" hidden></div>` +
|
|
1569
1575
|
tools
|
|
1570
1576
|
);
|
|
@@ -1667,9 +1673,39 @@ function wireHub(host, ctx) {
|
|
|
1667
1673
|
.forEach(
|
|
1668
1674
|
(x) => x.type === "button" && x.addEventListener("click", connect),
|
|
1669
1675
|
);
|
|
1676
|
+
const acts = host.querySelector(".hub-acts");
|
|
1677
|
+
const ask = host.querySelector("[data-hub-confirm]");
|
|
1678
|
+
const asking = (on) => {
|
|
1679
|
+
if (!acts || !ask) return;
|
|
1680
|
+
acts.hidden = on;
|
|
1681
|
+
ask.hidden = !on;
|
|
1682
|
+
host
|
|
1683
|
+
.querySelector(
|
|
1684
|
+
on
|
|
1685
|
+
? '[data-hub-action="disconnect-no"]'
|
|
1686
|
+
: '[data-hub-action="disconnect"]',
|
|
1687
|
+
)
|
|
1688
|
+
?.focus();
|
|
1689
|
+
};
|
|
1670
1690
|
host
|
|
1671
1691
|
.querySelector('[data-hub-action="disconnect"]')
|
|
1672
|
-
?.addEventListener("click", () =>
|
|
1692
|
+
?.addEventListener("click", () => asking(true));
|
|
1693
|
+
host
|
|
1694
|
+
.querySelector('[data-hub-action="disconnect-no"]')
|
|
1695
|
+
?.addEventListener("click", () => asking(false));
|
|
1696
|
+
ask?.addEventListener("keydown", (ev) => {
|
|
1697
|
+
if (ev.key === "Escape") {
|
|
1698
|
+
ev.preventDefault();
|
|
1699
|
+
asking(false);
|
|
1700
|
+
}
|
|
1701
|
+
});
|
|
1702
|
+
host
|
|
1703
|
+
.querySelector('[data-hub-action="disconnect-yes"]')
|
|
1704
|
+
?.addEventListener("click", () => {
|
|
1705
|
+
if (ask) ask.hidden = true;
|
|
1706
|
+
if (acts) acts.hidden = false;
|
|
1707
|
+
disconnect(false);
|
|
1708
|
+
});
|
|
1673
1709
|
const box = host.querySelector("#hub-allow-tools");
|
|
1674
1710
|
if (box)
|
|
1675
1711
|
box.addEventListener("change", () => {
|
|
@@ -865,6 +865,45 @@
|
|
|
865
865
|
"ag.sub.one": "{n} session · {w} waiting · {scope}",
|
|
866
866
|
"ag.all": "all workspaces",
|
|
867
867
|
"ag.waiting": "waiting",
|
|
868
|
+
"tx.asked": "Asked",
|
|
869
|
+
"tx.result": "Result",
|
|
870
|
+
"tx.error": "Error",
|
|
871
|
+
"tx.omitted": "{n} earlier lines not shown",
|
|
872
|
+
"tx.omitted.one": "{n} earlier line not shown",
|
|
873
|
+
"tx.empty": "Nothing yet.",
|
|
874
|
+
"tx.close": "Close",
|
|
875
|
+
"tx.loading": "Loading…",
|
|
876
|
+
"tx.failed": "Couldn't read it: {err}",
|
|
877
|
+
"hp.title": "Helpers ({n})",
|
|
878
|
+
"hp.bar": "Helpers: {n}",
|
|
879
|
+
"hp.barRunning": "Helpers: {n}, {r} running",
|
|
880
|
+
"hp.all": "All helpers ({n})",
|
|
881
|
+
"hp.runningN": "{n} running",
|
|
882
|
+
"hp.groupOf": "Helpers of {name}",
|
|
883
|
+
"hp.open": "{what}, {status}: open its transcript",
|
|
884
|
+
"hp.dlg": "{name}'s helpers",
|
|
885
|
+
"hp.earlier": "Earlier conversations ({n})",
|
|
886
|
+
"hp.none": "No helpers in this conversation.",
|
|
887
|
+
"hp.unsupported":
|
|
888
|
+
"This agent's runtime can't tell which helpers it started.",
|
|
889
|
+
"hp.workflow": "workflow {name}",
|
|
890
|
+
"hp.note":
|
|
891
|
+
"Helpers run inside this agent's own session, so Den shows them but can't stop or steer them. To change what they do, tell the agent.",
|
|
892
|
+
"hp.st.running": "running",
|
|
893
|
+
"hp.st.idle": "idle",
|
|
894
|
+
"hp.st.done": "done",
|
|
895
|
+
"hp.st.failed": "failed",
|
|
896
|
+
"hp.st.stopped": "stopped",
|
|
897
|
+
"b.helpers": "{n} helpers running",
|
|
898
|
+
"b.helpers.one": "{n} helper running",
|
|
899
|
+
"hub.rail": "Hub",
|
|
900
|
+
"hub.railTip": "{team} on Hub (opens in a new tab)",
|
|
901
|
+
"hub.railTipPlain": "Hub (opens in a new tab)",
|
|
902
|
+
"hub.railUnread": "{n} waiting on you",
|
|
903
|
+
"hub.railRejected":
|
|
904
|
+
"Hub doesn't accept this install's token. Reconnect in Settings › Hub.",
|
|
905
|
+
"hub.railForbidden":
|
|
906
|
+
"Hub doesn't allow this install. Ask a Hub admin (Settings › Hub).",
|
|
868
907
|
"ag.session": "session",
|
|
869
908
|
"ag.empty":
|
|
870
909
|
"Pick a session on the left to open its terminal. Open more to tile them (up to 4).",
|
|
@@ -1059,6 +1098,8 @@
|
|
|
1059
1098
|
// ON-6: the server agent, a speech bubble with a spark.
|
|
1060
1099
|
assistant:
|
|
1061
1100
|
'<path d="M5 5h14a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1h-8l-4 3v-3H5a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1z"/><path d="M12 8v4M10 10h4"/>',
|
|
1101
|
+
// Hub: a node joined to three (the company's shared place).
|
|
1102
|
+
hub: '<circle cx="12" cy="12" r="2.5"/><circle cx="5" cy="6" r="2"/><circle cx="19" cy="6" r="2"/><circle cx="12" cy="20" r="2"/><path d="M6.6 7.2l3.5 3.2M17.4 7.2l-3.5 3.2M12 14.5V18"/>',
|
|
1062
1103
|
// SEC-2a: who this browser is (the rail footer's identity chip).
|
|
1063
1104
|
person:
|
|
1064
1105
|
'<circle cx="12" cy="8" r="3.5"/><path d="M5 20c.8-3.6 3.6-5.5 7-5.5s6.2 1.9 7 5.5"/>',
|
|
@@ -1341,6 +1382,9 @@
|
|
|
1341
1382
|
health: {},
|
|
1342
1383
|
down: 0,
|
|
1343
1384
|
openLogs: 0,
|
|
1385
|
+
helpersRunning: 0,
|
|
1386
|
+
// The rail's Hub link: GET /api/hub?rail=1, or null (not connected).
|
|
1387
|
+
hub: null,
|
|
1344
1388
|
pins: [],
|
|
1345
1389
|
collapsed: false,
|
|
1346
1390
|
hidden: [],
|
|
@@ -1918,6 +1962,15 @@
|
|
|
1918
1962
|
order: 17,
|
|
1919
1963
|
href: "/agents",
|
|
1920
1964
|
keywords: ["sessions", "terminal"],
|
|
1965
|
+
// D32: helpers the agents started that run now.
|
|
1966
|
+
badge: function (s) {
|
|
1967
|
+
return s.helpersRunning > 0
|
|
1968
|
+
? {
|
|
1969
|
+
n: s.helpersRunning,
|
|
1970
|
+
label: t("b.helpers", { n: s.helpersRunning }),
|
|
1971
|
+
}
|
|
1972
|
+
: null;
|
|
1973
|
+
},
|
|
1921
1974
|
},
|
|
1922
1975
|
// ON-6: the server agent, this Den's own: one click away, after Agents.
|
|
1923
1976
|
{
|
|
@@ -2493,6 +2546,7 @@
|
|
|
2493
2546
|
);
|
|
2494
2547
|
});
|
|
2495
2548
|
html += '<div class="den-rail-grow"></div>';
|
|
2549
|
+
html += hubItem();
|
|
2496
2550
|
html += item(
|
|
2497
2551
|
{
|
|
2498
2552
|
id: SETTINGS.id,
|
|
@@ -2516,6 +2570,75 @@
|
|
|
2516
2570
|
return html;
|
|
2517
2571
|
}
|
|
2518
2572
|
|
|
2573
|
+
/**
|
|
2574
|
+
* The rail's Hub link, above Settings: only once this install is
|
|
2575
|
+
* connected. It opens the company's page on Hub in a new tab, with a
|
|
2576
|
+
* count of what waits on the person (whoami's unread). A token Hub
|
|
2577
|
+
* rejects, or a role it forbids, greys it, and it goes to Settings ›
|
|
2578
|
+
* Hub, which says what to do.
|
|
2579
|
+
*/
|
|
2580
|
+
function hubTarget(u) {
|
|
2581
|
+
try {
|
|
2582
|
+
var x = new URL(u);
|
|
2583
|
+
return x.protocol === "https:" ||
|
|
2584
|
+
(x.protocol === "http:" &&
|
|
2585
|
+
(x.hostname === "localhost" || x.hostname === "127.0.0.1"))
|
|
2586
|
+
? x.href
|
|
2587
|
+
: "";
|
|
2588
|
+
} catch (e) {
|
|
2589
|
+
return "";
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
function hubItem() {
|
|
2593
|
+
var h = state.hub;
|
|
2594
|
+
if (!h || !h.configured) return "";
|
|
2595
|
+
var label = t("hub.rail");
|
|
2596
|
+
if (h.state === "rejected" || h.state === "forbidden") {
|
|
2597
|
+
var why = t(
|
|
2598
|
+
h.state === "rejected" ? "hub.railRejected" : "hub.railForbidden",
|
|
2599
|
+
);
|
|
2600
|
+
return (
|
|
2601
|
+
'<a class="den-ni den-hub den-off" href="/settings#agents.hub" data-nav="hub" data-hub="' +
|
|
2602
|
+
esc(h.state) +
|
|
2603
|
+
'" title="' +
|
|
2604
|
+
esc(why) +
|
|
2605
|
+
'" aria-label="' +
|
|
2606
|
+
esc(label + ": " + why) +
|
|
2607
|
+
'">' +
|
|
2608
|
+
icon("hub") +
|
|
2609
|
+
'<span class="den-lbl">' +
|
|
2610
|
+
esc(label) +
|
|
2611
|
+
"</span></a>"
|
|
2612
|
+
);
|
|
2613
|
+
}
|
|
2614
|
+
var href = h.state === "connected" ? hubTarget(h.teamUrl || "") : "";
|
|
2615
|
+
if (!href) return "";
|
|
2616
|
+
var tip = h.team
|
|
2617
|
+
? t("hub.railTip", { team: h.team })
|
|
2618
|
+
: t("hub.railTipPlain");
|
|
2619
|
+
var n = typeof h.unread === "number" && h.unread > 0 ? h.unread : 0;
|
|
2620
|
+
var waiting = n ? t("hub.railUnread", { n: n }) : "";
|
|
2621
|
+
return (
|
|
2622
|
+
'<a class="den-ni den-hub" href="' +
|
|
2623
|
+
esc(href) +
|
|
2624
|
+
'" target="_blank" rel="noopener noreferrer" data-nav="hub" data-hub="connected" title="' +
|
|
2625
|
+
esc(tip) +
|
|
2626
|
+
'" aria-label="' +
|
|
2627
|
+
esc(label + ", " + tip + (waiting ? ", " + waiting : "")) +
|
|
2628
|
+
'">' +
|
|
2629
|
+
icon("hub") +
|
|
2630
|
+
'<span class="den-lbl">' +
|
|
2631
|
+
esc(label) +
|
|
2632
|
+
' <span class="den-ext" aria-hidden="true">↗</span></span>' +
|
|
2633
|
+
(n
|
|
2634
|
+
? '<span class="den-bdg" aria-hidden="true">' +
|
|
2635
|
+
esc(n > 99 ? "99+" : n) +
|
|
2636
|
+
"</span>"
|
|
2637
|
+
: "") +
|
|
2638
|
+
"</a>"
|
|
2639
|
+
);
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2519
2642
|
/**
|
|
2520
2643
|
* SEC-2a: the rail footer's identity chip: "Local" (this machine, no
|
|
2521
2644
|
* sign-in), "Signed in · this device" (a one-time link or a code) or
|
|
@@ -4710,6 +4833,80 @@
|
|
|
4710
4833
|
document.title = title.slice(0, at) + " · " + t(label);
|
|
4711
4834
|
})();
|
|
4712
4835
|
|
|
4836
|
+
// D32: the rail's Agents badge on every page: helpers running now
|
|
4837
|
+
// (GET /api/sessions/helpers, a count). The Agents page sets it itself
|
|
4838
|
+
// from its own list. Quiet on any failure: the badge just isn't there.
|
|
4839
|
+
(function () {
|
|
4840
|
+
if (page === "agents" || typeof rawFetch !== "function") return;
|
|
4841
|
+
function poll() {
|
|
4842
|
+
// A refusal page or a bad config: no polls (each would be refused).
|
|
4843
|
+
if (configInvalid()) return;
|
|
4844
|
+
if (document.hidden) return void setTimeout(poll, 15000);
|
|
4845
|
+
rawFetch("/api/sessions/helpers", {
|
|
4846
|
+
cache: "no-store",
|
|
4847
|
+
credentials: "same-origin",
|
|
4848
|
+
})
|
|
4849
|
+
.then(function (r) {
|
|
4850
|
+
return r.ok ? r.json() : null;
|
|
4851
|
+
})
|
|
4852
|
+
.then(
|
|
4853
|
+
function (b) {
|
|
4854
|
+
var n = b && typeof b.running === "number" ? b.running : 0;
|
|
4855
|
+
if (n !== state.helpersRunning) setState({ helpersRunning: n });
|
|
4856
|
+
},
|
|
4857
|
+
function () {},
|
|
4858
|
+
)
|
|
4859
|
+
.then(function () {
|
|
4860
|
+
setTimeout(poll, 15000);
|
|
4861
|
+
});
|
|
4862
|
+
}
|
|
4863
|
+
setTimeout(poll, 1500);
|
|
4864
|
+
})();
|
|
4865
|
+
|
|
4866
|
+
// The rail's Hub link (hubItem): GET /api/hub?rail=1 (which leaves
|
|
4867
|
+
// Connect's one-time result for Settings) at load and every minute while
|
|
4868
|
+
// the page shows. Quiet on any failure: the link just isn't there.
|
|
4869
|
+
(function () {
|
|
4870
|
+
if (typeof rawFetch !== "function") return;
|
|
4871
|
+
var last = "";
|
|
4872
|
+
function poll() {
|
|
4873
|
+
// A refusal page or a bad config: no polls (each would be refused).
|
|
4874
|
+
if (configInvalid()) return;
|
|
4875
|
+
if (document.hidden) return void setTimeout(poll, 60000);
|
|
4876
|
+
rawFetch("/api/hub?rail=1", {
|
|
4877
|
+
cache: "no-store",
|
|
4878
|
+
credentials: "same-origin",
|
|
4879
|
+
})
|
|
4880
|
+
.then(function (r) {
|
|
4881
|
+
return r.ok ? r.json() : null;
|
|
4882
|
+
})
|
|
4883
|
+
.then(
|
|
4884
|
+
function (b) {
|
|
4885
|
+
var h =
|
|
4886
|
+
b && b.configured
|
|
4887
|
+
? {
|
|
4888
|
+
configured: true,
|
|
4889
|
+
state: b.state,
|
|
4890
|
+
team: b.team,
|
|
4891
|
+
teamUrl: b.teamUrl,
|
|
4892
|
+
unread: b.unread,
|
|
4893
|
+
}
|
|
4894
|
+
: null;
|
|
4895
|
+
var k = JSON.stringify(h);
|
|
4896
|
+
if (k !== last) {
|
|
4897
|
+
last = k;
|
|
4898
|
+
setState({ hub: h });
|
|
4899
|
+
}
|
|
4900
|
+
},
|
|
4901
|
+
function () {},
|
|
4902
|
+
)
|
|
4903
|
+
.then(function () {
|
|
4904
|
+
setTimeout(poll, 60000);
|
|
4905
|
+
});
|
|
4906
|
+
}
|
|
4907
|
+
setTimeout(poll, 800);
|
|
4908
|
+
})();
|
|
4909
|
+
|
|
4713
4910
|
window.denShell = {
|
|
4714
4911
|
registerGroup: registerGroup,
|
|
4715
4912
|
registerNav: registerNav,
|
|
@@ -5114,6 +5311,9 @@
|
|
|
5114
5311
|
" text-align: left; background: none; border: 0; padding: 6px 10px; border-radius: var(--radius-button);",
|
|
5115
5312
|
" cursor: pointer; color: var(--muted); text-decoration: none; position: relative; font: inherit; }",
|
|
5116
5313
|
".den-ni:hover { background: var(--sunken); color: var(--text); }",
|
|
5314
|
+
// The rail's Hub link: ↗ (a new tab); greyed when Hub refuses it.
|
|
5315
|
+
".den-ni .den-ext { color: var(--faint); font-size: 12px; }",
|
|
5316
|
+
".den-ni.den-off { opacity: .55; }",
|
|
5117
5317
|
".den-ni[aria-current='page'] { background: color-mix(in srgb, var(--accent) 13%, transparent);",
|
|
5118
5318
|
" color: var(--text); font-weight: 500; }",
|
|
5119
5319
|
".den-ni[aria-current='page']::before { content: ''; position: absolute; left: -8px; top: 6px;",
|