@monster-orange/den 0.4.21 → 0.4.23
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 +115 -0
- package/docs/distributions.md +186 -42
- package/package.json +1 -1
- package/service/install.sh +213 -63
- package/src/shared/agents/den-claude.ts +13 -1
- package/src/shared/agents/guardrails/rotation-hook.ts +5 -0
- package/src/shared/agents/records.ts +6 -0
- package/src/shared/agents/rotate-settings.ts +309 -0
- package/src/shared/agents/rotation.ts +110 -5
- package/src/shared/agents/runtime.ts +6 -0
- package/src/shared/agents/session-name.ts +21 -1
- package/src/shared/den-config.ts +27 -12
- 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 +469 -17
- package/src/ui/cli/agents-command.ts +200 -3
- 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/server-agent-ops.ts +2 -2
- 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 +36 -5
- package/src/ui/cli/web-service.ts +160 -0
- package/src/ui/server/agent-routes.ts +149 -5
- package/src/ui/server/index.ts +17 -5
- package/src/ui/server/public/agents-page.js +316 -27
- package/src/ui/server/public/agents.html +62 -0
- package/src/ui/server/public/i18n/es.js +58 -2
- package/src/ui/server/public/i18n/ja.js +54 -2
- package/src/ui/server/public/settings-registry.js +204 -0
- package/src/ui/server/public/settings.html +43 -4
- package/src/ui/server/public/shell.js +56 -2
- package/src/ui/server/route-classes.ts +21 -0
- package/src/ui/server/session-routes.ts +27 -5
|
@@ -347,12 +347,119 @@ 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
|
+
// D28: on_due off: nothing will happen, so nothing is shown.
|
|
372
|
+
if (!r.rotation?.due || r.rotation.onDue === "off") return "";
|
|
353
373
|
return r.rotation.ready ? "ready" : "due";
|
|
354
374
|
}
|
|
355
375
|
|
|
376
|
+
/** D28: Settings › Agents (its Rotation row). */
|
|
377
|
+
function settingsHref() {
|
|
378
|
+
const nav = window.denShell?.nav?.() ?? [];
|
|
379
|
+
const base = nav.find((n) => n.id === "settings")?.href ?? "/settings";
|
|
380
|
+
return `${base}?q=rotation#agents`;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* D28: what happens to a due agent, in words, from its on_due (auto: Den
|
|
385
|
+
* asks for the handoff at idle and relaunches it; ask; auto that gave up
|
|
386
|
+
* asking). Older servers send no on_due: as ask.
|
|
387
|
+
*/
|
|
388
|
+
function rotMessage(r) {
|
|
389
|
+
const rot = r.rotation;
|
|
390
|
+
const why = rotationWhy(rot);
|
|
391
|
+
const auto = rot.onDue === "auto" && !rot.fellBack;
|
|
392
|
+
if (rot.relaunchWanted && !rot.ready) return t("ag.rc.wanted", { why });
|
|
393
|
+
if (auto && !isStopped(r))
|
|
394
|
+
return t(rot.ready ? "ag.rc.autoReady" : "ag.rc.autoAsk", { why });
|
|
395
|
+
if (rot.fellBack && !rot.ready)
|
|
396
|
+
return t(
|
|
397
|
+
rot.handoff === "invalid" ? "ag.rc.fellBackBad" : "ag.rc.fellBack",
|
|
398
|
+
{ why },
|
|
399
|
+
);
|
|
400
|
+
return t(
|
|
401
|
+
rot.ready
|
|
402
|
+
? "ag.rc.ready"
|
|
403
|
+
: rot.handoff === "invalid"
|
|
404
|
+
? "ag.rc.bad"
|
|
405
|
+
: "ag.rc.missing",
|
|
406
|
+
{ why },
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* D28: the rotation callout's buttons, wherever it shows (a row, the pane
|
|
412
|
+
* bar): Relaunch now (from the handoff, or asking for it first), Write
|
|
413
|
+
* handoff (a running agent without one), Keep this conversation with
|
|
414
|
+
* "don't ask again", and Settings. "" when nothing is due or on_due is off.
|
|
415
|
+
*/
|
|
416
|
+
function rotActionsHtml(r) {
|
|
417
|
+
const rot = r.rotation;
|
|
418
|
+
if (!rot?.due || rot.onDue === "off") return "";
|
|
419
|
+
const id = r.agent;
|
|
420
|
+
const now = `<button type="button" class="den-btn sm primary" data-act="relaunch" aria-label="${esc(t("ag.relaunchName", { id }))}">${esc(t("ag.relaunch"))}</button>`;
|
|
421
|
+
const write =
|
|
422
|
+
!isStopped(r) && !rot.ready && !rot.relaunchWanted
|
|
423
|
+
? `<button type="button" class="den-btn sm" data-act="rot-write" aria-label="${esc(t("ag.rc.writeName", { id }))}">${esc(t("ag.rc.write"))}</button>`
|
|
424
|
+
: "";
|
|
425
|
+
const keep = `<button type="button" class="den-btn sm" data-act="rot-keep" aria-label="${esc(t("ag.rc.keepName", { id }))}">${esc(t("ag.rc.keep"))}</button>`;
|
|
426
|
+
const never = `<label class="agnever"><input type="checkbox" data-rot-never> ${esc(t("ag.rc.never"))}</label>`;
|
|
427
|
+
const settings = `<a class="lnk" data-rot-settings href="${esc(settingsHref())}">${esc(t("ag.rc.settings"))}</a>`;
|
|
428
|
+
return now + write + keep + never + settings;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** D28: the effective settings and where each comes from, in words. */
|
|
432
|
+
function rotFromText(rot) {
|
|
433
|
+
if (!rot?.from) return "";
|
|
434
|
+
return t(
|
|
435
|
+
rot.after === 0
|
|
436
|
+
? "ag.rc.fromNever"
|
|
437
|
+
: rot.after === 1
|
|
438
|
+
? "ag.rc.from.one"
|
|
439
|
+
: "ag.rc.from",
|
|
440
|
+
{
|
|
441
|
+
n: rot.after,
|
|
442
|
+
src: rot.from.after_compactions,
|
|
443
|
+
mode: t(`ag.rc.mode.${rot.onDue}`),
|
|
444
|
+
src2: rot.from.on_due,
|
|
445
|
+
},
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** D28: the callout under a row: the message, its line of settings, the buttons. */
|
|
450
|
+
function rotCalloutHtml(r) {
|
|
451
|
+
const acts = rotActionsHtml(r);
|
|
452
|
+
if (!acts) return "";
|
|
453
|
+
const from = rotFromText(r.rotation);
|
|
454
|
+
return (
|
|
455
|
+
`<span class="agcant" data-rot-msg data-handoff="${esc(r.rotation.handoff)}">${esc(rotMessage(r))}</span>` +
|
|
456
|
+
(from
|
|
457
|
+
? `<span class="agcant agfrom" data-rot-from>${esc(from)}</span>`
|
|
458
|
+
: "") +
|
|
459
|
+
acts
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
|
|
356
463
|
/** A stopped agent's row: its button (it opens an ended pane) and actions. */
|
|
357
464
|
function stoppedHtml(r) {
|
|
358
465
|
const open = st.open.includes(r.id);
|
|
@@ -361,27 +468,15 @@ function stoppedHtml(r) {
|
|
|
361
468
|
const meta = [r.runtime, r.cwd].filter(Boolean).join(" · ");
|
|
362
469
|
const who = esc(r.agent);
|
|
363
470
|
const rot = rotationOf(r);
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
? `<button type="button" class="den-btn sm primary" data-act="relaunch" aria-label="${esc(t("ag.relaunchName", { id: r.agent }))}">${esc(t("ag.relaunch"))}</button>`
|
|
367
|
-
: "";
|
|
471
|
+
// D28: a due agent's callout (unless on_due is off).
|
|
472
|
+
const callout = rotCalloutHtml(r);
|
|
368
473
|
const resume = r.resumable
|
|
369
474
|
? `<button type="button" class="den-btn sm primary" data-act="resume" aria-label="${esc(t("ag.resumeName", { id: r.agent }))}">${esc(t("ag.resume"))}</button>`
|
|
370
475
|
: "";
|
|
371
476
|
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
477
|
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>`;
|
|
373
|
-
//
|
|
374
|
-
const
|
|
375
|
-
rot === "due"
|
|
376
|
-
? `<span class="agcant" data-handoff="${esc(r.rotation.handoff)}">${esc(
|
|
377
|
-
t(
|
|
378
|
-
r.rotation.handoff === "missing"
|
|
379
|
-
? "ag.rot.noHandoff"
|
|
380
|
-
: "ag.rot.badHandoff",
|
|
381
|
-
{ why: rotationWhy(r.rotation) },
|
|
382
|
-
),
|
|
383
|
-
)}</span>`
|
|
384
|
-
: "";
|
|
478
|
+
// D25: an agent with no pane open can be renamed too.
|
|
479
|
+
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>`;
|
|
385
480
|
const cant =
|
|
386
481
|
!r.resumable && r.noResume && rot !== "ready"
|
|
387
482
|
? `<span class="agcant" data-no-resume>${esc(r.noResume.startsWith(`${r.runtime} `) ? t("ag.noResume", { runtime: r.runtime }) : r.noResume)}</span>`
|
|
@@ -400,7 +495,7 @@ function stoppedHtml(r) {
|
|
|
400
495
|
(meta ? `<span class="meta">${esc(meta)}</span>` : "") +
|
|
401
496
|
`<span class="why">${esc(stopReason(r))}</span>` +
|
|
402
497
|
`</button>` +
|
|
403
|
-
`<div class="agacts">${
|
|
498
|
+
`<div class="agacts">${callout}${cant}${resume}${fresh}${rename}${remove}</div>` +
|
|
404
499
|
`</div>`
|
|
405
500
|
);
|
|
406
501
|
}
|
|
@@ -428,11 +523,12 @@ function rowHtml(r) {
|
|
|
428
523
|
? `<span class="tagx warn" title="${esc(t("ag.revokedTip"))}">${esc(t("ag.revoked"))}</span>`
|
|
429
524
|
: "";
|
|
430
525
|
const ovTags = r.kind === "agent" ? overrideTagsHtml(r) : "";
|
|
526
|
+
const wTag = whereTag(r);
|
|
431
527
|
const tags =
|
|
432
|
-
rotTag || revokedTag || ovTags
|
|
433
|
-
? `<span class="agtags">${revokedTag}${rotTag}${ovTags}</span>`
|
|
528
|
+
rotTag || revokedTag || ovTags || wTag
|
|
529
|
+
? `<span class="agtags">${revokedTag}${rotTag}${wTag}${ovTags}</span>`
|
|
434
530
|
: "";
|
|
435
|
-
|
|
531
|
+
const button =
|
|
436
532
|
`<button type="button" class="agr" data-id="${esc(r.id)}" data-kind="${esc(r.kind)}" data-state="${esc(r.state)}"` +
|
|
437
533
|
` aria-pressed="${open}"${r.attachable ? "" : ' aria-disabled="true"'}` +
|
|
438
534
|
` aria-label="${esc(t("ag.openName", { id: `${r.workspace} / ${displayName(r)}` }))}">` +
|
|
@@ -440,7 +536,16 @@ function rowHtml(r) {
|
|
|
440
536
|
`<span class="nm">${nm}</span>${right}` +
|
|
441
537
|
(meta ? `<span class="meta">${esc(meta)}</span>` : "") +
|
|
442
538
|
tags +
|
|
443
|
-
`</button
|
|
539
|
+
`</button>`;
|
|
540
|
+
// D28: a running agent whose rotation is due: its callout, with buttons
|
|
541
|
+
// (before, only a tag: nothing to press).
|
|
542
|
+
const callout = r.kind === "agent" ? rotCalloutHtml(r) : "";
|
|
543
|
+
if (!callout) return button;
|
|
544
|
+
return (
|
|
545
|
+
`<div class="agst" data-agent="${esc(r.agent)}" data-state="${esc(r.state)}" data-ws="${esc(r.workspace)}" data-rotation="${rot}">` +
|
|
546
|
+
button +
|
|
547
|
+
`<div class="agacts">${callout}</div>` +
|
|
548
|
+
`</div>`
|
|
444
549
|
);
|
|
445
550
|
}
|
|
446
551
|
|
|
@@ -586,6 +691,7 @@ async function refresh() {
|
|
|
586
691
|
} else if (r.ok) {
|
|
587
692
|
st.data = await r.json();
|
|
588
693
|
st.signedOut = false;
|
|
694
|
+
toastAutoRelaunches(st.data?.sessions ?? []);
|
|
589
695
|
}
|
|
590
696
|
} catch {
|
|
591
697
|
/* the last list stays; the next ask may answer */
|
|
@@ -976,9 +1082,10 @@ function paintBar(p) {
|
|
|
976
1082
|
const reconnect = p.lost
|
|
977
1083
|
? `<button type="button" class="den-btn sm" data-act="reconnect">${esc(t("ag.reconnect"))}</button>`
|
|
978
1084
|
: "";
|
|
979
|
-
// AG-18: ⋯, its menu: Change persona… (a
|
|
1085
|
+
// AG-18: ⋯, its menu: Rename… (D25, every agent), Change persona… (a
|
|
1086
|
+
// runtime that can resume).
|
|
980
1087
|
const more =
|
|
981
|
-
r?.kind === "agent"
|
|
1088
|
+
r?.kind === "agent"
|
|
982
1089
|
? `<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
1090
|
: "";
|
|
984
1091
|
bar.innerHTML =
|
|
@@ -987,6 +1094,10 @@ function paintBar(p) {
|
|
|
987
1094
|
(r?.tokenRevoked
|
|
988
1095
|
? `<span class="tagx warn" title="${esc(t("ag.revokedTip"))}">${esc(t("ag.revoked"))}</span>`
|
|
989
1096
|
: "") +
|
|
1097
|
+
whereTag(r, true) +
|
|
1098
|
+
(r?.kind === "agent" && rotActionsHtml(r)
|
|
1099
|
+
? `<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
|
+
: "") +
|
|
990
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>`;
|
|
991
1102
|
// Tabs: the tab's × closes it; the pane's bar has none.
|
|
992
1103
|
const x = bar.querySelector('[data-act="close"]');
|
|
@@ -1494,6 +1605,93 @@ async function restartRow(r, verb, button) {
|
|
|
1494
1605
|
}
|
|
1495
1606
|
}
|
|
1496
1607
|
|
|
1608
|
+
/**
|
|
1609
|
+
* D28 Relaunch now: from the handoff when it passes; else Den asks for it
|
|
1610
|
+
* first (a stopped agent resumes to write it) and relaunches it once it
|
|
1611
|
+
* passes, by itself.
|
|
1612
|
+
*/
|
|
1613
|
+
async function relaunchNow(r, button) {
|
|
1614
|
+
if (r.rotation?.ready) return restartRow(r, "relaunch", button);
|
|
1615
|
+
if (button) button.disabled = true;
|
|
1616
|
+
try {
|
|
1617
|
+
const out = await api("POST", agentPath(r, "handoff"), { relaunch: true });
|
|
1618
|
+
window.denShell?.toast?.(
|
|
1619
|
+
t(out?.resumed ? "ag.rc.askedResumed" : "ag.rc.askedRelaunch", {
|
|
1620
|
+
id: r.agent,
|
|
1621
|
+
}),
|
|
1622
|
+
);
|
|
1623
|
+
} catch (err) {
|
|
1624
|
+
window.denShell?.errorToast?.(err);
|
|
1625
|
+
}
|
|
1626
|
+
await refresh();
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
/** D28 Write handoff: the prompt, typed into its session at idle. */
|
|
1630
|
+
async function writeHandoff(r, button) {
|
|
1631
|
+
if (button) button.disabled = true;
|
|
1632
|
+
try {
|
|
1633
|
+
await api("POST", agentPath(r, "handoff"), {});
|
|
1634
|
+
window.denShell?.toast?.(t("ag.rc.asked", { id: r.agent }));
|
|
1635
|
+
} catch (err) {
|
|
1636
|
+
// Mid-turn is expected, not an error: say when to try again.
|
|
1637
|
+
if (/mid-turn/.test(String(err?.message ?? "")))
|
|
1638
|
+
window.denShell?.toast?.(t("ag.rc.midTurn", { id: r.agent }));
|
|
1639
|
+
else window.denShell?.errorToast?.(err);
|
|
1640
|
+
}
|
|
1641
|
+
await refresh();
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
/** D28 Keep this conversation (and "don't ask again": never, for it). */
|
|
1645
|
+
async function keepConversation(r, button) {
|
|
1646
|
+
const never = !!button
|
|
1647
|
+
?.closest(".agacts, .agrot")
|
|
1648
|
+
?.querySelector("[data-rot-never]")?.checked;
|
|
1649
|
+
if (button) button.disabled = true;
|
|
1650
|
+
try {
|
|
1651
|
+
await api("POST", agentPath(r, "keep"), never ? { never: true } : {});
|
|
1652
|
+
window.denShell?.toast?.(
|
|
1653
|
+
t(never ? "ag.rc.keptNever" : "ag.rc.kept", { id: r.agent }),
|
|
1654
|
+
);
|
|
1655
|
+
} catch (err) {
|
|
1656
|
+
window.denShell?.errorToast?.(err);
|
|
1657
|
+
}
|
|
1658
|
+
await refresh();
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
/**
|
|
1662
|
+
* D28: a relaunch Den did by itself since the last look: a toast saying
|
|
1663
|
+
* so, and that the old conversation is kept. The first look only notes
|
|
1664
|
+
* what is there.
|
|
1665
|
+
*/
|
|
1666
|
+
function toastAutoRelaunches(rows) {
|
|
1667
|
+
const seen = (st.rotSeen ??= new Map());
|
|
1668
|
+
const first = !st.rotLooked;
|
|
1669
|
+
st.rotLooked = true;
|
|
1670
|
+
for (const r of rows) {
|
|
1671
|
+
const last = r.kind === "agent" ? r.lastRotation : undefined;
|
|
1672
|
+
if (!last) continue;
|
|
1673
|
+
const key = `${r.workspace}/${r.agent}`;
|
|
1674
|
+
if (seen.get(key) === last.at) continue;
|
|
1675
|
+
seen.set(key, last.at);
|
|
1676
|
+
if (first || !last.auto) continue;
|
|
1677
|
+
window.denShell?.toast?.(
|
|
1678
|
+
t("ag.rc.autoRelaunched", {
|
|
1679
|
+
id: r.agent,
|
|
1680
|
+
why:
|
|
1681
|
+
last.reason === "pr-merged"
|
|
1682
|
+
? t("ag.rot.merged")
|
|
1683
|
+
: t(
|
|
1684
|
+
last.compactions === 1
|
|
1685
|
+
? "ag.rot.compactions.one"
|
|
1686
|
+
: "ag.rot.compactions",
|
|
1687
|
+
{ n: last.compactions },
|
|
1688
|
+
),
|
|
1689
|
+
old: last.conversation ?? "?",
|
|
1690
|
+
}),
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1497
1695
|
/** Undo: the removed agents come back (POST /api/agents/restore). */
|
|
1498
1696
|
async function undoRemove(ids) {
|
|
1499
1697
|
try {
|
|
@@ -2126,6 +2324,63 @@ function goLabel() {
|
|
|
2126
2324
|
return t(sd.change ? "ag.pc.go" : sd.bring ? "ag.bring.go" : "ag.go");
|
|
2127
2325
|
}
|
|
2128
2326
|
|
|
2327
|
+
/**
|
|
2328
|
+
* D25: the Rename dialog for agent row `r`: its name (empty: back to its
|
|
2329
|
+
* id), and the name other sessions will see it by (D24).
|
|
2330
|
+
*/
|
|
2331
|
+
function openRename(r, opener) {
|
|
2332
|
+
const dlg = $d("#ag-rn");
|
|
2333
|
+
if (!dlg || dlg.open) return;
|
|
2334
|
+
const input = $d("#ag-rn-name");
|
|
2335
|
+
const help = $d("#ag-rn-help");
|
|
2336
|
+
const err = $d("#ag-rn-err");
|
|
2337
|
+
$d("#ag-rn-h").textContent = t("ag.rn.title", { id: r.agent });
|
|
2338
|
+
$d("#ag-rn-t").textContent = t("ag.rn.name");
|
|
2339
|
+
$d("#ag-rn-go").textContent = t("ag.rn.go");
|
|
2340
|
+
$d("#ag-rn-cancel").textContent = t("ag.rn.cancel");
|
|
2341
|
+
input.value = r.displayName || "";
|
|
2342
|
+
input.placeholder = r.agent;
|
|
2343
|
+
const paintHelp = () => {
|
|
2344
|
+
const name = input.value.trim() || r.agent;
|
|
2345
|
+
help.textContent = t("ag.rn.help", {
|
|
2346
|
+
id: r.agent,
|
|
2347
|
+
peer: `${CLI}-${r.workspace}:${name}`,
|
|
2348
|
+
});
|
|
2349
|
+
};
|
|
2350
|
+
paintHelp();
|
|
2351
|
+
err.hidden = true;
|
|
2352
|
+
input.oninput = paintHelp;
|
|
2353
|
+
const close = () => {
|
|
2354
|
+
dlg.close?.();
|
|
2355
|
+
dlg.removeAttribute("open");
|
|
2356
|
+
opener?.focus?.();
|
|
2357
|
+
};
|
|
2358
|
+
$d("#ag-rn-cancel").onclick = close;
|
|
2359
|
+
$d("#ag-rn-form").onsubmit = async (e) => {
|
|
2360
|
+
e.preventDefault();
|
|
2361
|
+
const name = input.value.trim();
|
|
2362
|
+
const go = $d("#ag-rn-go");
|
|
2363
|
+
go.disabled = true;
|
|
2364
|
+
try {
|
|
2365
|
+
await api("POST", agentPath(r, "name"), {
|
|
2366
|
+
name: name && name !== r.agent ? name : null,
|
|
2367
|
+
});
|
|
2368
|
+
close();
|
|
2369
|
+
await refresh();
|
|
2370
|
+
paintLayout();
|
|
2371
|
+
} catch (x) {
|
|
2372
|
+
err.textContent = x?.message || String(x);
|
|
2373
|
+
err.hidden = false;
|
|
2374
|
+
} finally {
|
|
2375
|
+
go.disabled = false;
|
|
2376
|
+
}
|
|
2377
|
+
};
|
|
2378
|
+
if (typeof dlg.showModal === "function") dlg.showModal();
|
|
2379
|
+
else dlg.setAttribute("open", "");
|
|
2380
|
+
input.focus();
|
|
2381
|
+
input.select();
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2129
2384
|
/** AG-18: Change persona… for agent row `r` (its persona, model, name). */
|
|
2130
2385
|
function openPersonaChange(r) {
|
|
2131
2386
|
return openStart(false, {
|
|
@@ -2705,9 +2960,13 @@ export async function mountAgentsPage(root) {
|
|
|
2705
2960
|
(x) => x.agent === agent && x.workspace === ws,
|
|
2706
2961
|
);
|
|
2707
2962
|
if (!r) return;
|
|
2708
|
-
if (which === "
|
|
2963
|
+
if (which === "relaunch") void relaunchNow(r, act);
|
|
2964
|
+
else if (which === "rot-write") void writeHandoff(r, act);
|
|
2965
|
+
else if (which === "rot-keep") void keepConversation(r, act);
|
|
2966
|
+
else if (which === "resume" || which === "fresh")
|
|
2709
2967
|
void restartRow(r, which, act);
|
|
2710
2968
|
else if (which === "remove") void removeRow(r, act);
|
|
2969
|
+
else if (which === "rename") openRename(r, act);
|
|
2711
2970
|
return;
|
|
2712
2971
|
}
|
|
2713
2972
|
const b = e.target.closest(".agr");
|
|
@@ -2738,8 +2997,20 @@ export async function mountAgentsPage(root) {
|
|
|
2738
2997
|
const id = b.closest(".pane")?.dataset.id ?? b.dataset.id;
|
|
2739
2998
|
const p = id && st.panes.get(id);
|
|
2740
2999
|
if (!p) return;
|
|
3000
|
+
if (b.dataset.act === "where") {
|
|
3001
|
+
// D20: why it stops at logout (or what it lacks), for touch too.
|
|
3002
|
+
window.denShell?.toast?.(b.title);
|
|
3003
|
+
return;
|
|
3004
|
+
}
|
|
2741
3005
|
if (b.dataset.act === "close") closePane(id);
|
|
2742
|
-
else if (b.dataset.act === "
|
|
3006
|
+
else if (b.dataset.act === "relaunch" || b.dataset.act.startsWith("rot-")) {
|
|
3007
|
+
// D28: the callout's buttons in the pane bar.
|
|
3008
|
+
const r = rowOf(id);
|
|
3009
|
+
if (!r) return;
|
|
3010
|
+
if (b.dataset.act === "relaunch") void relaunchNow(r, b);
|
|
3011
|
+
else if (b.dataset.act === "rot-write") void writeHandoff(r, b);
|
|
3012
|
+
else if (b.dataset.act === "rot-keep") void keepConversation(r, b);
|
|
3013
|
+
} else if (b.dataset.act === "stop") void stopAgent(p, b);
|
|
2743
3014
|
else if (b.dataset.act === "resume") {
|
|
2744
3015
|
const r = rowOf(id);
|
|
2745
3016
|
if (r) void restartRow(r, "resume", b);
|
|
@@ -2750,7 +3021,25 @@ export async function mountAgentsPage(root) {
|
|
|
2750
3021
|
window.denShell?.openMenu?.(b, {
|
|
2751
3022
|
label: t("ag.more", { id: r.agent }),
|
|
2752
3023
|
items: [
|
|
2753
|
-
|
|
3024
|
+
// D25: a visible Rename, for touch and keyboard (not only a
|
|
3025
|
+
// double-click on a tab).
|
|
3026
|
+
{ label: t("ag.rn.item"), onSelect: () => openRename(r, b) },
|
|
3027
|
+
// D28: its rotation settings and where each comes from.
|
|
3028
|
+
{
|
|
3029
|
+
label: t("ag.rc.menu"),
|
|
3030
|
+
onSelect: () =>
|
|
3031
|
+
window.denShell?.toast?.(
|
|
3032
|
+
rotFromText(r.rotation) || t("ag.rc.noState"),
|
|
3033
|
+
),
|
|
3034
|
+
},
|
|
3035
|
+
...(r.personaChange
|
|
3036
|
+
? [
|
|
3037
|
+
{
|
|
3038
|
+
label: t("ag.pc.item"),
|
|
3039
|
+
onSelect: () => void openPersonaChange(r),
|
|
3040
|
+
},
|
|
3041
|
+
]
|
|
3042
|
+
: []),
|
|
2754
3043
|
],
|
|
2755
3044
|
});
|
|
2756
3045
|
}
|
|
@@ -233,6 +233,38 @@
|
|
|
233
233
|
font: 12px/1.4 var(--sans);
|
|
234
234
|
color: var(--faint);
|
|
235
235
|
}
|
|
236
|
+
/* D28: the rotation callout: its message, its settings, its buttons. */
|
|
237
|
+
.agst .agcant[data-rot-msg] {
|
|
238
|
+
color: var(--warn-text, var(--text));
|
|
239
|
+
}
|
|
240
|
+
.agst .agfrom {
|
|
241
|
+
font-size: 11.5px;
|
|
242
|
+
}
|
|
243
|
+
.agnever {
|
|
244
|
+
display: inline-flex;
|
|
245
|
+
align-items: center;
|
|
246
|
+
gap: 4px;
|
|
247
|
+
font: 12px/1.4 var(--sans);
|
|
248
|
+
color: var(--muted);
|
|
249
|
+
}
|
|
250
|
+
/* The callout takes a line of its own under the bar's name and
|
|
251
|
+
buttons, so it never runs under Stop (375 px). */
|
|
252
|
+
.pane .bar2:has(.agrot) {
|
|
253
|
+
flex-wrap: wrap;
|
|
254
|
+
row-gap: 4px;
|
|
255
|
+
}
|
|
256
|
+
.bar2 .agrot {
|
|
257
|
+
display: flex;
|
|
258
|
+
flex-wrap: wrap;
|
|
259
|
+
align-items: center;
|
|
260
|
+
gap: 4px;
|
|
261
|
+
min-width: 0;
|
|
262
|
+
order: 10;
|
|
263
|
+
flex: 1 1 100%;
|
|
264
|
+
}
|
|
265
|
+
.bar2 .agrot .agnever {
|
|
266
|
+
display: none;
|
|
267
|
+
}
|
|
236
268
|
.agst[data-review] > .agr {
|
|
237
269
|
border-left-color: var(--warn);
|
|
238
270
|
}
|
|
@@ -412,6 +444,16 @@
|
|
|
412
444
|
flex: none;
|
|
413
445
|
white-space: nowrap;
|
|
414
446
|
}
|
|
447
|
+
/* D20: the launchd tag is a button (tap: why); it looks like a tag. */
|
|
448
|
+
button.tagx {
|
|
449
|
+
background: none;
|
|
450
|
+
cursor: pointer;
|
|
451
|
+
line-height: inherit;
|
|
452
|
+
}
|
|
453
|
+
button.tagx:focus-visible {
|
|
454
|
+
outline: 2px solid var(--accent);
|
|
455
|
+
outline-offset: 1px;
|
|
456
|
+
}
|
|
415
457
|
.wslink {
|
|
416
458
|
color: inherit;
|
|
417
459
|
text-decoration: none;
|
|
@@ -771,6 +813,26 @@
|
|
|
771
813
|
</div>
|
|
772
814
|
</form>
|
|
773
815
|
</dialog>
|
|
816
|
+
<!-- D25: Rename, from a pane's ⋯ or a stopped agent's row. -->
|
|
817
|
+
<dialog class="ag-dlg" id="ag-rn" aria-labelledby="ag-rn-h">
|
|
818
|
+
<form method="dialog" id="ag-rn-form" novalidate>
|
|
819
|
+
<h2 id="ag-rn-h">Rename</h2>
|
|
820
|
+
<label class="fld"
|
|
821
|
+
><span id="ag-rn-t">Name</span>
|
|
822
|
+
<input id="ag-rn-name" maxlength="40" autocomplete="off" />
|
|
823
|
+
</label>
|
|
824
|
+
<p class="help" id="ag-rn-help"></p>
|
|
825
|
+
<p class="err" id="ag-rn-err" role="alert" hidden></p>
|
|
826
|
+
<div class="btns">
|
|
827
|
+
<button type="button" class="den-btn" id="ag-rn-cancel">
|
|
828
|
+
Cancel
|
|
829
|
+
</button>
|
|
830
|
+
<button type="submit" class="den-btn primary" id="ag-rn-go">
|
|
831
|
+
Rename
|
|
832
|
+
</button>
|
|
833
|
+
</div>
|
|
834
|
+
</form>
|
|
835
|
+
</dialog>
|
|
774
836
|
|
|
775
837
|
<script type="module">
|
|
776
838
|
import {
|
|
@@ -487,9 +487,50 @@
|
|
|
487
487
|
"Su sesión rotó ({why}), pero no escribió handoff.md. Reanúdalo para que lo escriba, o empieza de cero.",
|
|
488
488
|
"ag.rot.badHandoff":
|
|
489
489
|
"Su sesión rotó ({why}), pero su handoff.md no está completo. Reanúdalo para arreglarlo, o empieza de cero.",
|
|
490
|
-
"ag.relaunch": "Relanzar
|
|
491
|
-
"ag.relaunchName": "Relanzar {id}
|
|
490
|
+
"ag.relaunch": "Relanzar ahora",
|
|
491
|
+
"ag.relaunchName": "Relanzar {id} ahora",
|
|
492
492
|
"ag.relaunched": "{id} relanzado desde su traspaso",
|
|
493
|
+
"ag.rc.autoAsk":
|
|
494
|
+
"Toca rotar ({why}). Den le pide el traspaso cuando esté libre y luego lo relanza.",
|
|
495
|
+
"ag.rc.autoReady":
|
|
496
|
+
"Toca rotar ({why}). Su traspaso está listo: Den lo relanza cuando esté libre.",
|
|
497
|
+
"ag.rc.ready": "Toca rotar ({why}). Su traspaso está listo.",
|
|
498
|
+
"ag.rc.missing": "Toca rotar ({why}). Aún no tiene traspaso.",
|
|
499
|
+
"ag.rc.bad": "Toca rotar ({why}). Su traspaso no está completo.",
|
|
500
|
+
"ag.rc.fellBack":
|
|
501
|
+
"Toca rotar ({why}). No escribió el traspaso tras pedírselo 2 veces.",
|
|
502
|
+
"ag.rc.wanted":
|
|
503
|
+
"Toca rotar ({why}). Den lo relanza en cuanto escriba su traspaso.",
|
|
504
|
+
"ag.rc.write": "Escribir traspaso",
|
|
505
|
+
"ag.rc.writeName": "Pedir a {id} que escriba su traspaso",
|
|
506
|
+
"ag.rc.keep": "Mantener esta conversación",
|
|
507
|
+
"ag.rc.keepName": "Mantener la conversación de {id}",
|
|
508
|
+
"ag.rc.never": "No volver a preguntar para este agente",
|
|
509
|
+
"ag.rc.settings": "Ajustes",
|
|
510
|
+
"ag.rc.from":
|
|
511
|
+
"Rota tras {n} compactaciones ({src}); al tocar: {mode} ({src2})",
|
|
512
|
+
"ag.rc.fellBackBad":
|
|
513
|
+
"Toca rotar ({why}). Su traspaso sigue sin pasar la revisión tras pedírselo 2 veces.",
|
|
514
|
+
"ag.rc.from.one":
|
|
515
|
+
"Rota tras {n} compactación ({src}); al tocar: {mode} ({src2})",
|
|
516
|
+
"ag.rc.fromNever": "Nunca rota ({src}); al tocar: {mode} ({src2})",
|
|
517
|
+
"ag.rc.midTurn":
|
|
518
|
+
"{id} está a mitad de turno: pídelo de nuevo cuando termine",
|
|
519
|
+
"ag.rc.neverN": "nunca",
|
|
520
|
+
"ag.rc.mode.auto": "relanzar solo",
|
|
521
|
+
"ag.rc.mode.ask": "preguntar",
|
|
522
|
+
"ag.rc.mode.off": "nada",
|
|
523
|
+
"ag.rc.menu": "Ajustes de rotación",
|
|
524
|
+
"ag.rc.noState": "Aún no rota: cuenta desde su próximo inicio.",
|
|
525
|
+
"ag.rc.asked": "Se pidió a {id} su traspaso",
|
|
526
|
+
"ag.rc.askedRelaunch":
|
|
527
|
+
"Se pidió a {id} su traspaso. Den lo relanza en cuanto pase la revisión.",
|
|
528
|
+
"ag.rc.askedResumed":
|
|
529
|
+
"{id} reanudado para escribir su traspaso. Den lo relanza en cuanto pase la revisión.",
|
|
530
|
+
"ag.rc.kept": "Conversación de {id} mantenida: su cuenta vuelve a empezar",
|
|
531
|
+
"ag.rc.keptNever": "Conversación de {id} mantenida: ya no rotará",
|
|
532
|
+
"ag.rc.autoRelaunched":
|
|
533
|
+
"{id} relanzado desde su traspaso tras {why}. La conversación anterior {old} se conserva.",
|
|
493
534
|
"ag.remove": "Quitar",
|
|
494
535
|
"ag.removeName": "Quitar {id}",
|
|
495
536
|
"ag.removed": "{id} quitado",
|
|
@@ -705,6 +746,7 @@
|
|
|
705
746
|
"ver.pinned": "fijada {v}",
|
|
706
747
|
"ver.usedLatest": "{n} ahora sigue la versión más reciente",
|
|
707
748
|
"set.agents.personas": "Personas",
|
|
749
|
+
"set.agents.rotation": "Rotación",
|
|
708
750
|
// The Agents tab (AG-4a-term, agents-page.js).
|
|
709
751
|
"n.agents": "Agentes",
|
|
710
752
|
"kind.agent": "Agente",
|
|
@@ -790,6 +832,20 @@
|
|
|
790
832
|
"ag.hint.tile": "Un clic añade una sesión al mosaico, hasta 4.",
|
|
791
833
|
"ag.renameTip": "Doble clic para cambiar el nombre",
|
|
792
834
|
"ag.renameName": "Cambiar el nombre de {id}",
|
|
835
|
+
"ag.rn.item": "Cambiar el nombre…",
|
|
836
|
+
"ag.rn.title": "Cambiar el nombre de {id}",
|
|
837
|
+
"ag.rn.name": "Nombre",
|
|
838
|
+
"ag.rn.help":
|
|
839
|
+
"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}.",
|
|
840
|
+
"ag.rn.go": "Cambiar el nombre",
|
|
841
|
+
"ag.rn.cancel": "Cancelar",
|
|
842
|
+
"ag.rn.act": "Renombrar",
|
|
843
|
+
"ag.where.logout": "Se detiene al cerrar sesión",
|
|
844
|
+
"ag.where.logoutTip":
|
|
845
|
+
"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)",
|
|
846
|
+
"ag.where.bg": "Sin GUI",
|
|
847
|
+
"ag.where.bgTip":
|
|
848
|
+
"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
849
|
"ag.rt.ready": "Inicia {name} {version} desde",
|
|
794
850
|
"ag.rt.readyNoVer": "Inicia {name} desde",
|
|
795
851
|
"ag.rt.notFound": "No se encontró {name}.",
|