@monster-orange/den 0.4.1 → 0.4.3
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 +142 -0
- package/README.md +88 -158
- package/docs/distributions.md +151 -28
- package/docs/reference.md +177 -0
- package/package.json +1 -1
- package/src/shared/agents/den-claude.ts +57 -12
- package/src/shared/agents/generic.ts +1 -0
- package/src/shared/agents/runtime-search.ts +416 -0
- package/src/shared/agents/runtime.ts +26 -1
- package/src/shared/agents/runtimes.ts +30 -3
- package/src/shared/den-config.ts +88 -3
- package/src/shared/edge/render.ts +31 -0
- package/src/shared/sessions/client.ts +7 -0
- package/src/shared/sessions/den-pty.ts +10 -2
- package/src/shared/sessions/host.ts +33 -8
- package/src/shared/sessions/types.ts +8 -0
- package/src/ui/cli/agent-ops.ts +109 -21
- package/src/ui/cli/agents-command.ts +100 -4
- package/src/ui/cli/index.ts +33 -1
- package/src/ui/cli/sessions-command.ts +26 -9
- package/src/ui/cli/web-command.ts +60 -9
- package/src/ui/server/agent-routes.ts +11 -3
- package/src/ui/server/edge-exposure.ts +143 -0
- package/src/ui/server/index.ts +105 -23
- package/src/ui/server/public/agent-start.js +202 -0
- package/src/ui/server/public/agents-page.js +90 -18
- package/src/ui/server/public/agents-section.js +6 -18
- package/src/ui/server/public/agents.html +70 -0
- package/src/ui/server/public/i18n/es.js +11 -0
- package/src/ui/server/public/i18n/ja.js +11 -0
- package/src/ui/server/public/local-dev.html +43 -14
- package/src/ui/server/public/settings-registry.js +1 -1
- package/src/ui/server/public/shell.js +40 -0
- package/src/ui/server/public/topbar.js +127 -28
- package/src/ui/server/request-guard.ts +122 -7
- package/src/ui/server/ui-assets.ts +2 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Start agent form's choices (AG-10.0), shared by the Agents tab's
|
|
3
|
+
* dialog and the Workspaces page's agents section. DOM-free.
|
|
4
|
+
*
|
|
5
|
+
* A persona option shows its id only: its role sentence is the help line
|
|
6
|
+
* under the select (personaHelp), so a long role can't widen the select
|
|
7
|
+
* past the dialog. The runtime's first option names what it resolves to,
|
|
8
|
+
* "Claude Code (persona default)", from the persona picked; a runtime that
|
|
9
|
+
* can't run here says why, and one that can shows its version.
|
|
10
|
+
*
|
|
11
|
+
* AG-0.2, readiness before Start: the line under the runtime names the CLI
|
|
12
|
+
* it will run (its version and path); when that runtime can't run here,
|
|
13
|
+
* Start is disabled and the line says why and how to fix it (install it,
|
|
14
|
+
* or set agents.runtimes.<id>.bin in the user.yml it names, or run
|
|
15
|
+
* `<cli> agents doctor`). Nothing fails only after Start.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
function escHtml(s) {
|
|
19
|
+
return String(s ?? "").replace(
|
|
20
|
+
/[&<>"']/g,
|
|
21
|
+
(c) =>
|
|
22
|
+
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[
|
|
23
|
+
c
|
|
24
|
+
],
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The persona select's options: the id only. */
|
|
29
|
+
export function personaOptions(personas) {
|
|
30
|
+
return (personas ?? [])
|
|
31
|
+
.map((p) => `<option value="${escHtml(p.id)}">${escHtml(p.id)}</option>`)
|
|
32
|
+
.join("");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The help line under the persona select: the picked persona's role. */
|
|
36
|
+
export function personaHelp(personas, id) {
|
|
37
|
+
return (personas ?? []).find((p) => p.id === id)?.role ?? "";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** A runtime's name for people ("Claude Code"); its id when it has none. */
|
|
41
|
+
export function runtimeName(runtimes, id) {
|
|
42
|
+
const r = (runtimes ?? []).find((x) => x.id === id);
|
|
43
|
+
return r?.label || r?.id || id || "";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The runtime a persona runs on when the agent names none: the persona's
|
|
48
|
+
* own, else the default runtime.
|
|
49
|
+
*/
|
|
50
|
+
export function defaultRuntimeOf(personas, runtimes, personaId) {
|
|
51
|
+
const p = (personas ?? []).find((x) => x.id === personaId);
|
|
52
|
+
if (p?.runtime) return p.runtime;
|
|
53
|
+
return (runtimes ?? []).find((r) => r.default)?.id ?? "";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The runtime select's options. The first ("" = the persona's) reads
|
|
58
|
+
* "<name> (persona default)"; one that can't run here is disabled and
|
|
59
|
+
* says why.
|
|
60
|
+
*/
|
|
61
|
+
export function runtimeOptions(personas, runtimes, personaId, t) {
|
|
62
|
+
const name = runtimeName(
|
|
63
|
+
runtimes,
|
|
64
|
+
defaultRuntimeOf(personas, runtimes, personaId),
|
|
65
|
+
);
|
|
66
|
+
const first = name ? t("ag.runtime.default", { name }) : t("ag.runtime.auto");
|
|
67
|
+
return (
|
|
68
|
+
`<option value="">${escHtml(first)}</option>` +
|
|
69
|
+
(runtimes ?? [])
|
|
70
|
+
.map((r) => {
|
|
71
|
+
const label = runtimeName(runtimes, r.id);
|
|
72
|
+
return r.available
|
|
73
|
+
? `<option value="${escHtml(r.id)}">${escHtml(
|
|
74
|
+
r.version ? `${label} · ${r.version}` : label,
|
|
75
|
+
)}</option>`
|
|
76
|
+
: `<option value="${escHtml(r.id)}" disabled>${escHtml(
|
|
77
|
+
`${label} — ${r.why || t("ag.unavailable")}`,
|
|
78
|
+
)}</option>`;
|
|
79
|
+
})
|
|
80
|
+
.join("")
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The most searched directories named inline; the rest are counted. */
|
|
85
|
+
const SHOWN_DIRS = 4;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Whether Start can run, and the line that says so (AG-0.2): for the
|
|
89
|
+
* runtime picked (`runtimeId`, "" = the persona's), { ok, tone, html }.
|
|
90
|
+
* `tone` is "" (ready), "warn" (can't start) or "none" (nothing to say).
|
|
91
|
+
*/
|
|
92
|
+
export function readiness(data, personaId, runtimeId, t) {
|
|
93
|
+
const personas = data?.personas ?? [];
|
|
94
|
+
const runtimes = data?.runtimes ?? [];
|
|
95
|
+
const id = runtimeId || defaultRuntimeOf(personas, runtimes, personaId);
|
|
96
|
+
const r = runtimes.find((x) => x.id === id);
|
|
97
|
+
if (!r)
|
|
98
|
+
return {
|
|
99
|
+
ok: false,
|
|
100
|
+
tone: "warn",
|
|
101
|
+
html: escHtml(t("ag.rt.unknown", { id: id || "?" })),
|
|
102
|
+
};
|
|
103
|
+
const name = runtimeName(runtimes, r.id);
|
|
104
|
+
const mono = (x) => `<span class="mono">${escHtml(x)}</span>`;
|
|
105
|
+
if (r.available) {
|
|
106
|
+
if (!r.path) return { ok: true, tone: "none", html: "" };
|
|
107
|
+
return {
|
|
108
|
+
ok: true,
|
|
109
|
+
tone: "",
|
|
110
|
+
html:
|
|
111
|
+
escHtml(
|
|
112
|
+
r.version
|
|
113
|
+
? t("ag.rt.ready", { name, version: r.version })
|
|
114
|
+
: t("ag.rt.readyNoVer", { name }),
|
|
115
|
+
) +
|
|
116
|
+
" " +
|
|
117
|
+
mono(r.path),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const file = data?.userConfig || "config/local/user.yml";
|
|
121
|
+
const fix = t("ag.rt.fix", {
|
|
122
|
+
key: "\u0000k",
|
|
123
|
+
file: "\u0000f",
|
|
124
|
+
cmd: "\u0000c",
|
|
125
|
+
});
|
|
126
|
+
const fixHtml = escHtml(fix)
|
|
127
|
+
.replace("\u0000k", mono(r.setting || `agents.runtimes.${r.id}.bin`))
|
|
128
|
+
.replace("\u0000f", mono(file))
|
|
129
|
+
.replace("\u0000c", mono("__DEN_CLI__ agents doctor"));
|
|
130
|
+
let what;
|
|
131
|
+
if (r.source === "setting" || !r.searched)
|
|
132
|
+
what = escHtml(t("ag.rt.refused", { name, why: r.why || "" }));
|
|
133
|
+
else {
|
|
134
|
+
const dirs = r.searched;
|
|
135
|
+
const shown = dirs.slice(0, SHOWN_DIRS).map(mono).join(", ");
|
|
136
|
+
const more =
|
|
137
|
+
dirs.length > SHOWN_DIRS
|
|
138
|
+
? " " + escHtml(t("ag.rt.more", { n: dirs.length - SHOWN_DIRS }))
|
|
139
|
+
: "";
|
|
140
|
+
what =
|
|
141
|
+
escHtml(t("ag.rt.notFound", { name })) +
|
|
142
|
+
" " +
|
|
143
|
+
escHtml(t("ag.rt.searched")) +
|
|
144
|
+
" " +
|
|
145
|
+
(shown || escHtml(t("ag.rt.nowhere"))) +
|
|
146
|
+
more +
|
|
147
|
+
".";
|
|
148
|
+
}
|
|
149
|
+
return { ok: false, tone: "warn", html: `${what} ${fixHtml}` };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Fill a form's persona and runtime selects, its help line and its
|
|
154
|
+
* readiness line ([data-runtime-ready]), keep them in step when the
|
|
155
|
+
* persona or the runtime changes, and disable its submit button while
|
|
156
|
+
* the runtime can't run. Returns nothing; idempotent per form (the
|
|
157
|
+
* change listeners are bound once).
|
|
158
|
+
*/
|
|
159
|
+
export function fillStartForm(form, data, t) {
|
|
160
|
+
const personas = data?.personas ?? [];
|
|
161
|
+
const runtimes = data?.runtimes ?? [];
|
|
162
|
+
const ps = form.querySelector('select[name="persona"]');
|
|
163
|
+
const rs = form.querySelector('select[name="runtime"]');
|
|
164
|
+
const help = form.querySelector("[data-persona-help]");
|
|
165
|
+
const ready = form.querySelector("[data-runtime-ready]");
|
|
166
|
+
const go = form.querySelector('button[type="submit"]');
|
|
167
|
+
ps.innerHTML = personaOptions(personas);
|
|
168
|
+
const check = () => {
|
|
169
|
+
const r = readiness(data, ps.value, rs.value, t);
|
|
170
|
+
if (ready) {
|
|
171
|
+
ready.innerHTML = r.html;
|
|
172
|
+
ready.hidden = !r.html;
|
|
173
|
+
ready.dataset.tone = r.tone;
|
|
174
|
+
}
|
|
175
|
+
if (go) {
|
|
176
|
+
go.disabled = !r.ok;
|
|
177
|
+
go.dataset.blocked = r.ok ? "" : "runtime";
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
const sync = () => {
|
|
181
|
+
const keep = rs.value;
|
|
182
|
+
rs.innerHTML = runtimeOptions(personas, runtimes, ps.value, t);
|
|
183
|
+
if ([...rs.options].some((o) => o.value === keep && !o.disabled))
|
|
184
|
+
rs.value = keep;
|
|
185
|
+
if (help) {
|
|
186
|
+
const role = personaHelp(personas, ps.value);
|
|
187
|
+
help.textContent = role;
|
|
188
|
+
help.hidden = !role;
|
|
189
|
+
}
|
|
190
|
+
check();
|
|
191
|
+
};
|
|
192
|
+
ps.onchange = sync;
|
|
193
|
+
rs.onchange = check;
|
|
194
|
+
sync();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Whether a form's Start is held back by its runtime (readiness). */
|
|
198
|
+
export function blockedByRuntime(form) {
|
|
199
|
+
return (
|
|
200
|
+
form.querySelector('button[type="submit"]')?.dataset.blocked === "runtime"
|
|
201
|
+
);
|
|
202
|
+
}
|
|
@@ -15,8 +15,20 @@
|
|
|
15
15
|
*
|
|
16
16
|
* Nothing here keeps a session's state: the list is GET /api/sessions,
|
|
17
17
|
* asked again every few seconds while the page is shown.
|
|
18
|
+
*
|
|
19
|
+
* Start agent (AG-0.2, LK-1): the lock is checked before the dialog opens
|
|
20
|
+
* (the server, then the vault: checkUnlock). Locked, the dialog asks for
|
|
21
|
+
* the password inline at its top, and Start unlocks, then starts, without
|
|
22
|
+
* closing anything. The runtime line says what will run, or why it
|
|
23
|
+
* can't, before Start (agent-start.js readiness).
|
|
18
24
|
*/
|
|
19
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
checkUnlock,
|
|
27
|
+
onLockChange,
|
|
28
|
+
requireUnlock,
|
|
29
|
+
unlockWith,
|
|
30
|
+
} from "/topbar.js";
|
|
31
|
+
import { fillStartForm } from "/agent-start.js";
|
|
20
32
|
|
|
21
33
|
const t = (key, vars) => window.denShell?.t?.(key, vars) ?? key;
|
|
22
34
|
const esc = (s) =>
|
|
@@ -104,6 +116,12 @@ function displayName(r) {
|
|
|
104
116
|
return r.kind === "agent" ? (r.persona ?? r.label) : r.label;
|
|
105
117
|
}
|
|
106
118
|
|
|
119
|
+
// A pane's title and its Close: the agent's display name (AG-10.8), else
|
|
120
|
+
// its id, never the persona. There is no display name before AG-10.
|
|
121
|
+
function paneName(r) {
|
|
122
|
+
return r.kind === "agent" ? r.displayName || r.agent || r.label : r.label;
|
|
123
|
+
}
|
|
124
|
+
|
|
107
125
|
function rowHtml(r) {
|
|
108
126
|
const open = st.open.includes(r.id);
|
|
109
127
|
const nm =
|
|
@@ -293,7 +311,7 @@ function paintLayout() {
|
|
|
293
311
|
function paintBar(p) {
|
|
294
312
|
const r = rowOf(p.id);
|
|
295
313
|
const bar = p.el.querySelector(".bar2");
|
|
296
|
-
const name = r ?
|
|
314
|
+
const name = r ? paneName(r) : p.label || p.id;
|
|
297
315
|
const where = r
|
|
298
316
|
? [r.workspace, r.runtime].filter(Boolean).join(" · ")
|
|
299
317
|
: p.workspace || "";
|
|
@@ -471,7 +489,7 @@ function closePane(id, { quiet = false } = {}) {
|
|
|
471
489
|
|
|
472
490
|
async function stopAgent(p, button) {
|
|
473
491
|
const r = rowOf(p.id);
|
|
474
|
-
if (!r?.agent || !requireUnlock()) return;
|
|
492
|
+
if (!r?.agent || !(await requireUnlock())) return;
|
|
475
493
|
button.disabled = true;
|
|
476
494
|
try {
|
|
477
495
|
await api(
|
|
@@ -509,28 +527,67 @@ async function openStart() {
|
|
|
509
527
|
window.denShell?.errorToast?.(err);
|
|
510
528
|
return;
|
|
511
529
|
}
|
|
512
|
-
st.root.querySelector("#ag-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
)
|
|
517
|
-
.join("");
|
|
518
|
-
st.root.querySelector("#ag-runtime").innerHTML =
|
|
519
|
-
`<option value="">${esc(t("ag.runtime.auto"))}</option>` +
|
|
520
|
-
(data.runtimes ?? [])
|
|
521
|
-
.map(
|
|
522
|
-
(r) =>
|
|
523
|
-
`<option value="${esc(r.id)}"${r.available ? "" : " disabled"}>${esc(r.id)}${r.available ? "" : ` — ${esc(t("ag.unavailable"))}`}</option>`,
|
|
524
|
-
)
|
|
525
|
-
.join("");
|
|
530
|
+
fillStartForm(st.root.querySelector("#ag-form"), data, t);
|
|
531
|
+
// LK-1: the lock before the modal. Locked, its password is asked here.
|
|
532
|
+
paintInlineLock(!(await checkUnlock()));
|
|
533
|
+
if (dlg.open) return;
|
|
526
534
|
if (typeof dlg.showModal === "function") dlg.showModal();
|
|
527
535
|
else dlg.setAttribute("open", "");
|
|
536
|
+
st.root.querySelector("#ag-lock:not([hidden]) input")?.focus();
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/** Show (or hide) the dialog's inline password field (LK-1). */
|
|
540
|
+
function paintInlineLock(locked) {
|
|
541
|
+
const box = st.root?.querySelector("#ag-lock");
|
|
542
|
+
if (!box) return;
|
|
543
|
+
box.hidden = !locked;
|
|
544
|
+
const err = box.querySelector("#ag-lock-err");
|
|
545
|
+
if (!locked) {
|
|
546
|
+
box.querySelector("#ag-lock-pw").value = "";
|
|
547
|
+
err.hidden = true;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* The lock, for Start: the dialog's own field when it shows one (unlock,
|
|
553
|
+
* then go on), else the shell's rule (its popover is in the top layer).
|
|
554
|
+
*/
|
|
555
|
+
async function unlockForStart(go) {
|
|
556
|
+
const box = st.root.querySelector("#ag-lock");
|
|
557
|
+
if (box.hidden) return requireUnlock();
|
|
558
|
+
if (await checkUnlock()) {
|
|
559
|
+
paintInlineLock(false);
|
|
560
|
+
return true;
|
|
561
|
+
}
|
|
562
|
+
const input = box.querySelector("#ag-lock-pw");
|
|
563
|
+
const err = box.querySelector("#ag-lock-err");
|
|
564
|
+
if (!input.value) {
|
|
565
|
+
err.textContent = t("lock.title");
|
|
566
|
+
err.hidden = false;
|
|
567
|
+
input.focus();
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
go.disabled = true;
|
|
571
|
+
go.textContent = t("lock.checking");
|
|
572
|
+
const r = await unlockWith(input.value);
|
|
573
|
+
go.disabled = false;
|
|
574
|
+
go.textContent = t("ag.go");
|
|
575
|
+
if (r.ok) {
|
|
576
|
+
paintInlineLock(false);
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
579
|
+
err.textContent = r.error;
|
|
580
|
+
err.hidden = false;
|
|
581
|
+
input.value = "";
|
|
582
|
+
input.focus();
|
|
583
|
+
return false;
|
|
528
584
|
}
|
|
529
585
|
|
|
530
586
|
async function submitStart(form) {
|
|
531
|
-
if (!requireUnlock()) return;
|
|
532
587
|
const dlg = st.root.querySelector("#ag-dlg");
|
|
533
588
|
const go = st.root.querySelector("#ag-go");
|
|
589
|
+
if (go.disabled) return;
|
|
590
|
+
if (!(await unlockForStart(go))) return;
|
|
534
591
|
const ws = form.querySelector("#ag-ws").value || viewWorkspaces()[0];
|
|
535
592
|
const persona = form.querySelector("#ag-persona").value;
|
|
536
593
|
const runtime = form.querySelector("#ag-runtime").value;
|
|
@@ -573,6 +630,8 @@ function translateStatic(root) {
|
|
|
573
630
|
set("#ag-r-t", "ag.runtime");
|
|
574
631
|
set("#ag-cancel", "ag.cancel");
|
|
575
632
|
set("#ag-go", "ag.go");
|
|
633
|
+
set("#ag-lock-t", "lock.pw");
|
|
634
|
+
set("#ag-lock-note", "lock.title");
|
|
576
635
|
const h = root.querySelector("#ag-dlg-h");
|
|
577
636
|
if (h) h.textContent = h.textContent.replace(/^\+\s*/, "");
|
|
578
637
|
}
|
|
@@ -608,6 +667,10 @@ export async function mountAgentsPage(root) {
|
|
|
608
667
|
root.querySelector("#ag-start").addEventListener("click", () => {
|
|
609
668
|
void openStart();
|
|
610
669
|
});
|
|
670
|
+
// Unlocked elsewhere (the chip, another tab): the field goes.
|
|
671
|
+
onLockChange((unlocked) => {
|
|
672
|
+
if (unlocked) paintInlineLock(false);
|
|
673
|
+
});
|
|
611
674
|
root.querySelector("#ag-cancel").addEventListener("click", () => {
|
|
612
675
|
root.querySelector("#ag-dlg").close?.();
|
|
613
676
|
});
|
|
@@ -615,6 +678,15 @@ export async function mountAgentsPage(root) {
|
|
|
615
678
|
e.preventDefault();
|
|
616
679
|
void submitStart(e.currentTarget);
|
|
617
680
|
});
|
|
681
|
+
// Enter starts from any field (a select doesn't submit on its own); Esc
|
|
682
|
+
// cancels (the dialog's own cancel). A button keeps its own Enter.
|
|
683
|
+
root.querySelector("#ag-form").addEventListener("keydown", (e) => {
|
|
684
|
+
if (e.key !== "Enter" || e.isComposing) return;
|
|
685
|
+
if (e.target.closest("button, textarea")) return;
|
|
686
|
+
e.preventDefault();
|
|
687
|
+
if (!st.root.querySelector("#ag-go").disabled)
|
|
688
|
+
e.currentTarget.requestSubmit();
|
|
689
|
+
});
|
|
618
690
|
// The terminals follow the mode (light, dark) and the theme.
|
|
619
691
|
new MutationObserver(() => {
|
|
620
692
|
for (const p of st.panes.values())
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* (.den-sec, .den-btn, .den-dot) are reused as they are.
|
|
17
17
|
*/
|
|
18
18
|
import { requireUnlock } from "/topbar.js";
|
|
19
|
+
import { fillStartForm } from "/agent-start.js";
|
|
19
20
|
|
|
20
21
|
const t = (key, vars) => window.denShell?.t?.(key, vars) ?? key;
|
|
21
22
|
const esc = (s) =>
|
|
@@ -62,6 +63,8 @@ export function agentsSectionHtml(ws) {
|
|
|
62
63
|
<form class="agent-form" id="agent-start-form" hidden>
|
|
63
64
|
<label>${esc(t("ag.persona"))} <select class="form-sel" name="persona" required></select></label>
|
|
64
65
|
<label>${esc(t("ag.runtime"))} <select class="form-sel" name="runtime"></select></label>
|
|
66
|
+
<p class="agent-form-help" data-persona-help hidden></p>
|
|
67
|
+
<p class="agent-form-ready" data-runtime-ready role="status" hidden></p>
|
|
65
68
|
<button type="submit" class="den-btn sm primary" id="agent-start-go">${esc(t("ag.go"))}</button>
|
|
66
69
|
<button type="button" class="den-btn sm" data-agent-cancel>${esc(t("ag.cancel"))}</button>
|
|
67
70
|
</form>
|
|
@@ -185,22 +188,7 @@ async function refresh(ws) {
|
|
|
185
188
|
}
|
|
186
189
|
|
|
187
190
|
function fillForm(form, data) {
|
|
188
|
-
|
|
189
|
-
ps.innerHTML = (data.personas ?? [])
|
|
190
|
-
.map(
|
|
191
|
-
(p) =>
|
|
192
|
-
`<option value="${esc(p.id)}">${esc(p.id)}${p.role ? ` — ${esc(p.role)}` : ""}</option>`,
|
|
193
|
-
)
|
|
194
|
-
.join("");
|
|
195
|
-
const rs = form.querySelector('select[name="runtime"]');
|
|
196
|
-
rs.innerHTML =
|
|
197
|
-
`<option value="">${esc(t("ag.runtime.auto"))}</option>` +
|
|
198
|
-
(data.runtimes ?? [])
|
|
199
|
-
.map(
|
|
200
|
-
(r) =>
|
|
201
|
-
`<option value="${esc(r.id)}"${r.available ? "" : " disabled"}>${esc(r.id)}${r.available ? "" : ` — ${esc(t("ag.unavailable"))}`}</option>`,
|
|
202
|
-
)
|
|
203
|
-
.join("");
|
|
191
|
+
fillStartForm(form, data, t);
|
|
204
192
|
}
|
|
205
193
|
|
|
206
194
|
async function openForm(sec) {
|
|
@@ -229,7 +217,7 @@ async function openForm(sec) {
|
|
|
229
217
|
}
|
|
230
218
|
|
|
231
219
|
async function act(ws, verb, id, button) {
|
|
232
|
-
if (!requireUnlock()) return;
|
|
220
|
+
if (!(await requireUnlock())) return;
|
|
233
221
|
button.disabled = true;
|
|
234
222
|
try {
|
|
235
223
|
await api("POST", `${base(ws)}/${encodeURIComponent(id)}/${verb}`);
|
|
@@ -243,7 +231,7 @@ async function act(ws, verb, id, button) {
|
|
|
243
231
|
}
|
|
244
232
|
|
|
245
233
|
async function start(ws, form) {
|
|
246
|
-
if (!requireUnlock()) return;
|
|
234
|
+
if (!(await requireUnlock())) return;
|
|
247
235
|
const go = form.querySelector("#agent-start-go");
|
|
248
236
|
const persona = form.querySelector('select[name="persona"]').value;
|
|
249
237
|
const runtime = form.querySelector('select[name="runtime"]').value;
|
|
@@ -311,8 +311,56 @@
|
|
|
311
311
|
}
|
|
312
312
|
.ag-dlg form {
|
|
313
313
|
display: grid;
|
|
314
|
+
/* AG-10.0: minmax(0, 1fr), so a long option can't widen the form
|
|
315
|
+
past the dialog and push Cancel and Start out of sight. */
|
|
316
|
+
grid-template-columns: minmax(0, 1fr);
|
|
314
317
|
gap: 12px;
|
|
315
318
|
}
|
|
319
|
+
.ag-dlg .help {
|
|
320
|
+
margin: -6px 0 0;
|
|
321
|
+
font: 12.5px/1.45 var(--sans);
|
|
322
|
+
color: var(--muted);
|
|
323
|
+
overflow-wrap: anywhere;
|
|
324
|
+
}
|
|
325
|
+
.ag-dlg .help[hidden],
|
|
326
|
+
.ag-dlg .ready[hidden],
|
|
327
|
+
.ag-dlg .err[hidden],
|
|
328
|
+
.ag-dlg .ag-lock[hidden] {
|
|
329
|
+
display: none;
|
|
330
|
+
}
|
|
331
|
+
/* AG-0.2: the runtime it will run, or why it can't, before Start
|
|
332
|
+
(the mock's .helpl: 12px, faint, 1.4). */
|
|
333
|
+
.ag-dlg .ready {
|
|
334
|
+
margin: -6px 0 0;
|
|
335
|
+
font: 12px/1.4 var(--sans);
|
|
336
|
+
color: var(--faint);
|
|
337
|
+
overflow-wrap: anywhere;
|
|
338
|
+
}
|
|
339
|
+
.ag-dlg .ready[data-tone="warn"] {
|
|
340
|
+
color: var(--warn);
|
|
341
|
+
}
|
|
342
|
+
.ag-dlg .ready .mono {
|
|
343
|
+
font-family: var(--mono);
|
|
344
|
+
}
|
|
345
|
+
/* LK-1: locked, the password is asked here, at the top of the dialog,
|
|
346
|
+
and Start unlocks then starts. */
|
|
347
|
+
.ag-dlg .ag-lock {
|
|
348
|
+
display: grid;
|
|
349
|
+
gap: 6px;
|
|
350
|
+
padding: 10px 12px;
|
|
351
|
+
border: 1px solid
|
|
352
|
+
color-mix(in srgb, var(--warn) 45%, var(--border));
|
|
353
|
+
border-radius: var(--radius-button);
|
|
354
|
+
background: color-mix(in srgb, var(--warn) 8%, var(--raised));
|
|
355
|
+
}
|
|
356
|
+
.ag-dlg .ag-lock .help {
|
|
357
|
+
margin: 0;
|
|
358
|
+
}
|
|
359
|
+
.ag-dlg .err {
|
|
360
|
+
margin: 0;
|
|
361
|
+
font: 12.5px/1.45 var(--sans);
|
|
362
|
+
color: var(--bad);
|
|
363
|
+
}
|
|
316
364
|
.ag-dlg h2 {
|
|
317
365
|
margin: 0;
|
|
318
366
|
font: 600 17px var(--display);
|
|
@@ -325,7 +373,10 @@
|
|
|
325
373
|
text-transform: uppercase;
|
|
326
374
|
letter-spacing: 0.07em;
|
|
327
375
|
}
|
|
376
|
+
.ag-dlg input[type="password"],
|
|
328
377
|
.ag-dlg select {
|
|
378
|
+
width: 100%;
|
|
379
|
+
min-width: 0;
|
|
329
380
|
font: 13px var(--sans);
|
|
330
381
|
text-transform: none;
|
|
331
382
|
letter-spacing: 0;
|
|
@@ -396,6 +447,17 @@
|
|
|
396
447
|
<dialog class="ag-dlg" id="ag-dlg" aria-labelledby="ag-dlg-h">
|
|
397
448
|
<form method="dialog" id="ag-form">
|
|
398
449
|
<h2 id="ag-dlg-h">Start an agent</h2>
|
|
450
|
+
<div class="ag-lock" id="ag-lock" hidden>
|
|
451
|
+
<label
|
|
452
|
+
><span id="ag-lock-t">Admin password</span>
|
|
453
|
+
<input
|
|
454
|
+
type="password"
|
|
455
|
+
id="ag-lock-pw"
|
|
456
|
+
autocomplete="current-password"
|
|
457
|
+
/></label>
|
|
458
|
+
<p class="help" id="ag-lock-note"></p>
|
|
459
|
+
<p class="err" id="ag-lock-err" role="alert" hidden></p>
|
|
460
|
+
</div>
|
|
399
461
|
<label id="ag-ws-l"
|
|
400
462
|
><span id="ag-ws-t">Workspace</span>
|
|
401
463
|
<select name="ws" id="ag-ws"></select
|
|
@@ -404,10 +466,18 @@
|
|
|
404
466
|
><span id="ag-p-t">Persona</span>
|
|
405
467
|
<select name="persona" id="ag-persona" required></select
|
|
406
468
|
></label>
|
|
469
|
+
<p class="help" id="ag-p-help" data-persona-help hidden></p>
|
|
407
470
|
<label
|
|
408
471
|
><span id="ag-r-t">Runtime</span>
|
|
409
472
|
<select name="runtime" id="ag-runtime"></select
|
|
410
473
|
></label>
|
|
474
|
+
<p
|
|
475
|
+
class="ready"
|
|
476
|
+
id="ag-r-ready"
|
|
477
|
+
data-runtime-ready
|
|
478
|
+
role="status"
|
|
479
|
+
hidden
|
|
480
|
+
></p>
|
|
411
481
|
<div class="btns">
|
|
412
482
|
<button type="button" class="den-btn" id="ag-cancel">Cancel</button>
|
|
413
483
|
<button type="submit" class="den-btn primary" id="ag-go">
|
|
@@ -330,6 +330,7 @@
|
|
|
330
330
|
"ag.persona": "Persona",
|
|
331
331
|
"ag.runtime": "Runtime",
|
|
332
332
|
"ag.runtime.auto": "La de la persona",
|
|
333
|
+
"ag.runtime.default": "{name} (la de la persona)",
|
|
333
334
|
"ag.unavailable": "no disponible",
|
|
334
335
|
"ag.go": "Iniciar",
|
|
335
336
|
"ag.starting": "Iniciando…",
|
|
@@ -372,5 +373,15 @@
|
|
|
372
373
|
"ag.palStart": "Iniciar agente…",
|
|
373
374
|
"ag.cap":
|
|
374
375
|
"Hasta 4 paneles: se cerró el de {id} para hacer sitio (sigue en marcha)",
|
|
376
|
+
"ag.rt.ready": "Inicia {name} {version} desde",
|
|
377
|
+
"ag.rt.readyNoVer": "Inicia {name} desde",
|
|
378
|
+
"ag.rt.notFound": "No se encontró {name}.",
|
|
379
|
+
"ag.rt.searched": "Se buscó en:",
|
|
380
|
+
"ag.rt.more": "y {n} más",
|
|
381
|
+
"ag.rt.nowhere": "ningún directorio",
|
|
382
|
+
"ag.rt.refused": "{name} no puede iniciarse: {why}.",
|
|
383
|
+
"ag.rt.fix":
|
|
384
|
+
"Instálalo, o pon su ruta en {key} en {file}, o ejecuta {cmd}.",
|
|
385
|
+
"ag.rt.unknown": "Aquí no hay ningún runtime {id}.",
|
|
375
386
|
};
|
|
376
387
|
})((window.denMessages = window.denMessages || {}));
|
|
@@ -318,6 +318,7 @@
|
|
|
318
318
|
"ag.persona": "ペルソナ",
|
|
319
319
|
"ag.runtime": "ランタイム",
|
|
320
320
|
"ag.runtime.auto": "ペルソナの既定",
|
|
321
|
+
"ag.runtime.default": "{name}(ペルソナの既定)",
|
|
321
322
|
"ag.unavailable": "利用不可",
|
|
322
323
|
"ag.go": "開始",
|
|
323
324
|
"ag.starting": "開始中…",
|
|
@@ -357,5 +358,15 @@
|
|
|
357
358
|
"ag.palStart": "エージェントを開始…",
|
|
358
359
|
"ag.cap":
|
|
359
360
|
"ペインは最大 4 つです。{id} のペインを閉じました(実行は続きます)",
|
|
361
|
+
"ag.rt.ready": "{name} {version} を起動:",
|
|
362
|
+
"ag.rt.readyNoVer": "{name} を起動:",
|
|
363
|
+
"ag.rt.notFound": "{name} が見つかりません。",
|
|
364
|
+
"ag.rt.searched": "検索した場所:",
|
|
365
|
+
"ag.rt.more": "ほか {n} 件",
|
|
366
|
+
"ag.rt.nowhere": "なし",
|
|
367
|
+
"ag.rt.refused": "{name} を起動できません: {why}。",
|
|
368
|
+
"ag.rt.fix":
|
|
369
|
+
"インストールするか、{file} の {key} にパスを設定するか、{cmd} を実行してください。",
|
|
370
|
+
"ag.rt.unknown": "ランタイム {id} はここにありません。",
|
|
360
371
|
};
|
|
361
372
|
})((window.denMessages = window.denMessages || {}));
|