@monster-orange/den 0.4.21 → 0.4.22
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 +68 -0
- package/docs/distributions.md +95 -12
- package/package.json +1 -1
- package/service/install.sh +213 -63
- package/src/shared/agents/den-claude.ts +13 -1
- package/src/shared/agents/records.ts +6 -0
- package/src/shared/agents/runtime.ts +6 -0
- package/src/shared/agents/session-name.ts +21 -1
- package/src/shared/launchd-session.ts +92 -0
- package/src/shared/sessions/client.ts +24 -0
- package/src/shared/sessions/host.ts +114 -5
- package/src/shared/sessions/types.ts +5 -0
- package/src/ui/cli/agent-ops.ts +20 -2
- package/src/ui/cli/agents-command.ts +32 -1
- package/src/ui/cli/doctor-command.ts +72 -0
- package/src/ui/cli/doctor-where.ts +203 -0
- package/src/ui/cli/index.ts +1 -1
- package/src/ui/cli/service-command.ts +217 -11
- package/src/ui/cli/sessions-command.ts +46 -12
- package/src/ui/cli/web-command.ts +4 -0
- package/src/ui/server/index.ts +4 -0
- package/src/ui/server/public/agents-page.js +103 -6
- package/src/ui/server/public/agents.html +30 -0
- package/src/ui/server/public/i18n/es.js +14 -0
- package/src/ui/server/public/i18n/ja.js +14 -0
- package/src/ui/server/public/shell.js +14 -0
- package/src/ui/server/session-routes.ts +4 -0
|
@@ -347,6 +347,25 @@ function wsLinkHtml(ws) {
|
|
|
347
347
|
return `<a class="wslink" href="${esc(wsHref(ws))}" title="${esc(t("ag.goWs", { ws }))}">${esc(ws)}</a>`;
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
+
/**
|
|
351
|
+
* D20: where its session runs, as a tag: "Stops at logout" (the desktop
|
|
352
|
+
* session) or "No GUI" (outside it: kept across logout). `button`: a
|
|
353
|
+
* button that says why when tapped (the pane bar); else a span (a row,
|
|
354
|
+
* itself a button). "" when the host didn't say (off macOS, older host).
|
|
355
|
+
*/
|
|
356
|
+
function whereTag(r, button = false) {
|
|
357
|
+
const s = r?.launchd;
|
|
358
|
+
const logout = s === "Aqua" || s === "LoginWindow";
|
|
359
|
+
if (!logout && s !== "Background" && s !== "System") return "";
|
|
360
|
+
const key = logout ? "ag.where.logout" : "ag.where.bg";
|
|
361
|
+
const tip = t(`${key}Tip`, { cli: CLI });
|
|
362
|
+
const cls = `tagx${logout ? " warn" : ""}`;
|
|
363
|
+
const where = logout ? "logout" : "bg";
|
|
364
|
+
return button
|
|
365
|
+
? `<button type="button" class="${cls}" data-act="where" data-where="${where}" title="${esc(tip)}" aria-label="${esc(`${t(key)}: ${tip}`)}">${logout ? "⚠ " : ""}${esc(t(key))}</button>`
|
|
366
|
+
: `<span class="${cls}" data-where="${where}" title="${esc(tip)}">${logout ? "⚠ " : ""}${esc(t(key))}</span>`;
|
|
367
|
+
}
|
|
368
|
+
|
|
350
369
|
/** Its rotation, when one is due: "ready" or "due" (den#4). */
|
|
351
370
|
function rotationOf(r) {
|
|
352
371
|
if (!r.rotation?.due) return "";
|
|
@@ -370,6 +389,8 @@ function stoppedHtml(r) {
|
|
|
370
389
|
: "";
|
|
371
390
|
const fresh = `<button type="button" class="den-btn sm" data-act="fresh" aria-label="${esc(t("ag.freshName", { id: r.agent }))}">${esc(t("ag.fresh"))}</button>`;
|
|
372
391
|
const remove = `<button type="button" class="den-btn sm" data-act="remove" aria-label="${esc(t("ag.removeName", { id: r.agent }))}">${esc(t("ag.remove"))}</button>`;
|
|
392
|
+
// D25: an agent with no pane open can be renamed too.
|
|
393
|
+
const rename = `<button type="button" class="den-btn sm" data-act="rename" aria-label="${esc(t("ag.renameName", { id: r.agent }))}">${esc(t("ag.rn.act"))}</button>`;
|
|
373
394
|
// A rotation due without a handoff that passes: say so (den#4).
|
|
374
395
|
const handoff =
|
|
375
396
|
rot === "due"
|
|
@@ -400,7 +421,7 @@ function stoppedHtml(r) {
|
|
|
400
421
|
(meta ? `<span class="meta">${esc(meta)}</span>` : "") +
|
|
401
422
|
`<span class="why">${esc(stopReason(r))}</span>` +
|
|
402
423
|
`</button>` +
|
|
403
|
-
`<div class="agacts">${handoff}${cant}${relaunch}${resume}${fresh}${remove}</div>` +
|
|
424
|
+
`<div class="agacts">${handoff}${cant}${relaunch}${resume}${fresh}${rename}${remove}</div>` +
|
|
404
425
|
`</div>`
|
|
405
426
|
);
|
|
406
427
|
}
|
|
@@ -428,9 +449,10 @@ function rowHtml(r) {
|
|
|
428
449
|
? `<span class="tagx warn" title="${esc(t("ag.revokedTip"))}">${esc(t("ag.revoked"))}</span>`
|
|
429
450
|
: "";
|
|
430
451
|
const ovTags = r.kind === "agent" ? overrideTagsHtml(r) : "";
|
|
452
|
+
const wTag = whereTag(r);
|
|
431
453
|
const tags =
|
|
432
|
-
rotTag || revokedTag || ovTags
|
|
433
|
-
? `<span class="agtags">${revokedTag}${rotTag}${ovTags}</span>`
|
|
454
|
+
rotTag || revokedTag || ovTags || wTag
|
|
455
|
+
? `<span class="agtags">${revokedTag}${rotTag}${wTag}${ovTags}</span>`
|
|
434
456
|
: "";
|
|
435
457
|
return (
|
|
436
458
|
`<button type="button" class="agr" data-id="${esc(r.id)}" data-kind="${esc(r.kind)}" data-state="${esc(r.state)}"` +
|
|
@@ -976,9 +998,10 @@ function paintBar(p) {
|
|
|
976
998
|
const reconnect = p.lost
|
|
977
999
|
? `<button type="button" class="den-btn sm" data-act="reconnect">${esc(t("ag.reconnect"))}</button>`
|
|
978
1000
|
: "";
|
|
979
|
-
// AG-18: ⋯, its menu: Change persona… (a
|
|
1001
|
+
// AG-18: ⋯, its menu: Rename… (D25, every agent), Change persona… (a
|
|
1002
|
+
// runtime that can resume).
|
|
980
1003
|
const more =
|
|
981
|
-
r?.kind === "agent"
|
|
1004
|
+
r?.kind === "agent"
|
|
982
1005
|
? `<button type="button" class="den-btn sm" data-act="more" aria-haspopup="menu" aria-expanded="false" aria-label="${esc(t("ag.more", { id: r.agent }))}" title="${esc(t("ag.more", { id: r.agent }))}">⋯</button>`
|
|
983
1006
|
: "";
|
|
984
1007
|
bar.innerHTML =
|
|
@@ -987,6 +1010,7 @@ function paintBar(p) {
|
|
|
987
1010
|
(r?.tokenRevoked
|
|
988
1011
|
? `<span class="tagx warn" title="${esc(t("ag.revokedTip"))}">${esc(t("ag.revoked"))}</span>`
|
|
989
1012
|
: "") +
|
|
1013
|
+
whereTag(r, true) +
|
|
990
1014
|
`<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>`;
|
|
991
1015
|
// Tabs: the tab's × closes it; the pane's bar has none.
|
|
992
1016
|
const x = bar.querySelector('[data-act="close"]');
|
|
@@ -2126,6 +2150,63 @@ function goLabel() {
|
|
|
2126
2150
|
return t(sd.change ? "ag.pc.go" : sd.bring ? "ag.bring.go" : "ag.go");
|
|
2127
2151
|
}
|
|
2128
2152
|
|
|
2153
|
+
/**
|
|
2154
|
+
* D25: the Rename dialog for agent row `r`: its name (empty: back to its
|
|
2155
|
+
* id), and the name other sessions will see it by (D24).
|
|
2156
|
+
*/
|
|
2157
|
+
function openRename(r, opener) {
|
|
2158
|
+
const dlg = $d("#ag-rn");
|
|
2159
|
+
if (!dlg || dlg.open) return;
|
|
2160
|
+
const input = $d("#ag-rn-name");
|
|
2161
|
+
const help = $d("#ag-rn-help");
|
|
2162
|
+
const err = $d("#ag-rn-err");
|
|
2163
|
+
$d("#ag-rn-h").textContent = t("ag.rn.title", { id: r.agent });
|
|
2164
|
+
$d("#ag-rn-t").textContent = t("ag.rn.name");
|
|
2165
|
+
$d("#ag-rn-go").textContent = t("ag.rn.go");
|
|
2166
|
+
$d("#ag-rn-cancel").textContent = t("ag.rn.cancel");
|
|
2167
|
+
input.value = r.displayName || "";
|
|
2168
|
+
input.placeholder = r.agent;
|
|
2169
|
+
const paintHelp = () => {
|
|
2170
|
+
const name = input.value.trim() || r.agent;
|
|
2171
|
+
help.textContent = t("ag.rn.help", {
|
|
2172
|
+
id: r.agent,
|
|
2173
|
+
peer: `${CLI}-${r.workspace}:${name}`,
|
|
2174
|
+
});
|
|
2175
|
+
};
|
|
2176
|
+
paintHelp();
|
|
2177
|
+
err.hidden = true;
|
|
2178
|
+
input.oninput = paintHelp;
|
|
2179
|
+
const close = () => {
|
|
2180
|
+
dlg.close?.();
|
|
2181
|
+
dlg.removeAttribute("open");
|
|
2182
|
+
opener?.focus?.();
|
|
2183
|
+
};
|
|
2184
|
+
$d("#ag-rn-cancel").onclick = close;
|
|
2185
|
+
$d("#ag-rn-form").onsubmit = async (e) => {
|
|
2186
|
+
e.preventDefault();
|
|
2187
|
+
const name = input.value.trim();
|
|
2188
|
+
const go = $d("#ag-rn-go");
|
|
2189
|
+
go.disabled = true;
|
|
2190
|
+
try {
|
|
2191
|
+
await api("POST", agentPath(r, "name"), {
|
|
2192
|
+
name: name && name !== r.agent ? name : null,
|
|
2193
|
+
});
|
|
2194
|
+
close();
|
|
2195
|
+
await refresh();
|
|
2196
|
+
paintLayout();
|
|
2197
|
+
} catch (x) {
|
|
2198
|
+
err.textContent = x?.message || String(x);
|
|
2199
|
+
err.hidden = false;
|
|
2200
|
+
} finally {
|
|
2201
|
+
go.disabled = false;
|
|
2202
|
+
}
|
|
2203
|
+
};
|
|
2204
|
+
if (typeof dlg.showModal === "function") dlg.showModal();
|
|
2205
|
+
else dlg.setAttribute("open", "");
|
|
2206
|
+
input.focus();
|
|
2207
|
+
input.select();
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2129
2210
|
/** AG-18: Change persona… for agent row `r` (its persona, model, name). */
|
|
2130
2211
|
function openPersonaChange(r) {
|
|
2131
2212
|
return openStart(false, {
|
|
@@ -2708,6 +2789,7 @@ export async function mountAgentsPage(root) {
|
|
|
2708
2789
|
if (which === "resume" || which === "fresh" || which === "relaunch")
|
|
2709
2790
|
void restartRow(r, which, act);
|
|
2710
2791
|
else if (which === "remove") void removeRow(r, act);
|
|
2792
|
+
else if (which === "rename") openRename(r, act);
|
|
2711
2793
|
return;
|
|
2712
2794
|
}
|
|
2713
2795
|
const b = e.target.closest(".agr");
|
|
@@ -2738,6 +2820,11 @@ export async function mountAgentsPage(root) {
|
|
|
2738
2820
|
const id = b.closest(".pane")?.dataset.id ?? b.dataset.id;
|
|
2739
2821
|
const p = id && st.panes.get(id);
|
|
2740
2822
|
if (!p) return;
|
|
2823
|
+
if (b.dataset.act === "where") {
|
|
2824
|
+
// D20: why it stops at logout (or what it lacks), for touch too.
|
|
2825
|
+
window.denShell?.toast?.(b.title);
|
|
2826
|
+
return;
|
|
2827
|
+
}
|
|
2741
2828
|
if (b.dataset.act === "close") closePane(id);
|
|
2742
2829
|
else if (b.dataset.act === "stop") void stopAgent(p, b);
|
|
2743
2830
|
else if (b.dataset.act === "resume") {
|
|
@@ -2750,7 +2837,17 @@ export async function mountAgentsPage(root) {
|
|
|
2750
2837
|
window.denShell?.openMenu?.(b, {
|
|
2751
2838
|
label: t("ag.more", { id: r.agent }),
|
|
2752
2839
|
items: [
|
|
2753
|
-
|
|
2840
|
+
// D25: a visible Rename, for touch and keyboard (not only a
|
|
2841
|
+
// double-click on a tab).
|
|
2842
|
+
{ label: t("ag.rn.item"), onSelect: () => openRename(r, b) },
|
|
2843
|
+
...(r.personaChange
|
|
2844
|
+
? [
|
|
2845
|
+
{
|
|
2846
|
+
label: t("ag.pc.item"),
|
|
2847
|
+
onSelect: () => void openPersonaChange(r),
|
|
2848
|
+
},
|
|
2849
|
+
]
|
|
2850
|
+
: []),
|
|
2754
2851
|
],
|
|
2755
2852
|
});
|
|
2756
2853
|
}
|
|
@@ -412,6 +412,16 @@
|
|
|
412
412
|
flex: none;
|
|
413
413
|
white-space: nowrap;
|
|
414
414
|
}
|
|
415
|
+
/* D20: the launchd tag is a button (tap: why); it looks like a tag. */
|
|
416
|
+
button.tagx {
|
|
417
|
+
background: none;
|
|
418
|
+
cursor: pointer;
|
|
419
|
+
line-height: inherit;
|
|
420
|
+
}
|
|
421
|
+
button.tagx:focus-visible {
|
|
422
|
+
outline: 2px solid var(--accent);
|
|
423
|
+
outline-offset: 1px;
|
|
424
|
+
}
|
|
415
425
|
.wslink {
|
|
416
426
|
color: inherit;
|
|
417
427
|
text-decoration: none;
|
|
@@ -771,6 +781,26 @@
|
|
|
771
781
|
</div>
|
|
772
782
|
</form>
|
|
773
783
|
</dialog>
|
|
784
|
+
<!-- D25: Rename, from a pane's ⋯ or a stopped agent's row. -->
|
|
785
|
+
<dialog class="ag-dlg" id="ag-rn" aria-labelledby="ag-rn-h">
|
|
786
|
+
<form method="dialog" id="ag-rn-form" novalidate>
|
|
787
|
+
<h2 id="ag-rn-h">Rename</h2>
|
|
788
|
+
<label class="fld"
|
|
789
|
+
><span id="ag-rn-t">Name</span>
|
|
790
|
+
<input id="ag-rn-name" maxlength="40" autocomplete="off" />
|
|
791
|
+
</label>
|
|
792
|
+
<p class="help" id="ag-rn-help"></p>
|
|
793
|
+
<p class="err" id="ag-rn-err" role="alert" hidden></p>
|
|
794
|
+
<div class="btns">
|
|
795
|
+
<button type="button" class="den-btn" id="ag-rn-cancel">
|
|
796
|
+
Cancel
|
|
797
|
+
</button>
|
|
798
|
+
<button type="submit" class="den-btn primary" id="ag-rn-go">
|
|
799
|
+
Rename
|
|
800
|
+
</button>
|
|
801
|
+
</div>
|
|
802
|
+
</form>
|
|
803
|
+
</dialog>
|
|
774
804
|
|
|
775
805
|
<script type="module">
|
|
776
806
|
import {
|
|
@@ -790,6 +790,20 @@
|
|
|
790
790
|
"ag.hint.tile": "Un clic añade una sesión al mosaico, hasta 4.",
|
|
791
791
|
"ag.renameTip": "Doble clic para cambiar el nombre",
|
|
792
792
|
"ag.renameName": "Cambiar el nombre de {id}",
|
|
793
|
+
"ag.rn.item": "Cambiar el nombre…",
|
|
794
|
+
"ag.rn.title": "Cambiar el nombre de {id}",
|
|
795
|
+
"ag.rn.name": "Nombre",
|
|
796
|
+
"ag.rn.help":
|
|
797
|
+
"Lo muestran su pestaña, su fila y la cabecera del panel; el id {id} no cambia. Las demás sesiones lo ven como {peer} desde su próximo inicio o reanudación. Vacío: vuelve a {id}.",
|
|
798
|
+
"ag.rn.go": "Cambiar el nombre",
|
|
799
|
+
"ag.rn.cancel": "Cancelar",
|
|
800
|
+
"ag.rn.act": "Renombrar",
|
|
801
|
+
"ag.where.logout": "Se detiene al cerrar sesión",
|
|
802
|
+
"ag.where.logoutTip":
|
|
803
|
+
"Se ejecuta en la sesión de escritorio, así que cerrar sesión lo termina. Para que los agentes sigan: {cli} service install --mode always (opcional, aún no probado tras cerrar sesión)",
|
|
804
|
+
"ag.where.bg": "Sin GUI",
|
|
805
|
+
"ag.where.bgTip":
|
|
806
|
+
"Sigue ejecutándose tras cerrar sesión, fuera de la sesión de escritorio: sin el llavero de inicio de sesión ni apps con GUI. 1Password usa el token de la cuenta de servicio, los navegadores van sin interfaz y las carpetas protegidas necesitan Acceso total al disco ({cli} doctor).",
|
|
793
807
|
"ag.rt.ready": "Inicia {name} {version} desde",
|
|
794
808
|
"ag.rt.readyNoVer": "Inicia {name} desde",
|
|
795
809
|
"ag.rt.notFound": "No se encontró {name}.",
|
|
@@ -775,6 +775,20 @@
|
|
|
775
775
|
"ag.hint.tile": "クリックでセッションをタイルに追加します(最大 4)。",
|
|
776
776
|
"ag.renameTip": "ダブルクリックで名前を変更",
|
|
777
777
|
"ag.renameName": "{id} の名前を変更",
|
|
778
|
+
"ag.rn.item": "名前を変更…",
|
|
779
|
+
"ag.rn.title": "{id} の名前を変更",
|
|
780
|
+
"ag.rn.name": "名前",
|
|
781
|
+
"ag.rn.help":
|
|
782
|
+
"タブ、行、ペインの見出しに表示されます。ID {id} は変わりません。ほかのセッションからは、次の開始または再開から {peer} として見えます。空にすると {id} に戻ります。",
|
|
783
|
+
"ag.rn.go": "名前を変更",
|
|
784
|
+
"ag.rn.cancel": "キャンセル",
|
|
785
|
+
"ag.rn.act": "名前を変更",
|
|
786
|
+
"ag.where.logout": "ログアウトで停止",
|
|
787
|
+
"ag.where.logoutTip":
|
|
788
|
+
"デスクトップのログインセッションで動いているため、ログアウトすると終了します。エージェントを動かし続けるには: {cli} service install --mode always (任意。ログアウトをまたいだ動作はまだ未検証)",
|
|
789
|
+
"ag.where.bg": "GUI なし",
|
|
790
|
+
"ag.where.bgTip":
|
|
791
|
+
"デスクトップセッションの外で、ログアウト後も動き続けます。ログインキーチェーンと GUI アプリは使えません。1Password はサービスアカウントのトークン、ブラウザはヘッドレス、保護されたフォルダにはフルディスクアクセスが必要です ({cli} doctor)。",
|
|
778
792
|
"ag.rt.ready": "{name} {version} を起動:",
|
|
779
793
|
"ag.rt.readyNoVer": "{name} を起動:",
|
|
780
794
|
"ag.rt.notFound": "{name} が見つかりません。",
|
|
@@ -898,6 +898,20 @@
|
|
|
898
898
|
"ag.hint.tile": "Click adds a session as a tile, up to 4.",
|
|
899
899
|
"ag.renameTip": "Double-click to rename",
|
|
900
900
|
"ag.renameName": "Rename {id}",
|
|
901
|
+
"ag.rn.item": "Rename…",
|
|
902
|
+
"ag.rn.title": "Rename {id}",
|
|
903
|
+
"ag.rn.name": "Name",
|
|
904
|
+
"ag.rn.help":
|
|
905
|
+
"Its tab, row and pane header show it; the id {id} never changes. Other sessions see it as {peer} from its next start or resume. Empty: back to {id}.",
|
|
906
|
+
"ag.rn.go": "Rename",
|
|
907
|
+
"ag.rn.cancel": "Cancel",
|
|
908
|
+
"ag.rn.act": "Rename",
|
|
909
|
+
"ag.where.logout": "Stops at logout",
|
|
910
|
+
"ag.where.logoutTip":
|
|
911
|
+
"It runs in the desktop login session, so logging out ends it. To keep agents running: {cli} service install --mode always (opt-in, not yet proven across a logout)",
|
|
912
|
+
"ag.where.bg": "No GUI",
|
|
913
|
+
"ag.where.bgTip":
|
|
914
|
+
"It keeps running after logout, outside the desktop session: no login keychain and no GUI apps. 1Password uses the service-account token, browsers run headless, and protected folders need Full Disk Access ({cli} doctor).",
|
|
901
915
|
"ag.rt.ready": "Starts {name} {version} from",
|
|
902
916
|
"ag.rt.readyNoVer": "Starts {name} from",
|
|
903
917
|
"ag.rt.notFound": "{name} not found.",
|
|
@@ -143,6 +143,8 @@ export interface ListedSession {
|
|
|
143
143
|
/** ON-6: the server agent's row (its workspace is the server scope). */
|
|
144
144
|
server?: boolean;
|
|
145
145
|
kind: "agent" | "session";
|
|
146
|
+
/** D20: the launchd session its host runs in (Aqua ends at logout). */
|
|
147
|
+
launchd?: string;
|
|
146
148
|
/** The `<name>` part: an agent's id, a plain session's name. */
|
|
147
149
|
label: string;
|
|
148
150
|
agent?: string;
|
|
@@ -251,6 +253,7 @@ export function listedSessions(
|
|
|
251
253
|
state: r.state,
|
|
252
254
|
exited: !s || s.exited === true,
|
|
253
255
|
...(s?.exitCode !== undefined ? { exitCode: s.exitCode } : {}),
|
|
256
|
+
...(s?.launchd ? { launchd: s.launchd } : {}),
|
|
254
257
|
...(r.startedAt ? { startedAt: r.startedAt } : {}),
|
|
255
258
|
attachable: Boolean(s),
|
|
256
259
|
resumable: r.resumable,
|
|
@@ -276,6 +279,7 @@ export function listedSessions(
|
|
|
276
279
|
state: sessionState(s),
|
|
277
280
|
exited: s.exited === true,
|
|
278
281
|
...(s.exitCode !== undefined ? { exitCode: s.exitCode } : {}),
|
|
282
|
+
...(s.launchd ? { launchd: s.launchd } : {}),
|
|
279
283
|
startedAt: s.startedAt,
|
|
280
284
|
attachable: true,
|
|
281
285
|
});
|