@monster-orange/den 0.4.12 → 0.4.14
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 +151 -0
- package/README.md +7 -7
- package/docs/distributions.md +189 -11
- package/package.json +1 -1
- package/src/shared/agents/conversations.ts +227 -0
- package/src/shared/agents/default-pack/be-dev.md +1 -0
- package/src/shared/agents/default-pack/devops.md +1 -0
- package/src/shared/agents/default-pack/fe-dev.md +1 -0
- package/src/shared/agents/default-pack/orchestrator.md +3 -1
- package/src/shared/agents/default-pack/planner.md +1 -0
- package/src/shared/agents/default-pack/reviewer.md +1 -0
- package/src/shared/agents/default-pack/tester.md +16 -0
- package/src/shared/agents/default-pack/ux-designer.md +1 -0
- package/src/shared/agents/den-claude.ts +128 -13
- package/src/shared/agents/permissions.ts +108 -0
- package/src/shared/agents/persona-store.ts +286 -0
- package/src/shared/agents/personas.ts +116 -12
- package/src/shared/agents/records.ts +33 -0
- package/src/shared/agents/roles.ts +288 -0
- package/src/shared/agents/runtime.ts +50 -0
- package/src/shared/den-config.ts +22 -1
- package/src/shared/environment-manager/service-runner.ts +31 -12
- package/src/shared/environment-manager/workspaces.ts +11 -0
- package/src/ui/cli/agent-ops.ts +796 -29
- package/src/ui/cli/agents-command.ts +97 -3
- package/src/ui/cli/env-probe.ts +4 -1
- package/src/ui/mcp/api.ts +12 -1
- package/src/ui/mcp/tools.ts +60 -1
- package/src/ui/server/agent-resume-grant.ts +35 -0
- package/src/ui/server/agent-routes.ts +412 -22
- package/src/ui/server/public/agent-start.js +3 -2
- package/src/ui/server/public/agents-page.js +693 -49
- package/src/ui/server/public/agents.html +76 -15
- package/src/ui/server/public/i18n/es.js +143 -0
- package/src/ui/server/public/i18n/ja.js +144 -0
- package/src/ui/server/public/persona-ui.css +563 -0
- package/src/ui/server/public/persona-ui.js +796 -0
- package/src/ui/server/public/settings-page.js +4 -9
- package/src/ui/server/public/settings-registry.js +22 -0
- package/src/ui/server/public/settings.html +1 -0
- package/src/ui/server/public/shell.js +140 -0
- package/src/ui/server/public/topbar.js +2 -2
- package/src/ui/server/request-guard.ts +17 -4
- package/src/ui/server/route-classes.ts +31 -0
- package/src/ui/server/session-routes.ts +21 -5
- package/src/ui/server/ui-assets.ts +3 -0
|
@@ -27,8 +27,17 @@
|
|
|
27
27
|
* Nothing here keeps a session's state: the list is GET /api/sessions,
|
|
28
28
|
* asked again every few seconds while the page is shown.
|
|
29
29
|
*
|
|
30
|
-
* Start agent (AG-
|
|
31
|
-
*
|
|
30
|
+
* Start agent (AG-10.1): a searchable persona list (category and source
|
|
31
|
+
* chips, grouped by category, Plain session pinned last), What's a
|
|
32
|
+
* persona?, Details, + New persona, Duplicate or Edit (persona-ui.js),
|
|
33
|
+
* Runtime and Workspace, a first instruction (typed into the session once
|
|
34
|
+
* it is ready, by the server: never an argument), and Advanced: model and
|
|
35
|
+
* version (AG-12), permission mode and extra allowed commands with the
|
|
36
|
+
* preview of what the runtime is given (AG-13). What differs from the
|
|
37
|
+
* persona tags the session's row (AG-10.9). The runtime line says what
|
|
38
|
+
* will run, or why it can't, before Start (agent-start.js readiness); a
|
|
39
|
+
* persona whose runtime isn't installed starts on the first one that is,
|
|
40
|
+
* and the dialog says so (AG-12.2). Starting, stopping and
|
|
32
41
|
* resuming an agent never ask for the admin password: it is for sudo, and
|
|
33
42
|
* no agent action runs sudo (LK-0).
|
|
34
43
|
*
|
|
@@ -74,7 +83,27 @@
|
|
|
74
83
|
* and Start fresh. A running one says "Handoff due" or "Ready to relaunch"
|
|
75
84
|
* as a tag.
|
|
76
85
|
*/
|
|
77
|
-
import {
|
|
86
|
+
import { readiness, runtimeName, runtimeOptions } from "/agent-start.js";
|
|
87
|
+
import {
|
|
88
|
+
commandProblem,
|
|
89
|
+
filterBarHtml,
|
|
90
|
+
modelOptionsHtml,
|
|
91
|
+
normalizeData,
|
|
92
|
+
openPersonaEditor,
|
|
93
|
+
overrideTagsHtml,
|
|
94
|
+
permBlockHtml,
|
|
95
|
+
personaDetail,
|
|
96
|
+
personaDetailHtml,
|
|
97
|
+
personaListHtml,
|
|
98
|
+
personaName,
|
|
99
|
+
personaRowHtml,
|
|
100
|
+
personaRuntime,
|
|
101
|
+
runtimeInstalled,
|
|
102
|
+
splitCommands,
|
|
103
|
+
syncPermBlock,
|
|
104
|
+
versionLabel,
|
|
105
|
+
versionOptionsHtml,
|
|
106
|
+
} from "/persona-ui.js";
|
|
78
107
|
import {
|
|
79
108
|
editorUrl,
|
|
80
109
|
findPaths,
|
|
@@ -240,7 +269,7 @@ function nameHtml(r) {
|
|
|
240
269
|
* persona, not the name already shown. Else "".
|
|
241
270
|
*/
|
|
242
271
|
function sessionExtra(r) {
|
|
243
|
-
const n = r?.kind === "agent" ? (r.
|
|
272
|
+
const n = r?.kind === "agent" ? (r.sessionTitle ?? "").trim() : "";
|
|
244
273
|
if (!n || n === r.persona || n === paneName(r) || n === displayName(r))
|
|
245
274
|
return "";
|
|
246
275
|
return n;
|
|
@@ -377,9 +406,10 @@ function rowHtml(r) {
|
|
|
377
406
|
const revokedTag = r.tokenRevoked
|
|
378
407
|
? `<span class="tagx warn" title="${esc(t("ag.revokedTip"))}">${esc(t("ag.revoked"))}</span>`
|
|
379
408
|
: "";
|
|
409
|
+
const ovTags = r.kind === "agent" ? overrideTagsHtml(r) : "";
|
|
380
410
|
const tags =
|
|
381
|
-
rotTag || revokedTag
|
|
382
|
-
? `<span class="agtags">${revokedTag}${rotTag}</span>`
|
|
411
|
+
rotTag || revokedTag || ovTags
|
|
412
|
+
? `<span class="agtags">${revokedTag}${rotTag}${ovTags}</span>`
|
|
383
413
|
: "";
|
|
384
414
|
return (
|
|
385
415
|
`<button type="button" class="agr" data-id="${esc(r.id)}" data-kind="${esc(r.kind)}" data-state="${esc(r.state)}"` +
|
|
@@ -883,11 +913,26 @@ function refit(p) {
|
|
|
883
913
|
markSize(p);
|
|
884
914
|
}
|
|
885
915
|
|
|
886
|
-
/**
|
|
916
|
+
/**
|
|
917
|
+
* The pane's size and the PTY's, on the pane (data-cols, data-pty-cols),
|
|
918
|
+
* and the cell size xterm drew with (data-cell-w, data-cell-h: its
|
|
919
|
+
* renderer's CSS cell, sub-pixel), so a check can tell a missed row from
|
|
920
|
+
* a fit's remainder.
|
|
921
|
+
*/
|
|
887
922
|
function markSize(p) {
|
|
888
923
|
if (!p.term) return;
|
|
889
924
|
p.el.dataset.cols = String(p.term.cols);
|
|
890
925
|
p.el.dataset.rows = String(p.term.rows);
|
|
926
|
+
let cell = null;
|
|
927
|
+
try {
|
|
928
|
+
cell = p.term._core?._renderService?.dimensions?.css?.cell ?? null;
|
|
929
|
+
} catch {
|
|
930
|
+
cell = null;
|
|
931
|
+
}
|
|
932
|
+
if (cell?.width > 0 && cell?.height > 0) {
|
|
933
|
+
p.el.dataset.cellW = String(cell.width);
|
|
934
|
+
p.el.dataset.cellH = String(cell.height);
|
|
935
|
+
}
|
|
891
936
|
if (p.pty) {
|
|
892
937
|
p.el.dataset.ptyCols = String(p.pty.cols);
|
|
893
938
|
p.el.dataset.ptyRows = String(p.pty.rows);
|
|
@@ -1448,7 +1493,7 @@ function review() {
|
|
|
1448
1493
|
)?.focus();
|
|
1449
1494
|
}
|
|
1450
1495
|
|
|
1451
|
-
// ── Start agent
|
|
1496
|
+
// ── Start agent (AG-10.1) ───────────────────────────────────────────────
|
|
1452
1497
|
|
|
1453
1498
|
// Personas and runtimes, per workspace (AG-0.5: a workspace's own runtime
|
|
1454
1499
|
// paths win), asked once per page each.
|
|
@@ -1470,81 +1515,667 @@ function personasFor(ws) {
|
|
|
1470
1515
|
return personasAsked.get(key);
|
|
1471
1516
|
}
|
|
1472
1517
|
|
|
1473
|
-
/**
|
|
1474
|
-
|
|
1475
|
-
|
|
1518
|
+
/**
|
|
1519
|
+
* "Plain session": no persona, a shell in the workspace (AG-10.1). A ":"
|
|
1520
|
+
* is never in a persona's id.
|
|
1521
|
+
*/
|
|
1522
|
+
const PLAIN = ":plain";
|
|
1523
|
+
|
|
1524
|
+
/** The dialog's state while it is open. */
|
|
1525
|
+
const sd = {
|
|
1526
|
+
data: null,
|
|
1527
|
+
f: { q: "", cat: "", src: "" },
|
|
1528
|
+
pid: "",
|
|
1529
|
+
det: false,
|
|
1530
|
+
what: false,
|
|
1531
|
+
detail: null,
|
|
1532
|
+
// Advanced: "" = the persona's.
|
|
1533
|
+
model: "",
|
|
1534
|
+
ver: "",
|
|
1535
|
+
intent: "ask",
|
|
1536
|
+
extras: [],
|
|
1537
|
+
extrasOpen: false,
|
|
1538
|
+
// AG-17.1, Bring in: null for Start; else the workspace's conversations,
|
|
1539
|
+
// the runtimes that can't bring one in, and the one picked.
|
|
1540
|
+
bring: null,
|
|
1541
|
+
};
|
|
1542
|
+
|
|
1543
|
+
/** The conversation picked in Bring in, when it can be brought in. */
|
|
1544
|
+
function bringPick() {
|
|
1545
|
+
const c = (sd.bring?.list ?? []).find((x) => x.id === sd.bring.pick);
|
|
1546
|
+
return c && !c.open && !c.agent ? c : null;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
/** "3 min ago", "2 h ago", or the date. */
|
|
1550
|
+
function agoText(iso) {
|
|
1551
|
+
const ms = Date.now() - Date.parse(iso);
|
|
1552
|
+
if (!Number.isFinite(ms)) return "";
|
|
1553
|
+
const m = Math.round(ms / 60000);
|
|
1554
|
+
if (m < 1) return t("ag.bring.now");
|
|
1555
|
+
if (m < 60) return t("ag.bring.min", { n: m });
|
|
1556
|
+
const h = Math.round(m / 60);
|
|
1557
|
+
if (h < 48) return t("ag.bring.h", { n: h });
|
|
1558
|
+
return iso.slice(0, 10);
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
/** The folder a conversation ran in, inside the workspace's when it is. */
|
|
1562
|
+
function convFolder(c) {
|
|
1563
|
+
const root = sd.bring?.root;
|
|
1564
|
+
if (root && c.cwd === root) return ".";
|
|
1565
|
+
if (root && c.cwd.startsWith(`${root}/`)) return c.cwd.slice(root.length + 1);
|
|
1566
|
+
return c.cwd;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
function convRowHtml(c) {
|
|
1570
|
+
const taken = c.agent
|
|
1571
|
+
? ` <span class="tagx">${esc(t("ag.bring.agent", { id: c.agent }))}</span>`
|
|
1572
|
+
: c.open
|
|
1573
|
+
? ` <span class="tagx warn" title="${esc(t("ag.bring.openTip"))}">${esc(t("ag.bring.open"))}</span>`
|
|
1574
|
+
: "";
|
|
1575
|
+
const off = c.agent || c.open;
|
|
1576
|
+
return (
|
|
1577
|
+
`<button type="button" class="pr" role="radio" data-cid="${esc(c.id)}" aria-checked="${c.id === sd.bring.pick}"${off ? ' aria-disabled="true"' : ""}>` +
|
|
1578
|
+
`<span class="rd" aria-hidden="true"></span><span class="pn pnc"><span class="pt">${esc(c.title || t("ag.bring.untitled"))}</span>${taken}</span><span class="mono">${esc(agoText(c.lastAt))}</span>` +
|
|
1579
|
+
`<span class="pd mono">${esc(convFolder(c))} · ${esc(c.id.slice(0, 8))}</span></button>`
|
|
1580
|
+
);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
/** Bring in's conversation list and its help line. */
|
|
1584
|
+
function paintConversations() {
|
|
1585
|
+
const f = $d("#ag-conv-f");
|
|
1586
|
+
f.hidden = !sd.bring;
|
|
1587
|
+
if (!sd.bring) return;
|
|
1588
|
+
const list = sd.bring.list;
|
|
1589
|
+
const cant = (sd.bring.runtimes ?? [])
|
|
1590
|
+
.filter((r) => !r.bringIn && r.id !== "generic")
|
|
1591
|
+
.map((r) => r.why);
|
|
1592
|
+
$d("#ag-conv-h").textContent = [
|
|
1593
|
+
list.length ? t("ag.bring.help") : t("ag.bring.none"),
|
|
1594
|
+
// Each its own sentence (they read as one run-on line before).
|
|
1595
|
+
...cant.map((w) => (/[.!?]$/.test(w) ? w : `${w}.`)),
|
|
1596
|
+
].join(" ");
|
|
1597
|
+
$d("#ag-conv").innerHTML = list.map(convRowHtml).join("");
|
|
1598
|
+
$d("#ag-conv").hidden = !list.length;
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
/** Ask for the workspace's conversations (Bring in). */
|
|
1602
|
+
async function fillConversations(ws) {
|
|
1603
|
+
if (!sd.bring) return true;
|
|
1476
1604
|
try {
|
|
1477
|
-
|
|
1605
|
+
const r = await api(
|
|
1606
|
+
"GET",
|
|
1607
|
+
`/api/workspaces/${encodeURIComponent(ws)}/conversations`,
|
|
1608
|
+
);
|
|
1609
|
+
sd.bring.list = r?.conversations ?? [];
|
|
1610
|
+
sd.bring.runtimes = r?.runtimes ?? [];
|
|
1611
|
+
sd.bring.root = r?.folder ?? "";
|
|
1478
1612
|
} catch (err) {
|
|
1479
1613
|
window.denShell?.errorToast?.(err);
|
|
1480
1614
|
return false;
|
|
1481
1615
|
}
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
const
|
|
1490
|
-
if (
|
|
1491
|
-
|
|
1492
|
-
|
|
1616
|
+
if (!sd.bring.list.some((c) => c.id === sd.bring.pick))
|
|
1617
|
+
sd.bring.pick = sd.bring.list.find((c) => !c.open && !c.agent)?.id ?? "";
|
|
1618
|
+
paintConversations();
|
|
1619
|
+
return true;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
function pickConversation(id) {
|
|
1623
|
+
const c = (sd.bring?.list ?? []).find((x) => x.id === id);
|
|
1624
|
+
if (!c || c.open || c.agent) return;
|
|
1625
|
+
sd.bring.pick = id;
|
|
1626
|
+
for (const b of st.root.querySelectorAll("#ag-conv [data-cid]"))
|
|
1627
|
+
b.setAttribute("aria-checked", String(b.dataset.cid === id));
|
|
1628
|
+
paintRuntime(false);
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
const $d = (sel) => st.root.querySelector(sel);
|
|
1632
|
+
const curPersona = () =>
|
|
1633
|
+
sd.pid === PLAIN
|
|
1634
|
+
? null
|
|
1635
|
+
: (sd.data?.personas ?? []).find((p) => p.id === sd.pid) || null;
|
|
1636
|
+
|
|
1637
|
+
/** The runtime the dialog will start: the one picked, else the persona's. */
|
|
1638
|
+
function chosenRuntime() {
|
|
1639
|
+
const p = curPersona();
|
|
1640
|
+
return $d("#ag-runtime").value || personaRuntime(sd.data, p);
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
/** The persona's own model and version, on the runtime chosen (AG-12.3). */
|
|
1644
|
+
function personaModel() {
|
|
1645
|
+
const p = curPersona();
|
|
1646
|
+
const same = p && personaRuntime(sd.data, p) === chosenRuntime();
|
|
1647
|
+
return {
|
|
1648
|
+
model: same ? p.model || "" : "",
|
|
1649
|
+
version: same ? p.version || "latest" : "latest",
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
/** The persona list's rows, and Plain session pinned at the bottom. */
|
|
1654
|
+
function plistHtml() {
|
|
1655
|
+
const ps = sd.data?.personas ?? [];
|
|
1656
|
+
const cats = sd.data?.categories ?? [];
|
|
1657
|
+
const rows = personaListHtml(sd.f, ps, cats, (p) =>
|
|
1658
|
+
personaRowHtml(p, p.id === sd.pid, sd.data),
|
|
1659
|
+
);
|
|
1660
|
+
// Bring in continues a conversation: it needs a persona.
|
|
1661
|
+
if (sd.bring) return rows;
|
|
1662
|
+
return (
|
|
1663
|
+
rows +
|
|
1664
|
+
`<div class="pgh"><span>${esc(t("ag.session"))}</span><span></span></div>` +
|
|
1665
|
+
`<button type="button" class="pr" role="radio" data-pid="${esc(PLAIN)}" aria-checked="${sd.pid === PLAIN}"><span class="rd" aria-hidden="true"></span><span class="pn">${esc(t("ag.new.plain"))}</span><span></span><span class="pd">${esc(t("ag.new.plainP"))}</span></button>`
|
|
1666
|
+
);
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
function paintPlist(keepScroll = true) {
|
|
1670
|
+
const l = $d("#ag-persona");
|
|
1671
|
+
const top = l.scrollTop;
|
|
1672
|
+
l.innerHTML = plistHtml();
|
|
1673
|
+
l.scrollTop = keepScroll ? top : 0;
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
/** The Details row (and Duplicate or Edit) under the list, and Details. */
|
|
1677
|
+
function paintDetails() {
|
|
1678
|
+
const p = curPersona();
|
|
1679
|
+
const el = $d("#ag-detrow");
|
|
1680
|
+
if (!p) {
|
|
1681
|
+
el.innerHTML = "";
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
const mine = p.origin === "local";
|
|
1685
|
+
el.innerHTML =
|
|
1686
|
+
`<div class="fldh"><button type="button" class="lnk" data-x="det" aria-expanded="${sd.det}" aria-controls="ag-det">${esc(sd.det ? t("ag.new.hide") : t("ag.new.details", { n: personaName(p) }))}</button><span class="sp"></span>` +
|
|
1687
|
+
(mine
|
|
1688
|
+
? `<button type="button" class="lnk" data-x="edit">${esc(t("pe.editB"))}</button>`
|
|
1689
|
+
: "") +
|
|
1690
|
+
`<button type="button" class="lnk" data-x="dup">${esc(t("pe.dup"))}</button></div>` +
|
|
1691
|
+
(sd.det
|
|
1692
|
+
? personaDetailHtml(
|
|
1693
|
+
sd.detail?.id === p.id ? sd.detail : { ...p, instructions: null },
|
|
1694
|
+
sd.data,
|
|
1695
|
+
)
|
|
1696
|
+
: "");
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
/**
|
|
1700
|
+
* The runtime select and its readiness line (AG-0.2), and AG-12.2: a
|
|
1701
|
+
* persona whose runtime isn't installed starts on the first one that is,
|
|
1702
|
+
* and the dialog says so.
|
|
1703
|
+
*/
|
|
1704
|
+
function paintRuntime(reset) {
|
|
1705
|
+
const rs = $d("#ag-runtime");
|
|
1706
|
+
const go = $d("#ag-go");
|
|
1707
|
+
const ready = $d("#ag-r-ready");
|
|
1708
|
+
const plain = sd.pid === PLAIN;
|
|
1709
|
+
// Bring in: the conversation's runtime, its own history; no first
|
|
1710
|
+
// instruction, no Advanced (it keeps what the persona gives).
|
|
1711
|
+
$d("#ag-r-l").hidden = plain || !!sd.bring;
|
|
1712
|
+
$d("#ag-task-l").hidden = plain || !!sd.bring;
|
|
1713
|
+
$d("#ag-adv").hidden = plain || !!sd.bring;
|
|
1714
|
+
$d("#ag-name-l").hidden = plain || !!sd.bring;
|
|
1715
|
+
paintStartsAs();
|
|
1716
|
+
if (sd.bring) {
|
|
1717
|
+
$d("#ag-r-miss").hidden = true;
|
|
1718
|
+
ready.hidden = true;
|
|
1719
|
+
const ok = !!bringPick() && !!curPersona();
|
|
1720
|
+
go.disabled = !ok;
|
|
1721
|
+
go.dataset.blocked = ok ? "" : "conversation";
|
|
1722
|
+
return;
|
|
1493
1723
|
}
|
|
1494
|
-
const
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1724
|
+
const fb = $d("#ag-r-fb");
|
|
1725
|
+
fb.hidden = true;
|
|
1726
|
+
if (plain) {
|
|
1727
|
+
$d("#ag-r-miss").hidden = true;
|
|
1728
|
+
ready.hidden = true;
|
|
1729
|
+
go.disabled = false;
|
|
1730
|
+
go.dataset.blocked = "";
|
|
1731
|
+
return;
|
|
1498
1732
|
}
|
|
1733
|
+
const personas = sd.data?.personas ?? [];
|
|
1734
|
+
const runtimes = sd.data?.runtimes ?? [];
|
|
1735
|
+
const keep = reset ? "" : rs.value;
|
|
1736
|
+
rs.innerHTML = runtimeOptions(personas, runtimes, sd.pid, t);
|
|
1737
|
+
if ([...rs.options].some((o) => o.value === keep && !o.disabled))
|
|
1738
|
+
rs.value = keep;
|
|
1739
|
+
const p = curPersona();
|
|
1740
|
+
if (reset && p && !runtimeInstalled(sd.data, p)) {
|
|
1741
|
+
const first = [...rs.options].find((o) => o.value && !o.disabled);
|
|
1742
|
+
if (first) {
|
|
1743
|
+
rs.value = first.value;
|
|
1744
|
+
fb.textContent = t("rt.fallback", {
|
|
1745
|
+
p: personaName(p),
|
|
1746
|
+
r: runtimeName(runtimes, personaRuntime(sd.data, p)),
|
|
1747
|
+
f: runtimeName(runtimes, first.value),
|
|
1748
|
+
});
|
|
1749
|
+
fb.hidden = false;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
// AG-12.1: what isn't installed here, and Install… (later).
|
|
1753
|
+
const miss = runtimes.filter(
|
|
1754
|
+
(x) => !x.available && x.denTools !== undefined && x.source !== "setting",
|
|
1755
|
+
);
|
|
1756
|
+
const ml = $d("#ag-r-miss");
|
|
1757
|
+
ml.innerHTML = miss.length
|
|
1758
|
+
? `${esc(t("rt.missingLine"))} ${miss.map((x) => `<span class="mono">${esc(x.label || x.id)}</span>`).join(", ")} · <span class="lnk" aria-disabled="true" title="${esc(t("rt.later"))}">${esc(t("rt.install"))}</span> <span class="tagx">${esc(t("rt.later"))}</span>`
|
|
1759
|
+
: "";
|
|
1760
|
+
ml.hidden = !miss.length;
|
|
1761
|
+
const r = readiness(sd.data, sd.pid, rs.value, t);
|
|
1762
|
+
ready.innerHTML = r.html;
|
|
1763
|
+
ready.hidden = !r.html;
|
|
1764
|
+
ready.dataset.tone = r.tone;
|
|
1765
|
+
go.disabled = !r.ok;
|
|
1766
|
+
go.dataset.blocked = r.ok ? "" : "runtime";
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
/** Advanced (AG-12, AG-13): model, version and the permission block. */
|
|
1770
|
+
function paintAdvanced() {
|
|
1771
|
+
const p = curPersona();
|
|
1772
|
+
const body = $d("#ag-adv-b");
|
|
1773
|
+
if (!p) {
|
|
1774
|
+
body.innerHTML = "";
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
const rt = chosenRuntime();
|
|
1778
|
+
const rtv = (sd.data?.runtimes ?? []).find((r) => r.id === rt);
|
|
1779
|
+
const name = runtimeName(sd.data?.runtimes ?? [], rt);
|
|
1780
|
+
const def = personaModel();
|
|
1781
|
+
const models = rtv?.models ?? [];
|
|
1782
|
+
const model = sd.model || def.model;
|
|
1783
|
+
const first = t("ag.new.modelDef", {
|
|
1784
|
+
// No model of its own: just the version, so the label fits the select.
|
|
1785
|
+
m: def.model
|
|
1786
|
+
? `${def.model} · ${versionLabel(def.version)}`
|
|
1787
|
+
: versionLabel("latest"),
|
|
1788
|
+
});
|
|
1789
|
+
const verFirst = t("ag.new.verDef", {
|
|
1790
|
+
v: versionLabel(
|
|
1791
|
+
sd.model && sd.model !== def.model ? "latest" : def.version,
|
|
1792
|
+
),
|
|
1793
|
+
});
|
|
1794
|
+
body.innerHTML =
|
|
1795
|
+
`<p>${esc(t("ag.new.advNote"))}</p>` +
|
|
1796
|
+
`<div class="two"><label class="fld"><span>${esc(t("ag.new.model"))}</span><select id="ag-model"${models.length ? "" : " disabled"}>${models.length ? modelOptionsHtml(sd.data, rt, sd.model, first) : `<option value="">${esc(t("pe.nsModel", { r: name }))}</option>`}</select></label>` +
|
|
1797
|
+
`<label class="fld"><span>${esc(t("ag.new.ver"))}</span><select id="ag-ver"${models.length && model ? "" : " disabled"}>${versionOptionsHtml(sd.data, rt, model, sd.ver, verFirst)}</select></label></div>` +
|
|
1798
|
+
permBlockHtml("ag", sd.data, {
|
|
1799
|
+
intent: sd.intent,
|
|
1800
|
+
role: p.role,
|
|
1801
|
+
extras: sd.extras,
|
|
1802
|
+
runtime: rt,
|
|
1803
|
+
extrasOpen: sd.extrasOpen,
|
|
1804
|
+
disabled: !rtv?.permissions,
|
|
1805
|
+
});
|
|
1806
|
+
$d("#ag-tools").disabled = false;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
/** Pick persona `id` (or PLAIN): its own choices fill Advanced in. */
|
|
1810
|
+
function pickPersona(id) {
|
|
1811
|
+
sd.pid = id;
|
|
1812
|
+
sd.det = sd.det && id !== PLAIN;
|
|
1813
|
+
const p = curPersona();
|
|
1814
|
+
sd.model = "";
|
|
1815
|
+
sd.ver = "";
|
|
1816
|
+
sd.intent = p?.permission || "ask";
|
|
1817
|
+
sd.extras = [...(p?.allowedCommands ?? [])];
|
|
1818
|
+
sd.extrasOpen = sd.extras.length > 0;
|
|
1819
|
+
for (const b of st.root.querySelectorAll("#ag-persona [data-pid]"))
|
|
1820
|
+
b.setAttribute("aria-checked", String(b.dataset.pid === id));
|
|
1821
|
+
paintDetails();
|
|
1822
|
+
paintRuntime(true);
|
|
1823
|
+
paintAdvanced();
|
|
1824
|
+
if (sd.det) void loadDetail();
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
async function loadDetail() {
|
|
1828
|
+
const p = curPersona();
|
|
1829
|
+
if (!p) return;
|
|
1830
|
+
try {
|
|
1831
|
+
const d = await personaDetail(p.id);
|
|
1832
|
+
if (curPersona()?.id !== d.id) return;
|
|
1833
|
+
sd.detail = d;
|
|
1834
|
+
paintDetails();
|
|
1835
|
+
} catch (err) {
|
|
1836
|
+
window.denShell?.errorToast?.(err);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
/** The whole persona field, for the data asked. */
|
|
1841
|
+
function paintPersonaField() {
|
|
1842
|
+
const ps = sd.data?.personas ?? [];
|
|
1843
|
+
$d("#ag-pfbar").innerHTML = filterBarHtml(
|
|
1844
|
+
sd.f,
|
|
1845
|
+
"ag",
|
|
1846
|
+
ps,
|
|
1847
|
+
sd.data?.categories ?? [],
|
|
1848
|
+
);
|
|
1849
|
+
$d("#ag-what").hidden = !sd.what;
|
|
1850
|
+
paintPlist(false);
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
/** Ask for the workspace's personas and runtimes; false when it failed. */
|
|
1854
|
+
async function fillForWorkspace(ws) {
|
|
1855
|
+
try {
|
|
1856
|
+
sd.data = normalizeData(await personasFor(ws));
|
|
1857
|
+
} catch (err) {
|
|
1858
|
+
window.denShell?.errorToast?.(err);
|
|
1859
|
+
return false;
|
|
1860
|
+
}
|
|
1861
|
+
if (!(await fillConversations(ws))) return false;
|
|
1862
|
+
if (sd.bring && sd.pid === PLAIN) sd.pid = "";
|
|
1863
|
+
const ps = sd.data?.personas ?? [];
|
|
1864
|
+
if (sd.pid !== PLAIN && !ps.some((p) => p.id === sd.pid)) {
|
|
1865
|
+
// The first persona the list shows (grouped by category).
|
|
1866
|
+
const cats = (sd.data?.categories ?? []).map((c) => c.id);
|
|
1867
|
+
const sorted = [...ps].sort(
|
|
1868
|
+
(a, b) =>
|
|
1869
|
+
(cats.indexOf(a.category) + 1 || 99) -
|
|
1870
|
+
(cats.indexOf(b.category) + 1 || 99),
|
|
1871
|
+
);
|
|
1872
|
+
sd.pid = sorted[0]?.id ?? PLAIN;
|
|
1873
|
+
}
|
|
1874
|
+
paintPersonaField();
|
|
1875
|
+
pickPersona(sd.pid);
|
|
1499
1876
|
return true;
|
|
1500
1877
|
}
|
|
1501
1878
|
|
|
1502
|
-
|
|
1503
|
-
|
|
1879
|
+
/** The dialog's words for Start, or for Bring in (AG-17.1). */
|
|
1880
|
+
function dialogMode(bring) {
|
|
1881
|
+
sd.bring = bring ? { list: [], runtimes: [], root: "", pick: "" } : null;
|
|
1882
|
+
$d("#ag-dlg-h").textContent = t(bring ? "ag.bring.title" : "ag.new.title");
|
|
1883
|
+
$d("#ag-note").textContent = t(bring ? "ag.bring.note" : "ag.new.note");
|
|
1884
|
+
$d("#ag-go").textContent = t(bring ? "ag.bring.go" : "ag.go");
|
|
1885
|
+
$d("#ag-conv-f").hidden = !bring;
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
async function openStart(bring = false) {
|
|
1889
|
+
const dlg = $d("#ag-dlg");
|
|
1504
1890
|
if (!dlg || dlg.open) return;
|
|
1891
|
+
dialogMode(bring);
|
|
1505
1892
|
const shown = viewWorkspaces();
|
|
1506
|
-
const wsSel =
|
|
1893
|
+
const wsSel = $d("#ag-ws");
|
|
1507
1894
|
wsSel.innerHTML = shown
|
|
1508
1895
|
.map((w) => `<option value="${esc(w)}">${esc(w)}</option>`)
|
|
1509
1896
|
.join("");
|
|
1510
|
-
|
|
1897
|
+
wsSel.disabled = shown.length <= 1;
|
|
1898
|
+
sd.f = { q: "", cat: "", src: "" };
|
|
1899
|
+
sd.det = false;
|
|
1900
|
+
sd.what = false;
|
|
1901
|
+
$d("#ag-err").hidden = true;
|
|
1902
|
+
$d("#ag-task").value = "";
|
|
1903
|
+
$d("#ag-adv").open = false;
|
|
1511
1904
|
if (!(await fillForWorkspace(wsSel.value || shown[0]))) return;
|
|
1512
1905
|
if (dlg.open) return;
|
|
1513
1906
|
if (typeof dlg.showModal === "function") dlg.showModal();
|
|
1514
1907
|
else dlg.setAttribute("open", "");
|
|
1908
|
+
$d("#ag-q")?.focus();
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
/** What Start sends (agent-routes.ts checks each field again). */
|
|
1912
|
+
function startBody() {
|
|
1913
|
+
const p = curPersona();
|
|
1914
|
+
const rt = chosenRuntime();
|
|
1915
|
+
const rtv = (sd.data?.runtimes ?? []).find((r) => r.id === rt);
|
|
1916
|
+
const runtime = $d("#ag-runtime").value;
|
|
1917
|
+
const body = { persona: p.id, ...(runtime ? { runtime } : {}) };
|
|
1918
|
+
const def = personaModel();
|
|
1919
|
+
if (rtv?.models?.length) {
|
|
1920
|
+
if (sd.model) body.model = sd.model;
|
|
1921
|
+
if (sd.ver) {
|
|
1922
|
+
body.version = sd.ver;
|
|
1923
|
+
body.model ??= def.model || undefined;
|
|
1924
|
+
if (!body.model) delete body.version;
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
if (rtv?.permissions) {
|
|
1928
|
+
// A persona's own mode Den doesn't name (Claude Code's plan) stays its
|
|
1929
|
+
// own unless a mode is picked.
|
|
1930
|
+
if (p.permission !== undefined || sd.intent !== "ask")
|
|
1931
|
+
body.permission = sd.intent;
|
|
1932
|
+
body.allow = sd.extras;
|
|
1933
|
+
}
|
|
1934
|
+
const task = $d("#ag-task").value.trim();
|
|
1935
|
+
if (task) body.instruction = task;
|
|
1936
|
+
const name = $d("#ag-name").value.trim();
|
|
1937
|
+
if (name) body.name = name;
|
|
1938
|
+
return body;
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
function showStartError(err) {
|
|
1942
|
+
const el = $d("#ag-err");
|
|
1943
|
+
el.textContent = err?.message || String(err);
|
|
1944
|
+
// AG-10.10: that agent is running: offer to open it.
|
|
1945
|
+
const running = err?.data?.running;
|
|
1946
|
+
if (running?.sessionId) {
|
|
1947
|
+
const b = document.createElement("button");
|
|
1948
|
+
b.type = "button";
|
|
1949
|
+
b.className = "den-btn sm";
|
|
1950
|
+
b.id = "ag-open-running";
|
|
1951
|
+
b.textContent = t("ag.new.openRunning");
|
|
1952
|
+
b.addEventListener("click", () => {
|
|
1953
|
+
$d("#ag-dlg").close?.();
|
|
1954
|
+
void openPane(running.sessionId);
|
|
1955
|
+
});
|
|
1956
|
+
el.append(" ", b);
|
|
1957
|
+
}
|
|
1958
|
+
el.hidden = false;
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
/** AG-10.10: the id this start takes, so two of one persona are told apart. */
|
|
1962
|
+
function paintStartsAs() {
|
|
1963
|
+
const el = $d("#ag-as");
|
|
1964
|
+
if (!el) return;
|
|
1965
|
+
const p = sd.pid && sd.pid !== PLAIN && !sd.bring ? curPersona() : null;
|
|
1966
|
+
el.hidden = !p;
|
|
1967
|
+
if (p)
|
|
1968
|
+
el.textContent = t("ag.new.as", { id: sd.data?.startsAs?.[p.id] ?? p.id });
|
|
1515
1969
|
}
|
|
1516
1970
|
|
|
1517
1971
|
async function submitStart(form) {
|
|
1518
|
-
const dlg =
|
|
1519
|
-
const go =
|
|
1972
|
+
const dlg = $d("#ag-dlg");
|
|
1973
|
+
const go = $d("#ag-go");
|
|
1520
1974
|
if (go.disabled) return;
|
|
1521
1975
|
const ws = form.querySelector("#ag-ws").value || viewWorkspaces()[0];
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1976
|
+
if (!ws || !sd.pid) return;
|
|
1977
|
+
if (sd.bring) return submitBringIn(ws);
|
|
1978
|
+
const bad = sd.extras.find((c) => commandProblem(c));
|
|
1979
|
+
if (sd.pid !== PLAIN && bad)
|
|
1980
|
+
return showStartError(new Error(t("pe.badCmd", { c: bad })));
|
|
1525
1981
|
go.disabled = true;
|
|
1526
1982
|
go.textContent = t("ag.starting");
|
|
1527
1983
|
try {
|
|
1984
|
+
if (sd.pid === PLAIN) {
|
|
1985
|
+
const r = await api(
|
|
1986
|
+
"POST",
|
|
1987
|
+
`/api/workspaces/${encodeURIComponent(ws)}/sessions`,
|
|
1988
|
+
);
|
|
1989
|
+
window.denShell?.toast?.(
|
|
1990
|
+
t("ag.new.started", { p: t("ag.session"), w: ws }),
|
|
1991
|
+
);
|
|
1992
|
+
dlg.close?.();
|
|
1993
|
+
await refresh();
|
|
1994
|
+
if (r?.session?.id) await openPane(r.session.id);
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1528
1997
|
const r = await api(
|
|
1529
1998
|
"POST",
|
|
1530
1999
|
`/api/workspaces/${encodeURIComponent(ws)}/agents`,
|
|
1531
|
-
|
|
1532
|
-
persona,
|
|
1533
|
-
...(runtime ? { runtime } : {}),
|
|
1534
|
-
},
|
|
2000
|
+
startBody(),
|
|
1535
2001
|
);
|
|
1536
|
-
window.denShell?.toast?.(t("ag.started", { id: r?.agent?.id ??
|
|
2002
|
+
window.denShell?.toast?.(t("ag.started", { id: r?.agent?.id ?? sd.pid }));
|
|
2003
|
+
// AG-10.10: the next start of this persona takes another id.
|
|
2004
|
+
personasAsked.delete(ws);
|
|
2005
|
+
$d("#ag-name").value = "";
|
|
2006
|
+
for (const w of r?.warnings ?? []) window.denShell?.toast?.(w);
|
|
1537
2007
|
dlg.close?.();
|
|
1538
2008
|
await refresh();
|
|
1539
2009
|
if (r?.agent?.sessionId) await openPane(r.agent.sessionId);
|
|
1540
2010
|
} catch (err) {
|
|
1541
|
-
|
|
2011
|
+
showStartError(err);
|
|
1542
2012
|
} finally {
|
|
1543
2013
|
go.disabled = false;
|
|
1544
2014
|
go.textContent = t("ag.go");
|
|
2015
|
+
if (dlg.open) paintRuntime(false);
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
/** Bring in (AG-17.1): the runtime's resume of the conversation picked. */
|
|
2020
|
+
async function submitBringIn(ws) {
|
|
2021
|
+
const dlg = $d("#ag-dlg");
|
|
2022
|
+
const go = $d("#ag-go");
|
|
2023
|
+
const c = bringPick();
|
|
2024
|
+
const p = curPersona();
|
|
2025
|
+
if (!c || !p) return;
|
|
2026
|
+
go.disabled = true;
|
|
2027
|
+
go.textContent = t("ag.bring.going");
|
|
2028
|
+
try {
|
|
2029
|
+
const r = await api(
|
|
2030
|
+
"POST",
|
|
2031
|
+
`/api/workspaces/${encodeURIComponent(ws)}/conversations/${encodeURIComponent(c.id)}/import`,
|
|
2032
|
+
{ persona: p.id, runtime: c.runtime },
|
|
2033
|
+
);
|
|
2034
|
+
window.denShell?.toast?.(t("ag.bring.done", { id: r?.agent?.id ?? p.id }));
|
|
2035
|
+
for (const w of r?.warnings ?? []) window.denShell?.toast?.(w);
|
|
2036
|
+
dlg.close?.();
|
|
2037
|
+
await refresh();
|
|
2038
|
+
if (r?.agent?.sessionId) await openPane(r.agent.sessionId);
|
|
2039
|
+
} catch (err) {
|
|
2040
|
+
showStartError(err);
|
|
2041
|
+
// Still open elsewhere, or taken since: the list says so now.
|
|
2042
|
+
await fillConversations(ws);
|
|
2043
|
+
} finally {
|
|
2044
|
+
go.textContent = t("ag.bring.go");
|
|
2045
|
+
if (dlg.open) paintRuntime(false);
|
|
1545
2046
|
}
|
|
1546
2047
|
}
|
|
1547
2048
|
|
|
2049
|
+
/** New persona, Edit or Duplicate from the dialog; back to it after. */
|
|
2050
|
+
async function editFromStart(kind) {
|
|
2051
|
+
const p = curPersona();
|
|
2052
|
+
let src = null;
|
|
2053
|
+
if (kind !== "new" && p) {
|
|
2054
|
+
try {
|
|
2055
|
+
src = await personaDetail(p.id);
|
|
2056
|
+
} catch (err) {
|
|
2057
|
+
return void window.denShell?.errorToast?.(err);
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
openPersonaEditor({
|
|
2061
|
+
data: sd.data,
|
|
2062
|
+
src,
|
|
2063
|
+
dup: kind === "dup",
|
|
2064
|
+
onSaved: async (saved) => {
|
|
2065
|
+
personasAsked.clear();
|
|
2066
|
+
if (saved) sd.pid = saved.id;
|
|
2067
|
+
await fillForWorkspace($d("#ag-ws").value);
|
|
2068
|
+
},
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
/** Wire the dialog once (mountAgentsPage). */
|
|
2073
|
+
function wireStartDialog(root) {
|
|
2074
|
+
const form = root.querySelector("#ag-form");
|
|
2075
|
+
form.addEventListener("click", (e) => {
|
|
2076
|
+
const cr = e.target.closest("#ag-conv [data-cid]");
|
|
2077
|
+
if (cr) {
|
|
2078
|
+
e.preventDefault();
|
|
2079
|
+
pickConversation(cr.dataset.cid);
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
2082
|
+
const pr = e.target.closest("#ag-persona [data-pid]");
|
|
2083
|
+
if (pr) {
|
|
2084
|
+
e.preventDefault();
|
|
2085
|
+
pickPersona(pr.dataset.pid);
|
|
2086
|
+
pr.focus();
|
|
2087
|
+
return;
|
|
2088
|
+
}
|
|
2089
|
+
const chip = e.target.closest("[data-pf]");
|
|
2090
|
+
if (chip) {
|
|
2091
|
+
e.preventDefault();
|
|
2092
|
+
sd.f[chip.dataset.pf] = chip.dataset.v;
|
|
2093
|
+
for (const c of form.querySelectorAll(`[data-pf="${chip.dataset.pf}"]`))
|
|
2094
|
+
c.setAttribute("aria-pressed", String(c.dataset.v === chip.dataset.v));
|
|
2095
|
+
return paintPlist(false);
|
|
2096
|
+
}
|
|
2097
|
+
const pm = e.target.closest("[data-pm]");
|
|
2098
|
+
if (pm) {
|
|
2099
|
+
e.preventDefault();
|
|
2100
|
+
if (pm.disabled) return;
|
|
2101
|
+
sd.intent = pm.dataset.pm;
|
|
2102
|
+
return syncPermBlock(form, "ag", sd.data, {
|
|
2103
|
+
intent: sd.intent,
|
|
2104
|
+
role: curPersona()?.role,
|
|
2105
|
+
extras: sd.extras,
|
|
2106
|
+
runtime: chosenRuntime(),
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
const x = e.target.closest("[data-x]")?.dataset.x;
|
|
2110
|
+
if (!x) return;
|
|
2111
|
+
e.preventDefault();
|
|
2112
|
+
if (x === "what") {
|
|
2113
|
+
sd.what = !sd.what;
|
|
2114
|
+
$d("#ag-what").hidden = !sd.what;
|
|
2115
|
+
e.target
|
|
2116
|
+
.closest("[data-x]")
|
|
2117
|
+
.setAttribute("aria-expanded", String(sd.what));
|
|
2118
|
+
} else if (x === "det") {
|
|
2119
|
+
sd.det = !sd.det;
|
|
2120
|
+
paintDetails();
|
|
2121
|
+
if (sd.det) void loadDetail();
|
|
2122
|
+
} else if (x === "clear") {
|
|
2123
|
+
sd.f = { q: "", cat: "", src: "" };
|
|
2124
|
+
paintPersonaField();
|
|
2125
|
+
} else if (x === "raw") window.denShell?.toast?.(t("pe.rawLater"));
|
|
2126
|
+
else if (x === "newp") void editFromStart("new");
|
|
2127
|
+
else if (x === "dup") void editFromStart("dup");
|
|
2128
|
+
else if (x === "edit") void editFromStart("edit");
|
|
2129
|
+
});
|
|
2130
|
+
form.addEventListener("input", (e) => {
|
|
2131
|
+
if (e.target.id === "ag-q") {
|
|
2132
|
+
sd.f.q = e.target.value;
|
|
2133
|
+
paintPlist(false);
|
|
2134
|
+
} else if (e.target.id === "ag-tools") {
|
|
2135
|
+
sd.extras = splitCommands(e.target.value);
|
|
2136
|
+
syncPermBlock(form, "ag", sd.data, {
|
|
2137
|
+
intent: sd.intent,
|
|
2138
|
+
role: curPersona()?.role,
|
|
2139
|
+
extras: sd.extras,
|
|
2140
|
+
runtime: chosenRuntime(),
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
2143
|
+
});
|
|
2144
|
+
form.addEventListener(
|
|
2145
|
+
"toggle",
|
|
2146
|
+
(e) => {
|
|
2147
|
+
if (e.target.id === "ag-xtra") sd.extrasOpen = e.target.open;
|
|
2148
|
+
},
|
|
2149
|
+
true,
|
|
2150
|
+
);
|
|
2151
|
+
form.addEventListener("change", (e) => {
|
|
2152
|
+
const id = e.target.id;
|
|
2153
|
+
if (id === "ag-ws") void fillForWorkspace(e.target.value);
|
|
2154
|
+
else if (id === "ag-runtime") {
|
|
2155
|
+
sd.model = "";
|
|
2156
|
+
sd.ver = "";
|
|
2157
|
+
$d("#ag-r-fb").hidden = true;
|
|
2158
|
+
paintRuntime(false);
|
|
2159
|
+
paintAdvanced();
|
|
2160
|
+
} else if (id === "ag-model") {
|
|
2161
|
+
sd.model = e.target.value;
|
|
2162
|
+
sd.ver = "";
|
|
2163
|
+
paintAdvanced();
|
|
2164
|
+
} else if (id === "ag-ver") sd.ver = e.target.value;
|
|
2165
|
+
});
|
|
2166
|
+
// ↑ ↓ move through the persona list (a radio group).
|
|
2167
|
+
root.querySelector("#ag-persona").addEventListener("keydown", (e) => {
|
|
2168
|
+
if (e.key !== "ArrowDown" && e.key !== "ArrowUp") return;
|
|
2169
|
+
const rows = [...e.currentTarget.querySelectorAll("[data-pid]")];
|
|
2170
|
+
const i = rows.indexOf(document.activeElement);
|
|
2171
|
+
const next = rows[i + (e.key === "ArrowDown" ? 1 : -1)];
|
|
2172
|
+
if (!next) return;
|
|
2173
|
+
e.preventDefault();
|
|
2174
|
+
pickPersona(next.dataset.pid);
|
|
2175
|
+
next.focus();
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
2178
|
+
|
|
1548
2179
|
/**
|
|
1549
2180
|
* Once per document: a font that loads later (the theme's --mono as a web
|
|
1550
2181
|
* font) and a theme applied without a reload (its sheet, then
|
|
@@ -1588,11 +2219,25 @@ function translateStatic(root) {
|
|
|
1588
2219
|
};
|
|
1589
2220
|
set("#ag-h", "n.agents");
|
|
1590
2221
|
set("#ag-start", "ag.start");
|
|
2222
|
+
set("#ag-bring", "ag.bring");
|
|
2223
|
+
set("#ag-conv-t", "ag.bring.conv");
|
|
1591
2224
|
set("#ag-clear", "ag.clear");
|
|
1592
|
-
set("#ag-dlg-h", "ag.
|
|
2225
|
+
set("#ag-dlg-h", "ag.new.title");
|
|
1593
2226
|
set("#ag-ws-t", "ag.ws");
|
|
1594
2227
|
set("#ag-p-t", "ag.persona");
|
|
1595
2228
|
set("#ag-r-t", "ag.runtime");
|
|
2229
|
+
set("#ag-what-b", "ag.new.what");
|
|
2230
|
+
set("#ag-newp", "ag.new.newP");
|
|
2231
|
+
set("#ag-what-p", "pe.what");
|
|
2232
|
+
set("#ag-task-t", "ag.new.task");
|
|
2233
|
+
set("#ag-name-t", "ag.new.name");
|
|
2234
|
+
set("#ag-adv-s", "ag.new.adv");
|
|
2235
|
+
set("#ag-note", "ag.new.note");
|
|
2236
|
+
const task = root.querySelector("#ag-task");
|
|
2237
|
+
if (task) task.placeholder = t("ag.new.taskPh");
|
|
2238
|
+
root
|
|
2239
|
+
.querySelector("#ag-persona")
|
|
2240
|
+
?.setAttribute("aria-label", t("ag.persona"));
|
|
1596
2241
|
set("#ag-cancel", "ag.cancel");
|
|
1597
2242
|
set("#ag-go", "ag.go");
|
|
1598
2243
|
for (const b of root.querySelectorAll("[data-layouts] button")) {
|
|
@@ -1605,8 +2250,6 @@ function translateStatic(root) {
|
|
|
1605
2250
|
root
|
|
1606
2251
|
.querySelector("[data-layouts]")
|
|
1607
2252
|
?.setAttribute("aria-label", t("ag.layout"));
|
|
1608
|
-
const h = root.querySelector("#ag-dlg-h");
|
|
1609
|
-
if (h) h.textContent = h.textContent.replace(/^\+\s*/, "");
|
|
1610
2253
|
}
|
|
1611
2254
|
|
|
1612
2255
|
/** Fill the page drawn by agents.html and wire it. */
|
|
@@ -1676,12 +2319,13 @@ export async function mountAgentsPage(root) {
|
|
|
1676
2319
|
root.querySelector("#ag-start").addEventListener("click", () => {
|
|
1677
2320
|
void openStart();
|
|
1678
2321
|
});
|
|
2322
|
+
root.querySelector("#ag-bring")?.addEventListener("click", () => {
|
|
2323
|
+
void openStart(true);
|
|
2324
|
+
});
|
|
1679
2325
|
root.querySelector("#ag-clear")?.addEventListener("click", (e) => {
|
|
1680
2326
|
void clearStopped(e.currentTarget);
|
|
1681
2327
|
});
|
|
1682
|
-
root
|
|
1683
|
-
void fillForWorkspace(e.currentTarget.value);
|
|
1684
|
-
});
|
|
2328
|
+
wireStartDialog(root);
|
|
1685
2329
|
root.querySelector("#ag-cancel").addEventListener("click", () => {
|
|
1686
2330
|
root.querySelector("#ag-dlg").close?.();
|
|
1687
2331
|
});
|
|
@@ -1693,7 +2337,7 @@ export async function mountAgentsPage(root) {
|
|
|
1693
2337
|
// cancels (the dialog's own cancel). A button keeps its own Enter.
|
|
1694
2338
|
root.querySelector("#ag-form").addEventListener("keydown", (e) => {
|
|
1695
2339
|
if (e.key !== "Enter" || e.isComposing) return;
|
|
1696
|
-
if (e.target.closest("button, textarea")) return;
|
|
2340
|
+
if (e.target.closest("button, textarea, summary")) return;
|
|
1697
2341
|
e.preventDefault();
|
|
1698
2342
|
if (!st.root.querySelector("#ag-go").disabled)
|
|
1699
2343
|
e.currentTarget.requestSubmit();
|